mirror of
https://github.com/daylinmorgan/hwylterm.git
synced 2024-12-22 19:10:45 -06:00
Compare commits
No commits in common. "69ae06fa4765e840403c61f8d34c4b7484b9dc56" and "b6202f4f51edadd041d2b5f2aed1d75abcd4ee51" have entirely different histories.
69ae06fa47
...
b6202f4f51
5 changed files with 340 additions and 425 deletions
|
@ -368,7 +368,7 @@ when isMainModule:
|
||||||
|
|
||||||
hwylCli:
|
hwylCli:
|
||||||
name "bbansi"
|
name "bbansi"
|
||||||
settings ShowHelp
|
settings NoArgsShowHelp
|
||||||
usage "[bold]bbansi[/] [[[green]args...[/]] [[[faint]-h|-V[/]]"
|
usage "[bold]bbansi[/] [[[green]args...[/]] [[[faint]-h|-V[/]]"
|
||||||
description """
|
description """
|
||||||
bbansi "[[yellow] yellow text!"
|
bbansi "[[yellow] yellow text!"
|
||||||
|
|
|
@ -130,7 +130,7 @@ when isMainModule:
|
||||||
import ./[hwylcli]
|
import ./[hwylcli]
|
||||||
hwylcli:
|
hwylcli:
|
||||||
name "hwylchoose"
|
name "hwylchoose"
|
||||||
settings ShowHelp
|
settings NoArgsShowHelp
|
||||||
usage "[bold]hwylchoose[/] [[[green]args...[/]] [[[faint]-h[/]]"
|
usage "[bold]hwylchoose[/] [[[green]args...[/]] [[[faint]-h[/]]"
|
||||||
description """
|
description """
|
||||||
hwylchoose a b c d
|
hwylchoose a b c d
|
||||||
|
|
|
@ -8,41 +8,40 @@ import std/[
|
||||||
sugar
|
sugar
|
||||||
]
|
]
|
||||||
import ./[bbansi, parseopt3]
|
import ./[bbansi, parseopt3]
|
||||||
export parseopt3, sets, bbansi
|
export parseopt3
|
||||||
|
|
||||||
type
|
type
|
||||||
HwylFlagHelp* = tuple
|
HwylFlagHelp = tuple
|
||||||
short, long, description: string
|
short, long, description: string
|
||||||
HwylSubCmdHelp* = tuple
|
HwylSubCmdHelp = tuple
|
||||||
name, desc: string
|
name, desc: string
|
||||||
HwylCliStyles* = object
|
HwylCliStyles* = object
|
||||||
header* = "bold cyan"
|
hdr = "bold cyan"
|
||||||
flagShort* = "yellow"
|
shortFlag = "yellow"
|
||||||
flagLong* = "magenta"
|
longFlag = "magenta"
|
||||||
flagDesc* = ""
|
descFlag = ""
|
||||||
cmd* = "bold"
|
cmd = "bold"
|
||||||
HwylCliHelp* = object
|
HwylCliHelp* = object
|
||||||
|
cmd*: string
|
||||||
usage*: string
|
usage*: string
|
||||||
desc*: string
|
desc*: string
|
||||||
subcmds*: seq[HwylSubCmdHelp]
|
subcmds: seq[HwylSubCmdHelp]
|
||||||
flags*: seq[HwylFlagHelp]
|
flags*: seq[HwylFlagHelp]
|
||||||
styles*: HwylCliStyles
|
styles*: HwylCliStyles
|
||||||
subcmdLen*, subcmdDescLen*, shortArgLen*, longArgLen*, descArgLen*: int
|
subcmdLen, subcmdDescLen, shortArgLen, longArgLen, descArgLen: int
|
||||||
|
|
||||||
# NOTE: do i need both strips?
|
|
||||||
func firstLine(s: string): string =
|
|
||||||
s.strip().dedent().strip().splitlines()[0]
|
|
||||||
|
|
||||||
func newHwylCliHelp*(
|
func newHwylCliHelp*(
|
||||||
|
cmd = "",
|
||||||
usage = "",
|
usage = "",
|
||||||
desc = "",
|
desc = "",
|
||||||
subcmds: openArray[HwylSubCmdHelp] = @[],
|
subcmds: openArray[HwylSubCmdHelp] = @[],
|
||||||
flags: openArray[HwylFlagHelp] = @[],
|
flags: openArray[HwylFlagHelp] = @[],
|
||||||
styles = HwylCliStyles()
|
styles = HwylCliStyles()
|
||||||
): HwylCliHelp =
|
): HwylCliHelp =
|
||||||
|
result.cmd = cmd
|
||||||
result.desc = dedent(desc).strip()
|
result.desc = dedent(desc).strip()
|
||||||
result.subcmds =
|
result.subcmds = subcmds.mapIt((it.name,it.desc.strip().dedent().strip().splitlines()[0]))
|
||||||
subcmds.mapIt((it.name, it.desc.firstLine))
|
|
||||||
result.usage = dedent(usage).strip()
|
result.usage = dedent(usage).strip()
|
||||||
result.flags = @flags
|
result.flags = @flags
|
||||||
result.styles = styles
|
result.styles = styles
|
||||||
|
@ -56,10 +55,10 @@ func newHwylCliHelp*(
|
||||||
result.subcmdLen = max(result.subcmdLen, s.name.len)
|
result.subcmdLen = max(result.subcmdLen, s.name.len)
|
||||||
result.subcmdDescLen = max(result.subcmdDescLen, s.desc.len)
|
result.subcmdDescLen = max(result.subcmdDescLen, s.desc.len)
|
||||||
|
|
||||||
func render*(cli: HwylCliHelp, f: HwylFlagHelp): string =
|
func flagHelp(cli: HwylCliHelp, f: HwylFlagHelp): string =
|
||||||
result.add " "
|
result.add " "
|
||||||
if f.short != "":
|
if f.short != "":
|
||||||
result.add "[" & cli.styles.flagShort & "]"
|
result.add "[" & cli.styles.shortFlag & "]"
|
||||||
result.add "-" & f.short.alignLeft(cli.shortArgLen)
|
result.add "-" & f.short.alignLeft(cli.shortArgLen)
|
||||||
result.add "[/]"
|
result.add "[/]"
|
||||||
else:
|
else:
|
||||||
|
@ -67,7 +66,7 @@ func render*(cli: HwylCliHelp, f: HwylFlagHelp): string =
|
||||||
|
|
||||||
result.add " "
|
result.add " "
|
||||||
if f.long != "":
|
if f.long != "":
|
||||||
result.add "[" & cli.styles.flagLong & "]"
|
result.add "[" & cli.styles.longFlag & "]"
|
||||||
result.add "--" & f.long.alignLeft(cli.longArgLen)
|
result.add "--" & f.long.alignLeft(cli.longArgLen)
|
||||||
result.add "[/]"
|
result.add "[/]"
|
||||||
else:
|
else:
|
||||||
|
@ -76,12 +75,12 @@ func render*(cli: HwylCliHelp, f: HwylFlagHelp): string =
|
||||||
result.add " "
|
result.add " "
|
||||||
|
|
||||||
if f.description != "":
|
if f.description != "":
|
||||||
result.add "[" & cli.styles.flagDesc & "]"
|
result.add "[" & cli.styles.descFlag & "]"
|
||||||
result.add f.description
|
result.add f.description
|
||||||
result.add "[/]"
|
result.add "[/]"
|
||||||
result.add "\n"
|
result.add "\n"
|
||||||
|
|
||||||
func render*(cli: HwylCliHelp, subcmd: HwylSubCmdHelp): string =
|
func subCmdLine(cli: HwylCliHelp, subcmd: HwylSubCmdHelp): string =
|
||||||
result.add " "
|
result.add " "
|
||||||
result.add "[" & cli.styles.cmd & "]"
|
result.add "[" & cli.styles.cmd & "]"
|
||||||
result.add subcmd.name.alignLeft(cli.subcmdLen)
|
result.add subcmd.name.alignLeft(cli.subcmdLen)
|
||||||
|
@ -90,63 +89,49 @@ func render*(cli: HwylCliHelp, subcmd: HwylSubCmdHelp): string =
|
||||||
result.add subcmd.desc.alignLeft(cli.subcmdDescLen)
|
result.add subcmd.desc.alignLeft(cli.subcmdDescLen)
|
||||||
result.add "\n"
|
result.add "\n"
|
||||||
|
|
||||||
|
proc bbImpl(cli: HwylCliHelp): string =
|
||||||
# TODO: split this into separate procs to make overriding more fluid
|
if cli.cmd != "":
|
||||||
func render*(cli: HwylCliHelp): string =
|
result.add cli.cmd
|
||||||
|
result.add "\n"
|
||||||
if cli.usage != "":
|
if cli.usage != "":
|
||||||
result.add "[" & cli.styles.header & "]"
|
result.add "\n"
|
||||||
|
result.add "[" & cli.styles.hdr & "]"
|
||||||
result.add "usage[/]:\n"
|
result.add "usage[/]:\n"
|
||||||
result.add indent(cli.usage, 2 )
|
result.add indent(cli.usage, 2 )
|
||||||
result.add "\n"
|
|
||||||
if cli.desc != "":
|
if cli.desc != "":
|
||||||
result.add "\n"
|
result.add "\n\n"
|
||||||
result.add cli.desc
|
result.add cli.desc
|
||||||
result.add "\n"
|
result.add "\n"
|
||||||
if cli.subcmds.len > 0:
|
if cli.subcmds.len > 0:
|
||||||
result.add "\n"
|
result.add "\n"
|
||||||
result.add "[" & cli.styles.header & "]"
|
result.add "[" & cli.styles.hdr & "]"
|
||||||
result.add "subcommands[/]:\n"
|
result.add "subcommands[/]:\n"
|
||||||
for s in cli.subcmds:
|
for s in cli.subcmds:
|
||||||
result.add cli.render(s)
|
result.add cli.subcmdLine(s)
|
||||||
if cli.flags.len > 0:
|
if cli.flags.len > 0:
|
||||||
result.add "\n"
|
result.add "\n"
|
||||||
result.add "[" & cli.styles.header & "]"
|
result.add "[" & cli.styles.hdr & "]"
|
||||||
result.add "flags[/]:\n"
|
result.add "flags[/]:\n"
|
||||||
for f in cli.flags:
|
for f in cli.flags:
|
||||||
result.add render(cli,f)
|
result.add flagHelp(cli,f)
|
||||||
|
|
||||||
proc bb*(cli: HwylCliHelp): BbString =
|
proc bb*(cli: HwylCliHelp): BbString =
|
||||||
result = bb(render(cli))
|
result = bb(bbImpl(cli))
|
||||||
|
|
||||||
# ----------------------------------------
|
proc `$`*(cli: HwylCliHelp): string =
|
||||||
|
result = $bb(cli)
|
||||||
|
|
||||||
type
|
type
|
||||||
Count* = object ## Count type for an incrementing flag
|
CliSetting = enum
|
||||||
val*: int
|
NoHelpFlag, NoArgsShowHelp
|
||||||
|
|
||||||
type
|
|
||||||
# ----
|
|
||||||
CliSetting* = enum
|
|
||||||
NoHelpFlag, ## Remove the builtin help flag
|
|
||||||
ShowHelp, ## If cmdline empty show help
|
|
||||||
NoNormalize ## Don't normalize flags and commands
|
|
||||||
|
|
||||||
BuiltinFlag = object
|
|
||||||
name*: string
|
|
||||||
short*: char
|
|
||||||
long*: string
|
|
||||||
help*: NimNode
|
|
||||||
node: NimNode
|
|
||||||
|
|
||||||
CliFlag = object
|
CliFlag = object
|
||||||
name*: string
|
name*: string
|
||||||
ident*: NimNode
|
ident*: string
|
||||||
default*: NimNode
|
default*: NimNode
|
||||||
typeNode*: NimNode
|
typeSym*: string
|
||||||
short*: char
|
short*: char
|
||||||
long*: string
|
long*: string
|
||||||
help*: NimNode
|
help*: NimNode
|
||||||
|
|
||||||
CliCfg = object
|
CliCfg = object
|
||||||
stopWords*: seq[string]
|
stopWords*: seq[string]
|
||||||
styles: NimNode
|
styles: NimNode
|
||||||
|
@ -159,60 +144,74 @@ type
|
||||||
subName*: string # used for help the generator
|
subName*: string # used for help the generator
|
||||||
version*, usage*: NimNode
|
version*, usage*: NimNode
|
||||||
flags*: seq[CliFlag]
|
flags*: seq[CliFlag]
|
||||||
builtinFlags*: seq[BuiltinFlag]
|
|
||||||
flagGroups: Table[string, seq[CliFlag]]
|
|
||||||
required*: seq[string]
|
required*: seq[string]
|
||||||
inheritFlags*: seq[string]
|
globalFlags*: seq[CliFlag]
|
||||||
root*: bool
|
|
||||||
|
|
||||||
# some debug procs I use to wrap my ahead aroung the magic of *macro*
|
{.push hint[XDeclaredButNotUsed]:off .}
|
||||||
func `<<<`(n: NimNode) {.used.} =
|
func peekNode(n: NimNode) =
|
||||||
## for debugging macros
|
## for debugging macros
|
||||||
debugEcho treeRepr n
|
debugEcho treeRepr n
|
||||||
func `<<<`(s: string) {.used.} =
|
{.pop.}
|
||||||
debugEcho s
|
|
||||||
|
|
||||||
func bad(n: NimNode, argument: string = "") =
|
# TODO: do i need this?
|
||||||
var msg = "unexpected node kind: " & $n.kind
|
func newCliFlag(): CliFlag =
|
||||||
if argument != "":
|
result.help = newLit("")
|
||||||
msg &= " for argument: " & argument
|
|
||||||
error msg
|
|
||||||
|
|
||||||
func getFlagParamNode(node: NimNode): NimNode =
|
template badNode =
|
||||||
|
error "unexpected node kind: " & $node.kind
|
||||||
|
|
||||||
|
func typeSymFromNode(node: NimNode): string =
|
||||||
case node.kind
|
case node.kind
|
||||||
of nnkStrLit:
|
of nnkIdent, nnkStrLit:
|
||||||
result = node
|
result = node.strVal
|
||||||
of nnkStmtList:
|
of nnkBracketExpr:
|
||||||
result = node[0]
|
result = node[0].strVal & "[" & node[1].strVal & "]"
|
||||||
of nnkCommand:
|
else: badNode
|
||||||
result = node[1]
|
|
||||||
of nnkPrefix: # NOTE: should i double check prefix value?
|
|
||||||
result = node[1]
|
|
||||||
else: bad(node, "flag param")
|
|
||||||
|
|
||||||
func parseFlagParams(f: var CliFlag, node: NimNode) =
|
func getOptTypeSym(node: NimNode): string =
|
||||||
expectKind node, nnkStmtList
|
case node.kind:
|
||||||
for n in node:
|
of nnkCommand:
|
||||||
case n.kind
|
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:
|
||||||
|
result = optOptValue
|
||||||
|
of nnkStmtList:
|
||||||
|
result = optOptValue[0]
|
||||||
|
of nnkCommand:
|
||||||
|
result = optOptValue[1]
|
||||||
|
of nnkPrefix: # NOTE: should i double check prefix value?
|
||||||
|
result = optOptValue[1]
|
||||||
|
else: error "unexpected node kind: " & $optOptValue.kind
|
||||||
|
|
||||||
|
# TODO: don't use the confusing name optOpts here and above
|
||||||
|
func parseOptOpts(opt: var CliFlag, optOpts: NimNode) =
|
||||||
|
expectKind optOpts, nnkStmtList
|
||||||
|
for optOpt in optOpts:
|
||||||
|
case optOpt.kind
|
||||||
of nnkCall, nnkCommand, nnkPrefix:
|
of nnkCall, nnkCommand, nnkPrefix:
|
||||||
case n[0].strVal
|
case optOpt[0].strVal
|
||||||
of "help","?":
|
of "help","?":
|
||||||
f.help = getFlagParamNode(n[1])
|
opt.help = getOptOptNode(optOpt[1])
|
||||||
of "short", "-":
|
of "short", "-":
|
||||||
let val = getFlagParamNode(n).strVal
|
let val = getOptOptNode(optOpt).strVal
|
||||||
if val.len > 1:
|
if val.len > 1:
|
||||||
error "short flag must be a char"
|
error "short flag must be a char"
|
||||||
f.short = val[0].char
|
opt.short = val[0].char
|
||||||
of "*", "default":
|
of "*", "default":
|
||||||
f.default = getFlagParamNode(n)
|
opt.default = getOptOptNode(optOpt)
|
||||||
of "i", "ident":
|
of "i", "ident":
|
||||||
f.ident = getFlagParamNode(n).strVal.ident
|
opt.ident = getOptOptNode(optOpt).strVal
|
||||||
of "T":
|
of "T":
|
||||||
f.typeNode = n[1]
|
opt.typeSym = getOptTypeSym(optOpt)
|
||||||
else:
|
else:
|
||||||
error "unexpected setting: " & n[0].strVal
|
error "unexpected option setting: " & optOpt[0].strVal
|
||||||
else:
|
else:
|
||||||
bad(n, "flag params")
|
error "unexpected option node type: " & $optOpt.kind
|
||||||
|
|
||||||
func startFlag(f: var CliFlag, n: NimNode) =
|
func startFlag(f: var CliFlag, n: NimNode) =
|
||||||
f.name =
|
f.name =
|
||||||
|
@ -221,8 +220,6 @@ 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
|
||||||
|
@ -231,8 +228,10 @@ 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]:
|
||||||
bad(n, "flags")
|
error "unexpected node kind: " & $n.kind
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
@ -240,36 +239,18 @@ func parseCliFlag(n: NimNode): CliFlag =
|
||||||
# option:
|
# option:
|
||||||
# help "some help description"
|
# help "some help description"
|
||||||
else:
|
else:
|
||||||
parseFlagParams(result, n[1])
|
parseOptOpts(result, n[1])
|
||||||
|
|
||||||
if result.ident == nil:
|
if result.ident == "":
|
||||||
result.ident = result.name.ident
|
result.ident = result.name
|
||||||
if result.typeNode == nil:
|
if result.typeSym == "":
|
||||||
result.typeNode = ident"string"
|
result.typeSym = "string"
|
||||||
|
|
||||||
# TODO: change how this works?
|
|
||||||
func parseCliFlags(cfg: var CliCfg, node: NimNode) =
|
func parseCliFlags(flags: NimNode): seq[CliFlag] =
|
||||||
var group: string
|
expectKind flags, nnkStmtList
|
||||||
expectKind node, nnkStmtList
|
for f in flags:
|
||||||
for n in node:
|
result.add parseCliFlag(f)
|
||||||
var flag: CliFlag
|
|
||||||
case n.kind
|
|
||||||
of nnkCall, nnkCommand:
|
|
||||||
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
|
|
||||||
else: bad(n, "flag")
|
|
||||||
|
|
||||||
func parseCliSetting(s: string): CliSetting =
|
func parseCliSetting(s: string): CliSetting =
|
||||||
try: parseEnum[CliSetting](s)
|
try: parseEnum[CliSetting](s)
|
||||||
|
@ -302,7 +283,7 @@ func parseIdentLikeList(node: NimNode): seq[string] =
|
||||||
result.add n.strVal
|
result.add n.strVal
|
||||||
else: assert false
|
else: assert false
|
||||||
|
|
||||||
func parseCliBody(body: NimNode, name: string = "", root: bool= false): CliCfg
|
func parseCliBody(body: NimNode, name: string = ""): CliCfg
|
||||||
|
|
||||||
func isSubMarker(node: NimNode): bool =
|
func isSubMarker(node: NimNode): bool =
|
||||||
if node.kind == nnkPrefix:
|
if node.kind == nnkPrefix:
|
||||||
|
@ -329,21 +310,9 @@ func sliceStmts(node: NimNode): seq[
|
||||||
start = i + 1
|
start = i + 1
|
||||||
|
|
||||||
|
|
||||||
func addInheritedFlags(child: var CliCfg, parent: CliCfg, self = false) =
|
func addGlobalFlagsFrom(child: var CliCfg, parent: CliCfg) =
|
||||||
let names = child.flags.mapIt(it.name)
|
let names = child.flags.mapIt(it.name)
|
||||||
var groups: seq[string]
|
for f in parent.globalFlags:
|
||||||
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
|
||||||
|
@ -356,7 +325,8 @@ 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.addInheritedFlags(cfg)
|
subCfg.addGlobalFlagsFrom(cfg)
|
||||||
|
|
||||||
cfg.subcommands.add subCfg
|
cfg.subcommands.add subCfg
|
||||||
|
|
||||||
func parseHiddenFlags(cfg: var CliCfg, node: NimNode) =
|
func parseHiddenFlags(cfg: var CliCfg, node: NimNode) =
|
||||||
|
@ -375,41 +345,8 @@ func parseHiddenFlags(cfg: var CliCfg, node: NimNode) =
|
||||||
cfg.hidden.add n.strVal
|
cfg.hidden.add n.strVal
|
||||||
else: assert false
|
else: assert false
|
||||||
|
|
||||||
func addBuiltinFlags(cfg: var CliCfg) =
|
func parseCliBody(body: NimNode, name = ""): CliCfg =
|
||||||
# duplicated with below :/
|
|
||||||
let shorts = cfg.flags.mapIt(it.short).toHashSet()
|
|
||||||
|
|
||||||
let
|
|
||||||
name = cfg.name.replace(" ", "")
|
|
||||||
printHelpName = ident("print" & name & "Help")
|
|
||||||
|
|
||||||
if NoHelpFlag notin cfg.settings:
|
|
||||||
let helpNode = quote do:
|
|
||||||
`printHelpName`(); quit 0
|
|
||||||
cfg.builtinFlags.add BuiltinFlag(
|
|
||||||
name: "help",
|
|
||||||
long: "help",
|
|
||||||
help: newLit("show this help"),
|
|
||||||
short: if 'h' notin shorts: 'h' else: '\x00',
|
|
||||||
node: helpNode
|
|
||||||
)
|
|
||||||
|
|
||||||
if cfg.version != nil:
|
|
||||||
let version = cfg.version
|
|
||||||
let versionNode = quote do:
|
|
||||||
echo `version`; quit 0
|
|
||||||
|
|
||||||
cfg.builtinFlags.add BuiltinFlag(
|
|
||||||
name:"version",
|
|
||||||
long: "version",
|
|
||||||
help: newLit("print version"),
|
|
||||||
short: if 'V' notin shorts: 'V' else: '\x00',
|
|
||||||
node: versionNode
|
|
||||||
)
|
|
||||||
|
|
||||||
func parseCliBody(body: NimNode, name = "", root = false): CliCfg =
|
|
||||||
result.name = name
|
result.name = name
|
||||||
result.root = true
|
|
||||||
for call in body:
|
for call in body:
|
||||||
if call.kind notin [nnkCall, nnkCommand, nnkPrefix]:
|
if call.kind notin [nnkCall, nnkCommand, nnkPrefix]:
|
||||||
error "unexpected node kind: " & $call.kind
|
error "unexpected node kind: " & $call.kind
|
||||||
|
@ -424,8 +361,10 @@ func parseCliBody(body: NimNode, name = "", root = false): 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":
|
||||||
parseCliFlags(result, call[1])
|
result.flags = parseCliFlags(call[1])
|
||||||
of "settings":
|
of "settings":
|
||||||
parseCliSettings(result, call)
|
parseCliSettings(result, call)
|
||||||
of "stopWords":
|
of "stopWords":
|
||||||
|
@ -451,31 +390,32 @@ func parseCliBody(body: NimNode, name = "", root = false): CliCfg =
|
||||||
sub.pre = result.preSub
|
sub.pre = result.preSub
|
||||||
sub.post = result.postSub
|
sub.post = result.postSub
|
||||||
|
|
||||||
result.addInheritedFlags(result, self = true)
|
result.addGlobalFlagsFrom(result)
|
||||||
|
|
||||||
if result.name == "":
|
if result.name == "":
|
||||||
error "missing required option: name"
|
error "missing required option: name"
|
||||||
|
|
||||||
# TODO: validate "required" flags exist here
|
func flagsArray(cfg: CliCfg): NimNode =
|
||||||
result.addBuiltinFlags()
|
result = newTree(nnkBracket)
|
||||||
|
|
||||||
func flagToTuple(f: CliFlag | BuiltinFlag): NimNode =
|
for f in cfg.flags:
|
||||||
|
if f.name in cfg.hidden: continue
|
||||||
let
|
let
|
||||||
|
help = f.help
|
||||||
|
long = newLit(f.long)
|
||||||
short =
|
short =
|
||||||
if f.short != '\x00': newLit($f.short)
|
if f.short != '\x00': newLit($f.short)
|
||||||
else: newLit("")
|
else: newLit("")
|
||||||
long = newLit(f.long)
|
result.add quote do:
|
||||||
help = f.help
|
|
||||||
quote do:
|
|
||||||
(`short`, `long`, `help`)
|
(`short`, `long`, `help`)
|
||||||
|
|
||||||
func flagsArray(cfg: CliCfg): NimNode =
|
if NoHelpFlag notin cfg.settings:
|
||||||
result = newTree(nnkBracket)
|
result.add quote do:
|
||||||
for f in cfg.flags:
|
("h", "help", "show this help")
|
||||||
if f.name in cfg.hidden: continue
|
|
||||||
result.add f.flagToTuple()
|
if cfg.version != nil:
|
||||||
for f in cfg.builtinFlags:
|
result.add quote do:
|
||||||
result.add f.flagToTuple()
|
("v", "version", "print version")
|
||||||
|
|
||||||
func subCmdsArray(cfg: CliCfg): NimNode =
|
func subCmdsArray(cfg: CliCfg): NimNode =
|
||||||
result = newTree(nnkBracket)
|
result = newTree(nnkBracket)
|
||||||
|
@ -485,7 +425,7 @@ func subCmdsArray(cfg: CliCfg): NimNode =
|
||||||
result.add quote do:
|
result.add quote do:
|
||||||
(`cmd`, `desc`)
|
(`cmd`, `desc`)
|
||||||
|
|
||||||
proc hwylCliError*(msg: string | BbString) =
|
proc hwylCliError(msg: string | BbString) =
|
||||||
quit $(bb("error ", "red") & bb(msg))
|
quit $(bb("error ", "red") & bb(msg))
|
||||||
|
|
||||||
func defaultUsage(cfg: CliCfg): NimNode =
|
func defaultUsage(cfg: CliCfg): NimNode =
|
||||||
|
@ -497,6 +437,7 @@ func defaultUsage(cfg: CliCfg): NimNode =
|
||||||
|
|
||||||
func generateCliHelperProc(cfg: CliCfg, printHelpName: NimNode): NimNode =
|
func generateCliHelperProc(cfg: CliCfg, printHelpName: NimNode): NimNode =
|
||||||
let
|
let
|
||||||
|
name = newLit(cfg.name)
|
||||||
desc = cfg.desc or newLit("")
|
desc = cfg.desc or newLit("")
|
||||||
usage = cfg.usage or defaultUsage(cfg)
|
usage = cfg.usage or defaultUsage(cfg)
|
||||||
helpFlags = cfg.flagsArray()
|
helpFlags = cfg.flagsArray()
|
||||||
|
@ -505,204 +446,193 @@ func generateCliHelperProc(cfg: CliCfg, printHelpName: NimNode): NimNode =
|
||||||
|
|
||||||
result = quote do:
|
result = quote do:
|
||||||
proc `printHelpName`() =
|
proc `printHelpName`() =
|
||||||
echo bb(render(newHwylCliHelp(
|
echo newHwylCliHelp(
|
||||||
|
cmd = `name`,
|
||||||
desc = `desc`,
|
desc = `desc`,
|
||||||
usage = `usage`,
|
usage = `usage`,
|
||||||
subcmds = `subcmds`,
|
subcmds = `subcmds`,
|
||||||
flags = `helpFlags`,
|
flags = `helpFlags`,
|
||||||
styles = `styles`,
|
styles = `styles`,
|
||||||
)))
|
)
|
||||||
|
|
||||||
proc parse*(p: OptParser, key: string, val: string, target: var bool) =
|
# NOTE: is there a better way to do this?
|
||||||
target = true
|
proc checkVarSet[T](name: string, target: T) =
|
||||||
|
var default: T
|
||||||
|
if target == default:
|
||||||
|
hwylCliError("missing required flag: [b]" & name)
|
||||||
|
|
||||||
proc parse*(p: OptParser, key: string, val: string, target: var string) =
|
proc checkDefaultExists[T](target: T, key: string, val: string) =
|
||||||
target = val
|
var default: T
|
||||||
|
if target == default and val == "":
|
||||||
|
hwylCliError("expected value for: [b]" & key)
|
||||||
|
|
||||||
proc parse*(p: OptParser, key: string, val: string, target: var int) =
|
proc tryParseInt(key: string, val: string): int =
|
||||||
try:
|
try:
|
||||||
target = parseInt(val)
|
result = parseInt(val)
|
||||||
except:
|
except:
|
||||||
hwylCliError(
|
hwylCliError(
|
||||||
"failed to parse value for [b]" & key & "[/] as integer: [b]" & val
|
"failed to parse value for [b]" & key & "[/] as integer: [b]" & val
|
||||||
)
|
)
|
||||||
|
|
||||||
macro enumNames(a: typed): untyped =
|
func addOrOverwrite[T](target: var seq[T], default: seq[T], val: T) =
|
||||||
## unexported macro copied from std/enumutils
|
if target != default:
|
||||||
result = newNimNode(nnkBracket)
|
target.add val
|
||||||
for ai in a.getType[1][1..^1]:
|
|
||||||
assert ai.kind == nnkSym
|
|
||||||
result.add newLit ai.strVal
|
|
||||||
|
|
||||||
proc parse*[E: enum](p: OptParser, key: string, val: string, target: var E) =
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
proc parse*(p: OptParser, key: string, val: string, target: var float) =
|
|
||||||
try:
|
|
||||||
target = parseFloat(val)
|
|
||||||
except:
|
|
||||||
hwylCliError(
|
|
||||||
"failed to parse value for [b]" & key & "[/] as float: [b]" & val
|
|
||||||
)
|
|
||||||
|
|
||||||
proc parse*[T](p: OptParser, key: string, val: string, target: var seq[T]) =
|
|
||||||
var parsed: T
|
|
||||||
parse(p, key, val, parsed)
|
|
||||||
target.add parsed
|
|
||||||
|
|
||||||
proc parse*(p: OptParser, key: string, val: string, target: var Count) =
|
|
||||||
# if value set to that otherwise increment
|
|
||||||
if val != "":
|
|
||||||
var num: int
|
|
||||||
parse(p, key, val, num)
|
|
||||||
target.val = num
|
|
||||||
else:
|
else:
|
||||||
inc target.val
|
target = @[val]
|
||||||
|
|
||||||
|
func assignField(f: CliFlag): NimNode =
|
||||||
|
let key = ident"key"
|
||||||
|
let varName = ident(f.ident)
|
||||||
|
|
||||||
|
case f.typeSym
|
||||||
|
of "string":
|
||||||
|
let value = ident"val"
|
||||||
|
result = quote do:
|
||||||
|
checkDefaultExists(`varName`, `key`, `value`)
|
||||||
|
`varName` = `value`
|
||||||
|
|
||||||
|
of "bool":
|
||||||
|
let value = ident"true"
|
||||||
|
result = quote do:
|
||||||
|
`varName` = `value`
|
||||||
|
|
||||||
|
of "int":
|
||||||
|
let value = ident"val"
|
||||||
|
result = quote do:
|
||||||
|
checkDefaultExists(`varName`, `key`, `value`)
|
||||||
|
`varName` = tryParseInt(`key`, `value`)
|
||||||
|
|
||||||
|
of "seq[string]":
|
||||||
|
let value = ident"val"
|
||||||
|
let default = f.default or (quote do: @[])
|
||||||
|
result = quote do:
|
||||||
|
`varName`.addOrOverwrite(`default`, `value`)
|
||||||
|
|
||||||
|
of "seq[int]":
|
||||||
|
let value = ident"val"
|
||||||
|
let default = f.default or (quote do: @[])
|
||||||
|
result = quote do:
|
||||||
|
`varName`.addOrOverwrite(`default`, tryParseInt(`value`))
|
||||||
|
|
||||||
|
else: error "unable to generate assignment for fion, type: " & f.name & "," & f.typeSym
|
||||||
|
|
||||||
func shortLongCaseStmt(cfg: CliCfg, printHelpName: NimNode, version: NimNode): NimNode =
|
func shortLongCaseStmt(cfg: CliCfg, printHelpName: NimNode, version: NimNode): NimNode =
|
||||||
var caseStmt = nnkCaseStmt.newTree()
|
var caseStmt = nnkCaseStmt.newTree(ident("key"))
|
||||||
if NoNormalize notin cfg.settings:
|
|
||||||
caseStmt.add nnkCall.newTree(ident"optionNormalize", ident"key")
|
|
||||||
else:
|
|
||||||
caseStmt.add ident"key"
|
|
||||||
|
|
||||||
caseStmt.add nnkOfBranch.newTree(newLit(""), quote do: hwylCliError("empty flag not supported currently"))
|
caseStmt.add nnkOfBranch.newTree(newLit(""), quote do: hwylCliError("empty flag not supported currently"))
|
||||||
|
|
||||||
for f in cfg.builtinFlags:
|
if NoHelpFlag notin cfg.settings:
|
||||||
var branch = nnkOfBranch.newTree()
|
caseStmt.add nnkOfBranch.newTree(
|
||||||
if f.long != "": branch.add(newLit(f.long))
|
newLit("h"), newLit("help"),
|
||||||
if f.short != '\x00': branch.add(newLit($f.short))
|
quote do:
|
||||||
branch.add f.node
|
`printHelpName`(); quit 0
|
||||||
caseStmt.add branch
|
)
|
||||||
|
|
||||||
|
if cfg.version != nil:
|
||||||
|
caseStmt.add nnkOfBranch.newTree(
|
||||||
|
newLit("V"), newLit("version"),
|
||||||
|
quote do:
|
||||||
|
echo `version`; quit 0
|
||||||
|
)
|
||||||
|
|
||||||
# add flags
|
# add flags
|
||||||
for f in cfg.flags:
|
for f in cfg.flags:
|
||||||
var branch = nnkOfBranch.newTree()
|
var branch = nnkOfBranch.newTree()
|
||||||
if f.long != "":
|
if f.long != "": branch.add(newLit(f.long))
|
||||||
branch.add newLit(
|
|
||||||
if NoNormalize notin cfg.settings: optionNormalize(f.long)
|
|
||||||
else: f.long
|
|
||||||
)
|
|
||||||
if f.short != '\x00': branch.add(newLit($f.short))
|
if f.short != '\x00': branch.add(newLit($f.short))
|
||||||
let varName = f.ident
|
branch.add assignField(f)
|
||||||
let name = newLit(f.name)
|
|
||||||
branch.add quote do:
|
|
||||||
flagSet.incl `name`
|
|
||||||
parse(p, key, val, `varName`)
|
|
||||||
|
|
||||||
caseStmt.add branch
|
caseStmt.add branch
|
||||||
|
|
||||||
caseStmt.add nnkElse.newTree(quote do: hwylCliError("unknown flag: [b]" & key))
|
caseStmt.add nnkElse.newTree(quote do: hwylCliError("unknown flag: [b]" & key))
|
||||||
|
|
||||||
result = nnkStmtList.newTree(caseStmt)
|
result = nnkStmtList.newTree(caseStmt)
|
||||||
|
|
||||||
func isBool(f: CliFlag): bool =
|
|
||||||
f.typeNode == ident"bool"
|
|
||||||
|
|
||||||
func isCount(f: CliFlag): bool =
|
|
||||||
f.typeNode == ident"Count"
|
|
||||||
|
|
||||||
func getNoVals(cfg: CliCfg): tuple[long: NimNode, short: NimNode] =
|
func getNoVals(cfg: CliCfg): tuple[long: NimNode, short: NimNode] =
|
||||||
let flagFlags = cfg.flags.filterIt(it.isBool or it.isCount)
|
var long = nnkBracket.newTree()
|
||||||
let long =
|
var short = nnkCurly.newTree()
|
||||||
nnkBracket.newTree(
|
|
||||||
(flagFlags.mapIt(it.long) & cfg.builtinFlags.mapIt(it.long)).filterIt(it != "").mapIt(newLit(it))
|
if NoHelpFlag notin cfg.settings:
|
||||||
)
|
long.add newLit("help")
|
||||||
let short =
|
short.add newLit('h')
|
||||||
nnkCurly.newTree(
|
|
||||||
(flagFlags.mapIt(it.short) & cfg.builtinFlags.mapIt(it.short)).filterIt(it != '\x00').mapIt(newLit(it))
|
if cfg.version != nil:
|
||||||
)
|
long.add newLit("version")
|
||||||
|
short.add newLit('V')
|
||||||
|
|
||||||
|
for f in cfg.flags:
|
||||||
|
if f.typeSym == "bool":
|
||||||
|
if f.long != "":
|
||||||
|
long.add newLit(f.long)
|
||||||
|
if f.short != '\x00':
|
||||||
|
short.add newLit(f.short)
|
||||||
|
|
||||||
result = (nnkPrefix.newTree(ident"@",long), short)
|
result = (nnkPrefix.newTree(ident"@",long), short)
|
||||||
|
|
||||||
func setFlagVars(cfg: CliCfg): NimNode =
|
func setFlagVars(cfg: CliCfg): NimNode =
|
||||||
result = nnkVarSection.newTree().add(
|
result = nnkVarSection.newTree()
|
||||||
cfg.flags.mapIt(
|
# TODO: generalize this better...
|
||||||
nnkIdentDefs.newTree(it.ident, it.typeNode, newEmptyNode())
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
func literalFlags(f: CliFlag): NimNode =
|
|
||||||
var flags: seq[string]
|
|
||||||
if f.short != '\x00': flags.add "[b]" & "-" & $f.short & "[/]"
|
|
||||||
if f.long != "": flags.add "[b]" & "--" & f.long & "[/]"
|
|
||||||
result = newLit(flags.join("|"))
|
|
||||||
|
|
||||||
func addPostParseCheck(cfg: CliCfg, body: NimNode) =
|
|
||||||
## generate block to set defaults and check for required flags
|
|
||||||
let flagSet = ident"flagSet"
|
|
||||||
var required, default: seq[CliFlag]
|
|
||||||
|
|
||||||
for f in cfg.flags:
|
for f in cfg.flags:
|
||||||
if f.name in cfg.required and f.default == nil:
|
|
||||||
required.add f
|
|
||||||
elif f.default != nil:
|
|
||||||
default.add f
|
|
||||||
|
|
||||||
for f in required:
|
|
||||||
let flagLit = f.literalFlags
|
|
||||||
let name = newLit(f.name)
|
|
||||||
body.add quote do:
|
|
||||||
if `name` notin `flagSet`:
|
|
||||||
hwylCliError("expected a value for flag: " & `flagLit`)
|
|
||||||
|
|
||||||
for f in default:
|
|
||||||
let
|
let
|
||||||
name = newLit(f.name)
|
t =
|
||||||
target = f.ident
|
if f.typeSym == "seq[string]": nnkBracketExpr.newTree(newIdentNode("seq"),newIdentNode("string"))
|
||||||
default = f.default
|
elif f.typeSym == "seq[int]" : nnkBracketExpr.newTree(newIdentNode("seq"),newIdentNode("string"))
|
||||||
body.add quote do:
|
else: ident(f.typeSym)
|
||||||
if `name` notin `flagSet`:
|
val =
|
||||||
`target` = `default`
|
if f.default == nil: newEmptyNode() # use default here
|
||||||
|
else: f.default
|
||||||
|
|
||||||
|
result.add nnkIdentDefs.newTree(ident(f.ident), t, val)
|
||||||
|
|
||||||
|
func addRequiredFlagsCheck(cfg: CliCfg, body: NimNode) =
|
||||||
|
let requirdFlags = cfg.flags.filterIt(it.name in cfg.required and it.default == nil)
|
||||||
|
for f in requirdFlags:
|
||||||
|
let name = newLit(f.name)
|
||||||
|
let flag = ident(f.ident)
|
||||||
|
body.add quote do:
|
||||||
|
checkVarSet(`name`, `flag`)
|
||||||
|
|
||||||
|
func hwylCliImpl(cfg: CliCfg, root = false): NimNode =
|
||||||
|
|
||||||
func hwylCliImpl(cfg: CliCfg): NimNode =
|
|
||||||
let
|
let
|
||||||
version = cfg.version or newLit("")
|
version = cfg.version or newLit("")
|
||||||
name = cfg.name.replace(" ", "")
|
name = cfg.name.replace(" ", "")
|
||||||
printHelpName = ident("print" & name & "Help")
|
printHelpName = ident("print" & name & "Help")
|
||||||
parserProcName = ident("parse" & name)
|
parserProcName = ident("parse" & name)
|
||||||
args = ident"args"
|
|
||||||
optParser = ident("p")
|
|
||||||
cmdLine = ident"cmdLine"
|
|
||||||
flagSet = ident"flagSet"
|
|
||||||
kind = ident"kind"
|
|
||||||
key = ident"key"
|
|
||||||
val = ident"val"
|
|
||||||
(longNoVal, shortNoVal) = cfg.getNoVals()
|
|
||||||
printHelperProc = generateCliHelperProc(cfg, printHelpName)
|
|
||||||
flagVars = setFlagVars(cfg)
|
|
||||||
|
|
||||||
result = newTree(nnkStmtList)
|
result = newTree(nnkStmtList)
|
||||||
|
|
||||||
var
|
let
|
||||||
parserBody = nnkStmtList.newTree()
|
printHelperProc = generateCliHelperProc(cfg, printHelpName)
|
||||||
stopWords = nnkBracket.newTree(newLit("--"))
|
flagVars = setFlagVars(cfg)
|
||||||
|
|
||||||
|
# result.add setFlagVars(cfg)
|
||||||
|
|
||||||
|
var parserBody = nnkStmtList.newTree()
|
||||||
|
let
|
||||||
|
optParser = ident("p")
|
||||||
|
cmdLine = ident"cmdLine"
|
||||||
|
(longNoVal, shortNoVal) = cfg.getNoVals()
|
||||||
|
|
||||||
|
var stopWords = nnkBracket.newTree(newLit("--"))
|
||||||
for w in cfg.stopWords:
|
for w in cfg.stopWords:
|
||||||
stopWords.add newLit(w)
|
stopWords.add newLit(w)
|
||||||
|
|
||||||
stopWords = nnkPrefix.newTree(ident"@", stopWords)
|
stopWords = nnkPrefix.newTree(ident"@", stopWords)
|
||||||
|
|
||||||
# should this a CritBitTree?
|
|
||||||
parserBody.add quote do:
|
|
||||||
var `flagSet`: HashSet[string]
|
|
||||||
|
|
||||||
parserBody.add(
|
parserBody.add(
|
||||||
quote do:
|
quote do:
|
||||||
var `optParser` = initOptParser(
|
var `optParser` = initOptParser(
|
||||||
@`cmdLine`,
|
@`cmdLine`,
|
||||||
longNoVal = `longNoVal`,
|
longNoVal = `longNoVal`,
|
||||||
shortNoVal = `shortNoVal`,
|
shortNoVal = `shortNoVal`,
|
||||||
stopWords = `stopWords`,
|
stopWords = `stopWords`
|
||||||
opChars = {','}
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# TODO: first key needs to be normalized
|
|
||||||
|
let
|
||||||
|
kind = ident"kind"
|
||||||
|
key = ident"key"
|
||||||
|
val = ident"val"
|
||||||
|
|
||||||
parserBody.add nnkForStmt.newTree(
|
parserBody.add nnkForStmt.newTree(
|
||||||
kind, key, val,
|
kind, key, val,
|
||||||
nnkCall.newTree(nnkDotExpr.newTree(optParser,ident("getopt"))),
|
nnkCall.newTree(nnkDotExpr.newTree(optParser,ident("getopt"))),
|
||||||
|
@ -716,10 +646,7 @@ func hwylCliImpl(cfg: CliCfg): NimNode =
|
||||||
nnkOfBranch.newTree(ident("cmdError"), quote do: hwylCliError(p.message)),
|
nnkOfBranch.newTree(ident("cmdError"), quote do: hwylCliError(p.message)),
|
||||||
nnkOfBranch.newTree(ident("cmdEnd"), quote do: assert false),
|
nnkOfBranch.newTree(ident("cmdEnd"), quote do: assert false),
|
||||||
# TODO: add nArgs to change how cmdArgument is handled ...
|
# TODO: add nArgs to change how cmdArgument is handled ...
|
||||||
nnkOfBranch.newTree(ident("cmdArgument"),
|
nnkOfBranch.newTree(ident("cmdArgument"), quote do: result.add `key`),
|
||||||
quote do:
|
|
||||||
result.add `key`
|
|
||||||
),
|
|
||||||
nnkOfBranch.newTree(
|
nnkOfBranch.newTree(
|
||||||
ident("cmdShortOption"), ident("cmdLongOption"),
|
ident("cmdShortOption"), ident("cmdLongOption"),
|
||||||
shortLongCaseStmt(cfg, printHelpName, version)
|
shortLongCaseStmt(cfg, printHelpName, version)
|
||||||
|
@ -728,14 +655,14 @@ func hwylCliImpl(cfg: CliCfg): NimNode =
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if ShowHelp in cfg.settings:
|
if NoArgsShowHelp in cfg.settings:
|
||||||
parserBody.add quote do:
|
parserBody.add quote do:
|
||||||
if commandLineParams().len == 0:
|
if commandLineParams().len == 0:
|
||||||
`printHelpName`(); quit 1
|
`printHelpName`(); quit 1
|
||||||
|
|
||||||
let runProcName = ident("run" & name)
|
let runProcName = ident("run" & name)
|
||||||
let runBody = nnkStmtList.newTree()
|
let runBody = nnkStmtList.newTree()
|
||||||
addPostParseCheck(cfg, parserBody)
|
addRequiredFlagsCheck(cfg, runBody)
|
||||||
# move to proc?
|
# move to proc?
|
||||||
if cfg.pre != nil:
|
if cfg.pre != nil:
|
||||||
runBody.add cfg.pre
|
runBody.add cfg.pre
|
||||||
|
@ -744,27 +671,28 @@ func hwylCliImpl(cfg: CliCfg): NimNode =
|
||||||
if cfg.post != nil:
|
if cfg.post != nil:
|
||||||
runBody.add cfg.post
|
runBody.add cfg.post
|
||||||
|
|
||||||
|
# let runBody = cfg.run or nnkStmtList.newTree(nnkDiscardStmt.newTree(newEmptyNode()))
|
||||||
|
|
||||||
|
let args = ident"args"
|
||||||
|
|
||||||
if cfg.subcommands.len > 0:
|
if cfg.subcommands.len > 0:
|
||||||
var handleSubCommands = nnkStmtList.newTree()
|
var handleSubCommands = nnkStmtList.newTree()
|
||||||
handleSubCommands.add quote do:
|
handleSubCommands.add quote do:
|
||||||
if `args`.len == 0:
|
if `args`.len == 0:
|
||||||
hwylCliError("expected subcommand")
|
hwylCliError("expected subcommand")
|
||||||
|
|
||||||
var subCommandCase = nnkCaseStmt.newTree()
|
var subCommandCase = nnkCaseStmt.newTree(
|
||||||
if NoNormalize notin cfg.settings:
|
quote do: `args`[0]
|
||||||
subCommandCase.add(quote do: optionNormalize(`args`[0]))
|
)
|
||||||
else:
|
|
||||||
subCommandCase.add(quote do: `args`[0])
|
|
||||||
|
|
||||||
for sub in cfg.subcommands:
|
for sub in cfg.subcommands:
|
||||||
subCommandCase.add nnkOfBranch.newTree(
|
subCommandCase.add nnkOfBranch.newTree(
|
||||||
newLit(optionNormalize(sub.subName)),
|
newLit(sub.subName),
|
||||||
hwylCliImpl(sub)
|
hwylCliImpl(sub)
|
||||||
)
|
)
|
||||||
|
|
||||||
subcommandCase.add nnkElse.newTree(
|
subcommandCase.add nnkElse.newTree(
|
||||||
quote do:
|
quote do:
|
||||||
hwylCliError("unknown subcommand: [b]" & `args`[0])
|
hwylCliError("unknown subcommand " & `args`[0])
|
||||||
)
|
)
|
||||||
|
|
||||||
runBody.add handleSubCommands.add subCommandCase
|
runBody.add handleSubCommands.add subCommandCase
|
||||||
|
@ -780,7 +708,7 @@ func hwylCliImpl(cfg: CliCfg): NimNode =
|
||||||
let `args` = `parserProcName`(`cmdLine`)
|
let `args` = `parserProcName`(`cmdLine`)
|
||||||
`runBody`
|
`runBody`
|
||||||
|
|
||||||
if cfg.root:
|
if root:
|
||||||
result.add quote do:
|
result.add quote do:
|
||||||
`runProcName`()
|
`runProcName`()
|
||||||
else:
|
else:
|
||||||
|
@ -789,6 +717,51 @@ func hwylCliImpl(cfg: CliCfg): NimNode =
|
||||||
|
|
||||||
macro hwylCli*(body: untyped) =
|
macro hwylCli*(body: untyped) =
|
||||||
## generate a CLI styled by `hwylterm` and parsed by `parseopt3`
|
## generate a CLI styled by `hwylterm` and parsed by `parseopt3`
|
||||||
var cfg = parseCliBody(body, root = true)
|
var cfg = parseCliBody(body)
|
||||||
hwylCliImpl(cfg)
|
hwylCliImpl(cfg, root = true)
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
import std/strformat
|
||||||
|
hwylCli:
|
||||||
|
name "hwylterm"
|
||||||
|
... "a description of hwylterm"
|
||||||
|
globalFlags:
|
||||||
|
config:
|
||||||
|
T seq[string]
|
||||||
|
? "path to config file"
|
||||||
|
* @["config.yml"]
|
||||||
|
flags:
|
||||||
|
check:
|
||||||
|
T bool
|
||||||
|
? "load config and exit"
|
||||||
|
- c
|
||||||
|
run:
|
||||||
|
echo "hello from the main command"
|
||||||
|
echo fmt"{config=}, {check=}"
|
||||||
|
subcommands:
|
||||||
|
--- a
|
||||||
|
... "the \"a\" subcommand"
|
||||||
|
flags:
|
||||||
|
`long-flag` "some help"
|
||||||
|
flagg "some other help"
|
||||||
|
run:
|
||||||
|
echo "hello from hwylterm sub command!"
|
||||||
|
--- b
|
||||||
|
... """
|
||||||
|
some "B" command
|
||||||
|
|
||||||
|
a longer mulitline description that will be visibil in the subcommand help
|
||||||
|
it will automatically be "bb"'ed [bold]this is bold text[/]
|
||||||
|
"""
|
||||||
|
flags:
|
||||||
|
aflag:
|
||||||
|
T bool
|
||||||
|
? "some help"
|
||||||
|
bflag:
|
||||||
|
? "some other flag?"
|
||||||
|
* "wow"
|
||||||
|
run:
|
||||||
|
echo "hello from hwylterm sub `b` command"
|
||||||
|
echo aflag, bflag
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
import std/[strformat, strutils]
|
|
||||||
import hwylterm, hwylterm/hwylcli
|
|
||||||
|
|
||||||
|
|
||||||
type
|
|
||||||
Color = enum
|
|
||||||
red, blue, green
|
|
||||||
|
|
||||||
|
|
||||||
hwylCli:
|
|
||||||
name "example"
|
|
||||||
V "0.1.0"
|
|
||||||
... "a description of hwylterm"
|
|
||||||
flags:
|
|
||||||
[global]
|
|
||||||
yes:
|
|
||||||
T bool
|
|
||||||
? "set flag to yes"
|
|
||||||
[config]
|
|
||||||
confiG:
|
|
||||||
T seq[string]
|
|
||||||
? "path to config file"
|
|
||||||
* @["config.yml"]
|
|
||||||
preSub:
|
|
||||||
echo "this is run after subcommand parsing but before its run block"
|
|
||||||
run:
|
|
||||||
echo "this is always run prior to subcommand parsing"
|
|
||||||
subcommands:
|
|
||||||
--- "onelonger"
|
|
||||||
... """
|
|
||||||
the first subcommand
|
|
||||||
|
|
||||||
this command features both an enum flag and a Count flag
|
|
||||||
it also inherits the `[[config]` flag group
|
|
||||||
"""
|
|
||||||
|
|
||||||
flags:
|
|
||||||
color:
|
|
||||||
T Color
|
|
||||||
? "a color (red, green, blue)"
|
|
||||||
verbose:
|
|
||||||
T Count
|
|
||||||
? "a count flag"
|
|
||||||
- v
|
|
||||||
^[config]
|
|
||||||
run:
|
|
||||||
echo "hello from `example one` command!"
|
|
||||||
echo fmt"{color=}"
|
|
||||||
echo fmt"{verbose=}"
|
|
||||||
echo fmt"{config=}"
|
|
||||||
|
|
||||||
--- "two-longer"
|
|
||||||
... """
|
|
||||||
some second subcommand
|
|
||||||
|
|
||||||
a longer mulitline description that will be visible in the subcommand help
|
|
||||||
and it will automatically be "bb"'ed [bold]this is bold text[/]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
flags:
|
|
||||||
a:
|
|
||||||
T bool
|
|
||||||
? "some help"
|
|
||||||
b:
|
|
||||||
T seq[float]
|
|
||||||
? "multiple floats"
|
|
||||||
h "this will override the builtin 'h' for help"
|
|
||||||
run:
|
|
||||||
echo "hello from `example b` command"
|
|
||||||
echo fmt"{a=}, {b=}"
|
|
||||||
|
|
||||||
|
|
18
todo.md
18
todo.md
|
@ -14,9 +14,25 @@
|
||||||
- [ ] add a `commands` option for `newHwylCli` in `hwylterm/cli`
|
- [ ] add a `commands` option for `newHwylCli` in `hwylterm/cli`
|
||||||
- [ ] console object with customizable options to apply formatting
|
- [ ] console object with customizable options to apply formatting
|
||||||
|
|
||||||
### cli generator
|
### cli generato
|
||||||
|
|
||||||
- [ ] add support for types(metavars)/defaults/required in help output
|
- [ ] add support for types(metavars)/defaults/required in help output
|
||||||
|
- [ ] abstract the `globalFlags` argument to a `flagGroups` section with a builtin `global`
|
||||||
|
this would allow users to "inherit" flag groups in subcommands
|
||||||
|
```nim
|
||||||
|
flags:
|
||||||
|
# global flag group auto propagated down
|
||||||
|
--- global
|
||||||
|
config "path to config"
|
||||||
|
--- shared
|
||||||
|
shared:
|
||||||
|
`a-flag` "some shared flag"
|
||||||
|
-- sub
|
||||||
|
flags:
|
||||||
|
^shared
|
||||||
|
unique "some unique flag"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## features
|
## features
|
||||||
|
|
Loading…
Reference in a new issue