Skip to main content

usage/docs/markdown/
cmd.rs

1use crate::docs::markdown::renderer::MarkdownRenderer;
2use crate::docs::models::SpecCommand;
3use crate::error::UsageErr;
4
5impl MarkdownRenderer {
6    pub fn render_cmd(&self, cmd: &crate::SpecCommand) -> Result<String, UsageErr> {
7        let mut cmd = SpecCommand::from(cmd);
8        cmd.render_md(self);
9        self.render_with("cmd_template.md.tera", |ctx| ctx.insert("cmd", &cmd))
10    }
11}
12
13#[cfg(test)]
14mod tests {
15    use crate::docs::markdown::renderer::MarkdownRenderer;
16    use crate::test::SPEC_KITCHEN_SINK;
17    use crate::Spec;
18    use insta::assert_snapshot;
19
20    #[test]
21    fn test_render_markdown_cmd() {
22        let ctx = MarkdownRenderer::new(SPEC_KITCHEN_SINK.clone())
23            .with_multi(true)
24            .with_replace_pre_with_code_fences(true);
25        assert_snapshot!(ctx.render_cmd(&SPEC_KITCHEN_SINK.cmd).unwrap(), @r"
26        # `mycli`
27
28        - **Usage**: `mycli [FLAGS] <ARGS>… <SUBCOMMAND>`
29
30        ## Arguments
31
32        ### `<arg1>`
33
34        arg1 description
35
36        ### `[arg2]`
37
38        arg2 description
39
40        **Choices:**
41
42        - `choice1`
43        - `choice2`
44        - `choice3`
45
46        **Default:** `default value`
47
48        ### `<arg3>`
49
50        arg3 long description
51
52        ### `<argrest>…`
53
54        ### `[with-default]`
55
56        **Default:** `default value`
57
58        ## Flags
59
60        ### `--flag1`
61
62        flag1 description
63
64        ### `--flag2`
65
66        flag2 long description
67
68        includes a code block:
69
70        ```
71        $ echo hello world
72        hello world
73
74        more code
75        ```
76
77        Examples:
78
79        ```
80        # run with no arguments to use the interactive selector
81        $ mise use
82
83        # set the current version of node to 20.x in mise.toml of current directory
84        # will write the fuzzy version (e.g.: 20)
85        ```
86
87        some docs
88
89        ```
90        $ echo hello world
91        hello world
92        ```
93
94        ### `--flag3`
95
96        flag3 description
97
98        ### `--with-default`
99
100        **Default:** `default value`
101
102        ### `--shell <shell>`
103
104        **Choices:**
105
106        - `bash`
107        - `zsh`
108        - `fish`
109
110        ## Subcommands
111
112        - [`mycli plugin <SUBCOMMAND>`](/plugin.md)
113        ");
114    }
115
116    #[test]
117    fn test_render_markdown_cmd_effect() {
118        let spec: Spec = r#"
119name "mise"
120bin "mise"
121cmd "ls" effect="read" help="List installed tools"
122cmd "use" effect="write" help="Install a tool"
123cmd "uninstall" effect="destructive" help="Remove a tool"
124cmd "version" help="Show the version"
125        "#
126        .parse()
127        .unwrap();
128        let ctx = MarkdownRenderer::new(spec.clone()).with_multi(true);
129        let rendered = spec
130            .cmd
131            .subcommands
132            .values()
133            .map(|cmd| ctx.render_cmd(cmd).unwrap())
134            .collect::<Vec<_>>()
135            .join("\n\n");
136
137        // Every effect value must render its own label, and a command without
138        // one must not render the line at all.
139        assert_snapshot!(rendered, @r"
140        # `mise ls`
141
142        - **Usage**: `mise ls`
143        - **Effect**: read-only
144
145        List installed tools
146
147        # `mise use`
148
149        - **Usage**: `mise use`
150        - **Effect**: modifies state
151
152        Install a tool
153
154        # `mise uninstall`
155
156        - **Usage**: `mise uninstall`
157        - **Effect**: destructive — may delete or irreversibly overwrite
158
159        Remove a tool
160
161        # `mise version`
162
163        - **Usage**: `mise version`
164
165        Show the version
166        ");
167    }
168
169    #[test]
170    fn test_render_markdown_groups_by_heading() {
171        let spec: Spec = r#"
172bin "mycli"
173flag "--verbose" help="Verbose output"
174flag "--filter <pattern>" help="Only matching" help_heading="Filtering"
175flag "--hidden-one" help="Not shown" help_heading="Filtering" hide=#true
176arg "<file>" help="The file"
177arg "<mode>" help="How to run" help_heading="Behaviour"
178"#
179        .parse()
180        .unwrap();
181        let ctx = MarkdownRenderer::new(spec.clone()).with_multi(true);
182
183        // Each heading becomes its own section, hidden entries stay out, and a
184        // heading whose every entry is hidden produces no section at all.
185        assert_snapshot!(ctx.render_cmd(&spec.cmd).unwrap(), @"
186        # `mycli`
187
188        - **Usage**: `mycli [--verbose] [--filter <pattern>] <file> <mode>`
189
190        ## Arguments
191
192        ### `<file>`
193
194        The file
195
196        ## Behaviour
197
198        ### `<mode>`
199
200        How to run
201
202        ## Flags
203
204        ### `--verbose`
205
206        Verbose output
207
208        ## Filtering
209
210        ### `--filter <pattern>`
211
212        Only matching
213        ");
214    }
215
216    #[test]
217    fn test_render_markdown_groups_global_flags_too() {
218        // Global flags are rendered in their own section, which used to come from
219        // the flat list and so ignored headings that help output honored.
220        let spec: Spec = r#"
221bin "mycli"
222flag "--verbose" help="Verbose output" global=#true
223flag "--filter <pattern>" help="Only matching" help_heading="Filtering" global=#true
224flag "--local-one" help="Not global"
225cmd "sub" help="a subcommand"
226"#
227        .parse()
228        .unwrap();
229        let ctx = MarkdownRenderer::new(spec.clone()).with_multi(true);
230
231        assert_snapshot!(ctx.render_cmd(&spec.cmd).unwrap(), @"
232        # `mycli`
233
234        - **Usage**: `mycli [FLAGS] <SUBCOMMAND>`
235
236        ## Global Flags
237
238        ### `--verbose`
239
240        Verbose output
241
242        ## Filtering
243
244        ### `--filter <pattern>`
245
246        Only matching
247
248        ## Flags
249
250        ### `--local-one`
251
252        Not global
253
254        ## Subcommands
255
256        - [`mycli sub`](/sub.md)
257        ");
258    }
259
260    #[test]
261    fn generated_reference_separates_visible_flag_aliases() {
262        let spec: Spec = r#"
263bin "mycli"
264flag "-t -f --tail --follow" help="Follow output"
265"#
266        .parse()
267        .unwrap();
268        let ctx = MarkdownRenderer::new(spec.clone()).with_multi(true);
269        let rendered = ctx.render_cmd(&spec.cmd).unwrap();
270        assert!(rendered.contains("### `-t --tail`"), "{rendered}");
271        assert!(
272            rendered.contains("**Aliases:** `-f`, `--follow`"),
273            "{rendered}"
274        );
275        assert!(
276            !rendered.contains("### `-t -f --tail --follow`"),
277            "{rendered}"
278        );
279    }
280}