loom
why
i wanted to write docs that contains working code where the code is extracted and can actually be compiled and run. knuth called this literate programming and there are other tools for this but they're either retarded, over-done, or don't work with markdown. loom fixes that
code blocks with a file= attribute get extracted and written to disk whereas fragments with fragment= can be reused across blocks using <<name>> transclusion. cycles are detected and that's basically it
how
you write markdown like this:
```python file=gay.py
print("im gay")
```
then run loom on it:
./loom gay.md
and it generates gay.py with the contents of that code block
fragments
sometimes you want to define a piece of code once and include it in multiple places. fragments let you do that, for example:
```rust fragment=imports
use std::io;
use std::fs;
```
```rust file=main.rs
<<imports>>
fn main() {
// whatever
}
```
```rust file=lib.rs
<<imports>>
pub fn autism() {}
```
when loom processes this both main.rs and lib.rs will have the imports inlined at the top
fragments can reference other fragments too since loom builds a dependency graph and does a topological sort to resolve everything in the right order. if you create a cycle it will tell you
append mode
if you want multiple code blocks to contribute to the same file, use append=true:
```python file=script.py
def foo():
pass
```
```python file=script.py append=true
def bar():
pass
```
both blocks end up in script.py in order
building
loom is written in OCaml so you'll need OCaml installed with the str and unix libraries (they're usually included)
make build
sudo make install
for the js version (used by the obsidian plugin and available as an npm package):
make js
this uses dune and js_of_ocaml to compile the core to js
output goes to js/dist/loom.js
to clean everything:
make clean
running
you can execute code blocks directly from your markdown. add run=<mode> to make a block runnable:
```python run=isolated
def square(x):
return x * x
```
run modes
isolatedwhich runs the block standalone. input is appended as codethisruns the block as-is and the input goes to stdinfilewill run the full assembled file. input goes to stdin also
input blocks
provide test input with a separate block:
```python fragment=math run=isolated
def factorial(n):
return 1 if n <= 1 else n * factorial(n - 1)
```
```input for=math
print(factorial(5))
print(factorial(10))
```
when you run the math fragment the input block gets appended. output:
120
3628800
supported languages
interpreted: python, js, ts, ruby, bash, lua, go, OCaml
compiled: rust, C, C++
ill add more when i can be bothered to (not likely)
obsidian
there's a plugin that integrates loom into obsidian bc my friend is autistic and likes things to look pretty
make obsidian
then copy obsidian-plugin/main.js, manifest.json and styles.css to your vault at and enable the plugin in settings
commands
Ctrl+P and type loom. use your brain
there's also a play button in the ribbon for quick execution :)
embed
process current file extracts all code blocks with file= attributes which writes the files and replaces the code blocks with embeds like ![[utils.py]]
live sync
enable live sync keeps the code blocks in your markdown and sets up bidirectional sync so that as you type in the code block the generated file updates in real time
if you edit the generated file directly the changes sync back to the markdown
blocks with transclusion or append mode only sync forward
js api
the js build is published to npm as loom-md
you can use it in node or electron apps:
const { loom } = require('loom-md');
const markdown = `
\`\`\`python file=test.py
print("hello")
\`\`\`
`;
const result = loom.process(markdown);
if (result.success) {
for (const file of result.files) {
console.log(file.path, file.content);
}
} else {
console.error(result.error);
}
the result object has success, files (array of {path, content}) and error (string or null)
ts types are included
examples
these are more involved examples showing consistent hashing implemented in both OCaml and Haskell (which was initially my only intention until my friend thought i could do better) from the same markdown file
it demonstrates fragment composition and multi language extraction
run loom on example.md to generate the source files and then use the makefile in examples/ to compile them, like so:
./loom example.md
cd examples && make
./examples/hashring-ml
./examples/hashring-hs