1use crate::{Spec, SpecCommand};
2use std::sync::LazyLock;
3use tera::Tera;
4
5pub fn render_help(spec: &Spec, cmd: &SpecCommand, long: bool) -> String {
6 let docs_spec = crate::docs::models::Spec::from(spec.clone());
8 let docs_cmd = crate::docs::models::SpecCommand::from(cmd);
9
10 let mut ctx = tera::Context::new();
11 ctx.insert("spec", &docs_spec);
12 ctx.insert("cmd", &docs_cmd);
13 ctx.insert("long", &long);
14 let template = if long {
15 "spec_template_long.tera"
16 } else {
17 "spec_template_short.tera"
18 };
19 TERA.render(template, &ctx).unwrap().trim().to_string() + "\n"
20}
21
22static TERA: LazyLock<Tera> = LazyLock::new(|| {
23 let mut tera = Tera::default();
24
25 #[rustfmt::skip]
26 tera.add_raw_templates([
27 ("spec_template_short.tera", include_str!("templates/spec_template_short.tera")),
28 ("spec_template_long.tera", include_str!("templates/spec_template_long.tera")),
29 ]).unwrap();
30
31 tera.register_filter(
33 "ljust",
34 |value: &tera::Value, args: &std::collections::HashMap<String, tera::Value>| {
35 let value = value.as_str().unwrap_or("");
36 let width = args.get("width").and_then(|v| v.as_u64()).unwrap_or(0) as usize;
37 let result = format!("{:<width$}", value, width = width);
38 Ok(result.into())
39 },
40 );
41
42 tera
43});
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48 use insta::assert_snapshot;
49
50 #[test]
51 fn test_render_help_with_env() {
52 let spec = crate::spec! { r#"
53bin "testcli"
54flag "--color" env="MYCLI_COLOR" help="Enable color output"
55flag "--verbose" env="MYCLI_VERBOSE" help="Verbose output"
56flag "--debug" help="Debug mode"
57 "# }
58 .unwrap();
59
60 assert_snapshot!(render_help(&spec, &spec.cmd, false), @r"
61 Usage: testcli [FLAGS]
62
63 Flags:
64 --color Enable color output [env: MYCLI_COLOR]
65 --verbose Verbose output [env: MYCLI_VERBOSE]
66 --debug Debug mode
67 ");
68
69 assert_snapshot!(render_help(&spec, &spec.cmd, true), @r"
70 Usage: testcli [FLAGS]
71
72 Flags:
73 --color Enable color output
74 [env: MYCLI_COLOR]
75 --verbose Verbose output
76 [env: MYCLI_VERBOSE]
77 --debug Debug mode
78 ");
79 }
80
81 #[test]
82 fn test_render_help_with_arg_env() {
83 let spec = crate::spec! { r#"
84bin "testcli"
85arg "<input>" env="MY_INPUT" help="Input file"
86arg "<output>" env="MY_OUTPUT" help="Output file"
87arg "<extra>" help="Extra arg without env"
88arg "[default]" help="Arg with default value" default="default value"
89 "# }
90 .unwrap();
91
92 assert_snapshot!(render_help(&spec, &spec.cmd, false), @r"
93 Usage: testcli <ARGS>…
94
95 Arguments:
96 <input> Input file [env: MY_INPUT]
97 <output> Output file [env: MY_OUTPUT]
98 <extra> Extra arg without env
99 [default] Arg with default value (default: default value)
100 ");
101
102 assert_snapshot!(render_help(&spec, &spec.cmd, true), @r"
103 Usage: testcli <ARGS>…
104
105 Arguments:
106 <input> Input file
107 [env: MY_INPUT]
108 <output> Output file
109 [env: MY_OUTPUT]
110 <extra> Extra arg without env
111 [default] Arg with default value
112 (default: default value)
113 ");
114 }
115
116 #[test]
117 fn test_render_help_with_negated_flag() {
118 let spec = crate::spec! { r#"
119bin "testcli"
120flag "--compress" negate="--no-compress" default=#true help="Compress output"
121flag "--verbose" help="Verbose output"
122 "# }
123 .unwrap();
124
125 assert_snapshot!(render_help(&spec, &spec.cmd, false), @r"
126 Usage: testcli [--compress] [--verbose]
127
128 Flags:
129 --compress / --no-compress Compress output
130 --verbose Verbose output
131 ");
132
133 assert_snapshot!(render_help(&spec, &spec.cmd, true), @r"
134 Usage: testcli [--compress] [--verbose]
135
136 Flags:
137 --compress / --no-compress Compress output
138 --verbose Verbose output
139 ");
140 }
141
142 #[test]
143 fn test_render_help_with_before_after_help() {
144 let spec = crate::spec! { r#"
145bin "testcli"
146before_help "This text appears before the help"
147after_help "This text appears after the help"
148flag "--verbose" help="Enable verbose output"
149 "# }
150 .unwrap();
151
152 assert_snapshot!(render_help(&spec, &spec.cmd, false), @r"
153 This text appears before the help
154
155 Usage: testcli [--verbose]
156
157 Flags:
158 --verbose Enable verbose output
159
160 This text appears after the help
161 ");
162 }
163
164 #[test]
165 fn test_render_help_with_before_after_help_long() {
166 let spec = crate::spec! { r#"
167bin "testcli"
168before_help "short before"
169before_help_long "This is the long version of before help"
170after_help "short after"
171after_help_long "This is the long version of after help"
172flag "--verbose" help="Enable verbose output"
173 "# }
174 .unwrap();
175
176 assert_snapshot!(render_help(&spec, &spec.cmd, false), @r"
177 short before
178
179 Usage: testcli [--verbose]
180
181 Flags:
182 --verbose Enable verbose output
183
184 short after
185 ");
186
187 assert_snapshot!(render_help(&spec, &spec.cmd, true), @r"
188 This is the long version of before help
189
190 Usage: testcli [--verbose]
191
192 Flags:
193 --verbose Enable verbose output
194
195 This is the long version of after help
196 ");
197 }
198
199 #[test]
200 fn test_render_help_with_examples() {
201 let spec = crate::spec! { r#"
202bin "testcli"
203flag "--verbose" help="Enable verbose output"
204example "testcli --verbose" header="Run with verbose output"
205example "testcli" header="Run normally" help="Just runs the tool"
206 "# }
207 .unwrap();
208
209 assert_snapshot!(render_help(&spec, &spec.cmd, false), @r"
210 Usage: testcli [--verbose]
211
212 Flags:
213 --verbose Enable verbose output
214
215 Examples:
216 Run with verbose output:
217 $ testcli --verbose
218 Run normally:
219 $ testcli
220 ");
221
222 assert_snapshot!(render_help(&spec, &spec.cmd, true), @r"
223 Usage: testcli [--verbose]
224
225 Flags:
226 --verbose Enable verbose output
227
228 Examples:
229 Run with verbose output:
230 $ testcli --verbose
231 Run normally:
232 Just runs the tool
233 $ testcli
234 ");
235 }
236
237 #[test]
238 fn test_render_help_with_version() {
239 let spec = crate::spec! { r#"
240bin "testcli"
241name "TestCLI"
242version "1.2.3"
243flag "--verbose" help="Enable verbose output"
244 "# }
245 .unwrap();
246
247 assert_snapshot!(render_help(&spec, &spec.cmd, false), @r"
248 TestCLI 1.2.3
249 Usage: testcli [--verbose]
250
251 Flags:
252 --verbose Enable verbose output
253 ");
254 }
255
256 #[test]
257 fn test_render_help_with_author_license() {
258 let spec = crate::spec! { r#"
259bin "testcli"
260author "Test Author"
261license "MIT"
262flag "--verbose" help="Enable verbose output"
263 "# }
264 .unwrap();
265
266 assert_snapshot!(render_help(&spec, &spec.cmd, false), @r"
268 Usage: testcli [--verbose]
269
270 Flags:
271 --verbose Enable verbose output
272 ");
273
274 assert_snapshot!(render_help(&spec, &spec.cmd, true), @r"
276 Usage: testcli [--verbose]
277
278 Flags:
279 --verbose Enable verbose output
280
281 Author: Test Author
282 License: MIT
283 ");
284 }
285
286 #[test]
287 fn test_render_help_with_deprecated_command() {
288 let spec = crate::spec! { r#"
289bin "testcli"
290cmd "old-cmd" help="Do something" deprecated="use new-cmd instead"
291cmd "new-cmd" help="Do something better"
292 "# }
293 .unwrap();
294
295 assert_snapshot!(render_help(&spec, &spec.cmd, false), @r"
296 Usage: testcli <SUBCOMMAND>
297
298 Commands:
299 new-cmd Do something better
300 old-cmd [deprecated: use new-cmd instead] Do something
301 help Print this message or the help of the given subcommand(s)
302 ");
303 }
304}