Structopt: subcommands are all or nothing, allow hybrid

Created on 31 Jul 2017  路  1Comment  路  Source: TeXitoi/structopt

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?

question

Most helpful comment

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 }

>All comments

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 }
Was this page helpful?
0 / 5 - 0 ratings

Related issues

tathanhdinh picture tathanhdinh  路  6Comments

SirWindfield picture SirWindfield  路  3Comments

TedDriggs picture TedDriggs  路  6Comments

swfsql picture swfsql  路  3Comments

tathanhdinh picture tathanhdinh  路  3Comments