added tideurrl

This commit is contained in:
Paul Wilde 2023-11-23 20:10:04 +00:00
parent e6a72669ba
commit 7f4bf7bb0f
20 changed files with 67919 additions and 44 deletions

View file

@ -0,0 +1,32 @@
{
"version": 1,
"reverseDeps": {
"parsetoml": {
"0.7.1": {
"586fe63467a674008c4445ed1b8ac882177d7103": [
{
"path": "/srv/http/Development/wm_tools"
}
]
}
},
"argparse": {
"4.0.1": {
"e9c2ebe3f74b1dfc4df773686ae6dab7638a8662": [
{
"path": "/srv/http/Development/wm_tools"
}
]
}
},
"configparser": {
"0.1.0": {
"5f854c4a8243430e1799136ff0fd88d9d32b3228": [
{
"path": "/srv/http/Development/wm_tools"
}
]
}
}
}
}

File diff suppressed because it is too large Load diff

31987
nimbledeps/packages_temp.json Normal file

File diff suppressed because it is too large Load diff

View file

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

View file

@ -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"

View file

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

View file

@ -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

View file

@ -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}>"

View file

@ -0,0 +1,19 @@
{
"version": 1,
"metaData": {
"url": "https://github.com/iffy/nim-argparse",
"downloadMethod": "git",
"vcsRevision": "98c7c99bfbcaae750ac515a6fd603f85ed68668f",
"files": [
"/argparse/backend.nim",
"/argparse.nimble",
"/argparse/macrohelp.nim",
"/argparse.nim",
"/argparse/filler.nim"
],
"binaries": [],
"specialVersions": [
"4.0.1"
]
}
}

View file

@ -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

View file

@ -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"

View file

@ -0,0 +1,16 @@
{
"version": 1,
"metaData": {
"url": "https://github.com/xmonader/nim-configparser",
"downloadMethod": "git",
"vcsRevision": "7e805b43d76e4943bac26288f6f7d85bfc19eb02",
"files": [
"/configparser.nimble",
"/configparser.nim"
],
"binaries": [],
"specialVersions": [
"0.1.0"
]
}
}

View file

@ -0,0 +1,16 @@
{
"version": 1,
"metaData": {
"url": "https://github.com/NimParsers/parsetoml.git",
"downloadMethod": "git",
"vcsRevision": "6e5e16179fa2db60f2f37d8b1af4128aaa9c8aaf",
"files": [
"/parsetoml.nim",
"/parsetoml.nimble"
],
"binaries": [],
"specialVersions": [
"0.7.1"
]
}
}

View file

@ -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")

View file

@ -15,6 +15,7 @@ import util/temperaturr
import util/screenshurrt
import util/calculaturr
import util/brightnurrs
import util/tideurrl
proc dispatch*(cfg: Config) =
case cfg.run
@ -48,5 +49,7 @@ proc dispatch*(cfg: Config) =
calculaturr.go()
of Brightnurrs:
brightnurrs.go()
of Tideurrl:
tideurrl.go()
else:
echo "No valid run command given"

22
src/model/tides.nim Normal file
View file

@ -0,0 +1,22 @@
import times
const TIDE_URL* = "https://www.tidetimes.org.uk/%LOC-tide-times"
const DEFAULT_LOC* = "exmouth-dock"
type
Tide* = ref object
state*: string
time*: string
height*: string
tomorrow*: bool
TideList* = ref object
tides*: seq[Tide]
url*: string
last_updated*: DateTime
location*: string
proc newTideList*(): TideList =
var tl = TideList()
tl.url = TIDE_URL
tl.location = DEFAULT_LOC
return tl

View file

@ -16,4 +16,5 @@ type
Temperaturr,
Screenshurrt,
Calculaturr,
Brightnurrs
Brightnurrs,
Tideurrl

View file

@ -7,6 +7,7 @@ import model/pwgen
import model/volume
import model/brightness
import model/screenshot
import model/tides
proc parseArgs*() =
let params = commandLineParams()
@ -47,6 +48,8 @@ proc parseArgs*() =
myConfig.run = Calculaturr
of "brightnurrs", "brightness", "bright":
myConfig.run = Brightnurrs
of "tideurrl", "tides":
myConfig.run = Tideurrl
else:
echo p.help
quit(1)
@ -154,3 +157,23 @@ proc parseScreenshotArgs*(): Screenshot =
stderr.writeLine getCurrentExceptionMsg()
quit(1)
return ss
proc parseTideurrlArgs*(): TideList =
var t = newTideList()
let params = commandLineParams()
var p = newParser:
help("Args for tideurrl")
arg("tideurrl",help="can only ever be 'tideurrl' as you won't have gotten this far otherwise")
option("-l","--loc",help="location name")
try:
var opts = p.parse(params)
if opts.loc != "":
t.location = opts.loc
except ShortCircuit as err:
if err.flag == "argparse_help":
echo err.help
quit(1)
except UsageError:
stderr.writeLine getCurrentExceptionMsg()
quit(1)
return t

View file

@ -1,46 +1,46 @@
#curl https://www.tidetimes.org.uk/exmouth-dock-tide-times-20190101 | grep -E -o ">((High|Low)|([0-9]+:[0-9]+)|([0-9]+\.[0-9]+m))"
import ../../globurrl
import std/[re,httpclient,times,osproc,sequtils]
# TODO:
# Pass location in as variable
import re
import httpclient
import times
import osproc
import sequtils
import ../common
import ../parser
import ../output
import ../model/tides
const url* = "https://www.tidetimes.org.uk/%LOC-tide-times"
const loc* = "exmouth-dock"
const icon: string = "🌊 "
type
Tide = ref object
State: string
Time: string
Height: string
Tomorrow: bool
TideList = ref object
Tides: seq[Tide]
LastUpdated: DateTime
proc sortTides(tides: seq[Tide], is_tomorrow: bool = false): seq[Tide] =
let timenow = now()
var reltides: seq[Tide]
for tide in tides:
if timenow.format("HH:MM") <= tide.Time and not is_tomorrow:
if timenow.format("HH:MM") <= tide.time and not is_tomorrow:
reltides.add(tide)
elif is_tomorrow:
reltides.add(tide)
return reltides
proc getTideData(get_tomorrow: bool = false): seq[Tide] =
proc getTideData(mytides: TideList, get_tomorrow: bool = false): seq[Tide] =
var tides: seq[Tide]
let fnd = re">((High|Low)|([0-9]+:[0-9]+)|([0-9]+\.[0-9]+m))"
var client = newHttpClient()
var link = replace(url,re"\%LOC",loc)
var client = newHttpClient(timeout = 10000)
var link = replace(mytides.url,re"\%LOC",mytides.location)
if get_tomorrow:
let tomdate = now() + initTimeInterval(days = 1)
link &= "-" & tomdate.format("yyyyMMdd")
try:
# Remember to compile with -d:ssl else this won't work
let data = client.getContent(link)
let resp = client.request(link)
if resp.status != $Http200 or resp.body == "":
var data = newInfo("Tideurrl")
data.full_text = "Error Response: " & resp.status & ":\nBody:" & resp.body
discard outputData(data)
return @[]
let data = resp.body
let times = findAll(data,fnd)
var tide: Tide
var column = 0
@ -50,57 +50,61 @@ proc getTideData(get_tomorrow: bool = false): seq[Tide] =
let l = len(time) - 1
if time == ">High" or time == ">Low":
tide = Tide()
tide.State = time[1..l]
tide.state = time[1..l]
column = 1
continue
elif column == 1:
tide.Time = time[1..l]
tide.time = time[1..l]
column = 2
continue
elif column == 2:
tide.Height = time[1..l]
tide.height = time[1..l]
tides.add(tide)
column = 0
continue
except:
echo "Unable to get Tide Data : " & getCurrentExceptionMsg()
var data = newInfo("Tideurrl")
data.full_text = "Unable to get Tide Data : " & getCurrentExceptionMsg()
discard outputData(data)
return tides
proc getDesign(tides: seq[Tide]): Info =
proc getDesign(tl: TideList): Info =
var my_tides: seq[string] = @[]
my_tides.add(tl.location)
let tides = tl.tides
for tide in tides:
let str = icon & tide.State[0] & " " & tide.Time & " " & tide.Height
let str = icon & tide.state[0] & " " & tide.time & " " & tide.height
my_tides.add(str)
var data = newInfo("Tideurrl")
data.border = black
data.args = my_tides
return data
proc getTides*(get_tomorrow: bool = false) =
var mytides = TideList()
mytides.Tides = getTideData(get_tomorrow)
mytides.Tides = sortTides(mytides.Tides, get_tomorrow)
if len(mytides.Tides) == 0:
getTides(true)
proc getTides*(mytides: var TideList, get_tomorrow: bool = false) =
mytides.tides = mytides.getTideData(get_tomorrow)
mytides.tides = sortTides(mytides.tides, get_tomorrow)
if len(mytides.tides) == 0:
return
let data = getDesign(mytides.Tides)
let data = getDesign(mytides)
var opt_tomorrow = "tomorrow"
if get_tomorrow:
opt_tomorrow = "back"
let args = concat(data.args,@["---",opt_tomorrow])
let output = outputData(data,args)
if output == "tomorrow":
getTides(true)
getTides(mytides,true)
elif output == "back":
getTides()
elif output == "---":
getTides(mytides)
elif output == "---" or output == "":
return
elif output in args:
let url = replace(url,re"\%LOC",loc)
let url = replace(mytides.url,re"\%LOC",mytides.location)
discard execCmd("xdg-open " & url)
else:
mytides.location = output
getTides(mytides)
proc main() =
getTides()
if isMainModule:
main()
proc go*() =
var mytides = parseTideurrlArgs()
getTides(mytides)