/ docker-utils.el
docker-utils.el
1 ;;; docker-utils.el --- Random utilities -*- 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 (require 'json) 32 (require 'tramp) 33 (require 'tablist) 34 (require 'transient) 35 36 (defun docker-utils-get-marked-items-ids () 37 "Get the id part of `tablist-get-marked-items'." 38 (-map #'car (tablist-get-marked-items))) 39 40 (defun docker-utils-ensure-items () 41 "Ensure at least one item is selected." 42 (when (null (docker-utils-get-marked-items-ids)) 43 (user-error "This action cannot be used in an empty list"))) 44 45 (defun docker-utils-generate-new-buffer-name (program &rest args) 46 "Wrapper around `generate-new-buffer-name' using PROGRAM and ARGS." 47 (generate-new-buffer-name (format "* %s %s *" program (s-join " " args)))) 48 49 (defun docker-utils-generate-new-buffer (program &rest args) 50 "Wrapper around `generate-new-buffer' using PROGRAM and ARGS." 51 (generate-new-buffer (apply #'docker-utils-generate-new-buffer-name program args))) 52 53 (defmacro docker-utils-with-buffer (name &rest body) 54 "Wrapper around `with-current-buffer'. 55 Execute BODY in a buffer named with the help of NAME." 56 (declare (indent defun)) 57 `(with-current-buffer (docker-utils-generate-new-buffer "docker" ,name) 58 (setq buffer-read-only nil) 59 (erase-buffer) 60 ,@body 61 (setq buffer-read-only t) 62 (goto-char (point-min)) 63 (pop-to-buffer (current-buffer)))) 64 65 (defmacro docker-utils-transient-define-prefix (name arglist &rest args) 66 "Wrapper around `transient-define-prefix' forwarding NAME, ARGLIST and ARGS and calling `docker-utils-ensure-items'." 67 `(transient-define-prefix ,name ,arglist 68 ,@args 69 (interactive) 70 (docker-utils-ensure-items) 71 (transient-setup ',name))) 72 73 (defmacro docker-utils-define-transient-arguments (name) 74 "Define the transient arguments function using NAME that return the latest transient value or its default." 75 `(defun ,(intern (format "%s-arguments" name)) () 76 ,(format "Return the latest used arguments in the `%s' transient." name) 77 (let ((history (alist-get ',name transient-history)) 78 (default (transient-default-value (get ',name 'transient--prefix)))) 79 (if (equal 0 (length history)) 80 (car default) 81 (car history))))) 82 83 (defmacro docker-utils-refresh-entries (promise) 84 "Update the current buffer with the results of PROMISE." 85 `(let ((buffer (current-buffer)) 86 (entries (aio-await ,promise))) 87 (with-current-buffer buffer 88 (setq tabulated-list-entries entries) 89 (tabulated-list-print t)))) 90 91 (defvar docker-pop-to-buffer-action nil 92 "Action to use internally when `docker-utils-pop-to-buffer' calls `pop-to-buffer'.") 93 94 (defun docker-utils-pop-to-buffer (name) 95 "Like `pop-to-buffer', but suffix NAME with the host if on a remote host." 96 (pop-to-buffer 97 (if (file-remote-p default-directory) 98 (with-parsed-tramp-file-name default-directory nil (concat name " - " host)) 99 name) 100 docker-pop-to-buffer-action)) 101 102 (defun docker-utils-unit-multiplier (str) 103 "Return the correct multiplier for STR." 104 (let* ((unit (or str "B")) 105 (idx (-elem-index (upcase unit) '("B" "KB" "MB" "GB" "TB" "PB" "EB")))) 106 (expt 1024 (or idx 0)))) 107 108 (defun docker-utils-human-size-to-bytes (str) 109 "Parse STR and return size in bytes." 110 (let* ((parts (s-match "^\\([0-9\\.]+\\)\\([A-Za-z]+\\)?$" str))) 111 (unless parts 112 (error "Unexpected size format: %s" str)) 113 (let* ((value (string-to-number (-second-item parts))) 114 (multiplier (docker-utils-unit-multiplier (-third-item parts)))) 115 (* value multiplier)))) 116 117 (defun docker-utils-human-size-predicate (a b) 118 "Sort A and B by image size." 119 (< (docker-utils-human-size-to-bytes a) (docker-utils-human-size-to-bytes b))) 120 121 (defun docker-utils-columns-list-format (columns-spec) 122 "Convert COLUMNS-SPEC (a list of plists) to 'tabulated-list-format', i.e. a vector of (name width sort-fn)." 123 (apply 'vector 124 (--map-indexed 125 (-let* (((&plist :name name :width width :sort sort-fn-inner) it) 126 (sort-fn (if sort-fn-inner 127 (let ((idx it-index)) ;; Rebind the closure var! 128 ;; Sort fn is called with (id [entries..]) 129 ;; Extract the column value and pass to inner function 130 (-on sort-fn-inner (lambda (x) (elt (cadr x) idx)))) 131 t))) 132 (list name width sort-fn)) 133 columns-spec))) 134 135 (defun docker-utils-make-format-string (id-template column-spec) 136 "Make the format string to pass to docker-ls commands. 137 138 ID-TEMPLATE is the Go template used to extract the property that 139 identifies the object (usually its id). 140 COLUMN-SPEC is the value of docker-X-columns." 141 (let* ((templates (--map (plist-get it :template) column-spec)) 142 (delimited (string-join templates ","))) 143 (format "[%s,%s]" id-template delimited))) 144 145 (defun docker-utils-parse (column-specs line) 146 "Convert a LINE from \"docker ls\" to a `tabulated-list-entries' entry. 147 148 LINE is expected to be a JSON formatted array, and COLUMN-SPECS is the relevant 149 defcustom (e.g. `docker-image-columns`) used to apply any custom format functions." 150 (condition-case nil 151 (let* ((data (json-read-from-string line))) 152 ;; apply format function, if any 153 (--each-indexed 154 column-specs 155 (let ((fmt-fn (plist-get it :format)) 156 (data-index (+ it-index 1))) 157 (when fmt-fn (aset data data-index (apply fmt-fn (list (aref data data-index))))))) 158 159 (list (aref data 0) (seq-drop data 1))) 160 (json-readtable-error 161 (error "Could not read following string as json:\n%s" line)))) 162 163 (defun docker-utils-columns-setter (sym new-value) 164 "Convert NEW-VALUE into a list of plists, then assign to SYM. 165 166 If NEW-VALUE already looks like a list of plists, no conversion is performed and 167 NEW-VALUE is assigned to SYM unchanged. This is expected to be used as the 168 value of :set in a defcustom." 169 (let ((is-plist (plist-member (car new-value) :name)) 170 (new-value-plist (--map 171 (-interleave '(:name :width :template :sort :format) it) 172 new-value))) 173 (set sym (if is-plist new-value new-value-plist)))) 174 175 (defun docker-utils-columns-getter (sym) 176 "Convert the value of SYM for displaying in the customization menu. 177 178 Just strips the plist symbols and returns only values. 179 This has no effect on the actual value of the variable." 180 (--map 181 (-map (-partial #'plist-get it) '(:name :width :template :sort :format)) 182 (symbol-value sym))) 183 184 (defun docker-utils-package-p (package) 185 "Check if PACKAGE is available." 186 (or (featurep package) 187 (ignore-errors (require package)))) 188 189 (provide 'docker-utils) 190 191 ;;; docker-utils.el ends here