guix-pcomplete.el 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. ;;; guix-pcomplete.el --- Functions for completing guix commands -*- lexical-binding: t -*-
  2. ;; Copyright © 2015, 2017–2019 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 completions for "guix" command that may be used in
  18. ;; `shell', `eshell' and wherever `pcomplete' works.
  19. ;;; Code:
  20. (require 'pcomplete)
  21. (require 'pcmpl-unix)
  22. (require 'cl-lib)
  23. (require 'guix nil t)
  24. (require 'guix-read)
  25. (require 'guix-misc)
  26. (require 'guix-utils)
  27. (require 'guix-help-vars)
  28. ;;; Parsing guix output
  29. (defun guix-pcomplete-search-in-help (regexp &optional group
  30. &rest args)
  31. "Search for REGEXP in 'guix ARGS... --help' output.
  32. Return a list of strings matching REGEXP.
  33. GROUP specifies a parenthesized expression used in REGEXP."
  34. (with-temp-buffer
  35. (insert (guix-help-string args))
  36. (let (result)
  37. (guix-while-search regexp
  38. (push (match-string-no-properties group) result))
  39. (nreverse result))))
  40. (defmacro guix-pcomplete-define-options-finder (name docstring regexp
  41. &optional filter)
  42. "Define function NAME to receive guix options and commands.
  43. The defined function takes rest COMMANDS argument. This function
  44. will search for REGEXP in 'guix COMMANDS... --help' output (or
  45. 'guix --help' if COMMANDS is nil) using
  46. `guix-pcomplete-search-in-help' and will return its result.
  47. If FILTER is specified, it should be a function. The result is
  48. passed to this FILTER as argument and the result value of this
  49. function call is returned."
  50. (declare (doc-string 2) (indent 1))
  51. `(guix-memoized-defun ,name (&rest commands)
  52. ,docstring
  53. (let ((res (apply #'guix-pcomplete-search-in-help
  54. ,regexp guix-help-parse-regexp-group commands)))
  55. ,(if filter
  56. `(funcall ,filter res)
  57. 'res))))
  58. (guix-pcomplete-define-options-finder guix-pcomplete-commands
  59. "If COMMANDS is nil, return a list of available guix commands.
  60. If COMMANDS is non-nil (it should be a list of strings), return
  61. available subcommands, actions, etc. for 'guix COMMANDS'."
  62. guix-help-parse-command-regexp)
  63. (guix-pcomplete-define-options-finder guix-pcomplete-long-options
  64. "Return a list of available long options for 'guix COMMANDS'."
  65. guix-help-parse-long-option-regexp)
  66. (guix-pcomplete-define-options-finder guix-pcomplete-short-options
  67. "Return a string with available short options for 'guix COMMANDS'."
  68. guix-help-parse-short-option-regexp
  69. (lambda (list)
  70. (guix-concat-strings list "")))
  71. ;;; Completing
  72. (defvar guix-pcomplete-option-regexp (rx string-start "-")
  73. "Regexp to match an option.")
  74. (defvar guix-pcomplete-long-option-regexp (rx string-start "--")
  75. "Regexp to match a long option.")
  76. (defvar guix-pcomplete-long-option-with-arg-regexp
  77. (rx string-start
  78. (group "--" (one-or-more any)) "="
  79. (group (zero-or-more any)))
  80. "Regexp to match a long option with its argument.
  81. The first parenthesized group defines the option and the second
  82. group - the argument.")
  83. (defvar guix-pcomplete-short-option-with-arg-regexp
  84. (rx string-start
  85. (group "-" (not (any "-")))
  86. (group (zero-or-more any)))
  87. "Regexp to match a short option with its argument.
  88. The first parenthesized group defines the option and the second
  89. group - the argument.")
  90. (defun guix-pcomplete-match-option ()
  91. "Return non-nil, if the current argument is an option."
  92. (pcomplete-match guix-pcomplete-option-regexp 0))
  93. (defun guix-pcomplete-match-long-option ()
  94. "Return non-nil, if the current argument is a long option."
  95. (pcomplete-match guix-pcomplete-long-option-regexp 0))
  96. (defun guix-pcomplete-match-long-option-with-arg ()
  97. "Return non-nil, if the current argument is a long option with value."
  98. (pcomplete-match guix-pcomplete-long-option-with-arg-regexp 0))
  99. (defun guix-pcomplete-match-short-option-with-arg ()
  100. "Return non-nil, if the current argument is a short option with value."
  101. (pcomplete-match guix-pcomplete-short-option-with-arg-regexp 0))
  102. (defun guix-pcomplete-long-option-arg (option args)
  103. "Return a long OPTION's argument from a list of arguments ARGS."
  104. (let* ((re (concat "\\`" option "=\\(.*\\)"))
  105. (args (cl-member-if (lambda (arg)
  106. (string-match re arg))
  107. args))
  108. (cur (car args)))
  109. (when cur
  110. (match-string-no-properties 1 cur))))
  111. (defun guix-pcomplete-short-option-arg (option args)
  112. "Return a short OPTION's argument from a list of arguments ARGS."
  113. (let* ((re (concat "\\`" option "\\(.*\\)"))
  114. (args (cl-member-if (lambda (arg)
  115. (string-match re arg))
  116. args))
  117. (cur (car args)))
  118. (when cur
  119. (let ((arg (match-string-no-properties 1 cur)))
  120. (if (string= "" arg)
  121. (cadr args) ; take the next arg
  122. arg)))))
  123. (defun guix-pcomplete-complete-comma-args (entries)
  124. "Complete comma separated arguments using ENTRIES."
  125. (let ((index pcomplete-index))
  126. (while (= index pcomplete-index)
  127. (let* ((args (if (or (guix-pcomplete-match-long-option-with-arg)
  128. (guix-pcomplete-match-short-option-with-arg))
  129. (pcomplete-match-string 2 0)
  130. (pcomplete-arg 0)))
  131. (input (if (string-match ".*,\\(.*\\)" args)
  132. (match-string-no-properties 1 args)
  133. args)))
  134. (pcomplete-here* entries input)))))
  135. (defvar guix-pcomplete-package-commands
  136. '("archive"
  137. "build"
  138. "challenge"
  139. "copy"
  140. "edit"
  141. "environment"
  142. "graph"
  143. "install"
  144. "lint"
  145. "pack"
  146. "refresh"
  147. "remove"
  148. "show"
  149. "size")
  150. "List of commands that take package names as their last arguments.")
  151. (defun guix-pcomplete-complete-command-arg (command)
  152. "Complete argument for guix COMMAND."
  153. (cond
  154. ((member command guix-pcomplete-package-commands)
  155. (while t
  156. (pcomplete-here (guix-package-names))))
  157. (t (pcomplete-here* (pcomplete-entries)))))
  158. (defun guix-pcomplete-complete-option-arg (command option &optional input)
  159. "Complete argument for COMMAND's OPTION.
  160. INPUT is the current partially completed string."
  161. (cl-flet ((option? (short long)
  162. (or (string= option short)
  163. (string= option long)))
  164. (command? (&rest commands)
  165. (member command commands))
  166. (complete (entries)
  167. (pcomplete-here entries input nil t))
  168. (complete* (entries)
  169. (pcomplete-here* entries input t)))
  170. (cond
  171. ((or (option? "-L" "--load-path")
  172. (option? "-p" "--profile"))
  173. (complete* (pcomplete-dirs)))
  174. ((string= "--key-download" option)
  175. (complete* guix-help-key-policies))
  176. ((string= "--on-error" option)
  177. (complete* guix-help-on-error-strategies))
  178. ((or (and (command? "environment"
  179. "pack"
  180. "package"
  181. "refresh"
  182. "weather")
  183. (option? "-m" "--manifest"))
  184. (and (command? "pull"
  185. "time-machine")
  186. (option? "-C" "--channels")))
  187. (complete* (pcomplete-entries)))
  188. ((command? "package")
  189. (cond
  190. ;; For '--install[=]' and '--remove[=]', try to complete a package
  191. ;; name (INPUT) after the "=" sign, and then the rest packages
  192. ;; separated with spaces.
  193. ((or (option? "-i" "--install")
  194. (option? "-r" "--remove"))
  195. (complete (guix-package-names))
  196. (while (not (guix-pcomplete-match-option))
  197. (pcomplete-here (guix-package-names))))
  198. ((string= "--show" option)
  199. (complete (guix-package-names)))
  200. ((string= "--search-paths" option)
  201. (complete* guix-help-search-paths-types))
  202. ((or (option? "-f" "--install-from-file")
  203. (option? "-m" "--manifest"))
  204. (complete* (pcomplete-entries)))))
  205. ((and (command? "archive" "build" "environment" "graph"
  206. "pull" "size" "weather")
  207. (option? "-s" "--system"))
  208. (complete* (guix-system-types)))
  209. ((and (command? "environment" "publish")
  210. (option? "-u" "--user"))
  211. (complete* (pcmpl-unix-user-names)))
  212. ((and (command? "archive")
  213. (option? "-x" "--extract"))
  214. (complete* (pcomplete-dirs)))
  215. ((and (command? "build")
  216. (or (option? "-f" "--file")
  217. (option? "-r" "--root")
  218. (string= "--with-source" option)))
  219. (complete* (pcomplete-entries)))
  220. ((and (command? "describe")
  221. (option? "-f" "--format"))
  222. (complete* guix-help-describe-formats))
  223. ((command? "graph")
  224. (cond
  225. ((option? "-t" "--type")
  226. (complete* (guix-graph-node-type-names)))
  227. ((option? "-b" "--backend")
  228. (complete* (guix-graph-backend-names)))))
  229. ((and (command? "environment")
  230. (option? "-l" "--load"))
  231. (complete* (pcomplete-entries)))
  232. ((and (command? "hash" "download")
  233. (option? "-f" "--format"))
  234. (complete* guix-help-hash-formats))
  235. ((and (command? "lint")
  236. (option? "-c" "--checkers"))
  237. (guix-pcomplete-complete-comma-args
  238. (guix-lint-checker-names)))
  239. ((command? "pack")
  240. (cond
  241. ((option? "-C" "--compression")
  242. (complete* (guix-compressor-names)))
  243. ((option? "-f" "--format")
  244. (complete* (guix-pack-format-names)))
  245. ;; Although the argument for "--symlink" should be
  246. ;; "FILE-NAME=TARGET", it is still better to complete FILE-NAME
  247. ;; than to complete nothing.
  248. ((or (option? "-r" "--root")
  249. (option? "-S" "--symlink"))
  250. (complete* (pcomplete-entries)))))
  251. ((command? "potluck")
  252. (cond
  253. ;; ((option? "--license")) ; TODO
  254. ((member option '("--scratch" "--source" "--target"))
  255. (complete* (pcomplete-dirs)))))
  256. ((command? "publish")
  257. (cond
  258. ((member option '("--public-key" "--private-key"))
  259. (complete* (pcomplete-entries)))
  260. ((option? "-u" "--user")
  261. (complete* (pcmpl-unix-user-names)))))
  262. ((command? "refresh")
  263. (cond
  264. ((option? "-s" "--select")
  265. (complete* guix-help-refresh-subsets))
  266. ((option? "-t" "--type")
  267. (guix-pcomplete-complete-comma-args
  268. (guix-refresh-updater-names)))))
  269. ((and (command? "repl")
  270. (option? "-t" "--type"))
  271. (complete* guix-help-repl-types))
  272. ((and (command? "gc")
  273. (string= "--verify" option))
  274. (guix-pcomplete-complete-comma-args
  275. guix-help-verify-options))
  276. ((command? "size")
  277. (cond
  278. ((option? "-m" "--map-file")
  279. (complete* (pcomplete-entries)))
  280. ((string= "--sort" option)
  281. (complete* guix-help-size-sort-keys)))))))
  282. (defun guix-pcomplete-complete-options (command)
  283. "Complete options (with their arguments) for guix COMMAND."
  284. (while (guix-pcomplete-match-option)
  285. (let ((index pcomplete-index))
  286. (if (guix-pcomplete-match-long-option)
  287. ;; Long options.
  288. (if (guix-pcomplete-match-long-option-with-arg)
  289. (let ((option (pcomplete-match-string 1 0))
  290. (arg (pcomplete-match-string 2 0)))
  291. (guix-pcomplete-complete-option-arg
  292. command option arg))
  293. (pcomplete-here* (guix-pcomplete-long-options command))
  294. ;; We support '--opt arg' style (along with '--opt=arg'),
  295. ;; because 'guix package --install/--remove' may be used this
  296. ;; way. So try to complete an argument after the option has
  297. ;; been completed.
  298. ;;
  299. ;; XXX This leads to a problem: optional arguments cannot be
  300. ;; completed. For example, after typing "guix build --sources ",
  301. ;; most likely, a user would want to complete a package name, so
  302. ;; we can't complete sources type there.
  303. (unless (guix-pcomplete-match-option)
  304. (guix-pcomplete-complete-option-arg
  305. command (pcomplete-arg 0 -1))))
  306. ;; Short options.
  307. (let ((arg (pcomplete-arg 0)))
  308. (if (> (length arg) 2)
  309. ;; Support specifying an argument after a short option without
  310. ;; spaces (for example, '-L/tmp/foo').
  311. (guix-pcomplete-complete-option-arg
  312. command
  313. (substring-no-properties arg 0 2)
  314. (substring-no-properties arg 2))
  315. (pcomplete-opt (guix-pcomplete-short-options command))
  316. (guix-pcomplete-complete-option-arg
  317. command (pcomplete-arg 0 -1)))))
  318. ;; If there were no completions, move to the next argument and get
  319. ;; out if the last argument is achieved.
  320. (when (= index pcomplete-index)
  321. (if (= pcomplete-index pcomplete-last)
  322. (throw 'pcompleted nil)
  323. (pcomplete-next-arg))))))
  324. ;;;###autoload
  325. (defun pcomplete/guix ()
  326. "Completion for `guix'."
  327. (let ((commands (guix-pcomplete-commands)))
  328. (pcomplete-here* (cons "--help" commands))
  329. (let ((command (pcomplete-arg 'first 1)))
  330. (when (member command commands)
  331. (guix-pcomplete-complete-options command)
  332. (let ((subcommands (guix-pcomplete-commands command)))
  333. (when subcommands
  334. (pcomplete-here* subcommands)))
  335. (guix-pcomplete-complete-options command)
  336. (guix-pcomplete-complete-command-arg command)))))
  337. (provide 'guix-pcomplete)
  338. ;;; guix-pcomplete.el ends here