guix-utils.el 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. ;;; guix-utils.el --- General utility functions -*- lexical-binding: t -*-
  2. ;; Copyright © 2014–2016 Alex Kost <alezost@gmail.com>
  3. ;; This file is part of Emacs-Guix.
  4. ;; Emacs-Guix is free software; you can redistribute it and/or modify
  5. ;; it under the terms of the GNU General Public License as published by
  6. ;; the Free Software Foundation, either version 3 of the License, or
  7. ;; (at your option) any later version.
  8. ;;
  9. ;; Emacs-Guix is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;;
  14. ;; You should have received a copy of the GNU General Public License
  15. ;; along with Emacs-Guix. If not, see <http://www.gnu.org/licenses/>.
  16. ;;; Commentary:
  17. ;; This file provides auxiliary general code for Emacs-Guix package.
  18. ;;; Code:
  19. (require 'cl-lib)
  20. (require 'dash)
  21. (require 'bui-utils)
  22. (require 'guix nil t)
  23. (defun guix-guixsd? ()
  24. "Return non-nil, if current system is GuixSD."
  25. (file-exists-p "/run/current-system"))
  26. (defun guix-concat-strings (strings separator &optional location)
  27. "Return new string by concatenating STRINGS with SEPARATOR.
  28. If LOCATION is a symbol `head', add another SEPARATOR to the
  29. beginning of the returned string; if `tail' - add SEPARATOR to
  30. the end of the string; if nil, do not add SEPARATOR; otherwise
  31. add both to the end and to the beginning."
  32. (let ((str (mapconcat #'identity strings separator)))
  33. (cond ((null location)
  34. str)
  35. ((eq location 'head)
  36. (concat separator str))
  37. ((eq location 'tail)
  38. (concat str separator))
  39. (t
  40. (concat separator str separator)))))
  41. (defun guix-hexify (value)
  42. "Convert VALUE to string and hexify it."
  43. (url-hexify-string (bui-get-string value)))
  44. (defun guix-number->bool (number)
  45. "Convert NUMBER to boolean value.
  46. Return nil, if NUMBER is 0; return t otherwise."
  47. (not (zerop number)))
  48. (defun guix-list-maybe (object)
  49. "If OBJECT is list, return it; otherwise return (list OBJECT)."
  50. (if (listp object)
  51. object
  52. (list object)))
  53. (defun guix-shell-quote-argument (argument)
  54. "Quote shell command ARGUMENT.
  55. This function is similar to `shell-quote-argument', but less strict."
  56. (if (equal argument "")
  57. "''"
  58. (replace-regexp-in-string
  59. "\n" "'\n'"
  60. (replace-regexp-in-string
  61. (rx (not (any alnum "-=,./\n"))) "\\\\\\&" argument))))
  62. (defun guix-command-symbol (&optional args)
  63. "Return symbol by concatenating 'guix' and ARGS (strings)."
  64. (intern (guix-concat-strings (cons "guix" args) "-")))
  65. (defun guix-command-string (&optional args)
  66. "Return 'guix ARGS ...' string with quoted shell arguments."
  67. (let ((args (mapcar #'guix-shell-quote-argument args)))
  68. (guix-concat-strings (cons "guix" args) " ")))
  69. (defun guix-copy-command-as-kill (args &optional no-message?)
  70. "Put 'guix ARGS ...' string into `kill-ring'.
  71. See also `guix-copy-as-kill'."
  72. (bui-copy-as-kill (guix-command-string args) no-message?))
  73. (defun guix-compose-buffer-name (base-name postfix)
  74. "Return buffer name by appending BASE-NAME and POSTFIX.
  75. In a simple case the result is:
  76. BASE-NAME: POSTFIX
  77. If BASE-NAME is wrapped by '*', then the result is:
  78. *BASE-NAME: POSTFIX*"
  79. (let ((re (rx string-start
  80. (group (? "*"))
  81. (group (*? any))
  82. (group (? "*"))
  83. string-end)))
  84. (or (string-match re base-name)
  85. (error "Unexpected error in defining buffer name"))
  86. (let ((first* (match-string 1 base-name))
  87. (name-body (match-string 2 base-name))
  88. (last* (match-string 3 base-name)))
  89. ;; Handle the case when buffer name is wrapped by '*'.
  90. (if (and (string= "*" first*)
  91. (string= "*" last*))
  92. (concat "*" name-body ": " postfix "*")
  93. (concat base-name ": " postfix)))))
  94. (defun guix-completing-read (prompt table &optional predicate
  95. require-match initial-input
  96. hist def inherit-input-method)
  97. "Same as `completing-read' but return nil instead of an empty string."
  98. (let ((res (completing-read prompt table predicate
  99. require-match initial-input
  100. hist def inherit-input-method)))
  101. (unless (string= "" res) res)))
  102. (defun guix-completing-read-multiple (prompt table &optional predicate
  103. require-match initial-input
  104. hist def inherit-input-method)
  105. "Same as `completing-read-multiple' but remove duplicates in result."
  106. (cl-remove-duplicates
  107. (completing-read-multiple prompt table predicate
  108. require-match initial-input
  109. hist def inherit-input-method)
  110. :test #'string=))
  111. (declare-function org-read-date "org" t)
  112. (defun guix-read-date (prompt)
  113. "Prompt for a date or time using `org-read-date'.
  114. Return time value."
  115. (require 'org)
  116. (org-read-date nil t nil prompt))
  117. (defun guix-read-file-name (prompt &optional dir default-filename
  118. mustmatch initial predicate)
  119. "Read file name.
  120. This function is similar to `read-file-name' except it also
  121. expands the file name."
  122. (expand-file-name (read-file-name prompt dir default-filename
  123. mustmatch initial predicate)))
  124. (defcustom guix-find-file-function #'find-file
  125. "Function used to find a file.
  126. This function is called by `guix-find-file' with a file name as a
  127. single argument."
  128. :type '(choice (function-item find-file)
  129. (function-item org-open-file)
  130. (function :tag "Other function"))
  131. :group 'guix)
  132. (defun guix-find-file (file)
  133. "Find FILE (using `guix-find-file-function') if it exists."
  134. (if (file-exists-p file)
  135. (funcall guix-find-file-function file)
  136. (message "File '%s' does not exist." file)))
  137. (defvar url-handler-regexp)
  138. (defun guix-find-file-or-url (file-or-url)
  139. "Find FILE-OR-URL."
  140. (require 'url-handlers)
  141. (let ((file-name-handler-alist
  142. (cons (cons url-handler-regexp 'url-file-handler)
  143. file-name-handler-alist)))
  144. (find-file file-or-url)))
  145. (cl-defun guix-show-pretty-print (file-name &optional (mode 'scheme-mode))
  146. "Show FILE-NAME contents in MODE and pretty-print it."
  147. (let* ((base-name (file-name-nondirectory file-name))
  148. (buffer (generate-new-buffer base-name)))
  149. (with-current-buffer buffer
  150. (insert-file-contents file-name)
  151. (pp-buffer)
  152. (funcall mode)
  153. (toggle-truncate-lines -1))
  154. (switch-to-buffer buffer)))
  155. (defmacro guix-while-search (regexp &rest body)
  156. "Evaluate BODY after each search for REGEXP in the current buffer."
  157. (declare (indent 1) (debug t))
  158. `(save-excursion
  159. (goto-char (point-min))
  160. (while (re-search-forward ,regexp nil t)
  161. ,@body)))
  162. (defmacro guix-while-null (&rest body)
  163. "Evaluate BODY until its result becomes non-nil."
  164. (declare (indent 0) (debug t))
  165. (let ((result-var (make-symbol "result")))
  166. `(let (,result-var)
  167. (while (null ,result-var)
  168. (setq ,result-var ,@body))
  169. ,result-var)))
  170. (defun guix-modify (object &rest modifiers)
  171. "Apply MODIFIERS to OBJECT.
  172. OBJECT is passed as an argument to the first function from
  173. MODIFIERS list, the returned result is passed to the second
  174. function from the list and so on. Return result of the last
  175. modifier call."
  176. (if (null modifiers)
  177. object
  178. (apply #'guix-modify
  179. (funcall (car modifiers) object)
  180. (cdr modifiers))))
  181. (defun guix-modify-objects (objects &rest modifiers)
  182. "Apply MODIFIERS to each object from a list of OBJECTS.
  183. See `guix-modify' for details."
  184. (--map (apply #'guix-modify it modifiers)
  185. objects))
  186. (defun guix-make-symbol (&rest symbols)
  187. "Return `guix-SYMBOLS-...' symbol."
  188. (apply #'bui-make-symbol 'guix symbols))
  189. (defmacro guix-define-groups (name &rest args)
  190. "Define `guix-NAME' and `guix-NAME-faces' customization groups.
  191. See `bui-define-groups' for details."
  192. (declare (indent 1))
  193. `(bui-define-groups ,(bui-make-symbol 'guix name)
  194. :parent-group guix
  195. :parent-faces-group guix-faces
  196. ,@args))
  197. ;;; Fontification
  198. (defvar guix-font-lock-flush-function
  199. (if (fboundp 'font-lock-flush)
  200. #'font-lock-flush ; appeared in Emacs 25.1
  201. #'jit-lock-refontify)
  202. "Function used to refontify a buffer.
  203. This function is called without arguments after
  204. enabling/disabling `guix-prettify-mode',
  205. `guix-build-log-minor-mode' and `guix-devel-mode'.
  206. If nil, do not perform refontifying.")
  207. (defun guix-font-lock-flush ()
  208. "Refontify the current buffer using `guix-font-lock-flush-function'."
  209. (when guix-font-lock-flush-function
  210. (if (fboundp guix-font-lock-flush-function)
  211. (funcall guix-font-lock-flush-function)
  212. (message "Unknown function: %S" guix-font-lock-flush-function))))
  213. ;;; Diff
  214. (defvar guix-diff-switches "-u"
  215. "A string or list of strings specifying switches to be passed to diff.")
  216. (defun guix-diff (old new &optional switches no-async)
  217. "Same as `diff', but use `guix-diff-switches' as default."
  218. (diff old new (or switches guix-diff-switches) no-async))
  219. ;;; Completing readers definers
  220. (defmacro guix-define-reader (name read-fun completions prompt)
  221. "Define NAME function to read from minibuffer.
  222. READ-FUN may be `completing-read', `completing-read-multiple' or
  223. another function with the same arguments."
  224. `(defun ,name (&optional prompt initial-contents)
  225. (,read-fun ,(if prompt
  226. `(or prompt ,prompt)
  227. 'prompt)
  228. ,completions nil nil initial-contents)))
  229. (defmacro guix-define-readers (&rest args)
  230. "Define reader functions.
  231. ARGS should have a form [KEYWORD VALUE] ... The following
  232. keywords are available:
  233. - `completions-var' - variable used to get completions.
  234. - `completions-getter' - function used to get completions.
  235. - `single-reader', `single-prompt' - name of a function to read
  236. a single value, and a prompt for it.
  237. - `multiple-reader', `multiple-prompt' - name of a function to
  238. read multiple values, and a prompt for it.
  239. - `multiple-separator' - if specified, another
  240. `<multiple-reader-name>-string' function returning a string
  241. of multiple values separated the specified separator will be
  242. defined."
  243. (bui-plist-let args
  244. ((completions-var :completions-var)
  245. (completions-getter :completions-getter)
  246. (single-reader :single-reader)
  247. (single-prompt :single-prompt)
  248. (multiple-reader :multiple-reader)
  249. (multiple-prompt :multiple-prompt)
  250. (multiple-separator :multiple-separator))
  251. (let ((completions
  252. (cond ((and completions-var completions-getter)
  253. `(or ,completions-var
  254. (setq ,completions-var
  255. (funcall ',completions-getter))))
  256. (completions-var
  257. completions-var)
  258. (completions-getter
  259. `(funcall ',completions-getter)))))
  260. `(progn
  261. ,(when (and completions-var
  262. (not (boundp completions-var)))
  263. `(defvar ,completions-var nil))
  264. ,(when single-reader
  265. `(guix-define-reader ,single-reader guix-completing-read
  266. ,completions ,single-prompt))
  267. ,(when multiple-reader
  268. `(guix-define-reader ,multiple-reader completing-read-multiple
  269. ,completions ,multiple-prompt))
  270. ,(when (and multiple-reader multiple-separator)
  271. (let ((name (intern (concat (symbol-name multiple-reader)
  272. "-string"))))
  273. `(defun ,name (&optional prompt initial-contents)
  274. (guix-concat-strings
  275. (,multiple-reader prompt initial-contents)
  276. ,multiple-separator))))))))
  277. ;;; Memoizing
  278. (defun guix-memoize (function)
  279. "Return a memoized version of FUNCTION."
  280. (let ((cache (make-hash-table :test 'equal)))
  281. (lambda (&rest args)
  282. (let ((result (gethash args cache 'not-found)))
  283. (if (eq result 'not-found)
  284. (let ((result (apply function args)))
  285. (puthash args result cache)
  286. result)
  287. result)))))
  288. (defmacro guix-memoized-defun (name arglist docstring &rest body)
  289. "Define a memoized function NAME.
  290. See `defun' for the meaning of arguments."
  291. (declare (doc-string 3) (indent 2))
  292. `(defalias ',name
  293. (guix-memoize (lambda ,arglist ,@body))
  294. ;; Add '(name args ...)' string with real arglist to the docstring,
  295. ;; because *Help* will display '(name &rest ARGS)' for a defined
  296. ;; function (since `guix-memoize' returns a lambda with '(&rest
  297. ;; args)').
  298. ,(format "(%S %s)\n\n%s"
  299. name
  300. (mapconcat #'symbol-name arglist " ")
  301. docstring)))
  302. (defmacro guix-memoized-defalias (symbol definition &optional docstring)
  303. "Set SYMBOL's function definition to memoized version of DEFINITION."
  304. (declare (doc-string 3) (indent 1))
  305. `(defalias ',symbol
  306. (guix-memoize #',definition)
  307. ,(or docstring
  308. (format "Memoized version of `%S'." definition))))
  309. (defvar guix-utils-font-lock-keywords
  310. (eval-when-compile
  311. `((,(rx "(" (group (or "guix-define-reader"
  312. "guix-define-readers"
  313. "guix-define-groups"
  314. "guix-while-null"
  315. "guix-while-search"))
  316. symbol-end)
  317. . 1)
  318. (,(rx "("
  319. (group "guix-memoized-" (or "defun" "defalias"))
  320. symbol-end
  321. (zero-or-more blank)
  322. (zero-or-one
  323. (group (one-or-more (or (syntax word) (syntax symbol))))))
  324. (1 font-lock-keyword-face)
  325. (2 font-lock-function-name-face nil t)))))
  326. (font-lock-add-keywords 'emacs-lisp-mode guix-utils-font-lock-keywords)
  327. (provide 'guix-utils)
  328. ;;; guix-utils.el ends here