Light-weight Markdown-based literate programming



-- Hal Abelson, SICP

Writ is an attempt to enable a light-weight literate programming workflow.

You write Markdown files with your code in normal Markdown code blocks (indented or fenced) and then run the writ command to compile to source files your language's compiler or interpreter will understand.

Writ expects a one-to-one mapping between Markdown source files and source files in the target language, and uses a simple extension-based convention for generating the target files: a Markdown file named foo.js.md or foo.js.markdown will generate a foo.js file.

Writ is available on npm.

npm install -g writ

To use it, just run writ from the command line, specifying which Markdown files you want to compile. By default, it compiles the output in the same directory, you can pass the --dir flag to specify a different destination directory:

writ "src/*.md" --dir build

In the simplest case, if your code is completely straight-line, top-to-bottom, you don't need to know any syntax: writ will generate the target file by concatenating all the code blocks in the order they appear. This is exactly how literate CoffeeScript works.

For slightly more involved cases, writ supports syntax for:

  1. //!! .*[ !!//] for ignoring a code block
  2. //== name[ ==//] for naming a code block
  3. ##== name[ ==##] for naming a section
  4. //:: name[ :://] for including a code block

The // bits are configurable and are defaulted to the single-line comment token for your language.

The 'closing tags' (!!//, ==//, and :://) are optional, but must match the opening tag if present.

To keep a code block from being included in the generated output, start the code block with a line starting with:

//!! This is an ignored code block

//!! Also ignored !!//

A named code block is any code block in the document that starts with a line of the form:

//== name[ ==//]

You can later (or earlier) include that code in another block by dereferencing it with //::.

So the following Markdown:

# Main code chunk

//:: requires :://

//== requires ==//

var marked = require('marked');
var fs = require('fs');

Would compile to:

var marked = require('marked');
var fs = require('fs');

A name for sections can have internal whitespace, but it obviously should match up exactly when dereferencing names.

A few things to note about named sections:

  1. Named sections that are never referenced by a 'top-level' code block won't show up in the compiled output.

  2. If a name is referenced that doesn't exist, that comment line will remain as-is in the compiled output.

  3. Named sections can refer to other named sections, and writ will whine if it has to recurse more than 50 times when compiling.

  4. If you have multiple named sections with the same name, they'll get concatenated together in the order they appear in the source.

I've found it useful to be able to be able to have entire chunks of a document be a named section, so you can also use the double-equals (==) syntax in an H2-level header to name all the sections "under" a specific heading.

So this...

//:: Utilities :://

##== Utilities


    function noop() {}




    function mul(x, y) { return x * y; }

Would compile to:

function noop() {}

And you don't have to explicitly name each code block in the section.

Once any other H2 (named or not) is reached in the document, writ will return to processing as usual.

  • This one, of course. See writ.js.md for the source.

If you build something using writ, send a pull request adding a link to your library.