Thus, programs must be written for people to read, and only incidentally for machines to execute.
-- 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 requires Node v12.17 or higher.
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:
//!! .*[ !!//]
for ignoring a code block//== name[ ==//]
for naming a code block##== name[ ==##]
for naming a section//:: 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
```js
//:: requires :://
```
```js
//== requires ==//
import marked from 'marked';
import fs from 'fs';
```
Would compile to:
import marked from 'marked';
import fs from '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:
-
Named sections that are never referenced by a 'top-level' code block won't show up in the compiled output.
-
If a name is referenced that doesn't exist, that comment line will remain as-is in the compiled output.
-
Named sections can refer to other named sections, and writ will whine if it has to recurse more than 50 times when compiling.
-
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
noop
function noop() {}
add
function add(x, y) { return x + y; }
mul
function mul(x, y) { return x * y; }
Would compile to:
function noop() {}
function add(x, y) { return x + y; }
function mul(x, y) { return x * y; }
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.