I have a case where I would like a *NIX command-line program to be able to take a negative number as an argument:
numprog -2
Normally, the way I'd do this is intercept whatever the argument array is, scan for anything that looks like a negative number, remove it (but save it), and then pass that modified array on to whatever module parses command line options.
It's easy enough to get the array:
let args = commandLineParams()
And it's easy enough to search for the negative number and remove it from (in this case) the sequence.
But in the specific case above (where there's only one argument, and that argument is a negative number), you end up with an empty sequence. And if you pass that to initOptParser(), the behavior is to revert back to the unmodified list of args. As the docs put it:
If cmdline == "", the real command line as provided by the os module is retrieved instead if it is available. If the command line is not available, a ValueError will be raised.
One could argue that the whole use case is flawed, because the POSIX-ly correct way to pass a negative number to a *NIX program is something like:
numprog -- -2
Because per POSIX:
The first -- argument that is not an option-argument should be accepted as a delimiter indicating the end of options. Any following arguments should be treated as operands, even if they begin with the '-' character.
But that doesn't appear to work with Nim's parseopt module at all. The "--" is interpreted as a cmdLongOption, and the -2 is interpreted as a cmdShortOption (with the value "2").
Anything one can do here (without implementing a customized parseopt)?
Wow, this looks terrific (not to mention a lot of hard work)!
Since you've obviously spent some time thinking about all of this, do you have an opinion on whether the stdlib should support either of these behaviors? The first one (not being able to manipulate the raw argv) feels like an inconvenience only when dealing with a very narrow set of edge cases, even if it's a trivial thing to do in C. I'm less sure about the latter, though. Using "--" in this way seems like a pretty ordinary way to do things in (any?) shell.
Well, I called it "parseopt3" 7 years ago because I thought I might try to get it into the stdlib. In the early days, cligen was just cligen.nim, argcvt.nim, and then parseopt3.nim. Had I tried to get it in to stdlib (or just tolerated the stdlib diffs), it would have been only 2 modules. At the time, stdlib still had to figure out how to get rid of parseopt2 and PRs often seemed overly disputatious.. AFAICT we still have not figured out a way to evolve the stdlib and nimble is still what all the tutorials recommend and still fragile.
In the interim, parseopt3 grew a few more features (much less than the whole package gew). Just from memory, I think "option normalization", "auto-lengthening" (matching shortest unique prefix) & "operators" (+=).
The last is a deficit of most other CLIs, but cligen has the ability to "clear" aggregates. So, instead of cc -nostdinc -Ithis -Ithat, were cc written with cligen you could say cc -I,= -Ithis -Ithat, only with any of the many kinds of aggregates - seq[T], set[enum], etc. for any param of any command - either auto-supported by argcvt or added by users in the scope of dispatchGen instantiation. (--help-syntax says more).
Anyway, not being in the stdlib, but also being a natural "try out Nim for little CLI tools and deal with nimble much later" leaf dependency, I felt very free to pile things into cligen/ that might be generally useful for CLI tools. I added a process pool for only and a trie and ternary search tree and abbreviation system and libmagic wrapper for lc, some memory mapped file utils, fast IO/parsing/printing utils, and even a mini (like 30 lines)-generic formatting/templating/macro processor engine.. I've even heard it called "like the stdlib". I have been trying to be more concern separating since I got "mission creep" pushback.
While maybe interesteing context, that doesn't quite answer your question about these behaviors alone belonging in the stdlib. Araq often likes to depart from Unix conventions since he likes Windows more, but in my experience he is pretty open minded about "optional" things that are "cross-compatible" and don't seem hard to maintain (e.g. in this context). Symbol-table free parsing also plays a role while I surely don't worry about that in cligen { nor does the aboriginal getopt(3) }. Indeed, the very "essence" of cligen is that code APIs already provide the symbol table for you. So, it saves CLauthors from needing to repeat very much APIauthors' work (often the same person, obviously).
So, I'd say if you can find a way to make it work compatibly then there is a reasonable chance of getting a PR in. I also think it would be a reasonable "first contribution" effort and would encourage you to try. Or maybe you will find you cannot make it work & answer your own question better than I ever could. :-)
Well, that's a lot to think about (and I am very new to this language).
I know a lot about UNIX and basically nothing about Windows, so I'm really not in any position to say what is and is not "cross-compatible." In fact, I suppose I'm not really sure whether that means "CLI behavior that can be reasonably expected across all platforms that offer shell-like interfaces" or "functionality in the stdlib that is meaningful on one platform, but not others, and labeled as such."
But one solution, certainly, is cligen -- which, I gather, is something close to a "CLI framework." Sounds good to me!
I'm a bit distressed to hear that there isn't a lot of consensus on how to evolve the stdlib. I imagine this is a complicated matter for all language designers (and language communities). I'm also surprised to hear nimble described as "fragile" -- especially since, as you say, it is universally recommended.
Where is the problem:
include std / prelude
var number = 0
for i in 1 .. paramCount():
number = parseInt(paramStr(i))
echo "last number is ", number
Your dash handling doesn't fit parseopt, can happen. So use the primitives instead and build your own. Or use cligen. But the standard library is quite decent, all things considered.