Simple, omniglot, markdown-based literate programming and living documentation

Overview

Betwixt

Simple, markdown-based, polyglot literate programming and documentation tests.

Read code between the lines

Summary

Betwixt is heavily inspired by the literate programming features of Emacs Org Mode. The ability to write documentation intended for humans, with easily-consumable formatting, emphasis and organization, and embed code examples or even API call examples in that documentation, and have those examples be executable tests means documentation never gets out of date -- if you change the code, the documentation is the test and so you must update the documentation to pass.

Betwixt extracts code segments from markdown files (currently, only github flavor is supported, but broader support is planned) and tangles them into various source files as configured, allowing them to be built and executed as a part of the CI/CD pipeline, causing failure if the documentation is out of date, or simply allowing entire programs to be written in a format primarily suitable for human consumption, instead of the opposite.

Installation

At the moment, the only way to install betwixt is from source. As it gets more mature, it may get put in crates.io or a couple different distrobution package repositories.

Betwixt is built in rust. You will need to install rust first. Then, clone this project, and cargo build --release. You can copy the built executable anywhere in your path.

Usage

To use betwixt, you will first need a markdown file. The one you're reading now is just fine. Then, you'll need to add betwixt configuration commands to your file. This file already has a few, so you can safely use it for demonstration purposes.

Configuring Code Segments

Betwixt configuration is accomplished using markdown comments of a certain format. You can set properties, which will be applied to all following code blocks, until overwritten by other, more specific or more recent blocks. Below is a global block, which just means it applies to code blocks of all languages, by default.

<?btxt filename='test.py' ?>

This is going to configure betwixt to copy all code segments of all languages into a file called "test.py". You may use either single quotes ' or double quotes " for property values. You may also use three pipe operators (|||) if you need to embed code that contains quotes in a property.

Note that only properties with string values need or accept quotes. Properties that take boolean values (like ignore) take the literal true or false without quotes. Hopefully this is intuitive to most users.

You can also configure properties that only apply to code blocks of a certain language.

<?btxt+python filename='test.py' ?>

Which usually makes more sense, unless all your code blocks in a given file will be the same language.

