Switch subcommand parsing to an enum Subcommand#666
Conversation
8b3da22 to
f3e41ab
Compare
a35dcb6 to
99df9fd
Compare
f3e41ab to
914a821
Compare
99df9fd to
effa762
Compare
8bc7cfb to
9b7da57
Compare
0b5c70a to
607b95e
Compare
9b7da57 to
1cefecd
Compare
607b95e to
b28c3a5
Compare
1cefecd to
9474cbb
Compare
b28c3a5 to
f98a018
Compare
8c8ab59 to
d462926
Compare
f98a018 to
82dd66e
Compare
d462926 to
9029fba
Compare
1d7bdca to
ced5065
Compare
9029fba to
5aabda9
Compare
ced5065 to
ff30bea
Compare
5aabda9 to
282a801
Compare
ff30bea to
38483a1
Compare
282a801 to
7cf583b
Compare
38483a1 to
e6a5158
Compare
7cf583b to
482e78f
Compare
e6a5158 to
70d7c6e
Compare
482e78f to
7c8ea72
Compare
7c8ea72 to
f92f5e5
Compare
649d9d4 to
c120380
Compare
f92f5e5 to
01d62d8
Compare
c120380 to
f1b8bcc
Compare
01d62d8 to
4c980ca
Compare
f1b8bcc to
e722703
Compare
4c980ca to
4665ca1
Compare
labbott
left a comment
There was a problem hiding this comment.
I think this looks okay but I want to look at the clap stuff a little closer.
We should also double check that humility-probless gets built properly and runs without libusb (just checking that we can get a help message in the switch zone)
4665ca1 to
898cb40
Compare
bfd295e to
24d7716
Compare
898cb40 to
f49aa94
Compare
f49aa94 to
20d913b
Compare
jamesmunns
left a comment
There was a problem hiding this comment.
A couple of naggy nits (feel free to mostly ignore), but overall looks good to me.
Also, I think Args might not be the best name we can pick, it's more like a subcommand? That being said, that name isn't great either since it's used in Clap.
The one note I do think is worth doing: I think we could reduce some boilerplate by just "inlining" the "run" functions into the trait impl.
| app: UartConsoleArgs::command(), | ||
| name: "console-proxy", | ||
| run: console_proxy, | ||
| pub type Args = UartConsoleArgs; |
There was a problem hiding this comment.
It might be worth just putting the pub type Args = ...; at the top of each crate, and maybe include a doc comment like "Used by humility-bin/build.rs to generate the top level CLI", so folks see it in the future, and realize it's not load-bearing in the crate itself, and just used for codegen duck-typing purposes.
Right now, we use a sort of inconsistent mix of Args, the specific type, and Self in the impls. It might make sense to just pick one.
| Command { app: CountersArgs::command(), name: "counters", run: counters } | ||
| pub type Args = CountersArgs; | ||
| impl HumilitySubcommand for Args { | ||
| fn run(args: Self, context: &mut ExecutionContext) -> Result<()> { |
There was a problem hiding this comment.
Here we don't even use the alias, we use Self. Can we do this everywhere?
|
|
||
| fn auxflash(context: &mut ExecutionContext) -> Result<()> { | ||
| let subargs = AuxFlashArgs::try_parse_from(&context.cli.cmd)?; | ||
| fn auxflash( |
There was a problem hiding this comment.
I don't understand why we have this pattern of proxying the trait impl to a free function? It seems like a lot of syntactic noise for not a lot of benefit. Could this function just be the run method of HumilitySubcommand? Same comment for roughly all the other subcommands.
There was a problem hiding this comment.
It could, but I was trying to minimize the diff here for ease of review. We could do a follow-up PR to mechanically inline everything?
There was a problem hiding this comment.
Up to you! I think if you put the trait impls where the free funcs used to be, it would diff pretty cleanly, but happy to defer that since it's just a nit anyway.
| for cmd in cmds.iter() { | ||
| writeln!( | ||
| output, | ||
| " Subcommand::{}(args) => cmd_{}::Args::run(args, ctx),", |
There was a problem hiding this comment.
Ah, here is the magic. Man I don't love this.
That being said: I can't really think of a better alternative here than linkme though, which would be a lateral level of shenanigans, and probably wouldn't work as well with clap. But whew.
Opened #678 to possibly rethink this, as I now understand why it has to be Like This.
| } | ||
|
|
||
| /// Trait representing a Humility subcommand | ||
| pub trait HumilitySubcommand: Parser { |
There was a problem hiding this comment.
Minor nit, do we ever not use these impls in a duck-typing kind of way?
I guess requiring a trait impl "by convention" helps us avoid forgetting to impl Parser for each of the Args types, but I'm not sure if that's much value since we're duck-typing the Args name in codegen anyway.
|
@jamesmunns Thanks for the feedback! I killed a few birds with one stone by introducing a new macro: humility_cmd!(WritewordArgs, writeword);This addresses a bunch of issues that you raised:
|
(staged on #661)
tl;dr
This PR adds an
enum Subcommandcontaining every subcommand's arguments, so that all of our command parsing is done by the outer#[derive(Parser)].How things stand
Subcommands are accumulated by the
humility-bin'sbuild.rs. In current code, every command crate has to define afn init(), e.g.The build script uses
cargo-metadatato find allhumility-cmd-*dependencies ofhumility-bin, then constructs adcmdsfunction which returns a table of subcommands:Then, we dispatch based on command name; the first thing that every command's
initfunction has to do is to parse its arguments from thecmd: Vec<String>trailing argument:Because these subcommands are dispatched by our code (and not known at
#[derive(Parser)]time), we have to engage in additional shenanigans to get the command docstrings properly plumbed through to Clap.Let's just not do that
After this PR, every subcommand is required to define a
pub type Args(replacing thepub fn init()). This object must implement a newHumilitySubcommandtrait, e.g.We still use a
build.rsto iterate over subcommands, but now we produce apub enum Subcommand:This is embedded verbatim in the CLI argument object of
humility-bin. With this organization, theclapparser Just Works™. We also generate apub fn dispatchwhich takes theenumand calls the appropriate runner function with theArgsvariant as its first argument (removing the subargs parsing from the function itself).Other changes
humility_cmd::Command, which leftDumperas the only member of that crate, so I renamed it tohumility-hexdumpstd::process::exitin favor of returning anExitCodefrommain. This is still a little unorthodox, but I wanted to exactly preserve our existing CLI output. A few cases ofexitremain, and I'll get to them later