165 lines
5.2 KiB
Rust
165 lines
5.2 KiB
Rust
use clap::{Arg, Command as ClapCommand};
|
|
use warp_command_signatures::{
|
|
Argument, IsArgumentOptional, Opt, ParserDirectives, Priority, Signature,
|
|
};
|
|
|
|
/// Convert a [`clap::Command`] into a [`Signature`]. All subcommands, options, and arguments are
|
|
/// preserved on a best-effort basis.
|
|
pub fn signature_from_clap_command(cmd: &mut ClapCommand, bin_name: &str) -> Signature {
|
|
cmd.set_bin_name(bin_name);
|
|
// Building the command sets all sorts of derived properties like Args::get_num_args.
|
|
cmd.build();
|
|
convert_command(cmd, cmd.get_bin_name().expect("Set above").to_string())
|
|
}
|
|
|
|
fn convert_command(cmd: &ClapCommand, name: String) -> Signature {
|
|
let description = cmd.get_about().map(|s| s.to_string());
|
|
|
|
let arguments = convert_positional_args(cmd);
|
|
let options = convert_options(cmd);
|
|
let subcommands = convert_subcommands(cmd);
|
|
|
|
Signature {
|
|
name,
|
|
alias_generator: None,
|
|
description,
|
|
arguments,
|
|
subcommands,
|
|
options,
|
|
priority: Priority::default(),
|
|
parser_directives: ParserDirectives::default(),
|
|
}
|
|
}
|
|
|
|
/// Convert clap positional arguments to Signature arguments.
|
|
fn convert_positional_args(cmd: &ClapCommand) -> Option<Vec<Argument>> {
|
|
let positional_args: Vec<Argument> = cmd
|
|
.get_positionals()
|
|
.filter(|arg| !arg.is_hide_set())
|
|
.map(convert_arg_to_argument)
|
|
.collect();
|
|
|
|
if positional_args.is_empty() {
|
|
None
|
|
} else {
|
|
Some(positional_args)
|
|
}
|
|
}
|
|
|
|
/// Convert clap options/flags to Signature options.
|
|
fn convert_options(cmd: &ClapCommand) -> Option<Vec<Opt>> {
|
|
let opts: Vec<Opt> = cmd
|
|
.get_opts()
|
|
.filter(|arg| !arg.is_positional() && !arg.is_hide_set())
|
|
.map(convert_arg_to_opt)
|
|
.collect();
|
|
|
|
if opts.is_empty() {
|
|
None
|
|
} else {
|
|
Some(opts)
|
|
}
|
|
}
|
|
|
|
/// Convert clap subcommands to Signature subcommands by recursively converting each subcommand.
|
|
fn convert_subcommands(cmd: &ClapCommand) -> Option<Vec<Signature>> {
|
|
let subcommands: Vec<Signature> = cmd
|
|
.get_subcommands()
|
|
.filter(|subcmd| !subcmd.is_hide_set())
|
|
.flat_map(|cmd| {
|
|
std::iter::once(cmd.get_name())
|
|
.chain(cmd.get_visible_aliases())
|
|
.map(|cmd_or_alias| convert_command(cmd, cmd_or_alias.to_string()))
|
|
.collect::<Vec<_>>()
|
|
})
|
|
.collect();
|
|
|
|
if subcommands.is_empty() {
|
|
None
|
|
} else {
|
|
Some(subcommands)
|
|
}
|
|
}
|
|
|
|
/// Convert a [`clap::Arg`] to a positional [`Argument`].
|
|
fn convert_arg_to_argument(arg: &Arg) -> Argument {
|
|
let display_name = Some(arg.get_id().to_string());
|
|
let description = arg.get_help().map(|s| s.to_string());
|
|
let optional = if arg.is_required_set() {
|
|
IsArgumentOptional::Required
|
|
} else {
|
|
IsArgumentOptional::Optional(
|
|
arg.get_default_values()
|
|
.first()
|
|
.map(|s| s.to_string_lossy().to_string()),
|
|
)
|
|
};
|
|
|
|
Argument {
|
|
display_name,
|
|
description,
|
|
is_variadic: arg.get_num_args().is_some_and(|num_args| {
|
|
num_args.takes_values() && num_args.min_values() != num_args.max_values()
|
|
}),
|
|
// TODO: Extract argument types from clap value hints.
|
|
argument_types: vec![],
|
|
optional,
|
|
is_command: false,
|
|
skip_generator_validation: true,
|
|
}
|
|
}
|
|
|
|
/// Convert a [`clap::Arg]` to an [`Opt`] representing a flag/option.
|
|
fn convert_arg_to_opt(arg: &Arg) -> Opt {
|
|
let mut exact_string = Vec::new();
|
|
|
|
// Add short flags (e.g., "-h")
|
|
for short in arg.get_short_and_visible_aliases().into_iter().flatten() {
|
|
exact_string.push(format!("-{short}"));
|
|
}
|
|
|
|
// Add long flags (e.g., "--help")
|
|
for long in arg.get_long_and_visible_aliases().into_iter().flatten() {
|
|
exact_string.push(format!("--{long}"));
|
|
}
|
|
|
|
let description = arg.get_help().map(|s| s.to_string());
|
|
let required = arg.is_required_set();
|
|
|
|
let arguments = arg.get_num_args().and_then(|num_args| {
|
|
if num_args.takes_values() {
|
|
// TODO: Handle multi-valued flags. The Clap documentation is fairly unclear on how
|
|
// it models this (e.g. can we assume that get_default_values and get_value names are
|
|
// paired? Why is there only one ValueHint?). We currently don't need support for this.
|
|
|
|
Some(vec![Argument {
|
|
display_name: arg
|
|
.get_value_names()
|
|
.and_then(|names| names.first())
|
|
.map(|s| s.to_string()),
|
|
description: None,
|
|
is_variadic: false,
|
|
// TODO: Infer from ValuesHint.
|
|
argument_types: vec![],
|
|
optional: arg
|
|
.get_default_values()
|
|
.first()
|
|
.map(|s| IsArgumentOptional::Optional(Some(s.to_string_lossy().to_string())))
|
|
.unwrap_or(IsArgumentOptional::Required),
|
|
is_command: false,
|
|
skip_generator_validation: true,
|
|
}])
|
|
} else {
|
|
None
|
|
}
|
|
});
|
|
|
|
Opt {
|
|
exact_string,
|
|
description,
|
|
arguments,
|
|
required,
|
|
priority: Priority::default(),
|
|
}
|
|
}
|