Note that these betwixt blocks must start a line (although, technically, they don't have to also end a line). This is an implementation detail that also aligns with the behavior of other markdown flavors -- many heading and code blocks must also start a line. It is subject to change, however.

Properties

Currently, you can set the following properties in a betwixt block:

  • filename which indicates the file to which the code blocks should be written to. This should be a relative path.
  • mode indicates the write mode for writing to the files. By default it is append. Currently also supported is overwrite.
  • tag sets a tag, just a string, on the code block(s). This allows filtering on the command line to only tangle code with a certain tag. Additional functionality around tags is likely coming soon.
  • ignore indicates that the code block should not be tangled, and should be left alone
  • prefix sets a code block to be written to file before contents in visible code blocks are written. This is good for hiding boilerplate.
  • postfix sets a code block to be written to file after contents in visible code blocks are written.

While it is not treated as a normal property, you can also set a code property in a betwixt block. This is never inherited, and it is effectively treated as a code block for tangle operations. The difference is that it isn't visible in the rendered markdown -- this is useful for internal plumbing or boilerplate you don't want the end users seeing.

Scope

Properties are defined with a scope of markdown headings. Parent headings' properties are inherited by children, but don't affect siblings or parents. Global properties (properties with no language set) override unset values on properties with a language set. This should hopefully be intuitive.

Scope Example

Consider the following markdown source. There are not many code blocks here, we are simply focusing on betwixt blocks for properties. Note that the code blocks in this example have the triple backticks "`" replaced with triple single quotes. This is to allow code blocks to reside in github code blocks without further changes.

The root of the document (no explicit headings set yet) is the parent of all headings
The below betwixt block sets a global (no set language) `mode` property
<?btxt mode='overwrite' ?>

And the below block sets a filename for blocks that are _python_
<?btxt+python filename="foo.py" ?>

# A Level Down
Because we are now in a child heading, properties are all inherited.
At this point, all code blocks have mode='overwrite' and python blocks will have filename='foo.py' set
<?btxt+python tag="a" ?>
	
All code blocks below the above block will have the 'tag' property set to `a`

# A Child level
Code blocks at this point would lost the `a` "tag" property, since that was set in a sibling.
However, we still have the properties from the root/parent, so we still have `mode` set to "overwrite"
	
<?btxt+python filename="bar.py" ?>
	
Any python code blocks from this point on in this heading level would now be set to write to "bar.py"
	
## A Nested Child
	
Since this is a child level, all code blocks in this section will receive the properties set in "A Child Level" and the root, 
so properties at this point look like this:

 - mode="overwrite" for _all_ blocks
 - filename="bar.py" for python blocks
 
<?btxt tag="b" ?>
Now _all_ code blocks (regardless of langauge) have a tag property of "b" in this section (and any children)

'''python
# this has the tag 'b' and will write to "bar.py"
print("Hello, Betwixt!")
'''

# Another Child Level
Okay, now we've dropped our "A Nested Child" and gone _up_ a level. Any properties set on the sibling and child are now gone. This means that properties look like this:

- mode="overwrite" for _all_ blocks
- filename="foo.py" for python blocks

'''python
print("Hello Foo File!")
'''

Note that you can never reach the root level of the document once left, so properties set in the root are truly global

Hidden Code Example

For an example, look at the source of this markdown file compared to the rendered version. This markdown file is a simple but complete example of betwixt. The below code segment can be tangled into a source file that is executable, even though it isn't a complete, valid golang program by itself.

fmt.Println("Hello, Betwixt!")

Tangling Markdown

To tangle you just need to provide the markdown filename, and a destination output directory. You can use this README as the source.

betwixt README.md -o /tmp/

If you run the above command in the root of this repository, you can then see a complete (albeit painfully simple) go program in /tmp/main.go. If you have go installed, you can execute it with go run /tmp/main.go

Command Line Options

You can use --help to get more information on the command line options. This will always be the best place to go for up-to-date usage information. In general, the most useful parameters are:

  • o or --outpath to set the directory to write tangled files to. If a filename prop is set to foo.txt and -o is set to /tmp/ then code will tangle to /tmp/foo.txt
  • t to filter by a tag. Only code blocks with that tag set will be tangled
  • --flavor will set an optional Markdown flavor. This changes parsing tokens. Right now only github is supported, and is the default value. In order to support nested markdown, there is also the nested flavor, which is primarily there to allow betwixt to eat its own dog food.

State and Plans

Betwixt is still very, painfully premature. It does technically work, but it is going to be very rough around the edges with assumptions around utf8-encoded files, among other things. It'll also likely have a few fundamental bugs, and maybe even (gasp) some bad design decisions. Use at your own risk at the moment.

Ultimately, I want betwixt to have the following features before I will consider it complete:

  • Strict mode to prevent you from doing some things you probably don't intend to (e.g. source blocks that are never tangled)
  • Prefix and Postfix code properties
  • Clear and helpful error messages with line numbers
  • Unicode-aware parsing instead of bytes with several unicode encoding support
  • Simple test runner to create temp directories, execute commands, output success or failure, and cleanup
  • Insert mode to insert code blocks into a specific point in an existing file
  • More Markdown flavors and Org Mode syntax support
  • Support tangling from multiple markdown documents in a heirarchy (e.g. an Obsidian vault)
  • The ability to execute code blocks by tag or id and put the results in the MD file (a la org-babel)
  • Extension of above, interpolation to allow execution of one block to be input or variable to another block (a la org-babel). This will likely be more simplistic than OB's version.

Wait, Tangles?

Tangle is a fancy word for writing out all the different code segments in the documentation into the appropriate places in source files. The opposite is untangled, which is the documentation. This word comes from literate programming jargon.

Why not just X Language's documentation tests?

A lot of modern languages, and even some older ones, support embedding tests inside doc strings or other comments in the source code. This is a great system, however I find myself using them infrequently. Each language is a little (or even a lot) different from the last, and getting all engineers to use embedded documentation in some languages is harder than others. If documentation tests in your language are working for you, then don't let me stop you.

I also feel like they primarly fill a different need. They are necessary and great for technical documentation on how to use the API of a library. But they are perhaps not great for tutorials, guides that are not about the program or service itself (but perhaps about its REST API), or anything where you can't expect the user to understand the language of the source itself, but still want to ensure up to date documentation.

Why not just Emacs Org Mode?

The primary problem with Org Mode is that it only runs in emacs. And even with support for things like emacs batch mode, its not very portable. Its difficult to get a team of developers to all use it. Markdown, though, is ubiquitous. Almost every editor has support for it, most version control UIs (like this one) have support for rendering... But there are not many options for turning markdown into a literate programming tool.

Contributing

Feel free to create an issue with any feedback. At the moment, things are so early stage I'm not super willing to just open the floodgates to direct contribution -- nor would I expect anyone would want to dive into this (albeit small) codebase right now.

You might also like...
A tool to format codeblocks inside markdown and org documents.
A tool to format codeblocks inside markdown and org documents.

cbfmt (codeblock format) A tool to format codeblocks inside markdown, org, and restructuredtext documents. It iterates over all codeblocks, and format

BadDown is a simplistic and bad markup language inspired by Markdown.

BadDown BadDown is a simplistic and bad markup language inspired by Markdown. Features Simplicity: BadDown keeps it simple, with minimal syntax to cre

mdBook is a utility to create modern online books from Markdown files.

Create book from markdown files. Like Gitbook but implemented in Rust

Introducing Inlyne, a GPU powered yet browsless tool to help you quickly view markdown files in the blink of an eye.
Introducing Inlyne, a GPU powered yet browsless tool to help you quickly view markdown files in the blink of an eye.

Inlyne - a GPU powered, browserless, markdown + html viewer inlyne README.md --theme dark/light About Markdown files are a wonderful tool to get forma

mn is my project to learn rust, I will try to build a markdown editor

med is a markdown editor This is my project to learn rust programming language, I will try to build a powerful markdown editor. Highlight: Support Vim

Markdown to HTML converter written in Rust. Inspired by Katsuki Yuri's Makudaun Tool.
Markdown to HTML converter written in Rust. Inspired by Katsuki Yuri's Makudaun Tool.

Makurust Makurust is a powerful tool written in Rust that allows you to effortlessly convert your Markdown files into static HTML pages. Inspired by T

This is a tool to evaluate or export code from Markdown files.

Evaluate Markdown This is a tool to evaluate or export code from Markdown files. Why? Because I like writing Markdown files with code snippets (it's g

A zero-config leptos component to display markdown
A zero-config leptos component to display markdown

A port of yew-markdown using leptos ! Usage You can use this component to render both static and dynamic markdown. Static markdown use leptos::*; {

Transpile CommonMark Markdown to Typst, from within Typst!
Transpile CommonMark Markdown to Typst, from within Typst!

#set document(title: "cmarker.typ") #set page(numbering: "1", number-align: center) #set text(lang: "en") #align(center, text(weight: 700, 1.75em)[cma

Owner
Zach
Go, Rust, Python, PHP, and TS developer. Avoids Java.
Zach
A CLI tool you can pipe code and then ask for changes, add documentation, etc, using the OpenAI API.

AiBro This is your own little coding bro, immersed in the world of AI, crypto, and all other types of over hyped tech trends. You can pipe it code and

Josh Bainbridge 5 Sep 5, 2023
Format codebase in documentation 🦤

Gelatyx Format codebase in documentation ?? Features Format language code block inside documentation files Check mode. Ask Gelatyx is the documentatio

azzamsa 3 Oct 24, 2022
WIP GUI for NixOS documentation + configuration

nixos-druid Highly experimental GUI for NixOS. For now I expect to frequently make large changes and break stuff whenever I'm working on this. Screens

Sybrand Aarnoutse 6 Aug 23, 2022
The official documentation for Njord.

Docs The docs for Njord using mdBook. Not being used yet, will update this later. We refer to the API docs for now and examples. Contributors The foll

Njord 4 Dec 16, 2023
Tools - The Rome Toolchain. A linter, compiler, bundler, and more for JavaScript, TypeScript, HTML, Markdown, and CSS.

Rome is currently being rewritten in Rust. Read more about it in our latest blog post. The documentation below is out of date and available for poster

Rome 22k Jan 3, 2023
A Faster(⚡) formatter, linter, bundler, and more for JavaScript, TypeScript, JSON, HTML, Markdown, and CSS Lapce Plugin

Lapce Plugin for Rome Lapce-rome is a Lapce plugin for rome, The Rome is faster ⚡ , A formatter, linter, compiler, bundler, and more for JavaScript, T

xiaoxin 7 Dec 16, 2022
A command-line tool aiming to upload the local image used in your markdown file to the GitHub repo and replace the local file path with the returned URL.

Pup A command line tool aiming to upload the local image used in your markdown file to the GitHub repo and replace the local file path with the return

SteveLau 11 Aug 17, 2022
scan markdown files and execute `console` blocks

exec-commands − scan markdown files and execute console blocks exec-commands is a utility to update command-line-tool examples embedded in markdown fi

Hajime Suzuki 3 Nov 27, 2022
Core Fiberplane data models and methods for transforming them (templates, providers, markdown conversion)

fiberplane This repository is a monorepo for Rust code that is used throughout Fiberplane's product. Overview base64uuid - A utility for working with

Fiberplane 18 Feb 22, 2023
🏭 Convert Markdown documents into themed HTML pages with support for code syntax highlighting, LaTeX and Mermaid diagrams.

Marky Markdown Magician ?? Features Hot reload previewing ?? Conversion to HTML / PDF ?? Themes! ✨ Extensions - Math, diagrams, syntax-highlighting ??

Vadim 12 Feb 19, 2023