mdrip

Build Status

mdrip rips labeled command blocks from markdown files for execution.

mdrip accepts one label argument and any number of file name arguments, where the files are assumed to contain markdown. It scans the files for fenced code blocks immediately preceded by an HTML comment with embedded @labels.

If one of the block labels matches the label argument to the command line, the associated block is extracted. Extracted blocks are emitted to stdout, or, if --subshell is specified, concatenated to run as a subprocess.

This is a markdown-based instance of language-independent literate programming (for perspective, see the latex-based noweb). It's language independent because shell scripts can make, build and run programs in any programming language, via here documents and what not.

Build

Assuming Go installed:

export MDRIP=~/mdrip
GOPATH=$MDRIP/go go get github.com/monopole/mdrip
GOPATH=$MDRIP/go go test github.com/monopole/mdrip/util
$MDRIP/go/bin/mdrip   # Shows usage.

Example

This markdown coding tutorial (raw markdown here) has bash code blocks that write, compile and run a Go program.

Send code from that file to stdout:

$MDRIP/go/bin/mdrip lesson1 \
    $MDRIP/go/src/github.com/monopole/mdrip/example_tutorial.md

Alternatively, run it's code in a subshell:

$MDRIP/go/bin/mdrip --subshell lesson1 \
    $MDRIP/go/src/github.com/monopole/mdrip/example_tutorial.md

The above command has no output and exits with status zero if all the scripts labelled @lesson1 in the given markdown succeed. On any failure, however, the command dumps a report and exits with non-zero status.

This is one way to cover documentation with feature tests. Keeping code and documentation describing the code in the same file makes it much easier to keep them in sync.

Details

A script is a sequence of code blocks with a common label. If a block has multiple labels, it can be incorporated into multiple scripts. If a block has no label, it's ignored. The number of scripts that can be extracted from a set of markdown files equals the number of unique labels.

If code blocks are in bash syntax, and the tool is itself running in a bash shell, then piping mdrip output to source /dev/stdin is equivalent to a human copy/pasting code blocks to their own shell prompt. In this scenario, an error in block N will not stop execution of block N+1. To instead stop on error, pipe the output to bash -e.

Alternatively, the tool can itself run extracted code in a bash subshell like this

mdrip --subshell someLabel file1.md file2.md ...

If that command fails, so did something in a command block. mdrip reports which block failed and what it's stdout and stderr saw, while otherwise capturing and discarding subshell output.

There‘s no notion of encapsulation. Also, there’s no automatic cleanup. A block that does cleanup can be added to the markdown.

Special labels

  • The first label on a block is slightly special, in that it's reported as the block name for logging. But like any label it can be used for selection too.

  • The @sleep label causes mdrip to insert a sleep 2 command after the block. Appropriate if one is starting a server in the background in that block.