-
Notifications
You must be signed in to change notification settings - Fork 88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement default subcommand. #129
base: master
Are you sure you want to change the base?
Conversation
For reference, we need this in https://fuchsia-review.googlesource.com/c/fuchsia/+/688982 We would like to support the following:
So when calling |
53ed645
to
1e52d3b
Compare
argh/tests/lib.rs
Outdated
Usage: cmdname one --x <x> | ||
|
||
First subcommand. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to not print these two lines? The goal is to instruct the user of cmdname
that they can use the commands one
and two
but that by default the command one
would be called and immediately surface the options of that command. I think these two lines make this a bit confusing. Could we instead do something like:
Top-level command.
Options:
--help display usage information
Commands:
one First subcommand.
two Second subcommand.
When no command is given, the commad `one` is the default. Allowing the additional:
Options:
--x how many x
--help display usage information
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a bit harder to achieve given the state of the code, I can add the line of
When no command is given, the commad
one is the default. Allowing the additional
And do this in a later patch if that is okay with you
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did implement this in the end but it took a lot more code than what I'd like to do it.
4d50cb1
to
d4f6952
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I'm not opposed to this functionality, could you describe more the need for this? Argh currently doesn't require a subcommand to be specified. You can do something like this right now:
use argh::FromArgs;
/// Top Level
#[derive(FromArgs, PartialEq, Debug)]
struct TopLevel {
#[argh(subcommand)]
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,
}
fn main() {
let a: TopLevel = argh::from_env();
println!("{:?}", a);
}
The main downside is that it won't handle subcommand flags. However there are two ways you could handle this case. First, you could copy the arguments into TopLevel
with:
struct TopLevel {
#[argh(option)]
/// how many x
x: usize,
#[argh(subcommand)]
nested: Option<MySubCommandEnum>,
}
but this has the downside that you can specify these flags even when we're calling the two
subcommand. The other way is that you can capture the unknown arguments with:
struct TopLevel {
#[argh(positional)]
/// unknown arguments
args: Vec<String>,
#[argh(subcommand)]
nested: Option<MySubCommandEnum>,
}
argh will first try to populate the subcommands before handling positional arguments, so if nested
was None
, you could then re-parse the args into whatever default subcommand you want. The downside with this is that the type system doesn't guarantee that you can't get a args
values with a nested
value being specified at the same time.
README.md
Outdated
#[derive(FromArgs, PartialEq, Debug)] | ||
/// Top-level command. | ||
struct TopLevel { | ||
#[argh(subcommand, default = "one")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would using the string name of the subcommand be brittle? Since we wouldn't get an error if the variant was renamed. Instead, did you consider either:
- Call a method to create the default subcommand
- Copy the pattern of
from_str_fn
and have something like#[argh(subcommand, default(MySubCommandEnum::One)]
. - Adding a
#[argh(default)]
on the subcommand variant, with something like this:
#[derive(FromArgs)]
struct TopLevel {
#[argh(subcommand)]
nested: MySubCommandEnum,
}
#[derive(FromArgs)]
#[argh(subcommand)]
enum MySubCommandEnum {
#[argh(default)]
One(SubCommandOne),
Two(SubCommandTwo),
}
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did put some thoughts into it and I think the 3rd option is a bit opaque, i.e you'd have to find the subcommand to find the default and I'd like it to specified on the higher level struct.
Another alternative (Option 1) is to force trail impl of the subcommand but I think that is too much burden on the end user but it would confine the command to a common interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think option 2 would be the best choice here and it also turn a mislabeled default into a proper compile time error.
/// Just like `from_args`, but with an additional parameter to specify whether or not | ||
/// to print the first line of command_usage. | ||
/// Internal use only. | ||
fn from_args_show_command_usage( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding a new method without a default implementation is a breaking change. Is there another way we could do this?
Note that even if we do decide to make a breaking change, I'd prefer us not have internal use only methods like this. We have users that manually implement these traits rather than going through argh_derive
, and it'd be unclear how those users should implement these methods. It'd be somewhat better to have from_args_show_command_usage
call from_args
by default, but that's still a little ugly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, if that is the case then this would be a breaking change and the non-breaking/easiest way is what you have mentioned.
/// Just like `redact_arg_values`, but with an additional parameter to specify whether or not | ||
/// to print the first line of command_usage. | ||
/// Internal use only. | ||
fn redact_arg_values_show_command_usage( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also a breaking change.
fn option_subcommand_with_default() { | ||
#[derive(FromArgs, PartialEq, Debug)] | ||
/// Top-level command. | ||
struct TopLevel { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what happens if we have a name collision between top-level fields, and the default subcommand field? For example, say we change this struct to:
struct TopLevel {
#[argh(option)]
/// how many toplevel x
x: String,
nested: Option<MySubCommandEnum>,
}
we'd collide on --x
. I'm guessing the TopLevel field would probably take precedence. Ideally this would be an error, but I don't think we have enough info to enforce this in argh_derive.
@erickt This function was needed for https://fuchsia-review.googlesource.com/c/fuchsia/+/688982 in this case, we want to drop the support for
But we still wish to retain the behavior of
See also: https://fuchsia-review.googlesource.com/c/fuchsia/+/688982/comments/211fa14b_bfe6b463 |
@qhua948 Since it typically takes some time to hash out a feature like this, to unblock you on https://fuchsia-review.googlesource.com/c/fuchsia/+/688982, I think you could use this trick to get this to work: #[derive(FromArgs)]
#[argh(
subcommand,
description = "Display logs from a target device. Defaults to `watch` if no subcommand is specified",
note = "...",
)]
struct LogCommand {
#[argh(positional)]
/// unknown arguments
args: Vec<String>,
#[argh(subcommand)]
sub_command: Option<LogSubCommand>,
}
...
pub async fn log_impl<W: std::io::Write>(
diagnostics_proxy: DiagnosticsProxy,
rcs_proxy: Option<RemoteControlProxy>,
log_settings: Option<LogSettingsProxy>,
mut cmd: LogCommand,
writer: &mut W,
opts: LogOpts,
) -> Result<()> {
if cmd.sub_command.is_none() {
cmd.sub_command = LogWatchCommand::from_args(&cmd.args)?;
}
} |
Side note, #15 is tangentially related |
What @qhua948 said :) I'll add some additional context. @erickt the example you wrote is basically the approach we are following on struct TopLevel {
#[argh(option)]
/// how many x
x: usize,
#[argh(subcommand)]
nested: Option<MySubCommandEnum>,
} However, the fact that the $ ffx log dump --filter foo
Unrecognized argument: --filter
See 'ffx help <command>' for more information on a specific command.
$ ffx log --filter foo dump
// works
Now we are introducing Going with the Regarding flattening, that's another related issue that we are solving at the moment by duplicating the struct defining the flags for both |
The trick won't really work because
|
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. Tweaked output for default subcommands. Addressed various PR feedback.
@erickt I have implemented alternative style for defaults #[derive(FromArgs)]
struct TopLevel {
#[argh(subcommand), default = "SubCommandOne"]
nested: MySubCommandEnum,
}
#[derive(FromArgs)]
#[argh(subcommand)]
enum MySubCommandEnum {
#[argh(default)]
One(SubCommandOne),
Two(SubCommandTwo),
} When the default is specified wrongly, it will be a compile time error. |
I know this is an old PR, but is there any chance this could be reviewed? I'm working on a project where this behaviour would be nice. |
Don't think I have the bandwidth to do this right now, feel free to pick it up.
Dec 17, 2024 15:51:45 Logan .B ***@***.***>:
…
I know this is an old PR, but is there any chance this could be reviewed? I'm working on a project where this behaviour would be nice.
—
Reply to this email directly, view it on GitHub[#129 (comment)], or unsubscribe[https://github.com/notifications/unsubscribe-auth/AFZFMU4ZL6UKAQRGNSHYEYL2F6UWBAVCNFSM6AAAAABTXSIKX2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDKNBXGUYDENZXGE].
You are receiving this because you were mentioned.
[Tracking image][https://github.com/notifications/beacon/AFZFMU7KHU3O2W3ZIIQ22HT2F6UWBA5CNFSM6AAAAABTXSIKX2WGG33NNVSW45C7OR4XAZNMJFZXG5LFINXW23LFNZ2KUY3PNVWWK3TUL5UWJTUX27HLG.gif]
|
#57