guix-ui-generation.el 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. ;;; guix-ui-generation.el --- Interface for displaying generations -*- 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 an interface for displaying profile generations in
  18. ;; 'list' and 'info' buffers, and commands for working with them.
  19. ;;; Code:
  20. (require 'cl-lib)
  21. (require 'dash)
  22. (require 'bui)
  23. (require 'guix nil t)
  24. (require 'guix-ui)
  25. (require 'guix-ui-package)
  26. (require 'guix-ui-profile)
  27. (require 'guix-misc)
  28. (require 'guix-repl)
  29. (require 'guix-guile)
  30. (require 'guix-utils)
  31. (require 'guix-profiles)
  32. (guix-ui-define-entry-type generation)
  33. (defun guix-generation-get-entries (proc profile search-type
  34. search-values params)
  35. "Return 'generation' or 'system-generation' entries.
  36. PROC is the name of a Scheme procedure (either 'generation-sexps'
  37. or 'system-generation-sexps')."
  38. (apply #'guix-modify-objects
  39. (guix-eval-read (guix-make-guile-expression
  40. proc profile search-type search-values params))
  41. (when (or (null params)
  42. (memq 'number-of-packages params))
  43. (list
  44. (lambda (entry)
  45. (let ((generation (bui-entry-non-void-value entry 'number)))
  46. (if generation
  47. `((number-of-packages
  48. . ,(guix-profile-number-of-packages
  49. profile generation))
  50. ,@entry)
  51. entry)))))))
  52. (defun guix-generation-get-display (profile search-type &rest search-values)
  53. "Search for generations and show results.
  54. If PROFILE is nil, use `guix-current-profile'.
  55. See `guix-ui-get-entries' for the meaning of SEARCH-TYPE and
  56. SEARCH-VALUES."
  57. (apply #'bui-list-get-display-entries
  58. 'guix-generation
  59. (or profile guix-current-profile)
  60. search-type search-values))
  61. (defun guix-delete-generations (profile generations
  62. &optional operation-buffer)
  63. "Delete GENERATIONS from PROFILE.
  64. Each element from GENERATIONS is a generation number."
  65. (when (or (not guix-operation-confirm)
  66. (y-or-n-p
  67. (let ((count (length generations)))
  68. (if (> count 1)
  69. (format "Delete %d generations from profile '%s'? "
  70. count profile)
  71. (format "Delete generation %d from profile '%s'? "
  72. (car generations) profile)))))
  73. (guix-eval-in-repl
  74. (guix-make-guile-expression
  75. 'delete-generations* profile generations)
  76. operation-buffer)))
  77. (defun guix-switch-to-generation (profile generation
  78. &optional operation-buffer)
  79. "Switch PROFILE to GENERATION."
  80. (when (or (not guix-operation-confirm)
  81. (y-or-n-p (format "Switch profile '%s' to generation %d? "
  82. profile generation)))
  83. (guix-eval-in-repl
  84. (guix-make-guile-expression
  85. 'switch-to-generation* profile generation)
  86. operation-buffer)))
  87. (defun guix-generation-current-package-profile (&optional generation)
  88. "Return a directory where packages are installed for the
  89. current profile's GENERATION."
  90. (guix-package-profile (guix-ui-current-profile) generation))
  91. ;;; Generation 'info'
  92. (guix-ui-define-interface generation info
  93. :mode-name "Generation-Info"
  94. :buffer-name "*Guix Generation Info*"
  95. :get-entries-function 'guix-generation-info-get-entries
  96. :format '(guix-generation-info-insert-heading
  97. nil
  98. guix-generation-info-insert-buttons
  99. (prev-number format guix-generation-info-insert-previous)
  100. (current format guix-generation-info-insert-current)
  101. (number-of-packages format guix-generation-info-insert-packages)
  102. (file-name simple (indent bui-file))
  103. (time format (time)))
  104. :hint 'guix-generation-info-hint
  105. :titles '((prev-number . "Prev. generation"))
  106. :required '(id number))
  107. (defun guix-generation-info-hint ()
  108. (bui-format-hints
  109. guix-ui-hint
  110. (bui-default-hint)))
  111. (defface guix-generation-info-heading
  112. '((t :inherit bui-info-heading))
  113. "Face used for generation heading."
  114. :group 'guix-generation-info-faces)
  115. (defface guix-generation-info-current
  116. '((t :inherit guix-true))
  117. "Face used if a generation is the current one."
  118. :group 'guix-generation-info-faces)
  119. (defface guix-generation-info-not-current
  120. '((t :inherit guix-false))
  121. "Face used if a generation is not the current one."
  122. :group 'guix-generation-info-faces)
  123. (defun guix-generation-info-get-entries (profile search-type
  124. &rest search-values)
  125. "Return 'generation' entries for displaying them in 'info' buffer."
  126. (guix-generation-get-entries
  127. 'generation-sexps
  128. profile search-type search-values
  129. (cl-union guix-generation-info-required-params
  130. (bui-info-displayed-params 'guix-generation))))
  131. (defun guix-generation-info-insert-heading (entry)
  132. "Insert generation ENTRY heading at point."
  133. (bui-format-insert
  134. (concat "Generation "
  135. (number-to-string (bui-entry-value entry 'number)))
  136. 'guix-generation-info-heading)
  137. (bui-newline))
  138. (defun guix-generation-info-insert-profile-button (file-name)
  139. "Insert 'Profile' button for generation FILE-NAME."
  140. (let ((profile (guix-generation-file-name->profile file-name)))
  141. (bui-insert-action-button
  142. "Profile"
  143. (lambda (btn)
  144. (bui-get-display-entries
  145. 'guix-profile 'info
  146. (list 'file-name (button-get btn 'file-name))))
  147. (format "Show profile '%s'" profile)
  148. 'file-name profile)))
  149. (defun guix-generation-info-insert-buttons (entry)
  150. "Insert some buttons for generation ENTRY at point."
  151. (let ((file-name (bui-entry-non-void-value entry 'file-name)))
  152. (guix-generation-info-insert-profile-button file-name)
  153. (bui-insert-indent)
  154. (guix-profile-info-insert-search-paths-button file-name)
  155. (bui-newline)))
  156. (defun guix-generation-info-insert-previous (prev-number entry)
  157. "Insert PREV-NUMBER and button to compare generations."
  158. (bui-format-insert prev-number)
  159. (bui-insert-indent)
  160. (when (> prev-number 0)
  161. (let ((number (bui-entry-non-void-value entry 'number)))
  162. (bui-insert-action-button
  163. "Compare"
  164. (lambda (btn)
  165. (guix-diff
  166. (guix-profile-generation-packages-buffer
  167. (button-get btn 'prev-number))
  168. (guix-profile-generation-packages-buffer
  169. (button-get btn 'number))))
  170. (format "Show Diff of packages installed in generations %d and %d"
  171. prev-number number)
  172. 'prev-number prev-number
  173. 'number number))))
  174. (defun guix-generation-info-insert-packages (number entry)
  175. "Insert the NUMBER of packages and button to display packages."
  176. (bui-format-insert number)
  177. (bui-insert-indent)
  178. (let ((number (bui-entry-non-void-value entry 'number)))
  179. (bui-insert-action-button
  180. "Packages"
  181. (lambda (btn)
  182. (guix-package-get-display
  183. (guix-generation-current-package-profile
  184. (button-get btn 'number))
  185. 'installed))
  186. (format "Show packages installed in generation %d" number)
  187. 'number number)))
  188. (defun guix-generation-info-insert-current (val entry)
  189. "Insert boolean value VAL showing whether this generation is current."
  190. (if val
  191. (bui-info-insert-value-format "Yes" 'guix-generation-info-current)
  192. (bui-info-insert-value-format "No" 'guix-generation-info-not-current)
  193. (bui-insert-indent)
  194. (let ((number (bui-entry-non-void-value entry 'number)))
  195. (bui-insert-action-button
  196. "Switch"
  197. (lambda (btn)
  198. (guix-switch-to-generation (guix-ui-current-profile)
  199. (button-get btn 'number)
  200. (current-buffer)))
  201. (format "Switch to generation %d (make it the current one)"
  202. number)
  203. 'number number)
  204. (bui-insert-indent)
  205. (bui-insert-action-button
  206. "Delete"
  207. (lambda (btn)
  208. (guix-delete-generations (guix-ui-current-profile)
  209. (list (button-get btn 'number))
  210. (current-buffer)))
  211. (format "Delete generation %d" number)
  212. 'number number))))
  213. ;;; Generation 'list'
  214. (guix-ui-define-interface generation list
  215. :mode-name "Generation-List"
  216. :buffer-name "*Guix Generations*"
  217. :get-entries-function 'guix-generation-list-get-entries
  218. :describe-function 'guix-ui-list-describe
  219. :format '((number nil 5 bui-list-sort-numerically-0 :right-align t)
  220. (current guix-generation-list-get-current 10 t)
  221. (number-of-packages nil 11 bui-list-sort-numerically-2
  222. :right-align t)
  223. (time bui-list-get-time 20 t)
  224. (file-name bui-list-get-file-name 30 t))
  225. :titles '((number . "N.")
  226. (number-of-packages . "Packages"))
  227. :hint 'guix-generation-list-hint
  228. :sort-key '(number . t)
  229. :marks '((delete . ?D)))
  230. (let ((map guix-generation-list-mode-map))
  231. (define-key map (kbd "E") 'guix-generation-list-show-search-paths)
  232. (define-key map (kbd "P") 'guix-generation-list-show-packages)
  233. (define-key map (kbd "+") 'guix-generation-list-show-added-packages)
  234. (define-key map (kbd "-") 'guix-generation-list-show-removed-packages)
  235. (define-key map (kbd "=") 'guix-generation-list-diff)
  236. (define-key map (kbd "e") 'guix-generation-list-ediff)
  237. (define-key map (kbd "x") 'guix-generation-list-execute)
  238. (define-key map (kbd "c") 'guix-generation-list-set-current)
  239. (define-key map (kbd "d") 'guix-generation-list-mark-delete))
  240. (defvar guix-generation-list-default-hint
  241. '(("\\[guix-generation-list-show-packages]") " show packages;\n"
  242. ("\\[guix-generation-list-set-current]") " set current generation;\n"
  243. ("\\[guix-generation-list-diff]") " show Diff of the marked generations;\n"
  244. ("\\[guix-generation-list-mark-delete]") " mark for deletion; "
  245. ("\\[guix-generation-list-execute]") " execute operation (deletions);\n"))
  246. (defun guix-generation-list-hint ()
  247. (bui-format-hints
  248. guix-generation-list-default-hint
  249. guix-ui-hint
  250. (bui-default-hint)))
  251. (defun guix-generation-list-marked-file-names ()
  252. "Return a list of file names of the marked generations.
  253. If nothing is marked, return a list with generation at point."
  254. (let ((entries (bui-current-entries)))
  255. (mapcar (lambda (id)
  256. (bui-entry-non-void-value (bui-entry-by-id entries id)
  257. 'file-name))
  258. ;; XXX This should become available in bui > 1.1.0
  259. ;; (bui-list-marked-or-current)
  260. (or (bui-list-get-marked-id-list)
  261. (list (bui-list-current-id))))))
  262. (defun guix-generation-list-get-entries (profile search-type
  263. &rest search-values)
  264. "Return 'generation' entries for displaying them in 'list' buffer."
  265. (guix-generation-get-entries
  266. 'generation-sexps
  267. profile search-type search-values
  268. (cl-union guix-generation-list-required-params
  269. (bui-list-displayed-params 'guix-generation))))
  270. (defun guix-generation-list-get-current (val &optional _)
  271. "Return string from VAL showing whether this generation is current.
  272. VAL is a boolean value."
  273. (if val "(current)" ""))
  274. (defun guix-generation-list-set-current ()
  275. "Switch current profile to the generation at point."
  276. (interactive)
  277. (let* ((entry (bui-list-current-entry))
  278. (current (bui-entry-non-void-value entry 'current))
  279. (number (bui-entry-non-void-value entry 'number)))
  280. (if current
  281. (user-error "This generation is already the current one")
  282. (guix-switch-to-generation (guix-ui-current-profile)
  283. number (current-buffer)))))
  284. (defun guix-generation-list-show-packages ()
  285. "List installed packages for the generation at point."
  286. (interactive)
  287. (guix-package-get-display
  288. (guix-generation-current-package-profile (bui-list-current-id))
  289. 'installed))
  290. (defun guix-generation-list-show-search-paths (&optional type)
  291. "Display 'search paths' environment variables for the marked generations.
  292. If nothing is marked, use generation on the current line."
  293. (interactive (list (guix-read-search-paths-type)))
  294. (guix-show-search-paths
  295. (guix-generation-list-marked-file-names)
  296. type))
  297. (defun guix-generation-list-generations-to-compare ()
  298. "Return a sorted list of 2 marked generations for comparing."
  299. (let ((numbers (bui-list-get-marked-id-list 'general)))
  300. (if (/= (length numbers) 2)
  301. (user-error "2 generations should be marked for comparing")
  302. (sort numbers #'<))))
  303. (defun guix-generation-list-profiles-to-compare ()
  304. "Return a sorted list of 2 marked generation profiles for comparing."
  305. (mapcar #'guix-generation-current-package-profile
  306. (guix-generation-list-generations-to-compare)))
  307. (defun guix-generation-list-show-added-packages ()
  308. "List package outputs added to the latest marked generation.
  309. If 2 generations are marked with \\[guix-list-mark], display
  310. outputs installed in the latest marked generation that were not
  311. installed in the other one."
  312. (interactive)
  313. (bui-get-display-entries
  314. 'guix-output 'list
  315. (cl-list* (guix-ui-current-profile)
  316. 'profile-diff
  317. (reverse (guix-generation-list-profiles-to-compare)))
  318. 'add))
  319. (defun guix-generation-list-show-removed-packages ()
  320. "List package outputs removed from the latest marked generation.
  321. If 2 generations are marked with \\[guix-list-mark], display
  322. outputs not installed in the latest marked generation that were
  323. installed in the other one."
  324. (interactive)
  325. (bui-get-display-entries
  326. 'guix-output 'list
  327. (cl-list* (guix-ui-current-profile)
  328. 'profile-diff
  329. (guix-generation-list-profiles-to-compare))
  330. 'add))
  331. (defun guix-generation-list-compare (diff-fun gen-fun)
  332. "Run GEN-FUN on the 2 marked generations and run DIFF-FUN on the results."
  333. (cl-multiple-value-bind (gen1 gen2)
  334. (guix-generation-list-generations-to-compare)
  335. (funcall diff-fun
  336. (funcall gen-fun gen1)
  337. (funcall gen-fun gen2))))
  338. (defun guix-generation-list-ediff-manifests ()
  339. "Run Ediff on manifests of the 2 marked generations."
  340. (interactive)
  341. (guix-generation-list-compare
  342. #'ediff-files
  343. #'guix-profile-generation-manifest-file))
  344. (defun guix-generation-list-diff-manifests ()
  345. "Run Diff on manifests of the 2 marked generations."
  346. (interactive)
  347. (guix-generation-list-compare
  348. #'guix-diff
  349. #'guix-profile-generation-manifest-file))
  350. (defun guix-generation-list-ediff-packages ()
  351. "Run Ediff on package outputs installed in the 2 marked generations."
  352. (interactive)
  353. (guix-generation-list-compare
  354. #'ediff-buffers
  355. #'guix-profile-generation-packages-buffer))
  356. (defun guix-generation-list-diff-packages ()
  357. "Run Diff on package outputs installed in the 2 marked generations."
  358. (interactive)
  359. (guix-generation-list-compare
  360. #'guix-diff
  361. #'guix-profile-generation-packages-buffer))
  362. (defun guix-generation-list-ediff (arg)
  363. "Run Ediff on package outputs installed in the 2 marked generations.
  364. With ARG, run Ediff on manifests of the marked generations."
  365. (interactive "P")
  366. (if arg
  367. (guix-generation-list-ediff-manifests)
  368. (guix-generation-list-ediff-packages)))
  369. (defun guix-generation-list-diff (arg)
  370. "Run Diff on package outputs installed in the 2 marked generations.
  371. With ARG, run Diff on manifests of the marked generations."
  372. (interactive "P")
  373. (if arg
  374. (guix-generation-list-diff-manifests)
  375. (guix-generation-list-diff-packages)))
  376. (defun guix-generation-list-mark-delete (&optional arg)
  377. "Mark the current generation for deletion and move to the next line.
  378. With ARG, mark all generations for deletion."
  379. (interactive "P")
  380. (if arg
  381. (bui-list-mark-all 'delete)
  382. (bui-list--mark 'delete t)))
  383. (defun guix-generation-list-execute ()
  384. "Delete marked generations."
  385. (interactive)
  386. (let ((marked (bui-list-get-marked-id-list 'delete)))
  387. (or marked
  388. (user-error "No generations marked for deletion"))
  389. (guix-delete-generations (guix-ui-current-profile)
  390. marked (current-buffer))))
  391. ;;; Inserting packages to compare generations
  392. (defcustom guix-generation-packages-buffer-name-function
  393. #'guix-generation-packages-buffer-name-default
  394. "Function used to define name of a buffer with generation packages.
  395. This function is called with 2 arguments: PROFILE (string) and
  396. GENERATION (number)."
  397. :type '(choice (function-item guix-generation-packages-buffer-name-default)
  398. (function-item guix-generation-packages-buffer-name-long)
  399. (function :tag "Other function"))
  400. :group 'guix-generation)
  401. (defcustom guix-generation-packages-update-buffer t
  402. "If non-nil, always update list of packages during comparing generations.
  403. If nil, generation packages are received only once. So when you
  404. compare generation 1 and generation 2, the packages for both
  405. generations will be received. Then if you compare generation 1
  406. and generation 3, only the packages for generation 3 will be
  407. received. Thus if you use comparing of different generations a
  408. lot, you may set this variable to nil to improve the
  409. performance."
  410. :type 'boolean
  411. :group 'guix-generation)
  412. (defvar guix-generation-output-name-width 30
  413. "Width of an output name \"column\".
  414. This variable is used in auxiliary buffers for comparing generations.")
  415. (defun guix-generation-packages (profile)
  416. "Return a list of sorted packages installed in PROFILE.
  417. Each element of the list is a list of the package specification
  418. and its store file name."
  419. (sort (guix-eval-read
  420. (guix-make-guile-expression
  421. 'profile->specifications+file-names profile))
  422. (lambda (a b)
  423. (string< (car a) (car b)))))
  424. (defun guix-generation-packages-buffer-name-default (profile generation)
  425. "Return name of a buffer for displaying GENERATION's package outputs.
  426. Use base name of PROFILE file name."
  427. (let ((profile-name (file-name-base (directory-file-name profile))))
  428. (format "*Guix %s: generation %s*"
  429. profile-name generation)))
  430. (defun guix-generation-packages-buffer-name-long (profile generation)
  431. "Return name of a buffer for displaying GENERATION's package outputs.
  432. Use the full PROFILE file name."
  433. (format "*Guix generation %s (%s)*"
  434. generation profile))
  435. (defun guix-generation-packages-buffer-name (profile generation)
  436. "Return name of a buffer for displaying GENERATION's package outputs."
  437. (funcall guix-generation-packages-buffer-name-function
  438. profile generation))
  439. (defun guix-generation-insert-package (name file-name)
  440. "Insert package output NAME and store FILE-NAME at point."
  441. (insert name)
  442. (indent-to guix-generation-output-name-width 2)
  443. (insert file-name "\n"))
  444. (defun guix-generation-insert-packages (buffer profile)
  445. "Insert package outputs installed in PROFILE in BUFFER."
  446. (with-current-buffer buffer
  447. (setq buffer-read-only nil
  448. indent-tabs-mode nil)
  449. (erase-buffer)
  450. (mapc (-lambda ((name file-name))
  451. (guix-generation-insert-package name file-name))
  452. (guix-generation-packages profile))))
  453. (defun guix-generation-packages-buffer (profile generation)
  454. "Return buffer with package outputs installed in PROFILE's GENERATION.
  455. Create the buffer if needed."
  456. (let ((buf-name (guix-generation-packages-buffer-name
  457. profile generation)))
  458. (or (and (null guix-generation-packages-update-buffer)
  459. (get-buffer buf-name))
  460. (let ((buf (get-buffer-create buf-name)))
  461. (guix-generation-insert-packages
  462. buf
  463. (guix-package-profile profile generation))
  464. buf))))
  465. (defun guix-profile-generation-manifest-file (generation)
  466. "Return the file name of a GENERATION's manifest.
  467. GENERATION is a generation number of the current profile."
  468. (guix-manifest-file (guix-ui-current-profile) generation))
  469. (defun guix-profile-generation-packages-buffer (generation)
  470. "Insert GENERATION's package outputs in a buffer and return it.
  471. GENERATION is a generation number of the current profile."
  472. (guix-generation-packages-buffer (guix-ui-current-profile)
  473. generation))
  474. ;;; Interactive commands
  475. (declare-function guix-system-generations "guix-ui-system-generation" t)
  476. (declare-function guix-last-system-generations "guix-ui-system-generation" t)
  477. (declare-function guix-system-generations-by-time "guix-ui-system-generation" t)
  478. ;;;###autoload
  479. (defun guix-generations (&optional profile)
  480. "Display information about all generations.
  481. If PROFILE is nil, use `guix-current-profile'.
  482. Interactively with prefix, prompt for PROFILE."
  483. (interactive (list (guix-ui-read-generation-profile)))
  484. (let ((profile (guix-profile profile)))
  485. (if (guix-system-profile? profile)
  486. (guix-system-generations)
  487. (guix-generation-get-display profile 'all))))
  488. ;;;###autoload
  489. (defun guix-last-generations (number &optional profile)
  490. "Display information about last NUMBER generations.
  491. If PROFILE is nil, use `guix-current-profile'.
  492. Interactively with prefix, prompt for PROFILE."
  493. (interactive
  494. (list (read-number "The number of last generations: ")
  495. (guix-ui-read-generation-profile)))
  496. (let ((profile (guix-profile profile)))
  497. (if (guix-system-profile? profile)
  498. (guix-last-system-generations number)
  499. (guix-generation-get-display profile 'last number))))
  500. ;;;###autoload
  501. (defun guix-generations-by-time (from to &optional profile)
  502. "Display information about generations created between FROM and TO.
  503. FROM and TO should be time values.
  504. If PROFILE is nil, use `guix-current-profile'.
  505. Interactively with prefix, prompt for PROFILE."
  506. (interactive
  507. (list (guix-read-date "Find generations (from): ")
  508. (guix-read-date "Find generations (to): ")
  509. (guix-ui-read-generation-profile)))
  510. (let ((profile (guix-profile profile)))
  511. (if (guix-system-profile? profile)
  512. (guix-system-generations-by-time from to)
  513. (guix-generation-get-display profile 'time
  514. (float-time from)
  515. (float-time to)))))
  516. (provide 'guix-ui-generation)
  517. ;;; guix-ui-generation.el ends here