guix-ui-store-item.el 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. ;;; guix-ui-store-item.el --- Interface for displaying store items -*- lexical-binding: t -*-
  2. ;; Copyright © 2018 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 an interface to display store items in 'list' and
  18. ;; 'info' buffers.
  19. ;;; Code:
  20. (require 'cl-lib)
  21. (require 'ffap)
  22. (require 'dash)
  23. (require 'bui)
  24. (require 'guix-package)
  25. (require 'guix-guile)
  26. (require 'guix-repl)
  27. (require 'guix-misc)
  28. (require 'guix-utils)
  29. (require 'guix-auto-mode) ; for regexps
  30. ;;; Misc functionality (move to "guix-store.el"?)
  31. (defun guix-store-file-name-read ()
  32. "Read from minibuffer a store file name."
  33. (let* ((file (ffap-file-at-point))
  34. (file (and file
  35. (string-match-p guix-store-directory file)
  36. file)))
  37. ;; Read a string (not a file name), since using completions for
  38. ;; "/gnu/store" would probably be too much.
  39. (read-string "File from store: " file)))
  40. (defvar guix-store-file-name-regexp
  41. (rx-to-string
  42. `(and ,guix-store-directory "/"
  43. (regexp ,guix-hash-regexp) "-"
  44. (group (* any)))
  45. t)
  46. "Regexp matching a string with store file name.
  47. The first parenthesized group is the name itself (placed right
  48. after the hash part).")
  49. (defun guix-store-file-name< (a b)
  50. "Return non-nil if store file name A is less than B.
  51. This is similar to `string<', except the '/gnu/store/...-' parts
  52. of the file names are ignored."
  53. (cl-flet ((name (str)
  54. (and (string-match guix-store-file-name-regexp str)
  55. (match-string 1 str))))
  56. (string-lessp (name a) (name b))))
  57. ;;; Common for both interfaces
  58. (guix-define-groups store-item)
  59. (bui-define-entry-type guix-store-item
  60. :message-function 'guix-store-item-message
  61. :titles '((id . "File name")
  62. (time . "Registration time")
  63. (number-of-derivers . "Derivers")
  64. (number-of-references . "References")
  65. (number-of-referrers . "Referrers")
  66. (number-of-requisites . "Requisites")))
  67. (defcustom guix-store-item-show-total-size t
  68. "If non-nil, show total size after displaying store items."
  69. :type 'boolean
  70. :group 'guix-store-item)
  71. (defface guix-store-item-invalid
  72. '((t :inherit font-lock-warning-face))
  73. "Face used for store items that are not valid."
  74. :group 'guix-store-item-faces)
  75. (defun guix-store-item-get-entries (search-type
  76. &optional search-values params)
  77. "Receive 'store-item' entries.
  78. SEARCH-TYPE may be one of the following symbols: `id', `live',
  79. `dead', `referrers', `references', `derivers', `requisites',
  80. `failures'."
  81. (guix-eval-read
  82. (guix-make-guile-expression
  83. 'store-item-sexps search-type search-values params)))
  84. (defun guix-store-item-get-display (search-type &rest search-values)
  85. "Search for store items and show results."
  86. (apply #'bui-list-get-display-entries
  87. 'guix-store-item search-type search-values))
  88. (defun guix-store-item-message (entries search-type &rest search-values)
  89. "Display a message after showing store item ENTRIES."
  90. (let ((count (length entries))
  91. (val (car search-values)))
  92. (cl-flet ((msg (str &rest args)
  93. (if guix-store-item-show-total-size
  94. (apply #'message
  95. (concat str "\nTotal size: %s.")
  96. (-snoc args
  97. (guix-file-size-string
  98. (guix-store-item-entries-size entries))))
  99. (apply #'message str args ))))
  100. (cl-case search-type
  101. ((id path)
  102. (cl-case count
  103. (0 (message "No info on the store item(s) found."))
  104. (1 (msg "Store item '%s'." val))
  105. (t (msg "%d store items displayed." count))))
  106. (live (msg "%d live store items." count))
  107. (dead (msg "%d dead store items." count))
  108. (failures
  109. (cl-case count
  110. (0 (message "No failures found."))
  111. (1 (msg "A single failure found."))
  112. (t (msg "%d failures found." count))))
  113. (t
  114. (let ((type (symbol-name search-type))
  115. (paths (mapconcat #'identity search-values ", ")))
  116. (cl-case count
  117. (0 (message "No %s of '%s' found." type paths))
  118. (1 (msg "A single %s of '%s'."
  119. ;; Remove the trailing "s" from the search type
  120. ;; ("derivers" -> "deriver").
  121. (substring type 0 (1- (length type)))
  122. paths))
  123. (t (msg "%d %s of '%s'." count type paths)))))))))
  124. (defun guix-store-item-delete (&rest file-names)
  125. "Delete FILE-NAMES from the store."
  126. (or file-names
  127. (error "Nothing to delete"))
  128. (when (or (not guix-operation-confirm)
  129. (y-or-n-p
  130. (let ((count (length file-names)))
  131. (if (> count 1)
  132. (format "Try to delete these %d store items? " count)
  133. (format "Try to delete store item '%s'? "
  134. (car file-names))))))
  135. (guix-eval-in-repl
  136. (apply #'guix-make-guile-expression
  137. 'guix-command "gc" "--delete" file-names)
  138. (current-buffer))))
  139. (defun guix-store-item-entries-size (entries)
  140. "Return total size of store item ENTRIES."
  141. (--reduce-from (+ acc
  142. (or (bui-entry-non-void-value it 'size)
  143. 0))
  144. 0 entries))
  145. ;;; Store item 'info'
  146. (bui-define-interface guix-store-item info
  147. :mode-name "Store-Item-Info"
  148. :buffer-name "*Guix Store Item Info*"
  149. :get-entries-function 'guix-store-item-info-get-entries
  150. :format '((id nil (guix-info-insert-file-name))
  151. nil
  152. guix-store-item-info-insert-invalid
  153. (size format guix-store-item-info-insert-size)
  154. (time format (time))
  155. (number-of-derivers
  156. format guix-store-item-info-insert-number-of-derivers)
  157. (number-of-references
  158. format guix-store-item-info-insert-number-of-references)
  159. (number-of-referrers
  160. format guix-store-item-info-insert-number-of-referrers)
  161. (number-of-requisites
  162. format guix-store-item-info-insert-number-of-requisites)))
  163. (defvar guix-store-item-info-required-params
  164. '(id)
  165. "List of the required 'store-item' parameters.
  166. These parameters are received from the Scheme side
  167. along with the displayed parameters.
  168. Do not remove `id' from this info as it is required for
  169. identifying an entry.")
  170. (defun guix-store-item-info-get-entries (search-type &rest search-values)
  171. "Return 'store-item' entries for displaying them in 'info' buffer."
  172. (guix-store-item-get-entries
  173. search-type search-values
  174. (cl-union guix-store-item-info-required-params
  175. (bui-info-displayed-params 'guix-store-item))))
  176. (defun guix-info-insert-file-name (file-name)
  177. "Insert store item FILE-NAME at point."
  178. (bui-insert-button file-name 'bui-file)
  179. (bui-insert-indent)
  180. (bui-insert-action-button
  181. "Delete"
  182. (lambda (btn)
  183. (guix-store-item-delete (button-get btn 'file-name)))
  184. (format "Delete '%s' from the store" file-name)
  185. 'file-name file-name))
  186. (defun guix-store-item-info-insert-size (size entry)
  187. "Insert SIZE of the store item ENTRY at point."
  188. (bui-insert-non-nil size
  189. (insert (guix-file-size-string size))
  190. (bui-insert-indent)
  191. (let ((file-name (bui-entry-id entry)))
  192. (bui-insert-action-button
  193. "Size"
  194. (lambda (btn)
  195. (guix-package-size (button-get btn 'file-name)
  196. (guix-read-package-size-type)))
  197. (format "Show full size info on '%s'" file-name)
  198. 'file-name file-name))))
  199. (defun guix-info-insert-store-item (file-name)
  200. "Insert store FILE-NAME at point."
  201. (bui-insert-button file-name 'bui-file)
  202. (bui-insert-indent)
  203. (bui-insert-action-button
  204. "Store item"
  205. (lambda (btn)
  206. (guix-store-item (button-get btn 'file-name)))
  207. (format "Show more info on %s" file-name)
  208. 'file-name file-name))
  209. (defun guix-info-insert-store-items (file-names)
  210. "Insert store FILE-NAMES at point.
  211. FILE-NAMES can be a list or a single string."
  212. (bui-insert-non-nil file-names
  213. (dolist (file-name (guix-list-maybe file-names))
  214. (bui-newline)
  215. (bui-insert-indent)
  216. (guix-info-insert-store-item file-name))))
  217. (defun guix-store-item-info-insert-invalid (entry)
  218. "Insert a text if the store item ENTRY is not valid."
  219. (when (bui-entry-non-void-value entry 'invalid)
  220. (if (not (file-exists-p (bui-entry-id entry)))
  221. (insert "This file does not exist.\n\n")
  222. (insert "Guix daemon says this path is ")
  223. (bui-format-insert "not valid" 'guix-store-item-invalid)
  224. (insert ".\nApparently, you may remove it from the store.\n\n"))))
  225. (defun guix-store-item-info-insert-type-button (type entry)
  226. "Insert button to display TYPE of store item ENTRY at point.
  227. TYPE should be one of the following symbols: `derivers',
  228. `references', `referrers', `requisites'."
  229. (let ((file-name (bui-entry-id entry))
  230. (type-str (symbol-name type)))
  231. (bui-insert-action-button
  232. "Show"
  233. (lambda (btn)
  234. (guix-store-item-get-display (button-get btn 'search-type)
  235. (button-get btn 'file-name)))
  236. (format "Show %s of '%s'" type-str file-name)
  237. 'search-type type
  238. 'file-name file-name)))
  239. (defmacro guix-store-item-info-define-insert-number (type)
  240. "Define a function to insert number of TYPE.
  241. See `guix-store-item-info-insert-type-button' for the meaning of TYPE."
  242. (let* ((type-str (symbol-name type))
  243. (name (intern (concat "guix-store-item-info-insert-number-of-"
  244. type-str)))
  245. (desc (concat "Insert NUMBER of " type-str
  246. " of store item ENTRY at point.")))
  247. `(defun ,name (number entry)
  248. ,desc
  249. (bui-insert-non-nil number
  250. (bui-format-insert number)
  251. (unless (= 0 number)
  252. (bui-insert-indent)
  253. (guix-store-item-info-insert-type-button ',type entry))))))
  254. (guix-store-item-info-define-insert-number derivers)
  255. (guix-store-item-info-define-insert-number references)
  256. (guix-store-item-info-define-insert-number referrers)
  257. (guix-store-item-info-define-insert-number requisites)
  258. ;;; Store item 'list'
  259. (bui-define-interface guix-store-item list
  260. :mode-name "Store-Item-List"
  261. :buffer-name "*Guix Store Items*"
  262. :get-entries-function 'guix-store-item-list-get-entries
  263. :describe-function 'guix-store-item-list-describe
  264. :format '((id guix-store-item-list-get-name 65
  265. guix-store-item-list-sort-file-names-0)
  266. (size nil 20 bui-list-sort-numerically-1 :right-align t))
  267. :hint 'guix-store-item-list-hint
  268. :sort-key '(size . t)
  269. :marks '((delete . ?D)))
  270. (defvar guix-store-item-list-required-params
  271. '(id)
  272. "List of the required 'store-item' parameters.
  273. These parameters are received from the Scheme side
  274. along with the displayed parameters.
  275. Do not remove `id' from this list as it is required for
  276. identifying an entry.")
  277. (let ((map guix-store-item-list-mode-map))
  278. (define-key map (kbd "e") 'guix-store-item-list-edit)
  279. (define-key map (kbd "d") 'guix-store-item-list-mark-delete)
  280. (define-key map (kbd "f") 'guix-store-item-list-referrers)
  281. (define-key map (kbd "F") 'guix-store-item-list-references)
  282. (define-key map (kbd "D") 'guix-store-item-list-derivers)
  283. (define-key map (kbd "R") 'guix-store-item-list-requisites)
  284. (define-key map (kbd "z") 'guix-store-item-list-size)
  285. (define-key map (kbd "x") 'guix-store-item-list-execute))
  286. (defvar guix-store-item-list-default-hint
  287. '(("\\[guix-store-item-list-edit]") " go to the current store item;\n"
  288. ("\\[guix-store-item-list-derivers]") " show derivers; "
  289. ("\\[guix-store-item-list-requisites]") " show requisites;\n"
  290. ("\\[guix-store-item-list-referrers]") " show referrers; "
  291. ("\\[guix-store-item-list-references]") " show references;\n"
  292. ("\\[guix-store-item-list-mark-delete]") " mark for deletion; "
  293. ("\\[guix-store-item-list-execute]") " execute operation (deletions);\n"
  294. ("\\[guix-store-item-list-size]") " show size of the marked items;\n"))
  295. (defun guix-store-item-list-hint ()
  296. (bui-format-hints
  297. guix-store-item-list-default-hint
  298. (bui-default-hint)))
  299. (defun guix-store-item-list-get-entries (search-type &rest search-values)
  300. "Return 'store-item' entries for displaying them in 'list' buffer."
  301. (guix-store-item-get-entries
  302. search-type search-values
  303. (cl-union guix-store-item-list-required-params
  304. (bui-list-displayed-params 'guix-store-item))))
  305. (defun guix-store-item-list-get-name (name entry)
  306. "Return NAME of the store item ENTRY.
  307. Colorize it with an appropriate face if needed."
  308. (bui-get-string
  309. name
  310. (and (bui-entry-non-void-value entry 'invalid)
  311. 'guix-store-item-invalid)))
  312. (defun guix-store-item-list-sort-file-names-0 (a b)
  313. "Compare column 0 of tabulated entries A and B numerically.
  314. This function is used for sort predicates for `tabulated-list-format'.
  315. Return non-nil, if B is bigger than A."
  316. (cl-flet ((name (entry) (aref (cadr entry) 0)))
  317. (guix-store-file-name< (name a) (name b))))
  318. (defun guix-store-item-list-describe (&rest ids)
  319. "Describe store-items with IDS (list of identifiers)."
  320. (bui-get-display-entries 'guix-store-item 'info (cons 'id ids)))
  321. (defun guix-store-item-list-edit ()
  322. "Go to the current store item."
  323. (interactive)
  324. (guix-find-file (bui-list-current-id)))
  325. (defun guix-store-item-list-mark-delete (&optional arg)
  326. "Mark the current store-item for deletion and move to the next line.
  327. With ARG, mark all store-items for deletion."
  328. (interactive "P")
  329. (if arg
  330. (bui-list-mark-all 'delete)
  331. (bui-list--mark 'delete t)))
  332. (defun guix-store-item-list-execute ()
  333. "Delete store items marked with '\\[guix-store-item-list-mark-delete]'."
  334. (interactive)
  335. (let ((marked (bui-list-get-marked-id-list 'delete)))
  336. (or marked
  337. (user-error "No store items marked for deletion"))
  338. (apply #'guix-store-item-delete marked)))
  339. (defun guix-store-item-list-size ()
  340. "Show size of the marked (or current) store items.
  341. Store items can be marked with any mark."
  342. (interactive)
  343. (let* ((marked (bui-list-marked-or-current))
  344. (count (length marked))
  345. (msg (if (= 1 count)
  346. (format "Size of '%s': %%s." (car marked))
  347. (format "Size of %d marked items: %%s." count)))
  348. (size (guix-file-size-string
  349. (guix-store-item-entries-size
  350. (bui-entries-by-ids (bui-current-entries) marked)))))
  351. (message msg size)))
  352. (defmacro guix-store-item-list-define-show-items (type)
  353. "Define a function to show items by TYPE.
  354. See `guix-store-item-list-insert-type-button' for the meaning of TYPE."
  355. (let* ((type-str (symbol-name type))
  356. (name (intern (concat "guix-store-item-list-" type-str)))
  357. (desc (concat "Display " type-str
  358. " of the marked (or current) store items.")))
  359. `(defun ,name ()
  360. ,desc
  361. (interactive)
  362. (apply #'guix-store-item-get-display ',type
  363. (bui-list-marked-or-current 'general)))))
  364. (guix-store-item-list-define-show-items derivers)
  365. (guix-store-item-list-define-show-items references)
  366. (guix-store-item-list-define-show-items referrers)
  367. (guix-store-item-list-define-show-items requisites)
  368. ;;; Interactive commands
  369. ;;;###autoload
  370. (defun guix-store-item (&rest file-names)
  371. "Display store items with FILE-NAMES.
  372. Interactively, prompt for a single file name."
  373. (interactive (list (guix-store-file-name-read)))
  374. (apply #'guix-assert-files-exist file-names)
  375. (apply #'guix-store-item-get-display 'id file-names))
  376. ;;;###autoload
  377. (defun guix-store-item-referrers (&rest file-names)
  378. "Display referrers of the FILE-NAMES store item.
  379. This is analogous to 'guix gc --referrers FILE-NAMES' shell
  380. command. See Info node `(guix) Invoking guix gc'."
  381. (interactive (list (guix-store-file-name-read)))
  382. (apply #'guix-assert-files-exist file-names)
  383. (apply #'guix-store-item-get-display 'referrers file-names))
  384. ;;;###autoload
  385. (defun guix-store-item-references (&rest file-names)
  386. "Display references of the FILE-NAMES store item.
  387. This is analogous to 'guix gc --references FILE-NAMES' shell
  388. command. See Info node `(guix) Invoking guix gc'."
  389. (interactive (list (guix-store-file-name-read)))
  390. (apply #'guix-assert-files-exist file-names)
  391. (apply #'guix-store-item-get-display 'references file-names))
  392. ;;;###autoload
  393. (defun guix-store-item-requisites (&rest file-names)
  394. "Display requisites of the FILE-NAMES store item.
  395. This is analogous to 'guix gc --requisites FILE-NAMES' shell
  396. command. See Info node `(guix) Invoking guix gc'."
  397. (interactive (list (guix-store-file-name-read)))
  398. (apply #'guix-assert-files-exist file-names)
  399. (apply #'guix-store-item-get-display 'requisites file-names))
  400. ;;;###autoload
  401. (defun guix-store-item-derivers (&rest file-names)
  402. "Display derivers of the FILE-NAMES store item.
  403. This is analogous to 'guix gc --derivers FILE-NAMES' shell
  404. command. See Info node `(guix) Invoking guix gc'."
  405. (interactive (list (guix-store-file-name-read)))
  406. (apply #'guix-assert-files-exist file-names)
  407. (apply #'guix-store-item-get-display 'derivers file-names))
  408. ;;;###autoload
  409. (defun guix-store-failures ()
  410. "Display store items corresponding to cached build failures.
  411. This is analogous to 'guix gc --list-failures' shell command.
  412. See Info node `(guix) Invoking guix gc'."
  413. (interactive)
  414. (guix-store-item-get-display 'failures))
  415. ;;;###autoload
  416. (defun guix-store-live-items ()
  417. "Display live store items.
  418. This is analogous to 'guix gc --list-live' shell command.
  419. See Info node `(guix) Invoking guix gc'."
  420. (interactive)
  421. (guix-store-item-get-display 'live))
  422. ;;;###autoload
  423. (defun guix-store-dead-items ()
  424. "Display dead store items.
  425. This is analogous to 'guix gc --list-dead' shell command.
  426. See Info node `(guix) Invoking guix gc'."
  427. (interactive)
  428. (guix-store-item-get-display 'dead))
  429. (provide 'guix-ui-store-item)
  430. ;;; guix-ui-store-item.el ends here