TangleTycoon is another tool for generating code files from Markdown. My motivation for it is to merge documentation with some integration tests allowing for tutorials which compile for any language. Given an input markdown on stdin, group code blocks into streams, re-ordering them based on future dependencies. A stream is a set of code blocks which get accumulated to be written to a file specified by a map from streams to paths.
< $1 $BINDIR/tangletycoon.py --force --cpp main.cpp
Here, we send this readme to tangletycoon and write the default C++ stream to main.cpp. Our force flag is required since we didn't specify paths for other streams, such as the above shell block. So, we don't error when this is missing.
You can specify code blocks using backticks in Markdown and supply
the language immediately after. TangleTycoon uses the header after the
language specification to handle streams and dependencies. This header
looks like: $LANG name=$NAME stream=$STREAM dep=$DEP
. So, for C++, you
might have cpp name=main stream=tangle-tycoon.cpp dep=includes
. Note
that while the stream is given a file name, the file name is actually
specified using flags as seen above for --cpp main.cpp
which maps
main.cpp
to the cpp
stream.
A stream is a target for a contiguous sequence of code. By default,
each language goes to a stream with the name of the language. Otherwise,
it can be specified with stream=$STREAM
#include <iostream>
This is added to the same file as above.
void foo() {
std::cout << "Hello, world\n";
}
Code can be given names, and specified as dependencies so that, within a stream, that ordering is enforced. If a recursive dependency is specified, you'll probably get an infinite loop.
int main() {
foo();
}
Main is referencing a function which hasn't been declared, so declare it. Since this is specified as a dependency of the main code block, it will be placed before.
void foo();
Macros are definitely a useful way to combine blocks, but they require deeper integration with the code block and looked like they would complicate the implementation. You would also need to have a common namespace across code blocks where they are isolated for now.
- You can handle macros which you don't want generated by having the convention that the default namespace is not used
- Macros require the same degree of dependency handling
- Macros require more work to identify dependencies
My proposed syntax is to use the comment character for a given language, with a default of ###
to surround identifiers.
stdin input makes it easier to manipulate the markdown files before they
get into TangleTycoon. For example, supporting multiple inputs just using
cat
, or pre-processing prior to putting input in there..
TangleTycoon targets first class support for Bazel which wants control over output file paths.
TangleTycoon does not calculate results and inline them because it focuses on not needing to understand target languages and focusing on support for compiled languages and C++ in particular. Maybe with a DSL where you specify functions and mark that they should be invoked and the output serialized to string based on the type of the result, but then you have to integrate that with each language. I'm not sure that this complexity pays for itself. Rather, the way this could be handled is using tests and asserts as seen below:
def add2(x):
return x + 2
assert 4 == lib.add2(2);
We can be sure of the result by adding a test with an assert for the expected value. Since they're adjacent, the result is clear, but if we care about the output of lib.h, it's not polluted with our results.
import lib
Patches welcome. I probably won't add anything that increases size beyond the line guards which is currently 92 lines.
- Handle positional arguments. Positional arguments are in a fixed order, so track which is the next remaining one, and use that one. lang is an exception, which is always positional.
So, the header is lang stream name dep...
If we make sure that resolution of dependencies happens in file order, then the case where nothing is specified results in a file per language in order.
- Add documentation for integration with Bazel
BINDIR=$(dirname $2)
- Org Babel
- Leading literate coding environment
- Targets org files rather than markdown which prevents integration with git hosts which only support markdown.
- No support for sessions for C++ limiting tangling and weaving.
- Jupyter notebooks
- Allows interleaving text with code and can have C++ support with xeus-cling
- Personally had challenges with cling stability
- Changing order of evaluation is done interactively, rather than repeatably.
- Limited ability to generate multiple files automatically.
- driusan/lmt
- Meets a lot of the requirements: markdown, C++.
- I got inspiration here for using the header of the code block.
- Macros are an interesting way of re-working the ordering and better for repeating sections of code.
- Written in Go, but I don't have a Go compiler everywhere.
- joakimmj/md-tangle
- Delimiting code blocks is by tilde rather than backticks as is more standard.
- nuweb specifically targets latex.
- noweb
- brokestream/tangle.py Not Markdown
- Pweave Python specific
- Sweave R specific
- NanoLP