gleedoc

Package Version Hex Docs

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 substantial LLM-generated code, and I used LLMs for research and design. But I (as a Gleam amateur) have tried my best to review line by line, adjust, and refactor.

How it works

  1. Extract /// doc comments from your .gleam source files.
  2. Find fenced code blocks tagged with gleam inside those comments.
  3. Generate test modules in your test/ directory.
  4. Run the generated tests with gleam test.

How other languages do it

LanguageApproachKey Difference from Gleam
Rustcargo test compiles ```rust blocks from /// comments. No REPL needed.Gleam follows this model closely.
Elixirdoctest Module parses iex> prompts from @doc strings.Elixir has a REPL; Gleam does not.
Pythondoctest 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 --dev

Usage

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 test

Imports in generated tests

Each generated test file receives imports from three sources, merged and deduplicated automatically:

  1. The source module itself β€” gleedoc scans the module’s public names with glance and generates a list of unqualified imports that include all public functions/types/constants, so you can call functions directly in your snippets.
  2. The source module’s own top-level imports β€” any import statements at the top of the source file are carried over, so your snippets can use the same types and helpers the module itself uses without restating them.
  3. Imports written inside the code block β€” you can always add an explicit import line inside a snippet for anything extra.

For example, given this source file src/user.gleam:

import gleam/option.{type Option}              // 1️⃣

/// Returns a greeting for the user.
///
/// ```gleam
/// import gleam/option.{Some}                 // 2️⃣
///
/// let name = Some("Alice")
/// assert greet(name) == "Hello, Alice!"
/// ```
pub fn greet(name: Option(String)) -> String { // 3️⃣
  name
  |> option.map(fn(n) { "Hello, " <> n <> "!" })
  |> option.unwrap("")
}

The generated test file user_gleedoc_test.gleam will contain imports merged from all three sources:

// Generated by gleedoc - do not edit manually

import fixtures/user.{greet}             // source module public definitions (3️⃣)
import gleam/option.{type Option, Some}  // source module imports (1️⃣) + gleam code block imports (2️⃣)

// From: test/fixtures/user.gleam:5
pub fn greet_1_test() {
  let name = Some("Alice")
  assert greet(name) == "Hello, Alice!"
}

If the same module is imported in multiple places (e.g. gleam/option appears in both the source file and a code block), the unqualified names from all of them are merged into a single import line.

There are more examples in test/fixtures and test/integration/gleedoc.

API

You can also use gleedoc programmatically from your test suite:

// test/gleedoc_setup.gleam
import gleedoc

pub fn main() {
  let config = gleedoc.GleedocConfig(
    source_dir: "src",
    output_dir: "test",
    extra_imports: [],
  )

  case gleedoc.run(config) {
    Ok(Nil) -> Nil
    Error(snag) -> panic as snag.issue
  }
}

The GleedocConfig

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 glance

Key dependencies

PackageRole
glanceGleam source parser
simplifileCross-target file I/O
snagLightweight error handling

Development

gleam run -m prepare_tests && gleam test

You can also run the tests with the JavaScript target:

gleam run -m prepare_tests && gleam test -t javascript

Windows

On Windows, you probably want to make sure that autocrlf is false before checking out this repo:

git config --global core.autocrlf false

Contributing

Please kindly create an issue in your human voice, clearly describe the feature request or bug with reproduction steps, and ideally include a proposed solution before creating any PR.

Roadmap

Basics

Additional features before 1.0

Missing Features (compared to Rust, Elixir, and Python)

πŸ“† - Planned for 1.0 πŸ›‘ - Not Planned for 1.0 βœ… - Implemented

FeatureRustElixirPythongleedoc
Single-command CLI experienceβœ…βœ…βœ…πŸ“†
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

The Name

gleeunit for unit tests, gleedoc for doc tests! 😸

✨ Search Document