techdochelper: Run your documentation examples!

Note #

Turn on dark mode: upper right, little sun-looking thing. Code blocks in this theme look hideous in light mode, and I simply haven’t been able to work up the enthusiasm to learn enough scss to figure out how to fix it. I could change themes, I guess, but that affects the whole site, and… just use the dark theme for now, ok? Thanks.


I wrote another thing.

Years ago, in the Before-Time, I wrote a lot of Ruby. One of the fantastic things about Ruby tooling is the unit test ecosystem; the other was the documentation system.

Ruby has a documentation ecosystem which I suspect is still unmatched in the rest of computing world. Documentation was rich (text), with beautiful generated output, and – like unit tests – it was almost unthinkable to expose code to the community that didn’t have a documented API. But the thing I miss most about Ruby was the runnable examples.

Ruby encouraged embedding code snippets in API documentation; little “how-to” examples. Go has this as well, but it’s far rarer to see func ExampleFunc() that what’ you’d see in Ruby, and it still misses one very important source of example code bitrot: READMEs.

There are many times when it’s useful to have more literate documentation – code in documentation, rather than documentation in code. READMEs are a good example; nearly every library includes some example snippet. An elevator pitch, if you will, because libraries for any given solution are multitudinous and users need some way to sieve their options. Code in READMEs are not handled by the testing package and are prone to going stale.

Enter techdochelper. It’s a script that will parse out runnable code examples from a Markdown document, execute it, and inject the output. I initially wrote it to handle a library I was writing; about the 10th time I ran through the copy/paste/run/copy/paste cycle, I decided I needed to automate it, and techdochelper is the result.

It’s about 200 lines of bash, and is fully self-contained. If you’re one of those people, you could curl https://hg.sr.ht/~ser/techdochelper/raw/techdochelper.sh?rev=tip | bash it.

It works via containers (of course), and since I initially wrote it I decided to expand it to support other languages. At the moment (and unless someone else submits patches) it supports Go, bash, and Rust – the last not because I ever program Rust, but just to prove the concept. It’s pretty straightforward:

  1. In your README, you have code. Make sure that code is runnable – it has a package name, imports, and a main – and then add a file name to the fence syntax. E.g.:
     ```go main.go
     package main
     ...
    
  2. After one of these fences, add some unique keyword as a placeholder for the output, e.g.:
    OUTPUT
    
  3. Pipe that through techdochelper, telling the script what your magic safe-word is, e.g.:
    <README.md ./techdochelper.sh OUTPUT > NEW_README.md
    
  4. Bask in the glory of verified code and auto-generated (and accurate!) output

The script has several other features described in the readme: arguments, dependency and metadata files, performance wedges; but to paraphrase a paraphrase of something someone might have said something similar to: I come not to sell techdochelper, but to justify it.

I hope others will find the tool handy, not only for helping keep their documentation in sync with their APIs, but also to ease some effort needed to keep sample output accurate. If there’s one thing I’m certain of, it’s that incorrect documentation is worse than no documentation. Lies are more harmful than ignorance, and even though not bitrot is not malicious, it’s just as disruptive. techdochelper is here to help.

As I write this I haven’t yet set up a Matrix channel for techdochelper, but I did configure a mailing list on Sourcehut. If you find the tool at all helpful, please let me know! If you’d like to see other languages supported, there are two things you can do:

  1. Add it and send a patch. It’s literally adding 4 lines and changing a 5th, for many languages. The Rust entry looks like this:
function lang_rust() {
  PACKAGES="bash ca-certificates cargo"    # Alpine Linux packages needed to build Rust programs
  PRECMD="mkdir -p src ; mv main.rs src"   # Get the directory structure right for Rust's requirements
  CMD="cargo run"                          # Execute some code
}
  1. Shoot an email to the list. These days, I rarely write in anything other than bash or Go, but I’m happy to learn enough to add others if someone will benefit.