• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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