# A few cases of literate configuration
Freedom is a state of mind
Author: SqrtMinusOne

A post that arose from the discussion of literate configuration on the System Crafters Discord.

I am using the literate configuration strategy (based on Emacs’ Org Mode) to manage most of my configuration files. A piece of such a configuration can be as simple as an Org file, which is tangled to one or many plain-text configuration files, but it can be more.

In my opinion, a literate configuration can be more straightforward and concise than a “normal” one, thanks to Org Mode’s capabilities of working with source code. So here I present a few examples from my configuration where I think this is the case:

- Managing system colors
- Managing manifests for Guix profiles
- Configuring modules in polybar

I hope you find something interesting here!

## Colors

Let’s start with system colors.

My favorite color theme is Palenight (color codes), and I want to have one source of truth for these colors. Except for Emacs itself, which has doom-palenight (and in which I occasionally switch to `doom-one-light`, e.g. when reading a long text), it can be done rather nicely with Org Mode.

First, let’s define a table with all the color codes:

    #+tblname: colors
    | color         | key     | value   |
    |---------------+---------+---------|
    | black         | color0  | #292d3e |
    | red           | color1  | #f07178 |
    | green         | color2  | #c3e88d |
    | yellow        | color3  | #ffcb6b |
    | blue          | color4  | #82aaff |
    | magenta       | color5  | #c792ea |
    | cyan          | color6  | #89ddff |
    | white         | color7  | #d0d0d0 |
    | light-black   | color8  | #434758 |
    | light-red     | color9  | #ff8b92 |
    | light-green   | color10 | #ddffa7 |
    | light-yellow  | color11 | #ffe585 |
    | light-blue    | color12 | #9cc4ff |
    | light-magenta | color13 | #e1acff |
    | light-cyan    | color14 | #a3f7ff |
    | light-white   | color15 | #ffffff |
    | color-fg      |         | #000000 |

Contents of this table can then be accessed from a code block. Let’s define one to return the color code based on its name:

    #+NAME: get-color
    #+begin_src emacs-lisp :var table=colors name="black" quote=0
    (let ((color (seq-some (lambda (e) (and (string= name (car e)) (nth 2 e))) table)))
      (if (> quote 0)
          (concat "\"" color "\"")
        color))
    #+end_src

Evaluating this block of code should return color code, corresponding to “black”.

And the best part is that the results of evaluation of one code block can be included to others with noweb. For instance, here’s my zathura config:

    #+begin_src conf-space :noweb yes :tangle .config/zathura/zathurarc
    set abort-clear-search false
    set guioptions cs
    set selection-clipboard clipboard
    set recolor true
    map <C-r> set recolor false
    map <C-R> set recolor true

    set recolor-lightcolor <<get-color(name="black", quote=1)>>

    set completion-bg <<get-color(name="black", quote=1)>>
    set completion-fg <<get-color(name="white", quote=1)>>
    set completion-group-bg <<get-color(name="light-black", quote=1)>>
    set completion-group-fg <<get-color(name="white", quote=1)>>
    set completion-highlight-bg <<get-color(name="magenta", quote=1)>>
    set completion-highlight-fg <<get-color(name="black", quote=1)>>

    set inputbar-bg <<get-color(name="black", quote=1)>>
    set inputbar-fg <<get-color(name="light-magenta", quote=1)>>
    set statusbar-bg <<get-color(name="black", quote=1)>>
    set statusbar-fg <<get-color(name="light-magenta", quote=1)>>

    set notification-error-bg <<get-color(name="red", quote=1)>>
    set notification-error-fg <<get-color(name="color-fg", quote=1)>>
    set notification-warning-bg <<get-color(name="yellow", quote=1)>>
    set notification-warning-fg <<get-color(name="color-fg", quote=1)>>
    #+end_src

Running `M-x org-babel-expand-src-block` (`C-c C-v v`) on this code block will open the code buffer with noweb expressions expanded, for instance the line with `set recolor-lightcolor` will look like:

    set recolor-lightcolor "#292d3e"

