Allow something like:
#[derive(StructOpt)]
struct Opts {
#[structopt(flatten, prefix = "http-")]
http_address: Address,
#[structopt(flatten, prefix = "monitoring-", default_value = "127.0.0.1")]
monitoring_address: Address,
}
#[derive(StructOpt)]
sturct Address {
#[structopt(short = "p", long = "port")]
port: u16,
#[structopt(short = "a", long = "address")]
address: IpAddr
}
The goal being to end up with something like
myapp --http-address 0.0.0.0 --http-port 80 --monitoring-port 9876
There's some annoying semantic stuff that might make this impossible:
default_value? Does the type need to impl FromStr in such a way that it does the desired thing? Eg, in this case maybe default_value = "ip:port" where either part can be left out and not set a default for it. But then validation gets weird.Also unclear if there are more use cases. It fits well here, but who knows about other places!
This came up from thinking about https://github.com/rust-clique/clap-port-flag/pull/2#pullrequestreview-125409058.
This is implemented except the prefix feature
I hit a weird corner-case in flatten due to the lack of prefixing. The following will panic at runtime due to children structs having fields with the same name ("port"):
#[macro_use]
extern crate structopt;
#[derive(Debug, StructOpt)]
struct Opts {
#[structopt(flatten)]
incoming: Incoming,
#[structopt(flatten)]
outgoing: Outgoing,
}
#[derive(Debug, StructOpt)]
struct Incoming {
#[structopt(long = "port-in")]
port: u16,
}
#[derive(Debug, StructOpt)]
struct Outgoing {
#[structopt(long = "port-out")]
port: u16,
}
fn main() {
use structopt::StructOpt;
let cli = Opts::from_args();
println!("{:#?}", cli);
}
$ cargo run -q
thread 'main' panicked at 'Non-unique argument name: port is already in use', /home/lucab/.cargo/registry/src/github.com-1ecc6299db9ec823/clap-2.32.0/src/app/parser.rs:174:9
My current workaround is to disambiguate colliding fields with unique structopt-names, e.g.
#[structopt(name = "outgoing.port", long = "port-out")]
/cc @steveeJ FYI
I don't think the prefix thing is possible at compile time because we can't pass info between macro calls (every struct/enum is a separate macro call) but it seems we may be able generate code that dispatches this at runtime
@CreepySkeleton wouldn't we be able to do this by optionally making flatten a method?
#[structopt(flatten(prefix = "foo", suffix = "bar"))]
one thing I couldn't figure out is how would we make it work both as a sole identifier and as a method to not break current usage (without making it too ugly 馃槢)
@qmx It's not about precise syntax, it's about technical complexity. The main problem is: macro calls are independent, the expansion order is not defined, there's no way to pass some info from one invocation to another.
#[derive(StructOpt)] // first expansion
struct Opt {
// this is the place where we *actually* have
// the info needed
#[flatten(prefix = "foo")]
a: Foo,
}
#[derive(StructOpt)] // second expansion
struct Foo {
// this is the place where we need to *apply*
// this prefix info
#[structopt(short = "p", long = "port")]
port: u16,
}
We simply cannot pass this info on compile time. The only way to implement this is to generate code that dispatches it at runtime (i.e when user's program actually executes). This is how flatten implemented in the first place - but code for flatten is simple (~4 lines total). I really get shivers running down my spine every time I get to think about what it would take to implement this (ripping off the clap::App and rebuilding it at runtime... creepy).
one thing I couldn't figure out is how would we make it work both as a sole identifier and as a method to not break current usage
The syntax looks fine to me, could you explain what it would break, exactly?
We simply cannot pass this info on compile time. The only way to implement this is to generate code that dispatches it at runtime (i.e when user's program actually executes).
Ooooh, now I get what you meant! Thanks for taking the time to explain it!
Even if the implementation isn't that straightforward, would the project accept a PR for this functionality? Checking before I sink time trying to figure this out 馃槂
@qmx Um, I'm not the guy in charge here, you should be asking @TeXitoi. Personally, I have no objection
Any advancements on this issue? I'd really like to have this feature as well.
@oberien The real blocker here (apart from the lack of inter-expansion communication in proc-macros as mentioned above) is clap's API. To implement this we would need Arg::get_long(), Arg::get_short(), and Arg::get/set_name(&str) methods.
We need them because all we have at any flatten point is clap::Arg instance. To set a prefix/suffix we would need to figure out the base string to apply the prefix to and there's no way to get it for neither name, nor short, nor long.
You might say - "Come on, those fields are pub, just doc(hidden); go do the dirty trick and we're set up!", but... well, I don't know how @TeXitoi feels about it but I really don't think this is a good idea, even though clap v2.x branch is kind of frozen. I don't like setting a precedent like that, an act of usage of hidden API is worse than murdering a kitten.
But there is still a blink of light in the darkness: structopt is being imported directly into clap and, since it will be technically one repo, we could implement it with no problem.
I agree, I prefer avoiding these kind of dirty hacks.
Murdering a kitten is certainly rather worse than software development minutiae, but you have the point.
So is this the future direction for structopt?
I see this notice:
This crate is used by clap, and not meant to be used directly by consumers.
Or is this just until the stable clap_derive api is settled?
This warning explain that the derive crate should not be used directly, but the non derive crate export the functionalities.
This crate is used by clap, and not meant to be used directly by consumers.
For the record: this quote is from clap_derive, not from structopt. The wording is awkward though, we'll change it to something more descriptive. The meaning here is that the derive is pretty useless on it's own, without clap since the traits are defined in clap.
For the sake of having a place to direct future questions to, I'm giving the most comprehensive answer I can come up with to the questions I've been asked before at once:
structopt is going to continue working with clap 2.x, while clap_derive will be working with clap 3.x. structopt won't go anywhere, but will likely be receiving less attention from maintainers.clap_derive relates to structopt?clap_derive is "a new iteration of structopt". It will receive a number of new features, including this one. clap_derive be released? Is it being worked on?
Most helpful comment
For the record: this quote is from clap_derive, not from structopt. The wording is awkward though, we'll change it to something more descriptive. The meaning here is that the derive is pretty useless on it's own, without
clapsince the traits are defined inclap.For the sake of having a place to direct future questions to, I'm giving the most comprehensive answer I can come up with to the questions I've been asked before at once:
structoptis going to continue working with clap 2.x, whileclap_derivewill be working with clap 3.x.structoptwon't go anywhere, but will likely be receiving less attention from maintainers.clap_deriverelates tostructopt?The best description of
clap_deriveis "a new iteration of structopt". It will receive a number of new features, including this one.clap_derivebe released? Is it being worked on?We're working on it, albeit slowly. The release date is "when it's done", probably in a month or two, but no guarantees.