dockerfile-mode.el (10720B)
1 ;;; dockerfile-mode.el --- Major mode for editing Docker's Dockerfiles -*- lexical-binding: t -*- 2 3 ;; Copyright (c) 2013 Spotify AB 4 ;; Package-Requires: ((emacs "24")) 5 ;; Homepage: https://github.com/spotify/dockerfile-mode 6 ;; URL: https://github.com/spotify/dockerfile-mode 7 ;; Version: 1.7 8 ;; Keywords: docker languages processes tools 9 ;; 10 ;; Licensed under the Apache License, Version 2.0 (the "License"); you may not 11 ;; use this file except in compliance with the License. You may obtain a copy of 12 ;; the License at 13 ;; 14 ;; http://www.apache.org/licenses/LICENSE-2.0 15 ;; 16 ;; Unless required by applicable law or agreed to in writing, software 17 ;; distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 18 ;; WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 19 ;; License for the specific language governing permissions and limitations under 20 ;; the License. 21 22 ;;; Commentary: 23 24 ;; Provides a major mode `dockerfile-mode' for use with the standard 25 ;; `Dockerfile' file format. Additional convenience functions allow 26 ;; images to be built easily. 27 28 ;;; Code: 29 30 (require 'sh-script) 31 (require 'rx) 32 33 34 (declare-function cygwin-convert-file-name-to-windows "cygw32.c" (file &optional absolute-p)) 35 36 (defgroup dockerfile nil 37 "Dockerfile editing commands for Emacs." 38 :link '(custom-group-link :tag "Font Lock Faces group" font-lock-faces) 39 :prefix "dockerfile-" 40 :group 'languages) 41 42 (defcustom dockerfile-mode-command "docker" 43 "Which binary to use to build images." 44 :group 'dockerfile 45 :type 'string) 46 47 (defcustom dockerfile-use-sudo nil 48 "Runs docker builder command with sudo." 49 :type 'boolean 50 :group 'dockerfile) 51 52 (defcustom dockerfile-build-force-rm nil 53 "Runs docker builder command with --force-rm switch." 54 :type 'boolean 55 :group 'dockerfile) 56 57 (defcustom dockerfile-build-pull nil 58 "Runs docker builder command with --pull switch." 59 :type 'boolean 60 :group 'dockerfile) 61 62 (defcustom dockerfile-build-args nil 63 "List of --build-arg to pass to docker build. 64 65 Each element of the list will be passed as a separate 66 --build-arg to the docker build command." 67 :type '(repeat string) 68 :group 'dockerfile) 69 70 (defcustom dockerfile-build-progress "auto" 71 "Type of --progress output (auto, plain, tty) of docker build." 72 :group 'dockerfile 73 :type 'string) 74 75 (defcustom dockerfile-use-buildkit nil 76 "Use Docker buildkit for building images? 77 78 This is the new buildsystem for docker, and in time it will replace the old one 79 but for now it has to be explicitly enabled to work. 80 It is supported from docker 18.09" 81 :type 'boolean) 82 83 (defcustom dockerfile-enable-auto-indent t 84 "Toggles the auto indentation functionality." 85 :type 'boolean) 86 87 (defcustom dockerfile-indent-offset (or standard-indent 2) 88 "Dockerfile number of columns for margin-changing functions to indent." 89 :type 'integer 90 :safe #'integerp 91 :group 'dockerfile) 92 93 (defface dockerfile-image-name 94 '((t (:inherit (font-lock-type-face bold)))) 95 "Face to highlight the base image name after FROM instruction.") 96 97 (defface dockerfile-image-alias 98 '((t (:inherit (font-lock-constant-face bold)))) 99 "Face to highlight the base image alias inf FROM ... AS <alias> construct.") 100 101 (defconst dockerfile--from-regex 102 (rx "from " (group (+? nonl)) (or " " eol) (? "as " (group (1+ nonl))))) 103 104 (defvar dockerfile-font-lock-keywords 105 `(,(cons (rx (or line-start "onbuild ") 106 (group (or "from" "maintainer" "run" "cmd" "expose" "env" "arg" 107 "add" "copy" "entrypoint" "volume" "user" "workdir" "onbuild" 108 "label" "stopsignal" "shell" "healthcheck")) 109 word-boundary) 110 font-lock-keyword-face) 111 (,dockerfile--from-regex 112 (1 'dockerfile-image-name) 113 (2 'dockerfile-image-alias nil t)) 114 ,@(sh-font-lock-keywords) 115 ,@(sh-font-lock-keywords-2) 116 ,@(sh-font-lock-keywords-1)) 117 "Default `font-lock-keywords' for `dockerfile mode'.") 118 119 (defvar dockerfile-mode-map 120 (let ((map (make-sparse-keymap)) 121 (menu-map (make-sparse-keymap))) 122 (define-key map "\C-c\C-b" #'dockerfile-build-buffer) 123 (define-key map "\C-c\M-b" #'dockerfile-build-no-cache-buffer) 124 (define-key map "\C-c\C-c" #'comment-region) 125 (define-key map [menu-bar dockerfile-mode] (cons "Dockerfile" menu-map)) 126 (define-key menu-map [dfc] 127 '(menu-item "Comment Region" comment-region 128 :help "Comment Region")) 129 (define-key menu-map [dfb] 130 '(menu-item "Build" dockerfile-build-buffer 131 :help "Send the Dockerfile to docker build")) 132 (define-key menu-map [dfb] 133 '(menu-item "Build without cache" dockerfile-build-no-cache-buffer 134 :help "Send the Dockerfile to docker build without cache")) 135 map)) 136 137 (defvar dockerfile-mode-syntax-table 138 (let ((table (make-syntax-table))) 139 (modify-syntax-entry ?# "<" table) 140 (modify-syntax-entry ?\n ">" table) 141 (modify-syntax-entry ?' "\"" table) 142 (modify-syntax-entry ?= "." table) 143 table) 144 "Syntax table for `dockerfile-mode'.") 145 146 (define-abbrev-table 'dockerfile-mode-abbrev-table nil 147 "Abbrev table used while in `dockerfile-mode'.") 148 149 (unless dockerfile-mode-abbrev-table 150 (define-abbrev-table 'dockerfile-mode-abbrev-table ())) 151 152 (defun dockerfile-indent-line-function () 153 "Indent lines in a Dockerfile. 154 155 Lines beginning with a keyword are ignored, and any others are 156 indented by one `dockerfile-indent-offset'. Functionality toggled 157 by `dockerfile-enable-auto-indent'." 158 (when dockerfile-enable-auto-indent 159 (unless (member (get-text-property (line-beginning-position) 'face) 160 '(font-lock-comment-delimiter-face font-lock-keyword-face)) 161 (save-excursion 162 (beginning-of-line) 163 (unless (looking-at-p "\\s-*$") ; Ignore empty lines. 164 (indent-line-to dockerfile-indent-offset)))))) 165 166 (defun dockerfile-build-arg-string () 167 "Create a --build-arg string for each element in `dockerfile-build-args'." 168 (mapconcat (lambda (arg) (concat "--build-arg=" (replace-regexp-in-string "\\\\=" "=" (shell-quote-argument arg)))) 169 dockerfile-build-args " ")) 170 171 (defun dockerfile-standard-filename (file) 172 "Convert the FILE name to OS standard. 173 If in Cygwin environment, uses Cygwin specific function to convert the 174 file name. Otherwise, uses Emacs' standard conversion function." 175 (if (fboundp 'cygwin-convert-file-name-to-windows) 176 (replace-regexp-in-string 177 (rx "\\") "\\\\" (cygwin-convert-file-name-to-windows file) t t) 178 (convert-standard-filename file))) 179 180 (defun dockerfile-tag-string (image-name) 181 "Return a --tag shell-quoted IMAGE-NAME string. 182 183 Returns an empty string if IMAGE-NAME is blank." 184 (if (string= image-name "") "" (format "--tag %s " (shell-quote-argument image-name)))) 185 186 (define-obsolete-variable-alias 'docker-image-name 'dockerfile-image-name "2017-10-22") 187 188 (defvar dockerfile-image-name nil 189 "Name of the dockerfile currently being used. 190 This can be set in file or directory-local variables.") 191 192 (defvar dockerfile-image-name-history nil 193 "History of image names read by `dockerfile-read-image-name'.") 194 195 (defun dockerfile-read-image-name () 196 "Read a docker image name." 197 (read-string "Image name: " dockerfile-image-name 'dockerfile-image-name-history)) 198 199 200 ;;;###autoload 201 (defun dockerfile-build-buffer (image-name &optional no-cache) 202 "Build an image called IMAGE-NAME based upon the buffer. 203 204 If the prefix arg NO-CACHE is set, don't cache the image. 205 206 The shell command used to build the image is: 207 208 sudo docker build \\ 209 --no-cache \\ 210 --force-rm \\ 211 --pull \\ 212 --tag IMAGE-NAME \\ 213 --build-args args \\ 214 --progress type \\ 215 -f filename \\ 216 directory" 217 218 (interactive (list (dockerfile-read-image-name) prefix-arg)) 219 (save-buffer) 220 (compilation-start 221 (format 222 "%s%s%s build %s %s %s %s %s --progress %s -f %s %s" 223 (if dockerfile-use-buildkit "DOCKER_BUILDKIT=1 " "") 224 (if dockerfile-use-sudo "sudo " "") 225 dockerfile-mode-command 226 (if no-cache "--no-cache" "") 227 (if dockerfile-build-force-rm "--force-rm " "") 228 (if dockerfile-build-pull "--pull " "") 229 (dockerfile-tag-string image-name) 230 (dockerfile-build-arg-string) 231 dockerfile-build-progress 232 (shell-quote-argument (dockerfile-standard-filename 233 (or (file-remote-p (buffer-file-name) 'localname) 234 (buffer-file-name)))) 235 (shell-quote-argument (dockerfile-standard-filename 236 (or (file-remote-p default-directory 'localname) 237 default-directory)))) 238 nil 239 (lambda (_) (format "*docker-build-output: %s *" image-name)))) 240 241 ;;;###autoload 242 (defun dockerfile-build-no-cache-buffer (image-name) 243 "Build an image called IMAGE-NAME based upon the buffer without cache." 244 (interactive (list (dockerfile-read-image-name))) 245 (dockerfile-build-buffer image-name t)) 246 247 (defun dockerfile--imenu-function () 248 "Find the previous headline from point. 249 250 Search for a FROM instruction. If an alias is used this is 251 returned, otherwise the base image name is used." 252 (when (re-search-backward dockerfile--from-regex nil t) 253 (let ((data (match-data))) 254 (when (match-string 2) 255 ;; we drop the first match group because 256 ;; imenu-generic-expression can only use one offset, so we 257 ;; normalize to `1'. 258 (set-match-data (list (nth 0 data) (nth 1 data) (nth 4 data) (nth 5 data)))) 259 t))) 260 261 ;;;###autoload 262 (define-derived-mode dockerfile-mode prog-mode "Dockerfile" 263 "A major mode to edit Dockerfiles. 264 \\{dockerfile-mode-map}" 265 (set-syntax-table dockerfile-mode-syntax-table) 266 (set (make-local-variable 'imenu-generic-expression) 267 `(("Stage" dockerfile--imenu-function 1))) 268 (set (make-local-variable 'require-final-newline) mode-require-final-newline) 269 (set (make-local-variable 'comment-start) "#") 270 (set (make-local-variable 'comment-end) "") 271 (set (make-local-variable 'comment-start-skip) "#+ *") 272 (set (make-local-variable 'parse-sexp-ignore-comments) t) 273 (set (make-local-variable 'font-lock-defaults) 274 '(dockerfile-font-lock-keywords nil t)) 275 (setq local-abbrev-table dockerfile-mode-abbrev-table) 276 (set (make-local-variable 'indent-line-function) #'dockerfile-indent-line-function)) 277 278 ;;;###autoload 279 (add-to-list 'auto-mode-alist 280 (cons (concat "[/\\]" 281 "\\(?:Containerfile\\|Dockerfile\\)" 282 "\\(?:\\.[^/\\]*\\)?\\'") 283 'dockerfile-mode)) 284 285 ;;;###autoload 286 (add-to-list 'auto-mode-alist '("\\.dockerfile\\'" . dockerfile-mode)) 287 288 (provide 'dockerfile-mode) 289 290 ;;; dockerfile-mode.el ends here