#![allow(clippy::write_with_newline)] use std::fmt::Write; // Internal use clap::*; use clap_complete::*; /// Generate fig completion file pub struct Fig; impl Generator for Fig { fn file_name(&self, name: &str) -> String { format!("{name}.ts") } fn generate(&self, cmd: &Command, buf: &mut dyn std::io::Write) { let command = cmd.get_bin_name().unwrap(); let mut buffer = String::new(); write!( &mut buffer, "const completion: Fig.Spec = {{\n name: \"{}\",\n", escape_string(command) ) .unwrap(); write!( &mut buffer, " description: \"{}\",\n", escape_string(&cmd.get_about().unwrap_or_default().to_string()) ) .unwrap(); gen_fig_inner(&[], 2, cmd, &mut buffer); write!(&mut buffer, "}};\n\nexport default completion;\n").unwrap(); buf.write_all(buffer.as_bytes()) .expect("Failed to write to generated file"); } } // Escape string inside double quotes and convert whitespace fn escape_string(string: &str) -> String { string .replace('\\', "\\\\") .replace('\"', "\\\"") .replace('\t', " ") .replace('\n', " ") .replace('\r', "") } fn gen_fig_inner(parent_commands: &[&str], indent: usize, cmd: &Command, buffer: &mut String) { if cmd.has_subcommands() { write!(buffer, "{:indent$}subcommands: [\n", "", indent = indent).unwrap(); // generate subcommands for subcommand in cmd.get_subcommands() { let mut aliases: Vec<&str> = subcommand.get_all_aliases().collect(); if !aliases.is_empty() { aliases.insert(0, subcommand.get_name()); write!( buffer, "{:indent$}{{\n{:indent$} name: [", "", "", indent = indent + 2 ) .unwrap(); buffer.push_str( &aliases .iter() .map(|name| format!("\"{}\"", escape_string(name))) .collect::>() .join(", "), ); write!(buffer, "],\n").unwrap(); } else { write!( buffer, "{:indent$}{{\n{:indent$} name: \"{}\",\n", "", "", escape_string(subcommand.get_name()), indent = indent + 2 ) .unwrap(); } if let Some(data) = subcommand.get_about() { write!( buffer, "{:indent$}description: \"{}\",\n", "", escape_string(&data.to_string()), indent = indent + 4 ) .unwrap(); } if subcommand.is_hide_set() { write!(buffer, "{:indent$}hidden: true,\n", "", indent = indent + 4).unwrap(); } let mut parent_commands: Vec<_> = parent_commands.into(); parent_commands.push(subcommand.get_name()); gen_fig_inner(&parent_commands, indent + 4, subcommand, buffer); write!(buffer, "{:indent$}}},\n", "", indent = indent + 2).unwrap(); } write!(buffer, "{:indent$}],\n", "", indent = indent).unwrap(); } buffer.push_str(&gen_options(cmd, indent)); let args = cmd.get_positionals().collect::>(); match args.len() { 0 => {} 1 => { write!(buffer, "{:indent$}args: ", "", indent = indent).unwrap(); buffer.push_str(&gen_args(args[0], indent)); } _ => { write!(buffer, "{:indent$}args: [\n", "", indent = indent).unwrap(); for arg in args { write!(buffer, "{:indent$}", "", indent = indent + 2).unwrap(); buffer.push_str(&gen_args(arg, indent + 2)); } write!(buffer, "{:indent$}]\n", "", indent = indent).unwrap(); } }; } fn gen_options(cmd: &Command, indent: usize) -> String { let mut buffer = String::new(); let flags = generator::utils::flags(cmd); if cmd.get_opts().next().is_some() || !flags.is_empty() { write!(&mut buffer, "{:indent$}options: [\n", "", indent = indent).unwrap(); for option in cmd.get_opts() { write!(&mut buffer, "{:indent$}{{\n", "", indent = indent + 2).unwrap(); let mut names = vec![]; if let Some(shorts) = option.get_short_and_visible_aliases() { names.extend( shorts .iter() .map(|short| format!("-{}", escape_string(&short.to_string()))), ); } if let Some(longs) = option.get_long_and_visible_aliases() { names.extend( longs .iter() .map(|long| format!("--{}", escape_string(long))), ); } if names.len() > 1 { write!(&mut buffer, "{:indent$}name: [", "", indent = indent + 4).unwrap(); buffer.push_str( &names .iter() .map(|name| format!("\"{}\"", escape_string(name))) .collect::>() .join(", "), ); buffer.push_str("],\n"); } else { write!( &mut buffer, "{:indent$}name: \"{}\",\n", "", escape_string(&names[0]), indent = indent + 4 ) .unwrap(); } if let Some(data) = option.get_help() { write!( &mut buffer, "{:indent$}description: \"{}\",\n", "", escape_string(&data.to_string()), indent = indent + 4 ) .unwrap(); } if option.is_hide_set() { write!( &mut buffer, "{:indent$}hidden: true,\n", "", indent = indent + 4 ) .unwrap(); } let conflicts = arg_conflicts(cmd, option); if !conflicts.is_empty() { write!( &mut buffer, "{:indent$}exclusiveOn: [\n", "", indent = indent + 4 ) .unwrap(); for conflict in conflicts { write!( &mut buffer, "{:indent$}\"{}\",\n", "", escape_string(&conflict), indent = indent + 6 ) .unwrap(); } write!(&mut buffer, "{:indent$}],\n", "", indent = indent + 4).unwrap(); } if let ArgAction::Set | ArgAction::Append | ArgAction::Count = option.get_action() { write!( &mut buffer, "{:indent$}isRepeatable: true,\n", "", indent = indent + 4 ) .unwrap(); } if option.is_require_equals_set() { write!( &mut buffer, "{:indent$}requiresEquals: true,\n", "", indent = indent + 4 ) .unwrap(); } write!(&mut buffer, "{:indent$}args: ", "", indent = indent + 4).unwrap(); buffer.push_str(&gen_args(option, indent + 4)); write!(&mut buffer, "{:indent$}}},\n", "", indent = indent + 2).unwrap(); } for flag in generator::utils::flags(cmd) { write!(&mut buffer, "{:indent$}{{\n", "", indent = indent + 2).unwrap(); let mut flags = vec![]; if let Some(shorts) = flag.get_short_and_visible_aliases() { flags.extend(shorts.iter().map(|s| format!("-{s}"))); } if let Some(longs) = flag.get_long_and_visible_aliases() { flags.extend(longs.iter().map(|s| format!("--{s}"))); } if flags.len() > 1 { write!(&mut buffer, "{:indent$}name: [", "", indent = indent + 4).unwrap(); buffer.push_str( &flags .iter() .map(|name| format!("\"{}\"", escape_string(name))) .collect::>() .join(", "), ); buffer.push_str("],\n"); } else { write!( &mut buffer, "{:indent$}name: \"{}\",\n", "", escape_string(&flags[0]), indent = indent + 4 ) .unwrap(); } if let Some(data) = flag.get_help() { write!( &mut buffer, "{:indent$}description: \"{}\",\n", "", escape_string(&data.to_string()).as_str(), indent = indent + 4 ) .unwrap(); } let conflicts = arg_conflicts(cmd, &flag); if !conflicts.is_empty() { write!( &mut buffer, "{:indent$}exclusiveOn: [\n", "", indent = indent + 4 ) .unwrap(); for conflict in conflicts { write!( &mut buffer, "{:indent$}\"{}\",\n", "", escape_string(&conflict), indent = indent + 6 ) .unwrap(); } write!(&mut buffer, "{:indent$}],\n", "", indent = indent + 4).unwrap(); } if let ArgAction::Set | ArgAction::Append | ArgAction::Count = flag.get_action() { write!( &mut buffer, "{:indent$}isRepeatable: true,\n", "", indent = indent + 4 ) .unwrap(); } write!(&mut buffer, "{:indent$}}},\n", "", indent = indent + 2).unwrap(); } write!(&mut buffer, "{:indent$}],\n", "", indent = indent).unwrap(); } buffer } fn gen_args(arg: &Arg, indent: usize) -> String { if !arg.get_num_args().expect("built").takes_values() { return "".to_string(); } let mut buffer = String::new(); write!( &mut buffer, "{{\n{:indent$} name: \"{}\",\n", "", escape_string(arg.get_id().as_str()), indent = indent ) .unwrap(); let num_args = arg.get_num_args().expect("built"); if num_args != builder::ValueRange::EMPTY && num_args != builder::ValueRange::SINGLE { write!( &mut buffer, "{:indent$}isVariadic: true,\n", "", indent = indent + 2 ) .unwrap(); } if !arg.is_required_set() { write!( &mut buffer, "{:indent$}isOptional: true,\n", "", indent = indent + 2 ) .unwrap(); } if let Some(data) = generator::utils::possible_values(arg) { write!( &mut buffer, "{:indent$}suggestions: [\n", "", indent = indent + 2 ) .unwrap(); for value in data { if let Some(help) = value.get_help() { write!( &mut buffer, "{:indent$}{{\n{:indent$} name: \"{}\",\n", "", "", escape_string(value.get_name()), indent = indent + 4, ) .unwrap(); write!( &mut buffer, "{:indent$}description: \"{}\",\n", "", escape_string(&help.to_string()), indent = indent + 6 ) .unwrap(); write!(&mut buffer, "{:indent$}}},\n", "", indent = indent + 4).unwrap(); } else { write!( &mut buffer, "{:indent$}\"{}\",\n", "", escape_string(value.get_name()), indent = indent + 4, ) .unwrap(); } } write!(&mut buffer, "{:indent$}],\n", "", indent = indent + 2).unwrap(); } else { match arg.get_value_hint() { ValueHint::AnyPath | ValueHint::FilePath | ValueHint::ExecutablePath => { write!( &mut buffer, "{:indent$}template: \"filepaths\",\n", "", indent = indent + 2 ) .unwrap(); } ValueHint::DirPath => { write!( &mut buffer, "{:indent$}template: \"folders\",\n", "", indent = indent + 2 ) .unwrap(); } ValueHint::CommandString | ValueHint::CommandName | ValueHint::CommandWithArguments => { write!( &mut buffer, "{:indent$}isCommand: true,\n", "", indent = indent + 2 ) .unwrap(); } // Disable completion for others _ => (), }; }; write!(&mut buffer, "{:indent$}}},\n", "", indent = indent).unwrap(); buffer } fn arg_conflicts(cmd: &Command, arg: &Arg) -> Vec { let mut res = vec![]; for conflict in cmd.get_arg_conflicts_with(arg) { if let Some(s) = conflict.get_short() { res.push(format!("-{}", escape_string(&s.to_string()))); } if let Some(l) = conflict.get_long() { res.push(format!("--{}", escape_string(l))); } } res }