`M-x org-babel-tangle` (`C-c C-v t`) will also produce `zathurarc` with the colors set (given that there’s `:noweb yes` somewhere in the code block configuration).

One note is that by default running these commands will require the user to confirm evaluation of each code block. To avoid that, you can set `org-confirm-babel-evaluate` to `nil`, for example:

    (setq my/org-config-files
          '("/home/pavel/Emacs.org"
            "/home/pavel/Desktop.org"
            "/home/pavel/Console.org"
            "/home/pavel/Guix.org"
            "/home/pavel/Mail.org"))

    (add-hook 'org-mode-hook
              (lambda ()
                (when (member (buffer-file-name) my/org-config-files)
                  (setq-local org-confirm-babel-evaluate nil))))

And, to close the loop on colors, let’s generate `.Xresources` from that table:

    #+NAME: get-xresources
    #+begin_src emacs-lisp :var table=colors
    (mapconcat
     (lambda (elem)
       (concat "*" (nth 1 elem) ": " (nth 2 elem)))
     (seq-filter
      (lambda (elem) (and (nth 1 elem)
                          (not (string-empty-p (nth 1 elem)))))
      table)
     "\n")
    #+end_src

    #+begin_src conf-xdefaults :noweb yes :tangle ~/.Xresources
    <<get-xresources()>>

    *background: <<get-color(name="black")>>
    *foreground: <<get-color(name="white")>>
    #+end_src

So, whenever a program is capable of reading `.Xresources`, it will get colors from there, otherwise, it will get colors from noweb expressions in the literate config. Thus, in both cases, the color is set in a single Org Mode table.

## Guix dependencies

Another case I want to cover is using profiles in GNU Guix.

A “profile” in Guix is a way to group package installations. For instance, I have a “music” profile that has software like MPD, ncmpcpp that I’m still occasionally using because of its tag editor, etc. Corresponding to that profile, there’s a manifest named `music.scm` that looks like this:

    (specifications->manifest
     '(
       "flac"
       "cuetools"
       "shntool"
       "mpd-mpc"
       "mpd-watcher"
       "picard"
       "ncmpcpp"
       "mpd"))

I could generate this file with `org-babel` as any other, but that is often not so convenient. For example, I have a polybar module that uses sunwait to show sunset and sunrise times, and ideally, I want to declare `sunwait` to be in the “desktop-polybar” profile in the same section that has the polybar module definition and the bash script.

