1 #![allow(clippy::write_with_newline)]
2
3 use std::fmt::Write;
4
5 // Internal
6 use clap::*;
7 use clap_complete::*;
8
9 /// Generate fig completion file
10 pub struct Fig;
11
12 impl Generator for Fig {
file_name(&self, name: &str) -> String13 fn file_name(&self, name: &str) -> String {
14 format!("{}.ts", name)
15 }
16
generate(&self, cmd: &Command, buf: &mut dyn std::io::Write)17 fn generate(&self, cmd: &Command, buf: &mut dyn std::io::Write) {
18 let command = cmd.get_bin_name().unwrap();
19 let mut buffer = String::new();
20
21 write!(
22 &mut buffer,
23 "const completion: Fig.Spec = {{\n name: \"{}\",\n",
24 escape_string(command)
25 )
26 .unwrap();
27
28 write!(
29 &mut buffer,
30 " description: \"{}\",\n",
31 escape_string(&cmd.get_about().unwrap_or_default().to_string())
32 )
33 .unwrap();
34
35 gen_fig_inner(&[], 2, cmd, &mut buffer);
36
37 write!(&mut buffer, "}};\n\nexport default completion;\n").unwrap();
38
39 buf.write_all(buffer.as_bytes())
40 .expect("Failed to write to generated file");
41 }
42 }
43
44 // Escape string inside double quotes and convert whitespace
escape_string(string: &str) -> String45 fn escape_string(string: &str) -> String {
46 string
47 .replace('\\', "\\\\")
48 .replace('\"', "\\\"")
49 .replace('\t', " ")
50 .replace('\n', " ")
51 .replace('\r', "")
52 }
53
gen_fig_inner(parent_commands: &[&str], indent: usize, cmd: &Command, buffer: &mut String)54 fn gen_fig_inner(parent_commands: &[&str], indent: usize, cmd: &Command, buffer: &mut String) {
55 if cmd.has_subcommands() {
56 write!(buffer, "{:indent$}subcommands: [\n", "", indent = indent).unwrap();
57 // generate subcommands
58 for subcommand in cmd.get_subcommands() {
59 let mut aliases: Vec<&str> = subcommand.get_all_aliases().collect();
60 if !aliases.is_empty() {
61 aliases.insert(0, subcommand.get_name());
62
63 write!(
64 buffer,
65 "{:indent$}{{\n{:indent$} name: [",
66 "",
67 "",
68 indent = indent + 2
69 )
70 .unwrap();
71
72 buffer.push_str(
73 &aliases
74 .iter()
75 .map(|name| format!("\"{}\"", escape_string(name)))
76 .collect::<Vec<_>>()
77 .join(", "),
78 );
79
80 write!(buffer, "],\n").unwrap();
81 } else {
82 write!(
83 buffer,
84 "{:indent$}{{\n{:indent$} name: \"{}\",\n",
85 "",
86 "",
87 escape_string(subcommand.get_name()),
88 indent = indent + 2
89 )
90 .unwrap();
91 }
92
93 if let Some(data) = subcommand.get_about() {
94 write!(
95 buffer,
96 "{:indent$}description: \"{}\",\n",
97 "",
98 escape_string(&data.to_string()),
99 indent = indent + 4
100 )
101 .unwrap();
102 }
103
104 if subcommand.is_hide_set() {
105 write!(buffer, "{:indent$}hidden: true,\n", "", indent = indent + 4).unwrap();
106 }
107
108 let mut parent_commands: Vec<_> = parent_commands.into();
109 parent_commands.push(subcommand.get_name());
110 gen_fig_inner(&parent_commands, indent + 4, subcommand, buffer);
111
112 write!(buffer, "{:indent$}}},\n", "", indent = indent + 2).unwrap();
113 }
114 write!(buffer, "{:indent$}],\n", "", indent = indent).unwrap();
115 }
116
117 buffer.push_str(&gen_options(cmd, indent));
118
119 let args = cmd.get_positionals().collect::<Vec<_>>();
120
121 match args.len() {
122 0 => {}
123 1 => {
124 write!(buffer, "{:indent$}args: ", "", indent = indent).unwrap();
125
126 buffer.push_str(&gen_args(args[0], indent));
127 }
128 _ => {
129 write!(buffer, "{:indent$}args: [\n", "", indent = indent).unwrap();
130 for arg in args {
131 write!(buffer, "{:indent$}", "", indent = indent + 2).unwrap();
132 buffer.push_str(&gen_args(arg, indent + 2));
133 }
134 write!(buffer, "{:indent$}]\n", "", indent = indent).unwrap();
135 }
136 };
137 }
138
gen_options(cmd: &Command, indent: usize) -> String139 fn gen_options(cmd: &Command, indent: usize) -> String {
140 let mut buffer = String::new();
141
142 let flags = generator::utils::flags(cmd);
143
144 if cmd.get_opts().next().is_some() || !flags.is_empty() {
145 write!(&mut buffer, "{:indent$}options: [\n", "", indent = indent).unwrap();
146
147 for option in cmd.get_opts() {
148 write!(&mut buffer, "{:indent$}{{\n", "", indent = indent + 2).unwrap();
149
150 let mut names = vec![];
151
152 if let Some(shorts) = option.get_short_and_visible_aliases() {
153 names.extend(
154 shorts
155 .iter()
156 .map(|short| format!("-{}", escape_string(&short.to_string()))),
157 );
158 }
159
160 if let Some(longs) = option.get_long_and_visible_aliases() {
161 names.extend(
162 longs
163 .iter()
164 .map(|long| format!("--{}", escape_string(long))),
165 );
166 }
167
168 if names.len() > 1 {
169 write!(&mut buffer, "{:indent$}name: [", "", indent = indent + 4).unwrap();
170
171 buffer.push_str(
172 &names
173 .iter()
174 .map(|name| format!("\"{}\"", escape_string(name)))
175 .collect::<Vec<_>>()
176 .join(", "),
177 );
178
179 buffer.push_str("],\n");
180 } else {
181 write!(
182 &mut buffer,
183 "{:indent$}name: \"{}\",\n",
184 "",
185 escape_string(&names[0]),
186 indent = indent + 4
187 )
188 .unwrap();
189 }
190
191 if let Some(data) = option.get_help() {
192 write!(
193 &mut buffer,
194 "{:indent$}description: \"{}\",\n",
195 "",
196 escape_string(&data.to_string()),
197 indent = indent + 4
198 )
199 .unwrap();
200 }
201
202 if option.is_hide_set() {
203 write!(
204 &mut buffer,
205 "{:indent$}hidden: true,\n",
206 "",
207 indent = indent + 4
208 )
209 .unwrap();
210 }
211
212 let conflicts = arg_conflicts(cmd, option);
213
214 if !conflicts.is_empty() {
215 write!(
216 &mut buffer,
217 "{:indent$}exclusiveOn: [\n",
218 "",
219 indent = indent + 4
220 )
221 .unwrap();
222
223 for conflict in conflicts {
224 write!(
225 &mut buffer,
226 "{:indent$}\"{}\",\n",
227 "",
228 escape_string(&conflict),
229 indent = indent + 6
230 )
231 .unwrap();
232 }
233
234 write!(&mut buffer, "{:indent$}],\n", "", indent = indent + 4).unwrap();
235 }
236
237 if let ArgAction::Set | ArgAction::Append | ArgAction::Count = option.get_action() {
238 write!(
239 &mut buffer,
240 "{:indent$}isRepeatable: true,\n",
241 "",
242 indent = indent + 4
243 )
244 .unwrap();
245 }
246
247 if option.is_require_equals_set() {
248 write!(
249 &mut buffer,
250 "{:indent$}requiresEquals: true,\n",
251 "",
252 indent = indent + 4
253 )
254 .unwrap();
255 }
256
257 write!(&mut buffer, "{:indent$}args: ", "", indent = indent + 4).unwrap();
258
259 buffer.push_str(&gen_args(option, indent + 4));
260
261 write!(&mut buffer, "{:indent$}}},\n", "", indent = indent + 2).unwrap();
262 }
263
264 for flag in generator::utils::flags(cmd) {
265 write!(&mut buffer, "{:indent$}{{\n", "", indent = indent + 2).unwrap();
266
267 let mut flags = vec![];
268
269 if let Some(shorts) = flag.get_short_and_visible_aliases() {
270 flags.extend(shorts.iter().map(|s| format!("-{}", s)));
271 }
272
273 if let Some(longs) = flag.get_long_and_visible_aliases() {
274 flags.extend(longs.iter().map(|s| format!("--{}", s)));
275 }
276
277 if flags.len() > 1 {
278 write!(&mut buffer, "{:indent$}name: [", "", indent = indent + 4).unwrap();
279
280 buffer.push_str(
281 &flags
282 .iter()
283 .map(|name| format!("\"{}\"", escape_string(name)))
284 .collect::<Vec<_>>()
285 .join(", "),
286 );
287
288 buffer.push_str("],\n");
289 } else {
290 write!(
291 &mut buffer,
292 "{:indent$}name: \"{}\",\n",
293 "",
294 escape_string(&flags[0]),
295 indent = indent + 4
296 )
297 .unwrap();
298 }
299
300 if let Some(data) = flag.get_help() {
301 write!(
302 &mut buffer,
303 "{:indent$}description: \"{}\",\n",
304 "",
305 escape_string(&data.to_string()).as_str(),
306 indent = indent + 4
307 )
308 .unwrap();
309 }
310
311 let conflicts = arg_conflicts(cmd, &flag);
312
313 if !conflicts.is_empty() {
314 write!(
315 &mut buffer,
316 "{:indent$}exclusiveOn: [\n",
317 "",
318 indent = indent + 4
319 )
320 .unwrap();
321
322 for conflict in conflicts {
323 write!(
324 &mut buffer,
325 "{:indent$}\"{}\",\n",
326 "",
327 escape_string(&conflict),
328 indent = indent + 6
329 )
330 .unwrap();
331 }
332
333 write!(&mut buffer, "{:indent$}],\n", "", indent = indent + 4).unwrap();
334 }
335
336 if let ArgAction::Set | ArgAction::Append | ArgAction::Count = flag.get_action() {
337 write!(
338 &mut buffer,
339 "{:indent$}isRepeatable: true,\n",
340 "",
341 indent = indent + 4
342 )
343 .unwrap();
344 }
345
346 write!(&mut buffer, "{:indent$}}},\n", "", indent = indent + 2).unwrap();
347 }
348
349 write!(&mut buffer, "{:indent$}],\n", "", indent = indent).unwrap();
350 }
351
352 buffer
353 }
354
gen_args(arg: &Arg, indent: usize) -> String355 fn gen_args(arg: &Arg, indent: usize) -> String {
356 if !arg.get_num_args().expect("built").takes_values() {
357 return "".to_string();
358 }
359
360 let mut buffer = String::new();
361
362 write!(
363 &mut buffer,
364 "{{\n{:indent$} name: \"{}\",\n",
365 "",
366 escape_string(arg.get_id().as_str()),
367 indent = indent
368 )
369 .unwrap();
370
371 let num_args = arg.get_num_args().expect("built");
372 if num_args != builder::ValueRange::EMPTY && num_args != builder::ValueRange::SINGLE {
373 write!(
374 &mut buffer,
375 "{:indent$}isVariadic: true,\n",
376 "",
377 indent = indent + 2
378 )
379 .unwrap();
380 }
381
382 if !arg.is_required_set() {
383 write!(
384 &mut buffer,
385 "{:indent$}isOptional: true,\n",
386 "",
387 indent = indent + 2
388 )
389 .unwrap();
390 }
391
392 if let Some(data) = generator::utils::possible_values(arg) {
393 write!(
394 &mut buffer,
395 "{:indent$}suggestions: [\n",
396 "",
397 indent = indent + 2
398 )
399 .unwrap();
400
401 for value in data {
402 if let Some(help) = value.get_help() {
403 write!(
404 &mut buffer,
405 "{:indent$}{{\n{:indent$} name: \"{}\",\n",
406 "",
407 "",
408 escape_string(value.get_name()),
409 indent = indent + 4,
410 )
411 .unwrap();
412
413 write!(
414 &mut buffer,
415 "{:indent$}description: \"{}\",\n",
416 "",
417 escape_string(&help.to_string()),
418 indent = indent + 6
419 )
420 .unwrap();
421
422 write!(&mut buffer, "{:indent$}}},\n", "", indent = indent + 4).unwrap();
423 } else {
424 write!(
425 &mut buffer,
426 "{:indent$}\"{}\",\n",
427 "",
428 escape_string(value.get_name()),
429 indent = indent + 4,
430 )
431 .unwrap();
432 }
433 }
434
435 write!(&mut buffer, "{:indent$}],\n", "", indent = indent + 2).unwrap();
436 } else {
437 match arg.get_value_hint() {
438 ValueHint::AnyPath | ValueHint::FilePath | ValueHint::ExecutablePath => {
439 write!(
440 &mut buffer,
441 "{:indent$}template: \"filepaths\",\n",
442 "",
443 indent = indent + 2
444 )
445 .unwrap();
446 }
447 ValueHint::DirPath => {
448 write!(
449 &mut buffer,
450 "{:indent$}template: \"folders\",\n",
451 "",
452 indent = indent + 2
453 )
454 .unwrap();
455 }
456 ValueHint::CommandString | ValueHint::CommandName | ValueHint::CommandWithArguments => {
457 write!(
458 &mut buffer,
459 "{:indent$}isCommand: true,\n",
460 "",
461 indent = indent + 2
462 )
463 .unwrap();
464 }
465 // Disable completion for others
466 _ => (),
467 };
468 };
469
470 write!(&mut buffer, "{:indent$}}},\n", "", indent = indent).unwrap();
471
472 buffer
473 }
474
arg_conflicts(cmd: &Command, arg: &Arg) -> Vec<String>475 fn arg_conflicts(cmd: &Command, arg: &Arg) -> Vec<String> {
476 let mut res = vec![];
477
478 for conflict in cmd.get_arg_conflicts_with(arg) {
479 if let Some(s) = conflict.get_short() {
480 res.push(format!("-{}", escape_string(&s.to_string())));
481 }
482
483 if let Some(l) = conflict.get_long() {
484 res.push(format!("--{}", escape_string(l)));
485 }
486 }
487
488 res
489 }
490