1 ;;; guix-command.el --- Popup interface for guix commands -*- lexical-binding: t -*-
3 ;; Copyright © 2015 Alex Kost <alezost@gmail.com>
5 ;; This file is part of GNU Guix.
7 ;; GNU Guix 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 of the License, or
10 ;; (at your option) any later version.
12 ;; GNU Guix 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.
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
22 ;; This file provides a magit-like popup interface for running guix
23 ;; commands in Guix REPL. The entry point is "M-x guix". When it is
24 ;; called the first time, "guix --help" output is parsed and
25 ;; `guix-COMMAND-action' functions are generated for each available guix
26 ;; COMMAND. Then a window with these commands is popped up. When a
27 ;; particular COMMAND is called, "guix COMMAND --help" output is parsed,
28 ;; and a user get a new popup window with available options for this
31 ;; To avoid hard-coding all guix options, actions, etc., as much data is
32 ;; taken from "guix ... --help" outputs as possible. But this data is
33 ;; still incomplete: not all long options have short analogs, also
34 ;; special readers should be used for some options (for example, to
35 ;; complete package names while prompting for a package). So after
36 ;; parsing --help output, the arguments are "improved". All arguments
37 ;; (switches, options and actions) are `guix-command-argument'
40 ;; Only "M-x guix" command is available after this file is loaded. The
41 ;; rest commands/actions/popups are generated on the fly only when they
42 ;; are needed (that's why there is a couple of `eval'-s in this file).
44 ;; COMMANDS argument is used by many functions in this file. It means a
45 ;; list of guix commands without "guix" itself, e.g.: ("build"),
46 ;; ("import" "gnu"). The empty list stands for the plain "guix" without
49 ;; All actions in popup windows are divided into 2 groups:
51 ;; - 'Popup' actions - used to pop up another window. For example, every
52 ;; action in the 'guix' or 'guix import' window is a popup action. They
53 ;; are defined by `guix-command-define-popup-action' macro.
55 ;; - 'Execute' actions - used to do something with the command line (to
56 ;; run a command in Guix REPL or to copy it into kill-ring) constructed
57 ;; with the current popup. They are defined by
58 ;; `guix-command-define-execute-action' macro.
65 (require 'guix-help-vars
)
69 (require 'guix-external
)
71 (defgroup guix-commands nil
72 "Settings for guix popup windows."
75 (defvar guix-command-complex-with-shared-arguments
77 "List of guix commands which have subcommands with shared options.
78 I.e., 'guix foo --help' is the same as 'guix foo bar --help'.")
80 (defun guix-command-action-name (&optional commands
&rest name-parts
)
81 "Return name of action function for guix COMMANDS."
82 (guix-command-symbol (append commands name-parts
(list "action"))))
87 (cl-defstruct (guix-command-argument
88 (:constructor guix-command-make-argument
)
89 (:copier guix-command-copy-argument
))
90 name char doc fun switch? option? action?
)
92 (cl-defun guix-command-modify-argument
94 (name nil name-bound?
)
95 (char nil char-bound?
)
98 (switch? nil switch?-bound?
)
99 (option? nil option?-bound?
)
100 (action? nil action?-bound?
))
101 "Return a modified version of ARGUMENT."
103 (let ((copy (guix-command-copy-argument argument
)))
104 (and name-bound?
(setf (guix-command-argument-name copy
) name
))
105 (and char-bound?
(setf (guix-command-argument-char copy
) char
))
106 (and doc-bound?
(setf (guix-command-argument-doc copy
) doc
))
107 (and fun-bound?
(setf (guix-command-argument-fun copy
) fun
))
108 (and switch?-bound?
(setf (guix-command-argument-switch? copy
) switch?
))
109 (and option?-bound?
(setf (guix-command-argument-option? copy
) option?
))
110 (and action?-bound?
(setf (guix-command-argument-action? copy
) action?
))
113 (defun guix-command-modify-argument-from-alist (argument alist
)
114 "Return a modified version of ARGUMENT or nil if it wasn't modified.
115 Each assoc from ALIST have a form (NAME . PLIST). NAME is an
116 argument name. PLIST is a property list of argument parameters
118 (let* ((name (guix-command-argument-name argument
))
119 (plist (guix-assoc-value alist name
)))
121 (apply #'guix-command-modify-argument
124 (defmacro guix-command-define-argument-improver
(name alist
)
125 "Define NAME variable and function to modify an argument from ALIST."
128 (defvar ,name
,alist
)
129 (defun ,name
(argument)
130 (guix-command-modify-argument-from-alist argument
,name
))))
132 (guix-command-define-argument-improver
133 guix-command-improve-action-argument
134 '(("container" :char ?C
)
136 ("environment" :char ?E
)
141 (guix-command-define-argument-improver
142 guix-command-improve-common-argument
143 '(("--help" :switch? nil
)
144 ("--version" :switch? nil
)))
146 (guix-command-define-argument-improver
147 guix-command-improve-target-argument
148 '(("--target" :char ?T
)))
150 (guix-command-define-argument-improver
151 guix-command-improve-system-type-argument
152 '(("--system" :fun guix-read-system-type
)))
154 (guix-command-define-argument-improver
155 guix-command-improve-load-path-argument
156 '(("--load-path" :fun read-directory-name
)))
158 (guix-command-define-argument-improver
159 guix-command-improve-search-paths-argument
160 '(("--search-paths" :char ?P
)))
162 (guix-command-define-argument-improver
163 guix-command-improve-substitute-urls-argument
164 '(("--substitute-urls" :char ?U
)))
166 (guix-command-define-argument-improver
167 guix-command-improve-hash-argument
168 '(("--format" :fun guix-read-hash-format
)))
170 (guix-command-define-argument-improver
171 guix-command-improve-key-policy-argument
172 '(("--key-download" :fun guix-read-key-policy
)))
174 (defvar guix-command-improve-common-build-argument
175 '(("--no-substitutes" :char ?s
)
176 ("--no-build-hook" :char ?h
)
177 ("--max-silent-time" :char ?x
)))
179 (defun guix-command-improve-common-build-argument (argument)
180 (guix-command-modify-argument-from-alist
182 (append guix-command-improve-load-path-argument
183 guix-command-improve-substitute-urls-argument
184 guix-command-improve-common-build-argument
)))
186 (guix-command-define-argument-improver
187 guix-command-improve-archive-argument
188 '(("--generate-key" :char ?k
)))
190 (guix-command-define-argument-improver
191 guix-command-improve-build-argument
192 '(("--no-grafts" :char ?g
)
193 ("--root" :fun guix-read-file-name
)
194 ("--sources" :char ?S
:fun guix-read-source-type
:switch? nil
)
195 ("--with-source" :fun guix-read-file-name
)))
197 (guix-command-define-argument-improver
198 guix-command-improve-environment-argument
199 '(("--exec" :fun read-shell-command
)
200 ("--load" :fun guix-read-file-name
)))
202 (guix-command-define-argument-improver
203 guix-command-improve-gc-argument
204 '(("--list-dead" :char ?D
)
205 ("--list-live" :char ?L
)
206 ("--referrers" :char ?f
)
207 ("--verify" :fun guix-read-verify-options-string
)))
209 (guix-command-define-argument-improver
210 guix-command-improve-graph-argument
211 '(("--type" :fun guix-read-graph-type
)))
213 (guix-command-define-argument-improver
214 guix-command-improve-import-argument
215 '(("cran" :char ?r
)))
217 (guix-command-define-argument-improver
218 guix-command-improve-import-elpa-argument
219 '(("--archive" :fun guix-read-elpa-archive
)))
221 (guix-command-define-argument-improver
222 guix-command-improve-lint-argument
223 '(("--checkers" :fun guix-read-lint-checker-names-string
)))
225 (guix-command-define-argument-improver
226 guix-command-improve-package-argument
227 ;; Unlike all other options, --install/--remove do not have a form
228 ;; '--install=foo,bar' but '--install foo bar' instead, so we need
231 :name
"--install " :fun guix-read-package-names-string
232 :switch? nil
:option? t
)
234 :name
"--remove " :fun guix-read-package-names-string
235 :switch? nil
:option? t
)
236 ("--install-from-file" :fun guix-read-file-name
)
237 ("--manifest" :fun guix-read-file-name
)
238 ("--do-not-upgrade" :char ?U
)
239 ("--roll-back" :char ?R
)
240 ("--show" :char ?w
:fun guix-read-package-name
)))
242 (guix-command-define-argument-improver
243 guix-command-improve-refresh-argument
244 '(("--select" :fun guix-read-refresh-subset
)
245 ("--type" :fun guix-read-refresh-updater-names-string
)
246 ("--key-server" :char ?S
)))
248 (guix-command-define-argument-improver
249 guix-command-improve-size-argument
250 '(("--map-file" :fun guix-read-file-name
)))
252 (guix-command-define-argument-improver
253 guix-command-improve-system-argument
254 '(("disk-image" :char ?D
)
255 ("vm-image" :char ?V
)
256 ("--on-error" :char ?E
)
257 ("--no-grub" :char ?g
)
258 ("--full-boot" :char ?b
)))
260 (defvar guix-command-argument-improvers
262 guix-command-improve-action-argument
)
264 guix-command-improve-common-build-argument
265 guix-command-improve-target-argument
266 guix-command-improve-system-type-argument
267 guix-command-improve-archive-argument
)
269 guix-command-improve-common-build-argument
270 guix-command-improve-target-argument
271 guix-command-improve-system-type-argument
272 guix-command-improve-build-argument
)
274 guix-command-improve-hash-argument
)
276 guix-command-improve-hash-argument
)
278 guix-command-improve-common-build-argument
279 guix-command-improve-search-paths-argument
280 guix-command-improve-system-type-argument
281 guix-command-improve-environment-argument
)
283 guix-command-improve-gc-argument
)
285 guix-command-improve-graph-argument
)
287 guix-command-improve-import-argument
)
289 guix-command-improve-key-policy-argument
)
291 guix-command-improve-import-elpa-argument
)
293 guix-command-improve-lint-argument
)
295 guix-command-improve-common-build-argument
296 guix-command-improve-search-paths-argument
297 guix-command-improve-package-argument
)
299 guix-command-improve-key-policy-argument
300 guix-command-improve-refresh-argument
)
302 guix-command-improve-system-type-argument
303 guix-command-improve-substitute-urls-argument
304 guix-command-improve-size-argument
)
306 guix-command-improve-common-build-argument
307 guix-command-improve-system-argument
))
308 "Alist of guix commands and argument improvers for them.")
310 (defun guix-command-improve-argument (argument improvers
)
311 "Return ARGUMENT modified with IMPROVERS."
312 (or (cl-some (lambda (improver)
313 (funcall improver argument
))
317 (defun guix-command-improve-arguments (arguments commands
)
318 "Return ARGUMENTS for 'guix COMMANDS ...' modified for popup interface."
319 (let ((improvers (cons 'guix-command-improve-common-argument
320 (guix-assoc-value guix-command-argument-improvers
322 (mapcar (lambda (argument)
323 (guix-command-improve-argument argument improvers
))
326 (defun guix-command-parse-arguments (&optional commands
)
327 "Return a list of parsed 'guix COMMANDS ...' arguments."
329 (insert (guix-help-string commands
))
331 (guix-while-search guix-help-parse-option-regexp
332 (let* ((short (match-string-no-properties 1))
333 (name (match-string-no-properties 2))
334 (arg (match-string-no-properties 3))
335 (doc (match-string-no-properties 4))
337 (elt short
1) ; short option letter
338 (elt name
2))) ; first letter of the long option
339 ;; If "--foo=bar" or "--foo[=bar]" then it is 'option'.
340 (option?
(not (string= "" arg
)))
341 ;; If "--foo" or "--foo[=bar]" then it is 'switch'.
342 (switch?
(or (string= "" arg
)
343 (eq ?\
[ (elt arg
0)))))
344 (push (guix-command-make-argument
351 (guix-while-search guix-help-parse-command-regexp
352 (let* ((name (match-string-no-properties 1))
354 (push (guix-command-make-argument
357 :fun
(guix-command-action-name commands name
)
362 (defun guix-command-rest-argument (&optional commands
)
363 "Return '--' argument for COMMANDS."
364 (cl-flet ((argument (&rest args
)
365 (apply #'guix-command-make-argument
366 :name
"-- " :char ?
= :option? t args
)))
367 (let ((command (car commands
)))
370 '("archive" "build" "challenge" "edit" "environment"
371 "graph" "lint" "refresh"))
372 (argument :doc
"Packages" :fun
'guix-read-package-names-string
))
373 ((equal commands
'("container" "exec"))
374 (argument :doc
"PID Command [Args...]"))
375 ((string= command
"download")
376 (argument :doc
"URL"))
377 ((string= command
"gc")
378 (argument :doc
"Paths" :fun
'guix-read-file-name
))
379 ((member command
'("hash" "system"))
380 (argument :doc
"File" :fun
'guix-read-file-name
))
381 ((string= command
"size")
382 (argument :doc
"Package" :fun
'guix-read-package-name
))
383 ((equal commands
'("import" "nix"))
384 (argument :doc
"Nixpkgs Attribute"))
385 ;; Other 'guix import' subcommands, but not 'import' itself.
387 (string= command
"import"))
388 (argument :doc
"Package name"))))))
390 (defun guix-command-additional-arguments (&optional commands
)
391 "Return additional arguments for COMMANDS."
392 (let ((rest-arg (guix-command-rest-argument commands
)))
393 (and rest-arg
(list rest-arg
))))
395 ;; Ideally only `guix-command-arguments' function should exist with the
396 ;; contents of `guix-command-all-arguments', but we need to make a
397 ;; special case for `guix-command-complex-with-shared-arguments' commands.
399 (defun guix-command-all-arguments (&optional commands
)
400 "Return list of all arguments for 'guix COMMANDS ...'."
401 (let ((parsed (guix-command-parse-arguments commands
)))
402 (append (guix-command-improve-arguments parsed commands
)
403 (guix-command-additional-arguments commands
))))
405 (guix-memoized-defalias guix-command-all-arguments-memoize
406 guix-command-all-arguments
)
408 (defun guix-command-arguments (&optional commands
)
409 "Return list of arguments for 'guix COMMANDS ...'."
410 (let ((command (car commands
)))
412 guix-command-complex-with-shared-arguments
)
413 ;; Take actions only for 'guix system', and switches+options for
414 ;; 'guix system foo'.
415 (funcall (if (null (cdr commands
))
418 #'guix-command-argument-action?
419 (guix-command-all-arguments-memoize (list command
)))
420 (guix-command-all-arguments commands
))))
422 (defun guix-command-switch->popup-switch
(switch)
423 "Return popup switch from command SWITCH argument."
424 (list (guix-command-argument-char switch
)
425 (or (guix-command-argument-doc switch
)
427 (guix-command-argument-name switch
)))
429 (defun guix-command-option->popup-option
(option)
430 "Return popup option from command OPTION argument."
431 (list (guix-command-argument-char option
)
432 (or (guix-command-argument-doc option
)
434 (let ((name (guix-command-argument-name option
)))
435 (if (string-match-p " \\'" name
) ; ends with space
438 (or (guix-command-argument-fun option
)
439 'read-from-minibuffer
)))
441 (defun guix-command-action->popup-action
(action)
442 "Return popup action from command ACTION argument."
443 (list (guix-command-argument-char action
)
444 (or (guix-command-argument-doc action
)
445 (guix-command-argument-name action
)
447 (guix-command-argument-fun action
)))
449 (defun guix-command-sort-arguments (arguments)
450 "Sort ARGUMENTS by name in alphabetical order."
453 (let ((name1 (guix-command-argument-name a1
))
454 (name2 (guix-command-argument-name a2
)))
455 (cond ((null name1
) nil
)
457 (t (string< name1 name2
)))))))
459 (defun guix-command-switches (arguments)
460 "Return switches from ARGUMENTS."
461 (cl-remove-if-not #'guix-command-argument-switch? arguments
))
463 (defun guix-command-options (arguments)
464 "Return options from ARGUMENTS."
465 (cl-remove-if-not #'guix-command-argument-option? arguments
))
467 (defun guix-command-actions (arguments)
468 "Return actions from ARGUMENTS."
469 (cl-remove-if-not #'guix-command-argument-action? arguments
))
472 ;;; Post processing popup arguments
474 (defvar guix-command-post-processors
476 guix-command-post-process-rest-single
)
478 guix-command-post-process-package-args
)
480 guix-command-post-process-rest-single
))
481 "Alist of guix commands and functions for post-processing
482 a list of arguments returned from popup interface.
483 Each function is called on the returned arguments in turn.")
485 (defvar guix-command-rest-arg-regexp
486 (rx string-start
"-- " (group (+ any
)))
487 "Regexp to match a string with the 'rest' arguments.")
489 (defun guix-command-replace-args (args predicate modifier
)
490 "Replace arguments matching PREDICATE from ARGS.
491 Call MODIFIER on each argument matching PREDICATE and append the
492 returned list of strings to the end of ARGS. Remove the original
495 (args (mapcar (lambda (arg)
496 (if (funcall predicate arg
)
498 (push (funcall modifier arg
) rest
)
503 (apply #'append
(delq nil args
) rest
)
506 (cl-defun guix-command-post-process-matching-args (args regexp
508 "Modify arguments from ARGS matching REGEXP by moving them to
509 the end of ARGS list. If SPLIT? is non-nil, split matching
510 arguments into multiple subarguments."
511 (guix-command-replace-args
514 (string-match regexp arg
))
516 (let ((val (match-string (or group
0) arg
))
517 (fun (if split?
#'split-string
#'list
)))
518 (funcall fun val
)))))
520 (defun guix-command-post-process-rest-single (args)
521 "Modify ARGS by moving '-- ARG' argument to the end of ARGS list."
522 (guix-command-post-process-matching-args
523 args guix-command-rest-arg-regexp
526 (defun guix-command-post-process-rest-multiple (args)
527 "Modify ARGS by splitting '-- ARG ...' into multiple subarguments
528 and moving them to the end of ARGS list.
529 Remove '-- ' string."
530 (guix-command-post-process-matching-args
531 args guix-command-rest-arg-regexp
535 (defun guix-command-post-process-rest-multiple-leave (args)
536 "Modify ARGS by splitting '-- ARG ...' into multiple subarguments
537 and moving them to the end of ARGS list.
538 Leave '--' string as a separate argument."
539 (guix-command-post-process-matching-args
540 args guix-command-rest-arg-regexp
543 (defun guix-command-post-process-package-args (args)
544 "Adjust popup ARGS for 'guix package' command."
545 (guix-command-post-process-matching-args
546 args
(rx string-start
(or "--install " "--remove ") (+ any
))
549 (defun guix-command-post-process-args (commands args
)
550 "Adjust popup ARGS for guix COMMANDS."
551 (let* ((command (car commands
))
553 (append (guix-assoc-value guix-command-post-processors commands
)
554 (guix-assoc-value guix-command-post-processors command
))))
557 (list #'guix-command-post-process-rest-multiple
)))))
560 ;;; 'Execute' actions
562 (defvar guix-command-default-execute-arguments
564 (guix-command-make-argument
565 :name
"repl" :char ?r
:doc
"Run in Guix REPL")
566 (guix-command-make-argument
567 :name
"shell" :char ?s
:doc
"Run in shell")
568 (guix-command-make-argument
569 :name
"copy" :char ?c
:doc
"Copy command line"))
570 "List of default 'execute' action arguments.")
572 (defvar guix-command-additional-execute-arguments
573 (let ((graph-arg (guix-command-make-argument
574 :name
"view" :char ?v
:doc
"View graph")))
576 ,(guix-command-make-argument
577 :name
"log" :char ?l
:doc
"View build log"))
578 (("graph") ,graph-arg
)
580 ,(guix-command-make-argument
581 :name
"view" :char ?v
:doc
"View map"))
582 (("system" "dmd-graph") ,graph-arg
)
583 (("system" "extension-graph") ,graph-arg
)))
584 "Alist of guix commands and additional 'execute' action arguments.")
586 (defun guix-command-execute-arguments (commands)
587 "Return a list of 'execute' action arguments for COMMANDS."
588 (mapcar (lambda (arg)
589 (guix-command-modify-argument arg
591 :fun
(guix-command-action-name
592 commands
(guix-command-argument-name arg
))))
593 (append guix-command-default-execute-arguments
595 guix-command-additional-execute-arguments commands
))))
597 (defvar guix-command-special-executors
599 ("repl" . guix-run-environment-command-in-repl
))
601 ("repl" . guix-run-pull-command-in-repl
))
603 ("log" . guix-run-view-build-log
))
605 ("view" . guix-run-view-graph
))
607 ("view" . guix-run-view-size-map
))
608 (("system" "dmd-graph")
609 ("view" . guix-run-view-graph
))
610 (("system" "extension-graph")
611 ("view" . guix-run-view-graph
)))
612 "Alist of guix commands and alists of special executers for them.
613 See also `guix-command-default-executors'.")
615 (defvar guix-command-default-executors
616 '(("repl" . guix-run-command-in-repl
)
617 ("shell" . guix-run-command-in-shell
)
618 ("copy" . guix-copy-command-as-kill
))
619 "Alist of default executers for action names.")
621 (defun guix-command-executor (commands name
)
622 "Return function to run command line arguments for guix COMMANDS."
623 (or (guix-assoc-value guix-command-special-executors commands name
)
624 (guix-assoc-value guix-command-default-executors name
)))
626 (defun guix-run-environment-command-in-repl (args)
627 "Run 'guix ARGS ...' environment command in Guix REPL."
628 ;; As 'guix environment' usually tries to run another process, it may
629 ;; be fun but not wise to run this command in Geiser REPL.
630 (when (or (member "--dry-run" args
)
631 (member "--search-paths" args
)
633 (format "'%s' command will spawn an external process.
634 Do you really want to execute this command in Geiser REPL? "
635 (guix-command-string args
)))
636 (message "May \"M-x shell-mode\" be with you!")
638 (guix-run-command-in-repl args
)))
640 (defun guix-run-pull-command-in-repl (args)
641 "Run 'guix ARGS ...' pull command in Guix REPL.
642 Perform pull-specific actions after operation, see
643 `guix-after-pull-hook' and `guix-update-after-pull'."
645 (apply #'guix-make-guile-expression
'guix-command args
)
648 (defun guix-run-view-build-log (args)
649 "Add --log-file to ARGS, run 'guix ARGS ...' build command, and
650 open the log file(s)."
651 (let* ((args (if (member "--log-file" args
)
653 (apply #'list
(car args
) "--log-file" (cdr args
))))
654 (output (guix-command-output args
))
655 (files (split-string output
"\n" t
)))
657 (guix-find-file-or-url file
)
658 (guix-build-log-mode))))
660 (defun guix-run-view-graph (args)
661 "Run 'guix ARGS ...' graph command, make the image and open it."
662 (let* ((graph-file (guix-dot-file-name))
663 (dot-args (guix-dot-arguments graph-file
)))
664 (if (guix-eval-read (guix-make-guile-expression
665 'pipe-guix-output args dot-args
))
666 (guix-find-file graph-file
)
667 (error "Couldn't create a graph"))))
669 (defun guix-run-view-size-map (args)
670 "Run 'guix ARGS ...' size command, and open the map file."
671 (let* ((wished-map-file
672 (cl-some (lambda (arg)
673 (and (string-match "--map-file=\\(.+\\)" arg
)
674 (match-string 1 arg
)))
676 (map-file (or wished-map-file
(guix-png-file-name)))
677 (args (if wished-map-file
681 (concat "--map-file=" map-file
)
683 (guix-command-output args
)
684 (guix-find-file map-file
)))
687 ;;; Generating popups, actions, etc.
689 (defmacro guix-command-define-popup-action
(name &optional commands
)
690 "Define NAME function to generate (if needed) and run popup for COMMANDS."
691 (declare (indent 1) (debug t
))
692 (let* ((popup-fun (guix-command-symbol `(,@commands
"popup")))
693 (doc (format "Call `%s' (generate it if needed)."
695 `(defun ,name
(&optional arg
)
698 (unless (fboundp ',popup-fun
)
699 (guix-command-generate-popup ',popup-fun
',commands
))
702 (defmacro guix-command-define-execute-action
(name executor
704 "Define NAME function to execute the current action for guix COMMANDS.
705 EXECUTOR function is called with the current command line arguments."
706 (declare (indent 1) (debug t
))
707 (let* ((arguments-fun (guix-command-symbol `(,@commands
"arguments")))
708 (doc (format "Call `%s' with the current popup arguments."
710 `(defun ,name
(&rest args
)
712 (interactive (,arguments-fun
))
713 (,executor
(append ',commands
714 (guix-command-post-process-args
715 ',commands args
))))))
717 (defun guix-command-generate-popup-actions (actions &optional commands
)
718 "Generate 'popup' commands from ACTIONS arguments for guix COMMANDS."
719 (dolist (action actions
)
720 (let ((fun (guix-command-argument-fun action
)))
721 (unless (fboundp fun
)
722 (eval `(guix-command-define-popup-action ,fun
724 (list (guix-command-argument-name action
)))))))))
726 (defun guix-command-generate-execute-actions (actions &optional commands
)
727 "Generate 'execute' commands from ACTIONS arguments for guix COMMANDS."
728 (dolist (action actions
)
729 (let ((fun (guix-command-argument-fun action
)))
730 (unless (fboundp fun
)
731 (eval `(guix-command-define-execute-action ,fun
732 ,(guix-command-executor
733 commands
(guix-command-argument-name action
))
736 (defun guix-command-generate-popup (name &optional commands
)
737 "Define NAME popup with 'guix COMMANDS ...' interface."
738 (let* ((command (car commands
))
739 (man-page (concat "guix" (and command
(concat "-" command
))))
740 (doc (format "Popup window for '%s' command."
741 (guix-concat-strings (cons "guix" commands
)
743 (args (guix-command-arguments commands
))
744 (switches (guix-command-sort-arguments
745 (guix-command-switches args
)))
746 (options (guix-command-sort-arguments
747 (guix-command-options args
)))
748 (popup-actions (guix-command-sort-arguments
749 (guix-command-actions args
)))
750 (execute-actions (unless popup-actions
751 (guix-command-execute-arguments commands
)))
752 (actions (or popup-actions execute-actions
)))
754 (guix-command-generate-popup-actions popup-actions commands
)
755 (guix-command-generate-execute-actions execute-actions commands
))
757 `(guix-define-popup ,name
761 :switches
',(mapcar #'guix-command-switch-
>popup-switch switches
)
762 :options
',(mapcar #'guix-command-option-
>popup-option options
)
763 :actions
',(mapcar #'guix-command-action-
>popup-action actions
)
764 :max-action-columns
4))))
766 ;;;###autoload (autoload 'guix "guix-command" "Popup window for 'guix'." t)
767 (guix-command-define-popup-action guix
)
769 (defalias 'guix-edit-action
#'guix-edit
)
772 (defvar guix-command-font-lock-keywords
775 (group "guix-command-define-"
778 "argument-improver"))
782 (group (one-or-more (or (syntax word
) (syntax symbol
))))))
783 (1 font-lock-keyword-face
)
784 (2 font-lock-function-name-face nil t
)))))
786 (font-lock-add-keywords 'emacs-lisp-mode guix-command-font-lock-keywords
)
788 (provide 'guix-command
)
790 ;;; guix-command.el ends here