Skip to content

Commit

Permalink
Implement default subcommand.
Browse files Browse the repository at this point in the history
Added a new optional attribute field to `argh::subcommand(default = "xxx")`.

This command allows user to specify a default subcommand for a higher level subcommand.
  • Loading branch information
qhua948 committed Jun 14, 2022
1 parent 60b4980 commit 1e52d3b
Show file tree
Hide file tree
Showing 5 changed files with 506 additions and 29 deletions.
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,44 @@ struct SubCommandTwo {
}
```

Is it possible to specify a optional subcommand, just like specifing
a default for an optional argument.

```rust
use argh::FromArgs;

#[derive(FromArgs, PartialEq, Debug)]
/// Top-level command.
struct TopLevel {
#[argh(subcommand, default = "one")]
nested: Option<MySubCommandEnum>,
}

#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
enum MySubCommandEnum {
One(SubCommandOne),
Two(SubCommandTwo),
}

#[derive(FromArgs, PartialEq, Debug)]
/// First subcommand.
#[argh(subcommand, name = "one")]
struct SubCommandOne {
#[argh(option)]
/// how many x
x: usize,
}

#[derive(FromArgs, PartialEq, Debug)]
/// Second subcommand.
#[argh(subcommand, name = "two")]
struct SubCommandTwo {
#[argh(switch)]
/// whether to fooey
fooey: bool,
}
```


NOTE: This is not an officially supported Google product.
62 changes: 53 additions & 9 deletions argh/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,16 @@ pub fn parse_struct_args(
let mut remaining_args = args;
let mut positional_index = 0;
let mut options_ended = false;
let (has_default_subcommand_parse, subcmd_str) = match parse_subcommand {
Some(ref subcmd) => {
if let Some(subcmd_str) = subcmd.default {
(true, subcmd_str)
} else {
(false, "")
}
}
_ => (false, ""),
};

'parse_args: while let Some(&next_arg) = remaining_args.get(0) {
remaining_args = &remaining_args[1..];
Expand All @@ -698,8 +708,16 @@ pub fn parse_struct_args(
return Err("Trailing arguments are not allowed after `help`.".to_string().into());
}

parse_options.parse(next_arg, &mut remaining_args)?;
continue;
// Give a chance to default subcommand reparsing.
let parse_result = parse_options.parse(next_arg, &mut remaining_args);
if parse_result.is_err() {
if has_default_subcommand_parse {
break 'parse_args;
}
parse_result?
} else {
continue;
}
}

if let Some(ref mut parse_subcommand) = parse_subcommand {
Expand All @@ -710,11 +728,38 @@ pub fn parse_struct_args(
}
}

parse_positionals.parse(&mut positional_index, next_arg)?;
let parse_result = parse_positionals.parse(&mut positional_index, next_arg);
if parse_result.is_err() {
if has_default_subcommand_parse {
break 'parse_args;
}
parse_result?
} else {
continue;
}
}

if help {
Err(EarlyExit { output: help_func(), status: Ok(()) })
// If there is also a subcommand, we'd like to print the help for that subcommand as well.
if has_default_subcommand_parse {
let sub_parse = (&mut parse_subcommand.unwrap().parse_func)(
&[cmd_name, &[subcmd_str]].concat(),
args,
)
.expect_err("bad parse func");
Err(EarlyExit { output: help_func() + &"\n" + &sub_parse.output, status: Ok(()) })
} else {
Err(EarlyExit { output: help_func(), status: Ok(()) })
}
} else if let Some(ref mut parse_subcommand) = parse_subcommand {
// If we have a do have a potential subcommand and it not parsed.
if let Some(default_subcommand) = parse_subcommand.default {
// Pass the args again to the default args.
// TODO(qhua948, #130): make this better, pass args without re-parsing.
parse_subcommand.parse(help, cmd_name, default_subcommand, args).map(|_| ())
} else {
Ok(())
}
} else {
Ok(())
}
Expand Down Expand Up @@ -743,7 +788,7 @@ impl<'a> ParseStructOptions<'a> {
.arg_to_slot
.iter()
.find_map(|&(name, pos)| if name == arg { Some(pos) } else { None })
.ok_or_else(|| unrecognized_argument(arg))?;
.ok_or_else(|| unrecognized_arg(arg))?;

match self.slots[pos] {
ParseStructOption::Flag(ref mut b) => b.set_flag(arg),
Expand All @@ -763,10 +808,6 @@ impl<'a> ParseStructOptions<'a> {
}
}

fn unrecognized_argument(x: &str) -> String {
["Unrecognized argument: ", x, "\n"].concat()
}

// `--` or `-` options, including a mutable reference to their value.
#[doc(hidden)]
pub enum ParseStructOption<'a> {
Expand Down Expand Up @@ -847,6 +888,9 @@ pub struct ParseStructSubCommand<'a> {

// The function to parse the subcommand arguments.
pub parse_func: &'a mut dyn FnMut(&[&str], &[&str]) -> Result<(), EarlyExit>,

// The default subcommand as a literal string, which should matches one of the subcommand's "name" attribute.
pub default: Option<&'static str>,
}

impl<'a> ParseStructSubCommand<'a> {
Expand Down
Loading

0 comments on commit 1e52d3b

Please sign in to comment.