Skip to content

Add support for subcommands#17

Merged
TeXitoi merged 29 commits intoTeXitoi:masterfrom
williamyaoh:subcommands
Jul 17, 2017
Merged

Add support for subcommands#17
TeXitoi merged 29 commits intoTeXitoi:masterfrom
williamyaoh:subcommands

Conversation

@williamyaoh
Copy link
Contributor

@williamyaoh williamyaoh commented Jul 3, 2017

See #1 for detailed discussion of this feature.

Adds the ability to use #[derive(StructOpt)] on enums, thereby allowing the creation/easy parsing of clap::SubCommands.

This is not a breaking change.

Things that can still be improved:

  • Fix subcommand names that are hyphenated
  • Automatically downcase the names of subcommands for help messages (this could also apply to #[derive(StructOpt)] on structs or to changing argument names from _ to -)
  • Add the ability to use about attribute/doc comments on subcommand variants for help messages
  • Allow other enum variant types than curly-bracketed ones (e.g. "blank" subcommands)

Other stuff

There are some commits that don't technically have anything to do with adding subcommands in:

  • Add documentation for the PR that added doc comments for help messages a few weeks ago
  • Add a link from the structopt documentation to the structopt-derive documentation
  • Bump versions of structopt and structopt-derive to 0.1.0
  • Edit the structopt-derive documentation for clarity

Detailed documentation has been added to the structopt-derive crate specifying how to use the new subcommand functionality. Reproduced here:

Subcomamnds

Some applications, like git, support "subcommands;" an extra command that
is used to differentiate what the application should do. With git, these
would be add, init, fetch, commit, for a few examples.

clap has this functionality, so structopt supports this through enums:

#[derive(StructOpt)]
#[structopt(name = "git", about = "the stupid content tracker")]
enum Git {
    #[structopt(name = "add")]
    Add {
        #[structopt(short = "i")]
        interactive: bool,
        #[structopt(short = "p")]
        patch: bool,
        files: Vec<String>
    },
    #[structopt(name = "fetch")]
    Fetch {
        #[structopt(long = "dry-run")]
        dry_run: bool,
        #[structopt(long = "all")]
        all: bool,
        repository: Option<String>
    },
    #[structopt(name = "commit")]
    Commit {
        #[structopt(short = "m")]
        message: Option<String>,
        #[structopt(short = "a")]
        all: bool
    }
}

Using derive(StructOpt) on an enum instead of a struct will produce
a clap::App that only takes subcommands. So git add, git fetch,
and git commit would be commands allowed for the above example.

structopt also provides support for applications where certain flags
need to apply to all subcommands, as well as nested subcommands:

#[derive(StructOpt)]
#[structopt(name = "make-cookie")]
struct MakeCookie {
    #[structopt(name = "supervisor", default_value = "Puck", required = false, long = "supervisor")]
    supervising_faerie: String,
    #[structopt(name = "tree")]
    /// The faerie tree this cookie is being made in.
    tree: Option<String>,
    #[structopt(subcommand)]  // Note that we mark a field as a subcommand
    cmd: Command
}

#[derive(StructOpt)]
enum Command {
    #[structopt(name = "pound")]
    /// Pound acorns into flour for cookie dough.
    Pound {
        acorns: u32
    },
    #[structopt(name = "sparkle")]
    /// Add magical sparkles -- the secret ingredient!
    Sparkle {
        #[structopt(short = "m")]
        magicality: u64,
        #[structopt(short = "c")]
        color: String
    },
    #[structopt(name = "finish")]
    Finish {
        #[structopt(short = "t")]
        time: u32,
        #[structopt(subcommand)]  // Note that we mark a field as a subcommand
        type: FinishType
    }
}

#[derive(StructOpt)]
enum FinishType {
    #[structopt(name = "glaze")]
    Glaze {
        applications: u32
    },
    #[structopt(name = "powder")]
    Powder {
        flavor: String,
        dips: u32
    }
}

Marking a field with structopt(subcommand) will add the subcommands of the
designated enum to the current clap::App. The designated enum must also
be derived StructOpt. So the above example would take the following
commands:

  • make-cookie pound 50
  • make-cookie sparkle -mmm --color "green"
  • make-cookie finish 130 glaze 3

Optional subcommands

A nested subcommand can be marked optional:

#[derive(StructOpt)]
#[structopt(name = "foo")]
struct Foo {
    file: String,
    #[structopt(subcommand)]
    cmd: Option<Command>
}

#[derive(StructOpt)]
enum Command {
    Bar,
    Baz,
    Quux
}

@williamyaoh
Copy link
Contributor Author

I'd like some feedback on the documentation, as well as any bugs/kinks that still need to ironed out.

@williamyaoh williamyaoh mentioned this pull request Jul 3, 2017
@williamyaoh
Copy link
Contributor Author

I'm not really sure why the Travis build is failing; it seems to still be looking for structopt-derive 0.0.5 despite having bumped the version(s) in the manifest. rg '0\.0\.5' gets nothing, either.

@TeXitoi
Copy link
Owner

TeXitoi commented Jul 3, 2017

To fix Travis, you must update the structopt-derive version in the dev-depedencies of the Cargo.toml at the root of the project.

@TeXitoi
Copy link
Owner

TeXitoi commented Jul 3, 2017

And please update everything to 0.1.0, else we can't do non breaking change version.

@williamyaoh
Copy link
Contributor Author

It's updated at 72d9d35, so it seems to be something else.

@TeXitoi
Copy link
Owner

TeXitoi commented Jul 3, 2017

This commit update only the README.md.

@williamyaoh
Copy link
Contributor Author

Nevermind, you're right, I'm an idiot. I think I accidentally clobbered updating the [dev-dependencies] after using git reset to fix an older commit.

@ErichDonGubler
Copy link

Question about these two lines from the examples:

struct MakeCookie {
    #[structopt(name = "supervisor", default_value = "Puck")]
    supervising_faerie: Option<String>,
// ...

Is it necessary for the supervising_faerie field to be an Option, if a default is always provided? Wouldn't that just make client code unnecessarily need to call unwrap on this field every time?

Copy link
Owner

@TeXitoi TeXitoi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!

A rapid first read. I'll do a deeper read soon.

//! `Arg::with_name()` call (default to the field name).
//! Then, each field of the struct not marked as a subcommand corresponds
//! to a `clap::Arg`. As with the struct attributes, every method of
//! `clap::Arg`in the form of `fn function_name(self, &str)` can be used
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lack space before in

//! }
//! ```
//!
//! ## Subcomamnds
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo

//! #[structopt(name = "make-cookie")]
//! struct MakeCookie {
//! #[structopt(name = "supervisor", default_value = "Puck")]
//! supervising_faerie: Option<String>,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With default value you don't want an option

@TeXitoi
Copy link
Owner

TeXitoi commented Jul 3, 2017

Great doc ! Thanks for the corrections of my frenchy English ;-)

@williamyaoh
Copy link
Contributor Author

@ErichDonGubler You're right, will change that.

@ErichDonGubler
Copy link

@williamyaoh: Looks like something like this fails right now:

enum Subcommand {
    Something // Fails here
}

Adding curly braces fixes it, but shouldn't be necessary:

enum Subcommand {
    Something {}
}

@williamyaoh
Copy link
Contributor Author

@ErichDonGubler Yup, that's already on the Things to be improved list above already. It's fixable, though it's not trivial to do.

@williamyaoh
Copy link
Contributor Author

@ErichDonGubler Fixed at 4ff50e6. It ended up being simpler than I thought it would be.

@williamyaoh
Copy link
Contributor Author

Regarding downcasing of subcommand names by default: I can see the convenience, but on the other hand, it also means using "magic" from the programmer's perspective, where names that they never typed become part of the interface of their program. I'm not sure whether the convenience outweighs the confusion over the exact mechanism of translation of subcommand names.

What does everyone else think?

I think the actual semantics would be to translate all identifiers into kebab case, if no name attribute is specified. So

#[derive(StructOpt)]
enum DoStuff {
    DoTheThing
}

would automatically produce a command $ do-stuff do-the-thing, without having to specify a name attribute. Attributes would act the same way:

#[derive(StructOpt)]
struct DoOtherStuff {
    useful_information: bool
}

would automatically produce a command $ do-other-stuff --useful-information.

@TeXitoi
Copy link
Owner

TeXitoi commented Jul 5, 2017

I think translation to kebab-case is an important feature. I prefer this feature to be in another PR, it'll let me the time to validate this one soon.
Maybe open an issue about that to see feedbacks?

Copy link
Owner

@TeXitoi TeXitoi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! Only small feedbacks.

quote!( .arg(_structopt::clap::Arg::with_name(stringify!(#name)) #modifier #(#from_attr)*) )
});

assert!(subcmds.len() <= 1, "cannot have more than one nested subcommand");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer this assert be just after the declaration, it explain why we have collect

src/lib.rs Outdated
fn clap<'a, 'b>() -> clap::App<'a, 'b>;

/// Add this app's arguments/subcommands to another `clap::App`.
fn augment_clap<'a, 'b>(clap::App<'a, 'b>) -> clap::App<'a, 'b>;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a big fan of this, but I don't have any better idea.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could move that function into a inherent impl block for each type; that would remove it from cluttering up the StructOpt documentation, at least, which I think is the real issue.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea.

@williamyaoh
Copy link
Contributor Author

All right, unless someone else finds a bug in the subcommand-related stuff, that looks to be everything.

@williamyaoh
Copy link
Contributor Author

@TeXitoi Do you need anything else from me before merging?

@TeXitoi
Copy link
Owner

TeXitoi commented Jul 14, 2017 via email

@TeXitoi TeXitoi merged commit a7224b5 into TeXitoi:master Jul 17, 2017
@TeXitoi
Copy link
Owner

TeXitoi commented Jul 17, 2017

Published, thanks for your work!

@CAD97 CAD97 mentioned this pull request Feb 17, 2018
11 tasks
Eijebong pushed a commit to Eijebong/structopt that referenced this pull request Jan 2, 2019
fix a bunch of clippy warnings
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.

3 participants