Search code examples
rustcommand-line-interfaceclap

Clap subcommands over multiple rs files


Situation

I want to build a more complex CLI tool. For the purposes of this question, let's say I want to build my own implementation of an AWS Cli tool.

I want to split up the logic in the different services (like EC2, S3, sns, etc) and be able to execute on subcommands.

$ aws ec2 describe-instances

$ aws ec2 start-instances --instance-ids i-1348636c

$ aws s3 <Command> [<Arg> ...]

Complication

To properly split up the business logic, I want to distribute the subcommands over multiple files.

.
├── Cargo.lock
├── Cargo.toml
└── src
    ├── main.rs
    ├── S3.rs
    ├── Ec2.rs
    └── etc.rs

I want every service (S3, EC2, etc) to be in control of their own commands and arguments and this would mean that the structure of the subcommands needs to be distributed to the rs files of each the service (S3.rs, Ec2.rs, etc).

Question

What would be the most idiomatic way to create the struct for the args in rust/clap? I prefer to utilize the #[derive] macro as much as possible, because it looks like clap is recommending this.


Solution

  • For each level of commands, you need one enum that lists all subcommands, e.g.:

    // main.rs
    mod ec2;
    mod s3;
    
    #[derive(Parser)]
    enum Cli {
      S3(s3::Command),
      EC2(ec2::Command),
    }
    

    Each of the command modules can then have a separate struct that defines further options or subcommands.

    // ec2.rs
    #[derive(Parser)]
    pub(crate) struct Command {
      #[clap(long)]
      vpc: String,
      #[clap(subcommand)]
      subcommand: Subcommand
    }
    #[derive(Parser)]
    enum Subcommand {
      Instance(instance::Command),
      FooBar(…)
    }
    

    You can find a real-world example of this strategy in wasmer.