mirror of
https://github.com/clap-rs/clap
synced 2024-12-13 22:32:33 +00:00
feat: add value completion
This commit is contained in:
parent
07dbefef3f
commit
547bd288bf
8 changed files with 297 additions and 33 deletions
12
README.md
12
README.md
|
@ -66,13 +66,17 @@ fn main() {
|
|||
```nu
|
||||
module completions {
|
||||
|
||||
def "myapp choice" [] {
|
||||
[ "first" "second" ]
|
||||
}
|
||||
|
||||
# Tests completions
|
||||
export extern myapp [
|
||||
file?: string # some input file
|
||||
--config(-c) # some config file
|
||||
--conf # some config file
|
||||
-C # some config file
|
||||
choice?: string
|
||||
choice?: string@"myapp choice"
|
||||
--version(-V) # Print version information
|
||||
]
|
||||
|
||||
|
@ -87,9 +91,13 @@ module completions {
|
|||
--version(-V) # Print version information
|
||||
]
|
||||
|
||||
def "myapp some_cmd sub_cmd config" [] {
|
||||
[ "Lest quotes aren't escaped." ]
|
||||
}
|
||||
|
||||
# sub-subcommand
|
||||
export extern "myapp some_cmd sub_cmd" [
|
||||
--config: string # the other case to test
|
||||
--config: string@"myapp some_cmd sub_cmd config" # the other case to test
|
||||
--version(-V) # Print version information
|
||||
]
|
||||
|
||||
|
|
99
src/lib.rs
99
src/lib.rs
|
@ -1,6 +1,6 @@
|
|||
//! Generates [Nushell](https://github.com/nushell/nushell) completions for [`clap`](https://github.com/clap-rs/clap) based CLIs
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use clap::{builder::PossibleValue, Arg, Command};
|
||||
use clap_complete::Generator;
|
||||
|
||||
/// Generate Nushell complete file
|
||||
|
@ -10,46 +10,42 @@ enum Argument {
|
|||
Short(Vec<char>),
|
||||
Long(Vec<String>),
|
||||
ShortAndLong(Vec<char>, Vec<String>),
|
||||
Positional(String, bool),
|
||||
Positional(bool),
|
||||
}
|
||||
|
||||
struct ArgumentLine {
|
||||
id: String,
|
||||
name: String,
|
||||
arg: Argument,
|
||||
takes_values: bool,
|
||||
possible_values: Vec<PossibleValue>,
|
||||
help: Option<String>,
|
||||
}
|
||||
|
||||
impl ArgumentLine {
|
||||
fn append_type_and_help(&self, s: &mut String) {
|
||||
if self.takes_values {
|
||||
s.push_str(": string");
|
||||
}
|
||||
fn new(arg: &Arg, bin_name: &str) -> Self {
|
||||
let id = arg.get_id().to_string();
|
||||
let name = bin_name.to_string();
|
||||
|
||||
if let Some(help) = &self.help {
|
||||
s.push_str(format!("\t# {}", help).as_str());
|
||||
}
|
||||
|
||||
s.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Arg> for ArgumentLine {
|
||||
fn from(arg: &Arg) -> Self {
|
||||
let takes_values = arg
|
||||
.get_num_args()
|
||||
.map(|v| v.takes_values())
|
||||
.unwrap_or(false);
|
||||
|
||||
let possible_values = arg.get_possible_values();
|
||||
|
||||
let help = arg.get_help().map(|s| s.to_string());
|
||||
|
||||
if arg.is_positional() {
|
||||
let id = arg.get_id().to_string();
|
||||
let required = arg.is_required_set();
|
||||
let arg = Argument::Positional(id, required);
|
||||
let arg = Argument::Positional(required);
|
||||
|
||||
return Self {
|
||||
id,
|
||||
name,
|
||||
arg,
|
||||
takes_values,
|
||||
possible_values,
|
||||
help,
|
||||
};
|
||||
}
|
||||
|
@ -60,29 +56,71 @@ impl From<&Arg> for ArgumentLine {
|
|||
match shorts {
|
||||
Some(shorts) => match longs {
|
||||
Some(longs) => Self {
|
||||
id,
|
||||
name,
|
||||
arg: Argument::ShortAndLong(
|
||||
shorts,
|
||||
longs.iter().map(|s| s.to_string()).collect(),
|
||||
),
|
||||
takes_values,
|
||||
possible_values,
|
||||
help,
|
||||
},
|
||||
None => Self {
|
||||
id,
|
||||
name,
|
||||
arg: Argument::Short(shorts),
|
||||
takes_values,
|
||||
possible_values,
|
||||
help,
|
||||
},
|
||||
},
|
||||
None => match longs {
|
||||
Some(long) => Self {
|
||||
arg: Argument::Long(long.iter().map(|s| s.to_string()).collect()),
|
||||
Some(longs) => Self {
|
||||
id,
|
||||
name,
|
||||
arg: Argument::Long(longs.iter().map(|s| s.to_string()).collect()),
|
||||
takes_values,
|
||||
possible_values,
|
||||
help,
|
||||
},
|
||||
None => unreachable!("No short or long option found"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_value_hints(&self) -> Option<String> {
|
||||
if self.possible_values.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut s = format!(r#" def "{} {}" [] {{"#, self.name, self.id);
|
||||
s.push_str("\n [");
|
||||
|
||||
for value in &self.possible_values {
|
||||
s.push_str(format!(r#" "{}""#, value.get_name()).as_str());
|
||||
}
|
||||
|
||||
s.push_str(" ]\n }\n\n");
|
||||
|
||||
Some(s)
|
||||
}
|
||||
|
||||
fn append_type_and_help(&self, s: &mut String) {
|
||||
if self.takes_values {
|
||||
s.push_str(": string");
|
||||
|
||||
if !self.possible_values.is_empty() {
|
||||
s.push_str(format!(r#"@"{} {}""#, self.name, self.id).as_str())
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(help) = &self.help {
|
||||
s.push_str(format!("\t# {}", help).as_str());
|
||||
}
|
||||
|
||||
s.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for ArgumentLine {
|
||||
|
@ -125,8 +163,8 @@ impl ToString for ArgumentLine {
|
|||
self.append_type_and_help(&mut s);
|
||||
}
|
||||
}
|
||||
Argument::Positional(positional, required) => {
|
||||
s.push_str(format!(" {}", positional).as_str());
|
||||
Argument::Positional(required) => {
|
||||
s.push_str(format!(" {}", self.id).as_str());
|
||||
|
||||
if !*required {
|
||||
s.push('?');
|
||||
|
@ -165,23 +203,30 @@ impl Generator for Nushell {
|
|||
}
|
||||
|
||||
fn generate_completion(completions: &mut String, cmd: &Command, is_subcommand: bool) {
|
||||
if let Some(about) = cmd.get_about() {
|
||||
completions.push_str(format!(" # {}\n", about).as_str());
|
||||
}
|
||||
|
||||
let bin_name = cmd.get_bin_name().expect("Failed to get bin name");
|
||||
|
||||
for hint in cmd
|
||||
.get_arguments()
|
||||
.filter_map(|arg| ArgumentLine::new(arg, bin_name).generate_value_hints())
|
||||
{
|
||||
completions.push_str(&hint);
|
||||
}
|
||||
|
||||
let name = if is_subcommand {
|
||||
format!(r#""{}""#, bin_name)
|
||||
} else {
|
||||
bin_name.into()
|
||||
};
|
||||
|
||||
if let Some(about) = cmd.get_about() {
|
||||
completions.push_str(format!(" # {}\n", about).as_str());
|
||||
}
|
||||
|
||||
completions.push_str(format!(" export extern {} [\n", name).as_str());
|
||||
|
||||
let s: String = cmd
|
||||
.get_arguments()
|
||||
.map(|arg| ArgumentLine::from(arg).to_string())
|
||||
.map(|arg| ArgumentLine::new(arg, bin_name).to_string())
|
||||
.collect();
|
||||
|
||||
completions.push_str(&s);
|
||||
|
|
111
tests/common.rs
111
tests/common.rs
|
@ -51,6 +51,25 @@ pub fn feature_sample_command(name: &'static str) -> Command {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn special_commands_command(name: &'static str) -> clap::Command {
|
||||
feature_sample_command(name)
|
||||
.subcommand(
|
||||
clap::Command::new("some_cmd")
|
||||
.about("tests other things")
|
||||
.arg(
|
||||
clap::Arg::new("config")
|
||||
.long("config")
|
||||
.hide(true)
|
||||
.action(clap::ArgAction::Set)
|
||||
.require_equals(true)
|
||||
.help("the other case to test"),
|
||||
)
|
||||
.arg(clap::Arg::new("path").num_args(1..)),
|
||||
)
|
||||
.subcommand(clap::Command::new("some-cmd-with-hyphens").alias("hyphen"))
|
||||
.subcommand(clap::Command::new("some-hidden-cmd").hide(true))
|
||||
}
|
||||
|
||||
pub fn aliases_command(name: &'static str) -> Command {
|
||||
Command::new(name)
|
||||
.version("3.0")
|
||||
|
@ -80,18 +99,107 @@ pub fn sub_subcommands_command(name: &'static str) -> Command {
|
|||
feature_sample_command(name).subcommand(
|
||||
Command::new("some_cmd")
|
||||
.about("top level subcommand")
|
||||
.visible_alias("some_cmd_alias")
|
||||
.subcommand(
|
||||
Command::new("sub_cmd").about("sub-subcommand").arg(
|
||||
Arg::new("config")
|
||||
.long("config")
|
||||
.action(ArgAction::Set)
|
||||
.value_parser([PossibleValue::new("Lest quotes aren't escaped.")])
|
||||
.value_parser([
|
||||
PossibleValue::new("Lest quotes, aren't escaped.")
|
||||
.help("help,with,comma"),
|
||||
PossibleValue::new("Second to trigger display of options"),
|
||||
])
|
||||
.help("the other case to test"),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn value_hint_command(name: &'static str) -> clap::Command {
|
||||
clap::Command::new(name)
|
||||
.arg(
|
||||
clap::Arg::new("choice")
|
||||
.long("choice")
|
||||
.action(clap::ArgAction::Set)
|
||||
.value_parser(["bash", "fish", "zsh"]),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::new("unknown")
|
||||
.long("unknown")
|
||||
.value_hint(clap::ValueHint::Unknown),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::new("other")
|
||||
.long("other")
|
||||
.value_hint(clap::ValueHint::Other),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::new("path")
|
||||
.long("path")
|
||||
.short('p')
|
||||
.value_hint(clap::ValueHint::AnyPath),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::new("file")
|
||||
.long("file")
|
||||
.short('f')
|
||||
.value_hint(clap::ValueHint::FilePath),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::new("dir")
|
||||
.long("dir")
|
||||
.short('d')
|
||||
.value_hint(clap::ValueHint::DirPath),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::new("exe")
|
||||
.long("exe")
|
||||
.short('e')
|
||||
.value_hint(clap::ValueHint::ExecutablePath),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::new("cmd_name")
|
||||
.long("cmd-name")
|
||||
.value_hint(clap::ValueHint::CommandName),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::new("cmd")
|
||||
.long("cmd")
|
||||
.short('c')
|
||||
.value_hint(clap::ValueHint::CommandString),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::new("command_with_args")
|
||||
.action(clap::ArgAction::Set)
|
||||
.num_args(1..)
|
||||
.trailing_var_arg(true)
|
||||
.value_hint(clap::ValueHint::CommandWithArguments),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::new("user")
|
||||
.short('u')
|
||||
.long("user")
|
||||
.value_hint(clap::ValueHint::Username),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::new("host")
|
||||
.short('H')
|
||||
.long("host")
|
||||
.value_hint(clap::ValueHint::Hostname),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::new("url")
|
||||
.long("url")
|
||||
.value_hint(clap::ValueHint::Url),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::new("email")
|
||||
.long("email")
|
||||
.value_hint(clap::ValueHint::EmailAddress),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn assert_matches_path(
|
||||
expected_path: impl AsRef<std::path::Path>,
|
||||
gen: impl clap_complete::Generator,
|
||||
|
@ -103,5 +211,6 @@ pub fn assert_matches_path(
|
|||
|
||||
snapbox::Assert::new()
|
||||
.action_env("SNAPSHOTS")
|
||||
.normalize_paths(false)
|
||||
.matches_path(expected_path, buf);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,18 @@ fn feature_sample() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn special_commands() {
|
||||
let name = "my-app";
|
||||
let cmd = common::special_commands_command(name);
|
||||
common::assert_matches_path(
|
||||
"tests/snapshots/special_commands.nu",
|
||||
clap_complete_nushell::Nushell,
|
||||
cmd,
|
||||
name,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aliases() {
|
||||
let name = "my-app";
|
||||
|
@ -47,3 +59,15 @@ fn sub_subcommands() {
|
|||
name,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_hint() {
|
||||
let name = "my-app";
|
||||
let cmd = common::value_hint_command(name);
|
||||
common::assert_matches_path(
|
||||
"tests/snapshots/value_hint.nu",
|
||||
clap_complete_nushell::Nushell,
|
||||
cmd,
|
||||
name,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
module completions {
|
||||
|
||||
def "my-app choice" [] {
|
||||
[ "first" "second" ]
|
||||
}
|
||||
|
||||
# Tests completions
|
||||
export extern my-app [
|
||||
file?: string # some input file
|
||||
--config(-c) # some config file
|
||||
--conf # some config file
|
||||
-C # some config file
|
||||
choice?: string
|
||||
choice?: string@"my-app choice"
|
||||
--version(-V) # Print version information
|
||||
]
|
||||
|
||||
|
|
40
tests/snapshots/special_commands.nu
Normal file
40
tests/snapshots/special_commands.nu
Normal file
|
@ -0,0 +1,40 @@
|
|||
module completions {
|
||||
|
||||
def "my-app choice" [] {
|
||||
[ "first" "second" ]
|
||||
}
|
||||
|
||||
# Tests completions
|
||||
export extern my-app [
|
||||
file?: string # some input file
|
||||
--config(-c) # some config file
|
||||
--conf # some config file
|
||||
-C # some config file
|
||||
choice?: string@"my-app choice"
|
||||
--version(-V) # Print version information
|
||||
]
|
||||
|
||||
# tests things
|
||||
export extern "my-app test" [
|
||||
--case: string # the case to test
|
||||
--version(-V) # Print version information
|
||||
]
|
||||
|
||||
# tests other things
|
||||
export extern "my-app some_cmd" [
|
||||
--config: string # the other case to test
|
||||
path?: string
|
||||
--version(-V) # Print version information
|
||||
]
|
||||
|
||||
export extern "my-app some-cmd-with-hyphens" [
|
||||
--version(-V) # Print version information
|
||||
]
|
||||
|
||||
export extern "my-app some-hidden-cmd" [
|
||||
--version(-V) # Print version information
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
use completions *
|
|
@ -1,12 +1,16 @@
|
|||
module completions {
|
||||
|
||||
def "my-app choice" [] {
|
||||
[ "first" "second" ]
|
||||
}
|
||||
|
||||
# Tests completions
|
||||
export extern my-app [
|
||||
file?: string # some input file
|
||||
--config(-c) # some config file
|
||||
--conf # some config file
|
||||
-C # some config file
|
||||
choice?: string
|
||||
choice?: string@"my-app choice"
|
||||
--version(-V) # Print version information
|
||||
]
|
||||
|
||||
|
@ -21,9 +25,13 @@ module completions {
|
|||
--version(-V) # Print version information
|
||||
]
|
||||
|
||||
def "my-app some_cmd sub_cmd config" [] {
|
||||
[ "Lest quotes, aren't escaped." "Second to trigger display of options" ]
|
||||
}
|
||||
|
||||
# sub-subcommand
|
||||
export extern "my-app some_cmd sub_cmd" [
|
||||
--config: string # the other case to test
|
||||
--config: string@"my-app some_cmd sub_cmd config" # the other case to test
|
||||
--version(-V) # Print version information
|
||||
]
|
||||
|
||||
|
|
26
tests/snapshots/value_hint.nu
Normal file
26
tests/snapshots/value_hint.nu
Normal file
|
@ -0,0 +1,26 @@
|
|||
module completions {
|
||||
|
||||
def "my-app choice" [] {
|
||||
[ "bash" "fish" "zsh" ]
|
||||
}
|
||||
|
||||
export extern my-app [
|
||||
--choice: string@"my-app choice"
|
||||
--unknown: string
|
||||
--other: string
|
||||
--path(-p): string
|
||||
--file(-f): string
|
||||
--dir(-d): string
|
||||
--exe(-e): string
|
||||
--cmd-name: string
|
||||
--cmd(-c): string
|
||||
command_with_args?: string
|
||||
--user(-u): string
|
||||
--host(-H): string
|
||||
--url: string
|
||||
--email: string
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
use completions *
|
Loading…
Reference in a new issue