Reputation: 53
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> ...]
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).
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.
Upvotes: 3
Views: 2807
Reputation: 8544
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.
Upvotes: 6
Reputation: 759
The main.rs
is the default executable file in the rust program. If you want to distribute a library you would typically have different executables inside the bin
folder.
I would recommend moving the service files ec2.rs
and s3.rs
to src/bin
folder which makes them executables. You can now run these files from the command line/terminal directly with command cargo run --bin s3
.
src/bin/ec2.rs
src/bin/s3.rs
Further, for the subcommands, you need to specify the subcommand in an Enum
and provide these commands to the clap macro #[clap(subcommand)]
File src/bin/ec2.rs
Struct Cli {
#[clap(subcommand)]
command: Command,
}
enum Command {
StartInstance { arg1: String, arg2: String }
DescribeInstance { arg1: String }
}
File src/bin/ec2.rs
let args = Cli.parse();
let command = args.command;
// match on the command and extract args for further processing.
Upvotes: 0