2025-12-10 20:31:10 +01:00
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 : Duration is another option .
-- foo int : Foo is an option .
2025-12-30 16:52:10 +01:00
-- some any : Some is an option of any type .
2025-12-10 20:31:10 +01:00
-- time 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 : Duration is another option .
-- foo int : Foo is an option .
2025-12-30 16:52:10 +01:00
-- some any : Some is an option of any type .
2025-12-10 20:31:10 +01:00
-- time 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
2025-12-30 16:52:10 +01:00
} ) {
} ) ,
2025-12-10 20:31:10 +01:00
"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 - Foo sums three numbers
Synopsis :
foo [ options ] ... [ -- ] < a int > < b int > < c int >
foo < subcommand >
Foo 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 : 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 1 v
. in 0
. ti 0
\ fBName : \ fR
. br
. sp 1 v
. in 4
. ti 4
foo - Foo sums three numbers
. br
. sp 1 v
. in 0
. ti 0
\ fBSynopsis : \ fR
. br
. sp 1 v
. nf
foo [ options ] ... [ -- ] < a int > < b int > < c int >
foo < subcommand >
. fi
. br
. sp 1 v
. in 0
. ti 0
\ fBDescription : \ fR
. br
. sp 1 v
. in 4
. ti 4
Foo sums three numbers . It prints the sum to stdout .
. br
. sp 1 v
. in 4
. ti 4
The input numbers can be any integer .
. br
. sp 1 v
. in 0
. ti 0
\ fBOptions : \ fR
. br
. sp 1 v
. in 12
. ti 4
-- help : \ ~ Show help .
. br
. sp 1 v
. in 0
. ti 0
\ fBSubcommands : \ fR
. br
. sp 1 v
. 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 1 v
. in 0
. ti 0
\ fBName : \ fR
. br
. sp 1 v
. in 4
. ti 4
foo
. br
. sp 1 v
. in 0
. ti 0
\ fBSynopsis : \ fR
. br
. sp 1 v
. nf
foo [ options ] ...
foo < subcommand >
. fi
. br
. sp 1 v
. in 0
. ti 0
\ fBOptions : \ fR
. br
. sp 1 v
. in 12
. ti 4
-- help : \ ~ Show help .
. br
. sp 1 v
. in 0
. ti 0
\ fBSubcommands : \ fR
. br
. sp 1 v
. in 0
. ti 0
Show help for each subcommand by calling < command > help or < command > -- help .
. br
. sp 1 v
. in 2
. ti 2
\ fBfoo bar \ fR
. br
. sp 1 v
. in 4
. ti 4
\ fBSynopsis : \ fR
. br
. sp 1 v
. nf
foo bar [ options ] ...
foo bar < subcommand >
. fi
. br
. sp 1 v
. in 4
. ti 4
\ fBOptions : \ fR
. br
. sp 1 v
. 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 1 v
. in 0
. ti 0
\ fBName : \ fR
. br
. sp 1 v
. in 4
. ti 4
foo
. br
. sp 1 v
. in 0
. ti 0
\ fBSynopsis : \ fR
. br
. sp 1 v
. nf
foo [ options ] ...
foo < subcommand >
. fi
. br
. sp 1 v
. in 0
. ti 0
\ fBOptions : \ fR
. br
. sp 1 v
. in 12
. ti 4
-- help : \ ~ Show help .
. br
. sp 1 v
. in 0
. ti 0
\ fBSubcommands : \ fR
. br
. sp 1 v
. in 0
. ti 0
Show help for each subcommand by calling < command > help or < command > -- help .
. br
. sp 1 v
. in 2
. ti 2
\ fBfoo bar ( default ) \ fR
. br
. sp 1 v
. in 4
. ti 4
\ fBSynopsis : \ fR
. br
. sp 1 v
. nf
foo bar [ options ] ...
foo bar < subcommand >
. fi
. br
. sp 1 v
. in 4
. ti 4
\ fBOptions : \ fR
. br
. sp 1 v
. in 12
. ti 4
-- help : \ ~ Show help .
. br
. sp 1 v
. in 2
. ti 2
\ fBfoo real - foo \ fR
. br
. sp 1 v
. in 4
. ti 4
\ fBSynopsis : \ fR
. br
. sp 1 v
. nf
foo real - foo [ options ] ... [ -- ] < a int > < b int > < c int >
foo real - foo < subcommand >
. fi
. br
. sp 1 v
. in 4
. ti 4
\ fBDescription : \ fR
. br
. sp 1 v
. in 4
. ti 4
Foo sums three numbers . It prints the sum to stdout .
. br
. sp 1 v
. in 4
. ti 4
The input numbers can be any integer .
. br
. sp 1 v
. in 4
. ti 4
\ fBOptions : \ fR
. br
. sp 1 v
. in 12
. ti 4
-- help : \ ~ Show help .
. br
. sp 1 v
. in 2
. ti 2
\ fBfoo version \ fR
. br
. sp 1 v
. 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 1 v
. in 0
. ti 0
\ fBName : \ fR
. br
. sp 1 v
. in 4
. ti 4
foo
. br
. sp 1 v
. in 0
. ti 0
\ fBSynopsis : \ fR
. br
. sp 1 v
. nf
foo [ options ] ...
foo < subcommand >
. fi
. br
. sp 1 v
. in 0
. ti 0
\ fBOptions : \ fR
. br
. sp 1 v
. in 12
. ti 4
-- help : \ ~ Show help .
. br
. sp 1 v
. in 0
. ti 0
\ fBSubcommands : \ fR
. br
. sp 1 v
. in 0
. ti 0
Show help for each subcommand by calling < command > help or < command > -- help .
. br
. sp 1 v
. in 2
. ti 2
\ fBfoo bar \ fR
. br
. sp 1 v
. in 4
. ti 4
\ fBSynopsis : \ fR
. br
. sp 1 v
. nf
foo bar [ options ] ...
foo bar < subcommand >
. fi
. br
. sp 1 v
. in 4
. ti 4
\ fBOptions : \ fR
. br
. sp 1 v
. in 12
. ti 4
-- help : \ ~ Show help .
. br
. sp 1 v
. in 2
. ti 2
\ fBfoo bar baz \ fR
. br
. sp 1 v
. in 4
. ti 4
\ fBSynopsis : \ fR
. br
. sp 1 v
. nf
foo bar baz [ options ] ...
foo bar baz < subcommand >
. fi
. br
. sp 1 v
. in 4
. ti 4
\ fBOptions : \ fR
. br
. sp 1 v
. 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 1 v
. in 0
. ti 0
\ fBName : \ fR
. br
. sp 1 v
. in 4
. ti 4
foo
. br
. sp 1 v
. in 0
. ti 0
\ fBSynopsis : \ fR
. br
. sp 1 v
. nf
foo [ options ] ...
foo < subcommand >
. fi
. br
. sp 1 v
. in 0
. ti 0
\ fBOptions : \ fR
. br
. sp 1 v
. in 25
. ti 4
-- bar string [ * ] : \ ~ \ ~ \ ~ \ ~ Bars , any number .
. br
. in 25
. ti 4
-- duration duration : \ ~ Duration is another option .
. br
. in 25
. ti 4
-- foo int : \ ~ \ ~ \ ~ \ ~ \ ~ \ ~ \ ~ \ ~ \ ~ \ ~ \ ~ Foo is an option .
. br
. in 25
. ti 4
2025-12-30 16:52:10 +01:00
-- some any : \ ~ \ ~ \ ~ \ ~ \ ~ \ ~ \ ~ \ ~ \ ~ \ ~ Some is an option of any type .
. br
. in 25
. ti 4
2025-12-10 20:31:10 +01:00
-- time 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 1 v
. in 4
. ti 4
Hints :
. br
. sp 1 v
. 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 1 v
. in 0
. ti 0
\ fBSubcommands : \ fR
. br
. sp 1 v
. in 0
. ti 0
Show help for each subcommand by calling < command > help or < command > -- help .
. br
. sp 1 v
. in 2
. ti 2
\ fBfoo bar \ fR
. br
. sp 1 v
. in 4
. ti 4
\ fBSynopsis : \ fR
. br
. sp 1 v
. nf
foo bar [ options ] ...
foo bar < subcommand >
. fi
. br
. sp 1 v
. in 4
. ti 4
\ fBOptions : \ fR
. br
. sp 1 v
. in 15
. ti 4
-- bar int : \ ~
. br
. in 15
. ti 4
-- config : \ ~ \ ~ Configuration file .
. br
. in 15
. ti 4
-- help : \ ~ \ ~ \ ~ \ ~ Show help .
. br
. sp 1 v
. in 0
. ti 0
\ fBEnvironment variables : \ fR
. br
. sp 1 v
. 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 1 v
. 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 1 v
. in 4
. ti 4
\ fBExample environment variable : \ fR
. br
. sp 1 v
. nf
FOO_FOO = 42
. fi
. br
. sp 1 v
. in 0
. ti 0
\ fBConfiguration : \ fR
. br
. sp 1 v
. in 4
. ti 4
Every command line option ' s value can also be provided as an entry in a configuration file .
. br
. sp 1 v
. 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 1 v
. 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 1 v
. 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 1 v
. in 4
. ti 4
\ fBConfiguration files : \ fR
. br
. sp 1 v
. in 10
. ti 8
\ ( bu \ ~ zero or more configuration files defined by the -- config option
. br
. sp 1 v
. in 4
. ti 4
\ fBExample configuration entry : \ fR
. br
. sp 1 v
. nf
# Default value for -- foo :
foo = 42
. fi
. br
. sp 1 v
. in 4
. ti 4
\ fBExample for discarding an inherited entry : \ fR
. br
. sp 1 v
. 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 1 v
. in 0
. ti 0
\ fBName : \ fR
. br
. sp 1 v
. in 4
. ti 4
foo
. br
. sp 1 v
. in 0
. ti 0
\ fBSynopsis : \ fR
. br
. sp 1 v
. nf
foo [ options ] ...
foo < subcommand >
. fi
. br
. sp 1 v
. in 0
. ti 0
\ fBOptions : \ fR
. br
. sp 1 v
. 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 1 v
. in 4
. ti 4
Hints :
. br
. sp 1 v
. 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 1 v
. in 0
. ti 0
\ fBSubcommands : \ fR
. br
. sp 1 v
. in 10
. ti 4
help : \ ~ Show help .
. br
. sp 1 v
. in 0
. ti 0
\ fBEnvironment variables : \ fR
. br
. sp 1 v
. 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 1 v
. 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 1 v
. in 4
. ti 4
\ fBExample environment variable : \ fR
. br
. sp 1 v
. nf
FOO_FOO = true
. fi `
execTest (
t ,
testCase {
impl : ShortForm ( Command ( "foo" , func ( struct {
Foo bool
Bar int
2025-12-30 16:52:10 +01:00
} ) {
} ) , "f" , "foo" , "b" , "bar" ) ,
2025-12-10 20:31:10 +01:00
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 1 v
. in 0
. ti 0
\ fBName : \ fR
. br
. sp 1 v
. in 4
. ti 4
foo . bar
. br
. sp 1 v
. in 0
. ti 0
\ fBSynopsis : \ fR
. br
. sp 1 v
. nf
foo . bar [ options ] ...
foo . bar < subcommand >
. fi
. br
. sp 1 v
. in 0
. ti 0
\ fBOptions : \ fR
. br
. sp 1 v
. in 12
. ti 4
-- help : \ ~ Show help .
. br
. sp 1 v
. in 0
. ti 0
\ fBSubcommands : \ fR
. br
. sp 1 v
. 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 1 v
. in 0
. ti 0
\ fBName : \ fR
. br
. sp 1 v
. in 4
. ti 4
foo
. br
. sp 1 v
. in 0
. ti 0
\ fBSynopsis : \ fR
. br
. sp 1 v
. nf
foo [ options ] ...
foo < subcommand >
. fi
. br
. sp 1 v
. in 0
. ti 0
\ fBOptions : \ fR
. br
. sp 1 v
. in 15
. ti 4
-- foo int : \ ~
. br
. in 15
. ti 4
-- config : \ ~ \ ~ Configuration file .
. br
. in 15
. ti 4
-- help : \ ~ \ ~ \ ~ \ ~ Show help .
. br
. sp 1 v
. in 4
. ti 4
Option values can be set both via = or just separated by space .
. br
. sp 1 v
. in 0
. ti 0
\ fBSubcommands : \ fR
. br
. sp 1 v
. in 10
. ti 4
help : \ ~ Show help .
. br
. sp 1 v
. in 0
. ti 0
\ fBEnvironment variables : \ fR
. br
. sp 1 v
. 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 1 v
. 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 1 v
. in 4
. ti 4
\ fBExample environment variable : \ fR
. br
. sp 1 v
. nf
FOO_FOO = 42
. fi
. br
. sp 1 v
. in 0
. ti 0
\ fBConfiguration : \ fR
. br
. sp 1 v
. in 4
. ti 4
Every command line option ' s value can also be provided as an entry in a configuration file .
. br
. sp 1 v
. 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 1 v
. 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 1 v
. 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 1 v
. in 4
. ti 4
\ fBConfiguration files : \ fR
. br
. sp 1 v
. 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 1 v
. in 4
. ti 4
\ fBExample configuration entry : \ fR
. br
. sp 1 v
. nf
# Default value for -- foo :
foo = 42
. fi
. br
. sp 1 v
. in 4
. ti 4
\ fBExample for discarding an inherited entry : \ fR
. br
. sp 1 v
. 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 1 v
. in 0
. ti 0
\ fBName : \ fR
. br
. sp 1 v
. in 4
. ti 4
foo
. br
. sp 1 v
. in 0
. ti 0
\ fBSynopsis : \ fR
. br
. sp 1 v
. nf
foo [ options ] ...
foo < subcommand >
. fi
. br
. sp 1 v
. in 0
. ti 0
\ fBOptions : \ fR
. br
. sp 1 v
. in 16
. ti 4
-- foo bool : \ ~
. br
. in 16
. ti 4
-- config : \ ~ \ ~ \ ~ Configuration file .
. br
. in 16
. ti 4
-- help : \ ~ \ ~ \ ~ \ ~ \ ~ Show help .
. br
. sp 1 v
. in 4
. ti 4
Hints :
. br
. sp 1 v
. 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 1 v
. in 0
. ti 0
\ fBSubcommands : \ fR
. br
. sp 1 v
. in 10
. ti 4
help : \ ~ Show help .
. br
. sp 1 v
. in 0
. ti 0
\ fBEnvironment variables : \ fR
. br
. sp 1 v
. 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 1 v
. 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 1 v
. in 4
. ti 4
\ fBExample environment variable : \ fR
. br
. sp 1 v
. nf
FOO_FOO = true
. fi
. br
. sp 1 v
. in 0
. ti 0
\ fBConfiguration : \ fR
. br
. sp 1 v
. in 4
. ti 4
Every command line option ' s value can also be provided as an entry in a configuration file .
. br
. sp 1 v
. 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 1 v
. 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 1 v
. 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 1 v
. in 4
. ti 4
\ fBConfiguration files : \ fR
. br
. sp 1 v
. 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 1 v
. in 4
. ti 4
\ fBExample configuration entry : \ fR
. br
. sp 1 v
. nf
# Default value for -- foo :
foo = true
. fi
. br
. sp 1 v
. in 4
. ti 4
\ fBExample for discarding an inherited entry : \ fR
. br
. sp 1 v
. 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 - Foo sums three numbers
# # Synopsis :
` + " ` ` ` " + `
foo [ options ] ... [ -- ] < a int > < b int > < c int >
foo < subcommand >
` + " ` ` ` " + `
# # Description :
Foo 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 : Duration is another option .
- -- foo int : Foo is an option .
2025-12-30 16:52:10 +01:00
- -- some any : Some is an option of any type .
2025-12-10 20:31:10 +01:00
- -- time 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 ,
)
}