clap/clap_complete_fig/src/fig.rs
Ed Page d3f5d7ce34 fix: Clarify Arg/ArgGroup id's role
This adjusts names.  Adjusting the derive naming (and re-naming) is left
to #2475.

Fixes #3335
2022-02-11 14:11:50 -06:00

304 lines
8.5 KiB
Rust

// Std
use std::io::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!("{}.ts", name)
}
fn generate(&self, app: &App, buf: &mut dyn Write) {
let command = app.get_bin_name().unwrap();
let mut buffer = String::new();
buffer.push_str(&format!(
"const completion: Fig.Spec = {{\n name: \"{}\",\n",
command
));
buffer.push_str(&format!(
" description: \"{}\",\n",
app.get_about().unwrap_or_default()
));
gen_fig_inner(command, &[], 2, app, &mut buffer);
buffer.push_str("};\n\nexport default completion;\n");
buf.write_all(buffer.as_bytes())
.expect("Failed to write to generated file");
}
}
// Escape string inside double quotes
fn escape_string(string: &str) -> String {
string.replace('\\', "\\\\").replace('\"', "\\\"")
}
fn gen_fig_inner(
root_command: &str,
parent_commands: &[&str],
indent: usize,
app: &App,
buffer: &mut String,
) {
if app.has_subcommands() {
buffer.push_str(&format!("{:indent$}subcommands: [\n", "", indent = indent));
// generate subcommands
for subcommand in app.get_subcommands() {
buffer.push_str(&format!(
"{:indent$}{{\n{:indent$} name: \"{}\",\n",
"",
"",
subcommand.get_name(),
indent = indent + 2
));
if let Some(data) = subcommand.get_about() {
buffer.push_str(&format!(
"{:indent$}description: \"{}\",\n",
"",
escape_string(data),
indent = indent + 4
));
}
let mut parent_commands: Vec<_> = parent_commands.into();
parent_commands.push(subcommand.get_name());
gen_fig_inner(
root_command,
&parent_commands,
indent + 4,
subcommand,
buffer,
);
buffer.push_str(&format!("{:indent$}}},\n", "", indent = indent + 2));
}
buffer.push_str(&format!("{:indent$}],\n", "", indent = indent));
}
buffer.push_str(&gen_options(app, indent));
let args = app.get_positionals().collect::<Vec<_>>();
match args.len() {
0 => {}
1 => {
buffer.push_str(&format!("{:indent$}args: ", "", indent = indent));
buffer.push_str(&gen_args(args[0], indent));
}
_ => {
buffer.push_str(&format!("{:indent$}args: [\n", "", indent = indent));
for arg in args {
buffer.push_str(&gen_args(arg, indent));
}
buffer.push_str(&format!("{:indent$}]\n", "", indent = indent));
}
};
}
fn gen_options(app: &App, indent: usize) -> String {
let mut buffer = String::new();
buffer.push_str(&format!("{:indent$}options: [\n", "", indent = indent));
for option in app.get_opts() {
buffer.push_str(&format!("{:indent$}{{\n", "", indent = indent + 2));
let mut names = vec![];
if let Some(shorts) = option.get_short_and_visible_aliases() {
names.extend(shorts.iter().map(|short| format!("-{}", short)));
}
if let Some(longs) = option.get_long_and_visible_aliases() {
names.extend(longs.iter().map(|long| format!("--{}", long)));
}
if names.len() > 1 {
buffer.push_str(&format!("{:indent$}name: [", "", indent = indent + 4));
buffer.push_str(
&names
.iter()
.map(|name| format!("\"{}\"", name))
.collect::<Vec<_>>()
.join(", "),
);
buffer.push_str("],\n");
} else {
buffer.push_str(&format!(
"{:indent$}name: \"{}\",\n",
"",
names[0],
indent = indent + 4
));
}
if let Some(data) = option.get_help() {
buffer.push_str(&format!(
"{:indent$}description: \"{}\",\n",
"",
escape_string(data),
indent = indent + 4
));
}
buffer.push_str(&format!("{:indent$}args: ", "", indent = indent + 4));
buffer.push_str(&gen_args(option, indent + 4));
buffer.push_str(&format!("{:indent$}}},\n", "", indent = indent + 2));
}
for flag in generator::utils::flags(app) {
buffer.push_str(&format!("{:indent$}{{\n", "", indent = indent + 2));
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 {
buffer.push_str(&format!("{:indent$}name: [", "", indent = indent + 4));
buffer.push_str(
&flags
.iter()
.map(|name| format!("\"{}\"", name))
.collect::<Vec<_>>()
.join(", "),
);
buffer.push_str("],\n");
} else {
buffer.push_str(&format!(
"{:indent$}name: \"{}\",\n",
"",
flags[0],
indent = indent + 4
));
}
if let Some(data) = flag.get_help() {
buffer.push_str(&format!(
"{:indent$}description: \"{}\",\n",
"",
escape_string(data).as_str(),
indent = indent + 4
));
}
buffer.push_str(&format!("{:indent$}}},\n", "", indent = indent + 2));
}
buffer.push_str(&format!("{:indent$}],\n", "", indent = indent));
buffer
}
fn gen_args(arg: &Arg, indent: usize) -> String {
if !arg.is_takes_value_set() {
return "".to_string();
}
let mut buffer = String::new();
buffer.push_str(&format!(
"{{\n{:indent$} name: \"{}\",\n",
"",
arg.get_id(),
indent = indent
));
if arg.is_multiple_values_set() {
buffer.push_str(&format!(
"{:indent$}isVariadic: true,\n",
"",
indent = indent + 2
));
}
if !arg.is_required_set() {
buffer.push_str(&format!(
"{:indent$}isOptional: true,\n",
"",
indent = indent + 2
));
}
if let Some(data) = arg.get_possible_values() {
buffer.push_str(&format!(
"{:indent$}suggestions: [\n",
"",
indent = indent + 2
));
for value in data {
buffer.push_str(&format!(
"{:indent$}{{\n{:indent$} name: \"{}\",\n",
"",
"",
value.get_name(),
indent = indent + 4,
));
if let Some(help) = value.get_help() {
buffer.push_str(&format!(
"{:indent$}description: \"{}\",\n",
"",
escape_string(help),
indent = indent + 4
));
}
buffer.push_str(&format!("{:indent$}}},\n", "", indent = indent + 4));
}
buffer.push_str(&format!("{:indent$}]\n", "", indent = indent + 2));
} else {
match arg.get_value_hint() {
ValueHint::AnyPath | ValueHint::FilePath | ValueHint::ExecutablePath => {
buffer.push_str(&format!(
"{:indent$}template: \"filepaths\",\n",
"",
indent = indent + 2
));
}
ValueHint::DirPath => {
buffer.push_str(&format!(
"{:indent$}template: \"folders\",\n",
"",
indent = indent + 2
));
}
ValueHint::CommandString | ValueHint::CommandName | ValueHint::CommandWithArguments => {
buffer.push_str(&format!(
"{:indent$}isCommand: true,\n",
"",
indent = indent + 2
));
}
// Disable completion for others
_ => (),
};
};
buffer.push_str(&format!("{:indent$}}},\n", "", indent = indent));
buffer
}