mirror of
https://github.com/daylinmorgan/hwylterm.git
synced 2024-11-16 06:28:32 -06:00
massive feature in macro cli generator
This commit is contained in:
parent
56bb2bb7c1
commit
fed1c03ce8
7 changed files with 836 additions and 204 deletions
|
@ -26,7 +26,7 @@ task docs, "Deploy doc html + search index to public/ directory":
|
||||||
when defined(clean):
|
when defined(clean):
|
||||||
echo fmt"clearing {deployDir}"
|
echo fmt"clearing {deployDir}"
|
||||||
rmDir deployDir
|
rmDir deployDir
|
||||||
for module in ["cligen", "chooser", "logging", "cli", "parseopt3"]:
|
for module in ["cligen", "chooser", "logging", "hwylcli", "parseopt3"]:
|
||||||
selfExec fmt"{docCmd} --docRoot:{getCurrentDir()}/src/ src/hwylterm/{module}"
|
selfExec fmt"{docCmd} --docRoot:{getCurrentDir()}/src/ src/hwylterm/{module}"
|
||||||
selfExec fmt"{docCmd} --project --project src/{pkgName}.nim"
|
selfExec fmt"{docCmd} --project --project src/{pkgName}.nim"
|
||||||
docFixup(deployDir, pkgName)
|
docFixup(deployDir, pkgName)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
see also these utility modules:
|
see also these utility modules:
|
||||||
|
|
||||||
- [cli](./hwylterm/cli.html)
|
- [hwylcli](./hwylterm/hwylcli.html)
|
||||||
- [cligen adapter](./hwylterm/cligen.html), requires [cligen](https://github.com/c-blake/cligen)
|
- [cligen adapter](./hwylterm/cligen.html), requires [cligen](https://github.com/c-blake/cligen)
|
||||||
- [chooser](./hwylterm/chooser.html)
|
- [chooser](./hwylterm/chooser.html)
|
||||||
- [logging](./hwylterm/logging.html)
|
- [logging](./hwylterm/logging.html)
|
||||||
|
|
|
@ -254,7 +254,6 @@ func bb*(s: string): BbString =
|
||||||
else:
|
else:
|
||||||
next
|
next
|
||||||
|
|
||||||
|
|
||||||
result.closeFinalSpan
|
result.closeFinalSpan
|
||||||
|
|
||||||
proc bb*(s: string, style: string): BbString =
|
proc bb*(s: string, style: string): BbString =
|
||||||
|
@ -263,7 +262,9 @@ proc bb*(s: string, style: string): BbString =
|
||||||
proc bb*(s: string, style: Color256): BbString =
|
proc bb*(s: string, style: Color256): BbString =
|
||||||
bb(s, $style)
|
bb(s, $style)
|
||||||
|
|
||||||
proc `&`*(x: BbString, y: string): BbString =
|
proc bb*(s: BbString): BbString = s
|
||||||
|
|
||||||
|
func `&`*(x: BbString, y: string): BbString =
|
||||||
result = x
|
result = x
|
||||||
result.plain &= y
|
result.plain &= y
|
||||||
result.spans.add BbSpan(styles: @[], slice: [x.plain.len, result.plain.len - 1])
|
result.spans.add BbSpan(styles: @[], slice: [x.plain.len, result.plain.len - 1])
|
||||||
|
@ -343,30 +344,9 @@ proc bbEcho*(args: varargs[string, `$`]) {.raises: [IOError]} =
|
||||||
|
|
||||||
# NOTE: could move to standlone modules in the tools/ directory
|
# NOTE: could move to standlone modules in the tools/ directory
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
import ./[cli, parseopt3]
|
import ./[hwylcli]
|
||||||
|
|
||||||
const version = staticExec "git describe --tags --always --dirty=-dev"
|
const version = staticExec "git describe --tags --always --dirty=-dev"
|
||||||
|
proc showTestCard() =
|
||||||
proc writeHelp() =
|
|
||||||
let help = $newHwylCli(
|
|
||||||
"[bold]bbansi[/] [[[green]args...[/]] [[[faint]-h|-v[/]]",
|
|
||||||
"""
|
|
||||||
bbansi "[[yellow] yellow text!"
|
|
||||||
-> [yellow] yellow text![/]
|
|
||||||
bbansi "[[bold red] bold red text[[/] plain text..."
|
|
||||||
-> [bold red] bold red text[/] plain text...
|
|
||||||
bbansi "[[red]some red[[/red] but all italic" --style:italic
|
|
||||||
-> [italic][red]some red[/red] but all italic[/italic]
|
|
||||||
""",
|
|
||||||
[
|
|
||||||
("h", "help", "show this help"),
|
|
||||||
("v", "version", "show version"),
|
|
||||||
("s", "style", "set style for string"),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
echo help; quit 0
|
|
||||||
|
|
||||||
proc testCard() =
|
|
||||||
for style in [
|
for style in [
|
||||||
"bold", "faint", "italic", "underline", "blink", "reverse", "conceal", "strike"
|
"bold", "faint", "italic", "underline", "blink", "reverse", "conceal", "strike"
|
||||||
]:
|
]:
|
||||||
|
@ -379,52 +359,39 @@ bbansi "[[red]some red[[/red] but all italic" --style:italic
|
||||||
echo "on ", color, " -> ", fmt"[on {color}]****".bb
|
echo "on ", color, " -> ", fmt"[on {color}]****".bb
|
||||||
quit(QuitSuccess)
|
quit(QuitSuccess)
|
||||||
|
|
||||||
proc debug(bbs: BbString): string =
|
proc debugBb(bbs: BbString): string =
|
||||||
echo "bbString("
|
echo "bbString("
|
||||||
echo " plain: ", bbs.plain
|
echo " plain: ", bbs.plain
|
||||||
echo " spans: ", bbs.spans
|
echo " spans: ", bbs.spans
|
||||||
echo " escaped: ", escape($bbs)
|
echo " escaped: ", escape($bbs)
|
||||||
echo ")"
|
echo ")"
|
||||||
|
|
||||||
proc writeVersion() =
|
hwylCli:
|
||||||
echo bbfmt"[yellow]bbansi version[/][red] ->[/] [bold]{version}[/]"
|
name "bbansi"
|
||||||
quit 0
|
settings NoArgsShowHelp
|
||||||
|
usage "[bold]bbansi[/] [[[green]args...[/]] [[[faint]-h|-V[/]]"
|
||||||
var
|
description """
|
||||||
strArgs: seq[string]
|
bbansi "[[yellow] yellow text!"
|
||||||
style: string
|
-> [yellow] yellow text![/]
|
||||||
showDebug: bool
|
bbansi "[[bold red] bold red text[[/] plain text..."
|
||||||
var p = initOptParser(
|
-> [bold red] bold red text[/] plain text...
|
||||||
shortNoVal = {'h','v'},
|
bbansi "[[red]some red[[/red] but all italic" --style:italic
|
||||||
longNoVal = @["help", "version", "testCard"]
|
-> [italic][red]some red[/red] but all italic[/italic]
|
||||||
)
|
"""
|
||||||
for kind, key, val in p.getopt():
|
version bbfmt"[yellow]bbansi version[/][red] ->[/] [bold]{version}[/]"
|
||||||
case kind
|
hidden debug, testCard
|
||||||
of cmdError: quit($(bb"[red]cli error[/]: " & p.message), 1)
|
flags:
|
||||||
of cmdEnd: assert(false)
|
debug:
|
||||||
of cmdShortOption, cmdLongOption:
|
T bool
|
||||||
case key
|
testCard:
|
||||||
of "testCard" : testCard()
|
T bool
|
||||||
of "help" , "h": writeHelp()
|
style:
|
||||||
of "version", "v": writeVersion()
|
? "set style for string"
|
||||||
of "style" , "s":
|
- "s"
|
||||||
if val == "":
|
run:
|
||||||
bbEcho "[red]ERROR[/]: expected value for -s/--style"
|
if testCard: showTestCard()
|
||||||
quit(QuitFailure)
|
for arg in args:
|
||||||
style = val
|
let styled = arg.bb(style)
|
||||||
of "debug":
|
echo styled
|
||||||
showDebug = true
|
if debug:
|
||||||
else:
|
echo debugBb(styled)
|
||||||
bbEcho "[yellow]warning[/]: unexpected option/value -> ", key, ", ", val
|
|
||||||
of cmdArgument: strArgs.add key
|
|
||||||
if strArgs.len == 0:
|
|
||||||
writeHelp()
|
|
||||||
for arg in strArgs:
|
|
||||||
let styled =
|
|
||||||
if style != "":
|
|
||||||
arg.bb(style)
|
|
||||||
else:
|
|
||||||
arg.bb
|
|
||||||
echo styled
|
|
||||||
if showDebug:
|
|
||||||
echo debug(styled)
|
|
||||||
|
|
|
@ -127,56 +127,37 @@ proc choose*[T](things: openArray[T], height: Natural = 6): seq[T] =
|
||||||
|
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
import ./[cli, parseopt3]
|
import ./[hwylcli]
|
||||||
|
hwylcli:
|
||||||
proc writeHelp() =
|
name "hwylchoose"
|
||||||
echo newHwylCli(
|
settings NoArgsShowHelp
|
||||||
"[bold]hwylchoose[/] [[[green]args...[/]] [[[faint]-h[/]]",
|
usage "[bold]hwylchoose[/] [[[green]args...[/]] [[[faint]-h[/]]"
|
||||||
"""
|
description """
|
||||||
hwylchoose a b c d
|
hwylchoose a b c d
|
||||||
hwylchoose a,b,c,d -s ,
|
hwylchoose a,b,c,d -s ,
|
||||||
hwylchoose a,b,c,d --seperator ","
|
hwylchoose a,b,c,d --seperator ","
|
||||||
""",
|
hwylchoose --demo
|
||||||
[
|
"""
|
||||||
("h", "help", "show this help"),
|
hidden demo
|
||||||
("s", "seperator", "seperator to split items"),
|
flags:
|
||||||
]
|
demo:
|
||||||
)
|
T bool
|
||||||
|
separator:
|
||||||
var
|
help "separator to split items"
|
||||||
posArgs: seq[string]
|
short "s"
|
||||||
sep: string
|
run:
|
||||||
var p = initOptParser(
|
var items: seq[string]
|
||||||
shortNoVal = {'h'}, longNoVal = @["help", "demo"]
|
if demo:
|
||||||
)
|
items &= LowercaseLetters.toSeq().mapIt($it)
|
||||||
for kind, key, val in p.getopt():
|
|
||||||
case kind
|
|
||||||
of cmdError: quit($(bb"[red]cli error[/]: " & p.message), 1)
|
|
||||||
of cmdEnd: assert false
|
|
||||||
of cmdShortOption, cmdLongOption:
|
|
||||||
case key
|
|
||||||
of "help", "h":
|
|
||||||
writeHelp(); quit 0
|
|
||||||
of "demo":
|
|
||||||
posArgs &= LowercaseLetters.toSeq().mapIt($it)
|
|
||||||
of "seperator","s":
|
|
||||||
if val == "":
|
|
||||||
echo bb"[red]ERROR[/]: expected value for --seperator"
|
|
||||||
quit QuitFailure
|
|
||||||
sep = val
|
|
||||||
else:
|
else:
|
||||||
echo bb"[yellow]warning[/]: unexpected option/value -> ", key, ", ", val
|
if separator != "":
|
||||||
of cmdArgument:
|
if args.len != 1: quit "only pass one positional arg when using --separator"
|
||||||
posArgs.add key
|
items = args[0].split(separator).mapIt(strip(it))
|
||||||
if posArgs.len == 0: quit "expected values to choose"
|
else:
|
||||||
var items: seq[string]
|
items = args
|
||||||
if sep != "":
|
|
||||||
if posArgs.len != 1: quit "only pass one positional arg when using --sep"
|
let item = choose(items)
|
||||||
items = posArgs[0].split(sep).mapIt(strip(it))
|
echo "selected: ", item
|
||||||
else:
|
|
||||||
items = posArgs
|
|
||||||
let item = choose(items)
|
|
||||||
echo "selected: ", item
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
##[
|
|
||||||
# Cli
|
|
||||||
]##
|
|
||||||
|
|
||||||
import std/[strutils]
|
|
||||||
import ./bbansi
|
|
||||||
|
|
||||||
type
|
|
||||||
HwylFlag = tuple
|
|
||||||
short, long, description = ""
|
|
||||||
HwylCliStyles* = object
|
|
||||||
hdr = "b cyan"
|
|
||||||
shortFlag = "yellow"
|
|
||||||
longFlag = "magenta"
|
|
||||||
descFlag = ""
|
|
||||||
HwylCli* = object
|
|
||||||
cmd*: string
|
|
||||||
usage*: string
|
|
||||||
flags*: seq[HwylFlag]
|
|
||||||
styles*: HwylCliStyles
|
|
||||||
shortArgLen, longArgLen, descArgLen: int
|
|
||||||
|
|
||||||
|
|
||||||
func newHwylCli*(
|
|
||||||
cmd = "",
|
|
||||||
usage = "",
|
|
||||||
flags: openArray[HwylFlag] = @[],
|
|
||||||
styles = HwylCliStyles()
|
|
||||||
): HwylCli =
|
|
||||||
result.cmd = cmd
|
|
||||||
result.usage = usage
|
|
||||||
result.flags = @flags
|
|
||||||
result.styles = styles
|
|
||||||
for f in flags:
|
|
||||||
result.shortArgLen = max(result.shortArgLen, f.short.len)
|
|
||||||
result.longArgLen = max(result.longArgLen, f.long.len)
|
|
||||||
result.descArgLen = max(result.descArgLen, f.description.len)
|
|
||||||
|
|
||||||
|
|
||||||
func flagHelp(cli: HwylCli, f: HwylFlag): string =
|
|
||||||
result.add " "
|
|
||||||
if f.short != "":
|
|
||||||
result.add "[" & cli.styles.shortFlag & "]"
|
|
||||||
result.add "-" & f.short.alignLeft(cli.shortArgLen)
|
|
||||||
result.add "[/]"
|
|
||||||
else:
|
|
||||||
result.add " ".repeat(1 + cli.shortArgLen)
|
|
||||||
|
|
||||||
result.add " "
|
|
||||||
if f.long != "":
|
|
||||||
result.add "[" & cli.styles.longFlag & "]"
|
|
||||||
result.add "--" & f.long.alignLeft(cli.longArgLen)
|
|
||||||
result.add "[/]"
|
|
||||||
else:
|
|
||||||
result.add " ".repeat(2 + cli.longArgLen)
|
|
||||||
|
|
||||||
result.add " "
|
|
||||||
if f.description != "":
|
|
||||||
result.add "[" & cli.styles.descFlag & "]"
|
|
||||||
result.add f.description
|
|
||||||
result.add "[/]"
|
|
||||||
result.add "\n"
|
|
||||||
|
|
||||||
proc bbImpl(cli: HwylCli): string =
|
|
||||||
if cli.cmd != "":
|
|
||||||
result.add cli.cmd
|
|
||||||
result.add "\n"
|
|
||||||
if cli.usage != "":
|
|
||||||
result.add "\n"
|
|
||||||
result.add "[" & cli.styles.hdr & "]"
|
|
||||||
result.add "usage[/]:\n"
|
|
||||||
result.add indent(cli.usage, 2 )
|
|
||||||
result.add "\n"
|
|
||||||
if cli.flags.len > 0:
|
|
||||||
result.add "\n"
|
|
||||||
result.add "[" & cli.styles.hdr & "]"
|
|
||||||
result.add "flags[/]:\n"
|
|
||||||
for f in cli.flags:
|
|
||||||
result.add flagHelp(cli,f)
|
|
||||||
|
|
||||||
proc bb*(cli: HwylCli): BbString =
|
|
||||||
result = bb(bbImpl(cli))
|
|
||||||
|
|
||||||
proc `$`*(cli: HwylCli): string =
|
|
||||||
result = $bb(cli)
|
|
768
src/hwylterm/hwylcli.nim
Normal file
768
src/hwylterm/hwylcli.nim
Normal file
|
@ -0,0 +1,768 @@
|
||||||
|
##[
|
||||||
|
# HwylCli
|
||||||
|
]##
|
||||||
|
|
||||||
|
import std/[
|
||||||
|
macros, os, sequtils,
|
||||||
|
sets, strutils, tables,
|
||||||
|
sugar
|
||||||
|
]
|
||||||
|
import ./[bbansi, parseopt3]
|
||||||
|
export parseopt3
|
||||||
|
|
||||||
|
type
|
||||||
|
HwylFlagHelp = tuple
|
||||||
|
short, long, description: string
|
||||||
|
HwylSubCmdHelp = tuple
|
||||||
|
name, desc: string
|
||||||
|
HwylCliStyles* = object
|
||||||
|
hdr = "bold cyan"
|
||||||
|
shortFlag = "yellow"
|
||||||
|
longFlag = "magenta"
|
||||||
|
descFlag = ""
|
||||||
|
cmd = "bold"
|
||||||
|
HwylCliHelp* = object
|
||||||
|
cmd*: string
|
||||||
|
usage*: string
|
||||||
|
desc*: string
|
||||||
|
subcmds: seq[HwylSubCmdHelp]
|
||||||
|
flags*: seq[HwylFlagHelp]
|
||||||
|
styles*: HwylCliStyles
|
||||||
|
subcmdLen, subcmdDescLen, shortArgLen, longArgLen, descArgLen: int
|
||||||
|
|
||||||
|
|
||||||
|
func newHwylCliHelp*(
|
||||||
|
cmd = "",
|
||||||
|
usage = "",
|
||||||
|
desc = "",
|
||||||
|
subcmds: openArray[HwylSubCmdHelp] = @[],
|
||||||
|
flags: openArray[HwylFlagHelp] = @[],
|
||||||
|
styles = HwylCliStyles()
|
||||||
|
): HwylCliHelp =
|
||||||
|
result.cmd = cmd
|
||||||
|
result.desc = dedent(desc).strip()
|
||||||
|
result.subcmds = subcmds.mapIt((it.name,it.desc.splitlines()[0]))
|
||||||
|
result.usage = dedent(usage).strip()
|
||||||
|
result.flags = @flags
|
||||||
|
result.styles = styles
|
||||||
|
# TODO: incorporate into "styles?"
|
||||||
|
result.subcmdLen = 8
|
||||||
|
for f in flags:
|
||||||
|
result.shortArgLen = max(result.shortArgLen, f.short.len)
|
||||||
|
result.longArgLen = max(result.longArgLen, f.long.len)
|
||||||
|
result.descArgLen = max(result.descArgLen, f.description.len)
|
||||||
|
for s in subcmds:
|
||||||
|
result.subcmdLen = max(result.subcmdLen, s.name.len)
|
||||||
|
result.subcmdDescLen = max(result.subcmdDescLen, s.desc.len)
|
||||||
|
|
||||||
|
func flagHelp(cli: HwylCliHelp, f: HwylFlagHelp): string =
|
||||||
|
result.add " "
|
||||||
|
if f.short != "":
|
||||||
|
result.add "[" & cli.styles.shortFlag & "]"
|
||||||
|
result.add "-" & f.short.alignLeft(cli.shortArgLen)
|
||||||
|
result.add "[/]"
|
||||||
|
else:
|
||||||
|
result.add " ".repeat(1 + cli.shortArgLen)
|
||||||
|
|
||||||
|
result.add " "
|
||||||
|
if f.long != "":
|
||||||
|
result.add "[" & cli.styles.longFlag & "]"
|
||||||
|
result.add "--" & f.long.alignLeft(cli.longArgLen)
|
||||||
|
result.add "[/]"
|
||||||
|
else:
|
||||||
|
result.add " ".repeat(2 + cli.longArgLen)
|
||||||
|
|
||||||
|
result.add " "
|
||||||
|
|
||||||
|
if f.description != "":
|
||||||
|
result.add "[" & cli.styles.descFlag & "]"
|
||||||
|
result.add f.description
|
||||||
|
result.add "[/]"
|
||||||
|
result.add "\n"
|
||||||
|
|
||||||
|
func subCmdLine(cli: HwylCliHelp, subcmd: HwylSubCmdHelp): string =
|
||||||
|
# NOTE: set some minimum for the subcmdlen?
|
||||||
|
result.add " "
|
||||||
|
result.add "[" & cli.styles.cmd & "]"
|
||||||
|
result.add subcmd.name.alignLeft(cli.subcmdLen)
|
||||||
|
result.add "[/]"
|
||||||
|
result.add " "
|
||||||
|
result.add subcmd.desc.alignLeft(cli.subcmdDescLen)
|
||||||
|
result.add "\n"
|
||||||
|
|
||||||
|
proc bbImpl(cli: HwylCliHelp): string =
|
||||||
|
if cli.cmd != "":
|
||||||
|
result.add cli.cmd
|
||||||
|
result.add "\n"
|
||||||
|
if cli.usage != "":
|
||||||
|
result.add "\n"
|
||||||
|
result.add "[" & cli.styles.hdr & "]"
|
||||||
|
result.add "usage[/]:\n"
|
||||||
|
result.add indent(cli.usage, 2 )
|
||||||
|
if cli.desc != "":
|
||||||
|
result.add "\n\n"
|
||||||
|
result.add cli.desc
|
||||||
|
result.add "\n"
|
||||||
|
if cli.subcmds.len > 0:
|
||||||
|
result.add "\n"
|
||||||
|
result.add "[" & cli.styles.hdr & "]"
|
||||||
|
result.add "subcommands[/]:\n"
|
||||||
|
for s in cli.subcmds:
|
||||||
|
result.add cli.subcmdLine(s)
|
||||||
|
if cli.flags.len > 0:
|
||||||
|
result.add "\n"
|
||||||
|
result.add "[" & cli.styles.hdr & "]"
|
||||||
|
result.add "flags[/]:\n"
|
||||||
|
for f in cli.flags:
|
||||||
|
# NOTE: added to accomate dumb macro below
|
||||||
|
# if f != ("","",""):
|
||||||
|
result.add flagHelp(cli,f)
|
||||||
|
|
||||||
|
proc bb*(cli: HwylCliHelp): BbString =
|
||||||
|
result = bb(bbImpl(cli))
|
||||||
|
|
||||||
|
proc `$`*(cli: HwylCliHelp): string =
|
||||||
|
result = $bb(cli)
|
||||||
|
|
||||||
|
# ---------------------------
|
||||||
|
# const supportedbaseTypes = ["string", "bool", "int"]
|
||||||
|
# const supportedSeqTypes = ["string", "int"]
|
||||||
|
# #
|
||||||
|
# proc getObjectFieldTypes(x: NimNode): Table[string, string] =
|
||||||
|
# let impl = getType(x).getTypeImpl()
|
||||||
|
# for f in impl[2]:
|
||||||
|
# let name = f[0].strVal
|
||||||
|
# template bail = error "unsupported field type for: " & name
|
||||||
|
# case f[1].kind
|
||||||
|
# of nnkSym:
|
||||||
|
# let typeSym = f[1].strVal
|
||||||
|
# if typeSym notin supportedbaseTypes: bail
|
||||||
|
# result[name] = typeSym
|
||||||
|
# of nnkBracketExpr:
|
||||||
|
# if f[1].len > 2: bail
|
||||||
|
# if f[1][0].strVal notin "seq": bail
|
||||||
|
# let typeSym = f[1][1].strVal
|
||||||
|
# if typeSym notin supportedSeqTypes: bail
|
||||||
|
# result[name] = f[1][0].strVal & "[" & f[1][1].strVal & "]"
|
||||||
|
# else: bail
|
||||||
|
|
||||||
|
type
|
||||||
|
CliSetting = enum
|
||||||
|
NoHelpFlag, NoArgsShowHelp
|
||||||
|
CliFlag = object
|
||||||
|
name*: string
|
||||||
|
ident*: string
|
||||||
|
default*: NimNode
|
||||||
|
typeSym*: string
|
||||||
|
short*: char
|
||||||
|
long*: string
|
||||||
|
help*: NimNode
|
||||||
|
CliCfg = object
|
||||||
|
stopWords*: seq[string]
|
||||||
|
styles: NimNode
|
||||||
|
hidden*: seq[string]
|
||||||
|
subcommands: seq[CliCfg]
|
||||||
|
settings*: set[CliSetting]
|
||||||
|
run*: NimNode
|
||||||
|
desc*: NimNode
|
||||||
|
name*: string
|
||||||
|
subName*: string # used for help the generator
|
||||||
|
version*, usage*: NimNode
|
||||||
|
flags*: seq[CliFlag]
|
||||||
|
required*: seq[string]
|
||||||
|
globalFlags*: seq[CliFlag]
|
||||||
|
|
||||||
|
func `?`(n: NimNode) =
|
||||||
|
## for debugging macros
|
||||||
|
debugEcho treeRepr n
|
||||||
|
|
||||||
|
# TODO: do i need this?
|
||||||
|
func newCliFlag(): CliFlag =
|
||||||
|
result.help = newLit("")
|
||||||
|
|
||||||
|
template badNode =
|
||||||
|
error "unexpected node kind: " & $node.kind
|
||||||
|
|
||||||
|
func typeSymFromNode(node: NimNode): string =
|
||||||
|
case node.kind
|
||||||
|
of nnkIdent, nnkStrLit:
|
||||||
|
result = node.strVal
|
||||||
|
of nnkBracketExpr:
|
||||||
|
result = node[0].strVal & "[" & node[1].strVal & "]"
|
||||||
|
else: badNode
|
||||||
|
|
||||||
|
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:
|
||||||
|
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:
|
||||||
|
case optOpt[0].strVal
|
||||||
|
of "help","?":
|
||||||
|
opt.help = getOptOptNode(optOpt[1])
|
||||||
|
of "short", "-":
|
||||||
|
let val = getOptOptNode(optOpt).strVal
|
||||||
|
if val.len > 1:
|
||||||
|
error "short flag must be a char"
|
||||||
|
opt.short = val[0].char
|
||||||
|
of "*", "default":
|
||||||
|
opt.default = getOptOptNode(optOpt)
|
||||||
|
of "i", "ident":
|
||||||
|
opt.ident = getOptOptNode(optOpt).strVal
|
||||||
|
of "T":
|
||||||
|
opt.typeSym = getOptTypeSym(optOpt)
|
||||||
|
else:
|
||||||
|
error "unexpected option setting: " & optOpt[0].strVal
|
||||||
|
else:
|
||||||
|
error "unexpected option node type: " & $optOpt.kind
|
||||||
|
|
||||||
|
func startFlag(f: var CliFlag, n: NimNode) =
|
||||||
|
f.name =
|
||||||
|
case n[0].kind
|
||||||
|
of nnkIdent, nnkStrLit: n[0].strVal
|
||||||
|
of nnkAccQuoted: collect(for c in n[0]: c.strVal).join("")
|
||||||
|
else: error "unexpected node kind for option"
|
||||||
|
|
||||||
|
# assume a single character is a short flag
|
||||||
|
if f.name.len == 1:
|
||||||
|
f.short = f.name[0].char
|
||||||
|
else:
|
||||||
|
f.long = f.name
|
||||||
|
|
||||||
|
func parseCliFlag(n: NimNode): CliFlag =
|
||||||
|
if n.kind notin [nnkCommand, nnkCall]:
|
||||||
|
error "unexpected node kind: " & $n.kind
|
||||||
|
|
||||||
|
# deduplicate these...
|
||||||
|
result = newCliFlag()
|
||||||
|
startFlag(result, n)
|
||||||
|
# option "some help desc"
|
||||||
|
if n.kind == nnkCommand:
|
||||||
|
result.help = n[1]
|
||||||
|
# option:
|
||||||
|
# help "some help description"
|
||||||
|
else:
|
||||||
|
parseOptOpts(result, n[1])
|
||||||
|
|
||||||
|
if result.ident == "":
|
||||||
|
result.ident = result.name
|
||||||
|
if result.typeSym == "":
|
||||||
|
result.typeSym = "string"
|
||||||
|
|
||||||
|
|
||||||
|
func parseCliFlags(flags: NimNode): seq[CliFlag] =
|
||||||
|
expectKind flags, nnkStmtList
|
||||||
|
for f in flags:
|
||||||
|
result.add parseCliFlag(f)
|
||||||
|
|
||||||
|
func parseCliSetting(s: string): CliSetting =
|
||||||
|
try: parseEnum[CliSetting](s)
|
||||||
|
except: error "unknown cli setting: " & s
|
||||||
|
|
||||||
|
func parseCliSettings(cfg: var CliCfg, node: NimNode) =
|
||||||
|
case node.kind
|
||||||
|
of nnkCommand:
|
||||||
|
for n in node[1..^1]:
|
||||||
|
cfg.settings.incl parseCliSetting(n.strVal)
|
||||||
|
of nnkCall:
|
||||||
|
expectKind node[1], nnkStmtList
|
||||||
|
for n in node[1]:
|
||||||
|
cfg.settings.incl parseCliSetting(n.strVal)
|
||||||
|
else: assert false
|
||||||
|
|
||||||
|
func parseIdentLikeList(node: NimNode): seq[string] =
|
||||||
|
template check =
|
||||||
|
if n.kind notin [nnkStrLit,nnkIdent]:
|
||||||
|
error "expected StrLit or Ident, got:" & $n.kind
|
||||||
|
case node.kind
|
||||||
|
of nnkCommand:
|
||||||
|
for n in node[1..^1]:
|
||||||
|
check
|
||||||
|
result.add n.strVal
|
||||||
|
of nnkCall:
|
||||||
|
expectKind node[1], nnkStmtList
|
||||||
|
for n in node[1]:
|
||||||
|
check
|
||||||
|
result.add n.strVal
|
||||||
|
else: assert false
|
||||||
|
|
||||||
|
func parseCliBody(body: NimNode, name: string = ""): CliCfg
|
||||||
|
|
||||||
|
func isSubMarker(node: NimNode): bool =
|
||||||
|
if node.kind == nnkPrefix:
|
||||||
|
if eqIdent(node[0], "---"):
|
||||||
|
result = true
|
||||||
|
|
||||||
|
func sliceStmts(node: NimNode): seq[
|
||||||
|
tuple[name: string, slice: Slice[int]]
|
||||||
|
] =
|
||||||
|
|
||||||
|
if not isSubMarker(node[0]):
|
||||||
|
error "expected a subcommand delimiting line"
|
||||||
|
|
||||||
|
var
|
||||||
|
name: string = node[0][1].strVal
|
||||||
|
start = 1
|
||||||
|
let nodeLen = node.len()
|
||||||
|
|
||||||
|
for i in 1..<nodeLen:
|
||||||
|
if i == nodeLen - 1:
|
||||||
|
result.add (name, start..i)
|
||||||
|
elif isSubMarker(node[i]):
|
||||||
|
result.add (name, start..(i - 1))
|
||||||
|
name = node[i][1].strVal
|
||||||
|
start = i + 1
|
||||||
|
|
||||||
|
|
||||||
|
func addGlobalFlagsFrom(child: var CliCfg, parent: CliCfg) =
|
||||||
|
let names = child.flags.mapIt(it.name)
|
||||||
|
for f in parent.globalFlags:
|
||||||
|
if f.name in names:
|
||||||
|
error "global flag " & f.name & " conflicts with command flag"
|
||||||
|
child.flags.add f
|
||||||
|
|
||||||
|
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
|
||||||
|
subCfg.addGlobalFlagsFrom(cfg)
|
||||||
|
|
||||||
|
cfg.subcommands.add subCfg
|
||||||
|
|
||||||
|
func parseHiddenFlags(cfg: var CliCfg, node: NimNode) =
|
||||||
|
template check =
|
||||||
|
if n.kind notin [nnkStrLit, nnkIdent]:
|
||||||
|
error "expected string literal or ident"
|
||||||
|
case node.kind
|
||||||
|
of nnkCommand:
|
||||||
|
for n in node[1..^1]:
|
||||||
|
check
|
||||||
|
cfg.hidden.add n.strVal
|
||||||
|
of nnkCall:
|
||||||
|
expectKind node[1], nnkStmtList
|
||||||
|
for n in node[1]:
|
||||||
|
check
|
||||||
|
cfg.hidden.add n.strVal
|
||||||
|
else: assert false
|
||||||
|
|
||||||
|
func parseCliBody(body: NimNode, name = ""): CliCfg =
|
||||||
|
result.name = name
|
||||||
|
for call in body:
|
||||||
|
if call.kind notin [nnkCall, nnkCommand, nnkPrefix]:
|
||||||
|
error "unexpected node kind: " & $call.kind
|
||||||
|
let name = call[0].strVal
|
||||||
|
case name:
|
||||||
|
of "name":
|
||||||
|
expectKind call[1], nnkStrLit
|
||||||
|
result.name = call[1].strVal
|
||||||
|
of "version", "V":
|
||||||
|
result.version = call[1]
|
||||||
|
of "usage", "?":
|
||||||
|
result.usage = call[1]
|
||||||
|
of "description", "...":
|
||||||
|
result.desc = call[1]
|
||||||
|
of "globalFlags":
|
||||||
|
result.globalFlags = parseCliFlags(call[1])
|
||||||
|
of "flags":
|
||||||
|
result.flags = parseCliFlags(call[1])
|
||||||
|
of "settings":
|
||||||
|
parseCliSettings(result, call)
|
||||||
|
of "stopWords":
|
||||||
|
result.stopWords = parseIdentLikeList(call)
|
||||||
|
of "subcommands":
|
||||||
|
parseCliSubcommands(result, call)
|
||||||
|
of "hidden":
|
||||||
|
parseHiddenFlags(result, call)
|
||||||
|
of "run":
|
||||||
|
result.run = call[1]
|
||||||
|
of "styles":
|
||||||
|
result.styles = call[1]
|
||||||
|
of "required":
|
||||||
|
result.required = parseIdentLikeList(call)
|
||||||
|
else:
|
||||||
|
error "unknown hwylCli setting: " & name
|
||||||
|
|
||||||
|
result.addGlobalFlagsFrom(result)
|
||||||
|
|
||||||
|
if result.name == "":
|
||||||
|
error "missing required option: name"
|
||||||
|
|
||||||
|
func flagsArray(cfg: CliCfg): NimNode =
|
||||||
|
result = newTree(nnkBracket)
|
||||||
|
|
||||||
|
for f in cfg.flags:
|
||||||
|
if f.name in cfg.hidden: continue
|
||||||
|
let
|
||||||
|
help = f.help
|
||||||
|
long = newLit(f.long)
|
||||||
|
short =
|
||||||
|
if f.short != '\x00': newLit($f.short)
|
||||||
|
else: newLit("")
|
||||||
|
result.add quote do:
|
||||||
|
(`short`, `long`, `help`)
|
||||||
|
|
||||||
|
if NoHelpFlag notin cfg.settings:
|
||||||
|
result.add quote do:
|
||||||
|
("h", "help", "show this help")
|
||||||
|
|
||||||
|
if cfg.version != nil:
|
||||||
|
result.add quote do:
|
||||||
|
("v", "version", "print version")
|
||||||
|
|
||||||
|
func subCmdsArray(cfg: CliCfg): NimNode =
|
||||||
|
result = newTree(nnkBracket)
|
||||||
|
for s in cfg.subcommands:
|
||||||
|
let cmd = newLit(s.subName)
|
||||||
|
let desc = s.desc
|
||||||
|
result.add quote do:
|
||||||
|
(`cmd`, `desc`)
|
||||||
|
|
||||||
|
|
||||||
|
func defaultUsage(cfg: CliCfg): NimNode =
|
||||||
|
newLit("[b]" & cfg.name & "[/]" & " [[[faint]-h[/]]")
|
||||||
|
|
||||||
|
func generateCliHelperProc(cfg: CliCfg, printHelpName: NimNode): NimNode =
|
||||||
|
let
|
||||||
|
name = newLit(cfg.name)
|
||||||
|
desc = cfg.desc or newLit("")
|
||||||
|
usage = cfg.usage or defaultUsage(cfg)
|
||||||
|
helpFlags = cfg.flagsArray()
|
||||||
|
subcmds = cfg.subCmdsArray()
|
||||||
|
styles = cfg.styles or (quote do: HwylCliStyles())
|
||||||
|
|
||||||
|
result = quote do:
|
||||||
|
proc `printHelpName`() =
|
||||||
|
echo newHwylCliHelp(
|
||||||
|
cmd = `name`,
|
||||||
|
desc = `desc`,
|
||||||
|
usage = `usage`,
|
||||||
|
subcmds = `subcmds`,
|
||||||
|
flags = `helpFlags`,
|
||||||
|
styles = `styles`,
|
||||||
|
)
|
||||||
|
|
||||||
|
# NOTE: is there a better way to do this?
|
||||||
|
proc checkVarSet[T](name: string, target: T) =
|
||||||
|
var default: T
|
||||||
|
if target == default:
|
||||||
|
quit($bb("[red]error[/]: missing required flag: [b]" & name))
|
||||||
|
|
||||||
|
proc checkDefaultExists[T](target: T, key: string, val: string) =
|
||||||
|
var default: T
|
||||||
|
if target == default and val == "":
|
||||||
|
quit($bb("[red]error[/]: expected value for: [b]" & key))
|
||||||
|
|
||||||
|
proc tryParseInt(key: string, val: string): int =
|
||||||
|
try:
|
||||||
|
result = parseInt(val)
|
||||||
|
except:
|
||||||
|
quit($bb("[red]error[/]: failed to parse value for [b]" & key & "[/] as integer: [b]" & val))
|
||||||
|
|
||||||
|
func addOrOverwrite[T](target: var seq[T], default: seq[T], val: T) =
|
||||||
|
if target != default:
|
||||||
|
target.add val
|
||||||
|
else:
|
||||||
|
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 =
|
||||||
|
var caseStmt = nnkCaseStmt.newTree(ident("key"))
|
||||||
|
caseStmt.add nnkOfBranch.newTree(newLit(""), quote do: quit "empty flag not supported currently")
|
||||||
|
|
||||||
|
if NoHelpFlag notin cfg.settings:
|
||||||
|
caseStmt.add nnkOfBranch.newTree(
|
||||||
|
newLit("h"), newLit("help"),
|
||||||
|
quote do:
|
||||||
|
`printHelpName`(); quit 0
|
||||||
|
)
|
||||||
|
|
||||||
|
if cfg.version != nil:
|
||||||
|
caseStmt.add nnkOfBranch.newTree(
|
||||||
|
newLit("V"), newLit("version"),
|
||||||
|
quote do:
|
||||||
|
echo `version`; quit 0
|
||||||
|
)
|
||||||
|
|
||||||
|
# add flags
|
||||||
|
for f in cfg.flags:
|
||||||
|
var branch = nnkOfBranch.newTree()
|
||||||
|
if f.long != "": branch.add(newLit(f.long))
|
||||||
|
if f.short != '\x00': branch.add(newLit($f.short))
|
||||||
|
branch.add assignField(f)
|
||||||
|
caseStmt.add branch
|
||||||
|
|
||||||
|
caseStmt.add nnkElse.newTree(quote do: quit "unknown flag: " & key)
|
||||||
|
result = nnkStmtList.newTree(caseStmt)
|
||||||
|
|
||||||
|
|
||||||
|
func getNoVals(cfg: CliCfg): tuple[long: NimNode, short: NimNode] =
|
||||||
|
var long = nnkBracket.newTree()
|
||||||
|
var short = nnkCurly.newTree()
|
||||||
|
|
||||||
|
if NoHelpFlag notin cfg.settings:
|
||||||
|
long.add newLit("help")
|
||||||
|
short.add newLit('h')
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
func setFlagVars(cfg: CliCfg): NimNode =
|
||||||
|
result = nnkVarSection.newTree()
|
||||||
|
# TODO: generalize this better...
|
||||||
|
|
||||||
|
for f in cfg.flags:
|
||||||
|
let
|
||||||
|
t =
|
||||||
|
if f.typeSym == "seq[string]": nnkBracketExpr.newTree(newIdentNode("seq"),newIdentNode("string"))
|
||||||
|
elif f.typeSym == "seq[int]" : nnkBracketExpr.newTree(newIdentNode("seq"),newIdentNode("string"))
|
||||||
|
else: ident(f.typeSym)
|
||||||
|
val =
|
||||||
|
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 =
|
||||||
|
|
||||||
|
let
|
||||||
|
version = cfg.version or newLit("")
|
||||||
|
name = cfg.name.replace(" ", "")
|
||||||
|
printHelpName = ident("print" & name & "Help")
|
||||||
|
parserProcName = ident("parse" & name)
|
||||||
|
|
||||||
|
result = newTree(nnkStmtList)
|
||||||
|
|
||||||
|
let
|
||||||
|
printHelperProc = generateCliHelperProc(cfg, printHelpName)
|
||||||
|
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:
|
||||||
|
stopWords.add newLit(w)
|
||||||
|
stopWords = nnkPrefix.newTree(ident"@", stopWords)
|
||||||
|
|
||||||
|
parserBody.add(
|
||||||
|
quote do:
|
||||||
|
var `optParser` = initOptParser(
|
||||||
|
@`cmdLine`,
|
||||||
|
longNoVal = `longNoVal`,
|
||||||
|
shortNoVal = `shortNoVal`,
|
||||||
|
stopWords = `stopWords`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
let
|
||||||
|
kind = ident"kind"
|
||||||
|
key = ident"key"
|
||||||
|
val = ident"val"
|
||||||
|
|
||||||
|
parserBody.add nnkForStmt.newTree(
|
||||||
|
kind, key, val,
|
||||||
|
nnkCall.newTree(nnkDotExpr.newTree(optParser,ident("getopt"))),
|
||||||
|
nnkStmtList.newTree(
|
||||||
|
# # for debugging..
|
||||||
|
# quote do:
|
||||||
|
# echo `kind`,"|",`key`,"|",`val`
|
||||||
|
# ,
|
||||||
|
nnkCaseStmt.newTree(
|
||||||
|
kind,
|
||||||
|
nnkOfBranch.newTree(ident("cmdError"), quote do: quit($(bb"[red]cli error[/]: " & p.message), 1)),
|
||||||
|
nnkOfBranch.newTree(ident("cmdEnd"), quote do: assert false),
|
||||||
|
# TODO: add nArgs to change how cmdArgument is handled ...
|
||||||
|
nnkOfBranch.newTree(ident("cmdArgument"), quote do: result.add `key`),
|
||||||
|
nnkOfBranch.newTree(
|
||||||
|
ident("cmdShortOption"), ident("cmdLongOption"),
|
||||||
|
shortLongCaseStmt(cfg, printHelpName, version)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if NoArgsShowHelp in cfg.settings:
|
||||||
|
parserBody.add quote do:
|
||||||
|
if commandLineParams().len == 0:
|
||||||
|
`printHelpName`(); quit 1
|
||||||
|
|
||||||
|
let runProcName = ident("run" & name)
|
||||||
|
let runBody = nnkStmtList.newTree()
|
||||||
|
addRequiredFlagsCheck(cfg, runBody)
|
||||||
|
# move to proc?
|
||||||
|
if cfg.run != nil:
|
||||||
|
runBody.add cfg.run
|
||||||
|
|
||||||
|
# let runBody = cfg.run or nnkStmtList.newTree(nnkDiscardStmt.newTree(newEmptyNode()))
|
||||||
|
|
||||||
|
let args = ident"args"
|
||||||
|
|
||||||
|
if cfg.subcommands.len > 0:
|
||||||
|
var handleSubCommands = nnkStmtList.newTree()
|
||||||
|
handleSubCommands.add quote do:
|
||||||
|
if `args`.len == 0:
|
||||||
|
quit "expected subcommand"
|
||||||
|
|
||||||
|
var subCommandCase = nnkCaseStmt.newTree(
|
||||||
|
quote do: `args`[0]
|
||||||
|
)
|
||||||
|
for sub in cfg.subcommands:
|
||||||
|
subCommandCase.add nnkOfBranch.newTree(
|
||||||
|
newLit(sub.subName),
|
||||||
|
hwylCliImpl(sub)
|
||||||
|
)
|
||||||
|
|
||||||
|
subcommandCase.add nnkElse.newTree(
|
||||||
|
quote do:
|
||||||
|
quit $bb"[red]error[/] unknown subcommand: " & `args`[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
runBody.add handleSubCommands.add subCommandCase
|
||||||
|
|
||||||
|
result.add quote do:
|
||||||
|
# block:
|
||||||
|
`printHelperProc`
|
||||||
|
`flagVars`
|
||||||
|
proc `parserProcName`(`cmdLine`: openArray[string] = commandLineParams()): seq[string] =
|
||||||
|
`parserBody`
|
||||||
|
|
||||||
|
proc `runProcName`(`cmdLine`: openArray[string] = commandLineParams()) =
|
||||||
|
let `args` = `parserProcName`(`cmdLine`)
|
||||||
|
`runBody`
|
||||||
|
|
||||||
|
if root:
|
||||||
|
result.add quote do:
|
||||||
|
`runProcName`()
|
||||||
|
else:
|
||||||
|
result.add quote do:
|
||||||
|
`runProcName`(`args`[1..^1])
|
||||||
|
|
||||||
|
macro hwylCli*(body: untyped) =
|
||||||
|
var cfg = parseCliBody(body)
|
||||||
|
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"
|
||||||
|
flags:
|
||||||
|
check:
|
||||||
|
T bool
|
||||||
|
? "load config and exit"
|
||||||
|
- c
|
||||||
|
run:
|
||||||
|
echo "hello from the main command"
|
||||||
|
echo fmt"{config=}, {check=}"
|
||||||
|
subcommands:
|
||||||
|
--- a
|
||||||
|
description "the \"a\" subcommand"
|
||||||
|
flags:
|
||||||
|
`long-flag` "some help"
|
||||||
|
flagg "some other help"
|
||||||
|
run:
|
||||||
|
echo config
|
||||||
|
echo "hello from hwylterm sub command!"
|
||||||
|
echo `long-flag`
|
||||||
|
echo flagg
|
||||||
|
--- b
|
||||||
|
description "the \"b\" subcommand"
|
||||||
|
flags:
|
||||||
|
aflag:
|
||||||
|
T bool
|
||||||
|
? "some help"
|
||||||
|
bflag:
|
||||||
|
? "some other flag?"
|
||||||
|
* "wow"
|
||||||
|
run:
|
||||||
|
echo "hello from hwylterm sub `b` command"
|
||||||
|
echo aflag, bflag
|
||||||
|
|
||||||
|
|
1
todo.md
1
todo.md
|
@ -12,6 +12,7 @@
|
||||||
- [ ] revamp spinner api (new threads?)
|
- [ ] revamp spinner api (new threads?)
|
||||||
- [x] add Bbstring ~~indexing operations~~ strutils, that are span aware
|
- [x] add Bbstring ~~indexing operations~~ strutils, that are span aware
|
||||||
- [ ] 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
|
||||||
|
|
||||||
## features
|
## features
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue