mirror of
https://github.com/daylinmorgan/hwylterm.git
synced 2025-02-23 01:35:50 -06:00
extended flag syntax
This commit is contained in:
parent
2b1c7b607e
commit
f4c45d1ae4
5 changed files with 228 additions and 47 deletions
|
@ -276,19 +276,20 @@ type
|
||||||
func hasSubcommands(c: CliCfg): bool = c.subcommands.len > 0
|
func hasSubcommands(c: CliCfg): bool = c.subcommands.len > 0
|
||||||
|
|
||||||
|
|
||||||
template `<<<`(s: string) {.used.} =
|
# template `<<<`(s: string) {.used.} =
|
||||||
let pos = instantiationInfo()
|
# let pos = instantiationInfo()
|
||||||
debugEcho "$1:$2" % [pos.filename, $pos.line]
|
# debugEcho "$1:$2" % [pos.filename, $pos.line]
|
||||||
debugEcho s
|
# debugEcho s
|
||||||
debugEcho "^^^^^^^^^^^^^^^^^^^^^"
|
|
||||||
|
|
||||||
# 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*
|
||||||
template `<<<`(n: NimNode) {.used.} =
|
template `<<<`(n: NimNode) {.used.} =
|
||||||
## for debugging macros
|
## for debugging macros
|
||||||
<<< treeRepr n
|
<<< ("TreeRepr:\n" & (treeRepr n))
|
||||||
|
<<< ("Repr: \n" & (repr n))
|
||||||
|
|
||||||
|
|
||||||
template `<<<`(n: untyped) {.used.} =
|
template `<<<`(n: untyped) {.used.} =
|
||||||
debugEcho n, "|||", instantiationInfo().line
|
debugEcho n, "....................", instantiationInfo().line
|
||||||
|
|
||||||
func `<<<`(f: CliFlag) {.used.}=
|
func `<<<`(f: CliFlag) {.used.}=
|
||||||
var s: string
|
var s: string
|
||||||
|
@ -301,7 +302,7 @@ func `<<<`(f: CliFlag) {.used.}=
|
||||||
]
|
]
|
||||||
s.add "CliFlag(\n"
|
s.add "CliFlag(\n"
|
||||||
for (k,v) in fields:
|
for (k,v) in fields:
|
||||||
s.add "$1 = $2\n" % [k,v]
|
s.add " $1 = $2\n" % [k,v]
|
||||||
s.add ")"
|
s.add ")"
|
||||||
<<< s
|
<<< s
|
||||||
|
|
||||||
|
@ -321,24 +322,27 @@ func prettyRepr(n: NimNode): string =
|
||||||
result.add "╰"
|
result.add "╰"
|
||||||
result.add "─".repeat(maxWidth + 2)
|
result.add "─".repeat(maxWidth + 2)
|
||||||
|
|
||||||
func err(c: CliCfg, node: NimNode, msg: string = "") =
|
func err(c: CliCfg, node: NimNode, msg: string = "", instantiationInfo: tuple[filename: string, line: int, column: int]) =
|
||||||
var fullMsg: string
|
var fullMsg: string
|
||||||
fullMsg.add node.prettyRepr() & "\n"
|
fullMsg.add node.prettyRepr() & "\n"
|
||||||
fullMsg.add "parsing error"
|
fullMsg.add "parsing error ($1, $2) " % [instantiationInfo.filename, $instantiationInfo.line]
|
||||||
if msg != "":
|
if msg != "":
|
||||||
fullMsg.add ": " & msg
|
fullMsg.add ": " & msg
|
||||||
c.err fullMsg
|
c.err fullMsg
|
||||||
|
|
||||||
func expectLen(c: CliCfg, node: NimNode, length: Natural) =
|
template err(c: CliCfg, node: NimNode, msg: string = "") =
|
||||||
|
err c, node, msg, instantiationInfo()
|
||||||
|
|
||||||
|
template expectLen(c: CliCfg, node: NimNode, length: Natural) =
|
||||||
if node.len != length:
|
if node.len != length:
|
||||||
c.err node, fmt"expected node to be length {length} not {node.len}"
|
c.err node, "expected node to be length $1 not $2" % [$length, $node.len], instantiationInfo()
|
||||||
|
|
||||||
func expectKind(c: CliCfg, node: NimNode, kinds: varargs[NimNodeKind]) =
|
template expectKind(c: CliCfg, node: NimNode, kinds: varargs[NimNodeKind]) =
|
||||||
if node.kind notin kinds:
|
if node.kind notin kinds:
|
||||||
c.err node, fmt"expected node kind to be one of: $1 but got $2" % [$kinds, $node.kind]
|
c.err node, "expected node kind to be one of: $1 but got $2" % [$kinds, $node.kind], instantiationInfo()
|
||||||
|
|
||||||
func unexpectedKind(c: CliCfg, node: NimNode) =
|
template unexpectedKind(c: CliCfg, node: NimNode) =
|
||||||
c.err node, fmt"unexpected node kind: $1" & $node.kind
|
c.err node, "unexpected node kind: $1" % $node.kind, instantiationInfo()
|
||||||
|
|
||||||
|
|
||||||
template parseCliSetting(s: string) =
|
template parseCliSetting(s: string) =
|
||||||
|
@ -389,7 +393,7 @@ func getFlagParamNode(c: CliCfg, node: NimNode): NimNode =
|
||||||
else: c.unexpectedKind node
|
else: c.unexpectedKind node
|
||||||
|
|
||||||
# TODO: also accept the form `flag: "help"`
|
# TODO: also accept the form `flag: "help"`
|
||||||
func parseFlagParams(c: CliCfg, f: var CliFlag, node: NimNode) =
|
func parseFlagStmtList(c: CliCfg, f: var CliFlag, node: NimNode) =
|
||||||
c.expectKind node, nnkStmtList
|
c.expectKind node, nnkStmtList
|
||||||
for n in node:
|
for n in node:
|
||||||
case n.kind
|
case n.kind
|
||||||
|
@ -418,41 +422,148 @@ func parseFlagParams(c: CliCfg, f: var CliFlag, node: NimNode) =
|
||||||
else:
|
else:
|
||||||
c.unexpectedKind n
|
c.unexpectedKind n
|
||||||
|
|
||||||
func newFlag(cfg: var CliCfg , n: NimNode): CliFlag =
|
func getShortChar(c: CliCfg, n: NimNode): char =
|
||||||
cfg.expectKind n[0], nnkIdent, nnkStrLit, nnkAccQuoted
|
let val = n.strVal
|
||||||
|
if val.len > 1:
|
||||||
|
c.err n, "short flag must be a char not: " & val
|
||||||
|
result = val[0].char
|
||||||
|
|
||||||
case n[0].kind:
|
func parseCliFlagCall(c: var CliCfg, f: var CliFlag, nodes: seq[NimNode]) =
|
||||||
|
|
||||||
|
# TODO: be more careful here?
|
||||||
|
# TODO: ignore positionals that are ident'_'
|
||||||
|
template `<-`(target: untyped, node: NimNode) =
|
||||||
|
if node != ident"_":
|
||||||
|
target = node
|
||||||
|
|
||||||
|
case nodes.len:
|
||||||
|
|
||||||
|
# flag("help string")
|
||||||
|
of 1:
|
||||||
|
f.help <- nodes[0]
|
||||||
|
|
||||||
|
# flag(T, "help string")
|
||||||
|
of 2:
|
||||||
|
f.typeNode <- nodes[0]
|
||||||
|
f.help <- nodes[1]
|
||||||
|
|
||||||
|
# flag(NimNode , T , "help string")
|
||||||
|
of 3:
|
||||||
|
f.defaultVal <- nodes[0]
|
||||||
|
f.typeNode <- nodes[1]
|
||||||
|
f.help <- nodes[2]
|
||||||
|
|
||||||
|
else:
|
||||||
|
c.err "unexpected number of parameters for flag"
|
||||||
|
|
||||||
|
func newFlag(cfg: var CliCfg , n: NimNode): CliFlag =
|
||||||
|
cfg.expectKind n, nnkIdent, nnkStrLit, nnkAccQuoted
|
||||||
|
|
||||||
|
case n.kind:
|
||||||
of nnkIdent, nnkStrLit:
|
of nnkIdent, nnkStrLit:
|
||||||
result.name = n[0].strVal
|
result.name = n.strVal
|
||||||
of nnkAccQuoted:
|
of nnkAccQuoted:
|
||||||
result.name = collect(for c in n[0]: c.strVal).join("")
|
result.name = collect(for c in n: c.strVal).join("")
|
||||||
else: cfg.unexpectedKind n[0]
|
else: cfg.unexpectedKind n
|
||||||
|
|
||||||
result.help = newLit("") # by default no string
|
result.help = newLit("") # by default no string
|
||||||
|
|
||||||
# assume a single character is a short flag
|
# assume a single character is a short flag
|
||||||
if result.name.len == 1:
|
if result.name.len == 1:
|
||||||
result.short = result.name[0].char
|
result.short = result.name[0].char
|
||||||
else:
|
else:
|
||||||
result.long = result.name
|
result.long = result.name
|
||||||
|
|
||||||
|
type FlagKind = enum
|
||||||
|
Command ## flag "help"
|
||||||
|
InfixCommand ## a | aflag "help"
|
||||||
|
InfixCall ## a | aflag("help")
|
||||||
|
InfixStmt ## a | aflag: ? "help"
|
||||||
|
InfixCallStmt ## c | count ("some number"): * 5
|
||||||
|
Stmt ## count: * 5
|
||||||
|
CallStmt ## count("help"): * 5
|
||||||
|
Call ## count(5, int, "help")
|
||||||
|
|
||||||
func parseCliFlag(c: var CliCfg, n: NimNode, group: string) =
|
|
||||||
|
|
||||||
c.expectKind n, [nnkCommand, nnkCall]
|
func toFlagKind(c: CliCfg, n: NimNode): FlagKind =
|
||||||
var f = c.newFlag n
|
case n.kind:
|
||||||
|
of nnkInfix:
|
||||||
|
case n[^1].kind
|
||||||
|
of nnkCommand:
|
||||||
|
result = InfixCommand
|
||||||
|
of nnkStmtList:
|
||||||
|
case n[2].kind
|
||||||
|
of nnkIdent:
|
||||||
|
result = InfixStmt
|
||||||
|
of nnkCall:
|
||||||
|
result = InfixCallStmt
|
||||||
|
else: c.err n, "failed to determine flag kind"
|
||||||
|
of nnkCall:
|
||||||
|
result = InfixCall
|
||||||
|
else:
|
||||||
|
c.err n, "failed to determine flag kind with short flag"
|
||||||
|
of nnkCall:
|
||||||
|
if n.len == 2 and n[1].kind == nnkStmtList:
|
||||||
|
result = Stmt
|
||||||
|
elif n[^1].kind == nnkStmtList:
|
||||||
|
result = CallStmt
|
||||||
|
else:
|
||||||
|
result = Call
|
||||||
|
of nnkCommand:
|
||||||
|
result = Command
|
||||||
|
else:
|
||||||
|
c.unexpectedKind n
|
||||||
|
|
||||||
# option "some help desc"
|
func parseCliFlag(
|
||||||
if n.kind == nnkCommand:
|
c: var CliCfg,
|
||||||
|
n: NimNode,
|
||||||
|
group: string,
|
||||||
|
short: char = '\x00'
|
||||||
|
) =
|
||||||
|
var f : CliFlag
|
||||||
|
|
||||||
|
|
||||||
|
let flagKind = c.toFlagKind n
|
||||||
|
|
||||||
|
case flagKind
|
||||||
|
of Stmt:
|
||||||
|
f = c.newFlag n[0]
|
||||||
|
parseFlagStmtList c, f, n[1]
|
||||||
|
|
||||||
|
of InfixCommand:
|
||||||
|
f = c.newFlag n[2][0]
|
||||||
|
f.help = n[2][1]
|
||||||
|
|
||||||
|
of InfixStmt:
|
||||||
|
f = c.newFlag n[2]
|
||||||
|
parseFlagStmtList c, f, n[^1]
|
||||||
|
|
||||||
|
of InfixCallStmt:
|
||||||
|
f = c.newFlag n[2][0]
|
||||||
|
parseCliFlagCall c, f, n[^2][1..^1]
|
||||||
|
parseFlagStmtList c, f, n[^1]
|
||||||
|
|
||||||
|
of Call:
|
||||||
|
f = c.newFlag n[0]
|
||||||
|
parseCliFlagCall c, f, n[1..^1]
|
||||||
|
|
||||||
|
of CallStmt:
|
||||||
|
f = c.newFlag n[0]
|
||||||
|
parseCliFlagCall c, f, n[1..^2]
|
||||||
|
parseFlagStmtList c, f, n[^1]
|
||||||
|
|
||||||
|
of InfixCall:
|
||||||
|
f = c.newFlag n[2][0]
|
||||||
|
parseCliFlagCall c, f, n[^1][1..^1]
|
||||||
|
|
||||||
|
of Command:
|
||||||
|
f = c.newFlag n[0]
|
||||||
f.help = n[1]
|
f.help = n[1]
|
||||||
|
|
||||||
# option:
|
if short != '\x00':
|
||||||
# T string
|
f.short = short
|
||||||
# help "some help description"
|
|
||||||
else:
|
|
||||||
parseFlagParams c, f, n[1]
|
|
||||||
|
|
||||||
f.ident = f.ident or f.name.ident
|
f.ident = f.ident or f.name.ident
|
||||||
|
|
||||||
f.group = group
|
f.group = group
|
||||||
c.flagDefs.add f
|
c.flagDefs.add f
|
||||||
|
|
||||||
|
@ -492,15 +603,8 @@ func parseCliFlags(cfg: var CliCfg, node: NimNode) =
|
||||||
var group: string
|
var group: string
|
||||||
cfg.expectKind node, nnkStmtList
|
cfg.expectKind node, nnkStmtList
|
||||||
for n in node:
|
for n in node:
|
||||||
case n.kind
|
|
||||||
# flags:
|
|
||||||
# input "some input"
|
|
||||||
# count:
|
|
||||||
# T int
|
|
||||||
# ? "a number"
|
|
||||||
of nnkCall, nnkCommand:
|
|
||||||
cfg.parseCliFlag n, group
|
|
||||||
|
|
||||||
|
case n.kind
|
||||||
# start a new flag group
|
# start a new flag group
|
||||||
# flags:
|
# flags:
|
||||||
# [category]
|
# [category]
|
||||||
|
@ -516,7 +620,6 @@ func parseCliFlags(cfg: var CliCfg, node: NimNode) =
|
||||||
of nnkPrefix:
|
of nnkPrefix:
|
||||||
if
|
if
|
||||||
n[0].kind != nnkIdent or
|
n[0].kind != nnkIdent or
|
||||||
n[0].strVal != "^" or
|
|
||||||
n.len != 2 or
|
n.len != 2 or
|
||||||
n[1].kind notin [nnkBracket, nnkIdent, nnkStrLit]:
|
n[1].kind notin [nnkBracket, nnkIdent, nnkStrLit]:
|
||||||
cfg.err n, "unable to determine inherited flag/group"
|
cfg.err n, "unable to determine inherited flag/group"
|
||||||
|
@ -527,6 +630,29 @@ func parseCliFlags(cfg: var CliCfg, node: NimNode) =
|
||||||
of nnkIdent, nnkStrLit:
|
of nnkIdent, nnkStrLit:
|
||||||
cfg.inherit.flags.add n[1].strval
|
cfg.inherit.flags.add n[1].strval
|
||||||
else: cfg.unexpectedKind n
|
else: cfg.unexpectedKind n
|
||||||
|
|
||||||
|
# flags:
|
||||||
|
# input "some input"
|
||||||
|
# count:
|
||||||
|
# T int
|
||||||
|
# ? "a number"
|
||||||
|
of nnkCall, nnkCommand:
|
||||||
|
cfg.expectKind n, [nnkCommand, nnkCall]
|
||||||
|
cfg.parseCliFlag n, group
|
||||||
|
|
||||||
|
# flags:
|
||||||
|
# l | `long-flag` "flag with short using infix"
|
||||||
|
# n | `dry-run`("set dry-run"):
|
||||||
|
# ident dry
|
||||||
|
of nnkInfix:
|
||||||
|
cfg.expectKind n[0], nnkIdent
|
||||||
|
if n[0].strVal != "|":
|
||||||
|
cfg.err n, "unexpected infix operator in flags"
|
||||||
|
cfg.expectKind n[2], nnkCall, nnkCommand, nnkAccQuoted, nnkIdent
|
||||||
|
|
||||||
|
# need to make sure that this node getting passed here is stmt?
|
||||||
|
cfg.parseCliFlag n, group, cfg.getShortChar(n[1])
|
||||||
|
|
||||||
else: cfg.unexpectedKind n
|
else: cfg.unexpectedKind n
|
||||||
|
|
||||||
|
|
||||||
|
@ -701,7 +827,8 @@ func postPropagate(c: var CliCfg) =
|
||||||
else:
|
else:
|
||||||
short[f.short] = f
|
short[f.short] = f
|
||||||
|
|
||||||
if f.long in long:
|
if f.long == "": discard
|
||||||
|
elif f.long in long:
|
||||||
let conflict = long[f.long]
|
let conflict = long[f.long]
|
||||||
c.err "conflicting long flags for: " & f.name & " and " & conflict.name
|
c.err "conflicting long flags for: " & f.name & " and " & conflict.name
|
||||||
else:
|
else:
|
||||||
|
|
21
tests/cli/clis/allFlagKinds.nim
Normal file
21
tests/cli/clis/allFlagKinds.nim
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import std/strformat
|
||||||
|
import hwylterm, hwylterm/hwylcli
|
||||||
|
|
||||||
|
hwylCli:
|
||||||
|
name "flag-kinds"
|
||||||
|
flags:
|
||||||
|
a "kind: Command"
|
||||||
|
b | bbbb "kind: InfixCommand"
|
||||||
|
cccc:
|
||||||
|
? "kind: Stmt"
|
||||||
|
d | dddd:
|
||||||
|
? "kind: InfixStmt"
|
||||||
|
e(string, "kind: Call")
|
||||||
|
f | ffff("kind: InfixCallStmt"):
|
||||||
|
ident notffff
|
||||||
|
gggg(string, "kind: CallStmt"):
|
||||||
|
* "default"
|
||||||
|
h | hhhh("kind: InfixCall")
|
||||||
|
run:
|
||||||
|
echo fmt"{a=}, {bbbb=}, {cccc=}, {dddd=}"
|
||||||
|
echo fmt"{e=}, {notffff=}, {gggg=}, {hhhh=}"
|
9
tests/cli/clis/multiShortFlags.nim
Normal file
9
tests/cli/clis/multiShortFlags.nim
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import hwylterm, hwylterm/hwylcli
|
||||||
|
|
||||||
|
hwylCli:
|
||||||
|
name "multiple-short-flags"
|
||||||
|
flags:
|
||||||
|
a "first short"
|
||||||
|
b "second short"
|
||||||
|
run:
|
||||||
|
echo a, b
|
|
@ -1,4 +1,4 @@
|
||||||
import std/[compilesettings, os, osproc, strutils, times, unittest, terminal]
|
import std/[os, osproc, strutils, times, unittest, terminal]
|
||||||
|
|
||||||
const pathToSrc = currentSourcePath().parentDir()
|
const pathToSrc = currentSourcePath().parentDir()
|
||||||
const binDir = pathToSrc / "bin"
|
const binDir = pathToSrc / "bin"
|
||||||
|
|
|
@ -27,6 +27,30 @@ suite "flags":
|
||||||
"error failed to parse value for color as enum: black expected one of: red,blue,green",
|
"error failed to parse value for color as enum: black expected one of: red,blue,green",
|
||||||
)
|
)
|
||||||
okWithArgs("flagKey", "--key", "key set")
|
okWithArgs("flagKey", "--key", "key set")
|
||||||
|
okWithArgs("multiShortFlags", "--help","""
|
||||||
|
usage:
|
||||||
|
multiple-short-flags [flags]
|
||||||
|
|
||||||
|
flags:
|
||||||
|
-a first short
|
||||||
|
-b second short
|
||||||
|
-h --help show this help
|
||||||
|
""")
|
||||||
|
okWithArgs("allFlagKinds", "--help", """
|
||||||
|
usage:
|
||||||
|
flag-kinds [flags]
|
||||||
|
|
||||||
|
flags:
|
||||||
|
-a kind: Command
|
||||||
|
-b --bbbb kind: InfixCommand
|
||||||
|
--cccc kind: Stmt
|
||||||
|
-d --dddd kind: InfixStmt
|
||||||
|
-e string kind: Call
|
||||||
|
-f --ffff kind: InfixCallStmt
|
||||||
|
--gggg string kind: CallStmt (default: default)
|
||||||
|
-h --hhhh kind: InfixCall
|
||||||
|
--help show this help
|
||||||
|
""")
|
||||||
|
|
||||||
suite "subcommands":
|
suite "subcommands":
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue