c-blake / cligen

Nim library to infer/generate command-line-interfaces / option / argument parsing; Docs at
https://c-blake.github.io/cligen/
ISC License
509 stars 24 forks source link

Limiting option values #93

Closed SolitudeSF closed 5 years ago

SolitudeSF commented 5 years ago

Is there a builtin way to provide set of acceptable values for an option?

c-blake commented 5 years ago

On the parsing/input side, you can cut/paste redefine (post import and pre-dispatch) any of the standard ones from cligen/argcvt to do whatever logic/sanitizing of user input. You can always switch off the value of ArgcvtParams.parNm on a per parameter basis. On the output side there is argHelp which also receives ArgcvtParams and could list acceptable values to the user on a per-param basis. distinct types can potentially help if you are also the author of the API being wrapped and want to switch in "batches" of similarly typed parameters. cligen does understand enum types pretty well, too, if your use case fits in with those.

c-blake commented 5 years ago

You can look at test/FancyRepeats.nim for e.g. usage of parNm and test/Enums.nim for enum usage. There's really quite a bit of CLI-author control, but I don't know how much any of it exactly counts as what you mean by "builtin".

SolitudeSF commented 5 years ago

i guess enums work close enough, at least for limiting strings.

c-blake commented 5 years ago

Cool. The enum approach has other bonuses/drawbacks like if you use the value in a Nim case statement it will help you check that you handle all possible values (unless you put an else in there). If you want to keep them strings (and not convert to string via $ on the enum) then you could also do argParse/argHelp overloads for HashSet[string]. The parNm approach is, of course, very general..maybe too general (hence automating too little) unless you really need it. I wasn't sure if you meant things like "ints < 314" or something from your brief question.

SolitudeSF commented 5 years ago

nah, i meant just simple check if the value is in this limited set of acceptable values. the enums work ok for strings. sadly im too unfamiliar with the library to understand workarounds you're proposing.

well thinking about it, there is almost no case where there is limited set of acceptable values for option that is not a string. and for some edgecases you could just define argParse for distinct type

c-blake commented 5 years ago

What you say is right, but if even the distinct type doesn't get you where you want to be, you can re-define the overloads for argHelp and argParse for that type of parameter the way test/FancyRepeats.nim does. It sounds like you get it, but I'll elaborate just in case some passer by finds this thread via search.

FancyRepeats is after a different goal -- getting -vvvv means very verbose behavior, but the technique is the same. Inside your overloads you can handle the edge case one way and the regular int case the other. The FancyRepeats help calls that user-visible type "countr" (just a lame name I invented). For my "ints < 314" example constraint, the help might say "0..314" instead of "countr" to guide the CLI user. And in the argParse , instead of the inc(dst) different-style branch, it could still parseInt as normal in both branches (or just before a branch), but then check that the number is "in range" and error out if not. So, the parNm business is very general, but you have to do switching work yourself (within the Nim type for the argParse/argHelp, anyway). So, fully fleshed out if you replaced the special argParse/argHelp of test/FancyRepeats.nim then it would make users have to give a number in 0..314 for verb:

  proc argParse*(dst: var int, dfl: int; a: var ArgcvtParams): bool =
    if parseInt(strip(a.val), dst) == 0:
      a.msg = "Bad value: \"$1\" for option \"$2\"; expecting int\n$3" %
              [ a.val, a.key, a.help ]
      return false
    if a.parNm == "verb":               # make "verb" a special kind of int
      if verb < 0 or verb > 314:
        a.msg = "Bad value: \"$1\" for option \"$2\"; expecting 0..314\n$3" %
                [ a.val, a.key, a.help ]
      return false
    return true

  proc argHelp*(defVal: int, a: var ArgcvtParams): seq[string] =
    if a.parNm == "verb":    # This is a parNm-conditional limited int
      result = @[ a.argKeys, "0..314", a.argDf($defVal) ]
    else:
      result = @[ a.argKeys, "int", a.argDf($defVal) ]
c-blake commented 5 years ago

Of course Nim has ranged int types, too, but with the above technique you can implement basically arbitrary logic on a per-parameter basis.