Closed aMOPel closed 2 years ago
You're welcome. In my experience, good default values do "usually" exist and Nim already has the simpler routine(a=b)
abilities and the Number 1 rule of cligen
is to not burden common case users with concerns of the perfectly general. Various feedback has indicated that this is the core driver of its popularity/user-base.
If you just want a very smart/fancy CLI that has the abilities to solve your "unsolvables" then you may want to take a good look at test/ParseOnly.nim
. This is much less convenient to use/requires more work. However, it gives a few superpowers that ordinary Nim proc invocations do not have (whether the user passed CL-arguments that happen to equal default values and even in what order they were issued, if that winds up mattering).
If you are ok with your final = none int
syntax then it might be/(should be?) already feasible to also just do your own argcvt.nim
-like converters for Option[T]
types. I don't personally use these much/ever, but if you come up with some good new argcvt
material then I am happy to merge a PR.
I am closing this since I am sure that at least parseOnly
can solve the concrete use cases you mentioned (at some CLauthor work), but I am very open to discussing adding Option[T]
converters to cligen/argcvt.nim
.
I think by doing things at the argcvt
-level, you also allow CLauthors to decide (perhaps on a per wrapped T type Option[T]
basis) if a parse error of CL input yields a visible user exception/error message or a silent "as if none
" follow-on. { Yes, that may not often be the best idea, but its possibility might be desired flexibility for someone else..Maybe "warn & continue". }
I should also have said that if you do figure out an argcvt
-style way to make your Option[T]
types work, and for whatever reason do not want to submit a PR, then we can put it on the github Wiki or if you just tell me with some examples I can try to do it. I'm just in the middle of something else at this very moment.
So, for example, this (sort of) works:
import std/options
# proc foo*(optionalInt = option(1)): int = # also works
proc foo*(optionalInt = none int, neededFloat: float): int =
echo optionalInt
when isMainModule:
import cligen, cligen/argcvt
proc argParse[T](dst: var Option[T], dfl: Option[T],
a: var ArgcvtParams): bool =
var uw: T # An unwrapped value
if argParse(uw, (if dfl.isSome: dfl.get else: uw), a):
dst = option(uw)
return true
proc argHelp[T](dfl: Option[T], a: var ArgcvtParams): seq[string] =
@[a.argKeys, $T, (if dfl.isSome: $dfl.get else: "NONE")]
dispatch foo
It probably has some trouble with parameters that are other special wrapped generics like set[T]
at least in terms of help message consistency.
Also, possibly simpler for you depending upon your use cases, as in the above example with neededFloat
, if you do not syntactically put a default value (with "= something"
) in your proc
signature then it becomes a required parameter. This means the user must enter something for that parameter key to get a CL to parse (though it could be the short/1-letter variant). So, it is also sort of a solution to "no way to assign a meaningful default" (in both Nim and at the command-line, I believe).
Oh, and one other way to "detect setting by the user" that is simpler than my first mentioned parseOnly
is your own argParse
that references a global variable defined before your wrapped proc
like this:
import cligen, cligen/argcvt
var setXyz = false
proc argParse(dst: var int, dfl: int, a: var ArgcvtParams): bool =
if a.parNm == "xyz": setXyz = true
argcvt.argParse dst, dfl, a # module qualify to not recurse
proc foo(xyz=0, abc=2) =
if setXyz: echo "user set Xyz"
else : echo "user let Xyz default"
dispatch foo
test/FancyRepeats.nim
and test/FancyRepeats2.nim
have some other use cases of that. So, you really have a few choices.
Thank you. I will look into it. I didn't dive into the argcvt API yet, but the snippet looks very promising.
import cligen, cligen/argcvt proc argParse[T](dst: var Option[T], dfl: Option[T], a: var ArgcvtParams): bool = var uw: T # An unwrapped value if argParse(uw, (if dfl.isSome: dfl.get else: uw), a): dst = option(uw) return true proc argHelp[T](dfl: Option[T], a: var ArgcvtParams): seq[string] = @[a.argKeys, $T, (if dfl.isSome: $dfl.get else: "NONE")]
This works pretty well. The help messages can get a little inconsistent, when wrapping a wrapper type, as you pointed out. I tried it with none seq[string]
and got
-n=, --newValue= seq[string] NONE set newValue
compared to just seq[string]
with default @[""]
-n=, --newValue= strings "" append 1 val to newValue
It's good enough for my purposes, though. Thank you again.
You're welcome. Much of cligen
makes "good enough - given how automatic" trade offs.
The way you are using it, the default value is not really @[""]
anyway, right? The sense of this Option[T]
construction is to "hide the real default" from cligen
by putting it in the logic of the proc
body instead of the signature. The way to tell CLusers what "unspecified" means would be in the help
string for such parameters instead of the default value column. Maybe "?"
would have been a better choice than "NONE"
to emphasize a "hidden if
"? { In theory, we could issue a compile-time warning on not providing a help["thatParam"]
.. }
I did just try it out with an Option[seq[T]]
and the "incremental" operators break. I.e., in:
import std/options
proc foo*(sq = none seq[int]): int = echo sq
when isMainModule:
import cligen, cligen/argcvt
proc argParse(dst: var Option[seq[int]], dfl: Option[seq[int]],
a: var ArgcvtParams): bool =
var uw: seq[int]
if argParse(uw, (if dfl.isSome: dfl.get else: uw), a):
dst = option(uw); return true
proc argHelp(dfl: Option[seq[int]], a: var ArgcvtParams): seq[string] =
@[a.argKeys, "seq[int]", (if dfl.isSome: $dfl.get else: "?")]
dispatch foo
in a file topt.nim
you see:
nim r topt -s,+=,2,3 -s,+=,4
report some(@["4"])
instead of some(@["1","2","3","4"])
. Those are kind of advanced CLuse cases, though other things may also break. Any such breakage can surely be fixed in more type specific overloads like Option[seq[T]]
or possibly fixed in general with more CT magic in cligen/argcvt.nim
or maybe a lot of when
s everywhere. Such details should be worked out before we add it to cligen/argcvt
, but I'd reiterate that I am open to PRs on this.
For now, if you find yourself using this a lot, you can put those general Option[T]
definitions in some cgOpts.nim
file and then just import cligen; include cgOpts
. You should also be able to replace the whole argcvt
module with a subdir in your project called cligen/
& a module there called argcvt.nim
. (Used to work, but haven't tested it lately.)
This is complex enough that it bears reiterating that I suspect, for most users most of the time, a required parameter with no default value at all or a global var with a specific test is enough - what I might call "one-level optionality" -- which has no helpGen/prog.lang complexity. This "two-level optional" idea with Option[T]
default values may "over model optionality" { but, sure, I can imagine times such might be nice, e.g. when you want the Nim API part to use Option[T]
- Nim Is Choice! :-) }.
Incredibly handy library. Thank you a lot.
What do you think about integrating std/options into the current api?
Atm, when you want to express that an argument is optional, you have to give it a default value. But some types don't offer a good default value, which is devoid of meaning. Eg enums.
This issue gets even worse, when you're using the full range of a type.
with std/options you could express optional values like this: