Structopt: Enum handling

Created on 6 Jan 2018  Â·  15Comments  Â·  Source: TeXitoi/structopt

Hello

I can use an enum for a subcommand and everything works. However, if I want to have an option with some allowed values, I have harder time, eg. this doesn't work „out of the box“:

#[derive(StructOpt)]
enum Security {
  Insecure,
  Secure,
  Paranoid,
}

#[derive(StructOpt)]
struct Options {
  #[structopt(long = "security")]
  security: Security,
}

I have to manually implement FromStr for it to make it work, or use something like clap::arg_enum.

Would it be possible for structopt-derive to spit out such FromStr implementation as well for enums? I believe it shouldn't be much harder than the parsing of subcommands anyway. Or maybe some other trait and mark it at the use site (eg. similar to #[structopt(subcommand)], but #[structopt(enum)] or something).

Or maybe it is already possible, but I haven't grasped it from the documentation.

doc question

Most helpful comment

strum version (@Peternator7 you may be interested) a bit too much boilerplate, but featurefull:

extern crate structopt;
#[macro_use]
extern crate structopt_derive;
extern crate strum;
#[macro_use]
extern crate strum_macros;
#[macro_use]
extern crate lazy_static;

use structopt::StructOpt;
use strum::IntoEnumIterator;

#[derive(Debug, EnumString, ToString, EnumIter)]
enum Baz {
    #[strum(serialize="foo")]
    Foo,
    #[strum(serialize="bar")]
    Bar,
    #[strum(serialize="foo-bar")]
    FooBar,
}
lazy_static! {
    static ref BAZ_POSSIBLE_VALUES_STRING: Vec<String> = Baz::iter()
        .map(|e| e.to_string())
        .collect();
    static ref BAZ_POSSIBLE_VALUES: Vec<&'static str> = BAZ_POSSIBLE_VALUES_STRING
        .iter()
        .map(AsRef::as_ref)
        .collect();
}

#[derive(StructOpt, Debug)]
struct Opt {
    /// Important argument.
    #[structopt(possible_values_raw = "&BAZ_POSSIBLE_VALUES")]
    i: Baz,
}

