summary refs log tree commit diff
path: root/emacs
diff options
context:
space:
mode:
Diffstat (limited to 'emacs')
-rw-r--r--emacs/guix-info.el558
-rw-r--r--emacs/guix-utils.el35
2 files changed, 285 insertions, 308 deletions
diff --git a/emacs/guix-info.el b/emacs/guix-info.el
index 05a8143202..9c46810f60 100644
--- a/emacs/guix-info.el
+++ b/emacs/guix-info.el
@@ -81,122 +81,175 @@
   "Mouse face used for action buttons."
   :group 'guix-info-faces)
 
-(defcustom guix-info-ignore-empty-vals nil
+(defcustom guix-info-ignore-empty-values nil
   "If non-nil, do not display parameters with nil values."
   :type 'boolean
   :group 'guix-info)
 
+(defcustom guix-info-fill t
+  "If non-nil, fill string parameters to fit the window.
+If nil, insert text parameters (like synopsis or description) in
+a raw form."
+  :type 'boolean
+  :group 'guix-info)
+
 (defvar guix-info-param-title-format "%-18s: "
   "String used to format a title of a parameter.
 It should be a '%s'-sequence.  After inserting a title formatted
 with this string, a value of the parameter is inserted.
-This string is used by `guix-info-insert-title-default'.")
+This string is used by `guix-info-insert-title-format'.")
 
-(defvar guix-info-multiline-prefix (make-string 20 ?\s)
+(defvar guix-info-multiline-prefix
+  (make-string (length (format guix-info-param-title-format " "))
+               ?\s)
   "String used to format multi-line parameter values.
 If a value occupies more than one line, this string is inserted
 in the beginning of each line after the first one.
-This string is used by `guix-info-insert-val-default'.")
+This string is used by `guix-info-insert-value-format'.")
 
 (defvar guix-info-indent 2
   "Number of spaces used to indent various parts of inserted text.")
 
-(defvar guix-info-fill-column 60
-  "Column used for filling (word wrapping) parameters with long lines.
-If a value is not multi-line and it occupies more than this
-number of characters, it will be split into several lines.")
-
 (defvar guix-info-delimiter "\n\f\n"
   "String used to separate entries.")
 
-(defvar guix-info-insert-methods
+(defvar guix-info-format
   '((package
-     (name              guix-package-info-name)
-     (version           guix-package-info-version)
-     (license           guix-package-info-license)
-     (synopsis          guix-package-info-synopsis)
-     (description       guix-package-info-insert-description
-                        guix-info-insert-title-simple)
-     (outputs           guix-package-info-insert-outputs
-                        guix-info-insert-title-simple)
-     (source            guix-package-info-insert-source
-                        guix-info-insert-title-simple)
-     (home-url          guix-info-insert-url)
-     (inputs            guix-package-info-insert-inputs)
-     (native-inputs     guix-package-info-insert-native-inputs)
-     (propagated-inputs guix-package-info-insert-propagated-inputs)
-     (location          guix-package-info-insert-location))
+     guix-package-info-insert-heading
+     ignore
+     (synopsis ignore (simple guix-package-info-synopsis))
+     ignore
+     (description ignore (simple guix-package-info-description))
+     ignore
+     (outputs simple guix-package-info-insert-outputs)
+     (source simple guix-package-info-insert-source)
+     (location format (format guix-package-location))
+     (home-url format (format guix-url))
+     (license format (format guix-package-info-license))
+     (inputs format (format guix-package-input))
+     (native-inputs format (format guix-package-native-input))
+     (propagated-inputs format (format guix-package-propagated-input)))
     (installed
-     (path              guix-package-info-insert-output-path
-                        guix-info-insert-title-simple)
-     (dependencies      guix-package-info-insert-output-dependencies
-                        guix-info-insert-title-simple))
+     (path simple (indent guix-file))
+     (dependencies simple (indent guix-file)))
     (output
-     (name              guix-package-info-name)
-     (version           guix-output-info-insert-version)
-     (output            guix-output-info-insert-output)
-     (source            guix-package-info-insert-source
-                        guix-info-insert-title-simple)
-     (path              guix-package-info-insert-output-path
-                        guix-info-insert-title-simple)
-     (dependencies      guix-package-info-insert-output-dependencies
-                        guix-info-insert-title-simple)
-     (license           guix-package-info-license)
-     (synopsis          guix-package-info-synopsis)
-     (description       guix-package-info-insert-description
-                        guix-info-insert-title-simple)
-     (home-url          guix-info-insert-url)
-     (inputs            guix-package-info-insert-inputs)
-     (native-inputs     guix-package-info-insert-native-inputs)
-     (propagated-inputs guix-package-info-insert-propagated-inputs)
-     (location          guix-package-info-insert-location))
+     (name format (format guix-package-info-name))
+     (version format guix-output-info-insert-version)
+     (output format guix-output-info-insert-output)
+     (synopsis simple (indent guix-package-info-synopsis))
+     (source simple guix-package-info-insert-source)
+     (path simple (indent guix-file))
+     (dependencies simple (indent guix-file))
+     (location format (format guix-package-location))
+     (home-url format (format guix-url))
+     (license format (format guix-package-info-license))
+     (inputs format (format guix-package-input))
+     (native-inputs format (format guix-package-native-input))
+     (propagated-inputs format (format guix-package-propagated-input))
+     (description simple (indent guix-package-info-description)))
     (generation
-     (number            guix-generation-info-insert-number)
-     (current           guix-generation-info-insert-current)
-     (path              guix-info-insert-file-path)
-     (time              guix-info-insert-time)))
+     (number format guix-generation-info-insert-number)
+     (prev-number format (format))
+     (current format guix-generation-info-insert-current)
+     (path simple (indent guix-file))
+     (time format (time))))
   "Methods for inserting parameter values.
 Each element of the list should have a form:
 
-  (ENTRY-TYPE . ((PARAM INSERT-VALUE [INSERT-TITLE]) ...))
-
-INSERT-VALUE may be either nil, a face name or a function.  If it
-is nil or a face, `guix-info-insert-val-default' function is
-called with parameter value and INSERT-VALUE as arguments.  If it
-is a function, this function is called with parameter value and
-entry info (alist of parameters and their values) as arguments.
-
-INSERT-TITLE may be either nil, a face name or a function.  If it
-is nil or a face, `guix-info-insert-title-default' function is
-called with parameter title and INSERT-TITLE as arguments.  If it
-is a function, this function is called with parameter title as
-argument.")
-
-(defvar guix-info-displayed-params
-  '((package name version synopsis outputs source location home-url
-             license inputs native-inputs propagated-inputs description)
-    (output name version output synopsis source path dependencies location
-            home-url license inputs native-inputs propagated-inputs
-            description)
-    (installed path dependencies)
-    (generation number prev-number current time path))
-  "List of displayed entry parameters.
-Each element of the list should have a form:
+  (ENTRY-TYPE . (METHOD ...))
+
+Each METHOD should be either a function or should have the
+following form:
+
+  (PARAM INSERT-TITLE INSERT-VALUE)
+
+If METHOD is a function, it is called with an entry as argument.
+
+PARAM is a name of entry parameter.
+
+INSERT-TITLE may be either a symbol or a list.  If it is a
+symbol, it should be a function or an alias from
+`guix-info-title-aliases', in which case it is called with title
+as argument.  If it is a list, it should have a
+form (FUN-OR-ALIAS [ARGS ...]), in which case FUN-OR-ALIAS is
+called with title and ARGS as arguments.
 
-  (ENTRY-TYPE . (PARAM ...))
+INSERT-VALUE may be either a symbol or a list.  If it is a
+symbol, it should be a function or an alias from
+`guix-info-value-aliases', in which case it is called with value
+and entry as arguments.  If it is a list, it should have a
+form (FUN-OR-ALIAS [ARGS ...]), in which case FUN-OR-ALIAS is
+called with value and ARGS as arguments.
 
-The order of displayed parameters is the same as in this list.")
+Parameters are inserted in the same order as defined by this list.
+After calling each METHOD, a new line is inserted.")
 
-(defun guix-info-insert-methods (entry-type param)
-  "Return list of insert methods for parameter PARAM of ENTRY-TYPE.
-See `guix-info-insert-methods' for details."
-  (guix-assq-value guix-info-insert-methods
-                   entry-type param))
+(defun guix-info-param-title (entry-type param)
+  "Return a title of an ENTRY-TYPE parameter PARAM."
+  (guix-get-param-title entry-type param))
+
+(defun guix-info-format (entry-type)
+  "Return 'info' format for ENTRY-TYPE."
+  (guix-assq-value guix-info-format entry-type))
 
 (defun guix-info-displayed-params (entry-type)
-  "Return parameters of ENTRY-TYPE that should be displayed."
-  (guix-assq-value guix-info-displayed-params
-                   entry-type))
+  "Return a list of ENTRY-TYPE parameters that should be displayed."
+  (delq nil
+        (mapcar (lambda (spec)
+                  (pcase spec
+                    (`(,param . ,_) param)))
+                (guix-info-format entry-type))))
+
+
+;;; Inserting entries
+
+(defvar guix-info-title-aliases
+  '((format . guix-info-insert-title-format)
+    (simple . guix-info-insert-title-simple))
+  "Alist of aliases and functions to insert titles.")
+
+(defvar guix-info-value-aliases
+  '((format . guix-info-insert-value-format)
+    (indent . guix-info-insert-value-indent)
+    (simple . guix-info-insert-value-simple)
+    (time   . guix-info-insert-time))
+  "Alist of aliases and functions to insert values.")
+
+(defun guix-info-title-function (fun-or-alias)
+  "Convert FUN-OR-ALIAS into a function to insert a title."
+  (or (guix-assq-value guix-info-title-aliases fun-or-alias)
+      fun-or-alias))
+
+(defun guix-info-value-function (fun-or-alias)
+  "Convert FUN-OR-ALIAS into a function to insert a value."
+  (or (guix-assq-value guix-info-value-aliases fun-or-alias)
+      fun-or-alias))
+
+(defun guix-info-title-method->function (method)
+  "Convert title METHOD into a function to insert a title."
+  (pcase method
+    ((pred null) #'ignore)
+    ((pred symbolp) (guix-info-title-function method))
+    (`(,fun-or-alias . ,rest-args)
+     (lambda (title)
+       (apply (guix-info-title-function fun-or-alias)
+              title rest-args)))
+    (_ (error "Unknown title method '%S'" method))))
+
+(defun guix-info-value-method->function (method)
+  "Convert value METHOD into a function to insert a value."
+  (pcase method
+    ((pred null) #'ignore)
+    ((pred functionp) method)
+    (`(,fun-or-alias . ,rest-args)
+     (lambda (value _)
+       (apply (guix-info-value-function fun-or-alias)
+              value rest-args)))
+    (_ (error "Unknown value method '%S'" method))))
+
+(defun guix-info-fill-column ()
+  "Return fill column for the current window."
+  (min (window-width) fill-column))
 
 (defun guix-info-get-indent (&optional level)
   "Return `guix-info-indent' \"multiplied\" by LEVEL spaces.
@@ -215,115 +268,122 @@ ENTRIES should have a form of `guix-entries'."
                   entries
                   guix-info-delimiter))
 
-(defun guix-info-insert-entry-default (entry entry-type
-                                       &optional indent-level)
+(defun guix-info-insert-entry (entry entry-type &optional indent-level)
   "Insert ENTRY of ENTRY-TYPE into the current info buffer.
-If INDENT-LEVEL is non-nil, indent displayed information by this
-number of `guix-info-indent' spaces."
+If INDENT-LEVEL is non-nil, indent displayed data by this number
+of `guix-info-indent' spaces."
   (guix-with-indent (* (or indent-level 0)
                        guix-info-indent)
-    (mapc (lambda (param)
-            (guix-info-insert-param param entry entry-type))
-          (guix-info-displayed-params entry-type))))
+    (dolist (spec (guix-info-format entry-type))
+      (guix-info-insert-entry-unit spec entry entry-type))))
 
-(defun guix-info-insert-entry (entry entry-type &optional indent-level)
-  "Insert ENTRY of ENTRY-TYPE into the current info buffer.
-Use `guix-info-insert-ENTRY-TYPE-function' or
-`guix-info-insert-entry-default' if it is nil."
-  (let* ((var (intern (concat "guix-info-insert-"
-                              (symbol-name entry-type)
-                              "-function")))
-         (fun (symbol-value var)))
-    (if (functionp fun)
-        (funcall fun entry)
-      (guix-info-insert-entry-default entry entry-type indent-level))))
-
-(defun guix-info-insert-param (param entry entry-type)
+(defun guix-info-insert-entry-unit (format-spec entry entry-type)
   "Insert title and value of a PARAM at point.
 ENTRY is alist with parameters and their values.
 ENTRY-TYPE is a type of ENTRY."
-  (let ((val (guix-entry-value entry param)))
-    (unless (and guix-info-ignore-empty-vals (null val))
-      (let* ((title          (guix-get-param-title entry-type param))
-             (insert-methods (guix-info-insert-methods entry-type param))
-             (val-method     (car insert-methods))
-             (title-method   (cadr insert-methods)))
-        (guix-info-method-funcall title title-method
-                                  #'guix-info-insert-title-default)
-        (guix-info-method-funcall val val-method
-                                  #'guix-info-insert-val-default
-                                  entry)
-        (insert "\n")))))
-
-(defun guix-info-method-funcall (val method default-fun &rest args)
-  "Call METHOD or DEFAULT-FUN.
-
-If METHOD is a function and VAL is non-nil, call this
-function by applying it to VAL and ARGS.
-
-If METHOD is a face, propertize inserted VAL with this face."
-  (cond ((or (null method)
-             (facep method))
-         (funcall default-fun val method))
-        ((functionp method)
-         (apply method val args))
-        (t (error "Unknown method '%S'" method))))
-
-(defun guix-info-insert-title-default (title &optional face format)
-  "Insert TITLE formatted with `guix-info-param-title-format' at point."
+  (pcase format-spec
+    ((pred functionp)
+     (funcall format-spec entry)
+     (insert "\n"))
+    (`(,param ,title-method ,value-method)
+     (let ((value (guix-entry-value entry param)))
+       (unless (and guix-info-ignore-empty-values (null value))
+         (let ((title        (guix-info-param-title entry-type param))
+               (insert-title (guix-info-title-method->function title-method))
+               (insert-value (guix-info-value-method->function value-method)))
+           (funcall insert-title title)
+           (funcall insert-value value entry)
+           (insert "\n")))))
+    (_ (error "Unknown format specification '%S'" format-spec))))
+
+(defun guix-info-insert-title-simple (title &optional face)
+  "Insert \"TITLE: \" string at point.
+If FACE is nil, use `guix-info-param-title'."
   (guix-format-insert title
                       (or face 'guix-info-param-title)
-                      (or format guix-info-param-title-format)))
+                      "%s: "))
 
-(defun guix-info-insert-title-simple (title &optional face)
-  "Insert TITLE at point."
-  (guix-info-insert-title-default title face "%s:"))
-
-(defun guix-info-insert-val-default (val &optional face)
-  "Format and insert parameter value VAL at point.
-
-This function is intended to be called after
-`guix-info-insert-title-default'.
-
-If VAL is a one-line string longer than `guix-info-fill-column',
-split it into several short lines.  See also
-`guix-info-multiline-prefix'.
-
-If FACE is non-nil, propertize inserted line(s) with this FACE."
-  (guix-split-insert val face
-                     guix-info-fill-column
-                     (concat "\n" guix-info-multiline-prefix)))
-
-(defun guix-info-insert-val-simple (val &optional face-or-fun)
-  "Format and insert parameter value VAL at point.
-
-This function is intended to be called after
-`guix-info-insert-title-simple'.
-
-If VAL is a one-line string longer than `guix-info-fill-column',
-split it into several short lines and indent each line with
-`guix-info-indent' spaces.
-
-If FACE-OR-FUN is a face, propertize inserted line(s) with this FACE.
-
-If FACE-OR-FUN is a function, call it with VAL as argument.  If
-VAL is a list, call the function on each element of this list."
-  (if (null val)
-      (progn (guix-info-insert-indent)
-             (guix-format-insert nil))
-    (let ((prefix (concat "\n" (guix-info-get-indent))))
-      (insert prefix)
-      (if (functionp face-or-fun)
-          (guix-mapinsert face-or-fun
-                          (if (listp val) val (list val))
-                          prefix)
-        (guix-split-insert val face-or-fun
-                           guix-info-fill-column prefix)))))
-
-(defun guix-info-insert-time (seconds &optional _)
+(defun guix-info-insert-title-format (title &optional face)
+  "Insert TITLE using `guix-info-param-title-format' at point.
+If FACE is nil, use `guix-info-param-title'."
+  (guix-format-insert title
+                      (or face 'guix-info-param-title)
+                      guix-info-param-title-format))
+
+(defun guix-info-insert-value-simple (value &optional button-or-face indent)
+  "Format and insert parameter VALUE at point.
+
+VALUE may be split into several short lines to fit the current
+window, depending on `guix-info-fill', and each line is indented
+with INDENT number of spaces.
+
+If BUTTON-OR-FACE is a button type symbol, transform VALUE into
+this (these) button(s) and insert each one on a new line.  If it
+is a face symbol, propertize inserted line(s) with this face."
+  (or indent (setq indent 0))
+  (guix-with-indent indent
+    (let* ((button?  (guix-button-type? button-or-face))
+           (face     (unless button? button-or-face))
+           (fill-col (unless (or button?
+                                 (and (stringp value)
+                                      (not guix-info-fill)))
+                       (- (guix-info-fill-column) indent)))
+           (value    (if (and value button?)
+                         (guix-buttonize value button-or-face "\n")
+                       value)))
+      (guix-split-insert value face fill-col "\n"))))
+
+(defun guix-info-insert-value-indent (value &optional button-or-face)
+  "Format and insert parameter VALUE at point.
+
+This function is intended to be called after inserting a title
+with `guix-info-insert-title-simple'.
+
+VALUE may be split into several short lines to fit the current
+window, depending on `guix-info-fill', and each line is indented
+with `guix-info-indent'.
+
+For the meaning of BUTTON-OR-FACE, see `guix-info-insert-value-simple'."
+  (when value (insert "\n"))
+  (guix-info-insert-value-simple value button-or-face guix-info-indent))
+
+(defun guix-info-insert-value-format (value &optional button-or-face
+                                            &rest button-properties)
+  "Format and insert parameter VALUE at point.
+
+This function is intended to be called after inserting a title
+with `guix-info-insert-title-format'.
+
+VALUE may be split into several short lines to fit the current
+window, depending on `guix-info-fill' and
+`guix-info-multiline-prefix'.  If VALUE is a list, its elements
+will be separated with `guix-list-separator'.
+
+If BUTTON-OR-FACE is a button type symbol, transform VALUE into
+this (these) button(s).  If it is a face symbol, propertize
+inserted line(s) with this face.
+
+BUTTON-PROPERTIES are passed to `guix-buttonize' (only if
+BUTTON-OR-FACE is a button type)."
+  (let* ((button?  (guix-button-type? button-or-face))
+         (face     (unless button? button-or-face))
+         (fill-col (when (or button?
+                             guix-info-fill
+                             (not (stringp value)))
+                     (- (guix-info-fill-column)
+                        (length guix-info-multiline-prefix))))
+         (value    (if (and value button?)
+                       (apply #'guix-buttonize
+                              value button-or-face guix-list-separator
+                              button-properties)
+                     value)))
+    (guix-split-insert value face fill-col
+                       (concat "\n" guix-info-multiline-prefix))))
+
+(defun guix-info-insert-time (seconds &optional face)
   "Insert formatted time string using SECONDS at point."
-  (guix-info-insert-val-default (guix-get-time-string seconds)
-                                'guix-info-time))
+  (guix-format-insert (guix-get-time-string seconds)
+                      (or face 'guix-info-time)))
 
 
 ;;; Buttons
@@ -394,14 +454,6 @@ See `insert-text-button' for the meaning of PROPERTIES."
          'help-echo message
          properties))
 
-(defun guix-info-insert-file-path (path &optional _)
-  "Make button from file PATH and insert it at point."
-  (guix-insert-button path 'guix-file))
-
-(defun guix-info-insert-url (url &optional _)
-  "Make button from URL and insert it at point."
-  (guix-insert-button url 'guix-url))
-
 
 (defvar guix-info-mode-map
   (let ((map (make-sparse-keymap)))
@@ -418,7 +470,7 @@ See `insert-text-button' for the meaning of PROPERTIES."
 ;;; Displaying packages
 
 (guix-define-buffer-type info package
-  :required (id installed non-unique))
+  :required (id name version installed non-unique))
 
 (defface guix-package-info-heading
   '((t :inherit guix-info-heading))
@@ -483,57 +535,11 @@ See `insert-text-button' for the meaning of PROPERTIES."
   "Face used if a package is obsolete."
   :group 'guix-package-info-faces)
 
-(defvar guix-info-insert-package-function
-  #'guix-package-info-insert-with-heading
-  "Function used to insert a package information.
-It is called with a single argument - alist of package parameters.
-If nil, insert package in a default way.")
-
-(defvar guix-package-info-heading-params '(synopsis description)
-  "List of parameters displayed in a heading along with name and version.")
-
-(defcustom guix-package-info-fill-heading t
-  "If nil, insert heading parameters in a raw form, without
-filling them to fit the window."
-  :type 'boolean
-  :group 'guix-package-info)
-
 (defun guix-package-info-insert-heading (entry)
-  "Insert the heading for package ENTRY.
-Show package name, version, and `guix-package-info-heading-params'."
+  "Insert package ENTRY heading (name specification) at point."
   (guix-format-insert (concat (guix-entry-value entry 'name) " "
                               (guix-entry-value entry 'version))
-                      'guix-package-info-heading)
-  (insert "\n\n")
-  (mapc (lambda (param)
-          (let ((val  (guix-entry-value entry param))
-                (face (guix-get-symbol (symbol-name param)
-                                       'info 'package)))
-            (when val
-              (let* ((col (min (window-width) fill-column))
-                     (val (if guix-package-info-fill-heading
-                              (guix-get-filled-string val col)
-                            val)))
-                (guix-format-insert val (and (facep face) face))
-                (insert "\n\n")))))
-        guix-package-info-heading-params))
-
-(defun guix-package-info-insert-with-heading (entry)
-  "Insert package ENTRY with its heading at point."
-  (guix-package-info-insert-heading entry)
-  (mapc (lambda (param)
-          (unless (or (memq param '(name version))
-                      (memq param guix-package-info-heading-params))
-            (guix-info-insert-param param entry 'package)))
-        (guix-info-displayed-params 'package)))
-
-(defun guix-package-info-insert-description (desc &optional _)
-  "Insert description DESC at point."
-  (guix-info-insert-val-simple desc 'guix-package-info-description))
-
-(defun guix-package-info-insert-location (location &optional _)
-  "Make button from file LOCATION and insert it at point."
-  (guix-insert-button location 'guix-package-location))
+                      'guix-package-info-heading))
 
 (defmacro guix-package-info-define-insert-inputs (&optional type)
   "Define a face and a function for inserting package inputs.
@@ -544,8 +550,7 @@ Face name is `guix-package-info-TYPE-inputs'."
          (type-name (and type (concat type-str "-")))
          (type-desc (and type (concat type-str " ")))
          (face (intern (concat "guix-package-info-" type-name "inputs")))
-         (btn  (intern (concat "guix-package-" type-name "input")))
-         (fun  (intern (concat "guix-package-info-insert-" type-name "inputs"))))
+         (btn  (intern (concat "guix-package-" type-name "input"))))
     `(progn
        (defface ,face
          '((t :inherit guix-package-info-name-button))
@@ -554,29 +559,12 @@ Face name is `guix-package-info-TYPE-inputs'."
 
        (define-button-type ',btn
          :supertype 'guix-package-name
-         'face ',face)
-
-       (defun ,fun (inputs &optional _)
-         ,(concat "Make buttons from " type-desc "INPUTS and insert them at point.")
-         (guix-package-info-insert-full-names inputs ',btn)))))
+         'face ',face))))
 
 (guix-package-info-define-insert-inputs)
 (guix-package-info-define-insert-inputs native)
 (guix-package-info-define-insert-inputs propagated)
 
-(defun guix-package-info-insert-full-names (names button-type)
-  "Make BUTTON-TYPE buttons from package NAMES and insert them at point.
-NAMES is a list of strings."
-  (if names
-      (guix-info-insert-val-default
-       (with-temp-buffer
-         (guix-mapinsert (lambda (name)
-                           (guix-insert-button name button-type))
-                         names
-                         guix-list-separator)
-         (buffer-substring (point-min) (point-max))))
-    (guix-format-insert nil)))
-
 
 ;;; Inserting outputs and installed parameters
 
@@ -588,12 +576,6 @@ formatted with this string, an action button is inserted.")
 (defvar guix-package-info-obsolete-string "(This package is obsolete)"
   "String used if a package is obsolete.")
 
-(defvar guix-info-insert-installed-function nil
-  "Function used to insert an installed information.
-It is called with a single argument - alist of installed
-parameters (`output', `path', `dependencies').
-If nil, insert installed info in a default way.")
-
 (defun guix-package-info-insert-outputs (outputs entry)
   "Insert OUTPUTS from package ENTRY at point."
   (and (guix-entry-value entry 'obsolete)
@@ -668,13 +650,6 @@ ENTRY is an alist with package info."
              (guix-entry-id entry))
      'output output)))
 
-(defun guix-package-info-insert-output-path (path &optional _)
-  "Insert PATH of the installed output."
-  (guix-info-insert-val-simple path #'guix-info-insert-file-path))
-
-(defalias 'guix-package-info-insert-output-dependencies
-  'guix-package-info-insert-output-path)
-
 
 ;;; Inserting a source
 
@@ -711,10 +686,6 @@ prompt depending on `guix-operation-confirm' variable)."
             ;; no action is bound to a source button.
             (message "Yes, this is the source URL. What did you expect?")))
 
-(defun guix-package-info-insert-source-url (url &optional _)
-  "Make button from source URL and insert it at point."
-  (guix-insert-button url 'guix-package-source))
-
 (defun guix-package-info-show-source (entry-id package-id)
   "Show file name of a package source in the current info buffer.
 Find the file if needed (see `guix-package-info-auto-find-source').
@@ -746,7 +717,6 @@ PACKAGE-ID is an ID of the package which source to show."
 (defun guix-package-info-insert-source (source entry)
   "Insert SOURCE from package ENTRY at point.
 SOURCE is a list of URLs."
-  (guix-info-insert-indent)
   (if (null source)
       (guix-format-insert nil)
     (let* ((source-file (guix-entry-value entry 'source-file))
@@ -759,7 +729,7 @@ SOURCE is a list of URLs."
            (lambda (btn)
              (guix-package-info-show-source (button-get btn 'entry-id)
                                             (button-get btn 'package-id)))
-           "Show the source store path of the current package"
+           "Show the source store directory of the current package"
            'entry-id entry-id
            'package-id package-id)
         (unless (file-exists-p source-file)
@@ -770,10 +740,8 @@ SOURCE is a list of URLs."
               (button-get btn 'package-id)))
            "Download the source into the store"
            'package-id package-id))
-        (guix-info-insert-val-simple source-file
-                                     #'guix-info-insert-file-path))
-      (guix-info-insert-val-simple source
-                                   #'guix-package-info-insert-source-url))))
+        (guix-info-insert-value-indent source-file 'guix-file))
+      (guix-info-insert-value-indent source 'guix-package-source))))
 
 (defun guix-package-info-redisplay-after-download ()
   "Redisplay an 'info' buffer after downloading the package source.
@@ -788,19 +756,14 @@ This function is used to hide a \"Download\" button if needed."
 
 ;;; Displaying outputs
 
-(guix-define-buffer-type info output
+(guix-ui-info-define-interface output
   :buffer-name "*Guix Package Info*"
   :required (id package-id installed non-unique))
 
-(defvar guix-info-insert-output-function nil
-  "Function used to insert an output information.
-It is called with a single argument - alist of output parameters.
-If nil, insert output in a default way.")
-
 (defun guix-output-info-insert-version (version entry)
   "Insert output VERSION and obsolete text if needed at point."
-  (guix-info-insert-val-default version
-                                'guix-package-info-version)
+  (guix-info-insert-value-format version
+                                 'guix-package-info-version)
   (and (guix-entry-value entry 'obsolete)
        (guix-package-info-insert-obsolete-text)))
 
@@ -809,7 +772,7 @@ If nil, insert output in a default way.")
   (let* ((installed (guix-entry-value entry 'installed))
          (obsolete  (guix-entry-value entry 'obsolete))
          (action-type (if installed 'delete 'install)))
-    (guix-info-insert-val-default
+    (guix-info-insert-value-format
      output
      (if installed
          'guix-package-info-installed-outputs
@@ -840,14 +803,9 @@ If nil, insert output in a default way.")
   "Face used if a generation is not the current one."
   :group 'guix-generation-info-faces)
 
-(defvar guix-info-insert-generation-function nil
-  "Function used to insert a generation information.
-It is called with a single argument - alist of generation parameters.
-If nil, insert generation in a default way.")
-
 (defun guix-generation-info-insert-number (number &optional _)
   "Insert generation NUMBER and action buttons."
-  (guix-info-insert-val-default number 'guix-generation-info-number)
+  (guix-info-insert-value-format number 'guix-generation-info-number)
   (guix-info-insert-indent)
   (guix-info-insert-action-button
    "Packages"
@@ -868,8 +826,8 @@ If nil, insert generation in a default way.")
 (defun guix-generation-info-insert-current (val entry)
   "Insert boolean value VAL showing whether this generation is current."
   (if val
-      (guix-info-insert-val-default "Yes" 'guix-generation-info-current)
-    (guix-info-insert-val-default "No" 'guix-generation-info-not-current)
+      (guix-info-insert-value-format "Yes" 'guix-generation-info-current)
+    (guix-info-insert-value-format "No" 'guix-generation-info-not-current)
     (guix-info-insert-indent)
     (guix-info-insert-action-button
      "Switch"
diff --git a/emacs/guix-utils.el b/emacs/guix-utils.el
index fbe0a613da..3c75417a08 100644
--- a/emacs/guix-utils.el
+++ b/emacs/guix-utils.el
@@ -104,6 +104,28 @@ See `insert-text-button' for the meaning of PROPERTIES."
            :type (or type 'button)
            properties)))
 
+(defun guix-buttonize (value button-type separator &rest properties)
+  "Make BUTTON-TYPE button(s) from VALUE.
+Return a string with button(s).
+
+VALUE should be a string or a list of strings.  If it is a list
+of strings, buttons are separated with SEPARATOR string.
+
+PROPERTIES are passed to `guix-insert-button'."
+  (with-temp-buffer
+    (let ((labels (if (listp value) value (list value))))
+      (guix-mapinsert (lambda (label)
+                        (apply #'guix-insert-button
+                               label button-type properties))
+                      labels
+                      separator))
+    (buffer-substring (point-min) (point-max))))
+
+(defun guix-button-type? (symbol)
+  "Return non-nil, if SYMBOL is a button type."
+  (and symbol
+       (get symbol 'button-category-symbol)))
+
 (defun guix-split-insert (val &optional face col separator)
   "Convert VAL into a string, split it and insert at point.
 
@@ -122,14 +144,11 @@ Separate inserted lines with SEPARATOR."
 
 (defun guix-split-string (str &optional col)
   "Split string STR by lines and return list of result strings.
-If COL is non-nil and STR is a one-line string longer than COL,
-split it into several short lines."
-  (let ((strings (split-string str "\n *")))
-    (if (and col
-             (null (cdr strings))       ; if not multi-line
-             (> (length str) col))
-        (split-string (guix-get-filled-string str col) "\n")
-      strings)))
+If COL is non-nil, fill STR to this column."
+  (let ((str (if col
+                 (guix-get-filled-string str col)
+               str)))
+    (split-string str "\n *" t)))
 
 (defun guix-get-filled-string (str col)
   "Return string by filling STR to column COL."