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.
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 helloThe 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_srcThis 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 emacsRunning 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 emacsSetting 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 |