1842 lines
34 KiB
Go
1842 lines
34 KiB
Go
package wand
|
|
|
|
import (
|
|
"code.squareroundforest.org/arpio/wand/internal/tests/testlib"
|
|
"testing"
|
|
)
|
|
|
|
func TestSuggestHelp(t *testing.T) {
|
|
t.Run("help command", func(t *testing.T) {
|
|
const expect = `Show help:
|
|
foo help`
|
|
|
|
cmd := Command("foo", func() {})
|
|
execTest(t, testCase{impl: cmd, command: "foo bar"}, expect)
|
|
})
|
|
|
|
t.Run("help option", func(t *testing.T) {
|
|
const expect = `Show help:
|
|
foo --help`
|
|
|
|
cmd := Command("foo", func() {}, Command("help", func() {}))
|
|
execTest(t, testCase{impl: cmd, command: "foo bar"}, expect)
|
|
})
|
|
|
|
t.Run("no implementation", func(t *testing.T) {
|
|
const expect = `Show help:
|
|
foo --help`
|
|
|
|
cmd := Group("foo", Command("help", func() {}))
|
|
execTest(t, testCase{impl: cmd, command: "foo baz"}, expect)
|
|
})
|
|
}
|
|
|
|
func TestHelp(t *testing.T) {
|
|
t.Run("no impl", func(t *testing.T) {
|
|
const expect = `foo
|
|
|
|
Synopsis:
|
|
|
|
foo <subcommand>
|
|
|
|
Options:
|
|
|
|
--help: Show help.
|
|
|
|
Subcommands:
|
|
|
|
bar:
|
|
help: Show help.
|
|
|
|
Show help for each subcommand by calling foo <subcommand> help or foo
|
|
<subcommand> --help.`
|
|
|
|
cmd := Group("foo", Command("bar", func() {}))
|
|
execTest(t, testCase{impl: cmd, command: "foo help"}, "", expect)
|
|
})
|
|
|
|
t.Run("no impl option", func(t *testing.T) {
|
|
const expect = `foo
|
|
|
|
Synopsis:
|
|
|
|
foo <subcommand>
|
|
|
|
Options:
|
|
|
|
--help: Show help.
|
|
|
|
Subcommands:
|
|
|
|
bar:
|
|
help: Show help.
|
|
|
|
Show help for each subcommand by calling foo <subcommand> help or foo
|
|
<subcommand> --help.`
|
|
|
|
cmd := Group("foo", Command("bar", func() {}))
|
|
execTest(t, testCase{impl: cmd, command: "foo --help"}, "", expect)
|
|
})
|
|
|
|
t.Run("empty", func(t *testing.T) {
|
|
const expect = `foo
|
|
|
|
Synopsis:
|
|
|
|
foo [options]...
|
|
foo <subcommand>
|
|
|
|
Options:
|
|
|
|
--help: Show help.
|
|
|
|
Subcommands:
|
|
|
|
help: Show help.`
|
|
|
|
f := func() {}
|
|
execTest(t, testCase{impl: f, command: "foo help"}, "", expect)
|
|
})
|
|
|
|
t.Run("empty option", func(t *testing.T) {
|
|
const expect = `foo
|
|
|
|
Synopsis:
|
|
|
|
foo [options]...
|
|
foo <subcommand>
|
|
|
|
Options:
|
|
|
|
--help: Show help.
|
|
|
|
Subcommands:
|
|
|
|
help: Show help.`
|
|
|
|
f := func() {}
|
|
execTest(t, testCase{impl: f, command: "foo --help"}, "", expect)
|
|
})
|
|
|
|
t.Run("args", func(t *testing.T) {
|
|
const expect = `foo
|
|
|
|
Synopsis:
|
|
|
|
foo [options]... [--] <arg1 int> <arg2 int> <arg3 string>
|
|
foo <subcommand>
|
|
|
|
Options:
|
|
|
|
--help: Show help.
|
|
|
|
Subcommands:
|
|
|
|
help: Show help.`
|
|
|
|
f := func(a, b int, c string) {}
|
|
execTest(t, testCase{impl: f, command: "foo help"}, "", expect)
|
|
})
|
|
|
|
t.Run("args option", func(t *testing.T) {
|
|
const expect = `foo
|
|
|
|
Synopsis:
|
|
|
|
foo [options]... [--] <arg1 int> <arg2 int> <arg3 string>
|
|
foo <subcommand>
|
|
|
|
Options:
|
|
|
|
--help: Show help.
|
|
|
|
Subcommands:
|
|
|
|
help: Show help.`
|
|
|
|
f := func(a, b int, c string) {}
|
|
execTest(t, testCase{impl: f, command: "foo --help"}, "", expect)
|
|
})
|
|
|
|
t.Run("variadic args", func(t *testing.T) {
|
|
const expect = `foo
|
|
|
|
Synopsis:
|
|
|
|
foo [options]... [--] <arg1 int> [arg2 int]...
|
|
foo <subcommand>
|
|
|
|
Options:
|
|
|
|
--help: Show help.
|
|
|
|
Subcommands:
|
|
|
|
help: Show help.`
|
|
|
|
f := func(a int, b ...int) {}
|
|
execTest(t, testCase{impl: f, command: "foo help"}, "", expect)
|
|
})
|
|
|
|
t.Run("min variadic args", func(t *testing.T) {
|
|
const expect = `foo
|
|
|
|
Synopsis:
|
|
|
|
foo [options]... [--] <arg1 int> <arg2 int>...
|
|
foo <subcommand>
|
|
|
|
Expecting min 2 total number of arguments.
|
|
|
|
Options:
|
|
|
|
--help: Show help.
|
|
|
|
Subcommands:
|
|
|
|
help: Show help.`
|
|
|
|
cmd := Args(Command("foo", func(a int, b ...int) {}), 2, 0)
|
|
execTest(t, testCase{impl: cmd, command: "foo help"}, "", expect)
|
|
})
|
|
|
|
t.Run("max variadic args", func(t *testing.T) {
|
|
const expect = `foo
|
|
|
|
Synopsis:
|
|
|
|
foo [options]... [--] <arg1 int> [arg2 int]...
|
|
foo <subcommand>
|
|
|
|
Expecting max 3 total number of arguments.
|
|
|
|
Options:
|
|
|
|
--help: Show help.
|
|
|
|
Subcommands:
|
|
|
|
help: Show help.`
|
|
|
|
cmd := Args(Command("foo", func(a int, b ...int) {}), 0, 3)
|
|
execTest(t, testCase{impl: cmd, command: "foo help"}, "", expect)
|
|
})
|
|
|
|
t.Run("min and max variadic args", func(t *testing.T) {
|
|
const expect = `foo
|
|
|
|
Synopsis:
|
|
|
|
foo [options]... [--] <arg1 int> <arg2 int>...
|
|
foo <subcommand>
|
|
|
|
Expecting min 2 and max 3 total number of arguments.
|
|
|
|
Options:
|
|
|
|
--help: Show help.
|
|
|
|
Subcommands:
|
|
|
|
help: Show help.`
|
|
|
|
cmd := Args(Command("foo", func(a int, b ...int) {}), 2, 3)
|
|
execTest(t, testCase{impl: cmd, command: "foo help"}, "", expect)
|
|
})
|
|
|
|
t.Run("options", func(t *testing.T) {
|
|
const expect = `foo
|
|
|
|
Synopsis:
|
|
|
|
foo [options]...
|
|
foo <subcommand>
|
|
|
|
Options:
|
|
|
|
--bar string [*]: Bars, any number.
|
|
--duration duration: is another option.
|
|
--foo int: is an option.
|
|
--some any: is an option of any type.
|
|
--time time: is the third option here.
|
|
--help: Show help.
|
|
|
|
Hints:
|
|
|
|
- Options marked with [*] accept any number of instances.
|
|
- Option values can be set both via = or just separated by space.
|
|
|
|
Subcommands:
|
|
|
|
help: Show help.`
|
|
|
|
execTest(t, testCase{impl: testlib.Baz, command: "foo help"}, "", expect)
|
|
})
|
|
|
|
t.Run("options option", func(t *testing.T) {
|
|
const expect = `foo
|
|
|
|
Synopsis:
|
|
|
|
foo [options]...
|
|
foo <subcommand>
|
|
|
|
Options:
|
|
|
|
--bar string [*]: Bars, any number.
|
|
--duration duration: is another option.
|
|
--foo int: is an option.
|
|
--some any: is an option of any type.
|
|
--time time: is the third option here.
|
|
--help: Show help.
|
|
|
|
Hints:
|
|
|
|
- Options marked with [*] accept any number of instances.
|
|
- Option values can be set both via = or just separated by space.
|
|
|
|
Subcommands:
|
|
|
|
help: Show help.`
|
|
|
|
execTest(t, testCase{impl: testlib.Baz, command: "foo --help"}, "", expect)
|
|
})
|
|
|
|
t.Run("help option shadowed", func(t *testing.T) {
|
|
const expect = `foo
|
|
|
|
Synopsis:
|
|
|
|
foo [options]...
|
|
foo <subcommand>
|
|
|
|
Options:
|
|
|
|
--help bool: Custom help.
|
|
|
|
Hints:
|
|
|
|
- Bool options can be used with implicit true values.
|
|
- Option values can be set both via = or just separated by space.
|
|
|
|
Subcommands:
|
|
|
|
help: Show help.`
|
|
|
|
execTest(t, testCase{impl: testlib.CustomHelp, command: "foo help"}, "", expect)
|
|
})
|
|
|
|
t.Run("config option", func(t *testing.T) {
|
|
const expect = `foo
|
|
|
|
Synopsis:
|
|
|
|
foo [options]...
|
|
foo <subcommand>
|
|
|
|
Options:
|
|
|
|
--config: Configuration file.
|
|
--help: Show help.
|
|
|
|
Subcommands:
|
|
|
|
help: Show help.`
|
|
|
|
execTest(
|
|
t,
|
|
testCase{impl: func() {}, mergeConfTyped: []Config{ConfigFromOption()}, command: "foo help"},
|
|
"",
|
|
expect,
|
|
)
|
|
})
|
|
|
|
t.Run("grouped options", func(t *testing.T) {
|
|
const expect = `foo
|
|
|
|
Synopsis:
|
|
|
|
foo [options]...
|
|
foo <subcommand>
|
|
|
|
Options:
|
|
|
|
--bar, -b string:
|
|
--foo, -f bool:
|
|
--help: Show help.
|
|
|
|
Hints:
|
|
|
|
- Bool options can be used with implicit true values.
|
|
- The short form of bool options can be combined. The last short form
|
|
does not need to be a bool option. E.g. -abc=42.
|
|
- Option values can be set both via = or just separated by space.
|
|
|
|
Subcommands:
|
|
|
|
help: Show help.`
|
|
|
|
execTest(
|
|
t,
|
|
testCase{
|
|
impl: ShortForm(
|
|
Command("foo", func(struct {
|
|
Foo bool
|
|
Bar string
|
|
}) {
|
|
}),
|
|
"f", "foo", "b", "bar",
|
|
),
|
|
command: "foo help",
|
|
},
|
|
"",
|
|
expect,
|
|
)
|
|
})
|
|
|
|
t.Run("single option hint", func(t *testing.T) {
|
|
const expect = `foo
|
|
|
|
Synopsis:
|
|
|
|
foo [options]...
|
|
foo <subcommand>
|
|
|
|
Options:
|
|
|
|
--foo int:
|
|
--help: Show help.
|
|
|
|
Option values can be set both via = or just separated by space.
|
|
|
|
Subcommands:
|
|
|
|
help: Show help.`
|
|
|
|
execTest(
|
|
t,
|
|
testCase{
|
|
impl: func(struct{ Foo int }) {},
|
|
command: "foo help",
|
|
},
|
|
"",
|
|
expect,
|
|
)
|
|
})
|
|
|
|
t.Run("description", func(t *testing.T) {
|
|
const expect = `foo - sums three numbers
|
|
|
|
Synopsis:
|
|
|
|
foo [options]... [--] <a int> <b int> <c int>
|
|
foo <subcommand>
|
|
|
|
sums three numbers. It prints the sum to stdout.
|
|
|
|
The input numbers can be any integer.
|
|
|
|
Options:
|
|
|
|
--help: Show help.
|
|
|
|
Subcommands:
|
|
|
|
help: Show help.`
|
|
|
|
execTest(t, testCase{impl: testlib.Foo, command: "foo help"}, "", expect)
|
|
})
|
|
|
|
t.Run("help subcommand shadowed", func(t *testing.T) {
|
|
const expect = `foo
|
|
|
|
Synopsis:
|
|
|
|
foo [options]...
|
|
foo <subcommand>
|
|
|
|
Options:
|
|
|
|
--help: Show help.
|
|
|
|
Subcommands:
|
|
|
|
help:
|
|
|
|
Show help for each subcommand by calling foo <subcommand> help or foo
|
|
<subcommand> --help.`
|
|
|
|
execTest(
|
|
t,
|
|
testCase{impl: Command("foo", func() {}, Command("help", func() {})), command: "foo --help"},
|
|
"",
|
|
expect,
|
|
)
|
|
})
|
|
|
|
t.Run("default subcommand", func(t *testing.T) {
|
|
const expect = `foo
|
|
|
|
Synopsis:
|
|
|
|
foo [options]... [--] <arg1 int>
|
|
foo <subcommand>
|
|
|
|
Options:
|
|
|
|
--help: Show help.
|
|
|
|
Subcommands:
|
|
|
|
bar (default):
|
|
help: Show help.
|
|
|
|
Show help for each subcommand by calling foo <subcommand> help or foo
|
|
<subcommand> --help.`
|
|
|
|
execTest(
|
|
t,
|
|
testCase{impl: Group("foo", Default(Command("bar", func(a int) {}))), command: "foo help"},
|
|
"",
|
|
expect,
|
|
)
|
|
})
|
|
|
|
t.Run("subcommand description", func(t *testing.T) {
|
|
const expect = `bar
|
|
|
|
Synopsis:
|
|
|
|
bar <subcommand>
|
|
|
|
Options:
|
|
|
|
--help: Show help.
|
|
|
|
Subcommands:
|
|
|
|
foo: sums three numbers
|
|
help: Show help.
|
|
version: Show version information.
|
|
|
|
Show help for each subcommand by calling bar <subcommand> help or bar
|
|
<subcommand> --help.`
|
|
|
|
execTest(
|
|
t,
|
|
testCase{impl: Version(Group("bar", Command("foo", testlib.Foo)), "v1"), command: "bar help"},
|
|
"",
|
|
expect,
|
|
)
|
|
})
|
|
}
|
|
|
|
func TestMan(t *testing.T) {
|
|
t.Run("basic", func(t *testing.T) {
|
|
const expect = `.TH "foo" 1 "December 2025" "2025-12-03-abcd123" "User Commands"
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBName:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
foo - sums three numbers
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBSynopsis:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
foo [options]... [--] <a int> <b int> <c int>
|
|
foo <subcommand>
|
|
.fi
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBDescription:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
sums three numbers. It prints the sum to stdout.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
The input numbers can be any integer.
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBOptions:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 12
|
|
.ti 4
|
|
--help:\~Show help.
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBSubcommands:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 10
|
|
.ti 4
|
|
help:\~Show help.`
|
|
|
|
execTest(
|
|
t,
|
|
testCase{
|
|
impl: testlib.Foo,
|
|
command: "foo",
|
|
env: "_wandgenerate=man;_wandgeneratedate=2025-12-03;_wandgenerateversion=2025-12-03-abcd123",
|
|
},
|
|
"",
|
|
expect,
|
|
)
|
|
})
|
|
|
|
t.Run("with subcommand", func(t *testing.T) {
|
|
const expect = `.TH "foo" 1 "December 2025" "2025-12-03-abcd123" "User Commands"
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBName:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
foo
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBSynopsis:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
foo [options]...
|
|
foo <subcommand>
|
|
.fi
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBOptions:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 12
|
|
.ti 4
|
|
--help:\~Show help.
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBSubcommands:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
Show help for each subcommand by calling <command> help or <command> --help.
|
|
.br
|
|
.sp 1v
|
|
.in 2
|
|
.ti 2
|
|
\fBfoo bar\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBSynopsis:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
foo bar [options]...
|
|
foo bar <subcommand>
|
|
.fi
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBOptions:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 12
|
|
.ti 4
|
|
--help:\~Show help.`
|
|
|
|
execTest(
|
|
t,
|
|
testCase{
|
|
impl: Command("foo", func() {}, Command("bar", func() {})),
|
|
command: "foo",
|
|
env: "_wandgenerate=man;_wandgeneratedate=2025-12-03;_wandgenerateversion=2025-12-03-abcd123",
|
|
},
|
|
"",
|
|
expect,
|
|
)
|
|
})
|
|
|
|
t.Run("types of subcommand", func(t *testing.T) {
|
|
const expect = `.TH "foo" 1 "December 2025" "2025-12-03-abcd123" "User Commands"
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBName:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
foo
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBSynopsis:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
foo [options]...
|
|
foo <subcommand>
|
|
.fi
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBOptions:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 12
|
|
.ti 4
|
|
--help:\~Show help.
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBSubcommands:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
Show help for each subcommand by calling <command> help or <command> --help.
|
|
.br
|
|
.sp 1v
|
|
.in 2
|
|
.ti 2
|
|
\fBfoo bar (default)\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBSynopsis:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
foo bar [options]...
|
|
foo bar <subcommand>
|
|
.fi
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBOptions:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 12
|
|
.ti 4
|
|
--help:\~Show help.
|
|
.br
|
|
.sp 1v
|
|
.in 2
|
|
.ti 2
|
|
\fBfoo real-foo\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBSynopsis:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
foo real-foo [options]... [--] <a int> <b int> <c int>
|
|
foo real-foo <subcommand>
|
|
.fi
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBDescription:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Foo sums three numbers. It prints the sum to stdout.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
The input numbers can be any integer.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBOptions:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 12
|
|
.ti 4
|
|
--help:\~Show help.
|
|
.br
|
|
.sp 1v
|
|
.in 2
|
|
.ti 2
|
|
\fBfoo version\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Show version.`
|
|
|
|
execTest(
|
|
t,
|
|
testCase{
|
|
impl: Version(
|
|
Group("foo", Default(Command("bar", func() {})), Command("real-foo", testlib.Foo)),
|
|
"v1",
|
|
),
|
|
command: "foo",
|
|
env: "_wandgenerate=man;_wandgeneratedate=2025-12-03;_wandgenerateversion=2025-12-03-abcd123",
|
|
},
|
|
"",
|
|
expect,
|
|
)
|
|
})
|
|
|
|
t.Run("sub-subcommands", func(t *testing.T) {
|
|
const expect = `.TH "foo" 1 "December 2025" "2025-12-03-abcd123" "User Commands"
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBName:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
foo
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBSynopsis:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
foo [options]...
|
|
foo <subcommand>
|
|
.fi
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBOptions:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 12
|
|
.ti 4
|
|
--help:\~Show help.
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBSubcommands:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
Show help for each subcommand by calling <command> help or <command> --help.
|
|
.br
|
|
.sp 1v
|
|
.in 2
|
|
.ti 2
|
|
\fBfoo bar\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBSynopsis:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
foo bar [options]...
|
|
foo bar <subcommand>
|
|
.fi
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBOptions:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 12
|
|
.ti 4
|
|
--help:\~Show help.
|
|
.br
|
|
.sp 1v
|
|
.in 2
|
|
.ti 2
|
|
\fBfoo bar baz\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBSynopsis:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
foo bar baz [options]...
|
|
foo bar baz <subcommand>
|
|
.fi
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBOptions:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 12
|
|
.ti 4
|
|
--help:\~Show help.`
|
|
|
|
execTest(
|
|
t,
|
|
testCase{
|
|
impl: Command("foo", func() {}, Command("bar", func() {}, Command("baz", func() {}))),
|
|
command: "foo",
|
|
env: "_wandgenerate=man;_wandgeneratedate=2025-12-03;_wandgenerateversion=2025-12-03-abcd123",
|
|
},
|
|
"",
|
|
expect,
|
|
)
|
|
})
|
|
|
|
t.Run("options", func(t *testing.T) {
|
|
const expect = `.TH "foo" 1 "December 2025" "2025-12-03-abcd123" "User Commands"
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBName:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
foo
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBSynopsis:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
foo [options]...
|
|
foo <subcommand>
|
|
.fi
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBOptions:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 25
|
|
.ti 4
|
|
--bar string [*]:\~\~\~\~Bars, any number.
|
|
.br
|
|
.in 25
|
|
.ti 4
|
|
--duration duration:\~is another option.
|
|
.br
|
|
.in 25
|
|
.ti 4
|
|
--foo int:\~\~\~\~\~\~\~\~\~\~\~is an option.
|
|
.br
|
|
.in 25
|
|
.ti 4
|
|
--some any:\~\~\~\~\~\~\~\~\~\~is an option of any type.
|
|
.br
|
|
.in 25
|
|
.ti 4
|
|
--time time:\~\~\~\~\~\~\~\~\~is the third option here.
|
|
.br
|
|
.in 25
|
|
.ti 4
|
|
--config:\~\~\~\~\~\~\~\~\~\~\~\~Configuration file.
|
|
.br
|
|
.in 25
|
|
.ti 4
|
|
--help:\~\~\~\~\~\~\~\~\~\~\~\~\~\~Show help.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Hints:
|
|
.br
|
|
.sp 1v
|
|
.in 6
|
|
.ti 4
|
|
\(bu\~Options marked with [*] accept any number of instances.
|
|
.br
|
|
.in 6
|
|
.ti 4
|
|
\(bu\~Option values can be set both via = or just separated by space.
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBSubcommands:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
Show help for each subcommand by calling <command> help or <command> --help.
|
|
.br
|
|
.sp 1v
|
|
.in 2
|
|
.ti 2
|
|
\fBfoo bar\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBSynopsis:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
foo bar [options]...
|
|
foo bar <subcommand>
|
|
.fi
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBOptions:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 15
|
|
.ti 4
|
|
--bar int:\~
|
|
.br
|
|
.in 15
|
|
.ti 4
|
|
--config:\~\~Configuration file.
|
|
.br
|
|
.in 15
|
|
.ti 4
|
|
--help:\~\~\~\~Show help.
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBEnvironment variables:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Every command line option's value can also be provided as an environment variable. Environment variable names need to use snake casing like myapp_foo_bar_baz or MYAPP_FOO_BAR_BAZ, or other casing that doesn't include the '-' dash character, and they need to be prefixed with the name of the application, as in the base name of the command.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
When both the environment variable and the command line option is defined, the command line option overrides the environment variable. Multiple values for the same environment variable can be defined by concatenating the values with the ':' separator character. When overriding multiple values with command line options, all the environment values of the same field are dropped.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBExample environment variable:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
FOO_FOO=42
|
|
.fi
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBConfiguration:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Every command line option's value can also be provided as an entry in a configuration file.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Configuration file entries can use keys with different casings, e.g. snake case foo_bar_baz, or kebab case foo-bar-baz. The keys of the entries can use a limited set of characters: [a-zA-Z0-9_-], and the first character needs to be one of [a-zA-Z_]. Entry values can consist of any characters, except for newline, control characters, " (quote) and \\ (backslash), or the values can be quoted, in which case they can consist of any characters, spanning multiple lines, and only the " (quote) and \\ (backslash) characters need to be escaped by the \\ (backslash) character.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Configuration files allow multiple entries with the same key, when if the associated command line option also allows multiple instances (marked with [*]). When an entry is defined multiple configuration files, the effective value is overridden in the order of the definition of the possible config files (see the listing order below). To discard values defined in the overridden config files without defining new ones, we can set entries with only the key, omitting the = key/value separator. Entries in the config files are overridden by the environment variables, when defined, and by the command line options when defined.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Config files marked as optional don't need to be present in the file system, but if they exist, then they must contain valid configuration syntax which is wand's flavor of .ini files (https://code.squareroundforest.org/arpio/wand/src/branch/main/ini.treerack).
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBConfiguration files:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 10
|
|
.ti 8
|
|
\(bu\~zero or more configuration files defined by the --config option
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBExample configuration entry:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
# Default value for --foo:
|
|
foo = 42
|
|
.fi
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBExample for discarding an inherited entry:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
# Discarding an inherited entry:
|
|
foo
|
|
.fi`
|
|
|
|
execTest(
|
|
t,
|
|
testCase{
|
|
impl: Command("foo", testlib.Baz, Command("bar", func(struct{ Bar int }) {})),
|
|
command: "foo",
|
|
env: "_wandgenerate=man;_wandgeneratedate=2025-12-03;_wandgenerateversion=2025-12-03-abcd123",
|
|
mergeConfTyped: []Config{ConfigFromOption()},
|
|
},
|
|
"",
|
|
expect,
|
|
)
|
|
})
|
|
|
|
t.Run("short form grouping", func(t *testing.T) {
|
|
const expect = `.TH "foo" 1 "December 2025" "2025-12-03-abcd123" "User Commands"
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBName:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
foo
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBSynopsis:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
foo [options]...
|
|
foo <subcommand>
|
|
.fi
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBOptions:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 20
|
|
.ti 4
|
|
--bar, -b int:\~\~
|
|
.br
|
|
.in 20
|
|
.ti 4
|
|
--foo, -f bool:\~
|
|
.br
|
|
.in 20
|
|
.ti 4
|
|
--help:\~\~\~\~\~\~\~\~\~Show help.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Hints:
|
|
.br
|
|
.sp 1v
|
|
.in 6
|
|
.ti 4
|
|
\(bu\~Bool options can be used with implicit true values.
|
|
.br
|
|
.in 6
|
|
.ti 4
|
|
\(bu\~The short form of bool options can be combined. The last short form does not need to be a bool option. E.g. -abc=42.
|
|
.br
|
|
.in 6
|
|
.ti 4
|
|
\(bu\~Option values can be set both via = or just separated by space.
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBSubcommands:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 10
|
|
.ti 4
|
|
help:\~Show help.
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBEnvironment variables:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Every command line option's value can also be provided as an environment variable. Environment variable names need to use snake casing like myapp_foo_bar_baz or MYAPP_FOO_BAR_BAZ, or other casing that doesn't include the '-' dash character, and they need to be prefixed with the name of the application, as in the base name of the command.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
When both the environment variable and the command line option is defined, the command line option overrides the environment variable. Multiple values for the same environment variable can be defined by concatenating the values with the ':' separator character. When overriding multiple values with command line options, all the environment values of the same field are dropped.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBExample environment variable:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
FOO_FOO=true
|
|
.fi`
|
|
|
|
execTest(
|
|
t,
|
|
testCase{
|
|
impl: ShortForm(Command("foo", func(struct {
|
|
Foo bool
|
|
Bar int
|
|
}) {
|
|
}), "f", "foo", "b", "bar"),
|
|
command: "foo",
|
|
env: "_wandgenerate=man;_wandgeneratedate=2025-12-03;_wandgenerateversion=2025-12-03-abcd123",
|
|
},
|
|
"",
|
|
expect,
|
|
)
|
|
})
|
|
|
|
t.Run("command name unsupported by env", func(t *testing.T) {
|
|
const expect = `.TH "foo.bar" 1 "December 2025" "2025-12-03-abcd123" "User Commands"
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBName:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
foo.bar
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBSynopsis:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
foo.bar [options]...
|
|
foo.bar <subcommand>
|
|
.fi
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBOptions:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 12
|
|
.ti 4
|
|
--help:\~Show help.
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBSubcommands:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 10
|
|
.ti 4
|
|
help:\~Show help.`
|
|
|
|
execTest(
|
|
t,
|
|
testCase{
|
|
impl: func() {},
|
|
command: "foo.bar",
|
|
env: "_wandgenerate=man;_wandgeneratedate=2025-12-03;_wandgenerateversion=2025-12-03-abcd123",
|
|
},
|
|
"",
|
|
expect,
|
|
)
|
|
})
|
|
|
|
t.Run("standard config", func(t *testing.T) {
|
|
const expect = `.TH "foo" 1 "December 2025" "2025-12-03-abcd123" "User Commands"
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBName:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
foo
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBSynopsis:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
foo [options]...
|
|
foo <subcommand>
|
|
.fi
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBOptions:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 15
|
|
.ti 4
|
|
--foo int:\~
|
|
.br
|
|
.in 15
|
|
.ti 4
|
|
--config:\~\~Configuration file.
|
|
.br
|
|
.in 15
|
|
.ti 4
|
|
--help:\~\~\~\~Show help.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Option values can be set both via = or just separated by space.
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBSubcommands:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 10
|
|
.ti 4
|
|
help:\~Show help.
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBEnvironment variables:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Every command line option's value can also be provided as an environment variable. Environment variable names need to use snake casing like myapp_foo_bar_baz or MYAPP_FOO_BAR_BAZ, or other casing that doesn't include the '-' dash character, and they need to be prefixed with the name of the application, as in the base name of the command.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
When both the environment variable and the command line option is defined, the command line option overrides the environment variable. Multiple values for the same environment variable can be defined by concatenating the values with the ':' separator character. When overriding multiple values with command line options, all the environment values of the same field are dropped.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBExample environment variable:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
FOO_FOO=42
|
|
.fi
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBConfiguration:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Every command line option's value can also be provided as an entry in a configuration file.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Configuration file entries can use keys with different casings, e.g. snake case foo_bar_baz, or kebab case foo-bar-baz. The keys of the entries can use a limited set of characters: [a-zA-Z0-9_-], and the first character needs to be one of [a-zA-Z_]. Entry values can consist of any characters, except for newline, control characters, " (quote) and \\ (backslash), or the values can be quoted, in which case they can consist of any characters, spanning multiple lines, and only the " (quote) and \\ (backslash) characters need to be escaped by the \\ (backslash) character.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Configuration files allow multiple entries with the same key, when if the associated command line option also allows multiple instances (marked with [*]). When an entry is defined multiple configuration files, the effective value is overridden in the order of the definition of the possible config files (see the listing order below). To discard values defined in the overridden config files without defining new ones, we can set entries with only the key, omitting the = key/value separator. Entries in the config files are overridden by the environment variables, when defined, and by the command line options when defined.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Config files marked as optional don't need to be present in the file system, but if they exist, then they must contain valid configuration syntax which is wand's flavor of .ini files (https://code.squareroundforest.org/arpio/wand/src/branch/main/ini.treerack).
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBConfiguration files:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 10
|
|
.ti 8
|
|
\(bu\~/etc/foo/config (optional)
|
|
.br
|
|
.in 10
|
|
.ti 8
|
|
\(bu\~/Users/arpio/.foo/config (optional)
|
|
.br
|
|
.in 10
|
|
.ti 8
|
|
\(bu\~/Users/arpio/.config/foo/config (optional)
|
|
.br
|
|
.in 10
|
|
.ti 8
|
|
\(bu\~zero or more configuration files defined by the --config option
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBExample configuration entry:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
# Default value for --foo:
|
|
foo = 42
|
|
.fi
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBExample for discarding an inherited entry:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
# Discarding an inherited entry:
|
|
foo
|
|
.fi`
|
|
|
|
execTest(
|
|
t,
|
|
testCase{
|
|
impl: func(struct{ Foo int }) {},
|
|
command: "foo",
|
|
env: "_wandgenerate=man;_wandgeneratedate=2025-12-03;_wandgenerateversion=2025-12-03-abcd123",
|
|
mergeConfTyped: []Config{SystemConfig()},
|
|
},
|
|
"",
|
|
expect,
|
|
)
|
|
})
|
|
|
|
t.Run("bool option in env and config", func(t *testing.T) {
|
|
const expect = `.TH "foo" 1 "December 2025" "2025-12-03-abcd123" "User Commands"
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBName:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
foo
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBSynopsis:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
foo [options]...
|
|
foo <subcommand>
|
|
.fi
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBOptions:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 16
|
|
.ti 4
|
|
--foo bool:\~
|
|
.br
|
|
.in 16
|
|
.ti 4
|
|
--config:\~\~\~Configuration file.
|
|
.br
|
|
.in 16
|
|
.ti 4
|
|
--help:\~\~\~\~\~Show help.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Hints:
|
|
.br
|
|
.sp 1v
|
|
.in 6
|
|
.ti 4
|
|
\(bu\~Bool options can be used with implicit true values.
|
|
.br
|
|
.in 6
|
|
.ti 4
|
|
\(bu\~Option values can be set both via = or just separated by space.
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBSubcommands:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 10
|
|
.ti 4
|
|
help:\~Show help.
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBEnvironment variables:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Every command line option's value can also be provided as an environment variable. Environment variable names need to use snake casing like myapp_foo_bar_baz or MYAPP_FOO_BAR_BAZ, or other casing that doesn't include the '-' dash character, and they need to be prefixed with the name of the application, as in the base name of the command.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
When both the environment variable and the command line option is defined, the command line option overrides the environment variable. Multiple values for the same environment variable can be defined by concatenating the values with the ':' separator character. When overriding multiple values with command line options, all the environment values of the same field are dropped.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBExample environment variable:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
FOO_FOO=true
|
|
.fi
|
|
.br
|
|
.sp 1v
|
|
.in 0
|
|
.ti 0
|
|
\fBConfiguration:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Every command line option's value can also be provided as an entry in a configuration file.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Configuration file entries can use keys with different casings, e.g. snake case foo_bar_baz, or kebab case foo-bar-baz. The keys of the entries can use a limited set of characters: [a-zA-Z0-9_-], and the first character needs to be one of [a-zA-Z_]. Entry values can consist of any characters, except for newline, control characters, " (quote) and \\ (backslash), or the values can be quoted, in which case they can consist of any characters, spanning multiple lines, and only the " (quote) and \\ (backslash) characters need to be escaped by the \\ (backslash) character.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Configuration files allow multiple entries with the same key, when if the associated command line option also allows multiple instances (marked with [*]). When an entry is defined multiple configuration files, the effective value is overridden in the order of the definition of the possible config files (see the listing order below). To discard values defined in the overridden config files without defining new ones, we can set entries with only the key, omitting the = key/value separator. Entries in the config files are overridden by the environment variables, when defined, and by the command line options when defined.
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
Config files marked as optional don't need to be present in the file system, but if they exist, then they must contain valid configuration syntax which is wand's flavor of .ini files (https://code.squareroundforest.org/arpio/wand/src/branch/main/ini.treerack).
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBConfiguration files:\fR
|
|
.br
|
|
.sp 1v
|
|
.in 10
|
|
.ti 8
|
|
\(bu\~/etc/foo/config (optional)
|
|
.br
|
|
.in 10
|
|
.ti 8
|
|
\(bu\~/Users/arpio/.foo/config (optional)
|
|
.br
|
|
.in 10
|
|
.ti 8
|
|
\(bu\~/Users/arpio/.config/foo/config (optional)
|
|
.br
|
|
.in 10
|
|
.ti 8
|
|
\(bu\~zero or more configuration files defined by the --config option
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBExample configuration entry:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
# Default value for --foo:
|
|
foo = true
|
|
.fi
|
|
.br
|
|
.sp 1v
|
|
.in 4
|
|
.ti 4
|
|
\fBExample for discarding an inherited entry:\fR
|
|
.br
|
|
.sp 1v
|
|
.nf
|
|
# Discarding an inherited entry:
|
|
foo
|
|
.fi`
|
|
|
|
execTest(
|
|
t,
|
|
testCase{
|
|
impl: func(struct{ Foo bool }) {},
|
|
command: "foo",
|
|
env: "_wandgenerate=man;_wandgeneratedate=2025-12-03;_wandgenerateversion=2025-12-03-abcd123",
|
|
mergeConfTyped: []Config{SystemConfig()},
|
|
},
|
|
"",
|
|
expect,
|
|
)
|
|
})
|
|
}
|
|
|
|
func TestMarkdown(t *testing.T) {
|
|
t.Run("basic", func(t *testing.T) {
|
|
const expect = `# foo - sums three numbers
|
|
|
|
## Synopsis:
|
|
|
|
` + "```" + `
|
|
foo [options]... [--] <a int> <b int> <c int>
|
|
foo <subcommand>
|
|
` + "```" + `
|
|
|
|
## Description:
|
|
|
|
sums three numbers. It prints the sum to stdout.
|
|
|
|
The input numbers can be any integer.
|
|
|
|
## Options:
|
|
|
|
- --help: Show help.
|
|
|
|
## Subcommands:
|
|
|
|
- help: Show help.`
|
|
|
|
execTest(
|
|
t,
|
|
testCase{
|
|
impl: testlib.Foo,
|
|
command: "foo",
|
|
env: "_wandgenerate=markdown;_wandgeneratedate=2025-12-03;_wandgenerateversion=2025-12-03-abcd123",
|
|
},
|
|
"",
|
|
expect,
|
|
)
|
|
})
|
|
|
|
t.Run("with subcommand", func(t *testing.T) {
|
|
const expect = `# foo
|
|
|
|
## Synopsis:
|
|
|
|
` + "```" + `
|
|
foo [options]...
|
|
foo <subcommand>
|
|
` + "```" + `
|
|
|
|
## Options:
|
|
|
|
- --help: Show help.
|
|
|
|
## Subcommands:
|
|
|
|
Show help for each subcommand by calling \<command\> help or \<command\> --help.
|
|
|
|
### foo bar
|
|
|
|
#### Synopsis:
|
|
|
|
` + "```" + `
|
|
foo bar [options]...
|
|
foo bar <subcommand>
|
|
` + "```" + `
|
|
|
|
#### Options:
|
|
|
|
- --help: Show help.`
|
|
|
|
execTest(
|
|
t,
|
|
testCase{
|
|
impl: Command("foo", func() {}, Command("bar", func() {})),
|
|
command: "foo",
|
|
env: "_wandgenerate=markdown;_wandgeneratedate=2025-12-03;_wandgenerateversion=2025-12-03-abcd123",
|
|
},
|
|
"",
|
|
expect,
|
|
)
|
|
})
|
|
|
|
t.Run("options", func(t *testing.T) {
|
|
const expect = `# foo
|
|
|
|
## Synopsis:
|
|
|
|
` + "```" + `
|
|
foo [options]...
|
|
foo <subcommand>
|
|
` + "```" + `
|
|
|
|
## Options:
|
|
|
|
- --bar string \[\*\]: Bars, any number.
|
|
- --duration duration: is another option.
|
|
- --foo int: is an option.
|
|
- --some any: is an option of any type.
|
|
- --time time: is the third option here.
|
|
- --config: Configuration file.
|
|
- --help: Show help.
|
|
|
|
Hints:
|
|
|
|
- Options marked with \[\*\] accept any number of instances.
|
|
- Option values can be set both via = or just separated by space.
|
|
|
|
## Subcommands:
|
|
|
|
- help: Show help.
|
|
|
|
## Environment variables:
|
|
|
|
Every command line option's value can also be provided as an environment variable. Environment variable names
|
|
need to use snake casing like myapp\_foo\_bar\_baz or MYAPP\_FOO\_BAR\_BAZ, or other casing that doesn't include the
|
|
'-' dash character, and they need to be prefixed with the name of the application, as in the base name of the
|
|
command.
|
|
|
|
When both the environment variable and the command line option is defined, the command line option overrides the
|
|
environment variable. Multiple values for the same environment variable can be defined by concatenating the
|
|
values with the ':' separator character. When overriding multiple values with command line options, all the
|
|
environment values of the same field are dropped.
|
|
|
|
### Example environment variable:
|
|
|
|
` + "```" + `
|
|
FOO_FOO=42
|
|
` + "```" + `
|
|
|
|
## Configuration:
|
|
|
|
Every command line option's value can also be provided as an entry in a configuration file.
|
|
|
|
Configuration file entries can use keys with different casings, e.g. snake case foo\_bar\_baz, or kebab case
|
|
foo-bar-baz. The keys of the entries can use a limited set of characters: \[a-zA-Z0-9\_-\], and the first character
|
|
needs to be one of \[a-zA-Z\_\]. Entry values can consist of any characters, except for newline, control
|
|
characters, " (quote) and \\ (backslash), or the values can be quoted, in which case they can consist of any
|
|
characters, spanning multiple lines, and only the " (quote) and \\ (backslash) characters need to be escaped by
|
|
the \\ (backslash) character.
|
|
|
|
Configuration files allow multiple entries with the same key, when if the associated command line option also
|
|
allows multiple instances (marked with \[\*\]). When an entry is defined multiple configuration files, the
|
|
effective value is overridden in the order of the definition of the possible config files (see the listing order
|
|
below). To discard values defined in the overridden config files without defining new ones, we can set entries
|
|
with only the key, omitting the = key/value separator. Entries in the config files are overridden by the
|
|
environment variables, when defined, and by the command line options when defined.
|
|
|
|
Config files marked as optional don't need to be present in the file system, but if they exist, then they must
|
|
contain valid configuration syntax which is wand's flavor of .ini files
|
|
(https://code.squareroundforest.org/arpio/wand/src/branch/main/ini.treerack).
|
|
|
|
### Configuration files:
|
|
|
|
- zero or more configuration files defined by the --config option
|
|
|
|
### Example configuration entry:
|
|
|
|
` + "```" + `
|
|
# Default value for --foo:
|
|
foo = 42
|
|
` + "```" + `
|
|
|
|
### Example for discarding an inherited entry:
|
|
|
|
` + "```" + `
|
|
# Discarding an inherited entry:
|
|
foo
|
|
` + "```"
|
|
|
|
execTest(
|
|
t,
|
|
testCase{
|
|
impl: testlib.Baz,
|
|
command: "foo",
|
|
env: "_wandgenerate=markdown;_wandgeneratedate=2025-12-03;_wandgenerateversion=2025-12-03-abcd123",
|
|
mergeConfTyped: []Config{ConfigFromOption()},
|
|
},
|
|
"",
|
|
expect,
|
|
)
|
|
})
|
|
}
|
|
|
|
func TestVersion(t *testing.T) {
|
|
const expect = `v1`
|
|
|
|
execTest(
|
|
t,
|
|
testCase{
|
|
impl: Version(Command("foo", func() {}), "v1"),
|
|
command: "foo version",
|
|
},
|
|
"",
|
|
expect,
|
|
)
|
|
}
|