emacs: Use prompt for packages instead popup for edit action.
[jackhill/guix/guix.git] / emacs / guix-utils.el
index f99c2ba..78ea354 100644 (file)
@@ -1,6 +1,6 @@
-;;; guix-utils.el --- General utility functions
+;;; guix-utils.el --- General utility functions  -*- 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.
 
@@ -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)
@@ -138,13 +185,114 @@ split it into several short lines."
                              hist def inherit-input-method)
    :test #'string=))
 
-(defun guix-get-key-val (alist &rest keys)
-  "Return value from ALIST by KEYS.
+(declare-function org-read-date "org" t)
+
+(defun guix-read-date (prompt)
+  "Prompt for a date or time using `org-read-date'.
+Return time value."
+  (require 'org)
+  (org-read-date nil t nil prompt))
+
+(defun guix-find-file (file)
+  "Find FILE if it exists."
+  (if (file-exists-p file)
+      (find-file 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)))))
+
+\f
+;;; 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."
-  (let ((val alist))
-    (dolist (key keys val)
-      (setq val (cdr (assq key val))))))
+              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)
+
+\f
+;;; Diff
+
+(defvar guix-diff-switches "-u"
+  "A string or list of strings specifying switches to be passed to diff.")
+
+(defun guix-diff (old new &optional switches no-async)
+  "Same as `diff', but use `guix-diff-switches' as default."
+  (diff old new (or switches guix-diff-switches) no-async))
+
+\f
+;;; Memoizing
+
+(defun guix-memoize (function)
+  "Return a memoized version of FUNCTION."
+  (let ((cache (make-hash-table :test 'equal)))
+    (lambda (&rest args)
+      (let ((result (gethash args cache 'not-found)))
+        (if (eq result 'not-found)
+            (let ((result (apply function args)))
+              (puthash args result cache)
+              result)
+          result)))))
+
+(defmacro guix-memoized-defun (name arglist docstring &rest body)
+  "Define a memoized function NAME.
+See `defun' for the meaning of arguments."
+  (declare (doc-string 3) (indent 2))
+  `(defalias ',name
+     (guix-memoize (lambda ,arglist ,@body))
+     ;; Add '(name args ...)' string with real arglist to the docstring,
+     ;; because *Help* will display '(name &rest ARGS)' for a defined
+     ;; function (since `guix-memoize' returns a lambda with '(&rest
+     ;; args)').
+     ,(format "(%S %s)\n\n%s"
+              name
+              (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)