Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using AppSettings::SubcommandsNegateReqs #124

Closed
erickpires opened this issue Jul 13, 2018 · 18 comments
Closed

Using AppSettings::SubcommandsNegateReqs #124

erickpires opened this issue Jul 13, 2018 · 18 comments
Labels
enhancement We would love to have this feature! Feel free to supply a PR need-design The concrete desing is uncertain, please get involved in the discussion before sending a PR question

Comments

@erickpires
Copy link

I'm sorry if I'm missing something here, but I was unable to find any information on the documentation or in the examples.

I have a use case where my program has a required Arg (for example a input file name). I would like to implement shell completions generation using clap during runtime by passing a completionssubcommand and the shell to generate the completions for (the same way rustup does). For this to happen I need to set AppSettings::SubcommandsNegateReqs. Using the examples as a guide I did this:

#[structopt(..., raw(global_settings = "&[AppSettings::SubcommandsNegateReqs]")]

But now, when I run cargo run completions zsh for example, my program panics at Opt::from_args() due to a None been unwrap'd.

I've found a workaround this by getting the clap matches via Opt::clap().get_matches() and matching against the subcommands (and exiting early if the subcommand was found), but I found this very ugly and was thinking that there must be a clean way to do this using structopt.

@TeXitoi
Copy link
Owner

TeXitoi commented Jul 13, 2018

I didn't know this command. But what behavior do you expect, because from_args can't work in this case.

@TeXitoi TeXitoi added enhancement We would love to have this feature! Feel free to supply a PR question labels Jul 13, 2018
@erickpires
Copy link
Author

I understand that from_args can't work in this situation. When using clap to implement subcommands that should negate required arguments I use the subcomand() from ArgsMatches, so I think ideal solution here would be to try and replicate this behavior.

My thinking is, since I have a Subcommands enum that lists all the possible subcommands and my Opt struct has a field of type Subcommand, we could have something like Opt::subcommand() that would return an Option<Subcommands>. This way I can match against this, similar to what I would do when using clap and I know to only call from_args when the return is None.

Now, what I don't know is if this is even possible. I don't know what we can and cannot do with #[derive]. I would be happy to help implement this, but I would like to know your opinion about this.

@TeXitoi
Copy link
Owner

TeXitoi commented Jul 17, 2018

@kbknapp what do you think about that?

@TeXitoi TeXitoi added the need-design The concrete desing is uncertain, please get involved in the discussion before sending a PR label Oct 12, 2018
@blckngm
Copy link

blckngm commented Sep 30, 2019

I run into this issue as well. Here's my workaround: define an almost identical struct but make the fields optional, and use it to access match result.

use structopt::StructOpt;

#[derive(Debug, StructOpt)]
struct OptionsAndSubcommand {
    #[structopt(long)]
    arg: i32,

    #[structopt(subcommand)]
    cmd: Option<Cmd>,
}

#[derive(Debug, StructOpt)]
enum Cmd {
    Get {
        name: String,
    },
}

#[derive(Debug, StructOpt)]
struct OptionalOptions {
    // Proper support should allow something like:
    // #[structopt(required_unless_subcommand)]
    arg: Option<i32>,

    #[structopt(subcommand)]
    cmd: Option<Cmd>,
}

fn main() {
    let matches = OptionsAndSubcommand::clap()
        .setting(structopt::clap::AppSettings::SubcommandsNegateReqs)
        .get_matches();

    println!("Options: {:?}", OptionalOptions::from_clap(&matches));
}

If you don't expect any args when there is a subcommand, you can do this:

use structopt::StructOpt;

#[derive(Debug, StructOpt)]
struct OptionsAndSubcommand {
    #[structopt(long)]
    arg: i32,

    #[structopt(subcommand)]
    cmd: Option<Cmd>,
}

#[derive(Debug, StructOpt)]
enum Cmd {
    Get {
        name: String,
    },
}

fn main() {
    let matches = OptionsAndSubcommand::clap()
        .setting(structopt::clap::AppSettings::SubcommandsNegateReqs)
        .setting(structopt::clap::AppSettings::ArgsNegateSubcommands)
        .get_matches();

    if matches.subcommand_name().is_none() {
        println!("Options: {:?}", OptionsAndSubcommand::from_clap(&matches));
    } else {
        println!("Subcommand: {:?}", Cmd::from_clap(&matches));
    }
}

@keturn
Copy link

keturn commented Dec 24, 2019

First I tried

    #[structopt(required=true)]
    arg: Option<i32>,

but structopt complained I was contradicting myself.

So I was somewhat surprised to discover that this seems to work:

    #[structopt(required_unless="cmd")]
    arg: Option<i32>,

Not ideal, since it requires duplicating the name of the subcommand (or names, using required_unless_one), but it's a workaround.

Could structopt ease up on that "required is meaningless for Option" error when SubcommandsNegateReqs is on? Perhaps not where that error is raised currently, it doesn't look like that has any way to see what the app settings are.

Maybe some kind of allow mechanism to override that error? It is, after all, safer to wrap a "required" value in an Option than it is when I shoot myself in the foot by passing required=false on a non-Option field.

Or could that validation be done later, when it's known what the app settings are? (maybe even at runtime, if necessary)

@TeXitoi
Copy link
Owner

TeXitoi commented Jan 7, 2020

