fixed wallpapurr for when config file does not exist (gracefully exits)
This commit is contained in:
parent
fc9b01e77f
commit
f319a02bce
20 changed files with 72641 additions and 3 deletions
61
nimbledeps/nimbledata2.json
Normal file
61
nimbledeps/nimbledata2.json
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"reverseDeps": {
|
||||||
|
"parsetoml": {
|
||||||
|
"0.7.1": {
|
||||||
|
"586fe63467a674008c4445ed1b8ac882177d7103": [
|
||||||
|
{
|
||||||
|
"path": "/home/psw/Development/wmtools"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "wm_tools",
|
||||||
|
"version": "2.0.7",
|
||||||
|
"checksum": "cf7d7fe07cc76e3a739828bb67c35bdf8ad5a6ad"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"argparse": {
|
||||||
|
"4.0.1": {
|
||||||
|
"e9c2ebe3f74b1dfc4df773686ae6dab7638a8662": [
|
||||||
|
{
|
||||||
|
"path": "/home/psw/Development/wmtools"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "wm_tools",
|
||||||
|
"version": "2.0.7",
|
||||||
|
"checksum": "cf7d7fe07cc76e3a739828bb67c35bdf8ad5a6ad"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"configparser": {
|
||||||
|
"0.1.0": {
|
||||||
|
"5f854c4a8243430e1799136ff0fd88d9d32b3228": [
|
||||||
|
{
|
||||||
|
"path": "/home/psw/Development/wmtools"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "wm_tools",
|
||||||
|
"version": "2.0.7",
|
||||||
|
"checksum": "cf7d7fe07cc76e3a739828bb67c35bdf8ad5a6ad"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jsony": {
|
||||||
|
"1.1.5": {
|
||||||
|
"6aeb83e7481ca8686396a568096054bc668294df": [
|
||||||
|
{
|
||||||
|
"path": "/home/psw/Development/wmtools"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "wm_tools",
|
||||||
|
"version": "2.0.7",
|
||||||
|
"checksum": "cf7d7fe07cc76e3a739828bb67c35bdf8ad5a6ad"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33889
nimbledeps/packages_official.json
Normal file
33889
nimbledeps/packages_official.json
Normal file
File diff suppressed because it is too large
Load diff
33889
nimbledeps/packages_temp.json
Normal file
33889
nimbledeps/packages_temp.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,317 @@
|
||||||
|
## 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)
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Package
|
||||||
|
|
||||||
|
version = "4.0.1"
|
||||||
|
author = "Matt Haggard"
|
||||||
|
description = "A command line argument parser"
|
||||||
|
license = "MIT"
|
||||||
|
srcDir = "src"
|
||||||
|
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
|
||||||
|
requires "nim >= 1.0.10"
|
|
@ -0,0 +1,891 @@
|
||||||
|
import algorithm; export algorithm
|
||||||
|
import macros
|
||||||
|
import options; export options
|
||||||
|
import sequtils; export sequtils
|
||||||
|
import streams; export streams
|
||||||
|
import strformat
|
||||||
|
import strutils; export strutils
|
||||||
|
import tables
|
||||||
|
import os; export os
|
||||||
|
|
||||||
|
import ./macrohelp
|
||||||
|
import ./filler
|
||||||
|
|
||||||
|
type
|
||||||
|
UsageError* = object of ValueError
|
||||||
|
ShortCircuit* = object of CatchableError
|
||||||
|
flag*: string
|
||||||
|
help*: string
|
||||||
|
|
||||||
|
ComponentKind* = enum
|
||||||
|
ArgFlag
|
||||||
|
ArgOption
|
||||||
|
ArgArgument
|
||||||
|
|
||||||
|
Component* = object
|
||||||
|
varname*: string
|
||||||
|
hidden*: bool
|
||||||
|
help*: string
|
||||||
|
env*: string
|
||||||
|
case kind*: ComponentKind
|
||||||
|
of ArgFlag:
|
||||||
|
flagShort*: string
|
||||||
|
flagLong*: string
|
||||||
|
flagMultiple*: bool
|
||||||
|
shortCircuit*: bool
|
||||||
|
of ArgOption:
|
||||||
|
optShort*: string
|
||||||
|
optLong*: string
|
||||||
|
optMultiple*: bool
|
||||||
|
optDefault*: Option[string]
|
||||||
|
optChoices*: seq[string]
|
||||||
|
optRequired*: bool
|
||||||
|
of ArgArgument:
|
||||||
|
nargs*: int
|
||||||
|
argDefault*: Option[string]
|
||||||
|
|
||||||
|
Builder* = ref BuilderObj
|
||||||
|
BuilderObj* {.acyclic.} = object
|
||||||
|
## A compile-time object used to accumulate parser options
|
||||||
|
## before building the parser
|
||||||
|
name*: string
|
||||||
|
## Command name for subcommand parsers, or program name for
|
||||||
|
## the parent parser.
|
||||||
|
symbol*: string
|
||||||
|
## Unique tag to apply to Parser and Option types to avoid
|
||||||
|
## conflicts. By default, this is generated with Nim's
|
||||||
|
## gensym algorithm.
|
||||||
|
components*: seq[Component]
|
||||||
|
help*: string
|
||||||
|
groupName*: string
|
||||||
|
children*: seq[Builder]
|
||||||
|
parent*: Option[Builder]
|
||||||
|
runProcBodies*: seq[NimNode]
|
||||||
|
|
||||||
|
ParseState* = object
|
||||||
|
tokens*: seq[string]
|
||||||
|
cursor*: int
|
||||||
|
extra*: seq[string]
|
||||||
|
## tokens that weren't parsed
|
||||||
|
done*: bool
|
||||||
|
token*: Option[string]
|
||||||
|
## The current unprocessed token
|
||||||
|
key*: Option[string]
|
||||||
|
## The current key (possibly the head of a 'key=value' token)
|
||||||
|
value*: Option[string]
|
||||||
|
## The current value (possibly the tail of a 'key=value' token)
|
||||||
|
valuePartOfToken*: bool
|
||||||
|
## true if the value is part of the current token (e.g. 'key=value')
|
||||||
|
runProcs*: seq[proc()]
|
||||||
|
## Procs to be run at the end of parsing
|
||||||
|
|
||||||
|
var ARGPARSE_STDOUT* = newFileStream(stdout)
|
||||||
|
var builderStack* {.compileTime.} = newSeq[Builder]()
|
||||||
|
|
||||||
|
proc toVarname*(x: string): string =
|
||||||
|
## Convert x to something suitable as a Nim identifier
|
||||||
|
## Replaces - with _ for instance
|
||||||
|
x.replace("-", "_").strip(chars={'_'})
|
||||||
|
|
||||||
|
#--------------------------------------------------------------
|
||||||
|
# ParseState
|
||||||
|
#--------------------------------------------------------------
|
||||||
|
|
||||||
|
proc `$`*(state: ref ParseState): string {.inline.} = $(state[])
|
||||||
|
|
||||||
|
proc advance(state: ref ParseState, amount: int, skip = false) =
|
||||||
|
## Advance the parse by `amount` tokens
|
||||||
|
##
|
||||||
|
## If `skip` is given, add the passed-over tokens to `extra`
|
||||||
|
for i in 0..<amount:
|
||||||
|
if state.cursor >= state.tokens.len:
|
||||||
|
continue
|
||||||
|
if skip:
|
||||||
|
state.extra.add(state.tokens[state.cursor])
|
||||||
|
state.cursor.inc()
|
||||||
|
if state.cursor >= state.tokens.len:
|
||||||
|
state.done = true
|
||||||
|
state.token = none[string]()
|
||||||
|
state.key = none[string]()
|
||||||
|
state.value = none[string]()
|
||||||
|
state.valuePartOfToken = false
|
||||||
|
else:
|
||||||
|
let token = state.tokens[state.cursor]
|
||||||
|
state.token = some(token)
|
||||||
|
if token.startsWith("-") and '=' in token:
|
||||||
|
let parts = token.split("=", 1)
|
||||||
|
state.key = some(parts[0])
|
||||||
|
state.value = some(parts[1])
|
||||||
|
state.valuePartOfToken = true
|
||||||
|
else:
|
||||||
|
state.key = some(token)
|
||||||
|
state.valuePartOfToken = false
|
||||||
|
if (state.cursor + 1) < state.tokens.len:
|
||||||
|
state.value = some(state.tokens[state.cursor + 1])
|
||||||
|
else:
|
||||||
|
state.value = none[string]()
|
||||||
|
|
||||||
|
proc newParseState*(args: openArray[string]): ref ParseState =
|
||||||
|
new(result)
|
||||||
|
result.tokens = toSeq(args)
|
||||||
|
result.extra = newSeq[string]()
|
||||||
|
result.cursor = -1
|
||||||
|
result.advance(1)
|
||||||
|
|
||||||
|
proc consume*(state: ref ParseState, thing: ComponentKind) =
|
||||||
|
## Advance the parser, marking some tokens as consumed.
|
||||||
|
case thing
|
||||||
|
of ArgFlag:
|
||||||
|
state.advance(1)
|
||||||
|
of ArgOption:
|
||||||
|
state.advance(if state.valuePartOfToken: 1 else: 2)
|
||||||
|
of ArgArgument:
|
||||||
|
state.advance(1)
|
||||||
|
|
||||||
|
proc skip*(state: ref ParseState) {.inline.} =
|
||||||
|
state.advance(1, skip = true)
|
||||||
|
|
||||||
|
#--------------------------------------------------------------
|
||||||
|
# General
|
||||||
|
#--------------------------------------------------------------
|
||||||
|
|
||||||
|
proc safeIdentStr(x: string): string =
|
||||||
|
## Remove components of a string that make it unsuitable as a Nim identifier
|
||||||
|
for c in x:
|
||||||
|
case c
|
||||||
|
of '_':
|
||||||
|
if result.len >= 1 and result[result.len-1] != '_':
|
||||||
|
result.add c
|
||||||
|
of 'A'..'Z', 'a'..'z', '\x80'..'\xff':
|
||||||
|
result.add c
|
||||||
|
of '0'..'9':
|
||||||
|
if result.len >= 1:
|
||||||
|
result.add c
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
result.strip(chars = {'_'})
|
||||||
|
|
||||||
|
proc popleft*[T](s: var seq[T]):T =
|
||||||
|
## Pop from the front of a seq
|
||||||
|
result = s[0]
|
||||||
|
when (NimMajor, NimMinor, NimPatch) >= (1, 6, 0):
|
||||||
|
s.delete(0..0)
|
||||||
|
else:
|
||||||
|
s.delete(0, 0)
|
||||||
|
|
||||||
|
proc popright*[T](s: var seq[T], n = 0): T =
|
||||||
|
## Pop the nth item from the end of a seq
|
||||||
|
let idx = s.len - n - 1
|
||||||
|
result = s[idx]
|
||||||
|
when (NimMajor, NimMinor, NimPatch) >= (1, 6, 0):
|
||||||
|
s.delete(idx..idx)
|
||||||
|
else:
|
||||||
|
s.delete(idx, idx)
|
||||||
|
|
||||||
|
#--------------------------------------------------------------
|
||||||
|
# Component
|
||||||
|
#--------------------------------------------------------------
|
||||||
|
|
||||||
|
proc identDef(varname: NimNode, vartype: NimNode): NimNode =
|
||||||
|
## Return a property definition for an object.
|
||||||
|
##
|
||||||
|
## type
|
||||||
|
## Foo = object
|
||||||
|
## varname*: vartype <-- this is the AST being returned
|
||||||
|
return nnkIdentDefs.newTree(
|
||||||
|
nnkPostfix.newTree(
|
||||||
|
ident("*"),
|
||||||
|
varname,
|
||||||
|
),
|
||||||
|
vartype,
|
||||||
|
newEmptyNode()
|
||||||
|
)
|
||||||
|
|
||||||
|
proc propDefinitions(c: Component): seq[NimNode] =
|
||||||
|
## Return the type of this component as will be put in the
|
||||||
|
## parser return type object definition
|
||||||
|
##
|
||||||
|
## type
|
||||||
|
## Foo = object
|
||||||
|
## name*: string <-- this is the AST being returned
|
||||||
|
let varname = ident(c.varname.safeIdentStr)
|
||||||
|
case c.kind
|
||||||
|
of ArgFlag:
|
||||||
|
if c.flagMultiple:
|
||||||
|
result.add identDef(varname, ident("int"))
|
||||||
|
else:
|
||||||
|
result.add identDef(varname, ident("bool"))
|
||||||
|
of ArgOption:
|
||||||
|
if c.optMultiple:
|
||||||
|
result.add identDef(varname, parseExpr("seq[string]"))
|
||||||
|
else:
|
||||||
|
result.add identDef(varname, ident("string"))
|
||||||
|
result.add identDef(
|
||||||
|
ident(safeIdentStr(c.varname & "_opt")),
|
||||||
|
nnkBracketExpr.newTree(
|
||||||
|
ident("Option"),
|
||||||
|
ident("string")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
of ArgArgument:
|
||||||
|
if c.nargs != 1:
|
||||||
|
result.add identDef(varname, parseExpr("seq[string]"))
|
||||||
|
else:
|
||||||
|
result.add identDef(varname, ident("string"))
|
||||||
|
|
||||||
|
#--------------------------------------------------------------
|
||||||
|
# Builder
|
||||||
|
#--------------------------------------------------------------
|
||||||
|
|
||||||
|
proc newBuilder*(name = ""): Builder =
|
||||||
|
new(result)
|
||||||
|
result.name = name
|
||||||
|
result.symbol = genSym(nskLet, if name == "": "Argparse" else: name.safeIdentStr).toStrLit.strVal
|
||||||
|
result.children = newSeq[Builder]()
|
||||||
|
result.runProcBodies = newSeq[NimNode]()
|
||||||
|
result.components.add Component(
|
||||||
|
kind: ArgFlag,
|
||||||
|
varname: "argparse_help",
|
||||||
|
shortCircuit: true,
|
||||||
|
flagShort: "-h",
|
||||||
|
flagLong: "--help",
|
||||||
|
)
|
||||||
|
|
||||||
|
proc `$`*(b: Builder): string = $(b[])
|
||||||
|
|
||||||
|
proc optsIdent(b: Builder): NimNode =
|
||||||
|
## Name of the option type for this Builder
|
||||||
|
# let name = if b.name == "": "Argparse" else: b.name
|
||||||
|
ident("Opts" & b.symbol)
|
||||||
|
|
||||||
|
proc parserIdent(b: Builder): NimNode =
|
||||||
|
## Name of the parser type for this Builder
|
||||||
|
# let name = if b.name == "": "Argparse" else: b.name
|
||||||
|
ident("Parser" & b.symbol)
|
||||||
|
|
||||||
|
proc optsTypeDef*(b: Builder): NimNode =
|
||||||
|
## Generate the type definition for the return value of parsing:
|
||||||
|
var properties = nnkRecList.newTree()
|
||||||
|
for component in b.components:
|
||||||
|
if component.kind == ArgFlag:
|
||||||
|
if component.shortCircuit:
|
||||||
|
# don't add shortcircuits to the option type
|
||||||
|
continue
|
||||||
|
properties.add(component.propDefinitions())
|
||||||
|
if b.parent.isSome:
|
||||||
|
properties.add nnkIdentDefs.newTree(
|
||||||
|
nnkPostfix.newTree(
|
||||||
|
ident("*"),
|
||||||
|
ident("parentOpts")
|
||||||
|
),
|
||||||
|
nnkRefTy.newTree(
|
||||||
|
b.parent.get().optsIdent,
|
||||||
|
),
|
||||||
|
newEmptyNode()
|
||||||
|
)
|
||||||
|
|
||||||
|
if b.children.len > 0:
|
||||||
|
# .argparse_command
|
||||||
|
properties.add nnkIdentDefs.newTree(
|
||||||
|
nnkPostfix.newTree(
|
||||||
|
ident("*"),
|
||||||
|
ident("argparse_command"),
|
||||||
|
),
|
||||||
|
ident("string"),
|
||||||
|
newEmptyNode(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# subcommand opts
|
||||||
|
for child in b.children:
|
||||||
|
let childOptsIdent = child.optsIdent()
|
||||||
|
properties.add nnkIdentDefs.newTree(
|
||||||
|
nnkPostfix.newTree(
|
||||||
|
ident("*"),
|
||||||
|
ident("argparse_" & child.name.toVarname() & "_opts")
|
||||||
|
),
|
||||||
|
nnkBracketExpr.newTree(
|
||||||
|
ident("Option"),
|
||||||
|
nnkRefTy.newTree(childOptsIdent)
|
||||||
|
),
|
||||||
|
newEmptyNode()
|
||||||
|
)
|
||||||
|
|
||||||
|
# type MyOpts = object
|
||||||
|
result = nnkTypeDef.newTree(
|
||||||
|
b.optsIdent(),
|
||||||
|
newEmptyNode(),
|
||||||
|
nnkObjectTy.newTree(
|
||||||
|
newEmptyNode(),
|
||||||
|
newEmptyNode(),
|
||||||
|
properties,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
proc parserTypeDef*(b: Builder): NimNode =
|
||||||
|
## Generate the type definition for the Parser object:
|
||||||
|
##
|
||||||
|
## type
|
||||||
|
## MyParser = object
|
||||||
|
result = nnkTypeDef.newTree(
|
||||||
|
b.parserIdent(),
|
||||||
|
newEmptyNode(),
|
||||||
|
nnkObjectTy.newTree(
|
||||||
|
newEmptyNode(),
|
||||||
|
newEmptyNode(),
|
||||||
|
newEmptyNode(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
proc raiseShortCircuit*(flagname: string, help: string) {.inline.} =
|
||||||
|
var e: ref ShortCircuit
|
||||||
|
new(e)
|
||||||
|
e.flag = flagname
|
||||||
|
e.msg = "ShortCircuit on " & flagname
|
||||||
|
e.help = help
|
||||||
|
raise e
|
||||||
|
|
||||||
|
proc parseProcDef*(b: Builder): NimNode =
|
||||||
|
## Generate the parse proc for this Builder
|
||||||
|
##
|
||||||
|
## proc parse(p: MyParser, args: seq[string]): MyOpts =
|
||||||
|
result = newStmtList()
|
||||||
|
|
||||||
|
let parserIdent = b.parserIdent()
|
||||||
|
let optsIdent = b.optsIdent()
|
||||||
|
|
||||||
|
# flag/opt/arg handlers
|
||||||
|
var flagCase = newCaseStatement(parseExpr("token"))
|
||||||
|
var optCase = newCaseStatement(parseExpr("key"))
|
||||||
|
var requiredOptionGuard = newStmtList()
|
||||||
|
var setDefaults = newStmtList()
|
||||||
|
var filler = newArgFiller()
|
||||||
|
for component in b.components:
|
||||||
|
case component.kind
|
||||||
|
of ArgFlag:
|
||||||
|
var matches: seq[string]
|
||||||
|
if component.flagShort != "":
|
||||||
|
matches.add(component.flagShort) # of "-h":
|
||||||
|
if component.flagLong != "":
|
||||||
|
matches.add(component.flagLong) # of "--help":
|
||||||
|
var body = newStmtList()
|
||||||
|
if component.shortCircuit:
|
||||||
|
let varname = newStrLitNode(component.varname)
|
||||||
|
body.add quote do:
|
||||||
|
raiseShortCircuit(`varname`, parser.help)
|
||||||
|
else:
|
||||||
|
if component.flagMultiple:
|
||||||
|
let varname = ident(component.varname)
|
||||||
|
body.add quote do:
|
||||||
|
opts.`varname`.inc()
|
||||||
|
state.consume(ArgFlag)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
let varname = ident(component.varname)
|
||||||
|
body.add quote do:
|
||||||
|
opts.`varname` = true
|
||||||
|
state.consume(ArgFlag)
|
||||||
|
continue
|
||||||
|
if not body.isNil:
|
||||||
|
flagCase.add(matches, body)
|
||||||
|
of ArgOption:
|
||||||
|
let varname = ident(component.varname)
|
||||||
|
let varname_opt = ident(component.varname & "_opt")
|
||||||
|
if component.env != "":
|
||||||
|
# Set default from environment variable
|
||||||
|
let dft = newStrLitNode(component.optDefault.get(""))
|
||||||
|
let env = newStrLitNode(component.env)
|
||||||
|
setDefaults.add quote do:
|
||||||
|
opts.`varname` = getEnv(`env`, `dft`)
|
||||||
|
if component.optDefault.isSome:
|
||||||
|
setDefaults.add quote do:
|
||||||
|
opts.`varname_opt` = some(getEnv(`env`, `dft`))
|
||||||
|
elif component.optDefault.isSome:
|
||||||
|
# Set default
|
||||||
|
let dft = component.optDefault.get()
|
||||||
|
setDefaults.add quote do:
|
||||||
|
opts.`varname` = `dft`
|
||||||
|
opts.`varname_opt` = some(`dft`)
|
||||||
|
var matches: seq[string]
|
||||||
|
var optCombo: string
|
||||||
|
if component.optShort != "":
|
||||||
|
matches.add(component.optShort) # of "-h"
|
||||||
|
optCombo.add component.optShort
|
||||||
|
if component.optLong != "":
|
||||||
|
matches.add(component.optLong) # of "--help"
|
||||||
|
if optCombo != "":
|
||||||
|
optCombo.add ","
|
||||||
|
optCombo.add(component.optLong)
|
||||||
|
let optComboNode = newStrLitNode(optCombo)
|
||||||
|
|
||||||
|
# Make sure it has a value
|
||||||
|
let valueGuard = quote do:
|
||||||
|
if state.value.isNone:
|
||||||
|
raise UsageError.newException("Missing value for " & `optComboNode`)
|
||||||
|
|
||||||
|
# Make sure it in the set of expected choices
|
||||||
|
var choiceGuard = parseExpr("discard \"no choice guard\"")
|
||||||
|
if component.optChoices.len > 0:
|
||||||
|
let choices = component.optChoices
|
||||||
|
choiceGuard = quote do:
|
||||||
|
if state.value.get() notin `choices`:
|
||||||
|
raise UsageError.newException("Invalid value for " & `optComboNode` & ": " & state.value.get() & " (valid choices: " & $`choices` & ")")
|
||||||
|
|
||||||
|
# Make sure required options have been provided
|
||||||
|
if component.optRequired:
|
||||||
|
let envStr = newStrLitNode(component.env)
|
||||||
|
requiredOptionGuard.add quote do:
|
||||||
|
if `optComboNode` notin switches_seen and (`envStr` == "" or getEnv(`envStr`) == ""):
|
||||||
|
raise UsageError.newException("Option " & `optComboNode` & " is required and was not provided")
|
||||||
|
|
||||||
|
# Make sure it hasn't been provided twice
|
||||||
|
var duplicateGuard: NimNode
|
||||||
|
var body: NimNode
|
||||||
|
if component.optMultiple:
|
||||||
|
# -o apple -o banana
|
||||||
|
duplicateGuard = parseExpr("discard \"no duplicate guard\"")
|
||||||
|
body = quote do:
|
||||||
|
opts.`varname`.add(state.value.get())
|
||||||
|
state.consume(ArgOption)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# -o single
|
||||||
|
duplicateGuard = quote do:
|
||||||
|
if `optComboNode` in switches_seen:
|
||||||
|
raise UsageError.newException("Option " & `optComboNode` & " supplied multiple times")
|
||||||
|
switches_seen.add(`optComboNode`)
|
||||||
|
body = quote do:
|
||||||
|
opts.`varname` = state.value.get()
|
||||||
|
opts.`varname_opt` = some(opts.`varname`)
|
||||||
|
state.consume(ArgOption)
|
||||||
|
continue
|
||||||
|
if not body.isNil:
|
||||||
|
optCase.add(matches, newStmtList(
|
||||||
|
valueGuard,
|
||||||
|
choiceGuard,
|
||||||
|
duplicateGuard,
|
||||||
|
body,
|
||||||
|
))
|
||||||
|
of ArgArgument:
|
||||||
|
# Process positional arguments
|
||||||
|
if component.nargs == -1:
|
||||||
|
filler.wildcard(component.varname)
|
||||||
|
elif component.nargs == 1:
|
||||||
|
let varname = ident(component.varname)
|
||||||
|
if component.env != "":
|
||||||
|
filler.optional(component.varname)
|
||||||
|
let envStr = newStrLitNode(component.env)
|
||||||
|
let dftStr = newStrLitNode(component.argDefault.get(""))
|
||||||
|
setDefaults.add replaceNodes(quote do:
|
||||||
|
opts.`varname` = getEnv(`envStr`, `dftStr`)
|
||||||
|
)
|
||||||
|
elif component.argDefault.isSome:
|
||||||
|
filler.optional(component.varname)
|
||||||
|
let dftStr = newStrLitNode(component.argDefault.get())
|
||||||
|
setDefaults.add replaceNodes(quote do:
|
||||||
|
opts.`varname` = `dftStr`
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
filler.required(component.varname, 1)
|
||||||
|
elif component.nargs > 1:
|
||||||
|
filler.required(component.varname, component.nargs)
|
||||||
|
|
||||||
|
# args proc
|
||||||
|
let minArgs = newIntLitNode(filler.minArgs)
|
||||||
|
var argcase = newCaseStatement(parseExpr("state.extra.len"))
|
||||||
|
if filler.minArgs > 0:
|
||||||
|
for nargs in 0..<filler.minArgs:
|
||||||
|
let missing = newStrLitNode(filler.missing(nargs).join(", "))
|
||||||
|
argcase.add(nargs, replaceNodes(quote do:
|
||||||
|
raise UsageError.newException("Missing argument(s): " & `missing`)
|
||||||
|
))
|
||||||
|
let upperBreakpoint = filler.upperBreakpoint
|
||||||
|
for nargs in filler.minArgs..upperBreakpoint:
|
||||||
|
let channels = filler.channels(nargs)
|
||||||
|
var s = newStmtList()
|
||||||
|
for ch in filler.channels(nargs):
|
||||||
|
let varname = ident(ch.dest)
|
||||||
|
case ch.kind
|
||||||
|
of Wildcard:
|
||||||
|
let argsAfterWildcard = newIntLitNode(filler.numArgsAfterWildcard)
|
||||||
|
s.add replaceNodes(quote do:
|
||||||
|
for i in 0..<(state.extra.len - `argsAfterWildcard`):
|
||||||
|
opts.`varname`.add state.extra.popleft()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
for i in 0..<ch.idx.len:
|
||||||
|
s.add replaceNodes(quote do:
|
||||||
|
opts.`varname`.setOrAdd state.extra.popleft()
|
||||||
|
)
|
||||||
|
if nargs == upperBreakpoint:
|
||||||
|
argcase.addElse(s)
|
||||||
|
else:
|
||||||
|
argcase.add(nargs, s)
|
||||||
|
|
||||||
|
# commands
|
||||||
|
var commandCase = newCaseStatement(parseExpr("token"))
|
||||||
|
for child in b.children:
|
||||||
|
if filler.hasVariableArgs:
|
||||||
|
raise ValueError.newException("Mixing optional args with commands is not supported")
|
||||||
|
let childParserIdent = child.parserIdent()
|
||||||
|
let childOptsIdent = child.optsIdent()
|
||||||
|
let childNameStr = child.name.newStrLitNode()
|
||||||
|
let subopts_prop_name = ident("argparse_" & child.name.toVarname & "_opts")
|
||||||
|
commandCase.add(child.name, replaceNodes(quote do:
|
||||||
|
## Call the subcommand's parser
|
||||||
|
takeArgsFromExtra(opts, state)
|
||||||
|
argsTaken = true
|
||||||
|
opts.argparse_command = `childNameStr`
|
||||||
|
state.consume(ArgArgument)
|
||||||
|
var subparser = `childParserIdent`()
|
||||||
|
var subOpts: ref `childOptsIdent`
|
||||||
|
new(subOpts)
|
||||||
|
subOpts.parentOpts = opts
|
||||||
|
opts.`subopts_prop_name` = some(subOpts)
|
||||||
|
subparser.parse(subOpts, state, runblocks = runblocks, quitOnHelp = quitOnHelp, output = output)
|
||||||
|
continue
|
||||||
|
))
|
||||||
|
|
||||||
|
var addRunProcs = newStmtList()
|
||||||
|
var runProcs = newStmtList()
|
||||||
|
for p in b.runProcBodies:
|
||||||
|
addRunProcs.add(quote do:
|
||||||
|
state.runProcs.add(proc() =
|
||||||
|
`p`
|
||||||
|
))
|
||||||
|
if b.parent.isNone:
|
||||||
|
runProcs.add(quote do:
|
||||||
|
if runblocks:
|
||||||
|
for p in state.runProcs:
|
||||||
|
p()
|
||||||
|
)
|
||||||
|
|
||||||
|
proc mkCase(c: ref UnfinishedCase): NimNode =
|
||||||
|
if c.isValid and not c.hasElse:
|
||||||
|
c.addElse(parseExpr("discard"))
|
||||||
|
result = replaceNodes(c.finalize())
|
||||||
|
if result.isNil:
|
||||||
|
result = parseExpr("discard")
|
||||||
|
|
||||||
|
var flagCase_node = mkCase(flagCase)
|
||||||
|
var optCase_node = mkCase(optCase)
|
||||||
|
var argCase_node = mkCase(argCase)
|
||||||
|
var commandCase_node = mkCase(commandCase)
|
||||||
|
|
||||||
|
var parseProc = quote do:
|
||||||
|
proc parse(parser: `parserIdent`, opts: ref `optsIdent`, state: ref ParseState, runblocks = false, quitOnHelp = true, output:Stream = ARGPARSE_STDOUT) {.used.} =
|
||||||
|
try:
|
||||||
|
var switches_seen {.used.} : seq[string]
|
||||||
|
proc takeArgsFromExtra(opts: ref `optsIdent`, state: ref ParseState) =
|
||||||
|
`requiredOptionGuard`
|
||||||
|
`argCase_node`
|
||||||
|
# Set defaults
|
||||||
|
`setDefaults`
|
||||||
|
`addRunProcs`
|
||||||
|
var argCount {.used.} = 0
|
||||||
|
var argsTaken = false
|
||||||
|
var doneProcessingFlags = false
|
||||||
|
while not state.done:
|
||||||
|
# handle no-argument flags and commands
|
||||||
|
let token {.used.} = state.token.get()
|
||||||
|
if not doneProcessingFlags:
|
||||||
|
`flagCase_node`
|
||||||
|
# handle argument-taking flags
|
||||||
|
let key {.used.} = state.key.get()
|
||||||
|
`optCase_node`
|
||||||
|
if state.extra.len >= `minArgs`:
|
||||||
|
`commandCase_node`
|
||||||
|
if token == "--":
|
||||||
|
doneProcessingFlags = true
|
||||||
|
state.consume(ArgArgument)
|
||||||
|
continue
|
||||||
|
state.skip()
|
||||||
|
if not argsTaken:
|
||||||
|
takeArgsFromExtra(opts, state)
|
||||||
|
if state.extra.len > 0:
|
||||||
|
# There are extra args.
|
||||||
|
raise UsageError.newException("Unknown argument(s): " & state.extra.join(", "))
|
||||||
|
`runProcs`
|
||||||
|
except ShortCircuit as e:
|
||||||
|
if e.flag == "argparse_help" and runblocks:
|
||||||
|
output.write(parser.help())
|
||||||
|
if quitOnHelp:
|
||||||
|
quit(1)
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
result.add(replaceNodes(parseProc))
|
||||||
|
|
||||||
|
# Convenience parse/run procs
|
||||||
|
result.add replaceNodes(quote do:
|
||||||
|
proc parse(parser: `parserIdent`, args: seq[string], quitOnHelp = true): ref `optsIdent` {.used.} =
|
||||||
|
## Parse arguments using the `parserIdent` parser
|
||||||
|
var state = newParseState(args)
|
||||||
|
var opts: ref `optsIdent`
|
||||||
|
new(opts)
|
||||||
|
parser.parse(opts, state, quitOnHelp = quitOnHelp)
|
||||||
|
result = opts
|
||||||
|
)
|
||||||
|
# proc parse() with no args
|
||||||
|
result.add replaceNodes(quote do:
|
||||||
|
proc parse(parser: `parserIdent`, quitOnHelp = true): ref `optsIdent` {.used.} =
|
||||||
|
## Parse command line params
|
||||||
|
when declared(commandLineParams):
|
||||||
|
parser.parse(toSeq(commandLineParams()), quitOnHelp = quitOnHelp)
|
||||||
|
else:
|
||||||
|
var params: seq[string]
|
||||||
|
for i in 0..paramCount():
|
||||||
|
params.add(paramStr(i))
|
||||||
|
parser.parse(params, quitOnHelp = quitOnHelp)
|
||||||
|
)
|
||||||
|
result.add replaceNodes(quote do:
|
||||||
|
proc run(parser: `parserIdent`, args: seq[string], quitOnHelp = true, output:Stream = ARGPARSE_STDOUT) {.used.} =
|
||||||
|
## Run the matching run-blocks of the parser
|
||||||
|
var state = newParseState(args)
|
||||||
|
var opts: ref `optsIdent`
|
||||||
|
new(opts)
|
||||||
|
parser.parse(opts, state, runblocks = true, quitOnHelp = quitOnHelp, output = output)
|
||||||
|
)
|
||||||
|
# proc run() with no args
|
||||||
|
result.add replaceNodes(quote do:
|
||||||
|
proc run(parser: `parserIdent`) {.used.} =
|
||||||
|
## Run the matching run-blocks of the parser
|
||||||
|
when declared(commandLineParams):
|
||||||
|
parser.run(toSeq(commandLineParams()))
|
||||||
|
else:
|
||||||
|
var params: seq[string]
|
||||||
|
for i in 0..paramCount():
|
||||||
|
params.add(paramStr(i))
|
||||||
|
parser.run(params)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Shorter named convenience procs
|
||||||
|
if b.children.len > 0:
|
||||||
|
# .argparse_command -> .command shortcut
|
||||||
|
result.add replaceNodes(quote do:
|
||||||
|
proc command(opts: ref `optsIdent`): string {.used, inline.} =
|
||||||
|
opts.argparse_command
|
||||||
|
)
|
||||||
|
|
||||||
|
# .argparse_NAME_opts -> .NAME shortcut
|
||||||
|
for child in b.children:
|
||||||
|
let name = ident(child.name)
|
||||||
|
let fulloptname = ident("argparse_" & child.name.toVarname & "_opts")
|
||||||
|
let retval = nnkBracketExpr.newTree(
|
||||||
|
ident("Option"),
|
||||||
|
nnkRefTy.newTree(child.optsIdent())
|
||||||
|
)
|
||||||
|
result.add replaceNodes(quote do:
|
||||||
|
proc `name`(opts: ref `optsIdent`): `retval` {.used, inline.} =
|
||||||
|
opts.`fulloptname`
|
||||||
|
)
|
||||||
|
|
||||||
|
proc setOrAdd*(x: var string, val: string) =
|
||||||
|
x = val
|
||||||
|
|
||||||
|
proc setOrAdd*(x: var seq[string], val: string) =
|
||||||
|
x.add(val)
|
||||||
|
|
||||||
|
proc getHelpText*(b: Builder): string =
|
||||||
|
## Generate the static help text string
|
||||||
|
if b.help != "":
|
||||||
|
result.add(b.help)
|
||||||
|
result.add("\L\L")
|
||||||
|
|
||||||
|
# usage
|
||||||
|
var usage_parts:seq[string]
|
||||||
|
|
||||||
|
proc firstline(s:string):string =
|
||||||
|
s.split("\L")[0]
|
||||||
|
|
||||||
|
proc formatOption(flags:string, helptext:string, defaultval = none[string](), envvar:string = "", choices:seq[string] = @[], opt_width = 26, max_width = 100):string =
|
||||||
|
result.add(" " & flags)
|
||||||
|
var helptext = helptext
|
||||||
|
if choices.len > 0:
|
||||||
|
helptext.add(" Possible values: [" & choices.join(", ") & "]")
|
||||||
|
if defaultval.isSome:
|
||||||
|
helptext.add(&" (default: {defaultval.get()})")
|
||||||
|
if envvar != "":
|
||||||
|
helptext.add(&" (env: {envvar})")
|
||||||
|
helptext = helptext.strip()
|
||||||
|
if helptext != "":
|
||||||
|
if flags.len > opt_width:
|
||||||
|
result.add("\L")
|
||||||
|
result.add(" ")
|
||||||
|
result.add(" ".repeat(opt_width+1))
|
||||||
|
result.add(helptext)
|
||||||
|
else:
|
||||||
|
result.add(" ".repeat(opt_width - flags.len))
|
||||||
|
result.add(" ")
|
||||||
|
result.add(helptext)
|
||||||
|
|
||||||
|
var opts = ""
|
||||||
|
var args = ""
|
||||||
|
|
||||||
|
# Options and Arguments
|
||||||
|
for comp in b.components:
|
||||||
|
case comp.kind
|
||||||
|
of ArgFlag:
|
||||||
|
if not comp.hidden:
|
||||||
|
var flag_parts: seq[string]
|
||||||
|
if comp.flagShort != "":
|
||||||
|
flag_parts.add(comp.flagShort)
|
||||||
|
if comp.flagLong != "":
|
||||||
|
flag_parts.add(comp.flagLong)
|
||||||
|
opts.add(formatOption(flag_parts.join(", "), comp.help))
|
||||||
|
opts.add("\L")
|
||||||
|
of ArgOption:
|
||||||
|
if not comp.hidden:
|
||||||
|
var flag_parts: seq[string]
|
||||||
|
if comp.optShort != "":
|
||||||
|
flag_parts.add(comp.optShort)
|
||||||
|
if comp.optLong != "":
|
||||||
|
flag_parts.add(comp.optLong)
|
||||||
|
var flags = flag_parts.join(", ") & "=" & comp.varname.toUpper()
|
||||||
|
opts.add(formatOption(flags, comp.help, defaultval = comp.optDefault, envvar = comp.env, choices = comp.optChoices))
|
||||||
|
opts.add("\L")
|
||||||
|
of ArgArgument:
|
||||||
|
var leftside:string
|
||||||
|
if comp.nargs == 1:
|
||||||
|
leftside = comp.varname
|
||||||
|
if comp.argDefault.isSome:
|
||||||
|
leftside = &"[{comp.varname}]"
|
||||||
|
elif comp.nargs == -1:
|
||||||
|
leftside = &"[{comp.varname} ...]"
|
||||||
|
else:
|
||||||
|
leftside = (&"{comp.varname} ").repeat(comp.nargs)
|
||||||
|
usage_parts.add(leftside)
|
||||||
|
args.add(formatOption(leftside, comp.help, defaultval = comp.argDefault, envvar = comp.env, opt_width=16))
|
||||||
|
args.add("\L")
|
||||||
|
|
||||||
|
var commands = newOrderedTable[string,string](2)
|
||||||
|
|
||||||
|
if b.children.len > 0:
|
||||||
|
usage_parts.add("COMMAND")
|
||||||
|
for subbuilder in b.children:
|
||||||
|
var leftside = subbuilder.name
|
||||||
|
let group = subbuilder.groupName
|
||||||
|
if not commands.hasKey(group):
|
||||||
|
commands[group] = ""
|
||||||
|
let indent = if group == "": "" else: " "
|
||||||
|
commands[group].add(indent & formatOption(leftside, subbuilder.help.firstline, opt_width=16))
|
||||||
|
commands[group].add("\L")
|
||||||
|
|
||||||
|
if usage_parts.len > 0 or opts != "":
|
||||||
|
result.add("Usage:\L")
|
||||||
|
result.add(" ")
|
||||||
|
result.add(b.name & " ")
|
||||||
|
if opts != "":
|
||||||
|
result.add("[options] ")
|
||||||
|
result.add(usage_parts.join(" "))
|
||||||
|
result.add("\L\L")
|
||||||
|
|
||||||
|
if commands.len == 1:
|
||||||
|
let key = toSeq(commands.keys())[0]
|
||||||
|
result.add("Commands:\L\L")
|
||||||
|
result.add(commands[key])
|
||||||
|
result.add("\L")
|
||||||
|
elif commands.len > 0:
|
||||||
|
result.add("Commands:\L\L")
|
||||||
|
for key in commands.keys():
|
||||||
|
result.add(" " & key & ":\L\L")
|
||||||
|
result.add(commands[key])
|
||||||
|
result.add("\L")
|
||||||
|
|
||||||
|
if args != "":
|
||||||
|
result.add("Arguments:\L")
|
||||||
|
result.add(args)
|
||||||
|
result.add("\L")
|
||||||
|
|
||||||
|
if opts != "":
|
||||||
|
result.add("Options:\L")
|
||||||
|
result.add(opts)
|
||||||
|
result.add("\L")
|
||||||
|
|
||||||
|
result.stripLineEnd()
|
||||||
|
|
||||||
|
proc helpProcDef*(b: Builder): NimNode =
|
||||||
|
## Generate the help proc for the parser
|
||||||
|
let helptext = b.getHelpText()
|
||||||
|
let prog = newStrLitNode(b.name)
|
||||||
|
let parserIdent = b.parserIdent()
|
||||||
|
result = newStmtList()
|
||||||
|
result.add replaceNodes(quote do:
|
||||||
|
proc help(parser: `parserIdent`): string {.used.} =
|
||||||
|
## Get the help string for this parser
|
||||||
|
var prog = `prog`
|
||||||
|
if prog == "":
|
||||||
|
prog = getAppFilename().extractFilename()
|
||||||
|
result.add `helptext`.replace("{prog}", prog)
|
||||||
|
)
|
||||||
|
|
||||||
|
type
|
||||||
|
GenResponse* = tuple
|
||||||
|
types: NimNode
|
||||||
|
procs: NimNode
|
||||||
|
instance: NimNode
|
||||||
|
|
||||||
|
proc addParser*(name: string, group: string, content: proc()): Builder =
|
||||||
|
## Add a parser (whether main parser or subcommand) and return the Builder
|
||||||
|
## Call ``generateDefs`` to get the type and proc definitions.
|
||||||
|
builderStack.add newBuilder(name)
|
||||||
|
content()
|
||||||
|
var builder = builderStack.pop()
|
||||||
|
builder.groupName = group
|
||||||
|
if builder.help == "" and builderStack.len == 0:
|
||||||
|
builder.help = "{prog}"
|
||||||
|
|
||||||
|
if builderStack.len > 0:
|
||||||
|
# subcommand
|
||||||
|
builderStack[^1].children.add(builder)
|
||||||
|
builder.parent = some(builderStack[^1])
|
||||||
|
|
||||||
|
return builder
|
||||||
|
|
||||||
|
proc add_runProc*(body: NimNode) {.compileTime.} =
|
||||||
|
## Add a run block proc to the current parser
|
||||||
|
builderStack[^1].runProcBodies.add(replaceNodes(body))
|
||||||
|
|
||||||
|
proc add_command*(name: string, group: string, content: proc()) {.compileTime.} =
|
||||||
|
## Add a subcommand to a parser
|
||||||
|
discard addParser(name, group, content)
|
||||||
|
|
||||||
|
proc allChildren*(builder: Builder): seq[Builder] =
|
||||||
|
## Return all the descendents of this builder
|
||||||
|
for child in builder.children:
|
||||||
|
result.add child
|
||||||
|
result.add child.allChildren()
|
||||||
|
|
||||||
|
proc generateDefs*(builder: Builder): NimNode =
|
||||||
|
## Generate the AST definitions for the current builder
|
||||||
|
result = newStmtList()
|
||||||
|
var typeSection = nnkTypeSection.newTree()
|
||||||
|
var procsSection = newStmtList()
|
||||||
|
|
||||||
|
# children first to avoid forward declarations
|
||||||
|
for child in builder.allChildren().reversed:
|
||||||
|
typeSection.add child.optsTypeDef()
|
||||||
|
typeSection.add child.parserTypeDef()
|
||||||
|
procsSection.add child.helpProcDef()
|
||||||
|
procsSection.add child.parseProcDef()
|
||||||
|
|
||||||
|
# MyOpts = object
|
||||||
|
typeSection.add builder.optsTypeDef()
|
||||||
|
# MyParser = object
|
||||||
|
typeSection.add builder.parserTypeDef()
|
||||||
|
|
||||||
|
# proc help(p: MyParser, ...)
|
||||||
|
# proc parse(p: MyParser, ...)
|
||||||
|
# proc run(p: MyParser, ...)
|
||||||
|
procsSection.add builder.helpProcDef()
|
||||||
|
procsSection.add builder.parseProcDef()
|
||||||
|
|
||||||
|
# let parser = MyParser()
|
||||||
|
# parser
|
||||||
|
let parserIdent = builder.parserIdent()
|
||||||
|
let instantiationSection = quote do:
|
||||||
|
var parser = `parserIdent`()
|
||||||
|
parser
|
||||||
|
|
||||||
|
result.add(typeSection)
|
||||||
|
result.add(procsSection)
|
||||||
|
result.add(instantiationSection)
|
|
@ -0,0 +1,113 @@
|
||||||
|
import tables
|
||||||
|
|
||||||
|
type
|
||||||
|
SlotKind* = enum
|
||||||
|
Required
|
||||||
|
Optional
|
||||||
|
Wildcard
|
||||||
|
|
||||||
|
Slot = object
|
||||||
|
name: string
|
||||||
|
case kind*: SlotKind
|
||||||
|
of Required:
|
||||||
|
nargs: int
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
|
||||||
|
ArgFiller* = object
|
||||||
|
slots: seq[Slot]
|
||||||
|
counts: CountTableRef[SlotKind]
|
||||||
|
|
||||||
|
FillChannel* = tuple
|
||||||
|
idx: Slice[int]
|
||||||
|
dest: string
|
||||||
|
kind: SlotKind
|
||||||
|
|
||||||
|
proc newArgFiller*(): ref ArgFiller =
|
||||||
|
new(result)
|
||||||
|
result.counts = newCountTable[SlotKind]()
|
||||||
|
|
||||||
|
using
|
||||||
|
filler: ref ArgFiller
|
||||||
|
|
||||||
|
proc required*(filler; argname: string, nargs = 1) =
|
||||||
|
filler.slots.add(Slot(kind: Required, name: argname, nargs: nargs))
|
||||||
|
filler.counts.inc(Required, nargs)
|
||||||
|
|
||||||
|
proc optional*(filler; argname: string) =
|
||||||
|
filler.slots.add(Slot(kind: Optional, name: argname))
|
||||||
|
filler.counts.inc(Optional)
|
||||||
|
|
||||||
|
proc wildcard*(filler; argname: string) =
|
||||||
|
if filler.counts[Wildcard] > 0:
|
||||||
|
raise ValueError.newException("More than one wildcard argument not allowed")
|
||||||
|
filler.slots.add(Slot(kind: Wildcard, name: argname))
|
||||||
|
filler.counts.inc(Wildcard)
|
||||||
|
|
||||||
|
proc minArgs*(filler): int =
|
||||||
|
for slot in filler.slots:
|
||||||
|
if slot.kind == Required:
|
||||||
|
result.inc(slot.nargs)
|
||||||
|
|
||||||
|
proc numArgsAfterWildcard*(filler): int =
|
||||||
|
var afterWildcard = false
|
||||||
|
for slot in filler.slots:
|
||||||
|
if slot.kind == Wildcard:
|
||||||
|
afterWildcard = true
|
||||||
|
elif afterWildcard:
|
||||||
|
case slot.kind
|
||||||
|
of Required:
|
||||||
|
result.inc(slot.nargs)
|
||||||
|
of Optional:
|
||||||
|
result.inc(1)
|
||||||
|
of Wildcard:
|
||||||
|
discard
|
||||||
|
|
||||||
|
proc hasVariableArgs*(filler): bool =
|
||||||
|
filler.counts[Optional] > 0 or filler.counts[Wildcard] > 0
|
||||||
|
|
||||||
|
proc hasWildcard*(filler): bool =
|
||||||
|
filler.counts[Wildcard] > 0
|
||||||
|
|
||||||
|
proc upperBreakpoint*(filler): int =
|
||||||
|
filler.counts[Required] + filler.counts[Optional] + filler.counts[Wildcard]
|
||||||
|
|
||||||
|
proc channels*(filler; nargs: int): seq[FillChannel] =
|
||||||
|
## Given the number of arguments, show where those arguments will go
|
||||||
|
var toget = newCountTable[SlotKind]()
|
||||||
|
var left = nargs
|
||||||
|
for kind in [Required, Optional, Wildcard]:
|
||||||
|
var kind_left = filler.counts[kind]
|
||||||
|
let totake = min(kind_left, left)
|
||||||
|
if totake > 0:
|
||||||
|
left.dec(totake)
|
||||||
|
kind_left.dec(totake)
|
||||||
|
toget.inc(kind, totake)
|
||||||
|
var idx = 0
|
||||||
|
for slot in filler.slots:
|
||||||
|
if toget[slot.kind] > 0:
|
||||||
|
case slot.kind
|
||||||
|
of Required:
|
||||||
|
result.add (idx..(idx+slot.nargs - 1), slot.name, slot.kind)
|
||||||
|
of Optional:
|
||||||
|
result.add (idx..idx, slot.name, slot.kind)
|
||||||
|
of Wildcard:
|
||||||
|
result.add (idx..(idx + left), slot.name, slot.kind)
|
||||||
|
{.push assertions: off.}
|
||||||
|
toget[slot.kind] = max(toget[slot.kind] - result[^1][0].len, 0)
|
||||||
|
{.pop.}
|
||||||
|
idx.inc(result[^1][0].len)
|
||||||
|
|
||||||
|
proc missing*(filler; nargs: int): seq[string] =
|
||||||
|
## Given the number of arguments, which required arguments will
|
||||||
|
## not get a value?
|
||||||
|
var left = nargs
|
||||||
|
for slot in filler.slots:
|
||||||
|
if slot.kind == Required:
|
||||||
|
for c in 0..<slot.nargs:
|
||||||
|
left.dec()
|
||||||
|
if left < 0:
|
||||||
|
result.add slot.name
|
||||||
|
|
||||||
|
proc generate*(filler; containerName: string): NimNode =
|
||||||
|
discard
|
|
@ -0,0 +1,268 @@
|
||||||
|
import macros
|
||||||
|
import strformat
|
||||||
|
import strutils
|
||||||
|
import sequtils; export sequtils
|
||||||
|
|
||||||
|
type
|
||||||
|
UnfinishedObjectTypeDef* = object
|
||||||
|
root*: NimNode
|
||||||
|
insertion*: NimNode
|
||||||
|
|
||||||
|
UnfinishedCase* = object
|
||||||
|
root*: NimNode
|
||||||
|
cases*: seq[NimNode]
|
||||||
|
elsebody*: NimNode
|
||||||
|
|
||||||
|
UnfinishedIf = object
|
||||||
|
root*: NimNode
|
||||||
|
elsebody*: NimNode
|
||||||
|
|
||||||
|
InsertionPoint = object
|
||||||
|
parent*: NimNode
|
||||||
|
child*: NimNode
|
||||||
|
|
||||||
|
const ident* = (proc(s: string): NimNode)(ident)
|
||||||
|
const newIdentNode* = (proc(s: string): NimNode)(newIdentNode)
|
||||||
|
|
||||||
|
proc replaceNodes*(ast: NimNode): NimNode =
|
||||||
|
## Replace NimIdent and NimSym by a fresh ident node
|
||||||
|
##
|
||||||
|
## Use with the results of ``quote do: ...`` to get
|
||||||
|
## ASTs without symbol resolution having been done already.
|
||||||
|
proc inspect(node: NimNode): NimNode =
|
||||||
|
case node.kind:
|
||||||
|
of nnkIdent:
|
||||||
|
if "`gensym" in node.strVal:
|
||||||
|
return ident(node.strVal.split("`")[0])
|
||||||
|
else:
|
||||||
|
return ident(node.strVal)
|
||||||
|
of nnkSym:
|
||||||
|
return ident(node.strVal)
|
||||||
|
of nnkEmpty:
|
||||||
|
return node
|
||||||
|
of nnkLiterals:
|
||||||
|
return node
|
||||||
|
of nnkOpenSymChoice:
|
||||||
|
return inspect(node[0])
|
||||||
|
else:
|
||||||
|
var rTree = node.kind.newTree()
|
||||||
|
for child in node:
|
||||||
|
rTree.add inspect(child)
|
||||||
|
return rTree
|
||||||
|
result = inspect(ast)
|
||||||
|
|
||||||
|
proc parentOf*(node: NimNode, name:string): InsertionPoint =
|
||||||
|
## Recursively search for an ident node of the given name and return
|
||||||
|
## the parent of that node.
|
||||||
|
var stack:seq[NimNode] = @[node]
|
||||||
|
while stack.len > 0:
|
||||||
|
var n = stack.pop()
|
||||||
|
for child in n.children:
|
||||||
|
if child.kind == nnkIdent and child.strVal == name:
|
||||||
|
return InsertionPoint(parent:n, child:child)
|
||||||
|
else:
|
||||||
|
stack.add(child)
|
||||||
|
error("node not found: " & name)
|
||||||
|
|
||||||
|
proc parentOf*(node: NimNode, child:NimNode): InsertionPoint =
|
||||||
|
## Recursively search for an ident node of the given name and return
|
||||||
|
## the parent of that node.
|
||||||
|
var stack:seq[NimNode] = @[node]
|
||||||
|
while stack.len > 0:
|
||||||
|
var n = stack.pop()
|
||||||
|
for c in n.children:
|
||||||
|
if c == child:
|
||||||
|
return InsertionPoint(parent:n, child:c)
|
||||||
|
else:
|
||||||
|
stack.add(c)
|
||||||
|
error("node not found: " & child.repr)
|
||||||
|
|
||||||
|
proc getInsertionPoint*(node: var NimNode, name:string): InsertionPoint =
|
||||||
|
## Return a node pair that you can replace with something else
|
||||||
|
return node.parentOf(name)
|
||||||
|
|
||||||
|
proc clear*(point: InsertionPoint):int =
|
||||||
|
var i = 0
|
||||||
|
for child in point.parent.children:
|
||||||
|
if child == point.child:
|
||||||
|
break
|
||||||
|
inc(i)
|
||||||
|
point.parent.del(i, 1)
|
||||||
|
result = i
|
||||||
|
|
||||||
|
proc replace*(point: InsertionPoint, newnode: NimNode) =
|
||||||
|
## Replace the child
|
||||||
|
let i = point.clear()
|
||||||
|
point.parent.insert(i, newnode)
|
||||||
|
|
||||||
|
proc newObjectTypeDef*(name: string, isref:bool = false): UnfinishedObjectTypeDef {.compileTime.} =
|
||||||
|
## Creates:
|
||||||
|
## root ->
|
||||||
|
## type
|
||||||
|
## {name} = object
|
||||||
|
## insertion -> ...
|
||||||
|
##
|
||||||
|
var insertion = newNimNode(nnkRecList)
|
||||||
|
var objectty = nnkObjectTy.newTree(
|
||||||
|
newEmptyNode(),
|
||||||
|
newEmptyNode(),
|
||||||
|
insertion,
|
||||||
|
)
|
||||||
|
if isref:
|
||||||
|
objectty = nnkRefTy.newTree(objectty)
|
||||||
|
var root = newNimNode(nnkTypeSection).add(
|
||||||
|
newNimNode(nnkTypeDef).add(
|
||||||
|
ident(name),
|
||||||
|
newEmptyNode(),
|
||||||
|
objectty
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result = UnfinishedObjectTypeDef(root: root, insertion: insertion)
|
||||||
|
|
||||||
|
proc addObjectField*(objtypedef: UnfinishedObjectTypeDef, name: string, kind: NimNode) {.compileTime.} =
|
||||||
|
## Adds a field to an object definition created by newObjectTypeDef
|
||||||
|
objtypedef.insertion.add(newIdentDefs(
|
||||||
|
newNimNode(nnkPostfix).add(
|
||||||
|
ident("*"),
|
||||||
|
ident(name),
|
||||||
|
),
|
||||||
|
kind,
|
||||||
|
newEmptyNode(),
|
||||||
|
))
|
||||||
|
|
||||||
|
proc addObjectField*(objtypedef: UnfinishedObjectTypeDef, name: string, kind: string, isref: bool = false) {.compileTime.} =
|
||||||
|
## Adds a field to an object definition created by newObjectTypeDef
|
||||||
|
if isref:
|
||||||
|
addObjectField(objtypedef, name, nnkRefTy.newTree(ident(kind)))
|
||||||
|
else:
|
||||||
|
addObjectField(objtypedef, name, ident(kind))
|
||||||
|
|
||||||
|
#--------------------------------------------------------------
|
||||||
|
# case statements
|
||||||
|
#--------------------------------------------------------------
|
||||||
|
proc newCaseStatement*(key: NimNode): ref UnfinishedCase =
|
||||||
|
## Create a new, unfinished case statement. Call `finalize` to finish it.
|
||||||
|
##
|
||||||
|
## case(`key`)
|
||||||
|
new(result)
|
||||||
|
result.root = nnkCaseStmt.newTree(key)
|
||||||
|
|
||||||
|
proc newCaseStatement*(key: string): ref UnfinishedCase =
|
||||||
|
return newCaseStatement(ident(key))
|
||||||
|
|
||||||
|
proc add*(n: ref UnfinishedCase, opt: seq[NimNode], body: NimNode) =
|
||||||
|
## Adds a branch to an UnfinishedCase
|
||||||
|
##
|
||||||
|
## Usage:
|
||||||
|
## var c = newCaseStatement("foo")
|
||||||
|
## c.add(@[newLit("apple"), newLit("banana")], quote do:
|
||||||
|
## echo "apple or banana"
|
||||||
|
## )
|
||||||
|
var branch = nnkOfBranch.newTree()
|
||||||
|
for node in opt:
|
||||||
|
branch.add(node)
|
||||||
|
branch.add(body)
|
||||||
|
n.cases.add(branch)
|
||||||
|
|
||||||
|
proc add*(n: ref UnfinishedCase, opt: NimNode, body: NimNode) =
|
||||||
|
## Adds a branch to an UnfinishedCase
|
||||||
|
n.add(@[opt], body)
|
||||||
|
|
||||||
|
proc add*(n: ref UnfinishedCase, opt:string, body: NimNode) =
|
||||||
|
## Adds a branch to an UnfinishedCase
|
||||||
|
##
|
||||||
|
## c.add("foo", quote do:
|
||||||
|
## echo "value was foo"
|
||||||
|
## )
|
||||||
|
n.add(@[newStrLitNode(opt)], body)
|
||||||
|
|
||||||
|
proc add*(n: ref UnfinishedCase, opts: seq[string], body: NimNode) =
|
||||||
|
## Adds a branch to an UnfinishedCase
|
||||||
|
##
|
||||||
|
## c.add(@["foo", "foo-also"], quote do:
|
||||||
|
## echo "value was foo"
|
||||||
|
## )
|
||||||
|
n.add(opts.mapIt(newStrLitNode(it)), body)
|
||||||
|
|
||||||
|
proc add*(n: ref UnfinishedCase, opt:int, body: NimNode) =
|
||||||
|
## Adds an integer branch to an UnfinishedCase
|
||||||
|
add(n, @[newLit(opt)], body)
|
||||||
|
|
||||||
|
proc hasElse*(n: ref UnfinishedCase): bool =
|
||||||
|
not n.elsebody.isNil
|
||||||
|
|
||||||
|
proc addElse*(n: ref UnfinishedCase, body: NimNode) =
|
||||||
|
## Add an else: to an UnfinishedCase
|
||||||
|
n.elsebody = body
|
||||||
|
|
||||||
|
proc isValid*(n: ref UnfinishedCase): bool =
|
||||||
|
return n.cases.len > 0 or n.elsebody != nil
|
||||||
|
|
||||||
|
proc finalize*(n: ref UnfinishedCase): NimNode =
|
||||||
|
if n.cases.len > 0:
|
||||||
|
for branch in n.cases:
|
||||||
|
n.root.add(branch)
|
||||||
|
if n.elsebody != nil:
|
||||||
|
n.root.add(nnkElse.newTree(n.elsebody))
|
||||||
|
result = n.root
|
||||||
|
else:
|
||||||
|
result = n.elsebody
|
||||||
|
|
||||||
|
#--------------------------------------------------------------
|
||||||
|
# if statements
|
||||||
|
#--------------------------------------------------------------
|
||||||
|
|
||||||
|
proc newIfStatement*(): ref UnfinishedIf =
|
||||||
|
## Create an unfinished if statement.
|
||||||
|
new(result)
|
||||||
|
result.root = nnkIfStmt.newTree()
|
||||||
|
|
||||||
|
proc add*(n: ref UnfinishedIf, cond: NimNode, body: NimNode) =
|
||||||
|
## Add a branch to an if statement
|
||||||
|
##
|
||||||
|
## var f = newIfStatement()
|
||||||
|
## f.add()
|
||||||
|
add(n.root, nnkElifBranch.newTree(
|
||||||
|
cond,
|
||||||
|
body,
|
||||||
|
))
|
||||||
|
|
||||||
|
proc addElse*(n: ref UnfinishedIf, body: NimNode) =
|
||||||
|
## Add an else: to an UnfinishedIf
|
||||||
|
n.elsebody = body
|
||||||
|
|
||||||
|
proc isValid*(n: ref UnfinishedIf): bool =
|
||||||
|
return n.root.len > 0 or n.elsebody != nil
|
||||||
|
|
||||||
|
proc finalize*(n: ref UnfinishedIf): NimNode =
|
||||||
|
## Finish an If statement
|
||||||
|
result = n.root
|
||||||
|
if n.root.len == 0:
|
||||||
|
# This "if" is only an "else"
|
||||||
|
result = n.elsebody
|
||||||
|
elif n.elsebody != nil:
|
||||||
|
result.add(nnkElse.newTree(n.elsebody))
|
||||||
|
|
||||||
|
proc nimRepr*(n:NimNode): string =
|
||||||
|
case n.kind
|
||||||
|
of nnkStmtList:
|
||||||
|
var lines:seq[string]
|
||||||
|
for child in n:
|
||||||
|
lines.add(child.nimRepr)
|
||||||
|
result = lines.join("\L")
|
||||||
|
of nnkCommand:
|
||||||
|
let name = n[0].nimRepr
|
||||||
|
var args:seq[string]
|
||||||
|
for i, child in n:
|
||||||
|
if i == 0:
|
||||||
|
continue
|
||||||
|
args.add(child.nimRepr)
|
||||||
|
echo n.lispRepr
|
||||||
|
let arglist = args.join(", ")
|
||||||
|
result = &"{name}({arglist})"
|
||||||
|
of nnkIdent:
|
||||||
|
result = n.strVal
|
||||||
|
of nnkStrLit:
|
||||||
|
result = "[" & n.strVal & "]"
|
||||||
|
else:
|
||||||
|
result = &"<unknown {n.kind} {n.lispRepr}>"
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"metaData": {
|
||||||
|
"url": "https://github.com/iffy/nim-argparse",
|
||||||
|
"downloadMethod": "git",
|
||||||
|
"vcsRevision": "98c7c99bfbcaae750ac515a6fd603f85ed68668f",
|
||||||
|
"files": [
|
||||||
|
"/argparse/backend.nim",
|
||||||
|
"/argparse.nim",
|
||||||
|
"/argparse/macrohelp.nim",
|
||||||
|
"/argparse.nimble",
|
||||||
|
"/argparse/filler.nim"
|
||||||
|
],
|
||||||
|
"binaries": [],
|
||||||
|
"specialVersions": [
|
||||||
|
"4.0.1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
# configparser
|
||||||
|
# Copyright xmonader
|
||||||
|
# pure Ini configurations parser
|
||||||
|
import tables, strutils, strformat
|
||||||
|
|
||||||
|
|
||||||
|
type Section* = ref object
|
||||||
|
properties: Table[string, string]
|
||||||
|
|
||||||
|
|
||||||
|
proc setProperty*(this: Section, name: string, value: string) =
|
||||||
|
this.properties[name] = value
|
||||||
|
|
||||||
|
proc newSection*() : Section =
|
||||||
|
var s = Section()
|
||||||
|
s.properties = initTable[string, string]()
|
||||||
|
|
||||||
|
return s
|
||||||
|
|
||||||
|
proc `$`*(this: Section): string =
|
||||||
|
return "<Section" & $this.properties & " >"
|
||||||
|
|
||||||
|
type Ini* = ref object
|
||||||
|
sections: Table[string, Section]
|
||||||
|
|
||||||
|
proc newIni*(): Ini =
|
||||||
|
var ini = Ini()
|
||||||
|
ini.sections = initTable[string, Section]()
|
||||||
|
return ini
|
||||||
|
|
||||||
|
proc `$`*(this: Ini): string =
|
||||||
|
return "<Ini " & $this.sections & " >"
|
||||||
|
|
||||||
|
proc setSection*(this: Ini, name: string, section: Section) =
|
||||||
|
this.sections[name] = section
|
||||||
|
|
||||||
|
proc getSection*(this: Ini, name: string): Section =
|
||||||
|
return this.sections.getOrDefault(name)
|
||||||
|
|
||||||
|
proc hasSection*(this: Ini, name: string): bool =
|
||||||
|
return this.sections.contains(name)
|
||||||
|
|
||||||
|
proc deleteSection*(this: Ini, name:string) =
|
||||||
|
this.sections.del(name)
|
||||||
|
|
||||||
|
proc sectionsCount*(this: Ini) : int =
|
||||||
|
echo $this.sections
|
||||||
|
return len(this.sections)
|
||||||
|
|
||||||
|
proc hasProperty*(this: Ini, sectionName: string, key: string): bool=
|
||||||
|
return this.sections.contains(sectionName) and this.sections[sectionName].properties.contains(key)
|
||||||
|
|
||||||
|
proc setProperty*(this: Ini, sectionName: string, key: string, value: string) =
|
||||||
|
if this.sections.contains(sectionName):
|
||||||
|
this.sections[sectionName].setProperty(key, value)
|
||||||
|
else:
|
||||||
|
raise newException(ValueError, "Ini doesn't have section " & sectionName)
|
||||||
|
|
||||||
|
proc getProperty*(this: Ini, sectionName: string, key: string): string =
|
||||||
|
if this.sections.contains(sectionName):
|
||||||
|
return this.sections[sectionName].properties.getOrDefault(key)
|
||||||
|
else:
|
||||||
|
raise newException(ValueError, "Ini doesn't have section " & sectionName)
|
||||||
|
|
||||||
|
|
||||||
|
proc deleteProperty*(this: Ini, sectionName: string, key: string) =
|
||||||
|
if this.sections.contains(sectionName) and this.sections[sectionName].properties.contains(key):
|
||||||
|
this.sections[sectionName].properties.del(key)
|
||||||
|
else:
|
||||||
|
raise newException(ValueError, "Ini doesn't have section " & sectionName)
|
||||||
|
|
||||||
|
proc toIniString*(this: Ini, sep:char='='): string =
|
||||||
|
var output = ""
|
||||||
|
for sectName, section in this.sections:
|
||||||
|
output &= "[" & sectName & "]" & "\n"
|
||||||
|
for k, v in section.properties:
|
||||||
|
output &= k & sep & v & "\n"
|
||||||
|
output &= "\n"
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
type
|
||||||
|
ParserState = enum
|
||||||
|
readSection, readKV
|
||||||
|
|
||||||
|
|
||||||
|
proc parseIni*(s: string): Ini =
|
||||||
|
var ini = newIni()
|
||||||
|
var state: ParserState = readSection
|
||||||
|
let lines = s.splitLines
|
||||||
|
|
||||||
|
var currentSectionName: string = ""
|
||||||
|
var currentSection = newSection()
|
||||||
|
|
||||||
|
for rawLine in lines:
|
||||||
|
let line = rawLine.strip()
|
||||||
|
if line.strip() == "" or line.startsWith(";") or line.startsWith("#"):
|
||||||
|
continue
|
||||||
|
if line.startsWith("["):
|
||||||
|
if line.endsWith("]"):
|
||||||
|
state = readSection
|
||||||
|
else:
|
||||||
|
raise newException(ValueError, fmt("Excpected line {line} to start with [ and end with ]"))
|
||||||
|
|
||||||
|
if state == readSection:
|
||||||
|
currentSectionName = line[1..<line.len-1]
|
||||||
|
ini.setSection(currentSectionName, currentSection)
|
||||||
|
state = readKV
|
||||||
|
continue
|
||||||
|
|
||||||
|
if state == readKV:
|
||||||
|
let parts = line.split({'='})
|
||||||
|
if len(parts) == 2:
|
||||||
|
let key = parts[0].strip()
|
||||||
|
let val = parts[1].strip()
|
||||||
|
ini.setProperty(currentSectionName, key, val)
|
||||||
|
elif len(parts) > 2:
|
||||||
|
let key = parts[0].strip()
|
||||||
|
let val = line.replace(key & " =", "").strip()
|
||||||
|
ini.setProperty(currentSectionName, key, val)
|
||||||
|
else:
|
||||||
|
raise newException(ValueError, fmt("Expected line {line} to have key = value"))
|
||||||
|
return ini
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Package
|
||||||
|
|
||||||
|
version = "0.1.0"
|
||||||
|
author = "xmonader"
|
||||||
|
description = "pure Ini configurations parser"
|
||||||
|
license = "MIT"
|
||||||
|
srcDir = "src"
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
|
||||||
|
requires "nim >= 0.18.0"
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"metaData": {
|
||||||
|
"url": "https://github.com/xmonader/nim-configparser",
|
||||||
|
"downloadMethod": "git",
|
||||||
|
"vcsRevision": "7e805b43d76e4943bac26288f6f7d85bfc19eb02",
|
||||||
|
"files": [
|
||||||
|
"/configparser.nim",
|
||||||
|
"/configparser.nimble"
|
||||||
|
],
|
||||||
|
"binaries": [],
|
||||||
|
"specialVersions": [
|
||||||
|
"0.1.0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,899 @@
|
||||||
|
import jsony/objvar, std/json, std/options, std/parseutils, std/sets,
|
||||||
|
std/strutils, std/tables, std/typetraits, std/unicode
|
||||||
|
|
||||||
|
type JsonError* = object of ValueError
|
||||||
|
|
||||||
|
const whiteSpace = {' ', '\n', '\t', '\r'}
|
||||||
|
|
||||||
|
when defined(release):
|
||||||
|
{.push checks: off, inline.}
|
||||||
|
|
||||||
|
type
|
||||||
|
SomeTable*[K, V] = Table[K, V] | OrderedTable[K, V] |
|
||||||
|
TableRef[K, V] | OrderedTableRef[K, V]
|
||||||
|
RawJson* = distinct string
|
||||||
|
|
||||||
|
proc parseHook*[T](s: string, i: var int, v: var seq[T])
|
||||||
|
proc parseHook*[T: enum](s: string, i: var int, v: var T)
|
||||||
|
proc parseHook*[T: object|ref object](s: string, i: var int, v: var T)
|
||||||
|
proc parseHook*[T](s: string, i: var int, v: var SomeTable[string, T])
|
||||||
|
proc parseHook*[T](s: string, i: var int, v: var (SomeSet[T]|set[T]))
|
||||||
|
proc parseHook*[T: tuple](s: string, i: var int, v: var T)
|
||||||
|
proc parseHook*[T: array](s: string, i: var int, v: var T)
|
||||||
|
proc parseHook*[T: not object](s: string, i: var int, v: var ref T)
|
||||||
|
proc parseHook*(s: string, i: var int, v: var JsonNode)
|
||||||
|
proc parseHook*(s: string, i: var int, v: var char)
|
||||||
|
proc parseHook*[T: distinct](s: string, i: var int, v: var T)
|
||||||
|
|
||||||
|
template error(msg: string, i: int) =
|
||||||
|
## Shortcut to raise an exception.
|
||||||
|
raise newException(JsonError, msg & " At offset: " & $i)
|
||||||
|
|
||||||
|
template eatSpace*(s: string, i: var int) =
|
||||||
|
## Will consume whitespace.
|
||||||
|
while i < s.len:
|
||||||
|
let c = s[i]
|
||||||
|
if c notin whiteSpace:
|
||||||
|
break
|
||||||
|
inc i
|
||||||
|
|
||||||
|
template eatChar*(s: string, i: var int, c: char) =
|
||||||
|
## Will consume space before and then the character `c`.
|
||||||
|
## Will raise an exception if `c` is not found.
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i >= s.len:
|
||||||
|
error("Expected " & c & " but end reached.", i)
|
||||||
|
if s[i] == c:
|
||||||
|
inc i
|
||||||
|
else:
|
||||||
|
error("Expected " & c & " but got " & s[i] & " instead.", i)
|
||||||
|
|
||||||
|
proc parseSymbol*(s: string, i: var int): string =
|
||||||
|
## Will read a symbol and return it.
|
||||||
|
## Used for numbers and booleans.
|
||||||
|
eatSpace(s, i)
|
||||||
|
var j = i
|
||||||
|
while i < s.len:
|
||||||
|
case s[i]
|
||||||
|
of ',', '}', ']', whiteSpace:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
inc i
|
||||||
|
return s[j ..< i]
|
||||||
|
|
||||||
|
proc parseHook*(s: string, i: var int, v: var bool) =
|
||||||
|
## Will parse boolean true or false.
|
||||||
|
when nimvm:
|
||||||
|
case parseSymbol(s, i)
|
||||||
|
of "true":
|
||||||
|
v = true
|
||||||
|
of "false":
|
||||||
|
v = false
|
||||||
|
else:
|
||||||
|
error("Boolean true or false expected.", i)
|
||||||
|
else:
|
||||||
|
# Its faster to do char by char scan:
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i + 3 < s.len and
|
||||||
|
s[i+0] == 't' and
|
||||||
|
s[i+1] == 'r' and
|
||||||
|
s[i+2] == 'u' and
|
||||||
|
s[i+3] == 'e':
|
||||||
|
i += 4
|
||||||
|
v = true
|
||||||
|
elif i + 4 < s.len and
|
||||||
|
s[i+0] == 'f' and
|
||||||
|
s[i+1] == 'a' and
|
||||||
|
s[i+2] == 'l' and
|
||||||
|
s[i+3] == 's' and
|
||||||
|
s[i+4] == 'e':
|
||||||
|
i += 5
|
||||||
|
v = false
|
||||||
|
else:
|
||||||
|
error("Boolean true or false expected.", i)
|
||||||
|
|
||||||
|
proc parseHook*(s: string, i: var int, v: var SomeUnsignedInt) =
|
||||||
|
## Will parse unsigned integers.
|
||||||
|
when nimvm:
|
||||||
|
v = type(v)(parseInt(parseSymbol(s, i)))
|
||||||
|
else:
|
||||||
|
eatSpace(s, i)
|
||||||
|
var
|
||||||
|
v2: uint64 = 0
|
||||||
|
startI = i
|
||||||
|
while i < s.len and s[i] in {'0'..'9'}:
|
||||||
|
v2 = v2 * 10 + (s[i].ord - '0'.ord).uint64
|
||||||
|
inc i
|
||||||
|
if startI == i:
|
||||||
|
error("Number expected.", i)
|
||||||
|
v = type(v)(v2)
|
||||||
|
|
||||||
|
proc parseHook*(s: string, i: var int, v: var SomeSignedInt) =
|
||||||
|
## Will parse signed integers.
|
||||||
|
when nimvm:
|
||||||
|
v = type(v)(parseInt(parseSymbol(s, i)))
|
||||||
|
else:
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i < s.len and s[i] == '+':
|
||||||
|
inc i
|
||||||
|
if i < s.len and s[i] == '-':
|
||||||
|
var v2: uint64
|
||||||
|
inc i
|
||||||
|
parseHook(s, i, v2)
|
||||||
|
v = -type(v)(v2)
|
||||||
|
else:
|
||||||
|
var v2: uint64
|
||||||
|
parseHook(s, i, v2)
|
||||||
|
try:
|
||||||
|
v = type(v)(v2)
|
||||||
|
except:
|
||||||
|
error("Number type to small to contain the number.", i)
|
||||||
|
|
||||||
|
proc parseHook*(s: string, i: var int, v: var SomeFloat) =
|
||||||
|
## Will parse float32 and float64.
|
||||||
|
var f: float
|
||||||
|
eatSpace(s, i)
|
||||||
|
let chars = parseutils.parseFloat(s, f, i)
|
||||||
|
if chars == 0:
|
||||||
|
error("Failed to parse a float.", i)
|
||||||
|
i += chars
|
||||||
|
v = f
|
||||||
|
|
||||||
|
proc parseUnicodeEscape(s: string, i: var int): int =
|
||||||
|
inc i
|
||||||
|
result = parseHexInt(s[i ..< i + 4])
|
||||||
|
i += 3
|
||||||
|
# Deal with UTF-16 surrogates. Most of the time strings are encoded as utf8
|
||||||
|
# but some APIs will reply with UTF-16 surrogate pairs which needs to be dealt
|
||||||
|
# with.
|
||||||
|
if (result and 0xfc00) == 0xd800:
|
||||||
|
inc i
|
||||||
|
if s[i] != '\\':
|
||||||
|
error("Found an Orphan Surrogate.", i)
|
||||||
|
inc i
|
||||||
|
if s[i] != 'u':
|
||||||
|
error("Found an Orphan Surrogate.", i)
|
||||||
|
inc i
|
||||||
|
let nextRune = parseHexInt(s[i ..< i + 4])
|
||||||
|
i += 3
|
||||||
|
if (nextRune and 0xfc00) == 0xdc00:
|
||||||
|
result = 0x10000 + (((result - 0xd800) shl 10) or (nextRune - 0xdc00))
|
||||||
|
|
||||||
|
proc parseStringSlow(s: string, i: var int, v: var string) =
|
||||||
|
while i < s.len:
|
||||||
|
let c = s[i]
|
||||||
|
case c
|
||||||
|
of '"':
|
||||||
|
break
|
||||||
|
of '\\':
|
||||||
|
inc i
|
||||||
|
let c = s[i]
|
||||||
|
case c
|
||||||
|
of '"', '\\', '/': v.add(c)
|
||||||
|
of 'b': v.add '\b'
|
||||||
|
of 'f': v.add '\f'
|
||||||
|
of 'n': v.add '\n'
|
||||||
|
of 'r': v.add '\r'
|
||||||
|
of 't': v.add '\t'
|
||||||
|
of 'u':
|
||||||
|
v.add(Rune(parseUnicodeEscape(s, i)).toUTF8())
|
||||||
|
else:
|
||||||
|
v.add(c)
|
||||||
|
else:
|
||||||
|
v.add(c)
|
||||||
|
inc i
|
||||||
|
eatChar(s, i, '"')
|
||||||
|
|
||||||
|
proc parseStringFast(s: string, i: var int, v: var string) =
|
||||||
|
# It appears to be faster to scan the string once, then allocate exact chars,
|
||||||
|
# and then scan the string again populating it.
|
||||||
|
var
|
||||||
|
j = i
|
||||||
|
ll = 0
|
||||||
|
while j < s.len:
|
||||||
|
let c = s[j]
|
||||||
|
case c
|
||||||
|
of '"':
|
||||||
|
break
|
||||||
|
of '\\':
|
||||||
|
inc j
|
||||||
|
let c = s[j]
|
||||||
|
case c
|
||||||
|
of 'u':
|
||||||
|
ll += Rune(parseUnicodeEscape(s, j)).toUTF8().len
|
||||||
|
else:
|
||||||
|
inc ll
|
||||||
|
else:
|
||||||
|
inc ll
|
||||||
|
inc j
|
||||||
|
|
||||||
|
if ll > 0:
|
||||||
|
v = newString(ll)
|
||||||
|
var
|
||||||
|
at = 0
|
||||||
|
ss = cast[ptr UncheckedArray[char]](v[0].addr)
|
||||||
|
template add(ss: ptr UncheckedArray[char], c: char) =
|
||||||
|
ss[at] = c
|
||||||
|
inc at
|
||||||
|
while i < s.len:
|
||||||
|
let c = s[i]
|
||||||
|
case c
|
||||||
|
of '"':
|
||||||
|
break
|
||||||
|
of '\\':
|
||||||
|
inc i
|
||||||
|
let c = s[i]
|
||||||
|
case c
|
||||||
|
of '"', '\\', '/': ss.add(c)
|
||||||
|
of 'b': ss.add '\b'
|
||||||
|
of 'f': ss.add '\f'
|
||||||
|
of 'n': ss.add '\n'
|
||||||
|
of 'r': ss.add '\r'
|
||||||
|
of 't': ss.add '\t'
|
||||||
|
of 'u':
|
||||||
|
for c in Rune(parseUnicodeEscape(s, i)).toUTF8():
|
||||||
|
ss.add(c)
|
||||||
|
else:
|
||||||
|
ss.add(c)
|
||||||
|
else:
|
||||||
|
ss.add(c)
|
||||||
|
inc i
|
||||||
|
|
||||||
|
eatChar(s, i, '"')
|
||||||
|
|
||||||
|
proc parseHook*(s: string, i: var int, v: var string) =
|
||||||
|
## Parse string.
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i + 3 < s.len and
|
||||||
|
s[i+0] == 'n' and
|
||||||
|
s[i+1] == 'u' and
|
||||||
|
s[i+2] == 'l' and
|
||||||
|
s[i+3] == 'l':
|
||||||
|
i += 4
|
||||||
|
return
|
||||||
|
eatChar(s, i, '"')
|
||||||
|
|
||||||
|
when nimvm:
|
||||||
|
parseStringSlow(s, i, v)
|
||||||
|
else:
|
||||||
|
when defined(js):
|
||||||
|
parseStringSlow(s, i, v)
|
||||||
|
else:
|
||||||
|
parseStringFast(s, i, v)
|
||||||
|
|
||||||
|
proc parseHook*(s: string, i: var int, v: var char) =
|
||||||
|
var str: string
|
||||||
|
s.parseHook(i, str)
|
||||||
|
if str.len != 1:
|
||||||
|
error("String can't fit into a char.", i)
|
||||||
|
v = str[0]
|
||||||
|
|
||||||
|
proc parseHook*[T](s: string, i: var int, v: var seq[T]) =
|
||||||
|
## Parse seq.
|
||||||
|
eatChar(s, i, '[')
|
||||||
|
while i < s.len:
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i < s.len and s[i] == ']':
|
||||||
|
break
|
||||||
|
var element: T
|
||||||
|
parseHook(s, i, element)
|
||||||
|
v.add(element)
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i < s.len and s[i] == ',':
|
||||||
|
inc i
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
eatChar(s, i, ']')
|
||||||
|
|
||||||
|
proc parseHook*[T: array](s: string, i: var int, v: var T) =
|
||||||
|
eatSpace(s, i)
|
||||||
|
eatChar(s, i, '[')
|
||||||
|
for value in v.mitems:
|
||||||
|
eatSpace(s, i)
|
||||||
|
parseHook(s, i, value)
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i < s.len and s[i] == ',':
|
||||||
|
inc i
|
||||||
|
eatChar(s, i, ']')
|
||||||
|
|
||||||
|
proc parseHook*[T: not object](s: string, i: var int, v: var ref T) =
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i + 3 < s.len and
|
||||||
|
s[i+0] == 'n' and
|
||||||
|
s[i+1] == 'u' and
|
||||||
|
s[i+2] == 'l' and
|
||||||
|
s[i+3] == 'l':
|
||||||
|
i += 4
|
||||||
|
return
|
||||||
|
new(v)
|
||||||
|
parseHook(s, i, v[])
|
||||||
|
|
||||||
|
proc skipValue*(s: string, i: var int) =
|
||||||
|
## Used to skip values of extra fields.
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i < s.len and s[i] == '{':
|
||||||
|
eatChar(s, i, '{')
|
||||||
|
while i < s.len:
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i < s.len and s[i] == '}':
|
||||||
|
break
|
||||||
|
skipValue(s, i)
|
||||||
|
eatChar(s, i, ':')
|
||||||
|
skipValue(s, i)
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i < s.len and s[i] == ',':
|
||||||
|
inc i
|
||||||
|
eatChar(s, i, '}')
|
||||||
|
elif i < s.len and s[i] == '[':
|
||||||
|
eatChar(s, i, '[')
|
||||||
|
while i < s.len:
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i < s.len and s[i] == ']':
|
||||||
|
break
|
||||||
|
skipValue(s, i)
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i < s.len and s[i] == ',':
|
||||||
|
inc i
|
||||||
|
eatChar(s, i, ']')
|
||||||
|
elif i < s.len and s[i] == '"':
|
||||||
|
var str: string
|
||||||
|
parseHook(s, i, str)
|
||||||
|
else:
|
||||||
|
discard parseSymbol(s, i)
|
||||||
|
|
||||||
|
proc snakeCaseDynamic(s: string): string =
|
||||||
|
if s.len == 0:
|
||||||
|
return
|
||||||
|
var prevCap = false
|
||||||
|
for i, c in s:
|
||||||
|
if c in {'A'..'Z'}:
|
||||||
|
if result.len > 0 and result[result.len-1] != '_' and not prevCap:
|
||||||
|
result.add '_'
|
||||||
|
prevCap = true
|
||||||
|
result.add c.toLowerAscii()
|
||||||
|
else:
|
||||||
|
prevCap = false
|
||||||
|
result.add c
|
||||||
|
|
||||||
|
template snakeCase(s: string): string =
|
||||||
|
const k = snakeCaseDynamic(s)
|
||||||
|
k
|
||||||
|
|
||||||
|
proc parseObjectInner[T](s: string, i: var int, v: var T) =
|
||||||
|
while i < s.len:
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i < s.len and s[i] == '}':
|
||||||
|
break
|
||||||
|
var key: string
|
||||||
|
parseHook(s, i, key)
|
||||||
|
eatChar(s, i, ':')
|
||||||
|
when compiles(renameHook(v, key)):
|
||||||
|
renameHook(v, key)
|
||||||
|
block all:
|
||||||
|
for k, v in v.fieldPairs:
|
||||||
|
if k == key or snakeCase(k) == key:
|
||||||
|
var v2: type(v)
|
||||||
|
parseHook(s, i, v2)
|
||||||
|
v = v2
|
||||||
|
break all
|
||||||
|
skipValue(s, i)
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i < s.len and s[i] == ',':
|
||||||
|
inc i
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
when compiles(postHook(v)):
|
||||||
|
postHook(v)
|
||||||
|
|
||||||
|
proc parseHook*[T: tuple](s: string, i: var int, v: var T) =
|
||||||
|
eatSpace(s, i)
|
||||||
|
when T.isNamedTuple():
|
||||||
|
if i < s.len and s[i] == '{':
|
||||||
|
eatChar(s, i, '{')
|
||||||
|
parseObjectInner(s, i, v)
|
||||||
|
eatChar(s, i, '}')
|
||||||
|
return
|
||||||
|
eatChar(s, i, '[')
|
||||||
|
for name, value in v.fieldPairs:
|
||||||
|
eatSpace(s, i)
|
||||||
|
parseHook(s, i, value)
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i < s.len and s[i] == ',':
|
||||||
|
inc i
|
||||||
|
eatChar(s, i, ']')
|
||||||
|
|
||||||
|
proc parseHook*[T: enum](s: string, i: var int, v: var T) =
|
||||||
|
eatSpace(s, i)
|
||||||
|
var strV: string
|
||||||
|
if i < s.len and s[i] == '"':
|
||||||
|
parseHook(s, i, strV)
|
||||||
|
when compiles(enumHook(strV, v)):
|
||||||
|
enumHook(strV, v)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
v = parseEnum[T](strV)
|
||||||
|
except:
|
||||||
|
error("Can't parse enum.", i)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
strV = parseSymbol(s, i)
|
||||||
|
v = T(parseInt(strV))
|
||||||
|
except:
|
||||||
|
error("Can't parse enum.", i)
|
||||||
|
|
||||||
|
proc parseHook*[T: object|ref object](s: string, i: var int, v: var T) =
|
||||||
|
## Parse an object or ref object.
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i + 3 < s.len and
|
||||||
|
s[i+0] == 'n' and
|
||||||
|
s[i+1] == 'u' and
|
||||||
|
s[i+2] == 'l' and
|
||||||
|
s[i+3] == 'l':
|
||||||
|
i += 4
|
||||||
|
return
|
||||||
|
eatChar(s, i, '{')
|
||||||
|
when not v.isObjectVariant:
|
||||||
|
when compiles(newHook(v)):
|
||||||
|
newHook(v)
|
||||||
|
elif compiles(new(v)):
|
||||||
|
new(v)
|
||||||
|
else:
|
||||||
|
# Try looking for the discriminatorFieldName, then parse as normal object.
|
||||||
|
eatSpace(s, i)
|
||||||
|
var saveI = i
|
||||||
|
while i < s.len:
|
||||||
|
var key: string
|
||||||
|
parseHook(s, i, key)
|
||||||
|
eatChar(s, i, ':')
|
||||||
|
when compiles(renameHook(v, key)):
|
||||||
|
renameHook(v, key)
|
||||||
|
if key == v.discriminatorFieldName:
|
||||||
|
var discriminator: type(v.discriminatorField)
|
||||||
|
parseHook(s, i, discriminator)
|
||||||
|
new(v, discriminator)
|
||||||
|
when compiles(newHook(v)):
|
||||||
|
newHook(v)
|
||||||
|
break
|
||||||
|
skipValue(s, i)
|
||||||
|
if i < s.len and s[i] != '}':
|
||||||
|
eatChar(s, i, ',')
|
||||||
|
else:
|
||||||
|
when compiles(newHook(v)):
|
||||||
|
newHook(v)
|
||||||
|
elif compiles(new(v)):
|
||||||
|
new(v)
|
||||||
|
break
|
||||||
|
i = saveI
|
||||||
|
parseObjectInner(s, i, v)
|
||||||
|
eatChar(s, i, '}')
|
||||||
|
|
||||||
|
proc parseHook*[T](s: string, i: var int, v: var Option[T]) =
|
||||||
|
## Parse an Option.
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i + 3 < s.len and
|
||||||
|
s[i+0] == 'n' and
|
||||||
|
s[i+1] == 'u' and
|
||||||
|
s[i+2] == 'l' and
|
||||||
|
s[i+3] == 'l':
|
||||||
|
i += 4
|
||||||
|
return
|
||||||
|
var e: T
|
||||||
|
parseHook(s, i, e)
|
||||||
|
v = some(e)
|
||||||
|
|
||||||
|
proc parseHook*[T](s: string, i: var int, v: var SomeTable[string, T]) =
|
||||||
|
## Parse an object.
|
||||||
|
when compiles(new(v)):
|
||||||
|
new(v)
|
||||||
|
eatChar(s, i, '{')
|
||||||
|
while i < s.len:
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i < s.len and s[i] == '}':
|
||||||
|
break
|
||||||
|
var key: string
|
||||||
|
parseHook(s, i, key)
|
||||||
|
eatChar(s, i, ':')
|
||||||
|
var element: T
|
||||||
|
parseHook(s, i, element)
|
||||||
|
v[key] = element
|
||||||
|
if i < s.len and s[i] == ',':
|
||||||
|
inc i
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
eatChar(s, i, '}')
|
||||||
|
|
||||||
|
proc parseHook*[T](s: string, i: var int, v: var (SomeSet[T]|set[T])) =
|
||||||
|
## Parses `HashSet`, `OrderedSet`, or a built-in `set` type.
|
||||||
|
eatSpace(s, i)
|
||||||
|
eatChar(s, i, '[')
|
||||||
|
while true:
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i < s.len and s[i] == ']':
|
||||||
|
break
|
||||||
|
var e: T
|
||||||
|
parseHook(s, i, e)
|
||||||
|
v.incl(e)
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i < s.len and s[i] == ',':
|
||||||
|
inc i
|
||||||
|
eatChar(s, i, ']')
|
||||||
|
|
||||||
|
proc parseHook*(s: string, i: var int, v: var JsonNode) =
|
||||||
|
## Parses a regular json node.
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i < s.len and s[i] == '{':
|
||||||
|
v = newJObject()
|
||||||
|
eatChar(s, i, '{')
|
||||||
|
while i < s.len:
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i < s.len and s[i] == '}':
|
||||||
|
break
|
||||||
|
var k: string
|
||||||
|
parseHook(s, i, k)
|
||||||
|
eatChar(s, i, ':')
|
||||||
|
var e: JsonNode
|
||||||
|
parseHook(s, i, e)
|
||||||
|
v[k] = e
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i < s.len and s[i] == ',':
|
||||||
|
inc i
|
||||||
|
eatChar(s, i, '}')
|
||||||
|
elif i < s.len and s[i] == '[':
|
||||||
|
v = newJArray()
|
||||||
|
eatChar(s, i, '[')
|
||||||
|
while i < s.len:
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i < s.len and s[i] == ']':
|
||||||
|
break
|
||||||
|
var e: JsonNode
|
||||||
|
parseHook(s, i, e)
|
||||||
|
v.add(e)
|
||||||
|
eatSpace(s, i)
|
||||||
|
if i < s.len and s[i] == ',':
|
||||||
|
inc i
|
||||||
|
eatChar(s, i, ']')
|
||||||
|
elif i < s.len and s[i] == '"':
|
||||||
|
var str: string
|
||||||
|
parseHook(s, i, str)
|
||||||
|
v = newJString(str)
|
||||||
|
else:
|
||||||
|
var data = parseSymbol(s, i)
|
||||||
|
if data == "null":
|
||||||
|
v = newJNull()
|
||||||
|
elif data == "true":
|
||||||
|
v = newJBool(true)
|
||||||
|
elif data == "false":
|
||||||
|
v = newJBool(false)
|
||||||
|
elif data.len > 0 and data[0] in {'0'..'9', '-', '+'}:
|
||||||
|
try:
|
||||||
|
v = newJInt(parseInt(data))
|
||||||
|
except ValueError:
|
||||||
|
try:
|
||||||
|
v = newJFloat(parseFloat(data))
|
||||||
|
except ValueError:
|
||||||
|
error("Invalid number.", i)
|
||||||
|
else:
|
||||||
|
error("Unexpected.", i)
|
||||||
|
|
||||||
|
proc parseHook*[T: distinct](s: string, i: var int, v: var T) =
|
||||||
|
var x: T.distinctBase
|
||||||
|
parseHook(s, i, x)
|
||||||
|
v = cast[T](x)
|
||||||
|
|
||||||
|
proc fromJson*[T](s: string, x: typedesc[T]): T =
|
||||||
|
## Takes json and outputs the object it represents.
|
||||||
|
## * Extra json fields are ignored.
|
||||||
|
## * Missing json fields keep their default values.
|
||||||
|
## * `proc newHook(foo: var ...)` Can be used to populate default values.
|
||||||
|
var i = 0
|
||||||
|
s.parseHook(i, result)
|
||||||
|
|
||||||
|
proc fromJson*(s: string): JsonNode =
|
||||||
|
## Takes json parses it into `JsonNode`s.
|
||||||
|
var i = 0
|
||||||
|
s.parseHook(i, result)
|
||||||
|
|
||||||
|
proc dumpHook*(s: var string, v: bool)
|
||||||
|
proc dumpHook*(s: var string, v: uint|uint8|uint16|uint32|uint64)
|
||||||
|
proc dumpHook*(s: var string, v: int|int8|int16|int32|int64)
|
||||||
|
proc dumpHook*(s: var string, v: SomeFloat)
|
||||||
|
proc dumpHook*(s: var string, v: string)
|
||||||
|
proc dumpHook*(s: var string, v: char)
|
||||||
|
proc dumpHook*(s: var string, v: tuple)
|
||||||
|
proc dumpHook*(s: var string, v: enum)
|
||||||
|
type t[T] = tuple[a: string, b: T]
|
||||||
|
proc dumpHook*[N, T](s: var string, v: array[N, t[T]])
|
||||||
|
proc dumpHook*[N, T](s: var string, v: array[N, T])
|
||||||
|
proc dumpHook*[T](s: var string, v: seq[T])
|
||||||
|
proc dumpHook*(s: var string, v: object)
|
||||||
|
proc dumpHook*(s: var string, v: ref)
|
||||||
|
proc dumpHook*[T: distinct](s: var string, v: T)
|
||||||
|
|
||||||
|
proc dumpHook*[T: distinct](s: var string, v: T) =
|
||||||
|
var x = cast[T.distinctBase](v)
|
||||||
|
s.dumpHook(x)
|
||||||
|
|
||||||
|
proc dumpHook*(s: var string, v: bool) =
|
||||||
|
if v:
|
||||||
|
s.add "true"
|
||||||
|
else:
|
||||||
|
s.add "false"
|
||||||
|
|
||||||
|
const lookup = block:
|
||||||
|
## Generate 00, 01, 02 ... 99 pairs.
|
||||||
|
var s = ""
|
||||||
|
for i in 0 ..< 100:
|
||||||
|
if ($i).len == 1:
|
||||||
|
s.add("0")
|
||||||
|
s.add($i)
|
||||||
|
s
|
||||||
|
|
||||||
|
proc dumpNumberSlow(s: var string, v: uint|uint8|uint16|uint32|uint64) =
|
||||||
|
s.add $v.uint64
|
||||||
|
|
||||||
|
proc dumpNumberFast(s: var string, v: uint|uint8|uint16|uint32|uint64) =
|
||||||
|
# Its faster to not allocate a string for a number,
|
||||||
|
# but to write it out the digits directly.
|
||||||
|
if v == 0:
|
||||||
|
s.add '0'
|
||||||
|
return
|
||||||
|
# Max size of a uin64 number is 20 digits.
|
||||||
|
var digits: array[20, char]
|
||||||
|
var v = v
|
||||||
|
var p = 0
|
||||||
|
while v != 0:
|
||||||
|
# Its faster to look up 2 digits at a time, less int divisions.
|
||||||
|
let idx = v mod 100
|
||||||
|
digits[p] = lookup[idx*2+1]
|
||||||
|
inc p
|
||||||
|
digits[p] = lookup[idx*2]
|
||||||
|
inc p
|
||||||
|
v = v div 100
|
||||||
|
var at = s.len
|
||||||
|
if digits[p-1] == '0':
|
||||||
|
dec p
|
||||||
|
s.setLen(s.len + p)
|
||||||
|
dec p
|
||||||
|
while p >= 0:
|
||||||
|
s[at] = digits[p]
|
||||||
|
dec p
|
||||||
|
inc at
|
||||||
|
|
||||||
|
proc dumpHook*(s: var string, v: uint|uint8|uint16|uint32|uint64) =
|
||||||
|
when nimvm:
|
||||||
|
s.dumpNumberSlow(v)
|
||||||
|
else:
|
||||||
|
when defined(js):
|
||||||
|
s.dumpNumberSlow(v)
|
||||||
|
else:
|
||||||
|
s.dumpNumberFast(v)
|
||||||
|
|
||||||
|
proc dumpHook*(s: var string, v: int|int8|int16|int32|int64) =
|
||||||
|
if v < 0:
|
||||||
|
s.add '-'
|
||||||
|
dumpHook(s, 0.uint64 - v.uint64)
|
||||||
|
else:
|
||||||
|
dumpHook(s, v.uint64)
|
||||||
|
|
||||||
|
proc dumpHook*(s: var string, v: SomeFloat) =
|
||||||
|
s.add $v
|
||||||
|
|
||||||
|
proc dumpStrSlow(s: var string, v: string) =
|
||||||
|
s.add '"'
|
||||||
|
for c in v:
|
||||||
|
case c:
|
||||||
|
of '\\': s.add r"\\"
|
||||||
|
of '\b': s.add r"\b"
|
||||||
|
of '\f': s.add r"\f"
|
||||||
|
of '\n': s.add r"\n"
|
||||||
|
of '\r': s.add r"\r"
|
||||||
|
of '\t': s.add r"\t"
|
||||||
|
of '"': s.add r"\"""
|
||||||
|
else:
|
||||||
|
s.add c
|
||||||
|
s.add '"'
|
||||||
|
|
||||||
|
proc dumpStrFast(s: var string, v: string) =
|
||||||
|
# Its faster to grow the string only once.
|
||||||
|
# Then fill the string with pointers.
|
||||||
|
# Then cap it off to right length.
|
||||||
|
var at = s.len
|
||||||
|
s.setLen(s.len + v.len*2+2)
|
||||||
|
|
||||||
|
var ss = cast[ptr UncheckedArray[char]](s[0].addr)
|
||||||
|
template add(ss: ptr UncheckedArray[char], c: char) =
|
||||||
|
ss[at] = c
|
||||||
|
inc at
|
||||||
|
template add(ss: ptr UncheckedArray[char], c1, c2: char) =
|
||||||
|
ss[at] = c1
|
||||||
|
inc at
|
||||||
|
ss[at] = c2
|
||||||
|
inc at
|
||||||
|
|
||||||
|
ss.add '"'
|
||||||
|
for c in v:
|
||||||
|
case c:
|
||||||
|
of '\\': ss.add '\\', '\\'
|
||||||
|
of '\b': ss.add '\\', 'b'
|
||||||
|
of '\f': ss.add '\\', 'f'
|
||||||
|
of '\n': ss.add '\\', 'n'
|
||||||
|
of '\r': ss.add '\\', 'r'
|
||||||
|
of '\t': ss.add '\\', 't'
|
||||||
|
of '"': ss.add '\\', '"'
|
||||||
|
else:
|
||||||
|
ss.add c
|
||||||
|
ss.add '"'
|
||||||
|
s.setLen(at)
|
||||||
|
|
||||||
|
proc dumpHook*(s: var string, v: string) =
|
||||||
|
when nimvm:
|
||||||
|
s.dumpStrSlow(v)
|
||||||
|
else:
|
||||||
|
when defined(js):
|
||||||
|
s.dumpStrSlow(v)
|
||||||
|
else:
|
||||||
|
s.dumpStrFast(v)
|
||||||
|
|
||||||
|
template dumpKey(s: var string, v: string) =
|
||||||
|
const v2 = v.toJson() & ":"
|
||||||
|
s.add v2
|
||||||
|
|
||||||
|
proc dumpHook*(s: var string, v: char) =
|
||||||
|
s.add '"'
|
||||||
|
s.add v
|
||||||
|
s.add '"'
|
||||||
|
|
||||||
|
proc dumpHook*(s: var string, v: tuple) =
|
||||||
|
s.add '['
|
||||||
|
var i = 0
|
||||||
|
for _, e in v.fieldPairs:
|
||||||
|
if i > 0:
|
||||||
|
s.add ','
|
||||||
|
s.dumpHook(e)
|
||||||
|
inc i
|
||||||
|
s.add ']'
|
||||||
|
|
||||||
|
proc dumpHook*(s: var string, v: enum) =
|
||||||
|
s.dumpHook($v)
|
||||||
|
|
||||||
|
proc dumpHook*[N, T](s: var string, v: array[N, T]) =
|
||||||
|
s.add '['
|
||||||
|
var i = 0
|
||||||
|
for e in v:
|
||||||
|
if i != 0:
|
||||||
|
s.add ','
|
||||||
|
s.dumpHook(e)
|
||||||
|
inc i
|
||||||
|
s.add ']'
|
||||||
|
|
||||||
|
proc dumpHook*[T](s: var string, v: seq[T]) =
|
||||||
|
s.add '['
|
||||||
|
for i, e in v:
|
||||||
|
if i != 0:
|
||||||
|
s.add ','
|
||||||
|
s.dumpHook(e)
|
||||||
|
s.add ']'
|
||||||
|
|
||||||
|
proc dumpHook*[T](s: var string, v: Option[T]) =
|
||||||
|
if v.isNone:
|
||||||
|
s.add "null"
|
||||||
|
else:
|
||||||
|
s.dumpHook(v.get())
|
||||||
|
|
||||||
|
proc dumpHook*(s: var string, v: object) =
|
||||||
|
s.add '{'
|
||||||
|
var i = 0
|
||||||
|
when compiles(for k, e in v.pairs: discard):
|
||||||
|
# Tables and table like objects.
|
||||||
|
for k, e in v.pairs:
|
||||||
|
if i > 0:
|
||||||
|
s.add ','
|
||||||
|
s.dumpHook(k)
|
||||||
|
s.add ':'
|
||||||
|
s.dumpHook(e)
|
||||||
|
inc i
|
||||||
|
else:
|
||||||
|
# Normal objects.
|
||||||
|
for k, e in v.fieldPairs:
|
||||||
|
if i > 0:
|
||||||
|
s.add ','
|
||||||
|
s.dumpKey(k)
|
||||||
|
s.dumpHook(e)
|
||||||
|
inc i
|
||||||
|
s.add '}'
|
||||||
|
|
||||||
|
proc dumpHook*[N, T](s: var string, v: array[N, t[T]]) =
|
||||||
|
s.add '{'
|
||||||
|
var i = 0
|
||||||
|
# Normal objects.
|
||||||
|
for (k, e) in v:
|
||||||
|
if i > 0:
|
||||||
|
s.add ','
|
||||||
|
s.dumpHook(k)
|
||||||
|
s.add ':'
|
||||||
|
s.dumpHook(e)
|
||||||
|
inc i
|
||||||
|
s.add '}'
|
||||||
|
|
||||||
|
proc dumpHook*(s: var string, v: ref) =
|
||||||
|
if v == nil:
|
||||||
|
s.add "null"
|
||||||
|
else:
|
||||||
|
s.dumpHook(v[])
|
||||||
|
|
||||||
|
proc dumpHook*[T](s: var string, v: SomeSet[T]|set[T]) =
|
||||||
|
s.add '['
|
||||||
|
var i = 0
|
||||||
|
for e in v:
|
||||||
|
if i != 0:
|
||||||
|
s.add ','
|
||||||
|
s.dumpHook(e)
|
||||||
|
inc i
|
||||||
|
s.add ']'
|
||||||
|
|
||||||
|
proc dumpHook*(s: var string, v: JsonNode) =
|
||||||
|
## Dumps a regular json node.
|
||||||
|
if v == nil:
|
||||||
|
s.add "null"
|
||||||
|
else:
|
||||||
|
case v.kind:
|
||||||
|
of JObject:
|
||||||
|
s.add '{'
|
||||||
|
var i = 0
|
||||||
|
for k, e in v.pairs:
|
||||||
|
if i != 0:
|
||||||
|
s.add ","
|
||||||
|
s.dumpHook(k)
|
||||||
|
s.add ':'
|
||||||
|
s.dumpHook(e)
|
||||||
|
inc i
|
||||||
|
s.add '}'
|
||||||
|
of JArray:
|
||||||
|
s.add '['
|
||||||
|
var i = 0
|
||||||
|
for e in v:
|
||||||
|
if i != 0:
|
||||||
|
s.add ","
|
||||||
|
s.dumpHook(e)
|
||||||
|
inc i
|
||||||
|
s.add ']'
|
||||||
|
of JNull:
|
||||||
|
s.add "null"
|
||||||
|
of JInt:
|
||||||
|
s.dumpHook(v.getInt)
|
||||||
|
of JFloat:
|
||||||
|
s.dumpHook(v.getFloat)
|
||||||
|
of JString:
|
||||||
|
s.dumpHook(v.getStr)
|
||||||
|
of JBool:
|
||||||
|
s.dumpHook(v.getBool)
|
||||||
|
|
||||||
|
proc parseHook*(s: string, i: var int, v: var RawJson) =
|
||||||
|
let oldI = i
|
||||||
|
skipValue(s, i)
|
||||||
|
v = s[oldI ..< i].RawJson
|
||||||
|
|
||||||
|
proc dumpHook*(s: var string, v: RawJson) =
|
||||||
|
s.add v.string
|
||||||
|
|
||||||
|
proc toJson*[T](v: T): string =
|
||||||
|
dumpHook(result, v)
|
||||||
|
|
||||||
|
template toStaticJson*(v: untyped): static[string] =
|
||||||
|
## This will turn v into json at compile time and return the json string.
|
||||||
|
const s = v.toJson()
|
||||||
|
s
|
||||||
|
|
||||||
|
# A compiler bug prevents this from working. Otherwise toStaticJson and toJson
|
||||||
|
# can be same thing.
|
||||||
|
# TODO: Figure out the compiler bug.
|
||||||
|
# proc toJsonDynamic*[T](v: T): string =
|
||||||
|
# dumpHook(result, v)
|
||||||
|
# template toJson*[T](v: static[T]): string =
|
||||||
|
# ## This will turn v into json at compile time and return the json string.
|
||||||
|
# const s = v.toJsonDynamic()
|
||||||
|
# s
|
||||||
|
|
||||||
|
when defined(release):
|
||||||
|
{.pop.}
|
|
@ -0,0 +1,8 @@
|
||||||
|
version = "1.1.5"
|
||||||
|
author = "Andre von Houck"
|
||||||
|
description = "A loose direct to object json parser with hooks."
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
srcDir = "src"
|
||||||
|
|
||||||
|
requires "nim >= 1.4.0"
|
|
@ -0,0 +1,53 @@
|
||||||
|
import macros
|
||||||
|
|
||||||
|
proc hasKind(node: NimNode, kind: NimNodeKind): bool =
|
||||||
|
for c in node.children:
|
||||||
|
if c.kind == kind:
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
proc `[]`(node: NimNode, kind: NimNodeKind): NimNode =
|
||||||
|
for c in node.children:
|
||||||
|
if c.kind == kind:
|
||||||
|
return c
|
||||||
|
return nil
|
||||||
|
|
||||||
|
template fieldPairs*[T: ref object](x: T): untyped =
|
||||||
|
x[].fieldPairs
|
||||||
|
|
||||||
|
macro isObjectVariant*(v: typed): bool =
|
||||||
|
## Is this an object variant?
|
||||||
|
var typ = v.getTypeImpl()
|
||||||
|
if typ.kind == nnkSym:
|
||||||
|
return ident("false")
|
||||||
|
while typ.kind != nnkObjectTy:
|
||||||
|
typ = typ[0].getTypeImpl()
|
||||||
|
if typ[2].hasKind(nnkRecCase):
|
||||||
|
ident("true")
|
||||||
|
else:
|
||||||
|
ident("false")
|
||||||
|
|
||||||
|
proc discriminator*(v: NimNode): NimNode =
|
||||||
|
var typ = v.getTypeImpl()
|
||||||
|
while typ.kind != nnkObjectTy:
|
||||||
|
typ = typ[0].getTypeImpl()
|
||||||
|
return typ[nnkRecList][nnkRecCase][nnkIdentDefs][nnkSym]
|
||||||
|
|
||||||
|
macro discriminatorFieldName*(v: typed): untyped =
|
||||||
|
## Turns into the discriminator field.
|
||||||
|
return newLit($discriminator(v))
|
||||||
|
|
||||||
|
macro discriminatorField*(v: typed): untyped =
|
||||||
|
## Turns into the discriminator field.
|
||||||
|
let
|
||||||
|
fieldName = discriminator(v)
|
||||||
|
return quote do:
|
||||||
|
`v`.`fieldName`
|
||||||
|
|
||||||
|
macro new*(v: typed, d: typed): untyped =
|
||||||
|
## Creates a new object variant with the discriminator field.
|
||||||
|
let
|
||||||
|
typ = v.getTypeInst()
|
||||||
|
fieldName = discriminator(v)
|
||||||
|
return quote do:
|
||||||
|
`v` = `typ`(`fieldName`: `d`)
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"metaData": {
|
||||||
|
"url": "https://github.com/treeform/jsony",
|
||||||
|
"downloadMethod": "git",
|
||||||
|
"vcsRevision": "ea811bec7fa50f5abd3088ba94cda74285e93f18",
|
||||||
|
"files": [
|
||||||
|
"/jsony.nimble",
|
||||||
|
"/jsony/objvar.nim",
|
||||||
|
"/jsony.nim"
|
||||||
|
],
|
||||||
|
"binaries": [],
|
||||||
|
"specialVersions": [
|
||||||
|
"1.1.5"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"metaData": {
|
||||||
|
"url": "https://github.com/NimParsers/parsetoml.git",
|
||||||
|
"downloadMethod": "git",
|
||||||
|
"vcsRevision": "6e5e16179fa2db60f2f37d8b1af4128aaa9c8aaf",
|
||||||
|
"files": [
|
||||||
|
"/parsetoml.nimble",
|
||||||
|
"/parsetoml.nim"
|
||||||
|
],
|
||||||
|
"binaries": [],
|
||||||
|
"specialVersions": [
|
||||||
|
"0.7.1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,83 @@
|
||||||
|
# Packages
|
||||||
|
|
||||||
|
version = "0.7.1"
|
||||||
|
author = "Maurizio Tomasi <ziotom78 .at. gmail.com>"
|
||||||
|
description = "Toml parser library for Nim"
|
||||||
|
license = "MIT"
|
||||||
|
srcDir = "src"
|
||||||
|
skipDirs = @["decoder"]
|
||||||
|
|
||||||
|
# Deps
|
||||||
|
|
||||||
|
requires "nim >= 0.18.0"
|
||||||
|
|
||||||
|
from os import `/`, expandTilde
|
||||||
|
from strutils import `%`
|
||||||
|
|
||||||
|
task run_toml_test, "Validates parsetoml using toml-test":
|
||||||
|
exec("nim c -d:release decoder/decoder.nim")
|
||||||
|
# Needs "go" executable to be present in PATH.
|
||||||
|
# In GHA, add "- uses: actions/setup-go@v2"
|
||||||
|
let
|
||||||
|
tomlTestRepo = "github.com/BurntSushi/toml-test/cmd/toml-test@master"
|
||||||
|
exec("go get -u -v " & tomlTestRepo)
|
||||||
|
exec("toml-test decoder/decoder")
|
||||||
|
|
||||||
|
# https://github.com/NimParsers/parsetoml/issues/40
|
||||||
|
# FIXME: Delete below task once above issue is fixed
|
||||||
|
# i.e. parsetoml starts supporting TOML v1.0.0.
|
||||||
|
task run_toml_test_with_skips, "Validates parsetoml using toml-test (with test skips)":
|
||||||
|
exec("nim c -d:release decoder/decoder.nim")
|
||||||
|
# Needs "go" executable to be present in PATH.
|
||||||
|
# In GHA, add "- uses: actions/setup-go@v2"
|
||||||
|
let
|
||||||
|
tomlTestRepo = "github.com/BurntSushi/toml-test/cmd/toml-test@master"
|
||||||
|
exec("go get -u -v " & tomlTestRepo)
|
||||||
|
exec("toml-test" &
|
||||||
|
" -skip valid/array" &
|
||||||
|
" -skip valid/array-bool" &
|
||||||
|
" -skip valid/array-empty" &
|
||||||
|
" -skip valid/array-hetergeneous" &
|
||||||
|
" -skip valid/array-mixed-int-array" &
|
||||||
|
" -skip valid/array-mixed-int-float" &
|
||||||
|
" -skip valid/array-mixed-int-string" &
|
||||||
|
" -skip valid/array-mixed-string-table" &
|
||||||
|
" -skip valid/array-nested-double" &
|
||||||
|
" -skip valid/array-nested" &
|
||||||
|
" -skip valid/array-nospaces" &
|
||||||
|
" -skip valid/array-string-quote-comma-2" &
|
||||||
|
" -skip valid/array-string-quote-comma" &
|
||||||
|
" -skip valid/array-string-with-comma" &
|
||||||
|
" -skip valid/array-strings" &
|
||||||
|
" -skip valid/comment-everywhere" &
|
||||||
|
" -skip valid/comment-tricky" &
|
||||||
|
" -skip valid/datetime-local-date" &
|
||||||
|
" -skip valid/datetime-local-time" &
|
||||||
|
" -skip valid/example" &
|
||||||
|
" -skip valid/float-inf-and-nan" &
|
||||||
|
" -skip valid/float-zero" &
|
||||||
|
" -skip valid/inline-table-key-dotted" &
|
||||||
|
" -skip valid/inline-table-nest" &
|
||||||
|
" -skip valid/multiline-string-quotes" &
|
||||||
|
" -skip valid/multiline-string" &
|
||||||
|
" -skip valid/spec-example-1-compact" &
|
||||||
|
" -skip valid/spec-example-1" &
|
||||||
|
" -skip invalid/array-missing-separator" &
|
||||||
|
" -skip invalid/array-of-tables-1" &
|
||||||
|
" -skip invalid/duplicate-table-array2" &
|
||||||
|
" -skip invalid/encoding-bad-utf8-in-comment" &
|
||||||
|
" -skip invalid/encoding-bad-utf8-in-string" &
|
||||||
|
" -skip invalid/encoding-utf16" &
|
||||||
|
" -skip invalid/inline-table-double-comma" &
|
||||||
|
" -skip invalid/inline-table-no-comma" &
|
||||||
|
" -skip invalid/inline-table-trailing-comma" &
|
||||||
|
" -skip invalid/integer-double-sign-nex" &
|
||||||
|
" -skip invalid/integer-double-sign-plus" &
|
||||||
|
" -skip invalid/integer-leading-zero-sign-1" &
|
||||||
|
" -skip invalid/integer-leading-zero-sign-2" &
|
||||||
|
" -skip invalid/key-multiline" &
|
||||||
|
" -skip invalid/string-bad-multiline" &
|
||||||
|
" -skip invalid/string-multiline-escape-space" &
|
||||||
|
" -skip invalid/string-multiline-escape-space" &
|
||||||
|
" -skip valid/float-exponent" & # https://github.com/NimParsers/parsetoml/issues/51
|
||||||
|
" decoder/decoder")
|
|
@ -53,6 +53,9 @@ proc getLast() =
|
||||||
LAST = readFile(LAST_FILE).strip()
|
LAST = readFile(LAST_FILE).strip()
|
||||||
|
|
||||||
proc setLastFileName(file: string) =
|
proc setLastFileName(file: string) =
|
||||||
|
if file.len == 0:
|
||||||
|
echo "no file"
|
||||||
|
return
|
||||||
writeFile(LAST_FILE, file)
|
writeFile(LAST_FILE, file)
|
||||||
LAST = file
|
LAST = file
|
||||||
|
|
||||||
|
@ -61,9 +64,12 @@ proc getImageFromDir(): string =
|
||||||
var img_files = getFiles(BG_DIR).filter(proc(f: string): bool = f != LAST)
|
var img_files = getFiles(BG_DIR).filter(proc(f: string): bool = f != LAST)
|
||||||
randomize()
|
randomize()
|
||||||
img_files.shuffle()
|
img_files.shuffle()
|
||||||
|
if img_files.len > 0:
|
||||||
let img_file = img_files[0]
|
let img_file = img_files[0]
|
||||||
notify.send("Found : ", img_file)
|
notify.send("Found : ", img_file)
|
||||||
return img_file
|
return img_file
|
||||||
|
notify.send("No Background Images Found")
|
||||||
|
return ""
|
||||||
|
|
||||||
proc getCurrSwayBGPID(): string =
|
proc getCurrSwayBGPID(): string =
|
||||||
let pid = execCmdEx("pgrep swaybg")
|
let pid = execCmdEx("pgrep swaybg")
|
||||||
|
|
Loading…
Reference in a new issue