| CLI | Description |
|---|---|
| allFlagKinds | The flag DSL supports multiple equivalent syntaxes — this shows all of them side by side. |
| collections | Various collection types (seq/sets) allow for repeated flags or delimited values |
| customHelp | The help renderer can be replaced entirely by overriding the render procs before the hwylCli macro. |
| customParse | customize parsing of typical types and with distinct types |
| defaults | Flags can carry default values of any supported type, shown in help output automatically. |
| defaultsSub | Global flags with defaults and env var inference flow through to subcommands unchanged. |
| enumFlag | Nim enum types work directly as flag types, with automatic error messaging for invalid values. |
| flagKVs | Key=value pair flags let you accept structured input like --flag key=value in a single argument. |
| flagKey | Bool flags are the default type — no type annotation needed, just a name and help string. |
| flagObject | Flags can be objects ,including with their own collection types (seq, set, etc.) |
| flagSeparators | String flags accept both --flag value and --flag=value — either separator works. |
| flagSettings | Individual flags can opt out of default help display, and Count gives an incrementing integer flag. |
| generateOnly | GenerateOnly lets you run arbitrary code before the CLI starts, then invoke the parser manually. |
| helpSettings | Builtin style presets let you switch the entire help appearance without manual style configuration. |
| hideDefaultSetting | HideDefault on the CLI hides all flag defaults from help at once, without per-flag annotation. |
| inferEnv | InferEnv automatically binds CMDNAME_FLAGNAME environment variables to their flags. |
| inferShort | InferShort generates a short flag from the first letter of each long flag name automatically. |
| inheritFlags | Flag groups let subcommands selectively pull in flags from the parent using the ^ syntax. |
| multiShortFlags | Single-letter flag names are automatically short-only — no long form is generated. |
| posBasic | Positional arguments can bind to a different variable name in the run block via ident. |
| posFirst | A variadic positional can appear first, collecting values until the fixed positionals are satisfied from the right. |
| posLast | A variadic positional in the last position collects all remaining arguments after the fixed ones. |
| posNoMulti | Positionals with non-seq types accept exactly one value each and support type coercion. |
| propagateSetting | Settings flow down to subcommands by default; IgnoreParent opts out and per-subcommand settings extend or replace. |
| required | Marking a flag Required causes the CLI to exit with an error if it is not provided. |
| subHooks | preSub and postSub hooks run code around any subcommand dispatch, and can be nested per level. |
| subcommands | Subcommands each have their own flags, positionals, aliases, and can use hyphenated names. |
| version | Versions can be set using compile time defines or runtime code |
## The flag DSL supports multiple equivalent syntaxes — this shows all of them side by side. 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") i: "kind: Stmt (but one line)" run: echo fmt"{a=}, {bbbb=}, {cccc=}, {dddd=}" echo fmt"{e=}, {notffff=}, {gggg=}, {hhhh=}, {i=}"
Case 1 (ok)
allFlagKinds --help
## Various collection types (seq/sets) allow for repeated flags or delimited values import std/strformat import hwylterm, hwylterm/hwylcli type Mode = enum fun, sad, angry, sleepy hwylCli: name "collects" flags: inputs(seq[string], "sequence of inputs") modes(set[Mode], "set of modes") modes2(toHashSet([fun]), HashSet[Mode], "hashset of modes") outputs(seq[KVstring], "sequence of key/value pairs") run: echo fmt"{inputs=},{modes=},{modes2=},{outputs=}"
Case 1 (ok)
collections --inputs one --inputs two --inputs three --modes fun --modes sad \ --outputs:first:first.txt --outputs:second=second.txt
Case 2 (ok)
collections --inputs one --inputs,=two,three --modes,=,fun,sad,fun --modes2 sad --modes2 sleepy \ --outputs,:first:first.txt,second:second.txt
## The help renderer can be replaced entirely by overriding the render procs before the hwylCli macro. import std/[strutils,strformat, sequtils] import hwylterm import hwylterm/hwylcli except renderHeader # TODO: actually implement this in bbansi func stripMarkup*(s: string): string = result = bb(s).plain func render*(cli: HwylCliHelp, f: HwylFlagHelp): string = result.add " " if f.short != "": result.add "[" & cli.styles.flagShort & "]" result.add "-" & f.short.alignLeft(cli.lengths.shortArg) result.add "[/" & cli.styles.flagShort & "]" else: result.add " ".repeat(1 + cli.lengths.shortArg) result.add " " let indentSize = stripMarkup(result).len if f.long != "": result.add "[" & cli.styles.flagLong & "]" result.add "--" & f.long.alignLeft(cli.lengths.longArg) result.add "[/" & cli.styles.flagLong & "]" else: result.add " ".repeat(2 + cli.lengths.longArg) result.add "\n" result.add " ".repeat(indentSize + 4) if f.description != "": result.add "[" & cli.styles.flagDesc & "]" result.add f.description result.add "[/" & cli.styles.flagDesc & "]" if f.defaultVal != "": result.add " " result.add "[" & cli.styles.default & "]" result.add "(" & f.defaultVal & ")" result.add "[/" & cli.styles.default & "]" func render*(cli: HwylCliHelp, flags: seq[HwylFlagHelp]): string = result.add "flags".bbMarkup(cli.styles.header) result.add ":\n" result.add flags.mapIt(render(cli, it)).join("\n") hwylCli: name "custom-help" defaultFlagType string help: header """ a header indentations indentations more """ flags: input: ? "input" * "input.txt" output: ? "output" * "output.txt" run: echo fmt"{input=}, {output=}"
Case 1 (ok)
customHelp --help
## customize parsing of typical types and with distinct types import std/[os, paths, strformat] import hwylterm, hwylterm/hwylcli type Input = distinct string Data = object nums: seq[int] names: seq[string] proc `$`(v: Input): string {.borrow.} proc parse(p: OptParser, target: var Input) = checkVal p if not p.val.fileExists: hecho "warning input file does not exist: " & p.val target = Input(p.val) proc parse(p: OptParser, target: var Path) = checkVal p target = Path(p.val) proc fieldNames(o: object): seq[string] = for k, _ in o.fieldPairs: result.add k proc parse(p: var OptParser, target: var object) = let key = extractKey(p) if key notin target.fieldNames: hecho "ignoring unknown key: " & key return for name, field in target.fieldPairs: if name == key: parse(p, field) return hwylCli: name "customParse" flags: input(Input, "some input") output(Path, "some output") data(Data, "data") run: echo fmt"{input=},{output=}" echo data
Case 1 (ok)
customParse --input input.nim --output new.nim --data:unknown-key:val --data,=nums:1,2,3 \ --data:names:John --data:names:Jane
## Flags can carry default values of any supported type, shown in help output automatically. import std/[strformat] import hwylterm, hwylterm/hwylcli hwylCli: name "default-values" flags: input: T string * "testing" ? "some help after default" outputs: T seq[string] count: T int * 5 ? "some number" run: echo fmt"{input=} {outputs=}, {count=}"
Case 1 (ok)
defaults
## Global flags with defaults and env var inference flow through to subcommands unchanged. import std/[os, strformat] import hwylterm, hwylterm/hwylcli putEnv("DEFAULTS_SUB_COUNT", "15") putEnv("DEFAULTS_SUB_OUTPUTS", ",a,b,c") hwylCli: name "defaults-sub" settings InferEnv flags: [global] input: T string * "testing" ? "some help after default" outputs: T seq[string] count: T int * 5 ? "some number" subcommands: [a] run: assert count == 15 echo fmt"{input=}, {outputs=}, {count=}"
Case 1 (ok)
defaultsSub a
## Nim enum types work directly as flag types, with automatic error messaging for invalid values. import std/[strformat] import hwylterm, hwylterm/hwylcli type Color = enum red, blue, green hwylCli: name "enumFlag" flags: color: T Color run: echo fmt"{color=}"
Case 1 (ok)
enumFlag --color red
Case 2 (error)
enumFlag --color black
## Key=value pair flags let you accept structured input like --flag key=value in a single argument. import hwylterm, hwylterm/hwylcli type Inputs = KVString proc `$`(_:typedesc[seq[KVString]]): string = "k(str):v(str)..." hwylCli: name "flagKVs" flags: inputs(seq[Inputs], "version with type alias") counts(KV[string, int], "key value, custom types") builtins(seq[KVstring], "one using provided type") run: echo counts.key,":", counts.val for (k, v) in inputs: echo k, ":", v for item in builtins: echo item
Case 1 (ok)
flagKVs --counts a:5 --inputs "test:a" --inputs "test:b" --builtins,=,"key with space:a,key:value \ with space"
## Bool flags are the default type — no type annotation needed, just a name and help string. import hwylterm, hwylterm/hwylcli hwylCli: name "base" flags: key "a flag named 'key'" run: if key: echo "key set" else: echo "key not set"
Case 1 (ok)
flagKey --key
## Flags can be objects ,including with their own collection types (seq, set, etc.) import hwylterm, hwylterm/hwylcli type Person = object name: string age: int weight: float = 150.5 roles: seq[string] hwylCli: name "flagObject" flags: person(Person, "a person") run: echo person
Case 1 (ok)
flagObject --person:name:John --person,=roles=admin,user
Case 2 (ok)
flagObject --person name:John --person:age=25 --person:weight:160
Case 3 (error)
flagObject --person:nonexistient:error
## String flags accept both --flag value and --flag=value — either separator works. import hwylterm, hwylterm/hwylcli hwylCli: name "flag-separators" flags: input(string, "flag that expects some input") run: echo "input = ", input
Case 1 (error)
flagSeparators --input --help
Case 2 (ok)
flagSeparators --input=--help
## Individual flags can opt out of default help display, and Count gives an incrementing integer flag. import hwylterm, hwylterm/hwylcli hwylCli: name "flag-settings" flags: input: S HideDefault T string * "a secret default" ? "flag with default hidden" count: T Count * Count(val: 0) ? "a count var with default" run: discard
Case 1 (ok)
flagSettings --help
## GenerateOnly lets you run arbitrary code before the CLI starts, then invoke the parser manually. import std/[os, strformat] import hwylterm/hwylcli hwylCLi: name "gen only" settings GenerateOnly, ShowHelp subcommands: [one] positionals: args seq[string] run: assert args == @["a", "b", "c"] echo fmt"{args=}" [two] flags: input(string, "someinput") run: echo "running subcmd 2" echo fmt"{input=}" echo "some code that runs first" printGenOnlyHelp() if paramCount() == 0: let args = parseCmdLine("two --input test") runGenOnly(args) else: # use from parseCommandLine runGenOnly()
Case 1 (ok)
generateOnly one a b c
Case 2 (ok)
generateOnly
## Builtin style presets let you switch the entire help appearance without manual style configuration. import std/[os, strformat] import hwylterm, hwylterm/hwylcli putEnv("HWYLCLISTYLES_HEADER","red") # putEnv("HWYLCLISTYLES_SETTINGS", "Aliases") const identHelp = """predefined help string k could be short for key idk """ proc helpRuntime(s: string): string = result = s & """ some runtime generated help """ hwylCli: name "help-settings" flags: [group] input: T string ? """ required input A long help that continues here and spans multiple lines the lines are dedented then reindented [yellow]this is yellow....[/] """ S Required key: T string ? identHelp * "value" other: ? helpRuntime("first line of help") subcommands: [all] ... "show all help styling settings" help: styles: fromBuiltinHelpStyles(AllSettings) flags: ^[group] [minimal] ... "show minimal help with no styling" help: styles: fromBuiltinHelpStyles(Minimal) flags: ^[group] [noColor] ... "show noColor help " help: styles: fromBuiltinHelpStyles(WithoutColor) flags: ^[group] [noAnsi] ... "show noColor help " help: styles: fromBuiltinHelpStyles(WithoutAnsi) flags: ^[group] [longHelp] ... "-h != --help" flags: ^[group] settings LongHelp [noEnv] ... "ignore env styles" flags: ^[group] help: styles: newHwylCliStyles(settings = defaultStyleSettings + {HwylCliStyleSetting.NoEnv})
Case 1 (ok)
helpSettings all --help
Case 2 (ok)
helpSettings minimal --help
Case 3 (ok)
helpSettings noColor --help
Case 4 (ok)
helpSettings noAnsi --help
Case 5 (ok)
helpSettings longHelp --help
## HideDefault on the CLI hides all flag defaults from help at once, without per-flag annotation. import hwylterm, hwylterm/hwylcli hwylCli: name "setting-hide-default" settings HideDefault flags: input: T string * "a secret default" ? "flag with default hidden" count: T Count * Count(val: 0) ? "a count var with default" run: discard
Case 1 (ok)
hideDefaultSetting --help
## InferEnv automatically binds CMDNAME_FLAGNAME environment variables to their flags. import std/[strformat, os] import hwylterm, hwylterm/hwylcli putEnv("INFERENV_INPUT", "TEST") putEnv("INFERENV_COUNT", "5") putEnv("INFERENV_YES", "false") hwylCli: name "inferEnv" settings InferEnv flags: input: S HideDefault T string * "a secret default" ? "flag with default hidden" count: T Count * Count(val: 0) ? "a count var with default" shell: S NoEnv T string * "guess" yes: ? "a boolean flag" run: echo fmt"{input=}, {count=}, {shell=}, {yes=}"
Case 1 (ok)
inferEnv --input "command line"
## InferShort generates a short flag from the first letter of each long flag name automatically. import std/strformat import hwylterm, hwylterm/hwylcli hwylCli: name "inferred short flags" settings InferShort flags: input: T string ? "the input var" output: T string ? "the output var" count: T int ? "a number" - n nancy: ? "needed a flag that starts with n :)" ignore: S NoShort ? "a flag to not infer" run: echo fmt"{input=}, {output=}, {count=}, {nancy=}, {ignore=}"
Case 1 (ok)
inferShort -i input -o output
## Flag groups let subcommands selectively pull in flags from the parent using the ^ syntax. import std/strformat import hwylterm, hwylterm/hwylcli hwylCli: name "inherit-flags" help: styles: newHwylCliStyles(settings = defaultStyleSettings + {FlagGroups}) flags: [global] always "in all subcommands" [misc] misc1 "first misc flag" misc2 "second misc flag" ["_hidden"] other "flag from hidden group" subcommands: [first] ... "command with it's own flag" flags: # manually defined groups first "first first flag": group misc run: echo fmt"{always=},{first=}" [second] ... "command with 'misc' flags" flags: ^[misc] run: echo fmt"{always=},{misc1=},{misc2=}" [third] ... "command with only 'misc1' flag" flags: ^misc1 ^["_hidden"] run: echo fmt"{always=},{misc1=}"
Case 1 (ok)
inheritFlags first --help
Case 2 (ok)
inheritFlags third --help
Case 3 (ok)
inheritFlags first --always
Case 4 (ok)
inheritFlags second --always --misc2
Case 5 (ok)
inheritFlags third --misc1
## Single-letter flag names are automatically short-only — no long form is generated. import hwylterm, hwylterm/hwylcli hwylCli: name "multiple-short-flags" flags: a "first short" b "second short" run: echo a, b
Case 1 (ok)
multiShortFlags --help
## Positional arguments can bind to a different variable name in the run block via ident. import std/[strformat] import hwylterm, hwylterm/hwylcli hwylCli: name "posBasic" positionals: input: T string ? "the \"input\"" ident i run: echo fmt"input={i}"
Case 1 (ok)
posBasic --help
Case 2 (ok)
posBasic a
## A variadic positional can appear first, collecting values until the fixed positionals are satisfied from the right. import std/[strformat] import hwylterm, hwylterm/hwylcli hwylCli: name "positionals" positionals: first seq[string] second string third string run: echo fmt"{first=}, {second=}, {third=}"
Case 1 (ok)
posFirst --help
Case 2 (ok)
posFirst a b c d e
Case 3 (error)
posFirst a b
## A variadic positional in the last position collects all remaining arguments after the fixed ones. import std/[strformat] import hwylterm, hwylterm/hwylcli hwylCli: name "posLast" positionals: first string second string third seq[string] run: echo fmt"{first=}, {second=}, {third=}"
Case 1 (ok)
posLast a b
Case 2 (error)
posLast a
## Positionals with non-seq types accept exactly one value each and support type coercion. import std/[strformat] import hwylterm, hwylterm/hwylcli hwylCli: name "positionals" positionals: first int second string third string run: echo fmt"{first=}, {second=}, {third=}"
Case 1 (ok)
posNoMulti 5 b c
Case 2 (error)
posNoMulti 5 b c d
## Settings flow down to subcommands by default; IgnoreParent opts out and per-subcommand settings extend or replace. import hwylterm, hwylterm/hwylcli hwylCli: name "setting-propagate" settings InferShort, LongHelp flags: [misc] input: T string * "the default" ? "input flag" count: T Count * Count(val: 0) ? "a count var with default" subcommands: [one] settings IgnoreParent flags: ^[misc] [two] settings HideDefault flags: ^[misc]
Case 1 (ok)
propagateSetting one -h
Case 2 (ok)
propagateSetting two --help
## Marking a flag Required causes the CLI to exit with an error if it is not provided. import std/strformat import hwylterm, hwylterm/hwylcli hwylCli: name "required-flag" flags: input: S Required T string ? "a required flag!" run: echo fmt"{input=}"
Case 1 (ok)
required --input a
Case 2 (error)
required
Case 3 (ok)
required --help
## preSub and postSub hooks run code around any subcommand dispatch, and can be nested per level. import std/[strformat] import hwylterm, hwylterm/hwylcli hwylCli: name "subcommands" preSub: echo "preSub from root!" postSub: echo "postSub from root!" subcommands: [a] ... "subcommand 'a'" run: echo "inside sub 'a'" [b] ... "subcommand 'b'" run: echo "inside sub 'b'" subcommands: [a] ... "subcommand 'b a'" run: echo "inside sub 'b a'" [c] ... "subcommand 'c'" preSub: echo "preSub from 'c'!" run: echo "inside sub c" subcommands: [a] ... "subcommand 'c a'" run: echo "inside sub 'c a'"
Case 1 (ok)
subHooks a
Case 2 (ok)
subHooks b a
Case 3 (ok)
subHooks c a
## Subcommands each have their own flags, positionals, aliases, and can use hyphenated names. import std/[strformat] import hwylterm, hwylterm/hwylcli hwylCli: name "subcommands" V "0.0.0" subcommands: [help] [version] [a] ... "a subcommand with positionals" positionals: input string outputs seq[string] run: echo fmt"{input=} {outputs=}" [b] ... "a subcommand with flags" flags: input: T string * "testing" ? "some help after default" outputs: T seq[string] run: echo fmt"{input=} {outputs=}" [ccccc] ... "a subcommand with an alias" alias c run: echo "no flags :)" [dd-dd] ... "subcommand with hyphen" run: echo "not all hyphens are flags"
Case 1 (ok)
subcommands a b c
Case 2 (error)
subcommands b b c
Case 3 (ok)
subcommands b --input in --outputs out1 --outputs out2
Case 4 (ok)
subcommands ccccc
Case 5 (ok)
subcommands c
Case 6 (ok)
subcommands dd-dd
Case 7 (error)
subcommands e
Case 8 (ok)
subcommands help --help
Case 9 (ok)
subcommands help dd-dd
Case 10 (ok)
subcommands help c
Case 11 (ok)
subcommands help
## Versions can be set using compile time defines or runtime code import hwylterm, hwylterm/hwylcli hwylCli: name "version" version "0.1.0" run: echo "hello world"
Case 1 (ok)
version --help
Case 2 (ok)
version --version