Not exactly an answer, but I’ll take the opportunity to point out that Bun has a shell feature which makes it easy to mix and match JS and Bash in the same script, and it provides a compatibility layer for Windows users so that you don’t have to worry about platform differences in shell capabilities. bun.sh/guides/runtime/shell
Agreed. As nice as clap is, it’s not a combinator. Parser combinators have a the really nice feature of sharing the same “shape” as the data they parse, which makes them trivial to generate from a schema … or to just use them to represent your schema in the first place ;) .
Mentioned this to the other commenter, but this doesn’t use the type system to enforce the mutual exclusivity constraint. In Rust, the main way to do that via the type system is through enums.
I was gonna say, I feel like the current method does a good enough job documenting that validation has happened, but I guess you do want it reflected in the structure of the type, so that the code that takes the information from the struct can safely make the assumption that some of the options don’t exist. And then, yeah, it would be nice to not need a separate parsing step for that.
It’s an understandable interpretation for the lexical use of or which can imply exclusive disjunction.
In Rust the result type has the method .or() which returns either Ok(A) or Ok(B) (but not both), and I don’t see clambering to change it to xor, because the exclusive nature is implicit both linguistically and in the type state.
The result type in rust does not return a true/false but a type. More importantly though, it doesn’t return err if both values are set but simply returns the first value:
So… It’s not only not mapping your input to truth values, it also behaves more like I’d expect an “or” to behave, which is not “xor” or, if there’s more than two inputs, “exactly one”, but succeeding if any input is set.
…Which is basically how the OP’s or function also works, it takes several Option<T>s and returns the first valid one (and only that one), it doesn’t operate on boolean logic types — it’s a valid lexical use of or.
Another classic. Pick one output format: JSON, YAML, or XML. But definitely not two.
Emphasis mine.
It takes the input and fails if there is more than one valid one, which decidedly isn’t what’s an “or” in comp sci.
azertyfun@sh.itjust.works
on 07 Sep 09:54
collapse
Counterpoint: Yes, parse don’t validate, but CLIs should not be dealing with dependency management.
I love Python’s argparse because:
It’s “Parse, don’t validate” (even supports FileType as a target)
It enforces or strongly encourages good CLI design
Required arguments should in most situations be positional arguments, not flags. It’s curl <URL> not curl --url <URL>.
Flags should not depend on each other. That usually indicates spaghetti CLI design. Don’t do server --serve --port 8080 and server --reload with rules for mix-and-matching those, do server serve --port 8080 and server reload with two separate subparsers.
Mutually exclusive flags sometimes make sense but usually don’t. Don’t do –xml --json, do -f [xml|json].
This or( pattern of yours IMO should always be replaced by a subparser (which can use inheritance!). As a user the options’ data model should be immediately intuitive to me as I look at the –help and having mutually exclusive flags forces the user to do the extra work of dependency management. Don’t do server --env prod --auth abc --ssl, do server serve prod --auth abc --ssl where prod is its own subparser inheriting from AbstractServeParser or whatever.
Thinking of CLI flags as a direct mapping to runtime variables is the fundamental mistake here I think. A CLI should be a mapping to the set(s) of behavior(s) of your application. A good CLI may have mandatory positional arguments but has 0 mandatory flags, 0 mutually exclusive flags, and if it implements multiple separate behaviors should be a tree of subparsers. Any mandatory or mutually exclusive flags should be an immediate warning that you’re not being very UNIX-y in your CLI design.
I’ve used the node.js version of argparse, which as I understand it, is a clone of the python implementation and I’ve not seen how to do mutually exclusive flags. Mind you, at the time I didn’t need them, so it wasn’t an issue, but I don’t recall seeing any way to do it either.
Did I miss something?
azertyfun@sh.itjust.works
on 07 Sep 23:47
collapse
However I’ve never had to use that feature. Like I said it can make sense in specific contexts but it is a pretty strong indicator that you have built in a CLI antipattern or too much complexity.
threaded - newest
lexi-lambda.github.io/…/parse-don-t-validate/ - Edit: Ah I see, the author of this article is pointing to this older article too and admits being influenced by it. So never mind.
Oh boy … very cool.
Now how do I do this in bash?
Not exactly an answer, but I’ll take the opportunity to point out that Bun has a shell feature which makes it easy to mix and match JS and Bash in the same script, and it provides a compatibility layer for Windows users so that you don’t have to worry about platform differences in shell capabilities. bun.sh/guides/runtime/shell
I like the concept, and it’s great in TS. Unfortunately, not as doable in other languages.
I’m a bit curious if it’s possible to extend
clap
to do this in Rust though (specifically mutually-exclusive arg groups).clap already supports all this: docs.rs/clap/latest/clap/struct.Arg.html#method.c… It’s just a great library, having you could think of and applying the same parse-don’t-validate mentality.
This doesn’t represent the mutual exclusivity through the type system (which is what the article is all about).
I love clap and I use it a lot, but the only way to represent the exclusivity through the type system in Rust is through an enum.
Agreed. As nice as clap is, it’s not a combinator. Parser combinators have a the really nice feature of sharing the same “shape” as the data they parse, which makes them trivial to generate from a schema … or to just use them to represent your schema in the first place ;) .
Clap has dependent options and mutually-exclusive argument groups built-in: docs.rs/clap/latest/clap/_derive/…/index.html#arg…
For the environment-specific requirements, you can use compiler feature flags…
Mentioned this to the other commenter, but this doesn’t use the type system to enforce the mutual exclusivity constraint. In Rust, the main way to do that via the type system is through enums.
Ah, fair enough. Not sure how to do that then.
I was gonna say, I feel like the current method does a good enough job documenting that validation has happened, but I guess you do want it reflected in the structure of the type, so that the code that takes the information from the struct can safely make the assumption that some of the options don’t exist. And then, yeah, it would be nice to not need a separate parsing step for that.
I’m not sure the value added is worth the extra layer.
I guess my command line options just aren’t all that complicated.
Using “or” to define a function that does “xor”… Did that guy never hear about formal logic? That’s, like, first or second semester stuff…
sigh
Could have used
oneOf
orexactlyOne
, butor
is definitely a bad choice..
It’s an understandable interpretation for the lexical use of or which can imply exclusive disjunction.
In Rust the result type has the method
.or()
which returns eitherOk(A)
orOk(B)
(but not both), and I don’t see clambering to change it toxor
, because the exclusive nature is implicit both linguistically and in the type state.The result type in rust does not return a true/false but a type. More importantly though, it doesn’t return err if both values are set but simply returns the first value:
<img alt="" src="https://feddit.org/pictrs/image/0a0f53fb-1243-438d-87e7-22c0725c2001.jpeg">
So… It’s not only not mapping your input to truth values, it also behaves more like I’d expect an “or” to behave, which is not “xor” or, if there’s more than two inputs, “exactly one”, but succeeding if any input is set.
…Which is basically how the OP’s
or
function also works, it takes severalOption<T>
s and returns the first valid one (and only that one), it doesn’t operate on boolean logic types — it’s a valid lexical use ofor
.Absolutely not.
Emphasis mine.
It takes the input and fails if there is more than one valid one, which decidedly isn’t what’s an “or” in comp sci.
Counterpoint: Yes, parse don’t validate, but CLIs should not be dealing with dependency management.
I love Python’s
argparse
because:FileType
as a target)curl <URL>
notcurl --url <URL>
.server --serve --port 8080
andserver --reload
with rules for mix-and-matching those, doserver serve --port 8080
andserver reload
with two separate subparsers.–xml --json
, do-f [xml|json]
.or(
pattern of yours IMO should always be replaced by a subparser (which can use inheritance!). As a user the options’ data model should be immediately intuitive to me as I look at the–help
and having mutually exclusive flags forces the user to do the extra work of dependency management. Don’t doserver --env prod --auth abc --ssl
, doserver serve prod --auth abc --ssl
whereprod
is its own subparser inheriting fromAbstractServeParser
or whatever.Thinking of CLI flags as a direct mapping to runtime variables is the fundamental mistake here I think. A CLI should be a mapping to the set(s) of behavior(s) of your application. A good CLI may have mandatory positional arguments but has 0 mandatory flags, 0 mutually exclusive flags, and if it implements multiple separate behaviors should be a tree of subparsers. Any mandatory or mutually exclusive flags should be an immediate warning that you’re not being very UNIX-y in your CLI design.
I’ve used the node.js version of argparse, which as I understand it, is a clone of the python implementation and I’ve not seen how to do mutually exclusive flags. Mind you, at the time I didn’t need them, so it wasn’t an issue, but I don’t recall seeing any way to do it either.
Did I miss something?
docs.python.org/3/library/argparse.html#argparse.…
However I’ve never had to use that feature. Like I said it can make sense in specific contexts but it is a pretty strong indicator that you have built in a CLI antipattern or too much complexity.