diff --git a/docs/task-runner.md b/docs/task-runner.md index 2a93ea8..0c63bae 100644 --- a/docs/task-runner.md +++ b/docs/task-runner.md @@ -101,6 +101,17 @@ main() { } ``` +Another way is to use `@meta default-subcommand` + +```sh +# @cmd +# @meta default-subcommand +build() { :; } + +# @cmd +test() { :; } +``` + Remember, you can always use `--help` for detailed help information. ## Aliases diff --git a/src/build.rs b/src/build.rs index 253fac8..c329090 100644 --- a/src/build.rs +++ b/src/build.rs @@ -322,7 +322,7 @@ _argc_version{suffix}() {{ fn build_parse(cmd: &Command, suffix: &str) -> String { let mut parse_help = { - let help_flags = cmd.help_flags().join(" | "); + let help_flags = cmd.help_flags.join(" | "); format!( r#" {help_flags}) @@ -331,7 +331,7 @@ fn build_parse(cmd: &Command, suffix: &str) -> String { ) }; let parse_version = if cmd.exist_version() { - let version_flags = cmd.version_flags().join(" | "); + let version_flags = cmd.version_flags.join(" | "); format!( r#" {version_flags}) diff --git a/src/command/mod.rs b/src/command/mod.rs index f318df2..2a7d67e 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -7,8 +7,7 @@ use self::share_data::ShareData; use crate::argc_value::ArgcValue; use crate::matcher::Matcher; use crate::param::{ - EnvParam, EnvValue, FlagOptionParam, FlagOptionValue, Param, ParamData, PositionalParam, - PositionalValue, + EnvParam, EnvValue, FlagOptionParam, FlagOptionValue, Param, PositionalParam, PositionalValue, }; use crate::parser::{parse, parse_symbol, Event, EventData, EventScope, Position}; use crate::utils::{ @@ -48,6 +47,8 @@ pub(crate) struct Command { pub(crate) metadata: Vec<(String, String, Position)>, pub(crate) symbols: IndexMap, pub(crate) require_tools: IndexSet, + pub(crate) help_flags: Vec<&'static str>, + pub(crate) version_flags: Vec<&'static str>, } impl Command { @@ -415,130 +416,6 @@ impl Command { ) } - pub(crate) fn render_usage(&self) -> String { - let mut output = vec!["USAGE:".to_string()]; - output.extend(self.cmd_paths()); - let required_options: Vec = self - .flag_option_params - .iter() - .filter(|v| v.required()) - .map(|v| v.render_name_notations()) - .collect(); - if self.flag_option_params.len() != required_options.len() { - output.push("[OPTIONS]".to_string()); - } - output.extend(required_options); - if !self.subcommands.is_empty() { - output.push("".to_string()); - } else { - output.extend(self.positional_params.iter().map(|v| v.render_notation())); - } - output.join(" ") - } - - pub(crate) fn render_positionals(&self, term_width: Option) -> Vec { - let mut output = vec![]; - if self.positional_params.is_empty() { - return output; - } - let mut value_size = 0; - let list: Vec<_> = self - .positional_params - .iter() - .map(|param| { - let value = param.render_notation(); - value_size = value_size.max(value.len()); - (value, param.render_describe()) - }) - .collect(); - value_size += 2; - output.push("ARGS:".to_string()); - render_list(&mut output, list, value_size, term_width); - output - } - - pub(crate) fn render_flag_options(&self, term_width: Option) -> Vec { - let mut output = vec![]; - if self.flag_option_params.is_empty() { - return output; - } - let mut value_size = 0; - let list: Vec<_> = self - .all_flag_options() - .into_iter() - .map(|param| { - let value = param.render_body(); - let describe = param.render_describe(); - value_size = value_size.max(value.len()); - (value, describe) - }) - .collect(); - value_size += 2; - output.push("OPTIONS:".to_string()); - render_list(&mut output, list, value_size, term_width); - output - } - - pub(crate) fn render_subcommands(&self, term_width: Option) -> Vec { - let mut output = vec![]; - if self.subcommands.is_empty() { - return output; - } - let mut value_size = 0; - let list: Vec<_> = self - .subcommands - .iter() - .map(|subcmd| { - let value = subcmd.cmd_name(); - value_size = value_size.max(value.len()); - (value, subcmd.render_subcommand_describe()) - }) - .collect(); - value_size += 2; - output.push("COMMANDS:".to_string()); - render_list(&mut output, list, value_size, term_width); - output - } - - pub(crate) fn render_subcommand_describe(&self) -> String { - let mut output = self.describe_oneline().to_string(); - if let Some((aliases, _)) = &self.aliases { - if !output.is_empty() { - output.push(' ') - } - output.push_str(&format!("[aliases: {}]", aliases.join(", "))); - } - if self.has_metadata(META_DEFAULT_SUBCOMMAND) { - if !output.is_empty() { - output.push(' ') - } - output.push_str("[default]"); - } - output - } - - pub(crate) fn render_envs(&self, term_width: Option) -> Vec { - let mut output = vec![]; - if self.env_params.is_empty() { - return output; - } - let mut value_size = 0; - let list: Vec<_> = self - .env_params - .iter() - .map(|param| { - let value = param.render_body(); - value_size = value_size.max(value.len()); - (value, param.render_describe()) - }) - .collect(); - value_size += 2; - output.push("ENVIRONMENTS:".to_string()); - render_list(&mut output, list, value_size, term_width); - output.push("".to_string()); - output - } - pub(crate) fn describe_oneline(&self) -> &str { match self.describe.split_once('\n') { Some((v, _)) => v, @@ -609,34 +486,6 @@ impl Command { self.version.is_some() || self.is_root() } - pub(crate) fn help_flags(&self) -> Vec<&'static str> { - let mut output = vec!["--help", "-help"]; - let short = match self.find_flag_option("-h") { - Some(param) => param.id() == "help", - None => true, - }; - if short { - output.push("-h"); - } - output - } - - pub(crate) fn version_flags(&self) -> Vec<&'static str> { - let mut output = vec![]; - if self.exist_version() { - output.push("--version"); - output.push("-version"); - let short = match self.find_flag_option("-V") { - Some(param) => param.id() == "version", - None => true, - }; - if short { - output.push("-V"); - } - } - output - } - pub(crate) fn delegated(&self) -> bool { self.subcommands.is_empty() && self.flag_option_params.is_empty() @@ -669,6 +518,33 @@ impl Command { } } + self.help_flags = { + let mut flags = vec!["--help", "-help"]; + let short = match self.find_flag_option("-h") { + Some(param) => param.id() == "help", + None => true, + }; + if short { + flags.push("-h"); + } + flags + }; + self.version_flags = { + let mut flags = vec![]; + if self.exist_version() { + flags.push("--version"); + flags.push("-version"); + let short = match self.find_flag_option("-V") { + Some(param) => param.id() == "version", + None => true, + }; + if short { + flags.push("-V"); + } + } + flags + }; + // update derived_flag_option_params let mut describe = false; let mut single = false; @@ -779,14 +655,10 @@ impl Command { } else { None }; - let mut param_data = ParamData::new("help"); - param_data.describe = describe.to_string(); - Some(FlagOptionParam::new( - param_data, - true, + Some(FlagOptionParam::create_help_flag( short, long_prefix, - &[], + describe, )) } @@ -803,16 +675,157 @@ impl Command { } else { None }; - let mut param_data = ParamData::new("version"); - param_data.describe = describe.to_string(); - Some(FlagOptionParam::new( - param_data, - true, + Some(FlagOptionParam::create_version_flag( short, long_prefix, - &[], + describe, )) } + + fn render_usage(&self) -> String { + let mut output = vec!["USAGE:".to_string()]; + output.extend(self.cmd_paths()); + let required_options: Vec = self + .flag_option_params + .iter() + .filter(|v| v.required()) + .map(|v| v.render_name_notations()) + .collect(); + if self.flag_option_params.len() != required_options.len() { + output.push("[OPTIONS]".to_string()); + } + output.extend(required_options); + if !self.subcommands.is_empty() { + output.push("".to_string()); + } else { + output.extend(self.positional_params.iter().map(|v| v.render_notation())); + } + output.join(" ") + } + + fn render_flag_options(&self, term_width: Option) -> Vec { + let mut output = vec![]; + let default_subcmd = self.find_default_subcommand(); + if self.flag_option_params.is_empty() + && default_subcmd + .map(|subcmd| subcmd.flag_option_params.is_empty()) + .unwrap_or(true) + { + return output; + } + + let params = match default_subcmd { + Some(subcmd) => [self.all_flag_options(), subcmd.all_flag_options()].concat(), + None => self.all_flag_options(), + }; + + let mut value_size = 0; + let list: IndexMap = params + .into_iter() + .map(|param| { + let value = param.render_body(); + let describe = param.render_describe(); + value_size = value_size.max(value.len()); + (value, describe) + }) + .collect(); + value_size += 2; + output.push("OPTIONS:".to_string()); + render_list( + &mut output, + list.into_iter().collect(), + value_size, + term_width, + ); + output + } + + fn render_positionals(&self, term_width: Option) -> Vec { + let mut output = vec![]; + let params = match self.find_default_subcommand() { + Some(subcmd) => &subcmd.positional_params, + None => &self.positional_params, + }; + if params.is_empty() { + return output; + } + let mut value_size = 0; + let list: Vec<_> = params + .iter() + .map(|param| { + let value = param.render_notation(); + value_size = value_size.max(value.len()); + (value, param.render_describe()) + }) + .collect(); + value_size += 2; + output.push("ARGS:".to_string()); + render_list(&mut output, list, value_size, term_width); + output + } + + fn render_envs(&self, term_width: Option) -> Vec { + let mut output = vec![]; + let params = match self.find_default_subcommand() { + Some(subcmd) => &subcmd.env_params, + None => &self.env_params, + }; + if params.is_empty() { + return output; + } + let mut value_size = 0; + let list: Vec<_> = params + .iter() + .map(|param| { + let value = param.render_body(); + value_size = value_size.max(value.len()); + (value, param.render_describe()) + }) + .collect(); + value_size += 2; + output.push("ENVIRONMENTS:".to_string()); + render_list(&mut output, list, value_size, term_width); + output.push("".to_string()); + output + } + + fn render_subcommands(&self, term_width: Option) -> Vec { + let mut output = vec![]; + if self.subcommands.is_empty() { + return output; + } + let mut value_size = 0; + let list: Vec<_> = self + .subcommands + .iter() + .map(|subcmd| { + let value = subcmd.cmd_name(); + value_size = value_size.max(value.len()); + (value, subcmd.render_subcommand_describe()) + }) + .collect(); + value_size += 2; + output.push("COMMANDS:".to_string()); + render_list(&mut output, list, value_size, term_width); + output + } + + fn render_subcommand_describe(&self) -> String { + let mut output = self.describe_oneline().to_string(); + if let Some((aliases, _)) = &self.aliases { + if !output.is_empty() { + output.push(' ') + } + output.push_str(&format!("[aliases: {}]", aliases.join(", "))); + } + if self.has_metadata(META_DEFAULT_SUBCOMMAND) { + if !output.is_empty() { + output.push(' ') + } + output.push_str("[default]"); + } + output + } } #[derive(Debug, Serialize)] diff --git a/src/matcher.rs b/src/matcher.rs index 91741a5..d947a1b 100644 --- a/src/matcher.rs +++ b/src/matcher.rs @@ -120,8 +120,8 @@ impl<'a, 'b> Matcher<'a, 'b> { } } else if is_rest_args_positional || (cmd.is_empty_flags_options_subcommands() - && !cmd.help_flags().contains(&arg) - && !cmd.version_flags().contains(&arg)) + && !cmd.help_flags.contains(&arg) + && !cmd.version_flags.contains(&arg)) { add_positional_arg( &mut positional_args, @@ -146,6 +146,23 @@ impl<'a, 'b> Matcher<'a, 'b> { flag_option_args[cmd_level].push((k, values, Some(param.id()))); comp_option = Some(param.id()); } else { + if positional_args.is_empty() + && !cmd.help_flags.contains(&k) + && !cmd.version_flags.contains(&k) + { + if let Some(subcmd) = cmd.find_default_subcommand() { + match_command( + &mut cmds, + &mut cmd_level, + &mut cmd_arg_indexes, + &mut flag_option_args, + subcmd, + arg_index, + &mut is_rest_args_positional, + ); + continue; + } + } flag_option_args[cmd_level].push((k, vec![v], None)); } } else if let Some(param) = cmd.find_flag_option(arg) { @@ -228,6 +245,23 @@ impl<'a, 'b> Matcher<'a, 'b> { split_last_arg_at = Some(1); } } else { + if positional_args.is_empty() + && !cmd.help_flags.contains(&arg) + && !cmd.version_flags.contains(&arg) + { + if let Some(subcmd) = cmd.find_default_subcommand() { + match_command( + &mut cmds, + &mut cmd_level, + &mut cmd_arg_indexes, + &mut flag_option_args, + subcmd, + arg_index, + &mut is_rest_args_positional, + ); + continue; + } + } flag_option_args[cmd_level].push((arg, vec![], None)); } } else if let Some(subcmd) = @@ -287,7 +321,23 @@ impl<'a, 'b> Matcher<'a, 'b> { arg_comp = ArgComp::CommandOrPositional; } - let last_cmd = *cmds.last().unwrap(); + let last_cmd = cmds[cmd_level]; + if positional_args.is_empty() && flag_option_args[cmd_level].is_empty() { + if let Some(subcmd) = last_cmd.find_default_subcommand() { + if subcmd.command_fn.is_some() { + match_command( + &mut cmds, + &mut cmd_level, + &mut cmd_arg_indexes, + &mut flag_option_args, + subcmd, + arg_index - 1, + &mut is_rest_args_positional, + ); + } + } + } + for param in &last_cmd.positional_params { add_param_choice_fn(&mut choice_fns, param) } diff --git a/src/param.rs b/src/param.rs index 60eb22c..a1ba865 100644 --- a/src/param.rs +++ b/src/param.rs @@ -430,6 +430,22 @@ impl FlagOptionParam { self.inherited = true; } + pub(crate) fn create_help_flag(short: Option<&str>, long_prefix: &str, describe: &str) -> Self { + let mut param_data = ParamData::new("help"); + param_data.describe = describe.to_string(); + FlagOptionParam::new(param_data, true, short, long_prefix, &[]) + } + + pub(crate) fn create_version_flag( + short: Option<&str>, + long_prefix: &str, + describe: &str, + ) -> Self { + let mut param_data = ParamData::new("version"); + param_data.describe = describe.to_string(); + FlagOptionParam::new(param_data, true, short, long_prefix, &[]) + } + pub(crate) fn match_prefix<'a>(&self, arg: &'a str) -> Option<&'a str> { if self.prefixed { self.list_names().iter().find_map(|v| { diff --git a/tests/snapshots/integration__spec__default_subcommand.snap b/tests/snapshots/integration__spec__default_subcommand.snap index c583a29..5162b7d 100644 --- a/tests/snapshots/integration__spec__default_subcommand.snap +++ b/tests/snapshots/integration__spec__default_subcommand.snap @@ -9,6 +9,14 @@ prog -h command cat >&2 <<-'EOF' USAGE: prog +ARGS: + [VAL] + +OPTIONS: + -h, --help + -V, --version + --fa + COMMANDS: cmda [default] cmdb @@ -19,14 +27,67 @@ exit 0 # RUN_OUTPUT USAGE: prog +ARGS: + [VAL] + +OPTIONS: + -h, --help + -V, --version + --fa + COMMANDS: cmda [default] cmdb +************ RUN ************ +prog --fa + +# OUTPUT +argc_fa=1 +argc__args=( prog --fa ) +argc__fn=cmda +argc__positionals=( ) +cmda + +# RUN_OUTPUT +argc__args=([0]="prog" [1]="--fa") +argc__fn=cmda +argc__positionals=() +argc_fa=1 +cmda + +************ RUN ************ +prog --fa -h + +# OUTPUT +command cat >&2 <<-'EOF' +USAGE: prog cmda [OPTIONS] [VAL] + +ARGS: + [VAL] + +OPTIONS: + --fa + -h, --help + +EOF +exit 0 + +# RUN_OUTPUT +USAGE: prog cmda [OPTIONS] [VAL] + +ARGS: + [VAL] + +OPTIONS: + --fa + -h, --help + ************ RUN ************ prog v1 # OUTPUT +argc_val=v1 argc__args=( prog v1 ) argc__fn=cmda argc__positionals=( v1 ) @@ -36,12 +97,14 @@ cmda v1 argc__args=([0]="prog" [1]="v1") argc__fn=cmda argc__positionals=([0]="v1") +argc_val=v1 cmda v1 ************ RUN ************ prog cmda v1 # OUTPUT +argc_val=v1 argc__args=( prog cmda v1 ) argc__fn=cmda argc__positionals=( v1 ) @@ -51,6 +114,7 @@ cmda v1 argc__args=([0]="prog" [1]="cmda" [2]="v1") argc__fn=cmda argc__positionals=([0]="v1") +argc_val=v1 cmda v1 ************ RUN ************ @@ -67,5 +131,3 @@ argc__args=([0]="prog" [1]="cmdb" [2]="v1") argc__fn=cmdb argc__positionals=([0]="v1") cmdb v1 - - diff --git a/tests/spec.rs b/tests/spec.rs index 3455e96..488abcc 100644 --- a/tests/spec.rs +++ b/tests/spec.rs @@ -371,6 +371,8 @@ fn default_subcommand() { let script = r###" # @cmd # @meta default-subcommand +# @flag --fa +# @arg val cmda() { :; } # @cmd @@ -380,6 +382,8 @@ cmdb() { :; } script, [ vec!["prog", "-h"], + vec!["prog", "--fa"], + vec!["prog", "--fa", "-h"], vec!["prog", "v1"], vec!["prog", "cmda", "v1"], vec!["prog", "cmdb", "v1"],