318 lines
11 KiB
Nim
318 lines
11 KiB
Nim
|
## argparse is an explicit, strongly-typed command line argument parser.
|
||
|
##
|
||
|
## Use ``newParser`` to create a parser. Within the body
|
||
|
## of the parser use the following procs/templates (read the individual
|
||
|
## documentation below for more details):
|
||
|
##
|
||
|
## =================== ===================================================
|
||
|
## Proc Description
|
||
|
## =================== ===================================================
|
||
|
## ``flag(...)`` boolean flag (e.g. ``--dryrun``)
|
||
|
## ``option(...)`` option with argument (e.g. ``--output foo``)
|
||
|
## ``arg(...)`` positional argument (e.g. ``file1 file2``)
|
||
|
## ``help(...)`` add a help string to the parser or subcommand
|
||
|
## ``command "NAME":`` add a sub command
|
||
|
## ``run:`` code to run when the parser is used in run mode
|
||
|
## ``nohelpflag()`` disable the automatic ``-h/--help`` flag
|
||
|
## =================== ===================================================
|
||
|
##
|
||
|
## The following special variables are available within ``run`` blocks:
|
||
|
##
|
||
|
## - ``opts`` - contains your user-defined options. Same thing as returned from ``parse(...)`` scoped to the subcommand.
|
||
|
## - ``opts.parentOpts`` - a reference to parent options (i.e. from a subcommand)
|
||
|
## - ``opts.argparse_command`` - a string holding the chosen command
|
||
|
## - ``opts.command`` - same as above (if there is no flag/option/arg named ``"command"``)
|
||
|
## - ``opts.argparse_NAMEOFCOMMAND_opts`` - an ``Option[...]`` that will hold the options for the command named ``NAMEOFCOMMAND``
|
||
|
## - ``opts.NAMEOFCOMMAND`` - Same as above, but a shorter version (if there's no name conflict with other flags/options/args)
|
||
|
##
|
||
|
## If ``Parser.parse()`` and ``Parser.run()`` are called without arguments, they use the arguments from the command line.
|
||
|
##
|
||
|
## By default (unless ``nohelpflag`` is present) calling ``parse()`` with a help
|
||
|
## flag (``-h`` / ``--help``) will raise a ``ShortCircuit`` error. The error's ``flag``
|
||
|
## field will contain the name of the flag that triggered the short circuit.
|
||
|
## For help-related short circuits, the error's ``help`` field will contain the help text
|
||
|
## of the given subcommand.
|
||
|
##
|
||
|
runnableExamples:
|
||
|
var res:string
|
||
|
var p = newParser:
|
||
|
help("A demonstration of this library in a program named {prog}")
|
||
|
flag("-n", "--dryrun")
|
||
|
option("--name", default=some("bob"), help = "Name to use")
|
||
|
command("ls"):
|
||
|
run:
|
||
|
res = "did ls " & opts.parentOpts.name
|
||
|
command("run"):
|
||
|
option("-c", "--command")
|
||
|
run:
|
||
|
let name = opts.parentOpts.name
|
||
|
if opts.parentOpts.dryrun:
|
||
|
res = "would have run: " & opts.command & " " & name
|
||
|
else:
|
||
|
res = "ran " & opts.command & " " & name
|
||
|
try:
|
||
|
p.run(@["-n", "run", "--command", "something"])
|
||
|
except UsageError:
|
||
|
stderr.writeLine getCurrentExceptionMsg()
|
||
|
quit(1)
|
||
|
assert res == "would have run: something bob"
|
||
|
|
||
|
runnableExamples:
|
||
|
var p = newParser:
|
||
|
help("A description of this program, named {prog}")
|
||
|
flag("-n", "--dryrun")
|
||
|
option("-o", "--output", help="Write output to this file", default=some("somewhere.txt"))
|
||
|
option("-k", "--kind", choices = @["fruit", "vegetable"])
|
||
|
arg("input")
|
||
|
|
||
|
try:
|
||
|
let opts = p.parse(@["-n", "--output", "another.txt", "cranberry"])
|
||
|
assert opts.dryrun == true
|
||
|
assert opts.output == "another.txt"
|
||
|
assert opts.input == "cranberry"
|
||
|
except ShortCircuit as err:
|
||
|
if err.flag == "argparse_help":
|
||
|
echo err.help
|
||
|
quit(1)
|
||
|
except UsageError:
|
||
|
stderr.writeLine getCurrentExceptionMsg()
|
||
|
quit(1)
|
||
|
|
||
|
runnableExamples:
|
||
|
var p = newParser:
|
||
|
command "go":
|
||
|
flag("-a")
|
||
|
command "leave":
|
||
|
flag("-b")
|
||
|
|
||
|
let opts = p.parse(@["go", "-a"])
|
||
|
assert opts.command == "go"
|
||
|
assert opts.go.isSome
|
||
|
assert opts.go.get.a == true
|
||
|
assert opts.leave.isNone
|
||
|
|
||
|
import std/macros
|
||
|
import strutils
|
||
|
import argparse/backend; export backend
|
||
|
import argparse/macrohelp; export macrohelp
|
||
|
|
||
|
proc longAndShort(name1: string, name2: string): tuple[long: string, short: string] =
|
||
|
## Given two strings, return the longer and shorter of the two with
|
||
|
## shortname possibly being empty.
|
||
|
var
|
||
|
longname: string
|
||
|
shortname: string
|
||
|
if name2 == "":
|
||
|
longname = name1
|
||
|
else:
|
||
|
if name1.len > name2.len:
|
||
|
longname = name1
|
||
|
shortname = name2
|
||
|
else:
|
||
|
longname = name2
|
||
|
shortname = name1
|
||
|
return (longname, shortname)
|
||
|
|
||
|
template newParser*(name: string, body: untyped): untyped =
|
||
|
## Create a new parser with a static program name.
|
||
|
##
|
||
|
runnableExamples:
|
||
|
var p = newParser("my parser"):
|
||
|
help("'{prog}' == 'my parser'")
|
||
|
flag("-a")
|
||
|
assert p.parse(@["-a"]).a == true
|
||
|
|
||
|
macro domkParser() : untyped {.gensym.} =
|
||
|
let builder = addParser(name, "", proc() = body)
|
||
|
builder.generateDefs()
|
||
|
domkParser()
|
||
|
|
||
|
template newParser*(body: untyped): untyped =
|
||
|
## Create a new command-line parser named the same as the current executable.
|
||
|
##
|
||
|
runnableExamples:
|
||
|
var p = newParser:
|
||
|
flag("-a")
|
||
|
assert p.parse(@["-a"]).a == true
|
||
|
|
||
|
macro domkParser(): untyped =
|
||
|
let builder = addParser("", "", proc() = body)
|
||
|
builder.generateDefs()
|
||
|
domkParser()
|
||
|
|
||
|
proc flag*(name1: string, name2 = "", multiple = false, help = "", hidden = false, shortcircuit = false) {.compileTime.} =
|
||
|
## Add a boolean flag to the argument parser. The boolean
|
||
|
## will be available on the parsed options object as the
|
||
|
## longest named flag.
|
||
|
##
|
||
|
## If ``multiple`` is true then the flag can be specified multiple
|
||
|
## times and the datatype will be an int.
|
||
|
##
|
||
|
## If ``hidden`` is true then the flag usage is not shown in the help.
|
||
|
##
|
||
|
## If ``shortcircuit`` is true, then when the flag is encountered during
|
||
|
## processing, the parser will immediately raise a ``ShortCircuit`` error
|
||
|
## with the ``flag`` attribute set to this flag's name. This is how the
|
||
|
## default help flag is implemented.
|
||
|
##
|
||
|
## ``help`` is additional help text for this flag.
|
||
|
runnableExamples:
|
||
|
var p = newParser("Some Thing"):
|
||
|
flag("--show-name", help="Show the name")
|
||
|
flag("-a", help="Some flag named a")
|
||
|
flag("-n", "--dryrun", help="Don't actually run")
|
||
|
|
||
|
let opts = p.parse(@["--show-name", "-n"])
|
||
|
assert opts.show_name == true
|
||
|
assert opts.a == false
|
||
|
assert opts.dryrun == true
|
||
|
|
||
|
let names = longAndShort(name1, name2)
|
||
|
let varname = names.long.toVarname()
|
||
|
builderStack[^1].components.add Component(
|
||
|
kind: ArgFlag,
|
||
|
help: help,
|
||
|
varname: varname,
|
||
|
flagShort: names.short,
|
||
|
flagLong: names.long,
|
||
|
flagMultiple: multiple,
|
||
|
shortCircuit: shortcircuit,
|
||
|
hidden: hidden,
|
||
|
)
|
||
|
|
||
|
proc option*(name1: string, name2 = "", help = "", default = none[string](), env = "", multiple = false, choices: seq[string] = @[], required = false, hidden = false) {.compileTime.} =
|
||
|
## Add an option to the argument parser. The longest
|
||
|
## named flag will be used as the name on the parsed
|
||
|
## result.
|
||
|
##
|
||
|
## Additionally, an ``Option[string]`` named ``FLAGNAME_opt``
|
||
|
## will be available on the parse result.
|
||
|
##
|
||
|
## Set ``multiple`` to true to accept multiple options.
|
||
|
##
|
||
|
## Set ``default`` to the default string value.
|
||
|
##
|
||
|
## Set ``env`` to an environment variable name to use as the default value
|
||
|
##
|
||
|
## Set ``choices`` to restrict the possible choices.
|
||
|
##
|
||
|
## Set ``required = true`` if this is a required option. Yes, calling
|
||
|
## it a "required option" is a paradox :)
|
||
|
##
|
||
|
## Set ``hidden`` to prevent the option usage listing in the help text.
|
||
|
##
|
||
|
## ``help`` is additional help text for this option.
|
||
|
runnableExamples:
|
||
|
var p = newParser:
|
||
|
option("-a", "--apple", help="Name of apple")
|
||
|
assert p.parse(@["-a", "5"]).apple == "5"
|
||
|
assert p.parse(@[]).apple_opt.isNone
|
||
|
assert p.parse(@["--apple", "6"]).apple_opt.get() == "6"
|
||
|
|
||
|
let names = longAndShort(name1, name2)
|
||
|
let varname = names.long.toVarname()
|
||
|
builderStack[^1].components.add Component(
|
||
|
kind: ArgOption,
|
||
|
help: help,
|
||
|
hidden: hidden,
|
||
|
varname: varname,
|
||
|
env: env,
|
||
|
optShort: names.short,
|
||
|
optLong: names.long,
|
||
|
optMultiple: multiple,
|
||
|
optDefault: default,
|
||
|
optChoices: choices,
|
||
|
optRequired: required,
|
||
|
)
|
||
|
|
||
|
proc arg*(varname: string, default = none[string](), env = "", help = "", nargs = 1) {.compileTime.} =
|
||
|
## Add an argument to the argument parser.
|
||
|
##
|
||
|
## Set ``default`` to the default ``Option[string]`` value. This is only
|
||
|
## allowed for ``nargs = 1``.
|
||
|
##
|
||
|
## Set ``env`` to an environment variable name to use as the default value. This is only allowed for ``nargs = 1``.
|
||
|
##
|
||
|
## The value ``nargs`` has the following meanings:
|
||
|
##
|
||
|
## - ``nargs = 1`` : A single argument. The value type will be ``string``
|
||
|
## - ``nargs = 2`` (or more) : Accept a specific number of arguments. The value type will be ``seq[string]``
|
||
|
## - ``nargs = -1`` : Accept 0 or more arguments. Only one ``nargs = -1`` ``arg()`` is allowed per parser/command.
|
||
|
##
|
||
|
## ``help`` is additional help text for this argument.
|
||
|
runnableExamples:
|
||
|
var p = newParser:
|
||
|
arg("name", help = "Name of apple")
|
||
|
arg("twowords", nargs = 2)
|
||
|
arg("more", nargs = -1)
|
||
|
let res = p.parse(@["cameo", "hot", "dog", "things"])
|
||
|
assert res.name == "cameo"
|
||
|
assert res.twowords == @["hot", "dog"]
|
||
|
assert res.more == @["things"]
|
||
|
|
||
|
builderStack[^1].components.add Component(
|
||
|
kind: ArgArgument,
|
||
|
help: help,
|
||
|
varname: varname.toVarname(),
|
||
|
nargs: nargs,
|
||
|
env: env,
|
||
|
argDefault: default,
|
||
|
)
|
||
|
|
||
|
proc help*(helptext: string) {.compileTime.} =
|
||
|
## Add help to a parser or subcommand.
|
||
|
##
|
||
|
## You may use the special string ``{prog}`` within any help text, and it
|
||
|
## will be replaced by the program name.
|
||
|
##
|
||
|
runnableExamples:
|
||
|
var p = newParser:
|
||
|
help("Some helpful description")
|
||
|
command("dostuff"):
|
||
|
help("More helpful information")
|
||
|
echo p.help
|
||
|
|
||
|
builderStack[^1].help &= helptext
|
||
|
|
||
|
proc nohelpflag*() {.compileTime.} =
|
||
|
## Disable the automatic ``-h``/``--help`` flag
|
||
|
runnableExamples:
|
||
|
var p = newParser:
|
||
|
nohelpflag()
|
||
|
|
||
|
builderStack[^1].components.del(0)
|
||
|
|
||
|
template run*(body: untyped): untyped =
|
||
|
## Add a run block to this command
|
||
|
runnableExamples:
|
||
|
var p = newParser:
|
||
|
command("dostuff"):
|
||
|
run:
|
||
|
echo "Actually do stuff"
|
||
|
|
||
|
add_runproc(replaceNodes(quote(body)))
|
||
|
|
||
|
template command*(name: string, group: string, content: untyped): untyped =
|
||
|
## Add a subcommand to this parser
|
||
|
##
|
||
|
## ``group`` is a string used to group commands in help output
|
||
|
runnableExamples:
|
||
|
var p = newParser:
|
||
|
command("dostuff", "groupA"): discard
|
||
|
command("morestuff", "groupB"): discard
|
||
|
command("morelikethefirst", "groupA"): discard
|
||
|
echo p.help
|
||
|
add_command(name, group) do ():
|
||
|
content
|
||
|
|
||
|
template command*(name: string, content: untyped): untyped =
|
||
|
## Add a subcommand to this parser
|
||
|
runnableExamples:
|
||
|
var p = newParser:
|
||
|
command("dostuff"):
|
||
|
run:
|
||
|
echo "Actually do stuff"
|
||
|
p.run(@["dostuff"])
|
||
|
command(name, "", content)
|
||
|
|