We can remove the error and do a warning, with some mechanism to remove the warning.

@CreepySkeleton
Copy link
Collaborator

There's no way to issue warnings on stable.

@archer884
Copy link

struct OptionalOptions {
    // Proper support should allow something like:
    // #[structopt(required_unless_subcommand)]
    arg: Option<i32>,

    #[structopt(subcommand)]
    cmd: Option<Cmd>,
}

I'm not sure that's the way I'd want to go. Ideally, what I had in mind was:

enum Opt {
    #[structopt(default_command)]
    MainCommandWithLotsOfArgs {
        ...
    },
    Other,
    Subcommands,
    Here { some_arg: i32 },
}

The reason for this being just that the main command may involve lots of args that you don't feel like marking separately. At this point, of course, I'm just spitballing about ergonomics. I'm coming at this from the standpoint of a consumer, not a library author.

@CreepySkeleton
Copy link
Collaborator

I'm not sure that's the way I'd want to go. Ideally, what I had in mind was:

enum Opt {
    #[structopt(default_command)]
    MainCommandWithLotsOfArgs {
        ...
    },
    Other,
    Subcommands,
    Here { some_arg: i32 },
}

The reason for this being just that the main command may involve lots of args that you don't feel like marking separately. At this point, of course, I'm just spitballing about ergonomics. I'm coming at this from the standpoint of a consumer, not a library author.

Actually, this design looks neat and unambigous! 👌 If @TeXitoi doesn't mind, would you like to take a stab at implementation? I can mentor it and give you a hand.

I'm just spitballing about ergonomics. I'm coming at this from the standpoint of a consumer, not a library author.

It is always about ergonomics to some extend. Does stuff feel intuitive enough (define intuitive, aha), does it meld into other parts of the library, etc. The more people come and propose ideas, the better the chances that something worthy will eventually be found. I personally thing your proposal is great.

@archer884
Copy link

@CreepySkeleton I'd be cool with that.

@CreepySkeleton
Copy link
Collaborator

@TeXitoi What do you think about @archer884 design above?

@TeXitoi
Copy link
Owner

TeXitoi commented Apr 28, 2020

Not found of default_command, we van fond a better name.

It must work

  • within a structure for common args, and without for arg less subcommands.
  • with an embedded struct enum, and with a 1-uple enum containing a struct.

@archer884
Copy link

archer884 commented Apr 28, 2020

@TeXitoi, regarding the first bullet point: if the purpose of this is to make it possible not to have common args between all subcommands, how much value lies in making it work within a struct defining common args for subcommands?

I could be somewhat biased. As a user of this library (I think practically all of my tools are based on structopt), I don't think I have ever intentionally made use of the common arguments feature. To avoid rewriting the same code from subcommand to subcommand, my preferred strategy has been to define common arguments as a struct and #[structopt(flatten)] those into the parent structs of each subcommand. This avoids what I see as the somewhat confusing pattern of cmd --param arg subcommand --param otherarg. (Admittedly, Microsoft follows this pattern for their dotnet cli tool, but that's one of my biggest complaints about their tool.)

An example:

#[derive(Clone, Debug, StructOpt)]
enum SubCommands {
    Foo(Foo),
    Bar(Bar),
}

#[derive(Clone, Debug, StructOpt)]
struct Foo {
    #[structopt(flatten)]
    common_args: CommonArgs,
    other: String,
    args: String,
}

#[derive(Clone, Debug, StructOpt)]
struct Bar {
    #[structopt(flatten)]
    common_args: CommonArgs,
    other: i32,
    args: String,
}

#[derive(Clone, Debug, StructOpt)]
struct CommonArgs {
    name: String,
    date: String,
}

@TeXitoi
Copy link
Owner

TeXitoi commented Apr 28, 2020

The goal is to be consistent: you can without this feature, so this feature must not be an exception.

@CreepySkeleton
Copy link
Collaborator

Guys, I suspect you're speaking past each other here.

Regarding the first point, @TeXitoi could you please elaborate? A bit of code example would help immensely!

The second: @TeXitoi was talking about

enum Opt {
    #[structopt(default_command)]
    MainCommandWithLotsOfArgs(MainApp),
    Other,
    Subcommands,
    Here { some_arg: i32 },
}

struct MainApp { ... }

Super easy to implement, about +10 LOC.

@archer884
Copy link

archer884 commented Apr 29, 2020 via email

@TeXitoi
Copy link
Owner

TeXitoi commented Apr 29, 2020

enum Opt {
    #[structopt(default_command)]
    MainCommandWithLotsOfArgs(MainApp),
    Other,
    Subcommands,
    Here { some_arg: i32 },
}
struct BigOpt {
    #[structopt(subcommand)] 
    cmd: Opt,
    #[structopt(long, short)]] 
   config: String,
}

StructOpt on Opt and on BigOpt should work as expected (i.e. config always accessible).

@TeXitoi
Copy link
Owner

TeXitoi commented Jan 18, 2022

This is an enhancement, and structopt is now feature frozen.

@TeXitoi TeXitoi closed this as completed Jan 18, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement We would love to have this feature! Feel free to supply a PR need-design The concrete desing is uncertain, please get involved in the discussion before sending a PR question
Projects
None yet
Development

No branches or pull requests

6 participants