pleme-doc-gen 0.1.41

Rust replacement for the M0 Python _gen-patterns.py + _gen-docs.py scripts in pleme-io/actions. Walks every action.yml + emits substrate's patterns-full.nix + per-action README.md + root catalog. Per the NO-SHELL prime directive.
//! Unified rendering surface for every typed AST family.
//!
//! Per the ★★ PRIME DIRECTIVE — ruthless standardization. Every AST
//! module (`toml_ast`, `json_ast`, `yaml_ast`, `xml_ast`, `sexp_ast`,
//! `lined_ast`, `control_ast`) implements `Render`, which gives every
//! caller one shape: `value.render() -> String`. No tuple parameters,
//! no module-specific `render_document`/`render_forms` free functions
//! at the call site.
//!
//! Together with the per-AST escape-by-construction guarantee, this
//! makes the format!()-for-code anti-pattern structurally impossible:
//! every emitter constructs a typed value, calls `.render()`, writes
//! the bytes.

/// Render this typed AST value into its target source text.
pub trait Render {
    fn render(&self) -> String;
}

/// Atomically write the rendered AST to `path`. The single emit
/// site for every typed AST consumer — central to evolve later
/// with caching, attestation, or per-file pre-write hooks.
pub fn emit<R: Render, P: AsRef<std::path::Path>>(
    path: P,
    value: &R,
) -> std::io::Result<()> {
    std::fs::write(path, value.render())
}

// ── toml_ast ───────────────────────────────────────────────────────
impl Render for crate::toml_ast::Document {
    fn render(&self) -> String {
        crate::toml_ast::Document::render(self)
    }
}

// ── json_ast ───────────────────────────────────────────────────────
impl Render for crate::json_ast::Value {
    fn render(&self) -> String {
        crate::json_ast::render(self)
    }
}

// ── yaml_ast ───────────────────────────────────────────────────────
impl Render for crate::yaml_ast::Value {
    fn render(&self) -> String {
        crate::yaml_ast::render(self)
    }
}

// ── xml_ast ────────────────────────────────────────────────────────
impl Render for crate::xml_ast::Element {
    fn render(&self) -> String {
        crate::xml_ast::render_document(self)
    }
}
impl Render for crate::xml_ast::ElementNoDecl {
    fn render(&self) -> String {
        crate::xml_ast::ElementNoDecl::render(self)
    }
}

// ── sexp_ast — wrap forms-vector in a typed Document ──────────────
impl Render for crate::sexp_ast::Forms {
    fn render(&self) -> String {
        crate::sexp_ast::render_forms(&self.0)
    }
}

// ── lined_ast — wrap lines-vector in a typed Document ─────────────
impl Render for crate::lined_ast::Lines {
    fn render(&self) -> String {
        crate::lined_ast::render(&self.0)
    }
}

// ── control_ast — wrap lines + indent-width pair ──────────────────
impl Render for crate::control_ast::Document {
    fn render(&self) -> String {
        crate::control_ast::render(&self.lines, self.indent_width)
    }
}

// ── ruby_ast ──────────────────────────────────────────────────────
impl Render for crate::ruby_ast::Block {
    fn render(&self) -> String {
        crate::ruby_ast::Block::render(self)
    }
}

// ── kotlin_ast ────────────────────────────────────────────────────
impl Render for crate::kotlin_ast::File {
    fn render(&self) -> String {
        crate::kotlin_ast::File::render(self)
    }
}

// ── cmake_ast ─────────────────────────────────────────────────────
impl Render for crate::cmake_ast::File {
    fn render(&self) -> String {
        crate::cmake_ast::File::render(self)
    }
}

// ── swift_ast ─────────────────────────────────────────────────────
impl Render for crate::swift_ast::File {
    fn render(&self) -> String {
        crate::swift_ast::File::render(self)
    }
}

// ── scala_ast ─────────────────────────────────────────────────────
impl Render for crate::scala_ast::File {
    fn render(&self) -> String {
        crate::scala_ast::File::render(self)
    }
}

// ── lua_ast ───────────────────────────────────────────────────────
impl Render for crate::lua_ast::File {
    fn render(&self) -> String { crate::lua_ast::File::render(self) }
}

// ── meson_ast ─────────────────────────────────────────────────────
impl Render for crate::meson_ast::File {
    fn render(&self) -> String { crate::meson_ast::File::render(self) }
}

// ── zig_ast ───────────────────────────────────────────────────────
impl Render for crate::zig_ast::File {
    fn render(&self) -> String { crate::zig_ast::File::render(self) }
}

// ── python_ast ────────────────────────────────────────────────────
impl Render for crate::python_ast::File {
    fn render(&self) -> String { crate::python_ast::File::render(self) }
}

// ── elixir_ast ────────────────────────────────────────────────────
impl Render for crate::elixir_ast::Module {
    fn render(&self) -> String { crate::elixir_ast::Module::render(self) }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn render_trait_is_uniform_across_families() {
        // Compile-time check: every family resolves the same .render() method.
        fn assert_renders<R: Render>(_r: &R) {}

        assert_renders(&crate::json_ast::Value::obj());
        assert_renders(&crate::yaml_ast::Value::map());
        assert_renders(&crate::xml_ast::Element::new("x"));
        assert_renders(&crate::toml_ast::Document::new());
        assert_renders(&crate::sexp_ast::Forms(vec![]));
        assert_renders(&crate::lined_ast::Lines(vec![]));
        assert_renders(&crate::control_ast::Document::new(2));
        assert_renders(&crate::ruby_ast::Block::new("X", "x"));
        assert_renders(&crate::kotlin_ast::File::new());
        assert_renders(&crate::swift_ast::File::new());
        assert_renders(&crate::scala_ast::File::new());
        assert_renders(&crate::lua_ast::File::new());
        assert_renders(&crate::meson_ast::File::new());
        assert_renders(&crate::zig_ast::File::new());
        assert_renders(&crate::python_ast::File::new());
        assert_renders(&crate::elixir_ast::Module::new("X"));
    }
}