mirror of
https://github.com/daylinmorgan/hwylterm.git
synced 2024-11-16 06:28:32 -06:00
rework default flags
This commit is contained in:
parent
cb231dfc94
commit
d228123195
2 changed files with 153 additions and 147 deletions
|
@ -8,7 +8,7 @@ import std/[
|
||||||
sugar
|
sugar
|
||||||
]
|
]
|
||||||
import ./[bbansi, parseopt3]
|
import ./[bbansi, parseopt3]
|
||||||
export parseopt3
|
export parseopt3, sets
|
||||||
|
|
||||||
type
|
type
|
||||||
HwylFlagHelp* = tuple
|
HwylFlagHelp* = tuple
|
||||||
|
@ -129,9 +129,10 @@ type
|
||||||
node: NimNode
|
node: NimNode
|
||||||
CliFlag = object
|
CliFlag = object
|
||||||
name*: string
|
name*: string
|
||||||
ident*: string
|
ident*: NimNode
|
||||||
default*: NimNode
|
default*: NimNode
|
||||||
typeSym*: string
|
typeSym*: string
|
||||||
|
typeNode*: NimNode
|
||||||
short*: char
|
short*: char
|
||||||
long*: string
|
long*: string
|
||||||
help*: NimNode
|
help*: NimNode
|
||||||
|
@ -164,8 +165,14 @@ func `<<<`(s: string) =
|
||||||
func newCliFlag(): CliFlag =
|
func newCliFlag(): CliFlag =
|
||||||
result.help = newLit("")
|
result.help = newLit("")
|
||||||
|
|
||||||
template badNode =
|
func bad(n: NimNode, argument: string = "") =
|
||||||
error "unexpected node kind: " & $node.kind
|
|
||||||
|
var msg = "unexpected node kind: " & $n.kind
|
||||||
|
if argument != "":
|
||||||
|
msg &= " for argument: " & argument
|
||||||
|
|
||||||
|
# error "unexpected node kind: " & $n.kind
|
||||||
|
error msg
|
||||||
|
|
||||||
func typeSymFromNode(node: NimNode): string =
|
func typeSymFromNode(node: NimNode): string =
|
||||||
case node.kind
|
case node.kind
|
||||||
|
@ -173,7 +180,7 @@ func typeSymFromNode(node: NimNode): string =
|
||||||
result = node.strVal
|
result = node.strVal
|
||||||
of nnkBracketExpr:
|
of nnkBracketExpr:
|
||||||
result = node[0].strVal & "[" & node[1].strVal & "]"
|
result = node[0].strVal & "[" & node[1].strVal & "]"
|
||||||
else: badNode
|
else: bad node
|
||||||
|
|
||||||
func getOptTypeSym(node: NimNode): string =
|
func getOptTypeSym(node: NimNode): string =
|
||||||
case node.kind:
|
case node.kind:
|
||||||
|
@ -212,8 +219,10 @@ func parseOptOpts(opt: var CliFlag, optOpts: NimNode) =
|
||||||
of "*", "default":
|
of "*", "default":
|
||||||
opt.default = getOptOptNode(optOpt)
|
opt.default = getOptOptNode(optOpt)
|
||||||
of "i", "ident":
|
of "i", "ident":
|
||||||
opt.ident = getOptOptNode(optOpt).strVal
|
opt.ident = getOptOptNode(optOpt).strVal.ident
|
||||||
of "T":
|
of "T":
|
||||||
|
opt.typeNode = optOpt[1]
|
||||||
|
# TODO: remove this...
|
||||||
opt.typeSym = getOptTypeSym(optOpt)
|
opt.typeSym = getOptTypeSym(optOpt)
|
||||||
else:
|
else:
|
||||||
error "unexpected option setting: " & optOpt[0].strVal
|
error "unexpected option setting: " & optOpt[0].strVal
|
||||||
|
@ -248,8 +257,10 @@ func parseCliFlag(n: NimNode): CliFlag =
|
||||||
else:
|
else:
|
||||||
parseOptOpts(result, n[1])
|
parseOptOpts(result, n[1])
|
||||||
|
|
||||||
if result.ident == "":
|
if result.ident == nil:
|
||||||
result.ident = result.name
|
result.ident = result.name.ident
|
||||||
|
if result.typeNode == nil:
|
||||||
|
result.typeNode = ident"string"
|
||||||
if result.typeSym == "":
|
if result.typeSym == "":
|
||||||
result.typeSym = "string"
|
result.typeSym = "string"
|
||||||
|
|
||||||
|
@ -499,66 +510,32 @@ func generateCliHelperProc(cfg: CliCfg, printHelpName: NimNode): NimNode =
|
||||||
styles = `styles`,
|
styles = `styles`,
|
||||||
)
|
)
|
||||||
|
|
||||||
# NOTE: is there a better way to do this?
|
proc parse*(p: OptParser, key: string, val: string, target: var bool) =
|
||||||
proc checkVarSet[T](name: string, target: T) =
|
target = true
|
||||||
var default: T
|
|
||||||
if target == default:
|
|
||||||
hwylCliError("missing required flag: [b]" & name)
|
|
||||||
|
|
||||||
proc checkDefaultExists[T](target: T, key: string, val: string) =
|
proc parse*(p: OptParser, key: string, val: string, target: var string) =
|
||||||
var default: T
|
target = val
|
||||||
if target == default and val == "":
|
|
||||||
hwylCliError("expected value for: [b]" & key)
|
|
||||||
|
|
||||||
proc tryParseInt(key: string, val: string): int =
|
proc parse*(p: OptParser, key: string, val: string, target: var int) =
|
||||||
try:
|
try:
|
||||||
result = parseInt(val)
|
target = 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
|
||||||
)
|
)
|
||||||
|
|
||||||
func addOrOverwrite[T](target: var seq[T], default: seq[T], val: T) =
|
proc parse*(p: OptParser, key: string, val: string, target: var float) =
|
||||||
if target != default:
|
try:
|
||||||
target.add val
|
target = parseFloat(val)
|
||||||
else:
|
except:
|
||||||
target = @[val]
|
hwylCliError(
|
||||||
|
"failed to parse value for [b]" & key & "[/] as float: [b]" & val
|
||||||
|
)
|
||||||
|
|
||||||
func assignField(f: CliFlag): NimNode =
|
proc parse[T](p: OptParser, key: string, val: string, target: var seq[T]) =
|
||||||
let key = ident"key"
|
var parsed: T
|
||||||
let varName = ident(f.ident)
|
parse(p, key, val, parsed)
|
||||||
|
target.add parsed
|
||||||
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(ident("key"))
|
var caseStmt = nnkCaseStmt.newTree(ident("key"))
|
||||||
|
@ -571,20 +548,28 @@ func shortLongCaseStmt(cfg: CliCfg, printHelpName: NimNode, version: NimNode): N
|
||||||
branch.add f.node
|
branch.add f.node
|
||||||
caseStmt.add branch
|
caseStmt.add branch
|
||||||
|
|
||||||
|
|
||||||
# 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 != "": branch.add(newLit(f.long))
|
if f.long != "": branch.add(newLit(f.long))
|
||||||
if f.short != '\x00': branch.add(newLit($f.short))
|
if f.short != '\x00': branch.add(newLit($f.short))
|
||||||
branch.add assignField(f)
|
let varName = f.ident
|
||||||
|
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 getNoVals(cfg: CliCfg): tuple[long: NimNode, short: NimNode] =
|
func getNoVals(cfg: CliCfg): tuple[long: NimNode, short: NimNode] =
|
||||||
let boolFlags = cfg.flags.filterIt(it.typeSym == "bool")
|
let boolFlags = cfg.flags.filterIt(it.isBool)
|
||||||
let long =
|
let long =
|
||||||
nnkBracket.newTree(
|
nnkBracket.newTree(
|
||||||
(boolFlags.mapIt(it.long) & cfg.builtinFlags.mapIt(it.long)).filterIt(it != "").mapIt(newLit(it))
|
(boolFlags.mapIt(it.long) & cfg.builtinFlags.mapIt(it.long)).filterIt(it != "").mapIt(newLit(it))
|
||||||
|
@ -596,54 +581,77 @@ func getNoVals(cfg: CliCfg): tuple[long: NimNode, short: NimNode] =
|
||||||
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()
|
result = nnkVarSection.newTree().add(
|
||||||
# TODO: generalize this better...
|
cfg.flags.mapIt(
|
||||||
|
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:
|
||||||
let
|
if f.name in cfg.required and f.default == nil:
|
||||||
t =
|
required.add f
|
||||||
if f.typeSym == "seq[string]": nnkBracketExpr.newTree(newIdentNode("seq"),newIdentNode("string"))
|
elif f.default != nil:
|
||||||
elif f.typeSym == "seq[int]" : nnkBracketExpr.newTree(newIdentNode("seq"),newIdentNode("string"))
|
default.add f
|
||||||
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)
|
for f in required:
|
||||||
|
let flagLit = f.literalFlags
|
||||||
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 name = newLit(f.name)
|
||||||
let flag = ident(f.ident)
|
|
||||||
body.add quote do:
|
body.add quote do:
|
||||||
checkVarSet(`name`, `flag`)
|
if `name` notin `flagSet`:
|
||||||
|
hwylCliError("expected a value for flag: " & `flagLit`)
|
||||||
|
|
||||||
|
for f in default:
|
||||||
|
let
|
||||||
|
name = newLit(f.name)
|
||||||
|
target = f.ident
|
||||||
|
default = f.default
|
||||||
|
body.add quote do:
|
||||||
|
if `name` notin `flagSet`:
|
||||||
|
`target` = `default`
|
||||||
|
|
||||||
func hwylCliImpl(cfg: CliCfg, root = false): NimNode =
|
func hwylCliImpl(cfg: CliCfg, root = false): 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"
|
||||||
result = newTree(nnkStmtList)
|
optParser = ident("p")
|
||||||
|
cmdLine = ident"cmdLine"
|
||||||
let
|
flagSet = ident"flagSet"
|
||||||
|
kind = ident"kind"
|
||||||
|
key = ident"key"
|
||||||
|
val = ident"val"
|
||||||
|
(longNoVal, shortNoVal) = cfg.getNoVals()
|
||||||
printHelperProc = generateCliHelperProc(cfg, printHelpName)
|
printHelperProc = generateCliHelperProc(cfg, printHelpName)
|
||||||
flagVars = setFlagVars(cfg)
|
flagVars = setFlagVars(cfg)
|
||||||
|
|
||||||
var parserBody = nnkStmtList.newTree()
|
result = newTree(nnkStmtList)
|
||||||
let
|
|
||||||
optParser = ident("p")
|
var
|
||||||
cmdLine = ident"cmdLine"
|
parserBody = nnkStmtList.newTree()
|
||||||
(longNoVal, shortNoVal) = cfg.getNoVals()
|
stopWords = nnkBracket.newTree(newLit("--"))
|
||||||
|
|
||||||
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(
|
||||||
|
@ -654,11 +662,6 @@ func hwylCliImpl(cfg: CliCfg, root = false): NimNode =
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
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"))),
|
||||||
|
@ -672,7 +675,10 @@ func hwylCliImpl(cfg: CliCfg, root = false): 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"), quote do: result.add `key`),
|
nnkOfBranch.newTree(ident("cmdArgument"),
|
||||||
|
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)
|
||||||
|
@ -688,7 +694,7 @@ func hwylCliImpl(cfg: CliCfg, root = false): NimNode =
|
||||||
|
|
||||||
let runProcName = ident("run" & name)
|
let runProcName = ident("run" & name)
|
||||||
let runBody = nnkStmtList.newTree()
|
let runBody = nnkStmtList.newTree()
|
||||||
addRequiredFlagsCheck(cfg, runBody)
|
addPostParseCheck(cfg, parserBody)
|
||||||
# move to proc?
|
# move to proc?
|
||||||
if cfg.pre != nil:
|
if cfg.pre != nil:
|
||||||
runBody.add cfg.pre
|
runBody.add cfg.pre
|
||||||
|
@ -697,10 +703,6 @@ func hwylCliImpl(cfg: CliCfg, root = false): 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:
|
||||||
|
@ -746,49 +748,3 @@ macro hwylCli*(body: untyped) =
|
||||||
var cfg = parseCliBody(body)
|
var cfg = parseCliBody(body)
|
||||||
hwylCliImpl(cfg, root = true)
|
hwylCliImpl(cfg, root = true)
|
||||||
|
|
||||||
when isMainModule:
|
|
||||||
import std/strformat
|
|
||||||
|
|
||||||
hwylCli:
|
|
||||||
name "hwylterm"
|
|
||||||
version "0.1.0"
|
|
||||||
... "a description of hwylterm"
|
|
||||||
flags:
|
|
||||||
check:
|
|
||||||
T bool
|
|
||||||
? "load config and exit"
|
|
||||||
config:
|
|
||||||
T seq[string]
|
|
||||||
? "path to config file"
|
|
||||||
* @["config.yml"]
|
|
||||||
run:
|
|
||||||
echo "hello from the main command"
|
|
||||||
echo fmt"{config=}, {check=}"
|
|
||||||
subcommands:
|
|
||||||
--- a
|
|
||||||
... "the \"a\" subcommand"
|
|
||||||
flags:
|
|
||||||
# ^ other
|
|
||||||
`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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
50
tests/example.nim
Normal file
50
tests/example.nim
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import std/strformat
|
||||||
|
import hwylterm/hwylcli
|
||||||
|
|
||||||
|
hwylCli:
|
||||||
|
name "example"
|
||||||
|
V "0.1.0"
|
||||||
|
... "a description of hwylterm"
|
||||||
|
flags:
|
||||||
|
yes:
|
||||||
|
T bool
|
||||||
|
? "set flag to yes"
|
||||||
|
config:
|
||||||
|
T seq[string]
|
||||||
|
? "path to config file"
|
||||||
|
* @["config.yml"]
|
||||||
|
run:
|
||||||
|
echo "this is always run prior to subcommand parsing"
|
||||||
|
echo fmt"{yes=}, {config=}"
|
||||||
|
subcommands:
|
||||||
|
--- one
|
||||||
|
... "the first subcommand"
|
||||||
|
required flag
|
||||||
|
flags:
|
||||||
|
`long-flag` "some help"
|
||||||
|
flag:
|
||||||
|
? "some other help"
|
||||||
|
|
||||||
|
run:
|
||||||
|
echo "hello from `example one` command!"
|
||||||
|
echo "long-flag and flag are: " & `long-flag` & "," & `flag` & " by default strings"
|
||||||
|
|
||||||
|
--- two
|
||||||
|
... """
|
||||||
|
some second subcommand
|
||||||
|
|
||||||
|
a longer mulitline description that will be visible in the subcommand help
|
||||||
|
it will automatically be "bb"'ed [bold]this is bold text[/]
|
||||||
|
"""
|
||||||
|
flags:
|
||||||
|
aflag:
|
||||||
|
T bool
|
||||||
|
? "some help"
|
||||||
|
bflag:
|
||||||
|
T seq[float]
|
||||||
|
? "multiple floats"
|
||||||
|
run:
|
||||||
|
echo "hello from `example b` command"
|
||||||
|
echo fmt"{aflag=}, {bflag=}"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue