simfish:

LitDoc

LitDoc is a GNU/Guix extension that’s designed to facilitate literate programming. LitDoc is primarily designed around Emacs org-mode file format. With LitDoc specialized source blocks can be introduced within an org document for managing software dependencies, configuration, and runtime behavior in a reproducible manner.

cover image

Installation

A Linux system equipped with Git and GNU/Guix See the official GNU Guix installation instructions. is assumed.

To get started, clone the LitDoc project repository to some directory of choice such as ~/opt/litdoc.

 git clone https://gitlab.com/aarongile/projects/litdoc

User interaction with LitDoc is primarily managed via a Guix extension command, guix ld. To integrate the command to a shell environment of choice source .rcfile file located at the root of the project. For Bash users, for example, add the line below to ~/.bashrc.

. /opt/litdoc/.rcfile

Optionally export the environment variable LITDOC_PATH after setting its value to one or more colon(:) separated search paths, which is inspected when looking up documents by name.

# if more than one path is available,
# use `:` to seperate each
export LITDOC_PATH=#/path/first:/path/second:/path/third: ... /pathN

And that’s it! LitDoc is installed and ready to go.

To verify the installation run: guix ld help, a shell output of the form shown below should be displayed:

Usage: guix ld [OPTION] COMAND DOCUMENT [-- SHELL-COMMAND...]
Build an environment configured using literate document DOCUMENT.

[OPTIONS]

   --runtime=NAME      name the runtime configuration option to use

  -h, --help           display this help and exit

  -V, --version        display version information and exit

[COMMAND]

  find  DOCUMENT        find document DOCUMENT by filename

  build  DOCUMENT       build the site for document DOCUMENT

  inspect DOCUMENT      inspect document DOCUMENT

  *run DOCUMENT         (default)execute the literate document DOCUMENT

[-- ARG...]

   ARG ...               one or more command arguments to be passed to the the main
                         script of the document

Report bugs to: .
https://gitlab.com/aarongile/projects/litdoc/-/issues

Working with LitDoc Documents

As a plain Emacs org-mode document, a LitDoc file typically contains a mix prose and code. In org, code is expressed using source blocks that can be executed using Babel Babel is Org's ability to execute source code within Org documents. . When using LitDoc, in addition to mixing code with prose and executing it in-place while running Emacs, it's also possible to specify per document software dependency and execute code from within the command-line without even using Emacs.This is made possible by,

  • Guix: Leveraging Guix's software and environment management features Introduction to Guix Features .
  • Org Parser: using a tree-sitter based org-mode parser.

To get a better sense of how these work together, consider the following hello world document, hello.org

#+title: hello world

This is a hello world literate document. It uses
Python to print /"hello world."/

#+begin_src shell :tangle main
 #! /usr/bin/env python3

 print("Hello World!")

#+end_src

To run this document =python= has to be available locally.
The dependency can be specified using a scheme manifest
source block
#+begin_src scheme  :tangle manifest

