Open epage opened 2 years ago
Comment by epage Friday Aug 13, 2021 at 20:52 GMT
Note to self: when implementing this, look into also implementing #2688
Comment by kbknapp Saturday Aug 14, 2021 at 00:53 GMT
The idea of using helper constructors positional
, flag
, or option
is a great idea IMO instead of Arg::new
resulting in a not "always valid object." In fact I've seen several binaries do almost exactly this with type aliases and macros, ripgrep
being the largest (at least they did this a while back when I looked...not sure about the current status).
Comment by pksunkara Saturday Aug 14, 2021 at 00:54 GMT
From the discussion,
If we want takes_value to be true by default,
Arg::new
would havetakes_value(true)
set, which means it is a valid positional. Which is why I was saying it will be always valid and we don't need helper constructors
Comment by epage Saturday Aug 14, 2021 at 00:55 GMT
Yeah, I got the idea from ripgrep which I copied until I switched to Structopt
. I did a quick look earlier as part of this conversation and it looks like burntsushi has moved away from it.
Comment by kbknapp Saturday Aug 14, 2021 at 01:18 GMT
@pksunkara I was thinking more about the long game of leave Arg::new
as it is for v3, but deprecate it and introduce three constructors that are more explicit about what they're doing. Or least discuss doing so, even if it's not until v4.
Just for backstory; I think the point I was trying to make in the thread between @epage and I was that currently since Arg::new
returns a valid positional argument, when one simply adds short
or long
to it, clap implicitly unsets takes_value(true)
turning the argument into a flag which is somewhat surprising to the user (at least until they learn that's how it works). Now it's not that big a deal, and I doubt this particular issue has caused anyone true confusion.
But this minor constructor problem is indicative of certain other similar cases in clap that can/do either cause confusion for the consumer, or ambiguities for clap parsing itself. Ambiguities aren't the worst thing, but they lead to implicit parsing rules, that if not specified somewhere can cause subtle breakage or cause unintended consequences as the API grows or evolves.
clap's original design(tm) was deliberately different from something like getopt
which had distinct argument types (i.e. at the type system level). I found that most often I would iterate over my CLI design and change between various arg types fluidly while coming up with something that felt right for the particular tool I was working on. APIs like getopt
made that iteration harder because changing an arguments type was more involved. Whereas in clap it usually meant either adding or removing a single method call and that's it. Now as the clap API grew this caused some minor issues, like why does a "flag" effectively have all the methods for handling values? But it had a huge benefit that if I wanted to turn my flag into an option which I just added a single method call for that nifty new thing I wanted to try and done. Didn't work and back to a flag? Just comment out that single line again. It also had the benefit of being able to treat positional values and values of options as identical, which most of the time is nice (but it's not without oddities). Yes, that can be done through traits as well, but then you either end up with massive code bloat via generics along with the pain that threading these generics through the call stacks can be, or the slower dynamic dispatch.
Hopefully that makes sense, or at least captures some the "why" from the early days :smile:
Comment by epage Tuesday Aug 17, 2021 at 16:41 GMT
Thanks for the background!
Unless someone has an alternative idea on how things might work, I don't think Arg::new
can ever go away.
fn positional(name: &str);
fn flag(name: &str);
fn option(name: &str);
Arg::new
flag
and option
are "valid", short
or long
is still neededlong
from name
if no flag is no long/short is set. Might confuse users when adding a short removes the longlong
from name
generally. This turns it into an implicit version of the "Helper" solution below and needs Arg::new
fn postional(name: &str);
fn flag(name: &str, long: Option<&str>, short: Option<char>);
fn option(name: &str: long: Option<&str>, short: Option<char>);
None, None
fn new(name: &str);
fn postional(name: &str);
fn flag(name: &str, long: &str);
fn option(name: &str: long: &str);
flag
and option
are always valid but you can't have a short-only flag/optionArg::new
needed for the 1% case of short-only
Issue by epage Friday Aug 13, 2021 at 19:27 GMT Originally opened as https://github.com/clap-rs/clap/issues/2687
Discussed in https://github.com/clap-rs/clap/discussions/2674