Skip to content

kaushalmodi/cligen

 
 

Repository files navigation

cligen: A Native API-Inferred Command-Line Interface Generator For Nim

This approach to CLIs comes from Andrey Mikhaylenko's nice Python module 'argh'. Much as with Python, an intuitive subset of ordinary Nim calls maps cleanly onto command calls, syntactically and semantically. For such procs, cligen can automatically generate a command-line interface complete with long and short options and a nice help message. In Nim terms, adding a CLI can be as easy as adding a single line of code:

proc foobar(foo=1, bar=2.0, baz="hi", verb=false, paths: seq[string]): int =
  ##Some existing API call
  result = 1          # Of course, real code would have real logic here

when isMainModule: import cligen; dispatch(foobar)    # Whoa...Just one line??

Compile it to foobar (assuming nim c foobar.nim is appropriate, say) and then run ./foobar --help to get a minimal (but not so useless) help message:

Usage:
  foobar [optional-params] [paths]
Some existing API call

Options (opt&arg sep by :,=,spc):
  -h, --help                  print this help message
  -f=, --foo=  int     1      set foo
  -b=, --bar=  float   2.0    set bar
  --baz=       string  "hi"   set baz
  -v, --verb   toggle  false  set verb

Other invocations (foobar --foo=2 --bar=2.7 ...) all work as expected.

When you want to produce a better help string, tack on some parameter-keyed metadata with Nim's association-list literals:

  dispatch(foobar, help = { "foo" : "the beginning", "bar" : "the rate" })

Often this is all that is required to have a capable and user-friendly CLI, but some more controls are provided for more subtle use cases.

If you want to manually control the short option for a parameter, you can just override it with the 5th|short= macro parameter:

  dispatch(foobar, short = { "bar" : 'r' }))

With that, "bar" gets 'r' while "baz" gets 'b' as short options. To suppress some long option getting a short option at all, specify '\0' for its short key. To suppress all short options, give short a key of "".

By default, dispatchGen has requireSeparator=false making -abcdBar, -abcd Bar, --delta Bar or --delta=Bar all acceptable syntax for command options. Additionally, long option keys can be spelled flexibly, e.g. --dry-run or --dryRun, much like Nim's style-insensitive identifiers.

The same basic string-to-native type converters used for option values will be applied to convert optional positional arguments to seq[T] values or mandatory positional arguments to values of their types:

proc foobar(myMandatory: int, mynums: seq[int], foo=1, verb=false): int =
  ##Some API call
  result = 1          # Of course, real code would have real logic here
when isMainModule:
  import cligen; dispatch(foobar)

If it makes more sense to echo the result of the proc than convert its result to an 8-bit exit code, just pass echoResult=true:

import cligen, strutils   # generate a CLI for Nim stdlib's editDistance
dispatch(editDistance, echoResult=true)

If you want to expose two or more procs into a command with subcommands a la git or nimble, just use dispatchMulti in, say, a cmd.nim file. Each [] list in dispatchMulti is the argument list for each sub-dispatch. Tune command syntax and help strings in the same way as dispatch as in:

proc foo(myMandatory: int, mynums: seq[int], foo=1, verb=false) =
  ##Some API call
proc bar(myHiHo: int, myfloats: seq[float], verb=false) =
  ##Some other API call
when isMainModule:
  import cligen; dispatchMulti([foo, short={"verb": 'v'}], [bar])

With that, a user can run ./cmd foo -vm1 or ./cmd bar -m10 1.0 2.0. ./cmd --help will emit a brief help message and ./cmd help emits a more comprehensive message, while ./cmd subcommand --help emits just the message for subcommand.

That's basically it. Many users who have read this far can start using cligen without further delay, simply entering illegal commands or --help to get help messages that exhibit the basic mappings. Default help tables play well with automated "help to X" tools such as complete -F _longopt in bash, compdef _gnu_generic in zsh, or the GNU help2man package. Many simple examples are at test/. Here are some more DETAILS, and even more is in the module documentations ( parseopt3 argcvt cligen ) and RELEASE-NOTES.

More Motivation

There are so many CLI parser frameworks out there...Why do we need yet another? This approach to command-line interfaces has both great Don't Repeat Yourself ("DRY", or relatedly "a few points of edit") properties and also has nice "loose coupling" properties. cligen need not even be present on the system unless you are compiling a CLI executable. Similarly, wrapped routines need not be in the same module, modifiable, or know anything about cligen. This approach is great when you want to maintain both an API and a CLI in parallel. Easy dual API/CLI maintenance encourages preserving access to functionality via API/"Nim import". When so preserved, this then eases complex uses being driven by other Nim programs rather than by shell scripts (once usage complexity makes scripting language limitations annoying). Finally, and perhaps most importantly, the learning curve/cognitive load and even the extra program text for a CLI is all about as painless as possible - mostly learning what kind of proc is "command-like" enough, various minor controls/arguments to dispatch to enhance the help message, and the "binding/translation" between proc and command parameters. The last is helped a lot by the auto-generated help message.

About

Nim library to infer and generate command-line-interfaces

Resources

License

Stars

1 star

Watchers

1 watching

Forks

Packages

 
 
 

Contributors

Languages

  • Nim 99.8%
  • Shell 0.2%