View on GitHub

Blaze

Blaze allows you to write executable code inside Markdown

Download this project as a .zip file Download this project as a tar.gz file

Quickstart

Download the script, put it somewhere on your PATH, and make it executable, eg:

sudo curl http://blaze.oat.sh/blaze --output /usr/local/bin/blaze &&
sudo chmod +x /usr/local/bin/blaze

(future upgrades can be done in-place with blaze --upgrade)

Now go write your executable markdown with

#!/usr/bin/blaze python (or ruby, node, whatever!)

Usage

Fundamentally, Blaze is a drop-in replacement for /usr/bin/env. You stick it at the top of your script, and you can execute it. But Blaze’s REAL trick, is that if called with an .md file, it executes code inside triple-backtick codefences: This gives you is the ability to execute your markdown files as though they were normal scripts (it runs python, ruby, nodejs, shell, and many more). Here’s a ruby hello world example:

myscript.rb.md

#!blaze ruby

# This file is just markdown

As is this text.
Whatever you put in markdown's code fences,
gets executed by blaze:

```ruby
print "hello world"
```

If you were to run this file, you would see this:

λ ./myscript.rb.md
hello world

Congratulations, you just executed a markdown file! This technique is called Literate Programming.

“Literate Programming?”

Literate Programming (LP for short) inverts the code/comments relationship: In normal programming, we write comments inside code. In LP, you write executable code inside a human-readable document.

This documentation-first idea causes a mental shift: You are writing documentation that has occasional references to implamentation, not code that has a smattering of comments. This forces you to think of the audience as another programmer, not a machine. When you think about it, that’s who the real audience has been the whole time.

Blaze allows you to write executable code inside Markdown documents.

Motivation

I’ve been playing with many literate programming tools since this technique of document-first programming came into my life in 2016.

Literate programming (LP), a concept that has been around since at least the 80s, is back in the spotlight since the Eve language (by the Eve team headed by Chris Granger of Light Table fame) was released to the public in 2015.

While LP’s original concept aimed to create a document-first system whereby the building blocks of the actual code could be read (by the human or compiler) in any order (as it is in Eve), the contemporary state of the art is focused on creating the document-first model with existing procedural languages. With the powerful encapsulation techniques we have with modern languages, reading code in any order has become less important.

There are two main kinds of tool in the modern LP category:

  1. Tools that write beautiful documentation based on comments in source code, such as Docco and (a personal favourite) Marginalia
  2. Tools that allow you to execute code-fenced source code within some markup, such as Literate, litpro, and tiny lit.

The first is an evolution of the documentation processors you will be familiar with, no real innovation has happened in this space since Javadoc. (Much though I love Sphynx et al)

The second category I think is worth exploring.

It is into this ecosystem I present Blaze.

Advanced Usage

Blaze also allows as many paramaters to be passed to your interpreter as you like (unlike normal shebangs), which means you can use tools like python’s pex:

myscript.py.md

#!blaze pex arrow --
import arrow

# `Arrow` humanized dates example

```python
print("run", arrow.now().humanize())
```

(Note that we are able to use pex’s ephemeral venv trick to run python with any requirements pre-installed)

Combine these techniques together, and you get an all-encompasing example of a standalone literate webserver with built-in requirements:

myapi.py.md

#!blaze pex flask flask_restful --

# Imports
First the imports, this demo requires the `flask_restful` package.
Then we set up the Flask wsgi application object, `app` and the api wrapper, `api`.

```python
from flask import Flask
from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)
```

# Flask Restful resources
We define a single `HelloWorld` resource, that responds with a simple json
object on a `GET` request.

```python
class HelloWorld(Resource):
    def get(self):
        return {'hello': 'world'}
```

# Routing
`api.add_resource()` wires the `Resource` class `HelloWorld` into the flask
router at `/`.

```
api.add_resource(HelloWorld, '/')
```

# Run Server
After we have created everything, we run the flask werkzeug server.

```
if __name__ == '__main__':
    app.run()
```

Magic, right?

More examples in Ruby and Nodejs are in the examples/ folder, but the principle is the same: Code inside backticks is executed.

Mechanics

Here’s the basics of Blaze, a small shell script:

#!/usr/bin/env sh
args=$1
script=$2

cat $script | awk '{ if (/^```/) { i++; next } if ( i % 2 == 1) { print } }' > $script.out
$args $script.out
rm $script.out

(non-core code stripped from this example, for the real deal, check the source

As you can see blaze runs your script through awk to strip all text outside triple-backtick code fences, then runs it with the interpreter of your choice. There’s nothing to it really!

Overhead

Blaze introduces minimal startup overhead, somewhere between 5-20ms, an almost zero runtime overhead (sh is running, I suppose).

Prior Art / Acknowledgements

Blaze is currently a hacked-together LP tool that is only suitable for one-off scripts. It would not be possible without Rich Traube’s code-fence-stripping code here.

If you want to write a multiple-file project with exactly the same method, consider lit, which converts all [*.md] files to [*], with everything but codefenced code stripped out, project-wide.

Further Reading

I started out with a document that was a simple bulleted list of the features I wanted it to have, took each feature one at a time and fleshed it out into a paragraph, and then picked out paragraphs to implement inline. A bit of rewriting, and when the document was done, the blog worked.

And the name?

Blaze is lit 🔥