Basically, I want to be able to integrate subcommands into an existing CLI framework, without forcing the original commands to now be under a subcommand.
Afaics (please correct me if I'm wrong, or add an appropriate example), structopt forces the user to either use regular command line with no subcommands, or all only subcommands.
E.g. something like this (do not take this as a suggestion for syntax, or a functioning example, etc.):
#[derive(StructOpt, Debug)]
#[structopt(name = "derp")]
enum Fetch {
#[structopt(name = "fetch")]
/// fetch branches from remote repository
Fetch {
#[structopt(long = "dry-run")]
dry_run: bool,
#[structopt(long = "all")]
all: bool,
#[structopt(default_value = "origin")]
repository: String
},
}
#[derive(StructOpt, Debug)]
#[structopt(name = "derp", about = "merp a lerp")]
struct Args {
/// The specific function address to disassemble
#[structopt(short = "a", long = "address", help = "Disassemble the function at the given address")]
address_filter: Option<String>,
/// The binary to disassemble
#[structopt(help = "The binary to disassemble")]
binary: String,
fetch: Fetch
}
which would work via the following invocations:
derp fetch --all <repo>
derp --address <binary>
derp <binary>
E.g., I can add subcommands in gradually to an existing framework; or, perhaps subcommands are rarely used, but a good separation of tasks in the tool when they are used, so an explicit subcommand is nice, but otherwise the other command line arguments without any subcommand also work.
Another way of putting it, is basically how cargo works right now; it technically has a mixture of subcommands and "raw" non-subcommand commands, like --color or --explain.
So is something like this possible?
Use an option on the subcommand:
extern crate structopt;
#[macro_use]
extern crate structopt_derive;
use structopt::StructOpt;
#[derive(StructOpt, Debug)]
enum SubCommand {
#[structopt(name = "fetch")]
/// fetch branches from remote repository
Fetch {
#[structopt(long = "dry-run")]
dry_run: bool,
#[structopt(long = "all")]
all: bool,
#[structopt(default_value = "origin")]
repository: String
},
}
#[derive(StructOpt, Debug)]
struct Opt {
/// Disassemble the function at the given address
#[structopt(short = "a", long = "address")]
address_filter: Option<String>,
/// The binary to disassemble
binary: Option<String>,
#[structopt(subcommand)]
subcommand: Option<SubCommand>,
}
fn main() {
let opt = Opt::from_args();
println!("{:?}", opt);
}
$ ./target/debug/test-rs -h
test-rs 0.0.1
Guillaume Pinot <[email protected]>
USAGE:
test-rs [OPTIONS] [binary] [SUBCOMMAND]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-a, --address <address_filter> Disassemble the function at the given address
ARGS:
<binary> The binary to disassemble
SUBCOMMANDS:
fetch fetch branches from remote repository
help Prints this message or the help of the given subcommand(s)
$ ./target/debug/test-rs
Opt { address_filter: None, binary: None, subcommand: None }
$ ./target/debug/test-rs fetch --all repo
Opt { address_filter: None, binary: None, subcommand: Some(Fetch { dry_run: false, all: true, repository: "repo" }) }
$ ./target/debug/test-rs --address binary
Opt { address_filter: Some("binary"), binary: None, subcommand: None }
$ ./target/debug/test-rs binary
Opt { address_filter: None, binary: Some("binary"), subcommand: None }
Most helpful comment
Use an option on the subcommand: