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 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 |