# Executable Code Blocks

We use [md-babel-py](https://github.com/leshy/md-babel-py/) to execute code blocks in markdown and insert results.

## Golden Rule

**All code blocks must be executable.** Never write illustrative/pseudo code blocks. If you're showing an API usage pattern, create a minimal working example that actually runs. This ensures documentation stays correct as the codebase evolves.

## Running

```sh
md-babel-py run document.md          # edit in-place
md-babel-py run document.md --stdout # preview to stdout
md-babel-py run document.md --dry-run # show what would run
```

## Supported Languages

Python, Shell (sh), Node.js, plus visualization: Matplotlib, Graphviz, Pikchr, Asymptote, OpenSCAD, Diagon.

## Code Block Flags

Add flags after the language identifier:

| Flag | Effect |
|------|--------|
| `session=NAME` | Share state between blocks with same session name |
| `output=path.png` | Write output to file instead of inline |
| `no-result` | Execute but don't insert result |
| `skip` | Don't execute this block |
| `expected-error` | Block is expected to fail |

Use `skip` when a block would pull in **CUDA / GPU-only** stacks (for example perception models, `VoxelGridMapper` defaults, or imports that load torch with GPU expectations), or when it is **flaky in CI** (multi-module coordinators, timing-sensitive workers, pytest-style snippets that are not meant to run as a single script). Prefer `expected-error` only when the block is supposed to fail and you want to assert that failure.

## Examples

## md-babel-py

Execute code blocks in markdown files and insert the results.

**Use cases:**
- Keep documentation examples up-to-date automatically
- Validate code snippets in docs actually work
- Generate diagrams and charts from code in markdown
- Literate programming with executable documentation

## Languages

### Shell

```sh
echo "cwd: $(pwd)"
```

```results
cwd: /home/lesh/coding/dimos/docs
```

### Python

```python
a = "hello world"
print(a)
```

```results
hello world
```

Sessions preserve state between code blocks:

```python
print(a, "again")
```

```results
hello world again
```

### Node.js

```node
console.log("Hello from Node.js");
console.log(`Node version: ${process.version}`);
```

```results
Hello from Node.js
Node version: v24.11.1
```

### Matplotlib

```python
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
plt.style.use('dark_background')
x = np.linspace(0, 4 * np.pi, 200)
plt.figure(figsize=(8, 4))
plt.plot(x, np.sin(x), label='sin(x)', linewidth=2)
plt.plot(x, np.cos(x), label='cos(x)', linewidth=2)
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.grid(alpha=0.3)
plt.savefig('{output}', transparent=True)
```

![output](assets/matplotlib-demo.svg)

### Pikchr

SQLite's diagram language:

<details>
<summary>diagram source</summary>

```pikchr
color = white
fill = none
linewid = 0.4in

# Input file
In: file "README.md" fit
arrow

# Processing
Parse: box "Parse" rad 5px fit
arrow
Exec: box "Execute" rad 5px fit

# Fan out to languages
arrow from Exec.e right 0.3in then up 0.4in then right 0.3in
Sh: oval "Shell" fit
arrow from Exec.e right 0.3in then right 0.3in
Node: oval "Node" fit
arrow from Exec.e right 0.3in then down 0.4in then right 0.3in
Py: oval "Python" fit

# Merge back
X: dot at (Py.e.x + 0.3in, Node.e.y) invisible
line from Sh.e right until even with X then down to X
line from Node.e to X
line from Py.e right until even with X then up to X
Out: file "README.md" fit with .w at (X.x + 0.3in, X.y)
arrow from X to Out.w
```

</details>

![output](assets/pikchr-demo.svg)

### Asymptote

Vector graphics:

```asymptote
import graph;
import stats;

size(400,200,IgnoreAspect);
defaultpen(white);

int n=10000;
real[] a=new real[n];
for(int i=0; i < n; ++i) a[i]=Gaussrand();

draw(graph(Gaussian,min(a),max(a)),orange);

int N=bins(a);

histogram(a,min(a),max(a),N,normalize=true,low=0,rgb(0.4,0.6,0.8),rgb(0.2,0.4,0.6),bars=true);

xaxis("$x$",BottomTop,LeftTicks,p=white);
yaxis("$dP/dx$",LeftRight,RightTicks(trailingzero),p=white);
```

![output](assets/histogram.svg)

### Graphviz

```dot
A -> B -> C
A -> C
```

![output](assets/graph.svg)

### Diagon

ASCII art diagrams:

```diagon
1 + 1/2 + sum(i,0,10)
```

```results
        10
        ___
    1   ╲
1 + ─ + ╱   i
    2   ‾‾‾
         0
```

```diagon
A -> B -> C
A -> C
```

```results
┌───┐
│A  │
└┬─┬┘
 │┌▽┐
 ││B│
 │└┬┘
┌▽─▽┐
│C  │
└───┘
```

## Install

### Nix (recommended)

```sh
# Run directly from GitHub
nix run github:leshy/md-babel-py -- run README.md --stdout

# Or clone and run locally
nix run . -- run README.md --stdout
```

### Docker

```sh
# Pull from Docker Hub
docker run -v $(pwd):/work lesh/md-babel-py:main run /work/README.md --stdout

# Or build locally via Nix
nix build .#docker     # builds tarball to ./result
docker load < result   # loads image from tarball
docker run -v $(pwd):/work md-babel-py:latest run /work/file.md --stdout
```

### pipx

```sh
pipx install md-babel-py
# or: uv pip install md-babel-py
md-babel-py run README.md --stdout
```

If not using nix or docker, evaluators require system dependencies:

| Language  | System packages             |
|-----------|-----------------------------|
| python    | python3                     |
| node      | nodejs                      |
| dot       | graphviz                    |
| asymptote | asymptote, texlive, dvisvgm |
| pikchr    | pikchr                      |
| openscad  | openscad, xvfb, imagemagick |
| diagon    | diagon                      |

```sh
# Arch Linux
sudo pacman -S python nodejs graphviz asymptote texlive-basic openscad xorg-server-xvfb imagemagick

# Debian/Ubuntu
sudo apt-get install python3 nodejs graphviz asymptote texlive xvfb imagemagick openscad
```

Note: pikchr and diagon may need to be built from source. Use Docker or Nix for full evaluator support.

## Usage

```sh
# Edit file in-place
md-babel-py run document.md

# Output to separate file
md-babel-py run document.md --output result.md

# Print to stdout
md-babel-py run document.md --stdout

# Only run specific languages
md-babel-py run document.md --lang python,sh

# Dry run - show what would execute
md-babel-py run document.md --dry-run

# Longer subprocess limit (default 60s); see upstream README for MD_BABEL_EXECUTION_TIMEOUT
md-babel-py run document.md --execution-timeout 120
```
