1#![allow(clippy::write_with_newline)]
2
3use std::fmt::Write;
4
5// Internal
6use clap::*;
7use clap_complete::*;
8
9/// Generate fig completion file
10pub struct Fig;
11
12impl Generator for Fig {
13    fn file_name(&self, name: &str) -> String {
14        format!("{name}.ts")
15    }
16
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
45fn escape_string(string: &str) -> String {
46    string
47        .replace('\\', "\\\\")
48        .replace('\"', "\\\"")
49        .replace('\t', "    ")
50        .replace('\n', " ")
51        .replace('\r', "")
52}
53
54fn 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
139fn 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
355fn 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
475fn 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