dockerfile-mode.el (11099B)
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-build-extra-options nil 76 "Extra command-line options to send to docker build. 77 78 Use this variable to add custom command-line switches not covered by 79 existing dockerfile-build-* variables. 80 81 Example: 82 (setq-default dockerfile-build-extra-options \"--network host\")" 83 :group 'dockerfile 84 :type 'string) 85 86 (defcustom dockerfile-use-buildkit nil 87 "Use Docker buildkit for building images? 88 89 This is the new buildsystem for docker, and in time it will replace the old one 90 but for now it has to be explicitly enabled to work. 91 It is supported from docker 18.09" 92 :type 'boolean) 93 94 (defcustom dockerfile-enable-auto-indent t 95 "Toggles the auto indentation functionality." 96 :type 'boolean) 97 98 (defcustom dockerfile-indent-offset (or standard-indent 2) 99 "Dockerfile number of columns for margin-changing functions to indent." 100 :type 'integer 101 :safe #'integerp 102 :group 'dockerfile) 103 104 (defface dockerfile-image-name 105 '((t (:inherit (font-lock-type-face bold)))) 106 "Face to highlight the base image name after FROM instruction.") 107 108 (defface dockerfile-image-alias 109 '((t (:inherit (font-lock-constant-face bold)))) 110 "Face to highlight the base image alias inf FROM ... AS <alias> construct.") 111 112 (defconst dockerfile--from-regex 113 (rx "from " (group (+? nonl)) (or " " eol) (? "as " (group (1+ nonl))))) 114 115 (defvar dockerfile-font-lock-keywords 116 `(,(cons (rx (or line-start "onbuild ") 117 (group (or "from" "maintainer" "run" "cmd" "expose" "env" "arg" 118 "add" "copy" "entrypoint" "volume" "user" "workdir" "onbuild" 119 "label" "stopsignal" "shell" "healthcheck")) 120 word-boundary) 121 font-lock-keyword-face) 122 (,dockerfile--from-regex 123 (1 'dockerfile-image-name) 124 (2 'dockerfile-image-alias nil t)) 125 ,@(sh-font-lock-keywords) 126 ,@(sh-font-lock-keywords-2) 127 ,@(sh-font-lock-keywords-1)) 128 "Default `font-lock-keywords' for `dockerfile mode'.") 129 130 (defvar dockerfile-mode-map 131 (let ((map (make-sparse-keymap)) 132 (menu-map (make-sparse-keymap))) 133 (define-key map "\C-c\C-b" #'dockerfile-build-buffer) 134 (define-key map "\C-c\M-b" #'dockerfile-build-no-cache-buffer) 135 (define-key map "\C-c\C-c" #'comment-region) 136 (define-key map [menu-bar dockerfile-mode] (cons "Dockerfile" menu-map)) 137 (define-key menu-map [dfc] 138 '(menu-item "Comment Region" comment-region 139 :help "Comment Region")) 140 (define-key menu-map [dfb] 141 '(menu-item "Build" dockerfile-build-buffer 142 :help "Send the Dockerfile to docker build")) 143 (define-key menu-map [dfb] 144 '(menu-item "Build without cache" dockerfile-build-no-cache-buffer 145 :help "Send the Dockerfile to docker build without cache")) 146 map)) 147 148 (defvar dockerfile-mode-syntax-table 149 (let ((table (make-syntax-table))) 150 (modify-syntax-entry ?# "<" table) 151 (modify-syntax-entry ?\n ">" table) 152 (modify-syntax-entry ?' "\"" table) 153 (modify-syntax-entry ?= "." table) 154 table) 155 "Syntax table for `dockerfile-mode'.") 156 157 (define-abbrev-table 'dockerfile-mode-abbrev-table nil 158 "Abbrev table used while in `dockerfile-mode'.") 159 160 (unless dockerfile-mode-abbrev-table 161 (define-abbrev-table 'dockerfile-mode-abbrev-table ())) 162 163 (defun dockerfile-indent-line-function () 164 "Indent lines in a Dockerfile. 165 166 Lines beginning with a keyword are ignored, and any others are 167 indented by one `dockerfile-indent-offset'. Functionality toggled 168 by `dockerfile-enable-auto-indent'." 169 (when dockerfile-enable-auto-indent 170 (unless (member (get-text-property (line-beginning-position) 'face) 171 '(font-lock-comment-delimiter-face font-lock-keyword-face)) 172 (save-excursion 173 (beginning-of-line) 174 (unless (looking-at-p "\\s-*$") ; Ignore empty lines. 175 (indent-line-to dockerfile-indent-offset)))))) 176 177 (defun dockerfile-build-arg-string () 178 "Create a --build-arg string for each element in `dockerfile-build-args'." 179 (mapconcat (lambda (arg) (concat "--build-arg=" (replace-regexp-in-string "\\\\=" "=" (shell-quote-argument arg)))) 180 dockerfile-build-args " ")) 181 182 (defun dockerfile-standard-filename (file) 183 "Convert the FILE name to OS standard. 184 If in Cygwin environment, uses Cygwin specific function to convert the 185 file name. Otherwise, uses Emacs' standard conversion function." 186 (if (fboundp 'cygwin-convert-file-name-to-windows) 187 (replace-regexp-in-string 188 (rx "\\") "\\\\" (cygwin-convert-file-name-to-windows file) t t) 189 (convert-standard-filename file))) 190 191 (defun dockerfile-tag-string (image-name) 192 "Return a --tag shell-quoted IMAGE-NAME string. 193 194 Returns an empty string if IMAGE-NAME is blank." 195 (if (string= image-name "") "" (format "--tag %s " (shell-quote-argument image-name)))) 196 197 (define-obsolete-variable-alias 'docker-image-name 'dockerfile-image-name "2017-10-22") 198 199 (defvar dockerfile-image-name nil 200 "Name of the dockerfile currently being used. 201 This can be set in file or directory-local variables.") 202 203 (defvar dockerfile-image-name-history nil 204 "History of image names read by `dockerfile-read-image-name'.") 205 206 (defun dockerfile-read-image-name () 207 "Read a docker image name." 208 (read-string "Image name: " dockerfile-image-name 'dockerfile-image-name-history)) 209 210 211 ;;;###autoload 212 (defun dockerfile-build-buffer (image-name &optional no-cache) 213 "Build an image called IMAGE-NAME based upon the buffer. 214 215 If the prefix arg NO-CACHE is set, don't cache the image. 216 217 The shell command used to build the image is: 218 219 sudo docker build \\ 220 --no-cache \\ 221 --force-rm \\ 222 --pull \\ 223 --tag IMAGE-NAME \\ 224 --build-args args \\ 225 --progress type \\ 226 -f filename \\ 227 directory" 228 229 (interactive (list (dockerfile-read-image-name) prefix-arg)) 230 (save-buffer) 231 (compilation-start 232 (format 233 "%s%s%s build %s %s %s %s %s --progress %s %s -f %s %s" 234 (if dockerfile-use-buildkit "DOCKER_BUILDKIT=1 " "") 235 (if dockerfile-use-sudo "sudo " "") 236 dockerfile-mode-command 237 (if no-cache "--no-cache" "") 238 (if dockerfile-build-force-rm "--force-rm " "") 239 (if dockerfile-build-pull "--pull " "") 240 (dockerfile-tag-string image-name) 241 (dockerfile-build-arg-string) 242 dockerfile-build-progress 243 (or dockerfile-build-extra-options "") 244 (shell-quote-argument (dockerfile-standard-filename 245 (or (file-remote-p (buffer-file-name) 'localname) 246 (buffer-file-name)))) 247 (shell-quote-argument (dockerfile-standard-filename 248 (or (file-remote-p default-directory 'localname) 249 default-directory)))) 250 nil 251 (lambda (_) (format "*docker-build-output: %s *" image-name)))) 252 253 ;;;###autoload 254 (defun dockerfile-build-no-cache-buffer (image-name) 255 "Build an image called IMAGE-NAME based upon the buffer without cache." 256 (interactive (list (dockerfile-read-image-name))) 257 (dockerfile-build-buffer image-name t)) 258 259 (defun dockerfile--imenu-function () 260 "Find the previous headline from point. 261 262 Search for a FROM instruction. If an alias is used this is 263 returned, otherwise the base image name is used." 264 (when (re-search-backward dockerfile--from-regex nil t) 265 (let ((data (match-data))) 266 (when (match-string 2) 267 ;; we drop the first match group because 268 ;; imenu-generic-expression can only use one offset, so we 269 ;; normalize to `1'. 270 (set-match-data (list (nth 0 data) (nth 1 data) (nth 4 data) (nth 5 data)))) 271 t))) 272 273 ;;;###autoload 274 (define-derived-mode dockerfile-mode prog-mode "Dockerfile" 275 "A major mode to edit Dockerfiles. 276 \\{dockerfile-mode-map}" 277 (set-syntax-table dockerfile-mode-syntax-table) 278 (set (make-local-variable 'imenu-generic-expression) 279 `(("Stage" dockerfile--imenu-function 1))) 280 (set (make-local-variable 'require-final-newline) mode-require-final-newline) 281 (set (make-local-variable 'comment-start) "#") 282 (set (make-local-variable 'comment-end) "") 283 (set (make-local-variable 'comment-start-skip) "#+ *") 284 (set (make-local-variable 'parse-sexp-ignore-comments) t) 285 (set (make-local-variable 'font-lock-defaults) 286 '(dockerfile-font-lock-keywords nil t)) 287 (setq local-abbrev-table dockerfile-mode-abbrev-table) 288 (set (make-local-variable 'indent-line-function) #'dockerfile-indent-line-function)) 289 290 ;;;###autoload 291 (add-to-list 'auto-mode-alist 292 (cons (concat "[/\\]" 293 "\\(?:Containerfile\\|Dockerfile\\)" 294 "\\(?:\\.[^/\\]*\\)?\\'") 295 'dockerfile-mode)) 296 297 ;;;###autoload 298 (add-to-list 'auto-mode-alist '("\\.dockerfile\\'" . dockerfile-mode)) 299 300 (provide 'dockerfile-mode) 301 302 ;;; dockerfile-mode.el ends here