handle flag groups differently

This commit is contained in:
Daylin Morgan 2024-11-10 03:21:16 -06:00
parent d228123195
commit 8b37c0dad9
Signed by: daylin
GPG key ID: 950D13E9719334AD
2 changed files with 79 additions and 77 deletions

View file

@ -118,6 +118,8 @@ proc bb*(cli: HwylCliHelp): BbString =
proc `$`*(cli: HwylCliHelp): string = proc `$`*(cli: HwylCliHelp): string =
result = $bb(cli) result = $bb(cli)
# ----------------------------------------
type type
CliSetting = enum CliSetting = enum
NoHelpFlag, NoArgsShowHelp NoHelpFlag, NoArgsShowHelp
@ -131,7 +133,6 @@ type
name*: string name*: string
ident*: NimNode ident*: NimNode
default*: NimNode default*: NimNode
typeSym*: string
typeNode*: NimNode typeNode*: NimNode
short*: char short*: char
long*: string long*: string
@ -149,8 +150,9 @@ type
version*, usage*: NimNode version*, usage*: NimNode
flags*: seq[CliFlag] flags*: seq[CliFlag]
builtinFlags*: seq[BuiltinFlag] builtinFlags*: seq[BuiltinFlag]
flagGroups: Table[string, seq[CliFlag]]
required*: seq[string] required*: seq[string]
globalFlags*: seq[CliFlag] inheritFlags*: seq[string]
{.push hint[XDeclaredButNotUsed]:off .} {.push hint[XDeclaredButNotUsed]:off .}
# some debug procs I use to wrap my ahead aroung the magic of *macro* # some debug procs I use to wrap my ahead aroung the magic of *macro*
@ -159,75 +161,49 @@ func `<<<`(n: NimNode) =
debugEcho treeRepr n debugEcho treeRepr n
func `<<<`(s: string) = func `<<<`(s: string) =
debugEcho s debugEcho s
{.pop.}
# TODO: do i need this?
func newCliFlag(): CliFlag =
result.help = newLit("")
func bad(n: NimNode, argument: string = "") = func bad(n: NimNode, argument: string = "") =
var msg = "unexpected node kind: " & $n.kind var msg = "unexpected node kind: " & $n.kind
if argument != "": if argument != "":
msg &= " for argument: " & argument msg &= " for argument: " & argument
# error "unexpected node kind: " & $n.kind
error msg error msg
{.pop.}
func typeSymFromNode(node: NimNode): string = func getFlagParamNode(node: NimNode): NimNode =
case node.kind case node.kind
of nnkIdent, nnkStrLit:
result = node.strVal
of nnkBracketExpr:
result = node[0].strVal & "[" & node[1].strVal & "]"
else: bad node
func getOptTypeSym(node: NimNode): string =
case node.kind:
of nnkCommand:
result = typeSymFromNode(node[1]) # [0] is T
of nnkCall:
result = typeSymFromNode(node[1][0]) # [1] is stmtlist [0] is the type
else: error "unexpected node kind: " & $node.kind
func getOptOptNode(optOptValue: NimNode): NimNode =
case optOptValue.kind
of nnkStrLit: of nnkStrLit:
result = optOptValue result = node
of nnkStmtList: of nnkStmtList:
result = optOptValue[0] result = node[0]
of nnkCommand: of nnkCommand:
result = optOptValue[1] result = node[1]
of nnkPrefix: # NOTE: should i double check prefix value? of nnkPrefix: # NOTE: should i double check prefix value?
result = optOptValue[1] result = node[1]
else: error "unexpected node kind: " & $optOptValue.kind else: bad(node, "flag param")
# TODO: don't use the confusing name optOpts here and above func parseFlagParams(f: var CliFlag, node: NimNode) =
func parseOptOpts(opt: var CliFlag, optOpts: NimNode) = expectKind node, nnkStmtList
expectKind optOpts, nnkStmtList for n in node:
for optOpt in optOpts: case n.kind
case optOpt.kind
of nnkCall, nnkCommand, nnkPrefix: of nnkCall, nnkCommand, nnkPrefix:
case optOpt[0].strVal case n[0].strVal
of "help","?": of "help","?":
opt.help = getOptOptNode(optOpt[1]) f.help = getFlagParamNode(n[1])
of "short", "-": of "short", "-":
let val = getOptOptNode(optOpt).strVal let val = getFlagParamNode(n).strVal
if val.len > 1: if val.len > 1:
error "short flag must be a char" error "short flag must be a char"
opt.short = val[0].char f.short = val[0].char
of "*", "default": of "*", "default":
opt.default = getOptOptNode(optOpt) f.default = getFlagParamNode(n)
of "i", "ident": of "i", "ident":
opt.ident = getOptOptNode(optOpt).strVal.ident f.ident = getFlagParamNode(n).strVal.ident
of "T": of "T":
opt.typeNode = optOpt[1] f.typeNode = n[1]
# TODO: remove this...
opt.typeSym = getOptTypeSym(optOpt)
else: else:
error "unexpected option setting: " & optOpt[0].strVal error "unexpected setting: " & n[0].strVal
else: else:
error "unexpected option node type: " & $optOpt.kind bad(n, "flag params")
func startFlag(f: var CliFlag, n: NimNode) = func startFlag(f: var CliFlag, n: NimNode) =
f.name = f.name =
@ -236,6 +212,8 @@ func startFlag(f: var CliFlag, n: NimNode) =
of nnkAccQuoted: collect(for c in n[0]: c.strVal).join("") of nnkAccQuoted: collect(for c in n[0]: c.strVal).join("")
else: error "unexpected node kind for option" else: error "unexpected node kind for option"
f.help = newLit("") # by default no string
# assume a single character is a short flag # assume a single character is a short flag
if f.name.len == 1: if f.name.len == 1:
f.short = f.name[0].char f.short = f.name[0].char
@ -244,10 +222,8 @@ func startFlag(f: var CliFlag, n: NimNode) =
func parseCliFlag(n: NimNode): CliFlag = func parseCliFlag(n: NimNode): CliFlag =
if n.kind notin [nnkCommand, nnkCall]: if n.kind notin [nnkCommand, nnkCall]:
error "unexpected node kind: " & $n.kind bad(n, "flags")
# deduplicate these...
result = newCliFlag()
startFlag(result, n) startFlag(result, n)
# option "some help desc" # option "some help desc"
if n.kind == nnkCommand: if n.kind == nnkCommand:
@ -255,29 +231,43 @@ func parseCliFlag(n: NimNode): CliFlag =
# option: # option:
# help "some help description" # help "some help description"
else: else:
parseOptOpts(result, n[1]) parseFlagParams(result, n[1])
if result.ident == nil: if result.ident == nil:
result.ident = result.name.ident result.ident = result.name.ident
if result.typeNode == nil: if result.typeNode == nil:
result.typeNode = ident"string" result.typeNode = ident"string"
if result.typeSym == "":
result.typeSym = "string"
# TODO: handle flag groups here # TODO: change how this works?
# cfg.flagGroups = Table[string, seq[CliFlag]]? func parseCliFlags(cfg: var CliCfg, node: NimNode) =
func parseCliFlags(flags: NimNode): seq[CliFlag] = var group: string
# <<< flags expectKind node, nnkStmtList
expectKind flags, nnkStmtList for n in node:
for f in flags: var flag: CliFlag
case f.kind case n.kind
of nnkCall, nnkCommand: of nnkCall, nnkCommand:
result.add parseCliFlag(f) flag = parseCliFlag(n)
if group == "":
cfg.flags.add flag
else:
if group notin cfg.flagGroups: cfg.flagGroups[group] = @[flag]
else: cfg.flagGroups[group].add flag
of nnkBracket:
group = n[0].strVal
continue
of nnkPrefix:
if n[0].kind != nnkIdent and n[0].strVal != "^":
error "unexpected node in flags: " & $n.kind
expectKind n[1], nnkBracket
cfg.inheritFlags.add n[1][0].strVal
# of nnkPrefix: # of nnkPrefix:
# <<< "reached Prefix!" # if n[0].strVal != "---":
# <<< f # bad(n[0], "flag group prefix")
else: error "unexpected node kind parsing flags" # group = n[1].strVal
# continue
else: bad(n, "flag")
debugEcho cfg.flagGroups.keys().toSeq()
func parseCliSetting(s: string): CliSetting = func parseCliSetting(s: string): CliSetting =
try: parseEnum[CliSetting](s) try: parseEnum[CliSetting](s)
@ -337,9 +327,21 @@ func sliceStmts(node: NimNode): seq[
start = i + 1 start = i + 1
func addGlobalFlagsFrom(child: var CliCfg, parent: CliCfg) = func addInheritedFlags(child: var CliCfg, parent: CliCfg, self = false) =
let names = child.flags.mapIt(it.name) let names = child.flags.mapIt(it.name)
for f in parent.globalFlags: var groups: seq[string]
if not self:
groups.add child.inheritFlags
# autoinherit the "global" flags
if "global" in parent.flagGroups:
groups.add "global"
for g in groups:
if g notin parent.flagGroups:
debugEcho parent.flagGroups.keys().toSeq()
error "expected flag group: " & g & " to exist in parent command"
for f in parent.flagGroups[g]:
if f.name in names: if f.name in names:
error "global flag " & f.name & " conflicts with command flag" error "global flag " & f.name & " conflicts with command flag"
child.flags.add f child.flags.add f
@ -352,8 +354,7 @@ func parseCliSubcommands(cfg: var CliCfg, node: NimNode) =
nnkStmtList.newTree(node[1][s]), cfg.name & " " & name nnkStmtList.newTree(node[1][s]), cfg.name & " " & name
) )
subCfg.subName = name subCfg.subName = name
subCfg.addGlobalFlagsFrom(cfg) subCfg.addInheritedFlags(cfg)
cfg.subcommands.add subCfg cfg.subcommands.add subCfg
func parseHiddenFlags(cfg: var CliCfg, node: NimNode) = func parseHiddenFlags(cfg: var CliCfg, node: NimNode) =
@ -420,10 +421,8 @@ func parseCliBody(body: NimNode, name = ""): CliCfg =
result.usage = call[1] result.usage = call[1]
of "description", "...": of "description", "...":
result.desc = call[1] result.desc = call[1]
of "globalFlags":
result.globalFlags = parseCliFlags(call[1])
of "flags": of "flags":
result.flags = parseCliFlags(call[1]) parseCliFlags(result, call[1])
of "settings": of "settings":
parseCliSettings(result, call) parseCliSettings(result, call)
of "stopWords": of "stopWords":
@ -449,7 +448,7 @@ func parseCliBody(body: NimNode, name = ""): CliCfg =
sub.pre = result.preSub sub.pre = result.preSub
sub.post = result.postSub sub.post = result.postSub
result.addGlobalFlagsFrom(result) result.addInheritedFlags(result, self = true)
if result.name == "": if result.name == "":
error "missing required option: name" error "missing required option: name"

View file

@ -9,6 +9,7 @@ hwylCli:
yes: yes:
T bool T bool
? "set flag to yes" ? "set flag to yes"
[global]
config: config:
T seq[string] T seq[string]
? "path to config file" ? "path to config file"
@ -24,7 +25,6 @@ hwylCli:
`long-flag` "some help" `long-flag` "some help"
flag: flag:
? "some other help" ? "some other help"
run: run:
echo "hello from `example one` command!" echo "hello from `example one` command!"
echo "long-flag and flag are: " & `long-flag` & "," & `flag` & " by default strings" echo "long-flag and flag are: " & `long-flag` & "," & `flag` & " by default strings"
@ -43,6 +43,9 @@ hwylCli:
bflag: bflag:
T seq[float] T seq[float]
? "multiple floats" ? "multiple floats"
c:
? "this should be a single flag"
h "overwrite the short h from help"
run: run:
echo "hello from `example b` command" echo "hello from `example b` command"
echo fmt"{aflag=}, {bflag=}" echo fmt"{aflag=}, {bflag=}"