Litdoc is a command-line utility useful for authoring literate programs
using Emacs org-mode and GNU/Guix.
Litdoc
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, for example ~/opt/litdoc
.
mkdir -p ~/opt
cd ~/opt
git clone https://gitlab.com/aarongile/projects/litdoc
Interaction with Litdoc is based on its command-line interface litdoc
.
Integrate the command to a shell environment of choice by
sourcing .litrc
file. For Bash
users, for example, add the line
below to ~/.bashrc
.
# assuming /op/litdoc is the install path:
. /opt/litdoc/.litrc
Optionally add one or more search paths and export is
as LITDOC_PATH
, an environment variable litdoc inspects when
searching org files by name.
# if more than one path is available,
# use `:` to seperate each
export LITDOC_PATH=#/path/first:/path/second:/path/third: ... /pathN
That's it, Litdoc is installed and ready to go.
To verify the installation run: litdoc
a shell
output of the form shown below should be displayed:
Usage:
litdoc <org-name>
litdoc site <build|load|ls|lsdeps|find|inspect> <org-name>
Software requirements:
- GNU/Guix
- Git
Report bugs to: https://gitlab.com/arongile/litdoc/issues
Home page: https://simfish.dev/projects/litdoc
Getting Started
Literate programming with Emacs org-mode involves authoring documents that 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, 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 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 execute and run,
# verbose is optional
litdoc hello --verbose
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
.
,#+begin_src emacs-lisp
;; Helm
(use-package helm
:guix (:name emacs-helm)
: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
Both package specification approaches do not require the
existence of Emacs.
# notice we drop .org extension
litdoc build emacs --verbose
Running Code
Litdoc provides a command-line interface, litdoc,
for interacting
with org documents. Given an input org file ORGNAME.org
litdoc
can tangle, build, load, and run the file.
litdoc site tangle /ORGNAME/ |
tangle org document ORGNAME.org |
litdoc site build /ORGNAME/ |
build a Guix profile after parsing source blocks in /ORGNAME.org/ |
litdoc site load /ORGNAME/ |
load Guix profile for /ORGNAME.org/ in the current shell |
litdoc site run /ORGNAME/ |
run shell script specified in the main block |
To execute an document, ORGNAME.org
, use
# short form
litdoc ORGNAME
# long form
litdoc site run ORGNAME
# long form with additional paramaters
litdoc site run ORGNAME -- param1 param2 --opt=1 -o2 ...
This works as long as ORGNAME.org
contains a main
shell block.
That is, a shell source block with its :tangle
property set to main
.
Adding to the emacs.org
example, the code below adds a daemon
based Emacs launcher script:
,* 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 be run from the command-line with:
# verbose is optional
litdoc emacs --verbose
Setting up a Shell Environment
In addition to making org document runnable, 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 using Litdoc:
,* 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 litdoc site load emacs
,
typing in e
on the terminal should launch Emacs.
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 instruct
Litdoc to run Emacs in a GNU/Guix 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}/Documents/dev=/home/user/dev
# 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}/Documents/dev
directory, which
maps to /home/user/dev
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 |