;;; dockerfile-mode.el --- Major mode for editing Docker's Dockerfiles ;; Copyright (c) 2013 Spotify AB ;; ;; Licensed under the Apache License, Version 2.0 (the "License"); you may not ;; use this file except in compliance with the License. You may obtain a copy of ;; the License at ;; ;; http://www.apache.org/licenses/LICENSE-2.0 ;; ;; Unless required by applicable law or agreed to in writing, software ;; distributed under the License is distributed on an "AS IS" BASIS, WITHOUT ;; WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the ;; License for the specific language governing permissions and limitations under ;; the License. ;;; Code: (require 'sh-script) (require 'rx) (declare-function cygwin-convert-file-name-to-windows "cygw32.c" (file &optional absolute-p)) (defvar docker-image-name nil) (defgroup dockerfile nil "dockerfile code editing commands for Emacs." :link '(custom-group-link :tag "Font Lock Faces group" font-lock-faces) :prefix "dockerfile-" :group 'languages) (defcustom dockerfile-mode-hook nil "*Hook called by `dockerfile-mode'." :type 'hook :group 'dockerfile) (defcustom dockerfile-use-sudo nil "Runs docker builder command with sudo.") (defcustom dockerfile-build-args nil "List of --build-arg to pass to docker build. Each element of the list will be passed as a separate --build-arg to the docker build command." :type '(repeat string) :group 'dockerfile) (defvar dockerfile-font-lock-keywords `(,(cons (rx (or line-start "onbuild ") (group (or "from" "maintainer" "run" "cmd" "expose" "env" "arg" "add" "copy" "entrypoint" "volume" "user" "workdir" "onbuild" "label" "stopsignal")) word-boundary) font-lock-keyword-face) ,@(sh-font-lock-keywords) ,@(sh-font-lock-keywords-2) ,@(sh-font-lock-keywords-1)) "Default font-lock-keywords for `dockerfile mode'.") (defvar dockerfile-mode-map (let ((map (make-sparse-keymap)) (menu-map (make-sparse-keymap))) (define-key map "\C-c\C-b" 'dockerfile-build-buffer) (define-key map "\C-c\M-b" 'dockerfile-build-no-cache-buffer) (define-key map "\C-c\C-z" 'dockerfile-test-function) (define-key map "\C-c\C-c" 'comment-region) (define-key map [menu-bar dockerfile-mode] (cons "Dockerfile" menu-map)) (define-key menu-map [dfc] '(menu-item "Comment Region" comment-region :help "Comment Region")) (define-key menu-map [dfb] '(menu-item "Build" dockerfile-build-buffer :help "Send the Dockerfile to docker build")) (define-key menu-map [dfb] '(menu-item "Build without cache" dockerfile-build-no-cache-buffer :help "Send the Dockerfile to docker build without cache")) map)) (defvar dockerfile-mode-syntax-table (let ((table (make-syntax-table))) (modify-syntax-entry ?# "<" table) (modify-syntax-entry ?\n ">" table) (modify-syntax-entry ?' "\"" table) table) "Syntax table for `dockerfile-mode'.") (define-abbrev-table 'dockerfile-mode-abbrev-table nil "Abbrev table used while in `dockerfile-mode'.") (unless dockerfile-mode-abbrev-table (define-abbrev-table 'dockerfile-mode-abbrev-table ())) (defun dockerfile-build-arg-string () "Create a --build-arg string for each element in `dockerfile-build-args'." (mapconcat (lambda (arg) (concat "--build-arg " "\"" arg "\"")) dockerfile-build-args " ")) (defun standard-filename (file) "Convert the file name to OS standard. If in Cygwin environment, uses Cygwin specific function to convert the file name. Otherwise, uses Emacs' standard conversion function." (format "%s" (if (fboundp 'cygwin-convert-file-name-to-windows) (s-replace "\\" "\\\\" (cygwin-convert-file-name-to-windows file)) (convert-standard-filename file)))) ;;;###autoload (defun dockerfile-build-buffer (image-name) "Build an image based upon the buffer" (interactive (if (null docker-image-name) (list (read-string "image-name: " nil nil)) (list docker-image-name))) (save-buffer) (if (stringp image-name) (async-shell-command (format "%sdocker build -t %s %s -f \"%s\" \"%s\"" (if dockerfile-use-sudo "sudo " "") image-name (dockerfile-build-arg-string) (standard-filename (buffer-file-name)) (standard-filename (file-name-directory (buffer-file-name)))) "*docker-build-output*") (print "docker-image-name must be a string, consider surrounding it with double quotes"))) ;;;###autoload (defun dockerfile-build-no-cache-buffer (image-name) "Build an image based upon the buffer without cache" (interactive (if (null docker-image-name) (list (read-string "image-name: " nil nil)) (list docker-image-name))) (save-buffer) (if (stringp image-name) (async-shell-command (format "%s docker build --no-cache -t %s %s -f \"%s\" \"%s\"" (if dockerfile-use-sudo "sudo" "") image-name (dockerfile-build-arg-string) (standard-filename (buffer-file-name)) (standard-filename (file-name-directory (buffer-file-name)))) "*docker-build-output*") (print "docker-image-name must be a string, consider surrounding it with double quotes"))) ;; Handle emacs < 24, which does not have prog-mode (defalias 'dockerfile-parent-mode (if (fboundp 'prog-mode) 'prog-mode 'fundamental-mode)) ;;;###autoload (define-derived-mode dockerfile-mode dockerfile-parent-mode "Dockerfile" "A major mode to edit Dockerfiles. \\{dockerfile-mode-map} " (set-syntax-table dockerfile-mode-syntax-table) (set (make-local-variable 'require-final-newline) mode-require-final-newline) (set (make-local-variable 'comment-start) "#") (set (make-local-variable 'comment-end) "") (set (make-local-variable 'comment-start-skip) "#+ *") (set (make-local-variable 'parse-sexp-ignore-comments) t) (set (make-local-variable 'font-lock-defaults) '(dockerfile-font-lock-keywords nil t)) (setq local-abbrev-table dockerfile-mode-abbrev-table)) ;;;###autoload (add-to-list 'auto-mode-alist '("Dockerfile.*\\'" . dockerfile-mode)) (provide 'dockerfile-mode) ;;; dockerfile-mode.el ends here