diff options
Diffstat (limited to 'emacs/guix-utils.el')
-rw-r--r-- | emacs/guix-utils.el | 609 |
1 files changed, 0 insertions, 609 deletions
diff --git a/emacs/guix-utils.el b/emacs/guix-utils.el deleted file mode 100644 index 3e4ecc36ab..0000000000 --- a/emacs/guix-utils.el +++ /dev/null @@ -1,609 +0,0 @@ -;;; guix-utils.el --- General utility functions -*- lexical-binding: t -*- - -;; Copyright © 2014, 2015, 2016 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 general functions for guix.el package. - -;;; Code: - -(require 'cl-lib) - -(defvar guix-true-string "Yes") -(defvar guix-false-string "–") -(defvar guix-list-separator ", ") - -(defvar guix-time-format "%F %T" - "String used to format time values. -For possible formats, see `format-time-string'.") - -(defun guix-get-string (val &optional face) - "Convert VAL into a string and return it. - -VAL can be an expression of any type. -If VAL is t/nil, it is replaced with -`guix-true-string'/`guix-false-string'. -If VAL is list, its elements are concatenated using -`guix-list-separator'. - -If FACE is non-nil, propertize returned string with this FACE." - (let ((str (cond - ((stringp val) val) - ((null val) guix-false-string) - ((eq t val) guix-true-string) - ((numberp val) (number-to-string val)) - ((listp val) (mapconcat #'guix-get-string - val guix-list-separator)) - (t (prin1-to-string val))))) - (if (and val face) - (propertize str 'font-lock-face face) - str))) - -(defun guix-get-time-string (seconds) - "Return formatted time string from SECONDS. -Use `guix-time-format'." - (format-time-string guix-time-format (seconds-to-time seconds))) - -(defun guix-get-one-line (str) - "Return one-line string from a multi-line STR." - (replace-regexp-in-string "\n" " " str)) - -(defmacro guix-with-indent (indent &rest body) - "Evaluate BODY and indent inserted text by INDENT number of spaces." - (declare (indent 1) (debug t)) - (let ((region-beg-var (make-symbol "region-beg")) - (indent-var (make-symbol "indent"))) - `(let ((,region-beg-var (point)) - (,indent-var ,indent)) - ,@body - (unless (zerop ,indent-var) - (indent-rigidly ,region-beg-var (point) ,indent-var))))) - -(defun guix-format-insert (val &optional face format) - "Convert VAL into a string and insert it at point. -If FACE is non-nil, propertize VAL with FACE. -If FORMAT is non-nil, format VAL with FORMAT." - (let ((str (guix-get-string val face))) - (insert (if format - (format format str) - str)))) - -(cl-defun guix-mapinsert (function sequence separator &key indent column) - "Like `mapconcat' but for inserting text. -Apply FUNCTION to each element of SEQUENCE, and insert SEPARATOR -at point between each FUNCTION call. - -If INDENT is non-nil, it should be a number of spaces used to -indent each line of the inserted text. - -If COLUMN is non-nil, it should be a column number which -shouldn't be exceeded by the inserted text." - (pcase sequence - (`(,first . ,rest) - (let* ((indent (or indent 0)) - (max-column (and column (- column indent)))) - (guix-with-indent indent - (funcall function first) - (dolist (element rest) - (let ((before-sep-pos (and column (point)))) - (insert separator) - (let ((after-sep-pos (and column (point)))) - (funcall function element) - (when (and column - (> (current-column) max-column)) - (save-excursion - (delete-region before-sep-pos after-sep-pos) - (goto-char before-sep-pos) - (insert "\n"))))))))))) - -(defun guix-insert-button (label &optional type &rest properties) - "Make button of TYPE with LABEL and insert it at point. -See `insert-text-button' for the meaning of PROPERTIES." - (if (null label) - (guix-format-insert nil) - (apply #'insert-text-button label - :type (or type 'button) - properties))) - -(defun guix-buttonize (value button-type separator &rest properties) - "Make BUTTON-TYPE button(s) from VALUE. -Return a string with button(s). - -VALUE should be a string or a list of strings. If it is a list -of strings, buttons are separated with SEPARATOR string. - -PROPERTIES are passed to `guix-insert-button'." - (with-temp-buffer - (let ((labels (if (listp value) value (list value)))) - (guix-mapinsert (lambda (label) - (apply #'guix-insert-button - label button-type properties)) - labels - separator)) - (buffer-substring (point-min) (point-max)))) - -(defun guix-button-type? (symbol) - "Return non-nil, if SYMBOL is a button type." - (and symbol - (get symbol 'button-category-symbol))) - -(defun guix-split-insert (val &optional face col separator) - "Convert VAL into a string, split it and insert at point. - -If FACE is non-nil, propertize returned string with this FACE. - -If COL is non-nil and result string is a one-line string longer -than COL, split it into several short lines. - -Separate inserted lines with SEPARATOR." - (if (null val) - (guix-format-insert nil) - (let ((strings (guix-split-string (guix-get-string val) col))) - (guix-mapinsert (lambda (str) (guix-format-insert str face)) - strings - (or separator ""))))) - -(defun guix-split-string (str &optional col) - "Split string STR by lines and return list of result strings. -If COL is non-nil, fill STR to this column." - (let ((str (if col - (guix-get-filled-string str col) - str))) - (split-string str "\n *" t))) - -(defun guix-get-filled-string (str col) - "Return string by filling STR to column COL." - (with-temp-buffer - (insert str) - (let ((fill-column col)) - (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-hexify (value) - "Convert VALUE to string and hexify it." - (url-hexify-string (guix-get-string value))) - -(defun guix-number->bool (number) - "Convert NUMBER to boolean value. -Return nil, if NUMBER is 0; return t otherwise." - (not (zerop number))) - -(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-symbol-title (symbol) - "Return SYMBOL's name, a string. -This is like `symbol-name', but fancier." - (if (eq symbol 'id) - "ID" - (let ((str (replace-regexp-in-string "-" " " (symbol-name symbol)))) - (concat (capitalize (substring str 0 1)) - (substring str 1))))) - -(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-compose-buffer-name (base-name postfix) - "Return buffer name by appending BASE-NAME and POSTFIX. - -In a simple case the result is: - - BASE-NAME: POSTFIX - -If BASE-NAME is wrapped by '*', then the result is: - - *BASE-NAME: POSTFIX*" - (let ((re (rx string-start - (group (? "*")) - (group (*? any)) - (group (? "*")) - string-end))) - (or (string-match re base-name) - (error "Unexpected error in defining buffer name")) - (let ((first* (match-string 1 base-name)) - (name-body (match-string 2 base-name)) - (last* (match-string 3 base-name))) - ;; Handle the case when buffer name is wrapped by '*'. - (if (and (string= "*" first*) - (string= "*" last*)) - (concat "*" name-body ": " postfix "*") - (concat base-name ": " postfix))))) - -(defun guix-completing-read (prompt table &optional predicate - require-match initial-input - hist def inherit-input-method) - "Same as `completing-read' but return nil instead of an empty string." - (let ((res (completing-read prompt table predicate - require-match initial-input - hist def inherit-input-method))) - (unless (string= "" res) res))) - -(defun guix-completing-read-multiple (prompt table &optional predicate - require-match initial-input - hist def inherit-input-method) - "Same as `completing-read-multiple' but remove duplicates in result." - (cl-remove-duplicates - (completing-read-multiple prompt table predicate - require-match initial-input - hist def inherit-input-method) - :test #'string=)) - -(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-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))) - -(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) - (funcall guix-find-file-function file) - (message "File '%s' does not exist." file))) - -(defvar url-handler-regexp) - -(defun guix-find-file-or-url (file-or-url) - "Find FILE-OR-URL." - (require 'url-handlers) - (let ((file-name-handler-alist - (cons (cons url-handler-regexp 'url-file-handler) - file-name-handler-alist))) - (find-file file-or-url))) - -(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))) - -(defmacro guix-while-null (&rest body) - "Evaluate BODY until its result becomes non-nil." - (declare (indent 0) (debug t)) - (let ((result-var (make-symbol "result"))) - `(let (,result-var) - (while (null ,result-var) - (setq ,result-var ,@body)) - ,result-var))) - -(defun guix-modify (object modifiers) - "Apply MODIFIERS to OBJECT. -OBJECT is passed as an argument to the first function from -MODIFIERS list, the returned result is passed to the second -function from the list and so on. Return result of the last -modifier call." - (if (null modifiers) - object - (guix-modify (funcall (car modifiers) object) - (cdr modifiers)))) - -(defmacro guix-keyword-args-let (args varlist &rest body) - "Parse ARGS, bind variables from VARLIST and eval BODY. - -Find keyword values in ARGS, bind them to variables according to -VARLIST, then evaluate BODY. - -ARGS is a keyword/value property list. - -Each element of VARLIST has a form: - - (SYMBOL KEYWORD [DEFAULT-VALUE]) - -SYMBOL is a varible name. KEYWORD is a symbol that will be -searched in ARGS for an according value. If the value of KEYWORD -does not exist, bind SYMBOL to DEFAULT-VALUE or nil. - -The rest arguments (that present in ARGS but not in VARLIST) will -be bound to `%foreign-args' variable. - -Example: - - (guix-keyword-args-let '(:two 8 :great ! :guix is) - ((one :one 1) - (two :two 2) - (foo :smth)) - (list one two foo %foreign-args)) - - => (1 8 nil (:guix is :great !))" - (declare (indent 2)) - (let ((args-var (make-symbol "args"))) - `(let (,@(mapcar (lambda (spec) - (pcase-let ((`(,name ,_ ,val) spec)) - (list name val))) - varlist) - (,args-var ,args) - %foreign-args) - (while ,args-var - (pcase ,args-var - (`(,key ,val . ,rest-args) - (cl-case key - ,@(mapcar (lambda (spec) - (pcase-let ((`(,name ,key ,_) spec)) - `(,key (setq ,name val)))) - varlist) - (t (setq %foreign-args - (cl-list* key val %foreign-args)))) - (setq ,args-var rest-args)))) - ,@body))) - - -;;; Alist procedures - -(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) - -(defun guix-alist-put (value alist &rest keys) - "Put (add or replace if exists) VALUE to ALIST using KEYS. -Return the new alist. - -ALIST is alist of alists of alists ... which can be consecutively -accessed with KEYS. - -Example: - - (guix-alist-put - 'foo - '((one (a . 1) (b . 2)) - (two (m . 7) (n . 8))) - 'one 'b) - - => ((one (a . 1) (b . foo)) - (two (m . 7) (n . 8)))" - (or keys (error "Keys should be specified")) - (guix-alist-put-1 value alist keys)) - -(defun guix-alist-put-1 (value alist keys) - "Subroutine of `guix-alist-put'." - (cond - ((null keys) - value) - ((null alist) - (list (cons (car keys) - (guix-alist-put-1 value nil (cdr keys))))) - ((eq (car keys) (caar alist)) - (cons (cons (car keys) - (guix-alist-put-1 value (cdar alist) (cdr keys))) - (cdr alist))) - (t - (cons (car alist) - (guix-alist-put-1 value (cdr alist) keys))))) - -(defun guix-alist-put! (value variable &rest keys) - "Modify alist VARIABLE (symbol) by putting VALUE using KEYS. -See `guix-alist-put' for details." - (set variable - (apply #'guix-alist-put value (symbol-value variable) keys))) - - -;;; 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)) - - -;;; Completing readers definers - -(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." - (guix-keyword-args-let args - ((completions-var :completions-var) - (completions-getter :completions-getter) - (single-reader :single-reader) - (single-prompt :single-prompt) - (multiple-reader :multiple-reader) - (multiple-prompt :multiple-prompt) - (multiple-separator :multiple-separator)) - (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 guix-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)))))))) - - -;;; 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-utils-font-lock-keywords - (eval-when-compile - `((,(rx "(" (group (or "guix-define-reader" - "guix-define-readers" - "guix-keyword-args-let" - "guix-while-null" - "guix-while-search" - "guix-with-indent")) - symbol-end) - . 1) - (,(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-utils-font-lock-keywords) - -(provide 'guix-utils) - -;;; guix-utils.el ends here |