Skip to main content

usage/docs/markdown/
spec.rs

1use crate::docs::markdown::renderer::MarkdownRenderer;
2use crate::error::UsageErr;
3
4impl MarkdownRenderer {
5    pub fn render_spec(&self) -> Result<String, UsageErr> {
6        let all_commands = self.spec.cmd.all_subcommands();
7        // From the raw block for the same reason `render_config` does: the copy on `self.spec`
8        // was rendered by `new` before the builder options existed, and rendering happens once.
9        let mut config = crate::docs::models::SpecConfig::from(&self.raw_config);
10        config.render_md(self);
11        self.render_with("spec_template.md.tera", |ctx| {
12            ctx.insert("all_commands", &all_commands);
13            ctx.insert("config", &config);
14        })
15    }
16
17    pub fn render_index(&self) -> Result<String, UsageErr> {
18        let all_commands = self.spec.cmd.all_subcommands();
19        // So the index can link the settings page it sits beside. Without this the page was
20        // written and nothing pointed at it, which for a reader who starts at the index is
21        // the same as not writing it.
22        let config = crate::docs::models::SpecConfig::from(&self.raw_config);
23        // The name the page will actually be written under, so the link cannot point somewhere
24        // else than the file — including when a `settings` command has taken `settings.md`.
25        self.render_with("index_template.md.tera", |ctx| {
26            // An index links separate pages even when the renderer's ordinary mode is single-file.
27            ctx.insert("multi", &false);
28            ctx.insert("all_commands", &all_commands);
29            ctx.insert("config", &config);
30            ctx.insert("config_page", &self.config_page());
31        })
32    }
33}
34
35#[cfg(test)]
36mod tests {
37    use crate::docs::markdown::renderer::MarkdownRenderer;
38    use crate::test::SPEC_KITCHEN_SINK;
39    use crate::Spec;
40    use insta::assert_snapshot;
41
42    #[test]
43    fn test_render_markdown_spec() {
44        let ctx = MarkdownRenderer::new(SPEC_KITCHEN_SINK.clone());
45        assert_snapshot!(ctx.render_spec().unwrap(), @"
46        # `mycli`
47
48        - **Usage:** `mycli [FLAGS] <ARGS>… <SUBCOMMAND>`
49
50        ## Arguments
51        - **`<arg1>`** — arg1 description
52        - **`[arg2]`** — arg2 description
53
54          **Choices:** `choice1`, `choice2`, `choice3`
55
56          **Default:** `default value`
57        - **`<arg3>`** — arg3 long description
58        - **`<argrest>…`**
59        - **`[with-default]`**
60
61          **Default:** `default value`
62
63        ## Flags
64        - **`--flag1`** — flag1 description
65        - **`--flag2`** — flag2 long description
66
67          includes a code block:
68
69              $ echo hello world
70              hello world
71
72              more code
73
74          Examples:
75
76              # run with no arguments to use the interactive selector
77              $ mise use
78
79              # set the current version of node to 20.x in mise.toml of current directory
80              # will write the fuzzy version (e.g.: 20)
81
82          some docs
83
84              $ echo hello world
85              hello world
86        - **`--flag3`** — flag3 description
87        - **`--with-default`**
88
89          **Default:** `default value`
90        - **`--shell <shell>`**
91
92          **Choices:** `bash`, `zsh`, `fish`
93
94        ## `mycli plugin`
95
96        - **Usage:** `mycli plugin <SUBCOMMAND>`
97        - **Source code:** [`src/cli/plugin.rs`](https://github.com/jdx/mise/blob/main/src/cli/plugin.rs)
98
99        ## `mycli plugin install`
100
101        - **Usage:** `mycli plugin install [FLAGS] <plugin> <version>`
102        - **Source code:** [`src/cli/plugin/install.rs`](https://github.com/jdx/mise/blob/main/src/cli/plugin/install.rs)
103
104        install a plugin
105
106        ### Arguments
107        - **`<plugin>`**
108        - **`<version>`**
109
110        ### Flags
111        - **`-g --global`**
112        - **`-d --dir <dir>`**
113        - **`-f --force`**
114        ");
115    }
116
117    #[test]
118    fn hidden_commands_are_omitted_from_single_file_markdown() {
119        let spec: Spec = r#"
120            name "mycli"
121            bin "mycli"
122            cmd "visible" help="A documented command"
123            cmd "setup" hide=#true help="An internal command"
124        "#
125        .parse()
126        .unwrap();
127
128        let output = MarkdownRenderer::new(spec).render_spec().unwrap();
129
130        assert!(output.contains("## `mycli visible`"), "{output}");
131        assert!(!output.contains("mycli setup"), "{output}");
132        assert!(!output.contains("An internal command"), "{output}");
133    }
134
135    #[test]
136    fn package_metadata_reaches_markdown() {
137        let spec: Spec = r#"
138            name "metadata"
139            bin "metadata"
140            author "Example Maintainers"
141            license "MIT OR Apache-2.0"
142            repository "https://example.com/tool"
143        "#
144        .parse()
145        .unwrap();
146        let output = MarkdownRenderer::new(spec).render_spec().unwrap();
147
148        assert!(
149            output.contains("**Author:** Example Maintainers"),
150            "{output}"
151        );
152        assert!(
153            output.contains("**License:** MIT OR Apache-2.0"),
154            "{output}"
155        );
156        assert!(
157            output.contains("**Repository:** https://example.com/tool"),
158            "{output}"
159        );
160    }
161}