fn main() {
    let opt = Opt::from_args();
    println!("{:?}", opt);
}
cargo run -- -h
   Compiling test v0.0.1 (file:///home/texitoi/dev/test)
    Finished dev [unoptimized + debuginfo] target(s) in 1.29 secs
     Running `target/debug/test -h`
test 0.0.1
Guillaume Pinot <[email protected]>

USAGE:
    test <i>

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

ARGS:
    <i>    Important argument. [values: foo, bar, foo-bar]

All 15 comments

For me, that's not the goal of structopt, bit a work for another crate. Clap-derive will provide that.

Waiting for it, strum look interesting.

I think something using structopt parse and serde is also an interesting possibility.

Arg_enum should also be usable, I'll try to give you some examples when I'll have the time.

Yes, all these are possible (I actually ended up using a trick with serde). But they are not obvious and it's not clear how to integrate it fully, eg with list of options in help.

I guess using another crate is OK, but an example how to use it with structopt-derive could be useful.

Can you give a small example of your trick with serde, for the interested reader that may pass here (including me 😇)?

Sure. Nothing fancy:

#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
enum Output {
    /// Rust's pretty-printed Debug
    Human,
    /// Machine readable single-line JSON.
    Json,
    /// Pretty-printed JSON
    JsonPretty,
    /// Yaml.
    Yaml,
}

impl FromStr for Output {
    type Err = serde_json::error::Error;
    fn from_str(s: &str) -> Result<Output, serde_json::error::Error> {
        Ok(serde_json::from_str(&format!("\"{}\"", s))?)
    }
}

It implements the FromStr trait, so it can be used in structopt, but doesn't provide the help (either list of the options, or generated descriptions from the doc strings).

strum version (@Peternator7 you may be interested) a bit too much boilerplate, but featurefull:

extern crate structopt;
#[macro_use]
extern crate structopt_derive;
extern crate strum;
#[macro_use]
extern crate strum_macros;
#[macro_use]
extern crate lazy_static;

use structopt::StructOpt;
use strum::IntoEnumIterator;

#[derive(Debug, EnumString, ToString, EnumIter)]
enum Baz {
    #[strum(serialize="foo")]
    Foo,
    #[strum(serialize="bar")]
    Bar,
    #[strum(serialize="foo-bar")]
    FooBar,
}
lazy_static! {
    static ref BAZ_POSSIBLE_VALUES_STRING: Vec<String> = Baz::iter()
        .map(|e| e.to_string())
        .collect();
    static ref BAZ_POSSIBLE_VALUES: Vec<&'static str> = BAZ_POSSIBLE_VALUES_STRING
        .iter()
        .map(AsRef::as_ref)
        .collect();
}

#[derive(StructOpt, Debug)]
struct Opt {
    /// Important argument.
    #[structopt(possible_values_raw = "&BAZ_POSSIBLE_VALUES")]
    i: Baz,
}

fn main() {
    let opt = Opt::from_args();
    println!("{:?}", opt);
}
cargo run -- -h
   Compiling test v0.0.1 (file:///home/texitoi/dev/test)
    Finished dev [unoptimized + debuginfo] target(s) in 1.29 secs
     Running `target/debug/test -h`
test 0.0.1
Guillaume Pinot <[email protected]>

USAGE:
    test <i>

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

ARGS:
    <i>    Important argument. [values: foo, bar, foo-bar]

Clap's arg_enum! no kebab case:

extern crate structopt;
#[macro_use]
extern crate structopt_derive;
#[macro_use]
extern crate clap;

use structopt::StructOpt;

arg_enum! {
    #[derive(Debug)]
    enum Baz {
        Foo,
        Bar,
        FooBar
    }
}

#[derive(StructOpt, Debug)]
struct Opt {
    /// Important argument.
    #[structopt(possible_values_raw = "&Baz::variants()", case_insensitive_raw = "true")]
    i: Baz,
}

fn main() {
    let opt = Opt::from_args();
    println!("{:?}", opt);
}
cargo run -- -h
   Compiling test v0.0.1 (file:///home/texitoi/dev/test)
    Finished dev [unoptimized + debuginfo] target(s) in 1.26 secs
     Running `target/debug/test -h`
test 0.0.1
Guillaume Pinot <[email protected]>

USAGE:
    test <i>

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

ARGS:
    <i>    Important argument. [values: Foo, Bar, FooBar]

@kbknapp you may be interested.

That arg_enum! looks like the „correct“ solution to me, in general. Would it make sense to include it in some example/the documentation, so others find it too, when they have a similar problem? (I guess this might be quite common thing to do)

Sure we can add such an example. I'll do when I'll have some time. Feel free to open a PR if you're inspired.

Would be wonderful if there was support for making kebab-case enum values with arg_enum though

@Veetaha this is under development already. Probably it'll be a custom attribute, like

#[derive(ArgEnum)] // derive or attribute, not decided yet
enum Arg {
    Foo, 
    Bar
}

It's unlikely to be implemented in structopt though, we're importing it into clap v0.3 directly as clap_derive.

I'm not sure how soon it will be (I'm the one who does is supposed to do the import thing but unexpected RL stuff leaves me no time for open source. Next week, hopefully).

Correction: clap v3, not 0.3

@Veetaha this is under development already. Probably it'll be a custom attribute, like

#[derive(ArgEnum)] // derive or attribute, not decided yet
enum Arg {
    Foo, 
    Bar
}

It's unlikely to be implemented in structopt though, we're importing it into clap v0.3 directly as clap_derive.

I'm not sure how soon it will be (I'm the one who ~does~ is supposed to do the import thing but unexpected RL stuff leaves me no time for open source. Next week, hopefully).

@CreepySkeleton did this happen?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sphynx picture sphynx  Â·  9Comments

TedDriggs picture TedDriggs  Â·  6Comments

jackjen picture jackjen  Â·  7Comments

lucab picture lucab  Â·  9Comments

tathanhdinh picture tathanhdinh  Â·  6Comments