summaryrefslogtreecommitdiff
path: root/emacs
diff options
context:
space:
mode:
Diffstat (limited to 'emacs')
-rw-r--r--emacs/guix-backend.el14
-rw-r--r--emacs/guix-base.el121
-rw-r--r--emacs/guix-command.el671
-rw-r--r--emacs/guix-config.el.in40
-rw-r--r--emacs/guix-external.el72
-rw-r--r--emacs/guix-help-vars.el108
-rw-r--r--emacs/guix-info.el48
-rw-r--r--emacs/guix-init.el (renamed from emacs/guix-init.el.in)4
-rw-r--r--emacs/guix-list.el52
-rw-r--r--emacs/guix-main.scm78
-rw-r--r--emacs/guix-messages.el8
-rw-r--r--emacs/guix-pcomplete.el91
-rw-r--r--emacs/guix-popup.el48
-rw-r--r--emacs/guix-prettify.el17
-rw-r--r--emacs/guix-profiles.el (renamed from emacs/guix-profiles.el.in)4
-rw-r--r--emacs/guix-read.el176
-rw-r--r--emacs/guix-utils.el120
-rw-r--r--emacs/guix.el12
18 files changed, 1497 insertions, 187 deletions
diff --git a/emacs/guix-backend.el b/emacs/guix-backend.el
index 73a429b9ee..7db1daacf0 100644
--- a/emacs/guix-backend.el
+++ b/emacs/guix-backend.el
@@ -52,26 +52,16 @@
;;; Code:
(require 'geiser-mode)
+(require 'guix-config)
(require 'guix-emacs)
-(defvar guix-load-path
- (file-name-directory (or load-file-name
- (locate-library "guix")))
+(defvar guix-load-path guix-emacs-interface-directory
"Directory with scheme files for \"guix.el\" package.")
(defvar guix-helper-file
(expand-file-name "guix-helper.scm" guix-load-path)
"Auxiliary scheme file for loading.")
-(defvar guix-guile-program (or geiser-guile-binary "guile")
- "Name of the guile executable used for Guix REPL.
-May be either a string (the name of the executable) or a list of
-strings of the form:
-
- (NAME . ARGS)
-
-Where ARGS is a list of arguments to the guile program.")
-
;;; REPL
diff --git a/emacs/guix-base.el b/emacs/guix-base.el
index fe89584f18..3bee910b05 100644
--- a/emacs/guix-base.el
+++ b/emacs/guix-base.el
@@ -1,6 +1,6 @@
;;; guix-base.el --- Common definitions -*- lexical-binding: t -*-
-;; Copyright © 2014 Alex Kost <alezost@gmail.com>
+;; Copyright © 2014, 2015 Alex Kost <alezost@gmail.com>
;; This file is part of GNU Guix.
@@ -89,8 +89,8 @@ Each element of the list has a form:
(defun guix-get-param-title (entry-type param)
"Return title of an ENTRY-TYPE entry parameter PARAM."
- (or (guix-get-key-val guix-param-titles
- entry-type param)
+ (or (guix-assq-value guix-param-titles
+ entry-type param)
(prog1 (symbol-name param)
(message "Couldn't find title for '%S %S'."
entry-type param))))
@@ -102,15 +102,15 @@ Each element of the list has a form:
(defun guix-get-full-name (entry &optional output)
"Return name specification of the package ENTRY and OUTPUT."
- (guix-get-name-spec (guix-get-key-val entry 'name)
- (guix-get-key-val entry 'version)
+ (guix-get-name-spec (guix-assq-value entry 'name)
+ (guix-assq-value entry 'version)
output))
(defun guix-entry-to-specification (entry)
"Return name specification by the package or output ENTRY."
- (guix-get-name-spec (guix-get-key-val entry 'name)
- (guix-get-key-val entry 'version)
- (guix-get-key-val entry 'output)))
+ (guix-get-name-spec (guix-assq-value entry 'name)
+ (guix-assq-value entry 'version)
+ (guix-assq-value entry 'output)))
(defun guix-entries-to-specifications (entries)
"Return name specifications by the package or output ENTRIES."
@@ -120,13 +120,13 @@ Each element of the list has a form:
(defun guix-get-installed-outputs (entry)
"Return list of installed outputs for the package ENTRY."
(mapcar (lambda (installed-entry)
- (guix-get-key-val installed-entry 'output))
- (guix-get-key-val entry 'installed)))
+ (guix-assq-value installed-entry 'output))
+ (guix-assq-value entry 'installed)))
(defun guix-get-entry-by-id (id entries)
"Return entry from ENTRIES by entry ID."
(cl-find-if (lambda (entry)
- (equal id (guix-get-key-val entry 'id)))
+ (equal id (guix-assq-value entry 'id)))
entries))
(defun guix-get-package-id-and-output-by-output-id (oid)
@@ -172,13 +172,36 @@ If PATH is relative, it is considered to be relative to
(move-to-column col)
(recenter 1))))
-(defun guix-edit-package (id)
- "Edit (go to location of) package with ID."
- (let ((loc (guix-eval-read (guix-make-guile-expression
- 'package-location-string id))))
- (if loc
- (guix-find-location loc)
- (message "Couldn't find package location."))))
+(defun guix-package-location (id-or-name)
+ "Return location of a package with ID-OR-NAME.
+For the meaning of location, see `guix-find-location'."
+ (guix-eval-read (guix-make-guile-expression
+ 'package-location-string id-or-name)))
+
+
+;;; Receivable lists of packages, lint checkers, etc.
+
+(guix-memoized-defun guix-graph-type-names ()
+ "Return a list of names of available graph node types."
+ (guix-eval-read (guix-make-guile-expression 'graph-type-names)))
+
+(guix-memoized-defun guix-lint-checker-names ()
+ "Return a list of names of available lint checkers."
+ (guix-eval-read (guix-make-guile-expression 'lint-checker-names)))
+
+(guix-memoized-defun guix-package-names ()
+ "Return a list of names of available packages."
+ (sort
+ ;; Work around <https://github.com/jaor/geiser/issues/64>:
+ ;; list of strings is parsed much slower than list of lists,
+ ;; so we use 'package-names-lists' instead of 'package-names'.
+
+ ;; (guix-eval-read (guix-make-guile-expression 'package-names))
+
+ (mapcar #'car
+ (guix-eval-read (guix-make-guile-expression
+ 'package-names-lists)))
+ #'string<))
;;; Buffers and auto updating.
@@ -392,7 +415,6 @@ following keywords are available:
(prefix (concat "guix-" entry-type-str "-" buf-type-str))
(group (intern prefix))
(mode-map-str (concat prefix "-mode-map"))
- (mode-map (intern mode-map-str))
(parent-mode (intern (concat "guix-" buf-type-str "-mode")))
(mode (intern (concat prefix "-mode")))
(mode-init-fun (intern (concat prefix "-mode-initialize")))
@@ -910,11 +932,11 @@ ENTRIES is a list of package entries to get info about packages."
(outputs (cdr spec))
(entry (guix-get-entry-by-id id entries)))
(when entry
- (let ((location (guix-get-key-val entry 'location)))
+ (let ((location (guix-assq-value entry 'location)))
(concat (guix-get-full-name entry)
(when outputs
(concat ":"
- (mapconcat #'identity outputs ",")))
+ (guix-concat-strings outputs ",")))
(when location
(concat "\t(" location ")")))))))
specs)))
@@ -1061,6 +1083,63 @@ FILE. With a prefix argument, also prompt for PROFILE."
operation-buffer)))
+;;; Executing guix commands
+
+(defcustom guix-run-in-shell-function #'guix-run-in-shell
+ "Function used to run guix command.
+The function is called with a single argument - a command line string."
+ :type '(choice (function-item guix-run-in-shell)
+ (function-item guix-run-in-eshell)
+ (function :tag "Other function"))
+ :group 'guix)
+
+(defcustom guix-shell-buffer-name "*shell*"
+ "Default name of a shell buffer used for running guix commands."
+ :type 'string
+ :group 'guix)
+
+(declare-function comint-send-input "comint" t)
+
+(defun guix-run-in-shell (string)
+ "Run command line STRING in `guix-shell-buffer-name' buffer."
+ (shell guix-shell-buffer-name)
+ (goto-char (point-max))
+ (insert string)
+ (comint-send-input))
+
+(declare-function eshell-send-input "esh-mode" t)
+
+(defun guix-run-in-eshell (string)
+ "Run command line STRING in eshell buffer."
+ (eshell)
+ (goto-char (point-max))
+ (insert string)
+ (eshell-send-input))
+
+(defun guix-run-command-in-shell (args)
+ "Execute 'guix ARGS ...' command in a shell buffer."
+ (funcall guix-run-in-shell-function
+ (guix-command-string args)))
+
+(defun guix-run-command-in-repl (args)
+ "Execute 'guix ARGS ...' command in Guix REPL."
+ (guix-eval-in-repl
+ (apply #'guix-make-guile-expression
+ 'guix-command args)))
+
+(defun guix-command-output (args)
+ "Return string with 'guix ARGS ...' output."
+ (guix-eval-read
+ (apply #'guix-make-guile-expression
+ 'guix-command-output args)))
+
+(defun guix-help-string (&optional commands)
+ "Return string with 'guix COMMANDS ... --help' output."
+ (guix-eval-read
+ (apply #'guix-make-guile-expression
+ 'help-string commands)))
+
+
;;; Pull
(defcustom guix-update-after-pull t
diff --git a/emacs/guix-command.el b/emacs/guix-command.el
new file mode 100644
index 0000000000..81f619f434
--- /dev/null
+++ b/emacs/guix-command.el
@@ -0,0 +1,671 @@
+;;; guix-command.el --- Popup interface for guix commands -*- lexical-binding: t -*-
+
+;; Copyright © 2015 Alex Kost <alezost@gmail.com>
+
+;; This file is part of GNU Guix.
+
+;; GNU Guix 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.
+
+;; GNU Guix 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides a magit-like popup interface for running guix
+;; commands in Guix REPL. The entry point is "M-x guix". When it is
+;; called the first time, "guix --help" output is parsed and
+;; `guix-COMMAND-action' functions are generated for each available guix
+;; COMMAND. Then a window with these commands is popped up. When a
+;; particular COMMAND is called, "guix COMMAND --help" output is parsed,
+;; and a user get a new popup window with available options for this
+;; command and so on.
+
+;; To avoid hard-coding all guix options, actions, etc., as much data is
+;; taken from "guix ... --help" outputs as possible. But this data is
+;; still incomplete: not all long options have short analogs, also
+;; special readers should be used for some options (for example, to
+;; complete package names while prompting for a package). So after
+;; parsing --help output, the arguments are "improved". All arguments
+;; (switches, options and actions) are `guix-command-argument'
+;; structures.
+
+;; Only "M-x guix" command is available after this file is loaded. The
+;; rest commands/actions/popups are generated on the fly only when they
+;; are needed (that's why there is a couple of `eval'-s in this file).
+
+;; COMMANDS argument is used by many functions in this file. It means a
+;; list of guix commands without "guix" itself, e.g.: ("build"),
+;; ("import" "gnu"). The empty list stands for the plain "guix" without
+;; subcommands.
+
+;; All actions in popup windows are divided into 2 groups:
+;;
+;; - 'Popup' actions - used to pop up another window. For example, every
+;; action in the 'guix' or 'guix import' window is a popup action. They
+;; are defined by `guix-command-define-popup-action' macro.
+;;
+;; - 'Execute' actions - used to do something with the command line (to
+;; run a command in Guix REPL or to copy it into kill-ring) constructed
+;; with the current popup. They are defined by
+;; `guix-command-define-execute-action' macro.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'guix-popup)
+(require 'guix-utils)
+(require 'guix-help-vars)
+(require 'guix-read)
+(require 'guix-base)
+(require 'guix-external)
+
+(defgroup guix-commands nil
+ "Settings for guix popup windows."
+ :group 'guix)
+
+(defvar guix-command-complex-with-shared-arguments
+ '("system")
+ "List of guix commands which have subcommands with shared options.
+I.e., 'guix foo --help' is the same as 'guix foo bar --help'.")
+
+(defun guix-command-action-name (&optional commands &rest name-parts)
+ "Return name of action function for guix COMMANDS."
+ (guix-command-symbol (append commands name-parts (list "action"))))
+
+
+;;; Command arguments
+
+(cl-defstruct (guix-command-argument
+ (:constructor guix-command-make-argument)
+ (:copier guix-command-copy-argument))
+ name char doc fun switch? option? action?)
+
+(cl-defun guix-command-modify-argument
+ (argument &key
+ (name nil name-bound?)
+ (char nil char-bound?)
+ (doc nil doc-bound?)
+ (fun nil fun-bound?)
+ (switch? nil switch?-bound?)
+ (option? nil option?-bound?)
+ (action? nil action?-bound?))
+ "Return a modified version of ARGUMENT."
+ (declare (indent 1))
+ (let ((copy (guix-command-copy-argument argument)))
+ (and name-bound? (setf (guix-command-argument-name copy) name))
+ (and char-bound? (setf (guix-command-argument-char copy) char))
+ (and doc-bound? (setf (guix-command-argument-doc copy) doc))
+ (and fun-bound? (setf (guix-command-argument-fun copy) fun))
+ (and switch?-bound? (setf (guix-command-argument-switch? copy) switch?))
+ (and option?-bound? (setf (guix-command-argument-option? copy) option?))
+ (and action?-bound? (setf (guix-command-argument-action? copy) action?))
+ copy))
+
+(defun guix-command-modify-argument-from-alist (argument alist)
+ "Return a modified version of ARGUMENT or nil if it wasn't modified.
+Each assoc from ALIST have a form (NAME . PLIST). NAME is an
+argument name. PLIST is a property list of argument parameters
+to be modified."
+ (let* ((name (guix-command-argument-name argument))
+ (plist (guix-assoc-value alist name)))
+ (when plist
+ (apply #'guix-command-modify-argument
+ argument plist))))
+
+(defmacro guix-command-define-argument-improver (name alist)
+ "Define NAME variable and function to modify an argument from ALIST."
+ (declare (indent 1))
+ `(progn
+ (defvar ,name ,alist)
+ (defun ,name (argument)
+ (guix-command-modify-argument-from-alist argument ,name))))
+
+(guix-command-define-argument-improver
+ guix-command-improve-action-argument
+ '(("graph" :char ?G)
+ ("environment" :char ?E)
+ ("publish" :char ?u)
+ ("pull" :char ?P)
+ ("size" :char ?z)))
+
+(guix-command-define-argument-improver
+ guix-command-improve-common-argument
+ '(("--help" :switch? nil)
+ ("--version" :switch? nil)))
+
+(guix-command-define-argument-improver
+ guix-command-improve-target-argument
+ '(("--target" :char ?T)))
+
+(guix-command-define-argument-improver
+ guix-command-improve-system-type-argument
+ '(("--system" :fun guix-read-system-type)))
+
+(guix-command-define-argument-improver
+ guix-command-improve-load-path-argument
+ '(("--load-path" :fun read-directory-name)))
+
+(guix-command-define-argument-improver
+ guix-command-improve-search-paths-argument
+ '(("--search-paths" :char ?P)))
+
+(guix-command-define-argument-improver
+ guix-command-improve-substitute-urls-argument
+ '(("--substitute-urls" :char ?U)))
+
+(guix-command-define-argument-improver
+ guix-command-improve-hash-argument
+ '(("--format" :fun guix-read-hash-format)))
+
+(guix-command-define-argument-improver
+ guix-command-improve-key-policy-argument
+ '(("--key-download" :fun guix-read-key-policy)))
+
+(defvar guix-command-improve-common-build-argument
+ '(("--no-substitutes" :char ?s)
+ ("--no-build-hook" :char ?h)
+ ("--max-silent-time" :char ?x)))
+
+(defun guix-command-improve-common-build-argument (argument)
+ (guix-command-modify-argument-from-alist
+ argument
+ (append guix-command-improve-load-path-argument
+ guix-command-improve-substitute-urls-argument
+ guix-command-improve-common-build-argument)))
+
+(guix-command-define-argument-improver
+ guix-command-improve-archive-argument
+ '(("--generate-key" :char ?k)))
+
+(guix-command-define-argument-improver
+ guix-command-improve-build-argument
+ '(("--no-grafts" :char ?g)
+ ("--root" :fun guix-read-file-name)
+ ("--sources" :char ?S :fun guix-read-source-type :switch? nil)
+ ("--with-source" :fun guix-read-file-name)))
+
+(guix-command-define-argument-improver
+ guix-command-improve-environment-argument
+ '(("--exec" :fun read-shell-command)
+ ("--load" :fun guix-read-file-name)))
+
+(guix-command-define-argument-improver
+ guix-command-improve-gc-argument
+ '(("--list-dead" :char ?D)
+ ("--list-live" :char ?L)
+ ("--referrers" :char ?f)
+ ("--verify" :fun guix-read-verify-options-string)))
+
+(guix-command-define-argument-improver
+ guix-command-improve-graph-argument
+ '(("--type" :fun guix-read-graph-type)))
+
+(guix-command-define-argument-improver
+ guix-command-improve-import-argument
+ '(("cran" :char ?r)))
+
+(guix-command-define-argument-improver
+ guix-command-improve-import-elpa-argument
+ '(("--archive" :fun guix-read-elpa-archive)))
+
+(guix-command-define-argument-improver
+ guix-command-improve-lint-argument
+ '(("--checkers" :fun guix-read-lint-checker-names-string)))
+
+(guix-command-define-argument-improver
+ guix-command-improve-package-argument
+ ;; Unlike all other options, --install/--remove do not have a form
+ ;; '--install=foo,bar' but '--install foo bar' instead, so we need
+ ;; some tweaks.
+ '(("--install"
+ :name "--install " :fun guix-read-package-names-string
+ :switch? nil :option? t)
+ ("--remove"
+ :name "--remove " :fun guix-read-package-names-string
+ :switch? nil :option? t)
+ ("--install-from-file" :fun guix-read-file-name)
+ ("--manifest" :fun guix-read-file-name)
+ ("--do-not-upgrade" :char ?U)
+ ("--roll-back" :char ?R)
+ ("--show" :char ?w :fun guix-read-package-name)))
+
+(guix-command-define-argument-improver
+ guix-command-improve-refresh-argument
+ '(("--select" :fun guix-read-refresh-subset)
+ ("--key-server" :char ?S)))
+
+(guix-command-define-argument-improver
+ guix-command-improve-size-argument
+ '(("--map-file" :fun guix-read-file-name)))
+
+(guix-command-define-argument-improver
+ guix-command-improve-system-argument
+ '(("vm-image" :char ?V)
+ ("--on-error" :char ?E)
+ ("--no-grub" :char ?g)
+ ("--full-boot" :char ?b)))
+
+(defvar guix-command-argument-improvers
+ '((()
+ guix-command-improve-action-argument)
+ (("archive")
+ guix-command-improve-common-build-argument
+ guix-command-improve-target-argument
+ guix-command-improve-system-type-argument
+ guix-command-improve-archive-argument)
+ (("build")
+ guix-command-improve-common-build-argument
+ guix-command-improve-target-argument
+ guix-command-improve-system-type-argument
+ guix-command-improve-build-argument)
+ (("download")
+ guix-command-improve-hash-argument)
+ (("hash")
+ guix-command-improve-hash-argument)
+ (("environment")
+ guix-command-improve-common-build-argument
+ guix-command-improve-search-paths-argument
+ guix-command-improve-system-type-argument
+ guix-command-improve-environment-argument)
+ (("gc")
+ guix-command-improve-gc-argument)
+ (("graph")
+ guix-command-improve-graph-argument)
+ (("import")
+ guix-command-improve-import-argument)
+ (("import" "gnu")
+ guix-command-improve-key-policy-argument)
+ (("import" "elpa")
+ guix-command-improve-import-elpa-argument)
+ (("lint")
+ guix-command-improve-lint-argument)
+ (("package")
+ guix-command-improve-common-build-argument
+ guix-command-improve-search-paths-argument
+ guix-command-improve-package-argument)
+ (("refresh")
+ guix-command-improve-key-policy-argument
+ guix-command-improve-refresh-argument)
+ (("size")
+ guix-command-improve-system-type-argument
+ guix-command-improve-substitute-urls-argument
+ guix-command-improve-size-argument)
+ (("system")
+ guix-command-improve-common-build-argument
+ guix-command-improve-system-argument))
+ "Alist of guix commands and argument improvers for them.")
+
+(defun guix-command-improve-argument (argument improvers)
+ "Return ARGUMENT modified with IMPROVERS."
+ (or (guix-any (lambda (improver)
+ (funcall improver argument))
+ improvers)
+ argument))
+
+(defun guix-command-improve-arguments (arguments commands)
+ "Return ARGUMENTS for 'guix COMMANDS ...' modified for popup interface."
+ (let ((improvers (cons 'guix-command-improve-common-argument
+ (guix-assoc-value guix-command-argument-improvers
+ commands))))
+ (mapcar (lambda (argument)
+ (guix-command-improve-argument argument improvers))
+ arguments)))
+
+(defun guix-command-parse-arguments (&optional commands)
+ "Return a list of parsed 'guix COMMANDS ...' arguments."
+ (with-temp-buffer
+ (insert (guix-help-string commands))
+ (let (args)
+ (guix-while-search guix-help-parse-option-regexp
+ (let* ((short (match-string-no-properties 1))
+ (name (match-string-no-properties 2))
+ (arg (match-string-no-properties 3))
+ (doc (match-string-no-properties 4))
+ (char (if short
+ (elt short 1) ; short option letter
+ (elt name 2))) ; first letter of the long option
+ ;; If "--foo=bar" or "--foo[=bar]" then it is 'option'.
+ (option? (not (string= "" arg)))
+ ;; If "--foo" or "--foo[=bar]" then it is 'switch'.
+ (switch? (or (string= "" arg)
+ (eq ?\[ (elt arg 0)))))
+ (push (guix-command-make-argument
+ :name name
+ :char char
+ :doc doc
+ :switch? switch?
+ :option? option?)
+ args)))
+ (guix-while-search guix-help-parse-command-regexp
+ (let* ((name (match-string-no-properties 1))
+ (char (elt name 0)))
+ (push (guix-command-make-argument
+ :name name
+ :char char
+ :fun (guix-command-action-name commands name)
+ :action? t)
+ args)))
+ args)))
+
+(defun guix-command-rest-argument (&optional commands)
+ "Return '--' argument for COMMANDS."
+ (cl-flet ((argument (&rest args)
+ (apply #'guix-command-make-argument
+ :name "-- " :char ?= :option? t args)))
+ (let ((command (car commands)))
+ (cond
+ ((member command '("archive" "build" "graph" "edit"
+ "environment" "lint" "refresh"))
+ (argument :doc "Packages" :fun 'guix-read-package-names-string))
+ ((string= command "download")
+ (argument :doc "URL"))
+ ((string= command "gc")
+ (argument :doc "Paths" :fun 'guix-read-file-name))
+ ((member command '("hash" "system"))
+ (argument :doc "File" :fun 'guix-read-file-name))
+ ((string= command "size")
+ (argument :doc "Package" :fun 'guix-read-package-name))
+ ((equal commands '("import" "nix"))
+ (argument :doc "Nixpkgs Attribute"))
+ ;; Other 'guix import' subcommands, but not 'import' itself.
+ ((and (cdr commands)
+ (string= command "import"))
+ (argument :doc "Package name"))))))
+
+(defun guix-command-additional-arguments (&optional commands)
+ "Return additional arguments for COMMANDS."
+ (let ((rest-arg (guix-command-rest-argument commands)))
+ (and rest-arg (list rest-arg))))
+
+;; Ideally only `guix-command-arguments' function should exist with the
+;; contents of `guix-command-all-arguments', but we need to make a
+;; special case for `guix-command-complex-with-shared-arguments' commands.
+
+(defun guix-command-all-arguments (&optional commands)
+ "Return list of all arguments for 'guix COMMANDS ...'."
+ (let ((parsed (guix-command-parse-arguments commands)))
+ (append (guix-command-improve-arguments parsed commands)
+ (guix-command-additional-arguments commands))))
+
+(guix-memoized-defalias guix-command-all-arguments-memoize
+ guix-command-all-arguments)
+
+(defun guix-command-arguments (&optional commands)
+ "Return list of arguments for 'guix COMMANDS ...'."
+ (let ((command (car commands)))
+ (if (member command
+ guix-command-complex-with-shared-arguments)
+ ;; Take actions only for 'guix system', and switches+options for
+ ;; 'guix system foo'.
+ (funcall (if (null (cdr commands))
+ #'cl-remove-if-not
+ #'cl-remove-if)
+ #'guix-command-argument-action?
+ (guix-command-all-arguments-memoize (list command)))
+ (guix-command-all-arguments commands))))
+
+(defun guix-command-switch->popup-switch (switch)
+ "Return popup switch from command SWITCH argument."
+ (list (guix-command-argument-char switch)
+ (or (guix-command-argument-doc switch)
+ "Unknown")
+ (guix-command-argument-name switch)))
+
+(defun guix-command-option->popup-option (option)
+ "Return popup option from command OPTION argument."
+ (list (guix-command-argument-char option)
+ (or (guix-command-argument-doc option)
+ "Unknown")
+ (let ((name (guix-command-argument-name option)))
+ (if (string-match-p " \\'" name) ; ends with space
+ name
+ (concat name "=")))
+ (or (guix-command-argument-fun option)
+ 'read-from-minibuffer)))
+
+(defun guix-command-action->popup-action (action)
+ "Return popup action from command ACTION argument."
+ (list (guix-command-argument-char action)
+ (or (guix-command-argument-doc action)
+ (guix-command-argument-name action)
+ "Unknown")
+ (guix-command-argument-fun action)))
+
+(defun guix-command-sort-arguments (arguments)
+ "Sort ARGUMENTS by name in alphabetical order."
+ (sort arguments
+ (lambda (a1 a2)
+ (let ((name1 (guix-command-argument-name a1))
+ (name2 (guix-command-argument-name a2)))
+ (cond ((null name1) nil)
+ ((null name2) t)
+ (t (string< name1 name2)))))))
+
+(defun guix-command-switches (arguments)
+ "Return switches from ARGUMENTS."
+ (cl-remove-if-not #'guix-command-argument-switch? arguments))
+
+(defun guix-command-options (arguments)
+ "Return options from ARGUMENTS."
+ (cl-remove-if-not #'guix-command-argument-option? arguments))
+
+(defun guix-command-actions (arguments)
+ "Return actions from ARGUMENTS."
+ (cl-remove-if-not #'guix-command-argument-action? arguments))
+
+(defun guix-command-post-process-args (args)
+ "Adjust appropriately command line ARGS returned from popup command."
+ ;; XXX We need to split "--install foo bar" and similar strings into
+ ;; lists of strings. But some commands (e.g., 'guix hash') accept a
+ ;; file name as the 'rest' argument, and as file names may contain
+ ;; spaces, splitting by spaces will break such names. For example, the
+ ;; following argument: "-- /tmp/file with spaces" will be transformed
+ ;; into the following list: ("--" "/tmp/file" "with" "spaces") instead
+ ;; of the wished ("--" "/tmp/file with spaces").
+ (let* (rest
+ (rx (rx string-start
+ (or "-- " "--install " "--remove ")))
+ (args (mapcar (lambda (arg)
+ (if (string-match-p rx arg)
+ (progn (push (split-string arg) rest)
+ nil)
+ arg))
+ args)))
+ (if rest
+ (apply #'append (delq nil args) rest)
+ args)))
+
+
+;;; 'Execute' actions
+
+(defvar guix-command-default-execute-arguments
+ (list
+ (guix-command-make-argument
+ :name "repl" :char ?r :doc "Run in Guix REPL")
+ (guix-command-make-argument
+ :name "shell" :char ?s :doc "Run in shell")
+ (guix-command-make-argument
+ :name "copy" :char ?c :doc "Copy command line"))
+ "List of default 'execute' action arguments.")
+
+(defvar guix-command-additional-execute-arguments
+ `((("graph")
+ ,(guix-command-make-argument
+ :name "view" :char ?v :doc "View graph")))
+ "Alist of guix commands and additional 'execute' action arguments.")
+
+(defun guix-command-execute-arguments (commands)
+ "Return a list of 'execute' action arguments for COMMANDS."
+ (mapcar (lambda (arg)
+ (guix-command-modify-argument arg
+ :action? t
+ :fun (guix-command-action-name
+ commands (guix-command-argument-name arg))))
+ (append guix-command-default-execute-arguments
+ (guix-assoc-value
+ guix-command-additional-execute-arguments commands))))
+
+(defvar guix-command-special-executors
+ '((("environment")
+ ("repl" . guix-run-environment-command-in-repl))
+ (("pull")
+ ("repl" . guix-run-pull-command-in-repl))
+ (("graph")
+ ("view" . guix-run-view-graph)))
+ "Alist of guix commands and alists of special executers for them.
+See also `guix-command-default-executors'.")
+
+(defvar guix-command-default-executors
+ '(("repl" . guix-run-command-in-repl)
+ ("shell" . guix-run-command-in-shell)
+ ("copy" . guix-copy-command-as-kill))
+ "Alist of default executers for action names.")
+
+(defun guix-command-executor (commands name)
+ "Return function to run command line arguments for guix COMMANDS."
+ (or (guix-assoc-value guix-command-special-executors commands name)
+ (guix-assoc-value guix-command-default-executors name)))
+
+(defun guix-run-environment-command-in-repl (args)
+ "Run 'guix ARGS ...' environment command in Guix REPL."
+ ;; As 'guix environment' usually tries to run another process, it may
+ ;; be fun but not wise to run this command in Geiser REPL.
+ (when (or (member "--dry-run" args)
+ (member "--search-paths" args)
+ (when (y-or-n-p
+ (format "'%s' command will spawn an external process.
+Do you really want to execute this command in Geiser REPL? "
+ (guix-command-string args)))
+ (message "May \"M-x shell-mode\" be with you!")
+ t))
+ (guix-run-command-in-repl args)))
+
+(defun guix-run-pull-command-in-repl (args)
+ "Run 'guix ARGS ...' pull command in Guix REPL.
+Perform pull-specific actions after operation, see
+`guix-after-pull-hook' and `guix-update-after-pull'."
+ (guix-eval-in-repl
+ (apply #'guix-make-guile-expression 'guix-command args)
+ nil 'pull))
+
+(defun guix-run-view-graph (args)
+ "Run 'guix ARGS ...' graph command, make the image and open it."
+ (let* ((graph-file (guix-dot-file-name))
+ (dot-args (guix-dot-arguments graph-file)))
+ (if (guix-eval-read (guix-make-guile-expression
+ 'pipe-guix-output args dot-args))
+ (guix-find-file graph-file)
+ (error "Couldn't create a graph"))))
+
+
+;;; Generating popups, actions, etc.
+
+(defmacro guix-command-define-popup-action (name &optional commands)
+ "Define NAME function to generate (if needed) and run popup for COMMANDS."
+ (declare (indent 1) (debug t))
+ (let* ((popup-fun (guix-command-symbol `(,@commands "popup")))
+ (doc (format "Call `%s' (generate it if needed)."
+ popup-fun)))
+ `(defun ,name (&optional arg)
+ ,doc
+ (interactive "P")
+ (unless (fboundp ',popup-fun)
+ (guix-command-generate-popup ',popup-fun ',commands))
+ (,popup-fun arg))))
+
+(defmacro guix-command-define-execute-action (name executor
+ &optional commands)
+ "Define NAME function to execute the current action for guix COMMANDS.
+EXECUTOR function is called with the current command line arguments."
+ (declare (indent 1) (debug t))
+ (let* ((arguments-fun (guix-command-symbol `(,@commands "arguments")))
+ (doc (format "Call `%s' with the current popup arguments."
+ executor)))
+ `(defun ,name (&rest args)
+ ,doc
+ (interactive (,arguments-fun))
+ (,executor (append ',commands
+ (guix-command-post-process-args args))))))
+
+(defun guix-command-generate-popup-actions (actions &optional commands)
+ "Generate 'popup' commands from ACTIONS arguments for guix COMMANDS."
+ (dolist (action actions)
+ (let ((fun (guix-command-argument-fun action)))
+ (unless (fboundp fun)
+ (eval `(guix-command-define-popup-action ,fun
+ ,(append commands
+ (list (guix-command-argument-name action)))))))))
+
+(defun guix-command-generate-execute-actions (actions &optional commands)
+ "Generate 'execute' commands from ACTIONS arguments for guix COMMANDS."
+ (dolist (action actions)
+ (let ((fun (guix-command-argument-fun action)))
+ (unless (fboundp fun)
+ (eval `(guix-command-define-execute-action ,fun
+ ,(guix-command-executor
+ commands (guix-command-argument-name action))
+ ,commands))))))
+
+(defun guix-command-generate-popup (name &optional commands)
+ "Define NAME popup with 'guix COMMANDS ...' interface."
+ (let* ((command (car commands))
+ (man-page (concat "guix" (and command (concat "-" command))))
+ (doc (format "Popup window for '%s' command."
+ (guix-concat-strings (cons "guix" commands)
+ " ")))
+ (args (guix-command-arguments commands))
+ (switches (guix-command-sort-arguments
+ (guix-command-switches args)))
+ (options (guix-command-sort-arguments
+ (guix-command-options args)))
+ (popup-actions (guix-command-sort-arguments
+ (guix-command-actions args)))
+ (execute-actions (unless popup-actions
+ (guix-command-execute-arguments commands)))
+ (actions (or popup-actions execute-actions)))
+ (if popup-actions
+ (guix-command-generate-popup-actions popup-actions commands)
+ (guix-command-generate-execute-actions execute-actions commands))
+ (eval
+ `(guix-define-popup ,name
+ ,doc
+ 'guix-commands
+ :man-page ,man-page
+ :switches ',(mapcar #'guix-command-switch->popup-switch switches)
+ :options ',(mapcar #'guix-command-option->popup-option options)
+ :actions ',(mapcar #'guix-command-action->popup-action actions)
+ :max-action-columns 4))))
+
+;;;###autoload (autoload 'guix "guix-command" "Popup window for 'guix'." t)
+(guix-command-define-popup-action guix)
+
+(defalias 'guix-edit-action #'guix-edit)
+
+
+(defvar guix-command-font-lock-keywords
+ (eval-when-compile
+ `((,(rx "("
+ (group "guix-command-define-"
+ (or "popup-action"
+ "execute-action"
+ "argument-improver"))
+ symbol-end
+ (zero-or-more blank)
+ (zero-or-one
+ (group (one-or-more (or (syntax word) (syntax symbol))))))
+ (1 font-lock-keyword-face)
+ (2 font-lock-function-name-face nil t)))))
+
+(font-lock-add-keywords 'emacs-lisp-mode guix-command-font-lock-keywords)
+
+(provide 'guix-command)
+
+;;; guix-command.el ends here
diff --git a/emacs/guix-config.el.in b/emacs/guix-config.el.in
new file mode 100644
index 0000000000..16434cecea
--- /dev/null
+++ b/emacs/guix-config.el.in
@@ -0,0 +1,40 @@
+;;; guix-config.el --- Compile-time configuration of Guix.
+
+;; Copyright © 2015 Mathieu Lirzin <mthl@openmailbox.org>
+
+;; This file is part of GNU Guix.
+
+;; GNU Guix 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.
+
+;; GNU Guix 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 <http://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(defconst guix-emacs-interface-directory
+ (replace-regexp-in-string "${prefix}" "@prefix@" "@emacsuidir@"))
+
+(defconst guix-state-directory
+ ;; This must match `NIX_STATE_DIR' as defined in `daemon.am'.
+ (or (getenv "NIX_STATE_DIR") "@guix_localstatedir@/guix"))
+
+(defvar guix-guile-program "@GUILE@"
+ "Name of the guile executable used for Guix REPL.
+May be either a string (the name of the executable) or a list of
+strings of the form:
+
+ (NAME . ARGS)
+
+Where ARGS is a list of arguments to the guile program.")
+
+(provide 'guix-config)
+
+;;; guix-config.el ends here
diff --git a/emacs/guix-external.el b/emacs/guix-external.el
new file mode 100644
index 0000000000..d233473abe
--- /dev/null
+++ b/emacs/guix-external.el
@@ -0,0 +1,72 @@
+;;; guix-external.el --- External programs -*- lexical-binding: t -*-
+
+;; Copyright © 2015 Alex Kost <alezost@gmail.com>
+
+;; This file is part of GNU Guix.
+
+;; GNU Guix 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.
+
+;; GNU Guix 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides auxiliary code for running external programs.
+
+;;; Code:
+
+(defgroup guix-external nil
+ "Settings for external programs."
+ :group 'guix)
+
+(defcustom guix-dot-program (executable-find "dot")
+ "Name of the 'dot' executable."
+ :type 'string
+ :group 'guix-external)
+
+(defcustom guix-dot-default-arguments
+ '("-Tpng")
+ "Default arguments for 'dot' program."
+ :type '(repeat string)
+ :group 'guix-external)
+
+(defcustom guix-dot-file-name-function #'guix-png-file-name
+ "Function used to define a file name of a temporary 'dot' file.
+The function is called without arguments."
+ :type '(choice (function-item guix-png-file-name)
+ (function :tag "Other function"))
+ :group 'guix-external)
+
+(defun guix-dot-arguments (output-file &rest args)
+ "Return a list of dot arguments for writing a graph into OUTPUT-FILE.
+If ARGS is nil, use `guix-dot-default-arguments'."
+ (or guix-dot-program
+ (error (concat "Couldn't find 'dot'.\n"
+ "Set guix-dot-program to a proper value")))
+ (apply #'list
+ guix-dot-program
+ (concat "-o" output-file)
+ (or args guix-dot-default-arguments)))
+
+(defun guix-dot-file-name ()
+ "Call `guix-dot-file-name-function'."
+ (funcall guix-dot-file-name-function))
+
+(defun guix-png-file-name ()
+ "Return '.png' file name in the `temporary-file-directory'."
+ (concat (make-temp-name
+ (concat (file-name-as-directory temporary-file-directory)
+ "graph-"))
+ ".png"))
+
+(provide 'guix-external)
+
+;;; guix-external.el ends here
diff --git a/emacs/guix-help-vars.el b/emacs/guix-help-vars.el
new file mode 100644
index 0000000000..8117d28f3e
--- /dev/null
+++ b/emacs/guix-help-vars.el
@@ -0,0 +1,108 @@
+;;; guix-help-vars.el --- Variables related to --help output
+
+;; Copyright © 2015 Alex Kost <alezost@gmail.com>
+
+;; This file is part of GNU Guix.
+
+;; GNU Guix 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.
+
+;; GNU Guix 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides regular expressions to parse various "guix
+;; ... --help" outputs and lists of non-receivable items (system types,
+;; hash formats, etc.).
+
+;;; Code:
+
+
+;;; Regexps for parsing "guix ..." outputs
+
+(defvar guix-help-parse-option-regexp
+ (rx bol " "
+ (zero-or-one (group "-" (not (any "- ")))
+ ",")
+ (one-or-more " ")
+ (group "--" (one-or-more (or wordchar "-")))
+ (group (zero-or-one "[")
+ (zero-or-one "="))
+ (zero-or-more (not space))
+ (one-or-more space)
+ (group (one-or-more any)))
+ "Common regexp used to find command options.")
+
+(defvar guix-help-parse-command-regexp
+ (rx bol " "
+ (group wordchar (one-or-more (or wordchar "-"))))
+ "Regexp used to find guix commands.
+'Command' means any option not prefixed with '-'. For example,
+guix subcommand, system action, importer, etc.")
+
+(defvar guix-help-parse-long-option-regexp
+ (rx (or " " ", ")
+ (group "--" (one-or-more (or wordchar "-"))
+ (zero-or-one "=")))
+ "Regexp used to find long options.")
+
+(defvar guix-help-parse-short-option-regexp
+ (rx bol (one-or-more blank)
+ "-" (group (not (any "- "))))
+ "Regexp used to find short options.")
+
+(defvar guix-help-parse-package-regexp
+ (rx bol (group (one-or-more (not blank))))
+ "Regexp used to find names of the packages.")
+
+(defvar guix-help-parse-list-regexp
+ (rx bol (zero-or-more blank) "- "
+ (group (one-or-more (or wordchar "-"))))
+ "Regexp used to find various lists (lint checkers, graph types).")
+
+(defvar guix-help-parse-regexp-group 1
+ "Parenthesized expression of regexps used to find commands and
+options.")
+
+
+;;; Non-receivable lists of system types, hash formats, etc.
+
+(defvar guix-help-system-types
+ '("x86_64-linux" "i686-linux" "armhf-linux" "mips64el-linux")
+ "List of supported systems.")
+
+(defvar guix-help-source-types
+ '("package" "all" "transitive")
+ "List of supported sources types.")
+
+(defvar guix-help-hash-formats
+ '("nix-base32" "base32" "base16" "hex" "hexadecimal")
+ "List of supported hash formats.")
+
+(defvar guix-help-refresh-subsets
+ '("core" "non-core")
+ "List of supported 'refresh' subsets.")
+
+(defvar guix-help-key-policies
+ '("interactive" "always" "never")
+ "List of supported key download policies.")
+
+(defvar guix-help-verify-options
+ '("repair" "contents")
+ "List of supported 'verify' options")
+
+(defvar guix-help-elpa-archives
+ '("gnu" "melpa" "melpa-stable")
+ "List of supported ELPA archives.")
+
+(provide 'guix-help-vars)
+
+;;; guix-help-vars.el ends here
diff --git a/emacs/guix-info.el b/emacs/guix-info.el
index f17ce01ab6..4bdd62a6a5 100644
--- a/emacs/guix-info.el
+++ b/emacs/guix-info.el
@@ -178,13 +178,13 @@ The order of displayed parameters is the same as in this list.")
(defun guix-info-get-insert-methods (entry-type param)
"Return list of insert methods for parameter PARAM of ENTRY-TYPE.
See `guix-info-insert-methods' for details."
- (guix-get-key-val guix-info-insert-methods
- entry-type param))
+ (guix-assq-value guix-info-insert-methods
+ entry-type param))
(defun guix-info-get-displayed-params (entry-type)
"Return parameters of ENTRY-TYPE that should be displayed."
- (guix-get-key-val guix-info-displayed-params
- entry-type))
+ (guix-assq-value guix-info-displayed-params
+ entry-type))
(defun guix-info-get-indent (&optional level)
"Return `guix-info-indent' \"multiplied\" by LEVEL spaces.
@@ -232,7 +232,7 @@ Use `guix-info-insert-ENTRY-TYPE-function' or
"Insert title and value of a PARAM at point.
ENTRY is alist with parameters and their values.
ENTRY-TYPE is a type of ENTRY."
- (let ((val (guix-get-key-val entry param)))
+ (let ((val (guix-assq-value entry param)))
(unless (and guix-info-ignore-empty-vals (null val))
(let* ((title (guix-get-param-title entry-type param))
(insert-methods (guix-info-get-insert-methods entry-type param))
@@ -492,12 +492,12 @@ filling them to fit the window."
(defun guix-package-info-insert-heading (entry)
"Insert the heading for package ENTRY.
Show package name, version, and `guix-package-info-heading-params'."
- (guix-format-insert (concat (guix-get-key-val entry 'name) " "
- (guix-get-key-val entry 'version))
+ (guix-format-insert (concat (guix-assq-value entry 'name) " "
+ (guix-assq-value entry 'version))
'guix-package-info-heading)
(insert "\n\n")
(mapc (lambda (param)
- (let ((val (guix-get-key-val entry param))
+ (let ((val (guix-assq-value entry param))
(face (guix-get-symbol (symbol-name param)
'info 'package)))
(when val
@@ -587,10 +587,10 @@ If nil, insert installed info in a default way.")
(defun guix-package-info-insert-outputs (outputs entry)
"Insert OUTPUTS from package ENTRY at point."
- (and (guix-get-key-val entry 'obsolete)
+ (and (guix-assq-value entry 'obsolete)
(guix-package-info-insert-obsolete-text))
- (and (guix-get-key-val entry 'non-unique)
- (guix-get-key-val entry 'installed)
+ (and (guix-assq-value entry 'non-unique)
+ (guix-assq-value entry 'installed)
(guix-package-info-insert-non-unique-text
(guix-get-full-name entry)))
(insert "\n")
@@ -617,11 +617,11 @@ If nil, insert installed info in a default way.")
Make some fancy text with buttons and additional stuff if the
current OUTPUT is installed (if there is such output in
`installed' parameter of a package ENTRY)."
- (let* ((installed (guix-get-key-val entry 'installed))
- (obsolete (guix-get-key-val entry 'obsolete))
+ (let* ((installed (guix-assq-value entry 'installed))
+ (obsolete (guix-assq-value entry 'obsolete))
(installed-entry (cl-find-if
(lambda (entry)
- (string= (guix-get-key-val entry 'output)
+ (string= (guix-assq-value entry 'output)
output))
installed))
(action-type (if installed-entry 'delete 'install)))
@@ -655,8 +655,8 @@ ENTRY is an alist with package info."
(current-buffer)))
(concat type-str " '" full-name "'")
'action-type type
- 'id (or (guix-get-key-val entry 'package-id)
- (guix-get-key-val entry 'id))
+ 'id (or (guix-assq-value entry 'package-id)
+ (guix-assq-value entry 'id))
'output output)))
(defun guix-package-info-insert-output-path (path &optional _)
@@ -720,7 +720,7 @@ PACKAGE-ID is an ID of the package which source to show."
(entries (cl-substitute-if
new-entry
(lambda (entry)
- (equal (guix-get-key-val entry 'id)
+ (equal (guix-assq-value entry 'id)
entry-id))
guix-entries
:count 1)))
@@ -746,9 +746,9 @@ SOURCE is a list of URLs."
(guix-info-insert-indent)
(if (null source)
(guix-format-insert nil)
- (let* ((source-file (guix-get-key-val entry 'source-file))
- (entry-id (guix-get-key-val entry 'id))
- (package-id (or (guix-get-key-val entry 'package-id)
+ (let* ((source-file (guix-assq-value entry 'source-file))
+ (entry-id (guix-assq-value entry 'id))
+ (package-id (or (guix-assq-value entry 'package-id)
entry-id)))
(if (null source-file)
(guix-info-insert-action-button
@@ -798,13 +798,13 @@ If nil, insert output in a default way.")
"Insert output VERSION and obsolete text if needed at point."
(guix-info-insert-val-default version
'guix-package-info-version)
- (and (guix-get-key-val entry 'obsolete)
+ (and (guix-assq-value entry 'obsolete)
(guix-package-info-insert-obsolete-text)))
(defun guix-output-info-insert-output (output entry)
"Insert OUTPUT and action buttons at point."
- (let* ((installed (guix-get-key-val entry 'installed))
- (obsolete (guix-get-key-val entry 'obsolete))
+ (let* ((installed (guix-assq-value entry 'installed))
+ (obsolete (guix-assq-value entry 'obsolete))
(action-type (if installed 'delete 'install)))
(guix-info-insert-val-default
output
@@ -874,7 +874,7 @@ If nil, insert generation in a default way.")
(guix-switch-to-generation guix-profile (button-get btn 'number)
(current-buffer)))
"Switch to this generation (make it the current one)"
- 'number (guix-get-key-val entry 'number))))
+ 'number (guix-assq-value entry 'number))))
(provide 'guix-info)
diff --git a/emacs/guix-init.el.in b/emacs/guix-init.el
index 728bc375c2..3a727c7eb6 100644
--- a/emacs/guix-init.el.in
+++ b/emacs/guix-init.el
@@ -1,9 +1,5 @@
(require 'guix-autoloads)
-(defvar guix-load-path
- (replace-regexp-in-string "${prefix}" "@prefix@" "@emacsuidir@")
- "Directory with scheme files for \"guix.el\" package.")
-
(defcustom guix-package-enable-at-startup t
"If non-nil, activate Emacs packages installed in a user profile.
Set this variable to nil before requiring `guix-init' file to
diff --git a/emacs/guix-list.el b/emacs/guix-list.el
index e84d60a0aa..9796464dbf 100644
--- a/emacs/guix-list.el
+++ b/emacs/guix-list.el
@@ -1,6 +1,6 @@
;;; guix-list.el --- List buffers for displaying entries -*- lexical-binding: t -*-
-;; Copyright © 2014 Alex Kost <alezost@gmail.com>
+;; Copyright © 2014, 2015 Alex Kost <alezost@gmail.com>
;; This file is part of GNU Guix.
@@ -110,13 +110,13 @@ parameters and their values).")
(defun guix-list-get-param-title (entry-type param)
"Return title of an ENTRY-TYPE entry parameter PARAM."
- (or (guix-get-key-val guix-list-column-titles
- entry-type param)
+ (or (guix-assq-value guix-list-column-titles
+ entry-type param)
(guix-get-param-title entry-type param)))
(defun guix-list-get-column-format (entry-type)
"Return column format for ENTRY-TYPE."
- (guix-get-key-val guix-list-column-format entry-type))
+ (guix-assq-value guix-list-column-format entry-type))
(defun guix-list-get-displayed-params (entry-type)
"Return list of parameters of ENTRY-TYPE that should be displayed."
@@ -170,7 +170,7 @@ ENTRIES should have a form of `guix-entries'."
Values are taken from ENTRIES which should have the form of
`guix-entries'."
(mapcar (lambda (entry)
- (list (guix-get-key-val entry 'id)
+ (list (guix-assq-value entry 'id)
(guix-list-get-tabulated-entry entry entry-type)))
entries))
@@ -180,9 +180,9 @@ Parameters are taken from ENTRY of ENTRY-TYPE."
(guix-list-make-tabulated-vector
entry-type
(lambda (param _)
- (let ((val (guix-get-key-val entry param))
- (fun (guix-get-key-val guix-list-column-value-methods
- entry-type param)))
+ (let ((val (guix-assq-value entry param))
+ (fun (guix-assq-value guix-list-column-value-methods
+ entry-type param)))
(if fun
(funcall fun val entry)
(guix-get-string val))))))
@@ -221,7 +221,7 @@ VAL may be nil."
(guix-package-list-mode
(guix-list-current-id))
(guix-output-list-mode
- (guix-get-key-val (guix-list-current-entry) 'package-id))))
+ (guix-assq-value (guix-list-current-entry) 'package-id))))
(defun guix-list-for-each-line (fun &rest args)
"Call FUN with ARGS for each entry line."
@@ -262,7 +262,7 @@ ARGS is a list of additional values.")
(defsubst guix-list-get-mark (name)
"Return mark character by its NAME."
- (or (guix-get-key-val guix-list-mark-alist name)
+ (or (guix-assq-value guix-list-mark-alist name)
(error "Mark '%S' not found" name)))
(defsubst guix-list-get-mark-string (name)
@@ -355,8 +355,8 @@ With ARG, unmark all lines."
"Put marks according to `guix-list-mark-alist'."
(guix-list-for-each-line
(lambda ()
- (let ((mark-name (car (guix-get-key-val guix-list-marked
- (guix-list-current-id)))))
+ (let ((mark-name (car (guix-assq-value guix-list-marked
+ (guix-list-current-id)))))
(tabulated-list-put-tag
(guix-list-get-mark-string (or mark-name 'empty)))))))
@@ -472,7 +472,7 @@ With prefix (if ARG is non-nil), describe entries marked with any mark."
(defun guix-list-edit-package ()
"Go to the location of the current package."
(interactive)
- (guix-edit-package (guix-list-current-package-id)))
+ (guix-edit (guix-list-current-package-id)))
;;; Displaying packages
@@ -524,16 +524,16 @@ likely)."
Colorize it with `guix-package-list-installed' or
`guix-package-list-obsolete' if needed."
(guix-get-string name
- (cond ((guix-get-key-val entry 'obsolete)
+ (cond ((guix-assq-value entry 'obsolete)
'guix-package-list-obsolete)
- ((guix-get-key-val entry 'installed)
+ ((guix-assq-value entry 'installed)
'guix-package-list-installed))))
(defun guix-package-list-get-installed-outputs (installed &optional _)
"Return string with outputs from INSTALLED entries."
(guix-get-string
(mapcar (lambda (entry)
- (guix-get-key-val entry 'output))
+ (guix-assq-value entry 'output))
installed)))
(defun guix-package-list-marking-check ()
@@ -562,7 +562,7 @@ be separated with \",\")."
(interactive "P")
(guix-package-list-marking-check)
(let* ((entry (guix-list-current-entry))
- (all (guix-get-key-val entry 'outputs))
+ (all (guix-assq-value entry 'outputs))
(installed (guix-get-installed-outputs entry))
(available (cl-set-difference all installed :test #'string=)))
(or available
@@ -597,7 +597,7 @@ be separated with \",\")."
(installed (guix-get-installed-outputs entry)))
(or installed
(user-error "This package is not installed"))
- (when (or (guix-get-key-val entry 'obsolete)
+ (when (or (guix-assq-value entry 'obsolete)
(y-or-n-p "This package is not obsolete. Try to upgrade it anyway? "))
(guix-package-list-mark-outputs
'upgrade installed
@@ -611,14 +611,14 @@ accept an entry as argument."
(guix-package-list-marking-check)
(let ((obsolete (cl-remove-if-not
(lambda (entry)
- (guix-get-key-val entry 'obsolete))
+ (guix-assq-value entry 'obsolete))
guix-entries)))
(guix-list-for-each-line
(lambda ()
(let* ((id (guix-list-current-id))
(entry (cl-find-if
(lambda (entry)
- (equal id (guix-get-key-val entry 'id)))
+ (equal id (guix-assq-value entry 'id)))
obsolete)))
(when entry
(funcall fun entry)))))))
@@ -682,7 +682,7 @@ The specification is suitable for `guix-process-package-actions'."
(interactive)
(guix-package-list-marking-check)
(let* ((entry (guix-list-current-entry))
- (installed (guix-get-key-val entry 'installed)))
+ (installed (guix-assq-value entry 'installed)))
(if installed
(user-error "This output is already installed")
(guix-list--mark 'install t))))
@@ -692,7 +692,7 @@ The specification is suitable for `guix-process-package-actions'."
(interactive)
(guix-package-list-marking-check)
(let* ((entry (guix-list-current-entry))
- (installed (guix-get-key-val entry 'installed)))
+ (installed (guix-assq-value entry 'installed)))
(if installed
(guix-list--mark 'delete t)
(user-error "This output is not installed"))))
@@ -702,10 +702,10 @@ The specification is suitable for `guix-process-package-actions'."
(interactive)
(guix-package-list-marking-check)
(let* ((entry (guix-list-current-entry))
- (installed (guix-get-key-val entry 'installed)))
+ (installed (guix-assq-value entry 'installed)))
(or installed
(user-error "This output is not installed"))
- (when (or (guix-get-key-val entry 'obsolete)
+ (when (or (guix-assq-value entry 'obsolete)
(y-or-n-p "This output is not obsolete. Try to upgrade it anyway? "))
(guix-list--mark 'upgrade t))))
@@ -777,8 +777,8 @@ VAL is a boolean value."
"Switch current profile to the generation at point."
(interactive)
(let* ((entry (guix-list-current-entry))
- (current (guix-get-key-val entry 'current))
- (number (guix-get-key-val entry 'number)))
+ (current (guix-assq-value entry 'current))
+ (number (guix-assq-value entry 'number)))
(if current
(user-error "This generation is already the current one")
(guix-switch-to-generation guix-profile number (current-buffer)))))
diff --git a/emacs/guix-main.scm b/emacs/guix-main.scm
index e0dc683d88..c9b84d36d9 100644
--- a/emacs/guix-main.scm
+++ b/emacs/guix-main.scm
@@ -1,5 +1,5 @@
;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2014 Alex Kost <alezost@gmail.com>
+;;; Copyright © 2014, 2015 Alex Kost <alezost@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -45,6 +45,7 @@
(use-modules
(ice-9 vlist)
(ice-9 match)
+ (ice-9 popen)
(srfi srfi-1)
(srfi srfi-2)
(srfi srfi-11)
@@ -57,6 +58,8 @@
(guix licenses)
(guix utils)
(guix ui)
+ (guix scripts graph)
+ (guix scripts lint)
(guix scripts package)
(guix scripts pull)
(gnu packages))
@@ -68,7 +71,14 @@
(define (list-maybe obj)
(if (list? obj) obj (list obj)))
-(define full-name->name+version package-name->name+version)
+(define (full-name->name+version spec)
+ "Given package specification SPEC with or without output,
+return two values: name and version. For example, for SPEC
+\"foo-0.9.1b:lib\", return \"foo\" and \"0.9.1b\"."
+ (let-values (((name version output)
+ (package-specification->name+version+output spec)))
+ (values name version)))
+
(define (name+version->full-name name version)
(string-append name "-" version))
@@ -244,6 +254,10 @@ Example:
(filter-map (match-lambda
((_ (? package? package))
(package-full-name package))
+ ((_ (? package? package) output)
+ (make-package-specification (package-name package)
+ (package-version package)
+ output))
(_ #f))
inputs))
@@ -279,7 +293,7 @@ Example:
(license . ,package-license-names)
(source . ,package-source-names)
(synopsis . ,package-synopsis)
- (description . ,package-description)
+ (description . ,package-description-string)
(home-url . ,package-home-page)
(outputs . ,package-outputs)
(non-unique . ,(negate package-unique?))
@@ -887,9 +901,10 @@ GENERATIONS is a list of generation numbers."
(with-store store
(delete-generations store profile generations)))
-(define (package-location-string package-id)
- "Return a location string of a package PACKAGE-ID."
- (and-let* ((package (package-by-id package-id))
+(define (package-location-string id-or-name)
+ "Return a location string of a package with ID-OR-NAME."
+ (and-let* ((package (or (package-by-id id-or-name)
+ (first (packages-by-name id-or-name))))
(location (package-location package)))
(location->string location)))
@@ -927,3 +942,54 @@ GENERATIONS is a list of generation numbers."
(build-derivations store derivations))
(format #t "The source store path: ~a~%"
(package-source-derivation->store-path derivation))))))
+
+
+;;; Executing guix commands
+
+(define (guix-command . args)
+ "Run 'guix ARGS ...' command."
+ (catch 'quit
+ (lambda () (apply run-guix args))
+ (const #t)))
+
+(define (guix-command-output . args)
+ "Return string with 'guix ARGS ...' output."
+ (with-output-to-string
+ (lambda () (apply guix-command args))))
+
+(define (help-string . commands)
+ "Return string with 'guix COMMANDS ... --help' output."
+ (apply guix-command-output `(,@commands "--help")))
+
+(define (pipe-guix-output guix-args command-args)
+ "Run 'guix GUIX-ARGS ...' command and pipe its output to a shell command
+defined by COMMAND-ARGS.
+Return #t if the shell command was executed successfully."
+ (let ((pipe (apply open-pipe* OPEN_WRITE command-args)))
+ (with-output-to-port pipe
+ (lambda () (apply guix-command guix-args)))
+ (zero? (status:exit-val (close-pipe pipe)))))
+
+
+;;; Lists of packages, lint checkers, etc.
+
+(define (graph-type-names)
+ "Return a list of names of available graph node types."
+ (map node-type-name %node-types))
+
+(define (lint-checker-names)
+ "Return a list of names of available lint checkers."
+ (map (lambda (checker)
+ (symbol->string (lint-checker-name checker)))
+ %checkers))
+
+(define (package-names)
+ "Return a list of names of available packages."
+ (delete-duplicates
+ (fold-packages (lambda (pkg res)
+ (cons (package-name pkg) res))
+ '())))
+
+;; See the comment to 'guix-package-names' function in "guix-popup.el".
+(define (package-names-lists)
+ (map list (package-names)))
diff --git a/emacs/guix-messages.el b/emacs/guix-messages.el
index bd985a0670..2bf99de6fa 100644
--- a/emacs/guix-messages.el
+++ b/emacs/guix-messages.el
@@ -1,6 +1,6 @@
;;; guix-messages.el --- Minibuffer messages
-;; Copyright © 2014 Alex Kost <alezost@gmail.com>
+;; Copyright © 2014, 2015 Alex Kost <alezost@gmail.com>
;; This file is part of GNU Guix.
@@ -186,14 +186,14 @@
(defun guix-result-message (profile entries entry-type
search-type search-vals)
"Display an appropriate message after displaying ENTRIES."
- (let* ((type-spec (guix-get-key-val guix-messages
- entry-type search-type))
+ (let* ((type-spec (guix-assq-value guix-messages
+ entry-type search-type))
(fun-or-count-spec (car type-spec)))
(if (functionp fun-or-count-spec)
(funcall fun-or-count-spec profile entries search-vals)
(let* ((count (length entries))
(count-key (if (> count 1) 'many count))
- (msg-spec (guix-get-key-val type-spec count-key))
+ (msg-spec (guix-assq-value type-spec count-key))
(msg (car msg-spec))
(args (cdr msg-spec)))
(mapc (lambda (subst)
diff --git a/emacs/guix-pcomplete.el b/emacs/guix-pcomplete.el
index 9ec563cf52..4743be59bd 100644
--- a/emacs/guix-pcomplete.el
+++ b/emacs/guix-pcomplete.el
@@ -28,59 +28,7 @@
(require 'pcmpl-unix)
(require 'cl-lib)
(require 'guix-utils)
-
-
-;;; Regexps for parsing various "guix ..." outputs
-
-(defvar guix-pcomplete-parse-package-regexp
- (rx bol (group (one-or-more (not blank))))
- "Regexp used to find names of the packages.")
-
-(defvar guix-pcomplete-parse-command-regexp
- (rx bol " "
- (group wordchar (one-or-more (or wordchar "-"))))
- "Regexp used to find guix commands.
-'Command' means any option not prefixed with '-'. For example,
-guix subcommand, system action, importer, etc.")
-
-(defvar guix-pcomplete-parse-long-option-regexp
- (rx (or " " ", ")
- (group "--" (one-or-more (or wordchar "-"))
- (zero-or-one "=")))
- "Regexp used to find long options.")
-
-(defvar guix-pcomplete-parse-short-option-regexp
- (rx bol (one-or-more blank)
- "-" (group (not (any "- "))))
- "Regexp used to find short options.")
-
-(defvar guix-pcomplete-parse-list-regexp
- (rx bol (zero-or-more blank) "- "
- (group (one-or-more (or wordchar "-"))))
- "Regexp used to find various lists (lint checkers, graph types).")
-
-(defvar guix-pcomplete-parse-regexp-group 1
- "Parenthesized expression of regexps used to find commands and
-options.")
-
-
-;;; Non-receivable completions
-
-(defvar guix-pcomplete-systems
- '("x86_64-linux" "i686-linux" "armhf-linux" "mips64el-linux")
- "List of supported systems.")
-
-(defvar guix-pcomplete-hash-formats
- '("nix-base32" "base32" "base16" "hex" "hexadecimal")
- "List of supported hash formats.")
-
-(defvar guix-pcomplete-refresh-subsets
- '("core" "non-core")
- "List of supported 'refresh' subsets.")
-
-(defvar guix-pcomplete-key-policies
- '("interactive" "always" "never")
- "List of supported key download policies.")
+(require 'guix-help-vars)
;;; Interacting with guix
@@ -105,9 +53,8 @@ Return a list of strings matching REGEXP.
GROUP specifies a parenthesized expression used in REGEXP."
(with-temp-buffer
(apply #'guix-pcomplete-run-guix args)
- (goto-char (point-min))
(let (result)
- (while (re-search-forward regexp nil t)
+ (guix-while-search regexp
(push (match-string-no-properties group) result))
(nreverse result))))
@@ -129,7 +76,7 @@ function call is returned."
(let* ((args '("--help"))
(args (if command (cons command args) args))
(res (apply #'guix-pcomplete-run-guix-and-search
- ,regexp guix-pcomplete-parse-regexp-group args)))
+ ,regexp guix-help-parse-regexp-group args)))
,(if filter
`(funcall ,filter res)
'res))))
@@ -138,23 +85,23 @@ function call is returned."
"If COMMAND is nil, return a list of available guix commands.
If COMMAND is non-nil (it should be a string), return available
subcommands, actions, etc. for this guix COMMAND."
- guix-pcomplete-parse-command-regexp)
+ guix-help-parse-command-regexp)
(guix-pcomplete-define-options-finder guix-pcomplete-long-options
"Return a list of available long options for guix COMMAND."
- guix-pcomplete-parse-long-option-regexp)
+ guix-help-parse-long-option-regexp)
(guix-pcomplete-define-options-finder guix-pcomplete-short-options
"Return a string with available short options for guix COMMAND."
- guix-pcomplete-parse-short-option-regexp
+ guix-help-parse-short-option-regexp
(lambda (list)
- (mapconcat #'identity list "")))
+ (guix-concat-strings list "")))
(guix-memoized-defun guix-pcomplete-all-packages ()
"Return a list of all available Guix packages."
(guix-pcomplete-run-guix-and-search
- guix-pcomplete-parse-package-regexp
- guix-pcomplete-parse-regexp-group
+ guix-help-parse-package-regexp
+ guix-help-parse-regexp-group
"package" "--list-available"))
(guix-memoized-defun guix-pcomplete-installed-packages (&optional profile)
@@ -163,22 +110,22 @@ subcommands, actions, etc. for this guix COMMAND."
(list (concat "--profile=" profile))))
(args (append '("package" "--list-installed") args)))
(apply #'guix-pcomplete-run-guix-and-search
- guix-pcomplete-parse-package-regexp
- guix-pcomplete-parse-regexp-group
+ guix-help-parse-package-regexp
+ guix-help-parse-regexp-group
args)))
(guix-memoized-defun guix-pcomplete-lint-checkers ()
"Return a list of all available lint checkers."
(guix-pcomplete-run-guix-and-search
- guix-pcomplete-parse-list-regexp
- guix-pcomplete-parse-regexp-group
+ guix-help-parse-list-regexp
+ guix-help-parse-regexp-group
"lint" "--list-checkers"))
(guix-memoized-defun guix-pcomplete-graph-types ()
"Return a list of all available graph types."
(guix-pcomplete-run-guix-and-search
- guix-pcomplete-parse-list-regexp
- guix-pcomplete-parse-regexp-group
+ guix-help-parse-list-regexp
+ guix-help-parse-regexp-group
"graph" "--list-types"))
@@ -284,7 +231,7 @@ INPUT is the current partially completed string."
((option? "-L" "--load-path")
(complete* (pcomplete-dirs)))
((string= "--key-download" option)
- (complete* guix-pcomplete-key-policies))
+ (complete* guix-help-key-policies))
((command? "package")
(cond
@@ -313,7 +260,7 @@ INPUT is the current partially completed string."
((and (command? "archive" "build" "size")
(option? "-s" "--system"))
- (complete* guix-pcomplete-systems))
+ (complete* guix-help-system-types))
((and (command? "build")
(option? "-r" "--root"))
@@ -329,7 +276,7 @@ INPUT is the current partially completed string."
((and (command? "hash" "download")
(option? "-f" "--format"))
- (complete* guix-pcomplete-hash-formats))
+ (complete* guix-help-hash-formats))
((and (command? "lint")
(option? "-c" "--checkers"))
@@ -342,7 +289,7 @@ INPUT is the current partially completed string."
((and (command? "refresh")
(option? "-s" "--select"))
- (complete* guix-pcomplete-refresh-subsets))
+ (complete* guix-help-refresh-subsets))
((and (command? "size")
(option? "-m" "--map-file"))
diff --git a/emacs/guix-popup.el b/emacs/guix-popup.el
new file mode 100644
index 0000000000..59e98a352e
--- /dev/null
+++ b/emacs/guix-popup.el
@@ -0,0 +1,48 @@
+;;; guix-popup.el --- Popup windows library
+
+;; Copyright © 2015 Alex Kost <alezost@gmail.com>
+
+;; This file is part of GNU Guix.
+
+;; GNU Guix 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.
+
+;; GNU Guix 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides `guix-define-popup' macro which is just an alias
+;; to `magit-define-popup'. According to the manual (info
+;; "(magit-popup) Defining prefix and suffix commands") `magit-popup'
+;; library will eventually be superseded by a more general library.
+
+;;; Code:
+
+(require 'magit-popup)
+
+(defalias 'guix-define-popup 'magit-define-popup)
+
+(defvar guix-popup-font-lock-keywords
+ (eval-when-compile
+ `((,(rx "("
+ (group "guix-define-popup")
+ symbol-end
+ (zero-or-more blank)
+ (zero-or-one
+ (group (one-or-more (or (syntax word) (syntax symbol))))))
+ (1 font-lock-keyword-face)
+ (2 font-lock-function-name-face nil t)))))
+
+(font-lock-add-keywords 'emacs-lisp-mode guix-popup-font-lock-keywords)
+
+(provide 'guix-popup)
+
+;;; guix-popup.el ends here
diff --git a/emacs/guix-prettify.el b/emacs/guix-prettify.el
index b01495c86b..24dfbb33e2 100644
--- a/emacs/guix-prettify.el
+++ b/emacs/guix-prettify.el
@@ -1,6 +1,6 @@
;;; guix-prettify.el --- Prettify Guix store file names
-;; Copyright © 2014 Alex Kost <alezost@gmail.com>
+;; Copyright © 2014, 2015 Alex Kost <alezost@gmail.com>
;; This file is part of GNU Guix.
@@ -47,9 +47,12 @@
;;; Code:
+(require 'guix-utils)
+
(defgroup guix-prettify nil
"Prettify Guix store file names."
:prefix "guix-prettify-"
+ :group 'guix
:group 'font-lock
:group 'convenience)
@@ -136,13 +139,11 @@ enabling/disabling `guix-prettify-mode'. If nil, do nothing.")
(remove-text-properties (point-min)
(point-max)
'(composition nil))
- (save-excursion
- (goto-char (point-min))
- (while (re-search-forward guix-prettify-regexp nil t)
- (remove-text-properties
- (match-beginning guix-prettify-regexp-group)
- (match-end guix-prettify-regexp-group)
- '(composition nil))))))))
+ (guix-while-search guix-prettify-regexp
+ (remove-text-properties
+ (match-beginning guix-prettify-regexp-group)
+ (match-end guix-prettify-regexp-group)
+ '(composition nil)))))))
;;;###autoload
(define-minor-mode guix-prettify-mode
diff --git a/emacs/guix-profiles.el.in b/emacs/guix-profiles.el
index 1e43707b68..1a41745512 100644
--- a/emacs/guix-profiles.el.in
+++ b/emacs/guix-profiles.el
@@ -19,12 +19,14 @@
;;; Code:
+(require 'guix-config)
+
(defvar guix-user-profile
(expand-file-name "~/.guix-profile")
"User profile.")
(defvar guix-default-profile
- (concat (or (getenv "NIX_STATE_DIR") "@guix_localstatedir@/guix")
+ (concat guix-state-directory
"/profiles/per-user/"
(getenv "USER")
"/guix-profile")
diff --git a/emacs/guix-read.el b/emacs/guix-read.el
new file mode 100644
index 0000000000..5a7201c3aa
--- /dev/null
+++ b/emacs/guix-read.el
@@ -0,0 +1,176 @@
+;;; guix-read.el --- Minibuffer readers
+
+;; Copyright © 2015 Alex Kost <alezost@gmail.com>
+
+;; This file is part of GNU Guix.
+
+;; GNU Guix 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.
+
+;; GNU Guix 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file provides functions to prompt a user for packages, system
+;; types, hash formats and other guix related stuff.
+
+;;; Code:
+
+(require 'guix-help-vars)
+(require 'guix-utils)
+(require 'guix-base)
+
+(defun guix-read-file-name (prompt &optional dir default-filename
+ mustmatch initial predicate)
+ "Read file name.
+This function is similar to `read-file-name' except it also
+expands the file name."
+ (expand-file-name (read-file-name prompt dir default-filename
+ mustmatch initial predicate)))
+
+(defmacro guix-define-reader (name read-fun completions prompt)
+ "Define NAME function to read from minibuffer.
+READ-FUN may be `completing-read', `completing-read-multiple' or
+another function with the same arguments."
+ `(defun ,name (&optional prompt initial-contents)
+ (,read-fun ,(if prompt
+ `(or prompt ,prompt)
+ 'prompt)
+ ,completions nil nil initial-contents)))
+
+(defmacro guix-define-readers (&rest args)
+ "Define reader functions.
+
+ARGS should have a form [KEYWORD VALUE] ... The following
+keywords are available:
+
+ - `completions-var' - variable used to get completions.
+
+ - `completions-getter' - function used to get completions.
+
+ - `single-reader', `single-prompt' - name of a function to read
+ a single value, and a prompt for it.
+
+ - `multiple-reader', `multiple-prompt' - name of a function to
+ read multiple values, and a prompt for it.
+
+ - `multiple-separator' - if specified, another
+ `<multiple-reader-name>-string' function returning a string
+ of multiple values separated the specified separator will be
+ defined."
+ (let (completions-var
+ completions-getter
+ single-reader
+ single-prompt
+ multiple-reader
+ multiple-prompt
+ multiple-separator)
+
+ ;; Process the keyword args.
+ (while (keywordp (car args))
+ (pcase (pop args)
+ (`:completions-var (setq completions-var (pop args)))
+ (`:completions-getter (setq completions-getter (pop args)))
+ (`:single-reader (setq single-reader (pop args)))
+ (`:single-prompt (setq single-prompt (pop args)))
+ (`:multiple-reader (setq multiple-reader (pop args)))
+ (`:multiple-prompt (setq multiple-prompt (pop args)))
+ (`:multiple-separator (setq multiple-separator (pop args)))
+ (_ (pop args))))
+
+ (let ((completions
+ (cond ((and completions-var completions-getter)
+ `(or ,completions-var
+ (setq ,completions-var
+ (funcall ',completions-getter))))
+ (completions-var
+ completions-var)
+ (completions-getter
+ `(funcall ',completions-getter)))))
+ `(progn
+ ,(when (and completions-var
+ (not (boundp completions-var)))
+ `(defvar ,completions-var nil))
+
+ ,(when single-reader
+ `(guix-define-reader ,single-reader completing-read
+ ,completions ,single-prompt))
+
+ ,(when multiple-reader
+ `(guix-define-reader ,multiple-reader completing-read-multiple
+ ,completions ,multiple-prompt))
+
+ ,(when (and multiple-reader multiple-separator)
+ (let ((name (intern (concat (symbol-name multiple-reader)
+ "-string"))))
+ `(defun ,name (&optional prompt initial-contents)
+ (guix-concat-strings
+ (,multiple-reader prompt initial-contents)
+ ,multiple-separator))))))))
+
+(guix-define-readers
+ :completions-var guix-help-system-types
+ :single-reader guix-read-system-type
+ :single-prompt "System type: ")
+
+(guix-define-readers
+ :completions-var guix-help-source-types
+ :single-reader guix-read-source-type
+ :single-prompt "Source type: ")
+
+(guix-define-readers
+ :completions-var guix-help-hash-formats
+ :single-reader guix-read-hash-format
+ :single-prompt "Hash format: ")
+
+(guix-define-readers
+ :completions-var guix-help-refresh-subsets
+ :single-reader guix-read-refresh-subset
+ :single-prompt "Refresh subset: ")
+
+(guix-define-readers
+ :completions-var guix-help-key-policies
+ :single-reader guix-read-key-policy
+ :single-prompt "Key policy: ")
+
+(guix-define-readers
+ :completions-var guix-help-elpa-archives
+ :single-reader guix-read-elpa-archive
+ :single-prompt "ELPA archive: ")
+
+(guix-define-readers
+ :completions-var guix-help-verify-options
+ :multiple-reader guix-read-verify-options
+ :multiple-prompt "Verify option,s: "
+ :multiple-separator ",")
+
+(guix-define-readers
+ :completions-getter guix-graph-type-names
+ :single-reader guix-read-graph-type
+ :single-prompt "Graph node type: ")
+
+(guix-define-readers
+ :completions-getter guix-lint-checker-names
+ :multiple-reader guix-read-lint-checker-names
+ :multiple-prompt "Linter,s: "
+ :multiple-separator ",")
+
+(guix-define-readers
+ :completions-getter guix-package-names
+ :single-reader guix-read-package-name
+ :single-prompt "Package: "
+ :multiple-reader guix-read-package-names
+ :multiple-prompt "Package,s: "
+ :multiple-separator " ")
+
+(provide 'guix-read)
+
+;;; guix-read.el ends here
diff --git a/emacs/guix-utils.el b/emacs/guix-utils.el
index dc0c58a114..c1ce954f8f 100644
--- a/emacs/guix-utils.el
+++ b/emacs/guix-utils.el
@@ -128,6 +128,53 @@ split it into several short lines."
(fill-region (point-min) (point-max)))
(buffer-string)))
+(defun guix-concat-strings (strings separator &optional location)
+ "Return new string by concatenating STRINGS with SEPARATOR.
+If LOCATION is a symbol `head', add another SEPARATOR to the
+beginning of the returned string; if `tail' - add SEPARATOR to
+the end of the string; if nil, do not add SEPARATOR; otherwise
+add both to the end and to the beginning."
+ (let ((str (mapconcat #'identity strings separator)))
+ (cond ((null location)
+ str)
+ ((eq location 'head)
+ (concat separator str))
+ ((eq location 'tail)
+ (concat str separator))
+ (t
+ (concat separator str separator)))))
+
+(defun guix-shell-quote-argument (argument)
+ "Quote shell command ARGUMENT.
+This function is similar to `shell-quote-argument', but less strict."
+ (if (equal argument "")
+ "''"
+ (replace-regexp-in-string
+ "\n" "'\n'"
+ (replace-regexp-in-string
+ (rx (not (any alnum "-=,./\n"))) "\\\\\\&" argument))))
+
+(defun guix-command-symbol (&optional args)
+ "Return symbol by concatenating 'guix' and ARGS (strings)."
+ (intern (guix-concat-strings (cons "guix" args) "-")))
+
+(defun guix-command-string (&optional args)
+ "Return 'guix ARGS ...' string with quoted shell arguments."
+ (let ((args (mapcar #'guix-shell-quote-argument args)))
+ (guix-concat-strings (cons "guix" args) " ")))
+
+(defun guix-copy-as-kill (string &optional no-message?)
+ "Put STRING into `kill-ring'.
+If NO-MESSAGE? is non-nil, do not display a message about it."
+ (kill-new string)
+ (unless no-message?
+ (message "'%s' has been added to kill ring." string)))
+
+(defun guix-copy-command-as-kill (args &optional no-message?)
+ "Put 'guix ARGS ...' string into `kill-ring'.
+See also `guix-copy-as-kill'."
+ (guix-copy-as-kill (guix-command-string args) no-message?))
+
(defun guix-completing-read-multiple (prompt table &optional predicate
require-match initial-input
hist def inherit-input-method)
@@ -146,20 +193,56 @@ Return time value."
(require 'org)
(org-read-date nil t nil prompt))
-(defun guix-get-key-val (alist &rest keys)
- "Return value from ALIST by KEYS.
-ALIST is alist of alists of alists ... which can be consecutively
-accessed with KEYS."
- (let ((val alist))
- (dolist (key keys val)
- (setq val (cdr (assq key val))))))
+(defcustom guix-find-file-function #'find-file
+ "Function used to find a file.
+The function is called by `guix-find-file' with a file name as a
+single argument."
+ :type '(choice (function-item find-file)
+ (function-item org-open-file)
+ (function :tag "Other function"))
+ :group 'guix)
(defun guix-find-file (file)
"Find FILE if it exists."
(if (file-exists-p file)
- (find-file file)
+ (funcall guix-find-file-function file)
(message "File '%s' does not exist." file)))
+(defmacro guix-while-search (regexp &rest body)
+ "Evaluate BODY after each search for REGEXP in the current buffer."
+ (declare (indent 1) (debug t))
+ `(save-excursion
+ (goto-char (point-min))
+ (while (re-search-forward ,regexp nil t)
+ ,@body)))
+
+(defun guix-any (pred lst)
+ "Test whether any element from LST satisfies PRED.
+If so, return the return value from the successful PRED call.
+Return nil otherwise."
+ (when lst
+ (or (funcall pred (car lst))
+ (guix-any pred (cdr lst)))))
+
+
+;;; Alist accessors
+
+(defmacro guix-define-alist-accessor (name assoc-fun)
+ "Define NAME function to access alist values using ASSOC-FUN."
+ `(defun ,name (alist &rest keys)
+ ,(format "Return value from ALIST by KEYS using `%s'.
+ALIST is alist of alists of alists ... which can be consecutively
+accessed with KEYS."
+ assoc-fun)
+ (if (or (null alist) (null keys))
+ alist
+ (apply #',name
+ (cdr (,assoc-fun (car keys) alist))
+ (cdr keys)))))
+
+(guix-define-alist-accessor guix-assq-value assq)
+(guix-define-alist-accessor guix-assoc-value assoc)
+
;;; Diff
@@ -199,6 +282,27 @@ See `defun' for the meaning of arguments."
(mapconcat #'symbol-name arglist " ")
docstring)))
+(defmacro guix-memoized-defalias (symbol definition &optional docstring)
+ "Set SYMBOL's function definition to memoized version of DEFINITION."
+ (declare (doc-string 3) (indent 1))
+ `(defalias ',symbol
+ (guix-memoize #',definition)
+ ,(or docstring
+ (format "Memoized version of `%S'." definition))))
+
+(defvar guix-memoized-font-lock-keywords
+ (eval-when-compile
+ `((,(rx "("
+ (group "guix-memoized-" (or "defun" "defalias"))
+ symbol-end
+ (zero-or-more blank)
+ (zero-or-one
+ (group (one-or-more (or (syntax word) (syntax symbol))))))
+ (1 font-lock-keyword-face)
+ (2 font-lock-function-name-face nil t)))))
+
+(font-lock-add-keywords 'emacs-lisp-mode guix-memoized-font-lock-keywords)
+
(provide 'guix-utils)
;;; guix-utils.el ends here
diff --git a/emacs/guix.el b/emacs/guix.el
index afe7285696..244696a184 100644
--- a/emacs/guix.el
+++ b/emacs/guix.el
@@ -1,6 +1,6 @@
;;; guix.el --- Interface for GNU Guix package manager
-;; Copyright © 2014 Alex Kost <alezost@gmail.com>
+;; Copyright © 2014, 2015 Alex Kost <alezost@gmail.com>
;; Package-Requires: ((geiser "0.3"))
;; Keywords: tools
@@ -32,6 +32,7 @@
(require 'guix-list)
(require 'guix-info)
(require 'guix-utils)
+(require 'guix-read)
(defgroup guix nil
"Interface for Guix package manager."
@@ -193,6 +194,15 @@ Interactively with prefix, prompt for PROFILE."
(float-time from)
(float-time to)))
+;;;###autoload
+(defun guix-edit (id-or-name)
+ "Edit (go to location of) package with ID-OR-NAME."
+ (interactive (list (guix-read-package-name)))
+ (let ((loc (guix-package-location id-or-name)))
+ (if loc
+ (guix-find-location loc)
+ (message "Couldn't find package location."))))
+
(provide 'guix)
;;; guix.el ends here