gleedoc
A doc test library for Gleam, inspired by Rust and Elixir's doctest tooling.
Doc tests let you write executable examples in your documentation comments (///).
These examples are extracted, compiled, and run as part of your test suite, ensuring your documentation never goes out of date.
Disclaimer: This project contains many LLM-generated code, and I used LLMs to do research and design. But I (as a Gleam amateur) have tried my best to review line by line, adjust and refactor.
How it works
- Extract
///doc comments from your.gleamsource files. - Find fenced code blocks tagged with
gleaminside those comments. - Generate test modules in your
test/directory. - Run the generated tests with
gleam test.
How other languages do it
| Language | Approach | Key Difference from Gleam |
|---|---|---|
| Rust | cargo test compiles ```rust blocks from /// comments. No REPL needed. | Gleam follows this model closely. |
| Elixir | doctest Module parses iex> prompts from @doc strings. | Elixir has a REPL; Gleam does not. |
| Python | doctest parses >>> prompts from docstrings. | Python is interpreted; Gleam is compiled. |
Because Gleam is a compiled language with no built-in REPL, gleedoc adopts Rust's approach: doc blocks are treated as standalone Gleam code that gets compiled and executed. If a block panics, the test fails.
Installation
gleam add gleedoc --devUsage
Write doc comments with gleam code blocks in your source files:
// src/math.gleam
/// Adds two numbers together.
///
/// ```gleam
/// let result = add(1, 2)
/// assert result == 3
/// ```
pub fn add(a: Int, b: Int) -> Int {
a + b
}Then run gleedoc to generate tests:
gleam run -m gleedoc
This creates test/gleedoc/math_gleedoc_test.gleam containing:
// Generated by gleedoc - do not edit manually
import math.{add}
// From: src/math.gleam:4
pub fn add_1_test() {
let result = add(1, 2)
assert result == 3
}Now run your tests as usual:
gleam testAPI
You can also use gleedoc programmatically from your test suite:
// test/gleedoc_setup.gleam
import gleedoc
pub fn main() {
let config = gleedoc.GleedocConfig(
output_dir: "test",
source_dir: "src",
)
case gleedoc.run(config) {
Ok(Nil) -> Nil
Error(snag) -> panic as snag.issue
}
}Architecture
src/
gleedoc.gleam # Main entry point and CLI
gleedoc/
extract.gleam # Line-based doc comment extraction
parse.gleam # Markdown code block parsing
generate.gleam # Test file generation
scan.gleam # Public names and imports extraction with glanceKey dependencies
| Package | Role |
|---|---|
glance | Gleam source parser |
simplifile | Cross-target file I/O |
snag | Lightweight error handling |
Development
rm -rf test/integration/ && gleam test # Run the test suiteContributing
Please kindly create an issue in your human voice, describe the feature request or bug clearly with reproduction steps, and ideally with a proposed solution before creating any PR.
Roadmap
Implemented
-
Extract
///doc comments from source files -
Parse
```gleamfenced code blocks -
Generate compatible
gleeunittest files - Multi-file source discovery
-
Use
glanceto extract public names for unqualified imports -
Cross-module imports:
importstatements in code blocks are merged into generated tests -
Single-command
gleam run -m gleedocCLI experience
Missing Features (compared to Rust, Elixir, and Python)
| Feature | Rust | Elixir | Python | gleedoc |
|---|---|---|---|---|
ignore / skip attribute | ✅ | ✅ | ✅ | ❌ |
no_run (compile only) | ✅ | ❌ | ❌ | ❌ |
should_panic | ✅ | ❌ | ❌ | ❌ |
Hidden setup lines (#) | ✅ | ❌ | ❌ | ❌ |
Output assertions (// ->) | ❌ |
✅ (iex>) |
✅ (>>>) | ❌ |
| Module-level doc tests |
✅ (//!) | ✅ | ✅ | ❌ |
compile_fail | ✅ | ❌ | ❌ | ❌ |
Multi-target (erlang / javascript) |
✅ (cfg) | ❌ | ❌ | ❌ |
| Incremental / cached generation | ✅ | ✅ | ✅ | ❌ |
| Source-mapped error reporting | ✅ | ✅ | ✅ | ❌ |
Know Issues
- Generated tests will contain unused imports