diff options
Diffstat (limited to 'emacs')
-rw-r--r-- | emacs/guix-backend.el | 14 | ||||
-rw-r--r-- | emacs/guix-base.el | 121 | ||||
-rw-r--r-- | emacs/guix-command.el | 671 | ||||
-rw-r--r-- | emacs/guix-config.el.in | 40 | ||||
-rw-r--r-- | emacs/guix-external.el | 72 | ||||
-rw-r--r-- | emacs/guix-help-vars.el | 108 | ||||
-rw-r--r-- | emacs/guix-info.el | 48 | ||||
-rw-r--r-- | emacs/guix-init.el (renamed from emacs/guix-init.el.in) | 4 | ||||
-rw-r--r-- | emacs/guix-list.el | 52 | ||||
-rw-r--r-- | emacs/guix-main.scm | 78 | ||||
-rw-r--r-- | emacs/guix-messages.el | 8 | ||||
-rw-r--r-- | emacs/guix-pcomplete.el | 91 | ||||
-rw-r--r-- | emacs/guix-popup.el | 48 | ||||
-rw-r--r-- | emacs/guix-prettify.el | 17 | ||||
-rw-r--r-- | emacs/guix-profiles.el (renamed from emacs/guix-profiles.el.in) | 4 | ||||
-rw-r--r-- | emacs/guix-read.el | 176 | ||||
-rw-r--r-- | emacs/guix-utils.el | 120 | ||||
-rw-r--r-- | emacs/guix.el | 12 |
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 |