mirror of
https://github.com/daylinmorgan/hwylterm.git
synced 2025-02-23 09:45:50 -06:00
handle required at the flag level
This commit is contained in:
parent
6a93485202
commit
097d963b2a
6 changed files with 143 additions and 31 deletions
|
@ -37,12 +37,14 @@ import ./[bbansi, parseopt3]
|
||||||
export parseopt3, sets, bbansi
|
export parseopt3, sets, bbansi
|
||||||
|
|
||||||
type
|
type
|
||||||
HwylFlagHelp* = tuple
|
HwylFlagHelp* = tuple[
|
||||||
short, long, description, defaultVal: string
|
short, long, description, defaultVal: string; required: bool
|
||||||
HwylSubCmdHelp* = tuple
|
]
|
||||||
|
HwylSubCmdHelp* = tuple[
|
||||||
name, aliases, desc: string
|
name, aliases, desc: string
|
||||||
|
]
|
||||||
HwylCliStyleSetting = enum
|
HwylCliStyleSetting = enum
|
||||||
Aliases
|
Aliases, Required, Defaults
|
||||||
HwylCliStyles* = object
|
HwylCliStyles* = object
|
||||||
header* = "bold cyan"
|
header* = "bold cyan"
|
||||||
flagShort* = "yellow"
|
flagShort* = "yellow"
|
||||||
|
@ -51,7 +53,9 @@ type
|
||||||
default* = "faint"
|
default* = "faint"
|
||||||
required* = "red"
|
required* = "red"
|
||||||
cmd* = "bold"
|
cmd* = "bold"
|
||||||
settings*: set[HwylCliStyleSetting] = {Aliases}
|
minCmdLen* = 8
|
||||||
|
settings*: set[HwylCliStyleSetting] = {Aliases, Required, Defaults}
|
||||||
|
|
||||||
HwylCliLengths = object
|
HwylCliLengths = object
|
||||||
subcmd*, subcmdDesc*, shortArg*, longArg*, descArg*, defaultVal*: int
|
subcmd*, subcmdDesc*, shortArg*, longArg*, descArg*, defaultVal*: int
|
||||||
|
|
||||||
|
@ -87,7 +91,7 @@ func newHwylCliHelp*(
|
||||||
result.usage = dedent(usage).strip()
|
result.usage = dedent(usage).strip()
|
||||||
result.flags = @flags
|
result.flags = @flags
|
||||||
result.styles = styles
|
result.styles = styles
|
||||||
result.lengths.subcmd = 8 # TODO: incorporate into "styles?"
|
result.lengths.subcmd = styles.minCmdLen
|
||||||
for f in flags:
|
for f in flags:
|
||||||
result.lengths.shortArg = max(result.lengths.shortArg, f.short.len)
|
result.lengths.shortArg = max(result.lengths.shortArg, f.short.len)
|
||||||
result.lengths.longArg = max(result.lengths.longArg, f.long.len)
|
result.lengths.longArg = max(result.lengths.longArg, f.long.len)
|
||||||
|
@ -119,12 +123,20 @@ func render*(cli: HwylCliHelp, f: HwylFlagHelp): string =
|
||||||
result.add "[" & cli.styles.flagDesc & "]"
|
result.add "[" & cli.styles.flagDesc & "]"
|
||||||
result.add f.description
|
result.add f.description
|
||||||
result.add "[/" & cli.styles.flagDesc & "]"
|
result.add "[/" & cli.styles.flagDesc & "]"
|
||||||
if f.defaultVal != "":
|
|
||||||
|
if f.defaultVal != "" and Defaults in cli.styles.settings:
|
||||||
result.add " "
|
result.add " "
|
||||||
result.add "[" & cli.styles.default & "]"
|
result.add "[" & cli.styles.default & "]"
|
||||||
result.add "(" & f.defaultVal & ")"
|
result.add "(" & f.defaultVal & ")"
|
||||||
result.add "[/" & cli.styles.default & "]"
|
result.add "[/" & cli.styles.default & "]"
|
||||||
|
|
||||||
|
if f.required and Required in cli.styles.settings:
|
||||||
|
result.add " "
|
||||||
|
result.add "[" & cli.styles.required & "]"
|
||||||
|
result.add "(required)"
|
||||||
|
result.add "[/" & cli.styles.required & "]"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func render*(cli: HwylCliHelp, subcmd: HwylSubCmdHelp): string =
|
func render*(cli: HwylCliHelp, subcmd: HwylSubCmdHelp): string =
|
||||||
result.add " "
|
result.add " "
|
||||||
|
@ -190,13 +202,13 @@ type
|
||||||
NoHelpFlag, ## Remove the builtin help flag
|
NoHelpFlag, ## Remove the builtin help flag
|
||||||
ShowHelp, ## If cmdline empty show help
|
ShowHelp, ## If cmdline empty show help
|
||||||
NoNormalize, ## Don't normalize flags and commands
|
NoNormalize, ## Don't normalize flags and commands
|
||||||
NoPositional, ## Raise error if any remaing positional arguments DEPRECATED
|
|
||||||
HideDefault, ## Don't show default values
|
HideDefault, ## Don't show default values
|
||||||
InferShort ## Autodefine short flags
|
InferShort ## Autodefine short flags
|
||||||
|
|
||||||
CliFlagSetting* = enum
|
CliFlagSetting* = enum
|
||||||
HideDefault, ## Don't show default values
|
HideDefault, ## Don't show default values
|
||||||
NoShort ## Counter option to Parent's InferShort
|
NoShort, ## Counter option to Parent's InferShort
|
||||||
|
Required, ## Flag must be used (or have default value)
|
||||||
|
|
||||||
BuiltinFlag = object
|
BuiltinFlag = object
|
||||||
name*: string
|
name*: string
|
||||||
|
@ -240,7 +252,6 @@ type
|
||||||
stopWords*: seq[string]
|
stopWords*: seq[string]
|
||||||
help: CliHelp
|
help: CliHelp
|
||||||
defaultFlagType: NimNode
|
defaultFlagType: NimNode
|
||||||
required*: seq[string]
|
|
||||||
settings*: set[CliSetting]
|
settings*: set[CliSetting]
|
||||||
subName*: string # used for help generator
|
subName*: string # used for help generator
|
||||||
subcommands: seq[CliCfg]
|
subcommands: seq[CliCfg]
|
||||||
|
@ -735,7 +746,6 @@ func parseCliHelp(c: var CliCfg, node: NimNode) =
|
||||||
if node[1].kind != nnkStmtList:
|
if node[1].kind != nnkStmtList:
|
||||||
c.err node, "expected list of arguments for help"
|
c.err node, "expected list of arguments for help"
|
||||||
for n in node[1]:
|
for n in node[1]:
|
||||||
<<< n
|
|
||||||
c.expectLen n, 2
|
c.expectLen n, 2
|
||||||
let id = n[0].strVal
|
let id = n[0].strVal
|
||||||
var val: NimNode
|
var val: NimNode
|
||||||
|
@ -853,8 +863,6 @@ func parseCliBody(body: NimNode, name = "", root = false): CliCfg =
|
||||||
parseHiddenFlags(result, node)
|
parseHiddenFlags(result, node)
|
||||||
of "run":
|
of "run":
|
||||||
result.run = node[1]
|
result.run = node[1]
|
||||||
of "required":
|
|
||||||
result.required = result.parseIdentLikeList(node)
|
|
||||||
of "preSub":
|
of "preSub":
|
||||||
result.preSub = node[1]
|
result.preSub = node[1]
|
||||||
of "postSub":
|
of "postSub":
|
||||||
|
@ -874,7 +882,45 @@ func parseCliBody(body: NimNode, name = "", root = false): CliCfg =
|
||||||
if root:
|
if root:
|
||||||
propagate(result)
|
propagate(result)
|
||||||
|
|
||||||
func flagToTuple(c: CliCfg, f: CliFlag | BuiltinFlag): NimNode =
|
|
||||||
|
func isBool(f: CliFlag | BuiltinFlag): bool =
|
||||||
|
f.typeNode == ident"bool"
|
||||||
|
|
||||||
|
func isCount(f: CliFlag): bool =
|
||||||
|
f.typeNode == ident"Count"
|
||||||
|
|
||||||
|
func isRequiredFlag(cfg: CliCfg, f: CliFlag): bool =
|
||||||
|
result = (Required in f.settings and f.defaultVal == nil)
|
||||||
|
if result and f.isBool:
|
||||||
|
cfg.err "boolean flag `$1` can't be a required flag " % [f.long]
|
||||||
|
|
||||||
|
# TODO: deprecate builtinflag
|
||||||
|
func flagToTuple(c: CliCfg, f: BuiltinFlag): NimNode =
|
||||||
|
let
|
||||||
|
short =
|
||||||
|
if f.short != '\x00': newLit($f.short)
|
||||||
|
else: newLit("")
|
||||||
|
long = newLit(f.long)
|
||||||
|
help = f.help
|
||||||
|
|
||||||
|
defaultVal =
|
||||||
|
if (HideDefault in f.settings) or
|
||||||
|
(HideDefault in c.settings):
|
||||||
|
newLit""
|
||||||
|
else:
|
||||||
|
f.defaultVal or newLit""
|
||||||
|
required = newLit(false)
|
||||||
|
|
||||||
|
# BUG: if f.defaultVal is @[] `$` fails
|
||||||
|
# but works with `newSeq[T]()`
|
||||||
|
# could replace "defaultVal" with newSeq[T]()
|
||||||
|
# under the hood when parsing type/val
|
||||||
|
|
||||||
|
quote do:
|
||||||
|
(`short`, `long`, `help`, bbEscape($`defaultVal`), `required`)
|
||||||
|
|
||||||
|
|
||||||
|
func flagToTuple(c: CliCfg, f: CliFlag): NimNode =
|
||||||
let
|
let
|
||||||
short =
|
short =
|
||||||
if f.short != '\x00': newLit($f.short)
|
if f.short != '\x00': newLit($f.short)
|
||||||
|
@ -889,14 +935,22 @@ func flagToTuple(c: CliCfg, f: CliFlag | BuiltinFlag): NimNode =
|
||||||
else:
|
else:
|
||||||
f.defaultVal or newLit""
|
f.defaultVal or newLit""
|
||||||
|
|
||||||
|
required = newLit(c.isRequiredFlag(f))
|
||||||
|
|
||||||
# BUG: if f.defaultVal is @[] `$` fails
|
# BUG: if f.defaultVal is @[] `$` fails
|
||||||
# but works with `newSeq[T]()`
|
# but works with `newSeq[T]()`
|
||||||
# could replace "defaultVal" with newSeq[T]()
|
# could replace "defaultVal" with newSeq[T]()
|
||||||
# under the hood when parsing type/val
|
# under the hood when parsing type/val
|
||||||
|
result = nnkTupleConstr.newTree(
|
||||||
quote do:
|
short,
|
||||||
(`short`, `long`, `help`, bbEscape($`defaultVal`))
|
newLit(f.long),
|
||||||
|
f.help,
|
||||||
|
quote do: bbEscape($`defaultVal`),
|
||||||
|
required,
|
||||||
|
)
|
||||||
|
# quote do:
|
||||||
|
# (`short`, `long`, `help`, bbEscape($`defaultVal`), `required`)
|
||||||
|
#
|
||||||
func flagsArray(cfg: CliCfg): NimNode =
|
func flagsArray(cfg: CliCfg): NimNode =
|
||||||
result = newTree(nnkBracket)
|
result = newTree(nnkBracket)
|
||||||
for f in cfg.flags:
|
for f in cfg.flags:
|
||||||
|
@ -1099,12 +1153,6 @@ func shortLongCaseStmt(cfg: CliCfg, printHelpName: NimNode, version: NimNode): N
|
||||||
|
|
||||||
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)
|
let flagFlags = cfg.flags.filterIt(it.isBool or it.isCount)
|
||||||
|
@ -1246,7 +1294,7 @@ func addPostParseHook(cfg: CliCfg, body: NimNode) =
|
||||||
var required, default: seq[CliFlag]
|
var required, default: seq[CliFlag]
|
||||||
|
|
||||||
for f in cfg.flags:
|
for f in cfg.flags:
|
||||||
if f.name in cfg.required and f.defaultVal == nil:
|
if cfg.isRequiredFlag(f):
|
||||||
required.add f
|
required.add f
|
||||||
elif f.defaultVal != nil:
|
elif f.defaultVal != nil:
|
||||||
default.add f
|
default.add f
|
||||||
|
|
25
tests/cli/clis/helpSettings.nim
Normal file
25
tests/cli/clis/helpSettings.nim
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import std/strformat
|
||||||
|
import hwylterm, hwylterm/hwylcli
|
||||||
|
|
||||||
|
const noExtras = HwylCliStyles(settings: {})
|
||||||
|
|
||||||
|
hwylCli:
|
||||||
|
name "help-switches"
|
||||||
|
help:
|
||||||
|
styles: noExtras
|
||||||
|
subcommands:
|
||||||
|
[required]
|
||||||
|
alias r
|
||||||
|
help:
|
||||||
|
styles: noExtras
|
||||||
|
flags:
|
||||||
|
input:
|
||||||
|
T string
|
||||||
|
? "required input"
|
||||||
|
S Required
|
||||||
|
k:
|
||||||
|
T string
|
||||||
|
? "predefined flag"
|
||||||
|
* "value"
|
||||||
|
run:
|
||||||
|
echo fmt"{input=},{k=}"
|
|
@ -2,7 +2,7 @@ import std/strformat
|
||||||
import hwylterm, hwylterm/hwylcli
|
import hwylterm, hwylterm/hwylcli
|
||||||
|
|
||||||
hwylCli:
|
hwylCli:
|
||||||
name "base"
|
name "inherit-flags"
|
||||||
flags:
|
flags:
|
||||||
[global]
|
[global]
|
||||||
always "in all subcommands"
|
always "in all subcommands"
|
||||||
|
|
13
tests/cli/clis/required.nim
Normal file
13
tests/cli/clis/required.nim
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import std/strformat
|
||||||
|
import hwylterm, hwylterm/hwylcli
|
||||||
|
|
||||||
|
|
||||||
|
hwylCli:
|
||||||
|
name "required-flag"
|
||||||
|
flags:
|
||||||
|
input:
|
||||||
|
S Required
|
||||||
|
T string
|
||||||
|
? "a required flag!"
|
||||||
|
run:
|
||||||
|
echo fmt"{input=}"
|
|
@ -93,6 +93,32 @@ flags:
|
||||||
show this help
|
show this help
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
okWithArgs(
|
||||||
|
"helpSettings", "--help",
|
||||||
|
"""
|
||||||
|
usage:
|
||||||
|
help-switches subcmd [flags]
|
||||||
|
|
||||||
|
subcommands:
|
||||||
|
required
|
||||||
|
|
||||||
|
flags:
|
||||||
|
-h --help show this help
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
okWithArgs(
|
||||||
|
"helpSettings", "required --help",
|
||||||
|
"""
|
||||||
|
usage:
|
||||||
|
help-switches required [flags]
|
||||||
|
|
||||||
|
flags:
|
||||||
|
--input required input
|
||||||
|
-k predefined flag
|
||||||
|
-h --help show this help
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
suite "hooks":
|
suite "hooks":
|
||||||
okWithArgs(
|
okWithArgs(
|
||||||
|
|
2
todo.md
2
todo.md
|
@ -21,7 +21,7 @@
|
||||||
- [ ] add support for types(metavars)/defaults/required in help output
|
- [ ] add support for types(metavars)/defaults/required in help output
|
||||||
- [ ] add support for E/env param for flags to add custom env_var (in help (env: OPTIONAL_ENV_VAR))
|
- [ ] add support for E/env param for flags to add custom env_var (in help (env: OPTIONAL_ENV_VAR))
|
||||||
inject this code in the same place as the "default" setting
|
inject this code in the same place as the "default" setting
|
||||||
- [ ] generalize parser error handling
|
- [ ] BUG: flag can't be `key`
|
||||||
- [ ] consider default (or opt in) "help subcmd"
|
- [ ] consider default (or opt in) "help subcmd"
|
||||||
> app help (show all help?)
|
> app help (show all help?)
|
||||||
> app help <subcmd> same as app <subcmd> --help
|
> app help <subcmd> same as app <subcmd> --help
|
||||||
|
|
Loading…
Add table
Reference in a new issue