So here’s an approach I came up with. The relevant section of the config looks like this:

    *** sun
    | Category        | Guix dependency |
    |-----------------+-----------------|
    | desktop-polybar | sunwait         |

    Prints out the time of sunrise/sunset. Uses [[https://github.com/risacher/sunwait][sunwait]]

    #+begin_src bash :tangle ./bin/polybar/sun.sh :noweb yes
    ...script...
    #+end_src

    #+begin_src conf-windows :noweb yes
    ...polybar module definition...
    #+end_src

`sunwait` is declared in an Org table that looks like that:

Category | Guix dependency
--- | ---
desktop-polybar | sunwait

Such tables are spread through my `Desktop.org`, for instance, here is another one for a polybar module that depends on dateutils:

Category | Guix dependency
--- | ---
desktop-polybar | dateutils

Thus I made a function that extracts packages from all such tables from the current Org buffer. The rules are as follows:

- If a column name matches `[G|g]uix.*dep`, its contents are added to the result.
- If `CATEGORY` is passed, a column with name `[C|c]ategory` is used to filter results. That way, one Org file can be used to produce multiple manifests.
- If `CATEGORY` is not passed, entries with the non-empty category are filtered out
- If there is a `[D|d]isabled` column, entries that have a non-empty value in this column are filtered out.

And here is the implementation:

    (defun my/extract-guix-dependencies (&optional category)
      (let ((dependencies '()))
        (org-table-map-tables
         (lambda ()
           (let* ((table
                   (seq-filter
                    (lambda (q) (not (eq q 'hline)))
                    (org-table-to-lisp)))
                  (dep-name-index
                   (cl-position
                    nil
                    (mapcar #'substring-no-properties (nth 0 table))
                    :test (lambda (_ elem)
                            (string-match-p "[G|g]uix.*dep" elem))))
                  (category-name-index
                   (cl-position
                    nil
                    (mapcar #'substring-no-properties (nth 0 table))
                    :test (lambda (_ elem)
                            (string-match-p ".*[C|c]ategory.*" elem))))
                  (disabled-name-index
                   (cl-position
                    nil
                    (mapcar #'substring-no-properties (nth 0 table))
                    :test (lambda (_ elem)
                            (string-match-p ".*[D|d]isabled.*" elem)))))
             (when dep-name-index
               (dolist (elem (cdr table))
                 (when
                     (and
                      ;; Category
                      (or
                       ;; Category is not set and not present in the table
                       (and
                        (or (not category) (string-empty-p category))
                        (not category-name-index))
                       ;; Category is set and present in the table
                       (and
                        category-name-index
                        (not (string-empty-p category))
                        (string-match-p category (nth category-name-index elem))))
                      ;; Not disabled
                      (or
                       (not disabled-name-index)
                       (string-empty-p (nth disabled-name-index elem))))
                   (add-to-list
                    'dependencies
                    (substring-no-properties (nth dep-name-index elem)))))))))
        dependencies))

Let’s execute this function in the current buffer:

    (my/extract-guix-dependencies "desktop-polybar")

    ("dateutils" "sunwait")

As expected, it found both `dateutils` and `sunwait`. To make it work in the configuration, it is necessary to format the list so that Scheme could read it:

    (defun my/format-guix-dependencies (&optional category)
      (mapconcat
       (lambda (e) (concat "\"" e "\""))
       (my/extract-guix-dependencies category)
       "\n"))

And we need an Org snippet such as this:

    #+NAME: packages
    #+begin_src emacs-lisp :tangle no :var category=""
    (my/format-guix-dependencies category)
    #+end_src

Now, creating a manifest for the `desktop-polybar` profile is as simple as:

    #+begin_src scheme :tangle ~/.config/guix/manifests/desktop-polybar.scm :noweb yes
    (specifications->manifest
     '(
       <<packages("desktop-polybar")>>))
    #+end_src

There’s a newline symbol between “(” and `<<packages("desktop-polybar")>>` because whenever a noweb expression expands into multiple lines, for each new line noweb duplicates contents between the start of the line and the start of the expression.

One reason this is so is to support languages where indentation is a part of the syntax, for instance, Python:

    class TestClass:
        <<class-contents>>

So every line of `<<class-contents>>` will be indented appropriately. In our case though, it is a minor inconvenience to be aware of.

## Polybar

Now, the most crazy advanced case I’ve come up with so far.

Basically, here is how my polybar currently looks:

It has:

- colors from the general color theme;
- powerline-ish decorations between modules.

### Colors

The “colors” part is straightforward enough. Polybar can use `Xresources`, so we just need to generate the appropriate bindings of Xresources to the polybar variables:

    #+NAME: get-polybar-colors
    #+begin_src emacs-lisp :var table=colors :tangle no
    (mapconcat
     (lambda (elem)
       (format "%s = ${xrdb:%s}" (nth 0 elem) (nth 1 elem)))
     (seq-filter
      (lambda (elem) (when-let (name (nth 1 elem))
                       (not (string-empty-p name))))
      table)
     "\n")
    #+end_src

    #+begin_src conf-windows :noweb yes
    [colors]
    <<get-polybar-colors()>>

    background = ${xrdb:background}
    #+end_src

### Module decorations

As for the module decorations though, I find it ironic that with all this fancy rendering around I have to resort to Unicode glyphs.

Anyhow, the approach is to put a glyph between two blocks like this:

    block1  block2

And set the foreground and background colors like that:

| block1 | glyph | block2
--- | --- | --- | ---
foreground | F1 | B2 | F2
background | B1 | B1 | B2

So, that’s a start. First, let’s define the glyph symbols in the polybar config:

    [glyph]
    gleft = 
    gright = 

#### Defining modules

As we want to interweave polybar modules with these glyphs in the right order and with the right colors, it is reasonable to define a single source of truth:

    #+NAME: polybar_modules
    | Index | Module      | Color         | Glyph |
    |-------+-------------+---------------+-------|
    |     1 | pulseaudio  | light-magenta | +     |
    |     2 | mpd         | magenta       | +     |
    |     9 | battery     | light-cyan    | +     |
    |     3 | cpu         | cyan          | +     |
    |     4 | ram-memory  | light-green   | +     |
    |     5 | swap-memory | green         | +     |
    |     6 | network     | light-red     | +     |
    |     7 | openvpn     | light-red     |       |
    |     8 | xkeyboard   | red           | +     |
    |    10 | weather     | light-yellow  | +     |
    |    12 | sun         | yellow        | +     |
    |    13 | aw-afk      | light-blue    | +     |
    |    14 | date        | blue          | +     |

Also excluding some modules from certain monitors, which for now is about excluding `battery` from the monitors of my desktop PC:

    #+NAME: polybar_modules_exclude
    | Monitor  | Exclude |
    |----------+---------|
    | DVI-D-0  | battery |
    | HDMI-A-0 | battery |

#### Generating glyphs

To generate the required set of glyphs, we need a glyph for every possible combination of adjacent colors that can occur in polybar.

Most of these combinations can be inferred from the `polybar_modules` table, the rest are defined in another table:

    #+NAME: polybar_extra_colors
    | Color 1    | Color 2       |
    |------------+---------------|
    | background | white         |
    | background | light-magenta |
    | blue       | background    |

There’s a definition of the source block with the required variables:

    #+NAME: polybar-generate-glyphs
    #+begin_src emacs-lisp :var table=polybar_modules exclude-table=polybar_modules_exclude extra=polybar_extra_colors
    ...source...
    #+end_src

And there is the source block itself (because I want to have some syntax highlighting for this one in the post):

    (let* ((monitors
            (thread-last
              exclude-table
              (seq-map (lambda (el) (nth 0 el)))
              (seq-uniq)))
           (exclude-combinations
            (seq-map
             (lambda (monitor)
               (seq-map
                (lambda (el) (nth 1 el))
                (seq-filter
                 (lambda (el) (and (string-equal (nth 0 el) monitor)
                                   (nth 1 el)))
                 exclude-table)))
             `(,@monitors "")))
           (module-glyph-combinations
            (thread-last
              exclude-combinations
              (seq-map
               (lambda (exclude)
                 (thread-last
                   table
                   (seq-filter
                    (lambda (elt)
                      (not (or
                            (member (nth 1 elt) exclude)
                            (not (string-equal (nth 3 elt) "+")))))))))
              (seq-uniq)))
           (color-changes nil))
      (dolist (e extra)
        (add-to-list
         'color-changes
         (concat (nth 0 e) "--" (nth 1 e))))
      (dolist (comb module-glyph-combinations)
        (dotimes (i (1- (length comb)))
          (add-to-list
           'color-changes
           (concat (nth 2 (nth i comb))
                   "--"
                   (nth 2 (nth (1+ i) comb))))))
      (mapconcat
       (lambda (el)
         (let ((colors (split-string el "--")))
           (format "
    [module/glyph-%s--%s]
    type = custom/text
    content-background = ${colors.%s}
    content-foreground = ${colors.%s}
    content = ${glyph.gright}
    content-font = 5"
                   (nth 0 colors)
                   (nth 1 colors)
                   (nth 0 colors)
                   (nth 1 colors))))
       color-changes
       "\n"))

Here’s a rough outline of how the code works:

- `monitors` is a list of unique monitors in `exclude-table`
- `exclude-combilnations` is a list of lists of module names to be excluded for each monitor
- `module-glyphs-combinations` is a list of lists of actual modules for each monitor
- `color-changes` is a list of unique adjacent colors across modules in all monitors

Finally, `color-changes` is used to generate glyph modules that look like this:

    [module/glyph-light-cyan--cyan]
    type = custom/text
    content-background = ${colors.light-cyan}
    content-foreground = ${colors.cyan}
    content = ${glyph.gright}
    content-font = 5

As of now, 15 of such modules is generated.

And including this in the polybar config itself:

    #+begin_src conf-windows :noweb yes
    <<polybar-generate-glyphs()>>
    #+end_src

#### Individual modules

Another thing we need to do is to set the color of modules in accordance with the `polybar_modules` table. The background can be determined from the `Color` column with the following code block:

    #+NAME: get-polybar-bg
    #+begin_src emacs-lisp :var table=polybar_modules module="pulseaudio"
    (format
     "${colors.%s}"
     (nth
      2
      (seq-find
       (lambda (el) (string-equal (nth 1 el) module))
       table)))
    #+end_src

And that block is meant to be invoked in each module definition, e.g. for the `cpu` module:

    #+begin_src conf-windows :noweb yes
    [module/cpu]
    type = internal/cpu
    format = " <label>"
    label = %percentage%%
    format-background = <<get-polybar-bg(module="cpu")>>
    #+end_src

#### Global polybar configuration

To configure polybar itself, we first need to generate a set of modules for each monitor.

Here is the source block definition:

    #+NAME: polybar-generate-modules
    #+begin_src emacs-lisp :var table=polybar_modules exclude-table=polybar_modules_exclude monitor="DVI-D-0" first-color="background" last-color="background"
    ...
    #+end_src

The parameters here, excluding the two required tables, are:

- `monitor` - the current monitor on which to filter out the blocks by the `polybar_modules_exclude` table,
- `first-color` - the first color of the first glyph,
- `last-color` - the second color of the last glyph.

And here is the source:

    (let* ((exclude-modules
            (thread-last
              exclude-table
              (seq-filter (lambda (el) (string-equal (nth 0 el) monitor)))
              (seq-map (lambda (el) (nth 1 el)))))
           (modules
            (thread-last
              table
              (seq-filter (lambda (el) (not (member (nth 1 el) exclude-modules))))))
           (prev-color first-color)
           (ret nil))
      (concat
       (mapconcat
        (lambda (el)
          (apply
           #'concat
           (list
            (when (string-equal (nth 3 el) "+")
              (setq ret (format "glyph-%s--%s " prev-color (nth 2 el)))
              (setq prev-color (nth 2 el))
              ret)
            (nth 1 el))))
        modules
        " ")
       (unless (string-empty-p last-color) (format " glyph-%s--%s " prev-color last-color))))

Here’s how it evaluates on my current monitor:

    glyph-background--light-magenta pulseaudio glyph-light-magenta--magenta mpd glyph-magenta--cyan cpu glyph-cyan--light-green ram-memory glyph-light-green--green swap-memory glyph-green--light-red network openvpn glyph-light-red--red xkeyboard glyph-red--light-yellow weather glyph-light-yellow--yellow sun glyph-yellow--light-blue aw-afk glyph-light-blue--blue date glyph-blue--background

The polybar config doesn’t support conditional statements, but it does support environment variables, so we can pass the parameters from something like a bash script. Here’s an excerpt from mine:

    ...
    declare -A BLOCKS=(
        ["eDP"]="<<polybar-generate-modules(monitor="eDP")>>"
        ["eDP-1"]="<<polybar-generate-modules(monitor="eDP-1")>>"
        ["DVI-D-0"]="<<polybar-generate-modules(monitor="DVI-D-0")>>"
        ["HDMI-A-0"]="<<polybar-generate-modules(monitor="HDMI-A-0")>>"
    )
    ...
    pkill polybar
    for m in $(xrandr --query | grep " connected" | cut -d" " -f1); do
        ...
        export RIGHT_BLOCKS=${BLOCKS[$MONITOR]}
        polybar mybar &
    done

(The full script has a lot of stuff that is not relevant to this post, but you can check here if you are interested.)

So, in the case of polybar, literate configuration allows for implementing a sort of logic that wouldn’t be available with the base configuration (also a promise of projects like Guix Home, by the way). Maintaining this configuration, e.g. changing the order of modules, is much easier this way than it would be if everything was hardcoded in the polybar config itself.

Table of Contents
<Expand> <Collapse>