Skip to main content

ctl_core/surface/
templates.rs

1//! `MiniJinja` rendering for committed operator documents.
2
3use minijinja::{Environment, Error, context};
4use serde::Serialize;
5
6use super::Surface;
7
8/// Shared fragment that renders a skill frontmatter version line.
9pub const VERSION_FRAGMENT: &str = r"{% macro version_line(version) -%}
10version: {{ version }}
11{%- endmacro %}";
12
13/// Shared fragment that renders mounted invocations and the no-`--` rule.
14pub const INVOCATION_FRAGMENT: &str = r"{% macro mounted_invocation(surface, examples) -%}
15## Invocation
16
17```sh
18{% for example in examples -%}
19mise run {{ surface.mount }} {{ example }}
20{% endfor -%}
21```
22
23Never `mise run {{ surface.mount }} --`. The `--` in `#USAGE mount` is mise's
24completion bootstrap.
25{%- endmacro %}";
26
27/// Shared fragment that renders the visible top-level Clap commands.
28pub const COMMANDS_FRAGMENT: &str = r#"{% macro command_inventory(surface) -%}
29## Commands
30
31| Command | Aliases | Purpose |
32|:--|:--|:--|
33{% for command in surface.commands if not command.hidden -%}
34| `{{ command.name }}` | {% if command.visible_aliases %}`{{ command.visible_aliases | join("`, `") }}`{% else %}—{% endif %} | {{ command.about | replace("|", "\\|") | replace("\n", " ") }} |
35{% endfor -%}
36{{- "" -}}
37{%- endmacro %}"#;
38
39const FRAGMENTS: [(&str, &str); 3] = [
40    ("ctl/version.md.jinja", VERSION_FRAGMENT),
41    ("ctl/invocation.md.jinja", INVOCATION_FRAGMENT),
42    ("ctl/commands.md.jinja", COMMANDS_FRAGMENT),
43];
44
45/// Add ctl-core's shared operator fragments to an existing environment.
46pub fn add_fragments(environment: &mut Environment<'static>) -> Result<(), Error> {
47    for (name, source) in FRAGMENTS {
48        environment.add_template(name, source)?;
49    }
50    Ok(())
51}
52
53/// Create a strict [`Environment`] containing the shared fragments.
54pub fn environment() -> Result<Environment<'static>, Error> {
55    let mut environment = Environment::new();
56    environment.set_undefined_behavior(minijinja::UndefinedBehavior::Strict);
57    environment.set_keep_trailing_newline(true);
58    add_fragments(&mut environment)?;
59    Ok(environment)
60}
61
62/// Render one operator template with a [`Surface`] and consumer-owned context.
63pub fn render<T: Serialize>(
64    name: &'static str,
65    source: &'static str,
66    surface: &Surface,
67    content: &T,
68) -> Result<String, Error> {
69    let mut environment = environment()?;
70    environment.add_template(name, source)?;
71    environment
72        .get_template(name)?
73        .render(context! { surface, content })
74}