(specifications->manifest '("python"))

#+end_src

To run the document switch to a directory where, hello.org is located and run,

guix ld run hello

The command should produce the expected output:

> Hello World!

Behind the scene, LitDoc first parses the document, if it has not already, and extracts code blocks. It then inspects the extracted blocks to setup correctly an isolated Guix profile, which is activated whenever the document is executed.

Managing Dependencies

A LitDoc document can specify within document software dependency using specialized Guile/Scheme or Emacs/Lisp source block.

A Guile/Scheme based source block uses Guix's manifest specification syntax This approach is useful for listing software dependencies in bulk, See Basic Setup with Manifests .

,#+begin_src  scheme :tangle manifest

 (specifications->manifest '("emacs-next" "emacs-helm"))

,#+end_src

Notice the block's tangle property is set to the keyword manifest. This pseudo tangle target instructs LitDoc the document dependencies are specified in this one block.

Alternatively, instead of just specifying multiple dependencies in a single manifest, each dependency can be specified separately using an Emacs/Lisp block and a use-package syntax In contrast to a Guile/Scheme based manifest, an Emacs/Lisp use-package based package specification is not required to have a tangle target. Instead the keyword :guix is applied on a per package basis .

Example: Consider document for managing Emacs configuration.

This document installs and configuers Emacs and one of my
favorite Emacs package, =emacs-magit=.

The =manifest= code block below is used to specify
list of software packages to install in one go. In this
case the default emacs package and git are specificed.
,#+begin_src scheme  :tangle manifest
(specifications->manifest '("emacs" "git" ))
,#+end_src

We can make the document a bit more interesting by specifying
the =emacs-magit= package using a use-package syntax, where
configuration setting for the package can be included along
side the package specification.
,#+begin_src emacs-lisp

;; Helm
(use-package helm
  :guix (:name emacs-helm)
  :bind (("C-x g" . magit-status))
  :config
  (helm-mode 1))

,#+end_src

This approach is particularly useful when managing Emacs packages. As it is customary to place package specification along with its configuration for readability.

In either of the two case, emacs.org document can be made available in the current shell environment after running a site build command, Both package specification approaches do not require the existence of Emacs.

# notice we drop .org extension
guix ld build emacs

Running Code

When interacting with an org document, such as emacs.org in the example above, guix ld can be used to build, load, and run the file(if it exposes an interactive script). That is,

# Run guix ld build to incorporate the latest changes to the document
guix ld build emacs

# Run guix ld run, to load/run the document
guix ld run emacs

Lets expose a main shell script to make the emacs.org file interactive,

,* Running Emacs

  Emacs can be run as a daemon using the =--daemon= option.
  Once the daemon is started a light weight Emacs instance
  can be used to connect the daemon using =emacsclinet=.
  This approach will significantly improve Emacs startup time
  by applying load Emacs only once policy.

 ,#+begin_src shell :tangle main
 ,#!/bin/env bash

  if ! emacsclient -e 0 >&/dev/null; then
    emacs --daemon &
  fi
  emacsclient -c "$@"

 ,#+end_src

The main block can now be run from the command-line with:

guix ld run emacs

Setting up a Shell Environment

In addition to making org document runnable with main script block, it is also possible to provide code block that enables the documents to be to hooked into the host machine's shell environment. This is achieved using host.env code block.

A host.env block is useful for setting up shell variables, functions, and aliases. Any entry that goes into host.env block is equivalent to a code block that gets sourced from ~/.bashrc or the like.

The code snippet below Following example developed in sections 1,2 creates the alias command e, that introduces a shortcut for launching Emacs:

,* Startup Settings

 Settings in this section is intended to be sourced
 as part of the host's interactive shell session.

,#+begin_src  shell :tangle host.env

  alias e="litdoc emacs"

,#+end_src

Once this configuration is applied by running guix ld build emacs and reloading the shell, source ~/.bashrc, typing in e on the terminal should launch Emacs in daemon-mode.

Setting up Container Environment

By default org code blocks runs in the host machine. To run code in a GNU/Guix container environment include a container.opt shell block. The block contains a list of Guix container shell options See Invoking Guix Shell ,which instruct litdoc to run code in a containerized environment.

Building on the emacs.org example, and adding the org snippet below will run Emacs in a container environment:

,* Containerization

This code snippet below configures Emacs to run
in a Guix container environment.

,#+begin_src shell :tangle container.opt

# enable access to network
 --network

# working directory
--share="${HOME}/=/home/${USER}/

# X11 client/server communication
--expose=/tmp/.X11-unix

,#+end_src

Further, the container.opt block configures the container's access to several system resources. The --expose option, for example, is used to grant read-only access to selected file system entries. In this case, to /tmp/.X11-unix, which enables Emacs to connect with an X11 server.

In contrast, the --share option is used to grant both read and write access to file system entries. In the example above, it grants the container read-write access to ${HOME} directory, which is maped to /home/${USER} within the container environment This is a powerful feature and enables composing custom file system layout by combining several paths .

In general, container.opt options configure access to system resources such as the file system and network. Some useful resource access configuration options are summarized in table-1 For a complete list of available options see Invoking Guix shell .

Resource Access Control Comment
--network allow containers to have network access
--timeout=T Signal error if the container does not load after T second.
--max-jobs=N specify the number of build jobs during container construction.
--cores=N specify the number of CPU core to use.
--expose=SPEC allow containers to have read-only access to host
file or directory specified with SPEC
--share=SPEC allow containers to have both read and write access to
host file system specified with SPEC
SPEC has the form:
--share /tmp/t1=/tmp: map host /tmp/t1 => container /tmp
--share /tmp/t1: map host /tmp/t1 => =/tmp/t1