From 1d97c293ba7968d7bcd06b624104218cab6fbe3f Mon Sep 17 00:00:00 2001 From: mart-mihkel Date: Tue, 6 Aug 2024 10:46:18 +0300 Subject: [PATCH 1/4] test(complete): Test cases for allow_hyphen_values --- clap_complete/tests/testsuite/engine.rs | 155 ++++++++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/clap_complete/tests/testsuite/engine.rs b/clap_complete/tests/testsuite/engine.rs index 5acb77bc9e2..d4377ebb370 100644 --- a/clap_complete/tests/testsuite/engine.rs +++ b/clap_complete/tests/testsuite/engine.rs @@ -981,6 +981,161 @@ a_pos,c_pos" ); } +#[test] +fn suggest_allow_hyhpen() { + let mut cmd = Command::new("exhaustive") + .arg( + clap::Arg::new("format") + .long("format") + .short('F') + .allow_hyphen_values(true) + .value_parser(["--json", "--toml", "--yaml"]), + ) + .arg(clap::Arg::new("json").long("json")); + + assert_data_eq!(complete!(cmd, "--format --j[TAB]"), snapbox::str!["--json"]); + assert_data_eq!(complete!(cmd, "-F --j[TAB]"), snapbox::str!["--json"]); + assert_data_eq!(complete!(cmd, "--format --t[TAB]"), snapbox::str!["--toml"]); + assert_data_eq!(complete!(cmd, "-F --t[TAB]"), snapbox::str!["--toml"]); + + assert_data_eq!( + complete!(cmd, "--format --[TAB]"), + snapbox::str![ + "--json +--toml +--yaml" + ] + ); + + assert_data_eq!( + complete!(cmd, "-F --[TAB]"), + snapbox::str![ + "--json +--toml +--yaml" + ] + ); + + assert_data_eq!( + complete!(cmd, "--format --json --j[TAB]"), + snapbox::str![""] + ); + + assert_data_eq!(complete!(cmd, "-F --json --j[TAB]"), snapbox::str![""]); +} + +#[test] +fn suggest_positional_long_allow_hyhpen() { + let mut cmd = Command::new("exhaustive") + .arg( + clap::Arg::new("format") + .long("format") + .short('F') + .allow_hyphen_values(true) + .value_parser(["--json", "--toml", "--yaml"]), + ) + .arg( + clap::Arg::new("positional_a") + .value_parser(["--pos_a"]) + .index(1) + .allow_hyphen_values(true), + ) + .arg( + clap::Arg::new("positional_b") + .index(2) + .value_parser(["pos_b"]), + ); + + assert_data_eq!( + complete!(cmd, "--format --json --pos[TAB]"), + snapbox::str!["--pos_a"] + ); + assert_data_eq!( + complete!(cmd, "-F --json --pos[TAB]"), + snapbox::str!["--pos_a"] + ); + + assert_data_eq!( + complete!(cmd, "--format --json --pos_a [TAB]"), + snapbox::str![ + "--format +--help Print help +-F +-h Print help +--pos_a" + ] + ); + assert_data_eq!( + complete!(cmd, "-F --json --pos_a [TAB]"), + snapbox::str![ + "--format +--help Print help +-F +-h Print help +--pos_a" + ] + ); + + assert_data_eq!( + complete!(cmd, "--format --json --pos_a p[TAB]"), + snapbox::str![""] + ); + assert_data_eq!( + complete!(cmd, "-F --json --pos_a p[TAB]"), + snapbox::str![""] + ); +} + +#[test] +fn suggest_positional_short_allow_hyhpen() { + let mut cmd = Command::new("exhaustive") + .arg( + clap::Arg::new("format") + .long("format") + .short('F') + .allow_hyphen_values(true) + .value_parser(["--json", "--toml", "--yaml"]), + ) + .arg( + clap::Arg::new("positional_a") + .value_parser(["-a"]) + .index(1) + .allow_hyphen_values(true), + ) + .arg( + clap::Arg::new("positional_b") + .index(2) + .value_parser(["pos_b"]), + ); + + assert_data_eq!( + complete!(cmd, "--format --json -a [TAB]"), + snapbox::str![ + "--format +--help Print help +-F +-h Print help +-a" + ] + ); + assert_data_eq!( + complete!(cmd, "-F --json -a [TAB]"), + snapbox::str![ + "--format +--help Print help +-F +-h Print help +-a" + ] + ); + + assert_data_eq!( + complete!(cmd, "--format --json -a p[TAB]"), + snapbox::str![""] + ); + assert_data_eq!(complete!(cmd, "-F --json -a p[TAB]"), snapbox::str![""]); +} + fn complete(cmd: &mut Command, args: impl AsRef, current_dir: Option<&Path>) -> String { let input = args.as_ref(); let mut args = vec![std::ffi::OsString::from(cmd.get_name())]; From b7cfbdcf96c96da64f7d91c0bef915ef1a6dc37f Mon Sep 17 00:00:00 2001 From: mart-mihkel Date: Thu, 15 Aug 2024 13:46:05 +0300 Subject: [PATCH 2/4] feat(complete): Native support for hyphen values --- clap_complete/src/engine/complete.rs | 54 ++++++++++++++++++++----- clap_complete/tests/testsuite/engine.rs | 26 +++++++----- 2 files changed, 59 insertions(+), 21 deletions(-) diff --git a/clap_complete/src/engine/complete.rs b/clap_complete/src/engine/complete.rs index b9bb252293e..c78b3d62e8e 100644 --- a/clap_complete/src/engine/complete.rs +++ b/clap_complete/src/engine/complete.rs @@ -59,6 +59,13 @@ pub fn complete( } else if arg.is_escape() { is_escaped = true; } else if let Some((flag, value)) = arg.to_long() { + if let ParseState::Opt((opt, count)) = current_state { + if opt.is_allow_hyphen_values_set() { + next_state = parse_opt(opt, count); + continue; + } + } + if let Ok(flag) = flag { let opt = current_cmd.get_arguments().find(|a| { let longs = a.get_long_and_visible_aliases(); @@ -69,18 +76,32 @@ pub fn complete( }); is_find.unwrap_or(false) }); - if opt.map(|o| o.get_action().takes_values()).unwrap_or(false) { - if value.is_none() { - next_state = ParseState::Opt((opt.unwrap(), 1)); + + if let Some(opt) = opt { + if opt.get_action().takes_values() && value.is_none() { + next_state = ParseState::Opt((opt, 1)); }; + } else if pos_allows_hyphen(current_cmd, pos_index) { + (next_state, pos_index) = + parse_positional(current_cmd, pos_index, is_escaped, current_state); } } } else if let Some(short) = arg.to_short() { + if let ParseState::Opt((opt, count)) = current_state { + if opt.is_allow_hyphen_values_set() { + next_state = parse_opt(opt, count); + continue; + } + } + let (_, takes_value_opt, mut short) = parse_shortflags(current_cmd, short); if let Some(opt) = takes_value_opt { if short.next_value_os().is_none() { next_state = ParseState::Opt((opt, 1)); } + } else if pos_allows_hyphen(current_cmd, pos_index) { + (next_state, pos_index) = + parse_positional(current_cmd, pos_index, is_escaped, current_state); } } else { match current_state { @@ -88,14 +109,7 @@ pub fn complete( (next_state, pos_index) = parse_positional(current_cmd, pos_index, is_escaped, current_state); } - - ParseState::Opt((opt, count)) => { - let range = opt.get_num_args().expect("built"); - let max = range.max_values(); - if count < max { - next_state = ParseState::Opt((opt, count + 1)); - } - } + ParseState::Opt((opt, count)) => next_state = parse_opt(opt, count), } } } @@ -546,3 +560,21 @@ fn parse_positional<'a>( ), } } + +/// Parse optional flag argument. Return new state +fn parse_opt(opt: &clap::Arg, count: usize) -> ParseState<'_> { + let range = opt.get_num_args().expect("built"); + let max = range.max_values(); + if count < max { + ParseState::Opt((opt, count + 1)) + } else { + ParseState::ValueDone + } +} + +fn pos_allows_hyphen(cmd: &clap::Command, pos_index: usize) -> bool { + cmd.get_positionals() + .find(|a| a.get_index() == Some(pos_index)) + .map(|p| p.is_allow_hyphen_values_set()) + .unwrap_or(false) +} diff --git a/clap_complete/tests/testsuite/engine.rs b/clap_complete/tests/testsuite/engine.rs index d4377ebb370..923335b10dd 100644 --- a/clap_complete/tests/testsuite/engine.rs +++ b/clap_complete/tests/testsuite/engine.rs @@ -1018,10 +1018,13 @@ fn suggest_allow_hyhpen() { assert_data_eq!( complete!(cmd, "--format --json --j[TAB]"), - snapbox::str![""] + snapbox::str!["--json"] ); - assert_data_eq!(complete!(cmd, "-F --json --j[TAB]"), snapbox::str![""]); + assert_data_eq!( + complete!(cmd, "-F --json --j[TAB]"), + snapbox::str!["--json"] + ); } #[test] @@ -1062,7 +1065,7 @@ fn suggest_positional_long_allow_hyhpen() { --help Print help -F -h Print help ---pos_a" +pos_b" ] ); assert_data_eq!( @@ -1072,17 +1075,17 @@ fn suggest_positional_long_allow_hyhpen() { --help Print help -F -h Print help ---pos_a" +pos_b" ] ); assert_data_eq!( complete!(cmd, "--format --json --pos_a p[TAB]"), - snapbox::str![""] + snapbox::str!["pos_b"] ); assert_data_eq!( complete!(cmd, "-F --json --pos_a p[TAB]"), - snapbox::str![""] + snapbox::str!["pos_b"] ); } @@ -1115,7 +1118,7 @@ fn suggest_positional_short_allow_hyhpen() { --help Print help -F -h Print help --a" +pos_b" ] ); assert_data_eq!( @@ -1125,15 +1128,18 @@ fn suggest_positional_short_allow_hyhpen() { --help Print help -F -h Print help --a" +pos_b" ] ); assert_data_eq!( complete!(cmd, "--format --json -a p[TAB]"), - snapbox::str![""] + snapbox::str!["pos_b"] + ); + assert_data_eq!( + complete!(cmd, "-F --json -a p[TAB]"), + snapbox::str!["pos_b"] ); - assert_data_eq!(complete!(cmd, "-F --json -a p[TAB]"), snapbox::str![""]); } fn complete(cmd: &mut Command, args: impl AsRef, current_dir: Option<&Path>) -> String { From fbec05e639f82635bd64fc7bd20d18796cd4b598 Mon Sep 17 00:00:00 2001 From: mart-mihkel Date: Tue, 3 Sep 2024 11:41:09 +0300 Subject: [PATCH 3/4] refactor(complete): Fix typo in hyhpen --- clap_complete/tests/testsuite/engine.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clap_complete/tests/testsuite/engine.rs b/clap_complete/tests/testsuite/engine.rs index 923335b10dd..d802bb148cf 100644 --- a/clap_complete/tests/testsuite/engine.rs +++ b/clap_complete/tests/testsuite/engine.rs @@ -982,7 +982,7 @@ a_pos,c_pos" } #[test] -fn suggest_allow_hyhpen() { +fn suggest_allow_hyphen() { let mut cmd = Command::new("exhaustive") .arg( clap::Arg::new("format") @@ -1028,7 +1028,7 @@ fn suggest_allow_hyhpen() { } #[test] -fn suggest_positional_long_allow_hyhpen() { +fn suggest_positional_long_allow_hyphen() { let mut cmd = Command::new("exhaustive") .arg( clap::Arg::new("format") @@ -1090,7 +1090,7 @@ pos_b" } #[test] -fn suggest_positional_short_allow_hyhpen() { +fn suggest_positional_short_allow_hyphen() { let mut cmd = Command::new("exhaustive") .arg( clap::Arg::new("format") From 57b6cb8e4781216e7c01397d2bd54b97028a5520 Mon Sep 17 00:00:00 2001 From: mart-mihkel Date: Tue, 3 Sep 2024 11:42:55 +0300 Subject: [PATCH 4/4] refactor(complete): Simplify engine::complete --- clap_complete/src/engine/complete.rs | 34 +++++++++++++++------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/clap_complete/src/engine/complete.rs b/clap_complete/src/engine/complete.rs index c78b3d62e8e..75dad1b2b14 100644 --- a/clap_complete/src/engine/complete.rs +++ b/clap_complete/src/engine/complete.rs @@ -58,14 +58,12 @@ pub fn complete( parse_positional(current_cmd, pos_index, is_escaped, current_state); } else if arg.is_escape() { is_escaped = true; - } else if let Some((flag, value)) = arg.to_long() { - if let ParseState::Opt((opt, count)) = current_state { - if opt.is_allow_hyphen_values_set() { - next_state = parse_opt(opt, count); - continue; - } + } else if opt_allows_hyphen(¤t_state, &arg) { + match current_state { + ParseState::Opt((opt, count)) => next_state = parse_opt_value(opt, count), + _ => unreachable!("else branch is only reachable in Opt state"), } - + } else if let Some((flag, value)) = arg.to_long() { if let Ok(flag) = flag { let opt = current_cmd.get_arguments().find(|a| { let longs = a.get_long_and_visible_aliases(); @@ -87,13 +85,6 @@ pub fn complete( } } } else if let Some(short) = arg.to_short() { - if let ParseState::Opt((opt, count)) = current_state { - if opt.is_allow_hyphen_values_set() { - next_state = parse_opt(opt, count); - continue; - } - } - let (_, takes_value_opt, mut short) = parse_shortflags(current_cmd, short); if let Some(opt) = takes_value_opt { if short.next_value_os().is_none() { @@ -109,7 +100,7 @@ pub fn complete( (next_state, pos_index) = parse_positional(current_cmd, pos_index, is_escaped, current_state); } - ParseState::Opt((opt, count)) => next_state = parse_opt(opt, count), + ParseState::Opt((opt, count)) => next_state = parse_opt_value(opt, count), } } } @@ -562,7 +553,7 @@ fn parse_positional<'a>( } /// Parse optional flag argument. Return new state -fn parse_opt(opt: &clap::Arg, count: usize) -> ParseState<'_> { +fn parse_opt_value(opt: &clap::Arg, count: usize) -> ParseState<'_> { let range = opt.get_num_args().expect("built"); let max = range.max_values(); if count < max { @@ -578,3 +569,14 @@ fn pos_allows_hyphen(cmd: &clap::Command, pos_index: usize) -> bool { .map(|p| p.is_allow_hyphen_values_set()) .unwrap_or(false) } + +fn opt_allows_hyphen(state: &ParseState<'_>, arg: &clap_lex::ParsedArg<'_>) -> bool { + let val = arg.to_value_os(); + if val.starts_with("-") { + if let ParseState::Opt((opt, _)) = state { + return opt.is_allow_hyphen_values_set(); + } + } + + false +}