wmtools/nimbledeps/pkgs2/argparse-4.0.1-e9c2ebe3f74b1dfc4df773686ae6dab7638a8662/argparse.nim
2023-11-23 20:10:04 +00:00

317 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)