]> git.eshelyaron.com Git - emacs.git/commitdiff
Move command line completion code from kubed.el to new file cobra.el
authorEshel Yaron <me@eshelyaron.com>
Thu, 25 Jul 2024 06:32:52 +0000 (08:32 +0200)
committerEshel Yaron <me@eshelyaron.com>
Thu, 25 Jul 2024 06:32:52 +0000 (08:32 +0200)
lisp/minibuffer.el
lisp/net/cobra.el [new file with mode: 0644]
lisp/net/kubed.el

index ad8d0f12ea9082b822601e1d27e70321caca1be5..59d1c96015b384629ef55e8cc2d51975015f9f08 100644 (file)
@@ -1171,7 +1171,7 @@ styles for specific categories, such as files, buffers, etc."
   '((buffer (styles basic substring)
             (export-function . minibuffer-export-list-buffers))
     (file (export-function . minibuffer-export-dired))
-    (kubed-command-line (styles basic))
+    (cobra-command-line (styles basic))
     (unicode-name (styles basic substring))
     ;; A new style that combines substring and pcm might be better,
     ;; e.g. one that does not anchor to bos.
diff --git a/lisp/net/cobra.el b/lisp/net/cobra.el
new file mode 100644 (file)
index 0000000..1c5d5ec
--- /dev/null
@@ -0,0 +1,149 @@
+;;; cobra.el --- Complete Cobra command lines   -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2024  Eshel Yaron
+
+;; Author: Eshel Yaron <me@eshelyaron.com>
+;; Keywords: tools
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Cobra is a popular Golang framework for CLI programs.  This library
+;; defines function `cobra-read-command-line', which helps you read a
+;; command line for a program that uses Cobra, with completion.
+;; Prominent examples of Cobra programs are `kubectl' and `docker'.
+
+;;; Code:
+
+(defvar cobra--cache nil)
+
+(defun cobra-completion-table (executable s p a)
+  "Completion table for command lines that invoke EXECUTABLE.
+
+Perform completion action A on string S with predicate P."
+  (let ((start 0))
+    (while (string-match "[[:space:]=]" s start)
+      (setq start (match-end 0)))
+    (if (eq a 'metadata)
+        `(metadata
+          (category . cobra-command-line)
+          (affixation-function
+           . ,(lambda (cands)
+                (let ((max (seq-max
+                            (cons 0 (mapcar #'string-width cands)))))
+                  (mapcar
+                   (lambda (cand)
+                     (list
+                      cand ""
+                      (if-let
+                          ((desc (get-text-property
+                                  0 'cobra-argument-description
+                                  cand)))
+                          (concat
+                           (make-string (1+ (- max (string-width cand))) ?\s)
+                           (propertize desc 'face 'completions-annotations))
+                        "")))
+                   cands)))))
+      (let* ((lines
+              (cdr
+               (if (string= s (car cobra--cache))
+                   ;; Cache hit.
+                   cobra--cache
+                 (setq
+                  cobra--cache
+                  (cons s
+                        (apply #'process-lines-ignore-status
+                               executable "__complete"
+                               (let ((args (cdr (split-string-and-unquote s))))
+                                 (if (string-suffix-p " " s)
+                                     ;; Restore omitted empty argument.
+                                     (nconc args '(""))
+                                   args))))))))
+             (code nil)
+             (comps (seq-take-while
+                     (lambda (line)
+                       (not (and (string-match "^:\\([0-9]+\\)$" line)
+                                 (setq code (string-to-number
+                                             (match-string 1 line))))))
+                     lines)))
+        ;; `code' encodes "completion directives", as follows:
+        ;; #b000001: An error occurred, ignore completions.
+        ;; #b000010: Don't add space after completion.
+        ;; #b000100: Don't fall back to file completion.
+        ;; #b001000: Completions are really file extension filters.
+        ;; #b010000: Complete directory names.
+        ;; #b100000: Preserve completions order.
+        (when (and code (zerop (logand 1 code)))
+          ;; Error bit in unset, proceed.
+          (if (= #b100 (logand #b100 code))
+              ;; No file name completion.
+              (if (eq (car-safe a) 'boundaries)
+                  `(boundaries
+                    ,start . ,(and (string-match "[[:space:]=]" (cdr a))
+                                   (match-beginning 0)))
+                (let ((table
+                       (mapcar
+                        ;; Annotate completion candidates.
+                        (lambda (comp)
+                          (pcase (split-string comp "\t" t)
+                            (`(,c ,d . ,_)
+                             (propertize
+                              c 'cobra-argument-description
+                              ;; Only keep first sentence.
+                              (car (split-string d "\\." t))))
+                            (`(,c . ,_)  c)))
+                        comps)))
+                  (if a (complete-with-action a table (substring s start) p)
+                    ;; `try-completion'.
+                    (let ((comp (complete-with-action a table (substring s start) p)))
+                      (if (stringp comp) (concat (substring s 0 start) comp) comp)))))
+            ;; File name completion.
+            (setq p
+                  (cond
+                   ((= #b1000 (logand #b1000 code))
+                    ;; `comps' are valid extensions.
+                    (lambda (f)
+                      (or (file-directory-p f)
+                          (when (string-match "\\.[^.]*\\'" f)
+                            (member (substring f (1+ (match-beginning 0)))
+                                    comps)))))
+                   ((= #b10000 (logand #b10000 code))
+                    ;; Directory name completion.
+                    #'file-directory-p)))
+            (if (eq (car-safe a) 'boundaries)
+                ;; Find nested boundaries.
+                (let* ((suf (cdr a))
+                       (bounds (completion-boundaries
+                                (substring s start) #'completion-file-name-table p
+                                (substring suf 0 (string-match "[[:space:]=]" suf)))))
+                  `(boundaries ,(+ (car bounds) start) . ,(cdr bounds)))
+              (if a (complete-with-action a #'completion-file-name-table
+                                          (substring s start) p)
+                (let ((comp (complete-with-action a #'completion-file-name-table
+                                                  (substring s start) p)))
+                  (if (stringp comp) (concat (substring s 0 start) comp) comp))))))))))
+
+;;;###autoload
+(defun cobra-read-command-line (prompt initial &optional hist)
+  "Prompt with PROMPT for a command line starting with INITIAL.
+
+Optional argument HIST is the name of the history list variable to use,
+if it is nil or omitted, it defaults to `shell-command-history'."
+  (let ((exec (car (split-string-and-unquote initial))))
+    (completing-read prompt (apply-partially #'cobra-completion-table exec)
+                     nil nil initial (or hist 'shell-command-history))))
+
+(provide 'cobra)
+;;; cobra.el ends here
index f90c481c1fc8ec81bc4e7b034ef1c93041b0385e..1e5aef557c0e53ef3f65cdeb388da78e97277456 100644 (file)
@@ -1272,112 +1272,6 @@ Optional argument DEFAULT is the minibuffer default argument."
    (format-prompt prompt default) nil nil nil nil
    'kubed-container-images-history default))
 
-(defvar kubed-command-line-completion-cache nil)
-
-(defun kubed-command-line-completion-table (s p a)
-  "Completion table for `kubectl' command lines.
-
-Perform completion action A on string S with predicate P."
-  (let ((start 0))
-    (while (string-match "[[:space:]=]" s start)
-      (setq start (match-end 0)))
-    (if (eq a 'metadata)
-        `(metadata
-          (category . kubed-command-line)
-          (affixation-function
-           . ,(lambda (cands)
-                (let ((max (seq-max
-                            (cons 0 (mapcar #'string-width cands)))))
-                  (mapcar
-                   (lambda (cand)
-                     (list
-                      cand ""
-                      (if-let
-                          ((desc (get-text-property
-                                  0 'kubed-command-line-argument-description
-                                  cand)))
-                          (concat
-                           (make-string (1+ (- max (string-width cand))) ?\s)
-                           (propertize desc 'face 'completions-annotations))
-                        "")))
-                   cands)))))
-      (let* ((lines
-              (cdr
-               (if (string= s (car kubed-command-line-completion-cache))
-                   kubed-command-line-completion-cache
-                 (setq
-                  kubed-command-line-completion-cache
-                  (cons s
-                        (apply #'process-lines-ignore-status
-                               kubed-kubectl-executable "__complete"
-                               (let ((args (cdr (split-string-and-unquote s))))
-                                 (if (string-suffix-p " " s)
-                                     (nconc args '(""))
-                                   args))))))))
-             (code nil)
-             (comps (seq-take-while
-                     (lambda (line)
-                       (not (and (string-match "^:\\([0-9]+\\)$" line)
-                                 (setq code (string-to-number
-                                             (match-string 1 line))))))
-                     lines)))
-        ;; `code' encodes "completion directives", as follows:
-        ;; #b000001: An error occurred, ignore completions.
-        ;; #b000010: Don't add space after completion.
-        ;; #b000100: Don't fall back to file completion.
-        ;; #b001000: Completions are really file extension filters.
-        ;; #b010000: Complete directory names.
-        ;; #b100000: Preserve completions order.
-        (when (and code (zerop (logand 1 code)))
-          ;; Error bit in unset, proceed.
-          (if (= #b100 (logand #b100 code))
-              ;; No file name completion.
-              (if (eq (car-safe a) 'boundaries)
-                  `(boundaries
-                    ,start . ,(and (string-match "[[:space:]=]" (cdr a))
-                                   (match-beginning 0)))
-                (let ((table
-                       (mapcar
-                        ;; Annotate completion candidates.
-                        (lambda (comp)
-                          (pcase (split-string comp "\t" t)
-                            (`(,c ,d . ,_)
-                             (propertize
-                              c 'kubed-command-line-argument-description
-                              ;; Only keep first sentence.
-                              (car (split-string d "\\." t))))
-                            (`(,c . ,_)  c)))
-                        comps)))
-                  (if a (complete-with-action a table (substring s start) p)
-                    ;; `try-completion'.
-                    (let ((comp (complete-with-action a table (substring s start) p)))
-                      (if (stringp comp) (concat (substring s 0 start) comp) comp)))))
-            ;; File name completion.
-            (setq p
-                  (cond
-                   ((= #b1000 (logand #b1000 code))
-                    ;; `comps' are valid extensions.
-                    (lambda (f)
-                      (or (file-directory-p f)
-                          (when (string-match "\\.[^.]*\\'" f)
-                            (member (substring f (1+ (match-beginning 0)))
-                                    comps)))))
-                   ((= #b10000 (logand #b10000 code))
-                    ;; Directory name completion.
-                    #'file-directory-p)))
-            (if (eq (car-safe a) 'boundaries)
-                ;; Find nested boundaries.
-                (let* ((suf (cdr a))
-                       (bounds (completion-boundaries
-                                (substring s start) #'completion-file-name-table p
-                                (substring suf 0 (string-match "[[:space:]=]" suf)))))
-                  `(boundaries ,(+ (car bounds) start) . ,(cdr bounds)))
-              (if a (complete-with-action a #'completion-file-name-table
-                                          (substring s start) p)
-                (let ((comp (complete-with-action a #'completion-file-name-table
-                                                  (substring s start) p)))
-                  (if (stringp comp) (concat (substring s 0 start) comp) comp))))))))))
-
 (defvar kubed-kubectl-command-history nil
   "Minibuffer history for `kubed-kubectl-command'.")
 
@@ -1389,9 +1283,8 @@ This function calls `shell-command' (which see) to do the work.
 
 Interactively, prompt for COMMAND with completion for `kubectl' arguments."
   (interactive
-   (list (completing-read "Command: " #'kubed-command-line-completion-table
-                          nil nil (concat kubed-kubectl-executable " ")
-                          'kubed-kubectl-command-history)))
+   (list (cobra-read-command-line "Command: " "kubectl "
+                                  'kubed-kubectl-command-history)))
   (shell-command command))
 
 ;;;###autoload (autoload 'kubed-prefix-map "kubed" nil t 'keymap)