Skip to content

Switch subcommand parsing to an enum Subcommand#666

Open
mkeeter wants to merge 3 commits into
mkeeter/hiffy-decode-flatfrom
mkeeter/subcommand-enum
Open

Switch subcommand parsing to an enum Subcommand#666
mkeeter wants to merge 3 commits into
mkeeter/hiffy-decode-flatfrom
mkeeter/subcommand-enum

Conversation

@mkeeter
Copy link
Copy Markdown
Contributor

@mkeeter mkeeter commented May 14, 2026

(staged on #661)

tl;dr

This PR adds an enum Subcommand containing 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's build.rs. In current code, every command crate has to define a fn init(), e.g.

pub fn init() -> Command {
    Command {
        app: UartConsoleArgs::command(),
        name: "console-proxy",
        run: console_proxy,
    }
}

The build script uses cargo-metadata to find all humility-cmd-* dependencies of humility-bin, then constructs a dcmds function which returns a table of subcommands:

fn dcmds() -> Vec<CommandDescription> {
    vec![
      CommandDescription {
            init: cmd_console_proxy::init,
            // every subcommand's codegen has the same docmsg ¯\_(ツ)_/¯
            docmsg: "For additional documentation, run \"humility doc {}\"."
       },
       // ...etc
    ]
}

Then, we dispatch based on command name; the first thing that every command's init function has to do is to parse its arguments from the cmd: Vec<String> trailing argument:

fn console_proxy(context: &mut ExecutionContext) -> Result<()> {
    let subargs = UartConsoleArgs::try_parse_from(&context.cli.cmd)?;
    // ...do stuff with subargs

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 the pub fn init()). This object must implement a new HumilitySubcommand trait, e.g.

pub type Args = UartConsoleArgs;
impl HumilitySubcommand for UartConsoleArgs {
    fn run(args: Args, context: &mut ExecutionContext) -> anyhow::Result<()> {
        console_proxy(args, context)
    }
}

We still use a build.rs to iterate over subcommands, but now we produce a pub enum Subcommand:

#[derive(::clap::Parser, Debug)]
pub enum Subcommand {
    Auxflash(cmd_auxflash::Args),
    ConsoleProxy(cmd_console_proxy::Args),
    Counters(cmd_counters::Args),
    Dashboard(cmd_dashboard::Args),
    Debugmailbox(cmd_debugmailbox::Args),
    Diagnose(cmd_diagnose::Args),
    Discover(cmd_discover::Args),
    Doc(cmd_doc::Args),
    Dump(cmd_dump::Args),
    Ereport(cmd_ereport::Args),
    Exec(cmd_exec::Args),
    Extract(cmd_extract::Args),
    // ...etc

This is embedded verbatim in the CLI argument object of humility-bin. With this organization, the clap parser Just Works™. We also generate a pub fn dispatch which takes the enum and calls the appropriate runner function with the Args variant as its first argument (removing the subargs parsing from the function itself).

Other changes

  • I removed humility_cmd::Command, which left Dumper as the only member of that crate, so I renamed it to humility-hexdump
  • I removed a few places where we call std::process::exit in favor of returning an ExitCode from main. This is still a little unorthodox, but I wanted to exactly preserve our existing CLI output. A few cases of exit remain, and I'll get to them later

@mkeeter mkeeter requested review from bcantrill, cbiffle and labbott May 14, 2026 19:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant