Compare commits

..

No commits in common. "cbeefd675c0884feebad4dc62910092519f8b2ed" and "2a5dce888d7abf5409a62208d951ac3a4e6babfa" have entirely different histories.

3 changed files with 59 additions and 155 deletions

View file

@ -14,16 +14,13 @@ type
HwylFlagHelp* = tuple
short, long, description: string
HwylSubCmdHelp* = tuple
name, aliases, desc: string
HwylCliStyleSetting = enum
Aliases
name, desc: string
HwylCliStyles* = object
header* = "bold cyan"
flagShort* = "yellow"
flagLong* = "magenta"
flagDesc* = ""
cmd* = "bold"
settings*: set[HwylCliStyleSetting] = {Aliases}
HwylCliHelp* = object
usage*: string
desc*: string
@ -44,12 +41,8 @@ func newHwylCliHelp*(
styles = HwylCliStyles()
): HwylCliHelp =
result.desc = dedent(desc).strip()
if Aliases in styles.settings:
result.subcmds =
subcmds.mapIt((it.name & " " & it.aliases, it.aliases, it.desc.firstLine))
else:
result.subcmds =
subcmds.mapIt((it.name, it.aliases, it.desc.firstLine))
result.subcmds =
subcmds.mapIt((it.name, it.desc.firstLine))
result.usage = dedent(usage).strip()
result.flags = @flags
result.styles = styles
@ -154,7 +147,6 @@ type
long*: string
help*: NimNode
group*: string
inherited*: bool
Inherit = object
settings: set[CliSetting]
@ -162,8 +154,6 @@ type
groups: seq[string]
CliCfg = object
name*: string
alias*: HashSet[string]
stopWords*: seq[string]
styles: NimNode
hidden*: seq[string]
@ -171,6 +161,7 @@ type
settings*: set[CliSetting]
preSub*, postSub*, pre*, post*, run*: NimNode
desc*: NimNode
name*: string
subName*: string # used for help the generator
version*, usage*: NimNode
flags*: seq[CliFlag]
@ -180,33 +171,12 @@ type
inherit*: Inherit
root*: bool
template `<<<`(s: string) {.used.} =
let pos = instantiationInfo()
debugEcho "$1:$2" % [pos.filename, $pos.line]
debugEcho s
debugEcho "^^^^^^^^^^^^^^^^^^^^^"
# some debug procs I use to wrap my ahead aroung the magic of *macro*
template `<<<`(n: NimNode) {.used.} =
func `<<<`(n: NimNode) {.used.} =
## for debugging macros
<<< treeRepr n
func `<<<`(f: CliFlag) {.used.}=
var s: string
let fields = [
("name", f.name),
("long", f.long),
("short", $f.short),
("typeNode", f.typeNode.lispRepr),
("group", f.group)
]
s.add "CliFlag(\n"
for (k,v) in fields:
s.add "$1 = $2\n" % [k,v]
s.add ")"
<<< s
debugEcho treeRepr n
func `<<<`(s: string) {.used.} =
debugEcho s
func bad(n: NimNode, argument: string = "") =
var msg = "unexpected node kind: " & $n.kind
@ -288,6 +258,7 @@ func parseCliFlags(cfg: var CliCfg, node: NimNode) =
var group: string
expectKind node, nnkStmtList
for n in node:
<<< n
var flag: CliFlag
case n.kind
of nnkCall, nnkCommand:
@ -401,30 +372,28 @@ func inheritFrom(child: var CliCfg, parent: CliCfg) =
for f in flags:
if f notin pflags:
error "expected parent command to have flag: " & f
error "expected parent command to define flag: " & f
else:
child.flags.add pflags[f]
# so subcommands can continue the inheritance
child.flagDefs.add pflags[f]
for g in groups:
if g notin pgroups:
error "expected parent command to have flag group " & g
error "expected parent command to define flag group " & g
else:
child.flags.add pgroups[g]
# so subcommands can continue the inheritance
child.flagDefs.add pgroups[g]
child.flags &= pgroups[g]
func parseCliSubcommands(cfg: var CliCfg, node: NimNode) =
expectKind node[1], nnkStmtList
for (name, s) in sliceStmts(node[1]):
cfg.stopWords.add name
var subCfg = parseCliBody(
nnkStmtList.newTree(node[1][s]), cfg.name & " " & name
)
subCfg.subName = name
cfg.stopWords.add name
cfg.stopWords.add subCfg.alias.toSeq()
cfg.subcommands.add subCfg
subCfg.inheritFrom(cfg)
cfg.subcommands.add subCfg
func parseHiddenFlags(cfg: var CliCfg, node: NimNode) =
template check =
@ -474,30 +443,9 @@ func addBuiltinFlags(cfg: var CliCfg) =
node: versionNode
)
func pasrseCliAlias(cfg: var CliCfg, node: NimNode) =
# node[0] is "alias"
for n in node[1..^1]:
case n.kind
of nnkIdent, nnkStrLit:
cfg.alias.incl n.strVal
of nnkAccQuoted:
let s = n.mapIt(it.strVal).join("")
cfg.alias.incl s
else: bad(n, "alias")
func propagate(c: var CliCfg) =
for child in c.subcommands.mitems:
child.pre = c.preSub
child.post = c.postSub
child.inheritFrom(c)
propagate(child)
func parseCliBody(body: NimNode, name = "", root = false): CliCfg =
result.name = name
result.root = root
# TDOO: change call to n or node?
for call in body:
if call.kind notin [nnkCall, nnkCommand, nnkPrefix]:
error "unexpected node kind: " & $call.kind
@ -506,9 +454,6 @@ func parseCliBody(body: NimNode, name = "", root = false): CliCfg =
of "name":
expectKind call[1], nnkStrLit
result.name = call[1].strVal
of "alias":
if root: error "alias not supported for root command"
pasrseCliAlias(result, call)
of "version", "V":
result.version = call[1]
of "usage", "?":
@ -537,19 +482,16 @@ func parseCliBody(body: NimNode, name = "", root = false): CliCfg =
result.postSub = call[1]
else:
error "unknown hwylCli setting: " & name
#
# for sub in result.subcommands.mitems:
# # sub.inheritFrom(result)
# sub.pre = result.preSub
# sub.post = result.postSub
for sub in result.subcommands.mitems:
sub.pre = result.preSub
sub.post = result.postSub
if result.name == "":
error "missing required option: name"
# TODO: validate "required" flags exist here
result.addBuiltinFlags()
if root:
propagate(result)
func flagToTuple(f: CliFlag | BuiltinFlag): NimNode =
let
@ -573,10 +515,9 @@ func subCmdsArray(cfg: CliCfg): NimNode =
result = newTree(nnkBracket)
for s in cfg.subcommands:
let cmd = newLit(s.subName)
let aliases = newLit(s.alias.mapIt("($1)" % [it]).join(" "))
let desc = s.desc or newLit("")
result.add quote do:
(`cmd`, `aliases`, `desc`)
(`cmd`, `desc`)
proc hwylCliError*(msg: string | BbString) =
quit $(bb("error ", "red") & bb(msg))
@ -606,21 +547,13 @@ func generateCliHelperProc(cfg: CliCfg, printHelpName: NimNode): NimNode =
styles = `styles`,
)))
proc preParseCheck(key: string, val: string) =
if val == "":
hwylCliError(
"expected value for flag: [b]" & key
)
proc parse*(p: OptParser, key: string, val: string, target: var bool) =
target = true
proc parse*(p: OptParser, key: string, val: string, target: var string) =
preParseCheck(key, val)
target = val
proc parse*(p: OptParser, key: string, val: string, target: var int) =
preParseCheck(key, val)
try:
target = parseInt(val)
except:
@ -636,17 +569,15 @@ macro enumNames(a: typed): untyped =
result.add newLit ai.strVal
proc parse*[E: enum](p: OptParser, key: string, val: string, target: var E) =
preParseCheck(key, val)
try:
target = parseEnum[E](val)
except:
let choices = enumNames(E).join(",")
hwylCliError(
"failed to parse value for [b]" & key & "[/] as enum: [b]" & val & "[/] expected one of: " & choices
"failed to parse value for [b]" & key & "[/] as enum: [b]" & val & "[/], expected one of: " & choices
)
proc parse*(p: OptParser, key: string, val: string, target: var float) =
preParseCheck(key, val)
try:
target = parseFloat(val)
except:
@ -655,7 +586,6 @@ proc parse*(p: OptParser, key: string, val: string, target: var float) =
)
proc parse*[T](p: OptParser, key: string, val: string, target: var seq[T]) =
preParseCheck(key, val)
var parsed: T
parse(p, key, val, parsed)
target.add parsed
@ -725,15 +655,11 @@ func getNoVals(cfg: CliCfg): tuple[long: NimNode, short: NimNode] =
result = (nnkPrefix.newTree(ident"@",long), short)
func setFlagVars(cfg: CliCfg): NimNode =
## generate all variables not covered in global module
result = nnkVarSection.newTree()
let flags =
if cfg.root: cfg.flags
else: cfg.flags.filterIt(it.group != "global")
result.add flags.mapIt(
nnkIdentDefs.newTree(it.ident, it.typeNode, newEmptyNode())
)
result = nnkVarSection.newTree().add(
cfg.flags.mapIt(
nnkIdentDefs.newTree(it.ident, it.typeNode, newEmptyNode())
)
)
func literalFlags(f: CliFlag): NimNode =
var flags: seq[string]
@ -768,38 +694,6 @@ func addPostParseCheck(cfg: CliCfg, body: NimNode) =
if `name` notin `flagSet`:
`target` = `default`
func hwylCliImpl(cfg: CliCfg): NimNode
func genSubcommandHandler(cfg: CliCfg): NimNode =
let args = ident"args"
result = nnkStmtList.newTree()
result.add quote do:
if `args`.len == 0:
hwylCliError("expected subcommand")
var subCommandCase = nnkCaseStmt.newTree()
if NoNormalize notin cfg.settings:
subCommandCase.add(quote do: optionNormalize(`args`[0]))
else:
subCommandCase.add(quote do: `args`[0])
for sub in cfg.subcommands:
var branch = nnkOfBranch.newTree()
branch.add newLit(optionNormalize(sub.subName))
for a in sub.alias:
branch.add newLit(optionNormalize(a))
branch.add hwylCliImpl(sub)
subcommandCase.add branch
subcommandCase.add nnkElse.newTree(
quote do:
hwylCliError("unknown subcommand: [b]" & `args`[0])
)
result.add subCommandCase
func hwylCliImpl(cfg: CliCfg): NimNode =
let
version = cfg.version or newLit("")
@ -885,7 +779,29 @@ func hwylCliImpl(cfg: CliCfg): NimNode =
runBody.add cfg.post
if cfg.subcommands.len > 0:
runBody.add genSubcommandHandler(cfg)
var handleSubCommands = nnkStmtList.newTree()
handleSubCommands.add quote do:
if `args`.len == 0:
hwylCliError("expected subcommand")
var subCommandCase = nnkCaseStmt.newTree()
if NoNormalize notin cfg.settings:
subCommandCase.add(quote do: optionNormalize(`args`[0]))
else:
subCommandCase.add(quote do: `args`[0])
for sub in cfg.subcommands:
subCommandCase.add nnkOfBranch.newTree(
newLit(optionNormalize(sub.subName)),
hwylCliImpl(sub)
)
subcommandCase.add nnkElse.newTree(
quote do:
hwylCliError("unknown subcommand: [b]" & `args`[0])
)
runBody.add handleSubCommands.add subCommandCase
result.add quote do:
# block:
@ -898,6 +814,7 @@ func hwylCliImpl(cfg: CliCfg): NimNode =
let `args` {.used.} = `parserProcName`(`cmdLine`)
`runBody`
# if cfg.root and (GenerateOnly notin cfg.settings):
if cfg.root:
if GenerateOnly notin cfg.settings:
result.add quote do:

View file

@ -13,9 +13,6 @@ hwylCli:
... "a description of hwylterm"
flags:
[global]
color:
T Color
? "a color (red, green, blue)"
yes:
T bool
? "set flag to yes"
@ -28,12 +25,10 @@ hwylCli:
* @["config.yml"]
preSub:
echo "this is run after subcommand parsing but before its run block"
echo fmt"{yes=}, {color=}"
run:
echo "this is always run prior to subcommand parsing"
echo fmt"{yes=}, {color=}"
subcommands:
[one]
[onelonger]
... """
the first subcommand
@ -41,21 +36,15 @@ hwylCli:
it also inherits the `[[shared]` flag group
"""
alias o
flags:
color:
T Color
? "a color (red, green, blue)"
verbose:
T Count
? "a count flag"
- v
^[shared]
subcommands:
[subsub]
... "another level down subcommand"
flags:
^config
run:
echo fmt"{color=}"
run:
echo "hello from `example one` command!"
echo args
@ -63,7 +52,7 @@ hwylCli:
echo fmt"{verbose=}"
echo fmt"{config=}"
[two]
["two-longer"]
... """
some second subcommand

View file

@ -18,10 +18,8 @@
- [ ] add support for types(metavars)/defaults/required in help output
- [ ] add nargs to CliCfg
- [x] add support for inheriting a single flag from parent (even from a "group")
- [x] add support to either (lengthen commands) or provide an alias for a subcommand
- [x] add command aliases to hwylcli help with switch
- [ ] don't recreate "global"" variables in var section
- [ ] add support for inheriting a single flag from parent (even from a "group")
- [ ] add support to either (lengthen commands) or provide an alias for a subcommand
## features