From 9414c7694c49b294d22fe90da825e85316f8c4b9 Mon Sep 17 00:00:00 2001 From: sigoden Date: Thu, 20 Jul 2023 13:43:10 +0800 Subject: [PATCH] feat: compgen delegate (#211) --- src/command/mod.rs | 11 + src/compgen.rs | 25 ++- src/matcher.rs | 194 ++++++++++-------- tests/compgen.rs | 17 ++ .../integration__compgen__delegated.snap | 14 ++ 5 files changed, 166 insertions(+), 95 deletions(-) create mode 100644 tests/snapshots/integration__compgen__delegated.snap diff --git a/src/command/mod.rs b/src/command/mod.rs index 18357d83..4e594a1b 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -496,6 +496,17 @@ impl Command { } } + pub(crate) fn delegated(&self) -> bool { + self.subcommands.is_empty() + && self.flag_option_params.is_empty() + && self.positional_params.len() == 1 + && self + .positional_params + .first() + .map(|v| v.terminated) + .unwrap_or_default() + } + pub(crate) fn exist_main_fn(&self, cmd_paths: &[&str]) -> bool { self.get_cmd_fn(cmd_paths) .map(|v| v.ends_with("main")) diff --git a/src/compgen.rs b/src/compgen.rs index aadf4cc6..9e51b427 100644 --- a/src/compgen.rs +++ b/src/compgen.rs @@ -30,18 +30,21 @@ pub fn compgen( (last_arg.to_string(), None) } }; - let new_args: Vec = args - .iter() - .enumerate() - .map(|(i, v)| { - if i == args.len() - 1 { - last.clone() - } else { - v.to_string() - } - }) - .collect(); let cmd = Command::new(script_content)?; + let new_args: Vec = if cmd.delegated() { + args.to_vec() + } else { + args.iter() + .enumerate() + .map(|(i, v)| { + if i == args.len() - 1 { + last.clone() + } else { + v.to_string() + } + }) + .collect() + }; let matcher = Matcher::new(&cmd, &new_args); let compgen_values = matcher.compgen(shell); let mut default_nospace = unbalance.is_some(); diff --git a/src/matcher.rs b/src/matcher.rs index 5c725b24..001bba8c 100644 --- a/src/matcher.rs +++ b/src/matcher.rs @@ -69,112 +69,131 @@ impl<'a, 'b> Matcher<'a, 'b> { let mut flag_option_args = vec![vec![]]; let mut positional_args = vec![]; let mut dashes = vec![]; - let mut is_rest_args_positional = false; let mut is_last_arg_option_assign = false; let mut arg_comp = ArgComp::Any; let mut choices_fns = HashSet::new(); let args_len = args.len(); - if let Some(arg) = args.last() { - if arg.starts_with('-') { - arg_comp = ArgComp::FlagOrOption; - } else if !arg.is_empty() { - arg_comp = ArgComp::CommandOrPositional; + if root.delegated() { + positional_args = args.iter().skip(1).map(|v| v.as_str()).collect(); + } else { + let mut is_rest_args_positional = false; + if let Some(arg) = args.last() { + if arg.starts_with('-') { + arg_comp = ArgComp::FlagOrOption; + } else if !arg.is_empty() { + arg_comp = ArgComp::CommandOrPositional; + } } - } - while arg_index < args_len { - let cmd = cmds[cmd_level].1; - let arg = args[arg_index].as_str(); - if arg == "--" { - dashes.push(positional_args.len()); - if is_rest_args_positional { + while arg_index < args_len { + let cmd = cmds[cmd_level].1; + let arg = args[arg_index].as_str(); + if arg == "--" { + dashes.push(positional_args.len()); + if is_rest_args_positional { + add_positional_arg( + &mut positional_args, + arg, + &mut is_rest_args_positional, + cmd, + ); + } + } else if is_rest_args_positional + || !dashes.is_empty() + || (cmd.no_flags_options_subcommands() && !KNOWN_OPTIONS.contains(&arg)) + { add_positional_arg( &mut positional_args, arg, &mut is_rest_args_positional, cmd, ); - } - } else if is_rest_args_positional - || !dashes.is_empty() - || (cmd.no_flags_options_subcommands() && !KNOWN_OPTIONS.contains(&arg)) - { - add_positional_arg(&mut positional_args, arg, &mut is_rest_args_positional, cmd); - } else if arg.starts_with('-') { - if let Some((k, v)) = arg.split_once('=') { - let param = cmd.find_flag_option(k); - if arg_index == args_len - 1 { - if let Some(param) = param { - arg_comp = ArgComp::OptionValue(param.name.clone(), 0) + } else if arg.starts_with('-') { + if let Some((k, v)) = arg.split_once('=') { + let param = cmd.find_flag_option(k); + if arg_index == args_len - 1 { + if let Some(param) = param { + arg_comp = ArgComp::OptionValue(param.name.clone(), 0) + } + is_last_arg_option_assign = true; } - is_last_arg_option_assign = true; - } - if let Some((choices_fn, validate)) = param.and_then(|v| v.choices_fn.as_ref()) - { - if *validate { - choices_fns.insert(choices_fn.as_str()); + if let Some((choices_fn, validate)) = + param.and_then(|v| v.choices_fn.as_ref()) + { + if *validate { + choices_fns.insert(choices_fn.as_str()); + } } - } - flag_option_args[cmd_level].push((k, vec![v], param.map(|v| v.name.as_str()))); - } else if let Some(param) = cmd.find_flag_option(arg) { - if let Some((choices_fn, validate)) = param.choices_fn.as_ref() { - if *validate { - choices_fns.insert(choices_fn.as_str()); + flag_option_args[cmd_level].push(( + k, + vec![v], + param.map(|v| v.name.as_str()), + )); + } else if let Some(param) = cmd.find_flag_option(arg) { + if let Some((choices_fn, validate)) = param.choices_fn.as_ref() { + if *validate { + choices_fns.insert(choices_fn.as_str()); + } } - } - match_flag_option( - &mut flag_option_args[cmd_level], - args, - &mut arg_index, - param, - &mut arg_comp, - ); - } else if let Some(mut list) = match_combine_shorts(cmd, arg) { - let name = list.pop().and_then(|v| v.2).unwrap(); - let param = cmd.find_flag_option(name).unwrap(); - if let Some((choices_fn, validate)) = param.choices_fn.as_ref() { - if *validate { - choices_fns.insert(choices_fn.as_str()); + match_flag_option( + &mut flag_option_args[cmd_level], + args, + &mut arg_index, + param, + &mut arg_comp, + ); + } else if let Some(mut list) = match_combine_shorts(cmd, arg) { + let name = list.pop().and_then(|v| v.2).unwrap(); + let param = cmd.find_flag_option(name).unwrap(); + if let Some((choices_fn, validate)) = param.choices_fn.as_ref() { + if *validate { + choices_fns.insert(choices_fn.as_str()); + } } + flag_option_args[cmd_level].extend(list); + match_flag_option( + &mut flag_option_args[cmd_level], + args, + &mut arg_index, + param, + &mut arg_comp, + ); + } else { + flag_option_args[cmd_level].push((arg, vec![], None)); } - flag_option_args[cmd_level].extend(list); - match_flag_option( - &mut flag_option_args[cmd_level], - args, - &mut arg_index, - param, - &mut arg_comp, - ); + } else if let Some(subcmd) = cmd.find_subcommand(arg) { + cmd_level += 1; + cmds.push(( + arg, + subcmd, + subcmd.name.clone().unwrap_or_else(|| arg.to_string()), + arg_index, + )); + flag_option_args.push(vec![]); } else { - flag_option_args[cmd_level].push((arg, vec![], None)); + add_positional_arg( + &mut positional_args, + arg, + &mut is_rest_args_positional, + cmd, + ); } - } else if let Some(subcmd) = cmd.find_subcommand(arg) { - cmd_level += 1; - cmds.push(( - arg, - subcmd, - subcmd.name.clone().unwrap_or_else(|| arg.to_string()), - arg_index, - )); - flag_option_args.push(vec![]); - } else { - add_positional_arg(&mut positional_args, arg, &mut is_rest_args_positional, cmd); + arg_index += 1; } - arg_index += 1; - } - if is_rest_args_positional { - arg_comp = ArgComp::CommandOrPositional; - } + if is_rest_args_positional { + arg_comp = ArgComp::CommandOrPositional; + } - let last_cmd = cmds.last().unwrap().1; - choices_fns.extend(last_cmd.positional_params.iter().filter_map(|v| { - if let Some((choices_fn, validate)) = v.choices_fn.as_ref() { - if *validate { - return Some(choices_fn.as_str()); + let last_cmd = cmds.last().unwrap().1; + choices_fns.extend(last_cmd.positional_params.iter().filter_map(|v| { + if let Some((choices_fn, validate)) = v.choices_fn.as_ref() { + if *validate { + return Some(choices_fn.as_str()); + } } - } - None - })); + None + })); + } Self { cmds, args, @@ -236,6 +255,13 @@ impl<'a, 'b> Matcher<'a, 'b> { } let level = self.cmds.len() - 1; let mut last_cmd = self.cmds[level].1; + if last_cmd.delegated() { + return last_cmd + .positional_params + .first() + .map(comp_positional) + .unwrap_or_default(); + } let mut output = match &self.arg_comp { ArgComp::FlagOrOption => { let mut output = self.comp_flag_options(); diff --git a/tests/compgen.rs b/tests/compgen.rs index 53c6dcec..047de030 100644 --- a/tests/compgen.rs +++ b/tests/compgen.rs @@ -590,6 +590,23 @@ fn redirect_symbols() { snapshot_compgen!(script, vec![vec!["prog", ">", "Argc"]], argc::Shell::Bash); } +#[test] +fn delegated() { + let script = r###" +# @arg args~[`_choice_delegate`] + +_choice_delegate() { + echo $1 +} +"###; + + snapshot_compgen!( + script, + vec![vec!["prog", "abc"], vec!["prog", "-a"], vec!["prog", "-"]], + argc::Shell::Bash + ); +} + #[test] fn mult_char() { let script = r###" diff --git a/tests/snapshots/integration__compgen__delegated.snap b/tests/snapshots/integration__compgen__delegated.snap new file mode 100644 index 00000000..f4cc798e --- /dev/null +++ b/tests/snapshots/integration__compgen__delegated.snap @@ -0,0 +1,14 @@ +--- +source: tests/compgen.rs +expression: data +--- +************ COMPGEN `prog abc` ************ +abc + +************ COMPGEN `prog -a` ************ +-a + +************ COMPGEN `prog -` ************ +- + +