Compare commits

..

3 commits

Author SHA1 Message Date
6082cc3835
add todo 2024-11-14 11:06:56 -06:00
5f5dd86c4d
change call to node for readability 2024-11-14 11:06:11 -06:00
203082d893
support header footer in cli style 2024-11-14 11:05:26 -06:00
3 changed files with 110 additions and 48 deletions

View file

@ -25,8 +25,7 @@ type
cmd* = "bold" cmd* = "bold"
settings*: set[HwylCliStyleSetting] = {Aliases} settings*: set[HwylCliStyleSetting] = {Aliases}
HwylCliHelp* = object HwylCliHelp* = object
usage*: string header*, footer*, description*, usage*: string
desc*: string
subcmds*: seq[HwylSubCmdHelp] subcmds*: seq[HwylSubCmdHelp]
flags*: seq[HwylFlagHelp] flags*: seq[HwylFlagHelp]
styles*: HwylCliStyles styles*: HwylCliStyles
@ -37,13 +36,15 @@ func firstLine(s: string): string =
s.strip().dedent().strip().splitlines()[0] s.strip().dedent().strip().splitlines()[0]
func newHwylCliHelp*( func newHwylCliHelp*(
header = "",
usage = "", usage = "",
desc = "", footer = "",
description = "",
subcmds: openArray[HwylSubCmdHelp] = @[], subcmds: openArray[HwylSubCmdHelp] = @[],
flags: openArray[HwylFlagHelp] = @[], flags: openArray[HwylFlagHelp] = @[],
styles = HwylCliStyles() styles = HwylCliStyles()
): HwylCliHelp = ): HwylCliHelp =
result.desc = dedent(desc).strip() result.description = dedent(description).strip()
if Aliases in styles.settings: if Aliases in styles.settings:
result.subcmds = result.subcmds =
subcmds.mapIt((it.name & " " & it.aliases, it.aliases, it.desc.firstLine)) subcmds.mapIt((it.name & " " & it.aliases, it.aliases, it.desc.firstLine))
@ -99,14 +100,17 @@ func render*(cli: HwylCliHelp, subcmd: HwylSubCmdHelp): string =
# TODO: split this into separate procs to make overriding more fluid # TODO: split this into separate procs to make overriding more fluid
func render*(cli: HwylCliHelp): string = func render*(cli: HwylCliHelp): string =
if cli.header != "":
result.add cli.header
result.add "\n"
if cli.usage != "": if cli.usage != "":
result.add "[" & cli.styles.header & "]" result.add "[" & cli.styles.header & "]"
result.add "usage[/]:\n" result.add "usage[/]:\n"
result.add indent(cli.usage, 2 ) result.add indent(cli.usage, 2 )
result.add "\n" result.add "\n"
if cli.desc != "": if cli.description != "":
result.add "\n" result.add "\n"
result.add cli.desc result.add cli.description
result.add "\n" result.add "\n"
if cli.subcmds.len > 0: if cli.subcmds.len > 0:
result.add "\n" result.add "\n"
@ -120,6 +124,8 @@ func render*(cli: HwylCliHelp): string =
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 render(cli,f)
if cli.footer != "":
result.add cli.footer
proc bb*(cli: HwylCliHelp): BbString = proc bb*(cli: HwylCliHelp): BbString =
result = bb(render(cli)) result = bb(render(cli))
@ -161,18 +167,20 @@ type
flags: seq[string] flags: seq[string]
groups: seq[string] groups: seq[string]
CliHelp = object
header*, footer*, description*, usage*, styles*: NimNode
CliCfg = object CliCfg = object
name*: string name*: string
alias*: HashSet[string] alias*: HashSet[string]
stopWords*: seq[string] stopWords*: seq[string]
styles: NimNode help: CliHelp
hidden*: seq[string] hidden*: seq[string]
subcommands: seq[CliCfg] subcommands: seq[CliCfg]
settings*: set[CliSetting] settings*: set[CliSetting]
preSub*, postSub*, pre*, post*, run*: NimNode preSub*, postSub*, pre*, post*, run*: NimNode
desc*: NimNode
subName*: string # used for help the generator subName*: string # used for help the generator
version*, usage*: NimNode version*: NimNode
flags*: seq[CliFlag] flags*: seq[CliFlag]
builtinFlags*: seq[BuiltinFlag] builtinFlags*: seq[BuiltinFlag]
flagDefs*: seq[CliFlag] flagDefs*: seq[CliFlag]
@ -494,60 +502,110 @@ func propagate(c: var CliCfg) =
propagate(child) propagate(child)
func parseCliHelp(c: var CliCfg, node: NimNode) =
## some possible DSL inputs:
##
## ```
## help:
## header NimNode -> string
## usage NimNode -> string
## description NimNode -> string
## footer NimNode -> string
## styles NimNode -> HwylCliStyles()
## ```
##
## ```
## help NimNode
## ```
##
## ```
## ... NimNode
## `
expectLen node, 2
var help: CliHelp = c.help
case node.kind:
# help NimNode or ... NimNode
of nnkPrefix, nnkCommand:
help.description = node[1]
# help:
# description NimNode
# usage: NimNode
of nnkCall:
if node[1].kind != nnkStmtList:
error "expected list of arguments for help"
for n in node[1]:
expectLen n, 2
let id = n[0].strVal
var val: NimNode
case n.kind
of nnkCommand:
val =n[1]
of nnKCall:
val = n[1][0]
else: bad(n, id)
case id:
of "usage": help.usage = val
of "description": help.description = val
of "header": help.header = val
of "footer": help.footer = val
of "styles": help.styles = val
else: error "unknown help option: " & id
else: bad(node, "help")
c.help = help
func parseCliBody(body: NimNode, name = "", root = false): CliCfg = func parseCliBody(body: NimNode, name = "", root = false): CliCfg =
result.name = name result.name = name
result.root = root result.root = root
# TDOO: change call to n or node? for node in body:
for call in body: if node.kind notin [nnkCall, nnkCommand, nnkPrefix]:
if call.kind notin [nnkCall, nnkCommand, nnkPrefix]: error "unexpected node kind: " & $node.kind
error "unexpected node kind: " & $call.kind let name = node[0].strVal
let name = call[0].strVal
case name: case name:
of "name": of "name":
expectKind call[1], nnkStrLit expectKind node[1], nnkStrLit
result.name = call[1].strVal result.name = node[1].strVal
of "alias": of "alias":
if root: error "alias not supported for root command" if root: error "alias not supported for root command"
pasrseCliAlias(result, call) pasrseCliAlias(result, node)
of "version", "V": of "version", "V":
result.version = call[1] result.version = node[1]
of "usage", "?": of "usage", "?":
result.usage = call[1] result.help.usage = node[1]
of "description", "...": of "...", "help":
result.desc = call[1] parseCliHelp(result, node)
of "flags": of "flags":
parseCliFlags(result, call[1]) parseCliFlags(result, node[1])
of "settings": of "settings":
parseCliSettings(result, call) parseCliSettings(result, node)
of "stopWords": of "stopWords":
result.stopWords = parseIdentLikeList(call) result.stopWords = parseIdentLikeList(node)
of "subcommands": of "subcommands":
parseCliSubcommands(result, call) parseCliSubcommands(result, node)
of "hidden": of "hidden":
parseHiddenFlags(result, call) parseHiddenFlags(result, node)
of "run": of "run":
result.run = call[1] result.run = node[1]
of "styles":
result.styles = call[1]
of "required": of "required":
result.required = parseIdentLikeList(call) result.required = parseIdentLikeList(node)
of "preSub": of "preSub":
result.preSub = call[1] result.preSub = node[1]
of "postSub": of "postSub":
result.postSub = call[1] result.postSub = node[1]
else: else:
error "unknown hwylCli setting: " & name error "unknown hwylCli setting: " & name
#
# for sub in result.subcommands.mitems:
# # sub.inheritFrom(result)
# sub.pre = result.preSub
# sub.post = result.postSub
if result.name == "": if result.name == "":
error "missing required option: name" error "missing required option: name"
# TODO: validate "required" flags exist here # TODO: validate "required" flags exist here
result.addBuiltinFlags() result.addBuiltinFlags()
if root: if root:
propagate(result) propagate(result)
@ -574,7 +632,7 @@ func subCmdsArray(cfg: CliCfg): NimNode =
for s in cfg.subcommands: for s in cfg.subcommands:
let cmd = newLit(s.subName) let cmd = newLit(s.subName)
let aliases = newLit(s.alias.mapIt("($1)" % [it]).join(" ")) let aliases = newLit(s.alias.mapIt("($1)" % [it]).join(" "))
let desc = s.desc or newLit("") let desc = s.help.description or newLit("")
result.add quote do: result.add quote do:
(`cmd`, `aliases`, `desc`) (`cmd`, `aliases`, `desc`)
@ -588,19 +646,23 @@ func defaultUsage(cfg: CliCfg): NimNode =
s.add " [[[faint]flags[/]]" s.add " [[[faint]flags[/]]"
newLit(s) newLit(s)
func generateCliHelperProc(cfg: CliCfg, printHelpName: NimNode): NimNode = func generateCliHelpProc(cfg: CliCfg, printHelpName: NimNode): NimNode =
let let
desc = cfg.desc or newLit("") description = cfg.help.description or newLit""
usage = cfg.usage or defaultUsage(cfg) header = cfg.help.header or newLit""
footer = cfg.help.footer or newLit""
usage = cfg.help.usage or defaultUsage(cfg)
helpFlags = cfg.flagsArray() helpFlags = cfg.flagsArray()
subcmds = cfg.subCmdsArray() subcmds = cfg.subCmdsArray()
styles = cfg.styles or (quote do: HwylCliStyles()) styles = cfg.help.styles or (quote do: HwylCliStyles())
<<< usage
result = quote do: result = quote do:
proc `printHelpName`() = proc `printHelpName`() =
echo bb(render(newHwylCliHelp( echo bb(render(newHwylCliHelp(
desc = `desc`, header = `header`,
footer = `footer`,
usage = `usage`, usage = `usage`,
description = `description`,
subcmds = `subcmds`, subcmds = `subcmds`,
flags = `helpFlags`, flags = `helpFlags`,
styles = `styles`, styles = `styles`,
@ -814,7 +876,7 @@ func hwylCliImpl(cfg: CliCfg): NimNode =
key = ident"key" key = ident"key"
val = ident"val" val = ident"val"
(longNoVal, shortNoVal) = cfg.getNoVals() (longNoVal, shortNoVal) = cfg.getNoVals()
printHelperProc = generateCliHelperProc(cfg, printHelpName) printHelpProc = generateCliHelpProc(cfg, printHelpName)
flagVars = setFlagVars(cfg) flagVars = setFlagVars(cfg)
result = newTree(nnkStmtList) result = newTree(nnkStmtList)
@ -889,7 +951,7 @@ func hwylCliImpl(cfg: CliCfg): NimNode =
result.add quote do: result.add quote do:
# block: # block:
`printHelperProc` `printHelpProc`
`flagVars` `flagVars`
proc `parserProcName`(`cmdLine`: openArray[string] = commandLineParams()): seq[string] = proc `parserProcName`(`cmdLine`: openArray[string] = commandLineParams()): seq[string] =
`parserBody` `parserBody`

View file

@ -10,7 +10,6 @@ type
hwylCli: hwylCli:
name "example" name "example"
V "0.1.0" V "0.1.0"
... "a description of hwylterm"
flags: flags:
[global] [global]
color: color:

View file

@ -21,7 +21,8 @@
- [x] add support for inheriting a single flag from parent (even from a "group") - [x] add support for inheriting a single flag from parent (even from a "group")
- [x] add support to either (lengthen commands) or provide an alias for a subcommand - [x] add support to either (lengthen commands) or provide an alias for a subcommand
- [x] add command aliases to hwylcli help with switch - [x] add command aliases to hwylcli help with switch
- [ ] don't recreate "global"" variables in var section - [x] don't recreate "global"" variables in var section
- [ ] make proper test suite for cli generator
## features ## features