diff --git a/clap_generate/src/generators/shells/zsh.rs b/clap_generate/src/generators/shells/zsh.rs index e32303d6714..54ff712ce82 100644 --- a/clap_generate/src/generators/shells/zsh.rs +++ b/clap_generate/src/generators/shells/zsh.rs @@ -119,7 +119,8 @@ _{bin_name_underscore}_commands() {{ }}", bin_name_underscore = bin_name.replace(" ", "__"), bin_name = bin_name, - subcommands_and_args = subcommands_of(parser_of(p, bin_name)) + subcommands_and_args = + subcommands_of(parser_of(p, bin_name).expect(INTERNAL_ERROR_MSG)) )); } @@ -217,14 +218,20 @@ fn get_subcommands_of(p: &App) -> String { let mut subcmds = vec![]; for &(ref name, ref bin_name) in &sc_names { + debug!( + "get_subcommands_of:iter: p={}, name={}, bin_name={}", + p.get_name(), + name, + bin_name, + ); let mut v = vec![format!("({})", name)]; - let subcommand_args = get_args_of(parser_of(p, &*bin_name)); + let subcommand_args = get_args_of(parser_of(p, &*bin_name).expect(INTERNAL_ERROR_MSG)); if !subcommand_args.is_empty() { v.push(subcommand_args); } - let subcommands = get_subcommands_of(parser_of(p, &*bin_name)); + let subcommands = get_subcommands_of(parser_of(p, &*bin_name).expect(INTERNAL_ERROR_MSG)); if !subcommands.is_empty() { v.push(subcommands); @@ -252,15 +259,24 @@ esac", ) } -fn parser_of<'help, 'app>(p: &'app App<'help>, mut sc: &str) -> &'app App<'help> { - debug!("parser_of: sc={}", sc); +// Get the App for a given subcommand tree. +// +// Given the bin_name "a b c" and the App for "a" this returns the "c" App. +// Given the bin_name "a b c" and the App for "b" this returns the "c" App. +fn parser_of<'help, 'app>(p: &'app App<'help>, bin_name: &str) -> Option<&'app App<'help>> { + debug!("parser_of: p={}, bin_name={}", p.get_name(), bin_name); + + if bin_name == p.get_bin_name().unwrap_or(&String::new()) { + return Some(p); + } - if sc == p.get_bin_name().unwrap_or(&String::new()) { - return p; + for sc in p.get_subcommands() { + if let Some(ret) = parser_of(sc, bin_name) { + return Some(ret); + } } - sc = sc.split(' ').last().unwrap(); - p.find_subcommand(sc).expect(INTERNAL_ERROR_MSG) + None } // Writes out the args section, which ends up being the flags, opts and positionals, and a jump to diff --git a/clap_generate/tests/completions.rs b/clap_generate/tests/completions.rs index 1aa76b31d42..c26ecd1a03f 100644 --- a/clap_generate/tests/completions.rs +++ b/clap_generate/tests/completions.rs @@ -712,6 +712,108 @@ _my_app_commands() { _my_app "$@""#; +static ZSH_NESTED_SUBCOMMANDS: &str = r#"#compdef my_app + +autoload -U is-at-least + +_my_app() { + typeset -A opt_args + typeset -a _arguments_options + local ret=1 + + if is-at-least 5.2; then + _arguments_options=(-s -S -C) + else + _arguments_options=(-s -C) + fi + + local context curcontext="$curcontext" state line + _arguments "${_arguments_options[@]}" \ +'-h[Prints help information]' \ +'--help[Prints help information]' \ +'-V[Prints version information]' \ +'--version[Prints version information]' \ +":: :_my_app_commands" \ +"*::: :->first" \ +&& ret=0 + case $state in + (first) + words=($line[1] "${words[@]}") + (( CURRENT += 1 )) + curcontext="${curcontext%:*:*}:my_app-command-$line[1]:" + case $line[1] in + (second) +_arguments "${_arguments_options[@]}" \ +'-h[Prints help information]' \ +'--help[Prints help information]' \ +'-V[Prints version information]' \ +'--version[Prints version information]' \ +":: :_my_app__second_commands" \ +"*::: :->second" \ +&& ret=0 +case $state in + (second) + words=($line[1] "${words[@]}") + (( CURRENT += 1 )) + curcontext="${curcontext%:*:*}:my_app-second-command-$line[1]:" + case $line[1] in + (third) +_arguments "${_arguments_options[@]}" \ +'-h[Prints help information]' \ +'--help[Prints help information]' \ +'-V[Prints version information]' \ +'--version[Prints version information]' \ +&& ret=0 +;; + esac + ;; +esac +;; +(help) +_arguments "${_arguments_options[@]}" \ +'-h[Prints help information]' \ +'--help[Prints help information]' \ +'-V[Prints version information]' \ +'--version[Prints version information]' \ +&& ret=0 +;; + esac + ;; +esac +} + +(( $+functions[_my_app_commands] )) || +_my_app_commands() { + local commands; commands=( + "second:" \ +"help:Prints this message or the help of the given subcommand(s)" \ + ) + _describe -t commands 'my_app commands' commands "$@" +} +(( $+functions[_my_app__help_commands] )) || +_my_app__help_commands() { + local commands; commands=( + + ) + _describe -t commands 'my_app help commands' commands "$@" +} +(( $+functions[_my_app__second_commands] )) || +_my_app__second_commands() { + local commands; commands=( + "third:" \ + ) + _describe -t commands 'my_app second commands' commands "$@" +} +(( $+functions[_my_app__second__third_commands] )) || +_my_app__second__third_commands() { + local commands; commands=( + + ) + _describe -t commands 'my_app second third commands' commands "$@" +} + +_my_app "$@""#; + fn build_app() -> App<'static> { build_app_with_name("myapp") } @@ -777,6 +879,10 @@ fn build_app_special_help() -> App<'static> { ) } +fn build_app_nested_subcommands() -> App<'static> { + App::new("first").subcommand(App::new("second").subcommand(App::new("third"))) +} + pub fn common(app: &mut App, name: &str, fixture: &str) { let mut buf = vec![]; generate::(app, name, &mut buf); @@ -856,3 +962,9 @@ fn zsh_with_special_help() { let mut app = build_app_special_help(); common::(&mut app, "my_app", ZSH_SPECIAL_HELP); } + +#[test] +fn zsh_with_nested_subcommands() { + let mut app = build_app_nested_subcommands(); + common::(&mut app, "my_app", ZSH_NESTED_SUBCOMMANDS); +} diff --git a/src/build/app/mod.rs b/src/build/app/mod.rs index 0216a0e80ac..03ee06ff17d 100644 --- a/src/build/app/mod.rs +++ b/src/build/app/mod.rs @@ -263,6 +263,8 @@ impl<'help> App<'help> { } /// Find subcommand such that its name or one of aliases equals `name`. + /// + /// This does not recurse through subcommands of subcommands. #[inline] pub fn find_subcommand(&self, name: &T) -> Option<&App<'help>> where