usage/docs/markdown/
spec.rs1use 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 let config = &self.spec().config;
8 self.render_with("spec_template.md.tera", |ctx| {
9 ctx.insert("all_commands", &all_commands);
10 ctx.insert("config", config);
11 })
12 }
13
14 pub fn render_index(&self) -> Result<String, UsageErr> {
15 let all_commands = self.spec().cmd.all_subcommands();
16 let config = &self.spec().config;
20 self.render_with("index_template.md.tera", |ctx| {
23 ctx.insert("multi", &false);
25 ctx.insert("all_commands", &all_commands);
26 ctx.insert("config", config);
27 ctx.insert("config_page", &self.config_page());
28 })
29 }
30}
31
32#[cfg(test)]
33mod tests {
34 use crate::docs::markdown::renderer::MarkdownRenderer;
35 use crate::test::SPEC_KITCHEN_SINK;
36 use crate::Spec;
37 use insta::assert_snapshot;
38
39 #[test]
48 fn a_command_s_long_help_gets_the_rendering_options_it_was_asked_for() {
49 let src = "name \"v\"\nbin \"v\"\ncmd \"go\" help=\"Go\" long_help=\"Goes.\\n\\n v go --now\\n\\nOk.\"\n";
50 let spec: Spec = src.parse().unwrap();
51
52 let with_fences = MarkdownRenderer::new(spec.clone())
53 .with_indented_blocks_to_code_fences(true)
54 .render_spec()
55 .unwrap();
56 assert!(
57 with_fences.contains("```\nv go --now\n```"),
58 "the option did not reach the command's help:\n{with_fences}"
59 );
60
61 let without = MarkdownRenderer::new(spec).render_spec().unwrap();
64 assert!(!without.contains("```"), "{without}");
65 assert!(without.contains(" v go --now"), "{without}");
66 }
67
68 #[test]
73 fn the_former_builder_name_still_works() {
74 let src = "name \"v\"\nbin \"v\"\ncmd \"go\" help=\"Go\" long_help=\"Goes.\\n\\n v go --now\\n\\nOk.\"\n";
75 let spec: Spec = src.parse().unwrap();
76 let page = MarkdownRenderer::new(spec)
77 .with_replace_pre_with_code_fences(true)
78 .render_spec()
79 .unwrap();
80 assert!(page.contains("```\nv go --now\n```"), "{page}");
81 }
82
83 #[test]
85 fn an_option_set_after_a_render_still_takes_effect() {
86 let src = "name \"v\"\nbin \"v\"\ncmd \"go\" help=\"Go\" long_help=\"Goes.\\n\\n v go --now\\n\\nOk.\"\n";
87 let spec: Spec = src.parse().unwrap();
88
89 let ctx = MarkdownRenderer::new(spec);
90 assert!(!ctx.render_spec().unwrap().contains("```"));
91 let ctx = ctx.with_indented_blocks_to_code_fences(true);
92 assert!(
93 ctx.render_spec().unwrap().contains("```\nv go --now\n```"),
94 "a render before the builder froze the model"
95 );
96 }
97
98 #[test]
99 fn test_render_markdown_spec() {
100 let ctx = MarkdownRenderer::new(SPEC_KITCHEN_SINK.clone());
101 assert_snapshot!(ctx.render_spec().unwrap(), @"
102 # `mycli`
103
104 - **Usage:** `mycli [FLAGS] <ARGS>… [SUBCOMMAND]`
105
106 ## Arguments
107 - **`<arg1>`** — arg1 description
108 - **`[arg2]`** — arg2 description
109
110 **Choices:** `choice1`, `choice2`, `choice3`
111
112 **Default:** `default value`
113 - **`<arg3>`** — arg3 long description
114 - **`<argrest>…`**
115 - **`[with-default]`**
116
117 **Default:** `default value`
118
119 ## Flags
120 - **`--flag1`** — flag1 description
121 - **`--flag2`** — flag2 long description
122
123 includes a code block:
124
125 $ echo hello world
126 hello world
127
128 more code
129
130 Examples:
131
132 # run with no arguments to use the interactive selector
133 $ mise use
134
135 # set the current version of node to 20.x in mise.toml of current directory
136 # will write the fuzzy version (e.g.: 20)
137
138 some docs
139
140 $ echo hello world
141 hello world
142 - **`--flag3`** — flag3 description
143 - **`--with-default`**
144
145 **Default:** `default value`
146 - **`--shell <shell>`**
147
148 **Choices:** `bash`, `zsh`, `fish`
149
150 ## `mycli plugin`
151
152 - **Usage:** `mycli plugin [SUBCOMMAND]`
153 - **Source code:** [`src/cli/plugin.rs`](https://github.com/jdx/mise/blob/main/src/cli/plugin.rs)
154
155 ## `mycli plugin install`
156
157 - **Usage:** `mycli plugin install [FLAGS] <plugin> <version>`
158 - **Source code:** [`src/cli/plugin/install.rs`](https://github.com/jdx/mise/blob/main/src/cli/plugin/install.rs)
159
160 install a plugin
161
162 ### Arguments
163 - **`<plugin>`**
164 - **`<version>`**
165
166 ### Flags
167 - **`-g --global`**
168 - **`-d --dir <dir>`**
169 - **`-f --force`**
170 ");
171 }
172
173 #[test]
174 fn hidden_commands_are_omitted_from_single_file_markdown() {
175 let spec: Spec = r#"
176 name "mycli"
177 bin "mycli"
178 cmd "visible" help="A documented command"
179 cmd "setup" hide=#true help="An internal command"
180 "#
181 .parse()
182 .unwrap();
183
184 let output = MarkdownRenderer::new(spec).render_spec().unwrap();
185
186 assert!(output.contains("## `mycli visible`"), "{output}");
187 assert!(!output.contains("mycli setup"), "{output}");
188 assert!(!output.contains("An internal command"), "{output}");
189 }
190
191 #[test]
192 fn package_metadata_reaches_markdown() {
193 let spec: Spec = r#"
194 name "metadata"
195 bin "metadata"
196 author "Example Maintainers"
197 license "MIT OR Apache-2.0"
198 repository "https://example.com/tool"
199 "#
200 .parse()
201 .unwrap();
202 let output = MarkdownRenderer::new(spec).render_spec().unwrap();
203
204 assert!(
205 output.contains("**Author:** Example Maintainers"),
206 "{output}"
207 );
208 assert!(
209 output.contains("**License:** MIT OR Apache-2.0"),
210 "{output}"
211 );
212 assert!(
213 output.contains("**Repository:** https://example.com/tool"),
214 "{output}"
215 );
216 }
217}