guix-utils.el 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. ;;; guix-utils.el --- General utility functions -*- lexical-binding: t -*-
  2. ;; Copyright © 2014–2017 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. (defun guix-switch-to-buffer-or-funcall (buffer-or-name function
  146. &optional message)
  147. "Switch to BUFFER-OR-NAME if it exists.
  148. If BUFFER-OR-NAME does not exist, call FUNCTION without
  149. arguments, also display a message if MESSAGE is specified (it can
  150. be either nil, a string, or another value for a default
  151. message)."
  152. (let ((buffer (get-buffer buffer-or-name)))
  153. (if buffer
  154. (progn
  155. (switch-to-buffer buffer)
  156. (when message
  157. (message (if (stringp message)
  158. message
  159. (substitute-command-keys "\
  160. Press '\\[revert-buffer]' to update this buffer.")))))
  161. (funcall function))))
  162. (cl-defun guix-show-pretty-print (file-name &optional (mode 'scheme-mode))
  163. "Show FILE-NAME contents in MODE and pretty-print it."
  164. (let* ((base-name (file-name-nondirectory file-name))
  165. (buffer (generate-new-buffer base-name)))
  166. (with-current-buffer buffer
  167. (insert-file-contents file-name)
  168. (pp-buffer)
  169. (funcall mode)
  170. (toggle-truncate-lines -1))
  171. (switch-to-buffer buffer)))
  172. (defmacro guix-while-search (regexp &rest body)
  173. "Evaluate BODY after each search for REGEXP in the current buffer."
  174. (declare (indent 1) (debug t))
  175. `(save-excursion
  176. (goto-char (point-min))
  177. (while (re-search-forward ,regexp nil t)
  178. ,@body)))
  179. (defmacro guix-while-null (&rest body)
  180. "Evaluate BODY until its result becomes non-nil."
  181. (declare (indent 0) (debug t))
  182. (let ((result-var (make-symbol "result")))
  183. `(let (,result-var)
  184. (while (null ,result-var)
  185. (setq ,result-var ,@body))
  186. ,result-var)))
  187. (defun guix-modify (object &rest modifiers)
  188. "Apply MODIFIERS to OBJECT.
  189. OBJECT is passed as an argument to the first function from
  190. MODIFIERS list, the returned result is passed to the second
  191. function from the list and so on. Return result of the last
  192. modifier call."
  193. (if (null modifiers)
  194. object
  195. (apply #'guix-modify
  196. (funcall (car modifiers) object)
  197. (cdr modifiers))))
  198. (defun guix-modify-objects (objects &rest modifiers)
  199. "Apply MODIFIERS to each object from a list of OBJECTS.
  200. See `guix-modify' for details."
  201. (--map (apply #'guix-modify it modifiers)
  202. objects))
  203. (defun guix-make-symbol (&rest symbols)
  204. "Return `guix-SYMBOLS-...' symbol."
  205. (apply #'bui-make-symbol 'guix symbols))
  206. (defmacro guix-define-groups (name &rest args)
  207. "Define `guix-NAME' and `guix-NAME-faces' customization groups.
  208. See `bui-define-groups' for details."
  209. (declare (indent 1))
  210. `(bui-define-groups ,(bui-make-symbol 'guix name)
  211. :parent-group guix
  212. :parent-faces-group guix-faces
  213. ,@args))
  214. ;;; Fontification
  215. (defvar guix-font-lock-flush-function
  216. (if (fboundp 'font-lock-flush)
  217. #'font-lock-flush ; appeared in Emacs 25.1
  218. #'jit-lock-refontify)
  219. "Function used to refontify a buffer.
  220. This function is called without arguments after
  221. enabling/disabling `guix-prettify-mode',
  222. `guix-build-log-minor-mode' and `guix-devel-mode'.
  223. If nil, do not perform refontifying.")
  224. (defun guix-font-lock-flush ()
  225. "Refontify the current buffer using `guix-font-lock-flush-function'."
  226. (when guix-font-lock-flush-function
  227. (if (fboundp guix-font-lock-flush-function)
  228. (funcall guix-font-lock-flush-function)
  229. (message "Unknown function: %S" guix-font-lock-flush-function))))
  230. ;;; Diff
  231. (defvar guix-diff-switches "-u"
  232. "A string or list of strings specifying switches to be passed to diff.")
  233. (defun guix-diff (old new &optional switches no-async)
  234. "Same as `diff', but use `guix-diff-switches' as default."
  235. (diff old new (or switches guix-diff-switches) no-async))
  236. ;;; Completing readers definers
  237. (defmacro guix-define-reader (name read-fun completions prompt)
  238. "Define NAME function to read from minibuffer.
  239. READ-FUN may be `completing-read', `completing-read-multiple' or
  240. another function with the same arguments."
  241. `(defun ,name (&optional prompt initial-contents)
  242. (,read-fun ,(if prompt
  243. `(or prompt ,prompt)
  244. 'prompt)
  245. ,completions nil nil initial-contents)))
  246. (defmacro guix-define-readers (&rest args)
  247. "Define reader functions.
  248. ARGS should have a form [KEYWORD VALUE] ... The following
  249. keywords are available:
  250. - `completions-var' - variable used to get completions.
  251. - `completions-getter' - function used to get completions.
  252. - `single-reader', `single-prompt' - name of a function to read
  253. a single value, and a prompt for it.
  254. - `multiple-reader', `multiple-prompt' - name of a function to
  255. read multiple values, and a prompt for it.
  256. - `multiple-separator' - if specified, another
  257. `<multiple-reader-name>-string' function returning a string
  258. of multiple values separated the specified separator will be
  259. defined."
  260. (bui-plist-let args
  261. ((completions-var :completions-var)
  262. (completions-getter :completions-getter)
  263. (single-reader :single-reader)
  264. (single-prompt :single-prompt)
  265. (multiple-reader :multiple-reader)
  266. (multiple-prompt :multiple-prompt)
  267. (multiple-separator :multiple-separator))
  268. (let ((completions
  269. (cond ((and completions-var completions-getter)
  270. `(or ,completions-var
  271. (setq ,completions-var
  272. (funcall ',completions-getter))))
  273. (completions-var
  274. completions-var)
  275. (completions-getter
  276. `(funcall ',completions-getter)))))
  277. `(progn
  278. ,(when (and completions-var
  279. (not (boundp completions-var)))
  280. `(defvar ,completions-var nil))
  281. ,(when single-reader
  282. `(guix-define-reader ,single-reader guix-completing-read
  283. ,completions ,single-prompt))
  284. ,(when multiple-reader
  285. `(guix-define-reader ,multiple-reader completing-read-multiple
  286. ,completions ,multiple-prompt))
  287. ,(when (and multiple-reader multiple-separator)
  288. (let ((name (intern (concat (symbol-name multiple-reader)
  289. "-string"))))
  290. `(defun ,name (&optional prompt initial-contents)
  291. (guix-concat-strings
  292. (,multiple-reader prompt initial-contents)
  293. ,multiple-separator))))))))
  294. ;;; Memoizing
  295. (defun guix-memoize (function)
  296. "Return a memoized version of FUNCTION."
  297. (let ((cache (make-hash-table :test 'equal)))
  298. (lambda (&rest args)
  299. (let ((result (gethash args cache 'not-found)))
  300. (if (eq result 'not-found)
  301. (let ((result (apply function args)))
  302. (puthash args result cache)
  303. result)
  304. result)))))
  305. (defmacro guix-memoized-defun (name arglist docstring &rest body)
  306. "Define a memoized function NAME.
  307. See `defun' for the meaning of arguments."
  308. (declare (doc-string 3) (indent 2))
  309. `(defalias ',name
  310. (guix-memoize (lambda ,arglist ,@body))
  311. ;; Add '(name args ...)' string with real arglist to the docstring,
  312. ;; because *Help* will display '(name &rest ARGS)' for a defined
  313. ;; function (since `guix-memoize' returns a lambda with '(&rest
  314. ;; args)').
  315. ,(format "(%S %s)\n\n%s"
  316. name
  317. (mapconcat #'symbol-name arglist " ")
  318. docstring)))
  319. (defmacro guix-memoized-defalias (symbol definition &optional docstring)
  320. "Set SYMBOL's function definition to memoized version of DEFINITION."
  321. (declare (doc-string 3) (indent 1))
  322. `(defalias ',symbol
  323. (guix-memoize #',definition)
  324. ,(or docstring
  325. (format "Memoized version of `%S'." definition))))
  326. (defvar guix-utils-font-lock-keywords
  327. (eval-when-compile
  328. `((,(rx "(" (group (or "guix-define-reader"
  329. "guix-define-readers"
  330. "guix-define-groups"
  331. "guix-while-null"
  332. "guix-while-search"))
  333. symbol-end)
  334. . 1)
  335. (,(rx "("
  336. (group "guix-memoized-" (or "defun" "defalias"))
  337. symbol-end
  338. (zero-or-more blank)
  339. (zero-or-one
  340. (group (one-or-more (or (syntax word) (syntax symbol))))))
  341. (1 font-lock-keyword-face)
  342. (2 font-lock-function-name-face nil t)))))
  343. (font-lock-add-keywords 'emacs-lisp-mode guix-utils-font-lock-keywords)
  344. (provide 'guix-utils)
  345. ;;; guix-utils.el ends here