/ docker-process.el
docker-process.el
1 ;;; docker-process.el --- Docker process -*- lexical-binding: t -*- 2 3 ;; Author: Philippe Vaucher <philippe.vaucher@gmail.com> 4 5 ;; This file is NOT part of GNU Emacs. 6 7 ;; This program is free software; you can redistribute it and/or modify 8 ;; it under the terms of the GNU General Public License as published by 9 ;; the Free Software Foundation; either version 3, or (at your option) 10 ;; any later version. 11 ;; 12 ;; This program is distributed in the hope that it will be useful, 13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 ;; GNU General Public License for more details. 16 ;; 17 ;; You should have received a copy of the GNU General Public License 18 ;; along with GNU Emacs; see the file COPYING. If not, write to the 19 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 20 ;; Boston, MA 02110-1301, USA. 21 22 ;;; Commentary: 23 24 ;;; Code: 25 (eval-when-compile 26 (setq-local byte-compile-warnings '(not docstrings))) 27 28 (require 's) 29 (require 'aio) 30 (require 'dash) 31 32 (require 'docker-group) 33 (require 'docker-utils) 34 35 (defcustom docker-run-as-root nil 36 "Run docker as root." 37 :group 'docker 38 :type 'boolean) 39 40 (defcustom docker-show-messages t 41 "If non-nil `message' docker commands which are run." 42 :group 'docker 43 :type 'boolean) 44 45 (defcustom docker-terminal-backend 'auto 46 "Terminal backend used for commands that need a live buffer. 47 When set to `auto', prefer eat, then vterm, then shell." 48 :group 'docker 49 :type '(choice (const :tag "Auto (eat > vterm > shell)" auto) 50 (const :tag "Eat" eat) 51 (const :tag "Vterm" vterm) 52 (const :tag "Shell" shell))) 53 54 (defcustom docker-run-async-with-buffer-function nil 55 "Obsolete; use `docker-terminal-backend' instead." 56 :group 'docker 57 :type 'symbol) 58 59 (make-obsolete-variable 'docker-run-async-with-buffer-function 'docker-terminal-backend "2.5.0") 60 61 62 (defmacro docker-with-sudo (&rest body) 63 "Ensure `default-directory' is set correctly according to `docker-run-as-root' then execute BODY." 64 (declare (indent defun)) 65 `(let ((default-directory (if (and docker-run-as-root (not (file-remote-p default-directory))) 66 "/sudo::" 67 default-directory))) 68 ,@body)) 69 70 (defun docker-run-start-file-process-shell-command (program &rest args) 71 "Execute \"PROGRAM ARGS\" and return the process." 72 (docker-with-sudo 73 (let* ((process-args (-remove 's-blank? (-flatten args))) 74 (command (s-join " " (-insert-at 0 program process-args)))) 75 (when docker-show-messages (message "Running: %s" command)) 76 (start-file-process-shell-command command (apply #'docker-utils-generate-new-buffer-name program process-args) command)))) 77 78 (defun docker-run-async (program &rest args) 79 "Execute \"PROGRAM ARGS\" and return a promise with the results." 80 (let* ((process (apply #'docker-run-start-file-process-shell-command program args)) 81 (promise (aio-promise))) 82 (set-process-query-on-exit-flag process nil) 83 (set-process-sentinel process (-partial #'docker-process-sentinel promise)) 84 promise)) 85 86 (defun docker-run-async-with-buffer (program interactive &rest args) 87 "Execute \"PROGRAM ARGS\" and display output in a new buffer. 88 INTERACTIVE selects an interactive terminal buffer when non-nil. 89 Prefer `docker-run-async-with-buffer-interactive' or 90 `docker-run-async-with-buffer-noninteractive'." 91 (if docker-run-async-with-buffer-function 92 (apply docker-run-async-with-buffer-function program interactive args) 93 (apply #'docker-run-async-with-buffer-dispatch 94 (docker--terminal-backend) 95 program interactive args))) 96 97 (defun docker-run-async-with-buffer-interactive (program &rest args) 98 "Execute \"PROGRAM ARGS\" and display output in an interactive buffer." 99 (apply #'docker-run-async-with-buffer program t args)) 100 101 (defun docker-run-async-with-buffer-noninteractive (program &rest args) 102 "Execute \"PROGRAM ARGS\" and display output in a non-interactive buffer." 103 (apply #'docker-run-async-with-buffer program nil args)) 104 105 (defun docker--terminal-backend-available-p (backend) 106 "Return non-nil when BACKEND is available." 107 (pcase backend 108 ('eat (fboundp 'eat-other-window)) 109 ('vterm (fboundp 'vterm-other-window)) 110 ('shell t) 111 (_ nil))) 112 113 (defun docker--terminal-backend () 114 "Return the selected backend symbol." 115 (pcase docker-terminal-backend 116 ('auto (cond 117 ((docker--terminal-backend-available-p 'eat) 'eat) 118 ((docker--terminal-backend-available-p 'vterm) 'vterm) 119 (t 'shell))) 120 (_ docker-terminal-backend))) 121 122 (defun docker-run-async-with-buffer-dispatch (backend program interactive &rest args) 123 "Dispatch PROGRAM to BACKEND and display output in a new buffer." 124 (pcase backend 125 ('eat (if (docker--terminal-backend-available-p 'eat) 126 (apply #'docker-run-async-with-buffer-eat program interactive args) 127 (error "The eat package is not installed"))) 128 ('vterm (if (docker--terminal-backend-available-p 'vterm) 129 (apply #'docker-run-async-with-buffer-vterm program interactive args) 130 (error "The vterm package is not installed"))) 131 ('shell (apply #'docker-run-async-with-buffer-shell program interactive args)) 132 (_ (error "Unsupported docker terminal backend: %s" backend)))) 133 134 (defun docker-run-async-with-buffer-shell (program &optional interactive &rest args) 135 "Execute \"PROGRAM ARGS\" and display output in a new buffer. 136 If INTERACTIVE is non-nil, use a `shell' buffer for interactive use. 137 Otherwise, use a non-interactive buffer with ANSI color support." 138 (let* ((process (apply #'docker-run-start-file-process-shell-command program args)) 139 (buffer (process-buffer process))) 140 (set-process-query-on-exit-flag process nil) 141 (if interactive 142 (with-current-buffer buffer (shell-mode)) 143 (with-current-buffer buffer (special-mode))) 144 (set-process-filter process 145 (if interactive 146 'comint-output-filter 147 'docker-process-filter-noninteractive)) 148 (switch-to-buffer-other-window buffer))) 149 150 (defun docker-run-async-with-buffer-vterm (program &optional interactive &rest args) 151 "Execute \"PROGRAM ARGS\" and display output in a new `vterm' buffer. 152 If INTERACTIVE is nil, fall back to shell mode since vterm is interactive." 153 (if (not interactive) 154 ;; vterm is interactive only, fall back to shell for non-interactive output 155 (apply #'docker-run-async-with-buffer-shell program nil args) 156 (defvar vterm-kill-buffer-on-exit) 157 (defvar vterm-shell) 158 (if (fboundp 'vterm-other-window) 159 (let* ((process-args (-remove 's-blank? (-flatten args))) 160 (vterm-shell (s-join " " (-insert-at 0 program process-args))) 161 (vterm-kill-buffer-on-exit nil)) 162 (vterm-other-window 163 (apply #'docker-utils-generate-new-buffer-name program process-args))) 164 (error "The vterm package is not installed")))) 165 166 (defun docker-run-async-with-buffer-eat (program &optional interactive &rest args) 167 "Execute \"PROGRAM ARGS\" and display output in a new `eat' buffer. 168 If INTERACTIVE is nil, fall back to shell mode since eat is interactive." 169 (if (not interactive) 170 (apply #'docker-run-async-with-buffer-shell program nil args) 171 (defvar eat-buffer-name) 172 (if (fboundp 'eat-other-window) 173 (let* ((process-args (-remove 's-blank? (-flatten args))) 174 (command (s-join " " (-insert-at 0 program process-args))) 175 (eat-buffer-name (apply #'docker-utils-generate-new-buffer-name 176 program process-args))) 177 (eat-other-window command)) 178 (error "The eat package is not installed")))) 179 180 (defun docker-process-filter-noninteractive (proc string) 181 "Process filter for non-interactive streaming buffers. 182 Strips carriage returns and applies ANSI color codes." 183 (when (buffer-live-p (process-buffer proc)) 184 (with-current-buffer (process-buffer proc) 185 (let ((inhibit-read-only t) 186 (moving (= (point) (process-mark proc)))) 187 (save-excursion 188 (goto-char (process-mark proc)) 189 (insert (ansi-color-apply (replace-regexp-in-string "\r" "" string))) 190 (set-marker (process-mark proc) (point))) 191 (when moving (goto-char (process-mark proc))))))) 192 193 (defun docker-process-sentinel (promise process event) 194 "Sentinel that resolves the PROMISE using PROCESS and EVENT." 195 (when (memq (process-status process) '(exit signal)) 196 (setq event (substring event 0 -1)) 197 (if (not (string-equal event "finished")) 198 (aio-resolve promise 199 (lambda () 200 (error "Error running: \"%s\" (%s)" (process-name process) event))) 201 (aio-resolve promise 202 (lambda () 203 (when docker-show-messages 204 (message "Finished: %s" (process-name process))) 205 (run-with-timer 2 nil (lambda () (message nil))) 206 (with-current-buffer (process-buffer process) 207 (prog1 (buffer-substring-no-properties (point-min) (point-max)) 208 (kill-buffer)))))))) 209 210 (provide 'docker-process) 211 212 ;;; docker-process.el ends here