config, env, options testing
This commit is contained in:
parent
c05cfc7656
commit
2d93474cb1
11
Makefile
11
Makefile
@ -8,10 +8,10 @@ lib: $(SOURCES) iniparser.gen.go docreflect.gen.go
|
|||||||
|
|
||||||
build: lib .build/wand
|
build: lib .build/wand
|
||||||
|
|
||||||
check: $(SOURCES) build
|
check: $(SOURCES) build docreflect_test.go
|
||||||
go test -count 1 ./...
|
go test -count 1 ./...
|
||||||
|
|
||||||
.cover: $(SOURCES) build
|
.cover: $(SOURCES) build docreflect_test.go
|
||||||
go test -count 1 -coverprofile .cover ./...
|
go test -count 1 -coverprofile .cover ./...
|
||||||
|
|
||||||
cover: .cover
|
cover: .cover
|
||||||
@ -33,6 +33,13 @@ docreflect.gen.go: $(SOURCES)
|
|||||||
> docreflect.gen.go \
|
> docreflect.gen.go \
|
||||||
|| rm -f docreflect.gen.go
|
|| rm -f docreflect.gen.go
|
||||||
|
|
||||||
|
docreflect_test.go: $(SOURCES)
|
||||||
|
go run script/docreflect/docs.go \
|
||||||
|
wand_test \
|
||||||
|
code.squareroundforest.org/arpio/wand/internal/tests/testlib \
|
||||||
|
> docreflect_test.go \
|
||||||
|
|| rm -f docreflect_test.go
|
||||||
|
|
||||||
.build:
|
.build:
|
||||||
mkdir -p .build
|
mkdir -p .build
|
||||||
|
|
||||||
|
|||||||
15
command.go
15
command.go
@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var commandNameExpression = regexp.MustCompile("^[a-zA-Z_][a-zA-Z_0-9]*$")
|
var commandNameExpression = regexp.MustCompile("^[a-zA-Z_][a-zA-Z_0-9]*$")
|
||||||
@ -119,7 +120,7 @@ func validateImpl(cmd Cmd, conf Config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func validateCommandTree(cmd Cmd, conf Config) error {
|
func validateCommandTree(cmd Cmd, conf Config) error {
|
||||||
if cmd.isHelp {
|
if cmd.helpFor != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,8 +232,13 @@ func validateShortFormsTree(cmd Cmd) (map[string]string, map[string]string, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
mf := mapFields(cmd.impl)
|
mf := mapFields(cmd.impl)
|
||||||
|
_, helpDefined := mf["help"]
|
||||||
for i := 0; i < len(cmd.shortForms); i += 2 {
|
for i := 0; i < len(cmd.shortForms); i += 2 {
|
||||||
s, l := cmd.shortForms[i], cmd.shortForms[i+1]
|
s, l := cmd.shortForms[i], cmd.shortForms[i+1]
|
||||||
|
if s == "h" && l == "help" && !helpDefined {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
r := []rune(s)
|
r := []rune(s)
|
||||||
if len(r) != 1 || r[0] < 'a' || r[0] > 'z' {
|
if len(r) != 1 || r[0] < 'a' || r[0] > 'z' {
|
||||||
return nil, nil, fmt.Errorf("invalid short form: %s", s)
|
return nil, nil, fmt.Errorf("invalid short form: %s", s)
|
||||||
@ -275,7 +281,12 @@ func validateShortForms(cmd Cmd) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(um) != 0 {
|
if len(um) != 0 {
|
||||||
return errors.New("unmapped short forms")
|
var umn []string
|
||||||
|
for n := range um {
|
||||||
|
umn = append(umn, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("unmapped short forms: %s", strings.Join(umn, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -131,9 +131,9 @@ func selectCommand(cmd Cmd, args []string) (Cmd, []string, []string) {
|
|||||||
return defaultCommand(cmd), []string{cmd.name}, args
|
return defaultCommand(cmd), []string{cmd.name}, args
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd, fullCommand, args := selectCommand(sc, args[1:])
|
sc, fullCommand, args := selectCommand(sc, args[1:])
|
||||||
fullCommand = append([]string{cmd.name}, fullCommand...)
|
fullCommand = append([]string{cmd.name}, fullCommand...)
|
||||||
return cmd, fullCommand, args
|
return sc, fullCommand, args
|
||||||
}
|
}
|
||||||
|
|
||||||
func boolOption(name string, value bool) option {
|
func boolOption(name string, value bool) option {
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package wand
|
package wand
|
||||||
|
|
||||||
/*
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
@ -39,6 +38,7 @@ func TestCommand(t *testing.T) {
|
|||||||
One, Two bool
|
One, Two bool
|
||||||
Three string
|
Three string
|
||||||
}
|
}
|
||||||
|
|
||||||
fm := func(m m) string {
|
fm := func(m m) string {
|
||||||
return fmt.Sprintf("%t;%t;%s", m.One, m.Two, m.Three)
|
return fmt.Sprintf("%t;%t;%s", m.One, m.Two, m.Three)
|
||||||
}
|
}
|
||||||
@ -81,106 +81,607 @@ func TestCommand(t *testing.T) {
|
|||||||
return fmt.Sprint(d.One2)
|
return fmt.Sprint(d.One2)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("no args", testExec(t, ff, "", "foo", "", "0"))
|
t.Run("no args", testExec(testCase{impl: ff, command: "foo"}, "", "0"))
|
||||||
|
|
||||||
t.Run("basic options", func(t *testing.T) {
|
t.Run("basic options", func(t *testing.T) {
|
||||||
t.Run("space", testExec(t, ff, "", "foo --one baz --second-field 42", "", "baz42"))
|
t.Run("space", testExec(testCase{impl: ff, command: "foo --one baz --second-field 42"}, "", "baz42"))
|
||||||
t.Run("eq", testExec(t, ff, "", "foo --one=baz --second-field=42", "", "baz42"))
|
t.Run("eq", testExec(testCase{impl: ff, command: "foo --one=baz --second-field=42"}, "", "baz42"))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("short options combined, explicit last", func(t *testing.T) {
|
t.Run("short options combined, explicit last", func(t *testing.T) {
|
||||||
t.Run("bool last", testExec(t, ShortForm(fb, "one", "a", "two", "b", "three", "c"), "", "foo -abc true", "", "true;true;true;false"))
|
t.Run(
|
||||||
t.Run("string last", testExec(t, ShortForm(fm, "one", "a", "two", "b", "three", "c"), "", "foo -abc bar", "", "true;true;bar"))
|
"bool last",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fb), "a", "one", "b", "two", "c", "three"),
|
||||||
|
command: "foo -abc true",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;true;true;false",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"string last",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fm), "a", "one", "b", "two", "c", "three"),
|
||||||
|
command: "foo -abc bar",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;true;bar",
|
||||||
|
),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("multiple values", func(t *testing.T) {
|
t.Run("multiple values", func(t *testing.T) {
|
||||||
t.Run("bools, short", testExec(t, ShortForm(fl, "one", "a"), "", "foo -a -a -a", "", "true,true,true;"))
|
t.Run(
|
||||||
t.Run("bools, short, combined", testExec(t, ShortForm(fl, "one", "a"), "", "foo -aaa", "", "true,true,true;"))
|
"bools, short",
|
||||||
t.Run("bools, short, explicit", testExec(t, ShortForm(fl, "one", "a"), "", "foo -a true -a true -a true", "", "true,true,true;"))
|
testExec(
|
||||||
t.Run("bools, short, combined, last explicit", testExec(t, ShortForm(fl, "one", "a"), "", "foo -aaa true", "", "true,true,true;"))
|
testCase{
|
||||||
t.Run("bools, long", testExec(t, fl, "", "foo --one --one --one", "", "true,true,true;"))
|
impl: ShortForm(Command("foo", fl), "a", "one"),
|
||||||
t.Run("bools, long, explicit", testExec(t, fl, "", "foo --one true --one true --one true", "", "true,true,true;"))
|
command: "foo -a -a -a",
|
||||||
t.Run("mixd, short", testExec(t, ShortForm(fl, "one", "a", "two", "b"), "", "foo -a -b bar", "", "true;bar"))
|
},
|
||||||
t.Run("mixed, short, combined", testExec(t, ShortForm(fl, "one", "a", "two", "b"), "", "foo -ab bar", "", "true;bar"))
|
"",
|
||||||
t.Run("mixed, long", testExec(t, fl, "", "foo --one --two bar", "", "true;bar"))
|
"true,true,true;",
|
||||||
t.Run("mixed, long, explicit", testExec(t, fl, "", "foo --one true --two bar", "", "true;bar"))
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"bools, short, combined",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fl), "a", "one"),
|
||||||
|
command: "foo -aaa",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true,true,true;",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"bools, short, explicit",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fl), "a", "one"),
|
||||||
|
command: "foo -a true -a true -a true",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true,true,true;",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"bools, short, combined, last explicit",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fl), "a", "one"),
|
||||||
|
command: "foo -aaa true",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true,true,true;",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"bools, long",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fl,
|
||||||
|
command: "foo --one --one --one",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true,true,true;",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"bools, long, explicit",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fl,
|
||||||
|
command: "foo --one true --one true --one true",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true,true,true;"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"mixd, short",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fl), "a", "one", "b", "two"),
|
||||||
|
command: "foo -a -b bar",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;bar",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"mixed, short, combined",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fl), "a", "one", "b", "two"),
|
||||||
|
command: "foo -ab bar",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;bar",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"mixed, long",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fl,
|
||||||
|
command: "foo --one --two bar",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;bar",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"mixed, long, explicit",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fl,
|
||||||
|
command: "foo --one true --two bar",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;bar",
|
||||||
|
),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("implicit bool option", func(t *testing.T) {
|
t.Run("implicit bool option", func(t *testing.T) {
|
||||||
t.Run("short", testExec(t, ShortForm(fb, "one", "a"), "", "foo -a", "", "true;false;false;false"))
|
t.Run(
|
||||||
|
"short",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fb), "a", "one"),
|
||||||
|
command: "foo -a",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;false;false;false",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
t.Run(
|
t.Run(
|
||||||
"short, multiple",
|
"short, multiple",
|
||||||
testExec(t, ShortForm(fb, "one", "a", "two", "b", "three", "c"), "", "foo -a -b -c", "", "true;true;true;false"),
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fb), "a", "one", "b", "two", "c", "three"),
|
||||||
|
command: "foo -a -b -c",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;true;true;false",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
t.Run(
|
t.Run(
|
||||||
"short, combined",
|
"short, combined",
|
||||||
testExec(t, ShortForm(fb, "one", "a", "two", "b", "three", "c"), "", "foo -abc", "", "true;true;true;false"),
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fb), "a", "one", "b", "two", "c", "three"),
|
||||||
|
command: "foo -abc",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;true;true;false",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
t.Run(
|
t.Run(
|
||||||
"short, combined, multiple",
|
"short, combined, multiple",
|
||||||
testExec(t, ShortForm(fb, "one", "a", "two", "b", "three", "c", "four", "d"), "", "foo -ab -cd", "", "true;true;true;true"),
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fb), "a", "one", "b", "two", "c", "three", "d", "four"),
|
||||||
|
command: "foo -ab -cd",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;true;true;true",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
t.Run(
|
t.Run(
|
||||||
"short, multiple values",
|
"short, multiple values",
|
||||||
testExec(t, ShortForm(flb, "one", "a", "two", "b", "three", "c"), "", "foo -aba -cab", "", "true,true,true;true,true;true"),
|
|
||||||
)
|
|
||||||
|
|
||||||
t.Run("long", testExec(t, fb, "", "foo --one", "", "true;false;false;false"))
|
|
||||||
t.Run("long, multiple", testExec(t, fb, "", "foo --one --two --three", "", "true;true;true;false"))
|
|
||||||
t.Run("long, multiple values", testExec(t, flb, "", "foo --one --two --one", "", "true,true;true;"))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("explicit bool option", func(t *testing.T) {
|
|
||||||
t.Run("short, true", testExec(t, ShortForm(fb, "one", "a"), "", "foo -a true", "", "true;false;false;false"))
|
|
||||||
t.Run("short, false", testExec(t, ShortForm(fb, "one", "a"), "", "foo -a false", "", "false;false;false;false"))
|
|
||||||
t.Run("short, with eq", testExec(t, ShortForm(fb, "one", "a"), "", "foo -a=true", "", "true;false;false;false"))
|
|
||||||
t.Run("short, true variant, capital", testExec(t, ShortForm(fb, "one", "a"), "", "foo -a True", "", "true;false;false;false"))
|
|
||||||
t.Run("short, true variant, 1", testExec(t, ShortForm(fb, "one", "a"), "", "foo -a 1", "", "true;false;false;false"))
|
|
||||||
t.Run("short, false variant, 0", testExec(t, ShortForm(fb, "one", "a"), "", "foo -a 0", "", "false;false;false;false"))
|
|
||||||
t.Run("short, combined", testExec(t, ShortForm(fb, "one", "a", "two", "b"), "", "foo -ab true", "", "true;true;false;false"))
|
|
||||||
t.Run(
|
|
||||||
"short, combined, multiple",
|
|
||||||
testExec(
|
testExec(
|
||||||
t,
|
testCase{
|
||||||
ShortForm(fb, "one", "a", "two", "b", "three", "c", "four", "d"),
|
impl: ShortForm(Command("foo", flb), "a", "one", "b", "two", "c", "three"),
|
||||||
"", "foo -ab true -cd true",
|
command: "foo -aba -cab",
|
||||||
"", "true;true;true;true",
|
},
|
||||||
|
"",
|
||||||
|
"true,true,true;true,true;true",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
t.Run("long", testExec(t, fb, "", "foo --one true", "", "true;false;false;false"))
|
t.Run(
|
||||||
t.Run("long, false", testExec(t, fb, "", "foo --one false", "", "false;false;false;false"))
|
"long",
|
||||||
t.Run("logn, with eq", testExec(t, fb, "", "foo --one=true", "", "true;false;false;false"))
|
testExec(
|
||||||
t.Run("long, mixed, first", testExec(t, fb, "", "foo --one false --two", "", "false;true;false;false"))
|
testCase{
|
||||||
t.Run("long, mixed, last", testExec(t, fb, "", "foo --one --two false", "", "true;false;false;false"))
|
impl: fb,
|
||||||
|
command: "foo --one",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;false;false;false",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"long, multiple",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fb,
|
||||||
|
command: "foo --one --two --three",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;true;true;false",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"long, multiple values",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: flb,
|
||||||
|
command: "foo --one --two --one",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true,true;true;",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("explicit bool option", func(t *testing.T) {
|
||||||
|
t.Run(
|
||||||
|
"short, true",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fb), "a", "one"),
|
||||||
|
command: "foo -a true",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;false;false;false",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"short, false",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fb), "a", "one"),
|
||||||
|
command: "foo -a false",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"false;false;false;false",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"short, with eq",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fb), "a", "one"),
|
||||||
|
command: "foo -a=true",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;false;false;false",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"short, true variant, capital",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fb), "a","one"),
|
||||||
|
command: "foo -a True",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;false;false;false",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"short, true variant, 1",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fb), "a", "one"),
|
||||||
|
command: "foo -a 1",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;false;false;false",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"short, false variant, 0",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fb), "a", "one"),
|
||||||
|
command: "foo -a 0",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"false;false;false;false",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"short, combined",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fb), "a", "one", "b", "two"),
|
||||||
|
command: "foo -ab true",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;true;false;false",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"short, combined, multiple",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fb), "a", "one", "b", "two", "c", "three", "d", "four"),
|
||||||
|
command: "foo -ab true -cd true",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;true;true;true",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"long",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fb,
|
||||||
|
command: "foo --one true",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;false;false;false",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"long, false",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fb,
|
||||||
|
command: "foo --one false",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"false;false;false;false",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"logn, with eq",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fb,
|
||||||
|
command: "foo --one=true",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;false;false;false"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"long, mixed, first",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fb,
|
||||||
|
command: "foo --one false --two",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"false;true;false;false",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"long, mixed, last",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fb,
|
||||||
|
command: "foo --one --two false",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;false;false;false",
|
||||||
|
),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("expected bool option", func(t *testing.T) {
|
t.Run("expected bool option", func(t *testing.T) {
|
||||||
t.Run("short, implicit", testExec(t, ShortForm(fb, "one", "a"), "", "foo -a", "", "true;false;false;false"))
|
|
||||||
t.Run("short, explicit", testExec(t, ShortForm(fb, "one", "a"), "", "foo -a true", "", "true;false;false;false"))
|
|
||||||
t.Run("short, automatic positional", testExec(t, ShortForm(fbp, "one", "a"), "", "foo -a bar", "", "true;false;false;false;bar"))
|
|
||||||
t.Run("short, combined", testExec(t, ShortForm(fb, "one", "a", "two", "b"), "", "foo -ab true", "", "true;true;false;false"))
|
|
||||||
t.Run(
|
t.Run(
|
||||||
"short, combined, automatic positional",
|
"short, implicit",
|
||||||
testExec(t, ShortForm(fbp, "one", "a", "two", "b"), "", "foo -ab bar", "", "true;true;false;false;bar"),
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fb), "a", "one"),
|
||||||
|
command: "foo -a",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;false;false;false"),
|
||||||
)
|
)
|
||||||
|
|
||||||
t.Run("long, implicit", testExec(t, fb, "", "foo --one", "", "true;false;false;false"))
|
t.Run(
|
||||||
t.Run("long, explicit", testExec(t, fb, "", "foo --one true", "", "true;false;false;false"))
|
"short, explicit",
|
||||||
t.Run("long, automatic positional", testExec(t, fbp, "", "foo --one bar", "", "true;false;false;false;bar"))
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fb), "a", "one"),
|
||||||
|
command: "foo -a true",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;false;false;false",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"short, automatic positional",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fbp), "a", "one"),
|
||||||
|
command: "foo -a bar",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;false;false;false;bar",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"short, combined",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fb), "a","one", "b", "two"),
|
||||||
|
command: "foo -ab true",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;true;false;false",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"short, combined, automatic positional",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fbp), "a", "one", "b", "two"),
|
||||||
|
command: "foo -ab bar",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;true;false;false;bar"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"long, implicit",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fb,
|
||||||
|
command: "foo --one",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;false;false;false",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"long, explicit",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fb,
|
||||||
|
command: "foo --one true",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;false;false;false",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"long, automatic positional",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fbp,
|
||||||
|
command: "foo --one bar",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;false;false;false;bar",
|
||||||
|
),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("positional", func(t *testing.T) {
|
t.Run("positional", func(t *testing.T) {
|
||||||
t.Run("basic", testExec(t, fp, "", "foo bar baz", "", "0;bar,baz"))
|
t.Run(
|
||||||
t.Run("explicit", testExec(t, fp, "", "foo -- bar baz", "", "0;bar,baz"))
|
"basic",
|
||||||
t.Run("mixed", testExec(t, fp, "", "foo bar -- baz", "", "0;bar,baz"))
|
testExec(
|
||||||
t.Run("with option", testExec(t, fp, "", "foo bar --second-field 42 baz", "", "42;bar,baz"))
|
testCase{
|
||||||
t.Run("with bool option at the end", testExec(t, fbp, "", "foo bar baz --one", "", "true;false;false;false;bar;baz"))
|
impl: fp,
|
||||||
t.Run("with expected bool, implicit", testExec(t, fbp, "", "foo bar --one baz", "", "true;false;false;false;bar;baz"))
|
command: "foo bar baz",
|
||||||
t.Run("with expected bool, explicit", testExec(t, fbp, "", "foo bar --one true baz", "", "true;false;false;false;bar;baz"))
|
},
|
||||||
t.Run("option format", testExec(t, fbp, "", "foo -- --one", "", "false;false;false;false;--one"))
|
"",
|
||||||
|
"0;bar,baz",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"explicit",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fp,
|
||||||
|
command: "foo -- bar baz",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"0;bar,baz",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"mixed",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fp,
|
||||||
|
command: "foo bar -- baz",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"0;bar,baz",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"with option",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fp,
|
||||||
|
command: "foo bar --second-field 42 baz",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"42;bar,baz",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"with bool option at the end",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fbp,
|
||||||
|
command: "foo bar baz --one",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;false;false;false;bar;baz",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"with expected bool, implicit",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fbp,
|
||||||
|
command: "foo bar --one baz",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;false;false;false;bar;baz",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"with expected bool, explicit",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fbp,
|
||||||
|
command: "foo bar --one true baz",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;false;false;false;bar;baz",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"option format",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fbp,
|
||||||
|
command: "foo -- --one",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"false;false;false;false;--one",
|
||||||
|
),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("example", func(t *testing.T) {
|
t.Run("example", func(t *testing.T) {
|
||||||
@ -203,10 +704,10 @@ func TestCommand(t *testing.T) {
|
|||||||
t.Run(
|
t.Run(
|
||||||
"full",
|
"full",
|
||||||
testExec(
|
testExec(
|
||||||
t,
|
testCase{
|
||||||
ShortForm(fs, "foo", "a", "bar", "b"),
|
impl: ShortForm(Command("foo", fs), "a", "foo", "b", "bar"),
|
||||||
"",
|
command: "foo -ab --bar baz -b --qux --quux corge -- grault",
|
||||||
"foo -ab --bar baz -b --qux --quux corge -- grault",
|
},
|
||||||
"",
|
"",
|
||||||
"true;true,true,true;true;corge;baz;grault",
|
"true;true,true,true;true;corge;baz;grault",
|
||||||
),
|
),
|
||||||
@ -214,28 +715,204 @@ func TestCommand(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("expected or unexpected", func(t *testing.T) {
|
t.Run("expected or unexpected", func(t *testing.T) {
|
||||||
t.Run("capital letters", testExec(t, fp, "", "foo --One bar", "", "0;--One,bar"))
|
|
||||||
t.Run("digit in option name", testExec(t, fd, "", "foo --one-2", "", "true"))
|
|
||||||
t.Run("dash in option name", testExec(t, ff, "", "foo --second-field 42", "", "42"))
|
|
||||||
t.Run("unpexpected character", testExec(t, fp, "", "foo --one#", "", "0;--one#"))
|
|
||||||
t.Run(
|
t.Run(
|
||||||
"invalid short option set",
|
"capital letters",
|
||||||
testExec(t, ShortForm(fp, "one", "a", "one", "b", "second-field", "c"), "", "foo -aBc", "", "0;-aBc"),
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fp,
|
||||||
|
command: "foo --One bar",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"0;--One,bar",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
t.Run("positional separator, no value", testExec(t, fp, "", "foo --one bar --", "", "bar0;"))
|
t.Run(
|
||||||
t.Run("positional separator, expecting value", testExec(t, fp, "", "foo --one --", "--one", ""))
|
"digit in option name",
|
||||||
t.Run("shot flag set, expecting value", testExec(t, ShortForm(fp, "second-field", "b"), "", "foo --one -b", "--one", ""))
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fd,
|
||||||
|
command: "foo --one-2",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"dash in option name",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ff,
|
||||||
|
command: "foo --second-field 42",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"42",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"unpexpected character",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fp,
|
||||||
|
command: "foo --one#",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"0;--one#",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"invalid short option set",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fp), "a", "one", "b", "one", "c", "second-field"),
|
||||||
|
command: "foo -aBc",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"0;-aBc",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"positional separator, no value",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fp,
|
||||||
|
command: "foo --one bar --",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"bar0;",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"positional separator, expecting value",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fp,
|
||||||
|
command: "foo --one --",
|
||||||
|
},
|
||||||
|
"--one",
|
||||||
|
"",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"shot flag set, expecting value",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fp), "b", "second-field"),
|
||||||
|
command: "foo --one -b",
|
||||||
|
},
|
||||||
|
"--one",
|
||||||
|
"",
|
||||||
|
),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("preserve order", func(t *testing.T) {
|
t.Run("preserve order", func(t *testing.T) {
|
||||||
t.Run("bools", testExec(t, fl, "", "foo --one --one false --one", "", "true,false,true;"))
|
t.Run(
|
||||||
t.Run("strings", testExec(t, fl, "", "foo --two 1 --two 2 --two 3", "", ";1,2,3"))
|
"bools",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fl,
|
||||||
|
command: "foo --one --one false --one",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true,false,true;",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"strings",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fl,
|
||||||
|
command: "foo --two 1 --two 2 --two 3",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
";1,2,3",
|
||||||
|
),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("select subcommand", func(t *testing.T) {
|
t.Run("select subcommand", func(t *testing.T) {
|
||||||
t.Run("named", testExec(t, Command("", nil, Command("bar", ff), Command("baz", ff)), "", "foo baz", "", "0"))
|
t.Run(
|
||||||
t.Run("default", testExec(t, Command("", nil, Command("bar", ff), Default(Command("baz", ff))), "", "foo", "", "0"))
|
"named",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: Group("foo", Command("bar", ff), Command("baz", ff)),
|
||||||
|
command: "foo baz",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"0",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"default",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: Group("foo", Command("bar", ff), Default(Command("baz", ff))),
|
||||||
|
command: "foo",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"0",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("help option", func(t *testing.T) {
|
||||||
|
t.Run(
|
||||||
|
"short form not defined",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fm,
|
||||||
|
command: "foo -h",
|
||||||
|
},
|
||||||
|
"foo help",
|
||||||
|
"",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"short form not help",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fm), "h", "one"),
|
||||||
|
command: "foo -h",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"true;false;",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"short form",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: ShortForm(Command("foo", fm), "h", "help"),
|
||||||
|
command: "foo -h",
|
||||||
|
contains: true,
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"Synopsis",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"long form",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fm,
|
||||||
|
command: "foo --help",
|
||||||
|
contains: true,
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"Synopsis",
|
||||||
|
),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|||||||
50
config.go
50
config.go
@ -61,6 +61,39 @@ func (f *file) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func unescapeConfig(s string) string {
|
||||||
|
var (
|
||||||
|
u []rune
|
||||||
|
escaped bool
|
||||||
|
)
|
||||||
|
|
||||||
|
r := []rune(s)
|
||||||
|
for _, ri := range r {
|
||||||
|
if escaped {
|
||||||
|
u = append(u, ri)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ri == '\\' {
|
||||||
|
escaped = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
u = append(u, ri)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unquoteConfig(s string) string {
|
||||||
|
if len(s) < 2 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
s = s[1 : len(s)-1]
|
||||||
|
return unescapeConfig(s)
|
||||||
|
}
|
||||||
|
|
||||||
func readConfigFile(cmd Cmd, conf Config) (config, error) {
|
func readConfigFile(cmd Cmd, conf Config) (config, error) {
|
||||||
var f io.ReadCloser
|
var f io.ReadCloser
|
||||||
if conf.test == "" {
|
if conf.test == "" {
|
||||||
@ -97,11 +130,22 @@ func readConfigFile(cmd Cmd, conf Config) (config, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if token.Name == "value" {
|
if token.Name != "value" {
|
||||||
value = token.Text()
|
|
||||||
hasValue = true
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasValue = true
|
||||||
|
for _, valueToken := range token.Nodes {
|
||||||
|
if valueToken.Name == "value-chars" {
|
||||||
|
value = unescapeConfig(valueToken.Text())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if valueToken.Name == "quoted" {
|
||||||
|
value = unquoteConfig(valueToken.Text())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.originalNames == nil {
|
if c.originalNames == nil {
|
||||||
|
|||||||
167
config_test.go
167
config_test.go
@ -1,44 +1,151 @@
|
|||||||
package wand
|
package wand
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"fmt"
|
||||||
"code.squareroundforest.org/arpio/notation"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConfig(t *testing.T) {
|
func TestConfig(t *testing.T) {
|
||||||
type options struct {
|
type f struct{ One, SecondVar string }
|
||||||
FooBarBaz int
|
ff := func(f f) string {
|
||||||
Foo string
|
return f.One + f.SecondVar
|
||||||
FooBar []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl := func(o options) {
|
type i struct{ One, SecondVar int }
|
||||||
if o.FooBarBaz != 42 {
|
fi := func(i i) string {
|
||||||
t.Fatal(notation.Sprintw(o))
|
return fmt.Sprintf("%d;%d", i.One, i.SecondVar)
|
||||||
}
|
|
||||||
|
|
||||||
if o.Foo != "" {
|
|
||||||
t.Fatal(notation.Sprintw(o))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(o.FooBar) != 2 || o.FooBar[0] != "bar" || o.FooBar[1] != "baz" {
|
|
||||||
t.Fatal(notation.Sprintw(o))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stdin := bytes.NewBuffer(nil)
|
type m struct{ One, SecondVar []string }
|
||||||
stdout := bytes.NewBuffer(nil)
|
fm := func(m m) string {
|
||||||
stderr := bytes.NewBuffer(nil)
|
return strings.Join([]string{strings.Join(m.One, ","), strings.Join(m.SecondVar, ",")}, ";")
|
||||||
|
}
|
||||||
|
|
||||||
exec(
|
t.Run(
|
||||||
stdin,
|
"common config var casing",
|
||||||
stdout,
|
testExec(testCase{impl: ff, conf: "one=bar\nsecond_var=baz", command: "foo"}, "", "barbaz"),
|
||||||
stderr,
|
|
||||||
func(int) {},
|
|
||||||
Command("test", impl),
|
|
||||||
SystemConfig(),
|
|
||||||
nil,
|
|
||||||
[]string{"test", "--config", "./internal/tests/config.ini"},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"camel casing",
|
||||||
|
testExec(testCase{impl: ff, conf: "one=bar\nsecondVar=baz", command: "foo"}, "", "barbaz"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"empty var",
|
||||||
|
testExec(testCase{impl: ff, conf: "one=bar\nsecondVar=", command: "foo"}, "", "bar"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"discard env var",
|
||||||
|
testExec(testCase{impl: ff, conf: "one=bar\nsecondVar=baz\none", command: "foo"}, "", "baz"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"eq in value",
|
||||||
|
|
||||||
|
// this one is a good example for fixing the error reporting in the parser. Try it by removing the
|
||||||
|
// quotes:
|
||||||
|
testExec(testCase{impl: ff, conf: "one=\"bar=baz\"", command: "foo"}, "", "bar=baz"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"keeps original name",
|
||||||
|
testExec(testCase{impl: fi, conf: "ONE=bar", command: "foo"}, "ONE", ""),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"keeps original name, last wins on conflict",
|
||||||
|
testExec(testCase{impl: fi, conf: "ONE=bar\none=baz", command: "foo"}, "one", ""),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"2 entries",
|
||||||
|
testExec(testCase{impl: fm, conf: "one=bar\none=baz", command: "foo"}, "", "bar,baz;"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"3 entries",
|
||||||
|
testExec(testCase{impl: fm, conf: "one=bar\none=baz\none=qux", command: "foo"}, "", "bar,baz,qux;"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"with empty",
|
||||||
|
testExec(testCase{impl: fm, conf: "one=bar\none=baz\none=\none=qux\none=", command: "foo"}, "", "bar,baz,,qux,;"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"escape",
|
||||||
|
testExec(testCase{impl: fm, conf: "one=bar\\\nbaz", command: "foo"}, "", "bar\nbaz;"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"quote",
|
||||||
|
testExec(testCase{impl: fm, conf: "one=\"bar\nbaz\"", command: "foo"}, "", "bar\nbaz;"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"escape char",
|
||||||
|
testExec(testCase{impl: fm, conf: "one=bar\\\\\none=baz", command: "foo"}, "", "bar\\,baz;"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"escape char last",
|
||||||
|
testExec(testCase{impl: fm, conf: "one=bar\\", command: "foo"}, "parse failed", ""),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"discard in same doc",
|
||||||
|
testExec(testCase{impl: fm, conf: "one=bar\nsecond-var=baz\none", command: "foo"}, "", ";baz"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"discard in previous doc",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fm,
|
||||||
|
mergeConf: []string{"one=bar\nsecond-var=baz", "one\nsecond-var=qux"},
|
||||||
|
command: "foo",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
";qux",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"discard in previous same doc",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fm,
|
||||||
|
mergeConf: []string{"one=bar\nsecond-var=baz", "one\nsecond-var=qux\nsecond-var"},
|
||||||
|
command: "foo",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
";",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
// check the syntax for tests
|
||||||
|
// - white space ignored
|
||||||
|
// - comment line
|
||||||
|
// - comment at the end of an entry
|
||||||
|
// - invalid key
|
||||||
|
// check the code for tests
|
||||||
|
|
||||||
|
t.Run("white space ignored", testExec(testCase{impl: fm, conf: "one = bar ", command: "foo"}, "", "bar;"))
|
||||||
|
t.Run(
|
||||||
|
"comments",
|
||||||
|
testExec(
|
||||||
|
testCase{
|
||||||
|
impl: fm,
|
||||||
|
conf: "# comment on a line\none=bar # comment after an entry",
|
||||||
|
command: "foo",
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"bar;",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run("invald key", testExec(testCase{impl: fm, conf: "one two = bar", command: "foo"}, "parse failed", ""))
|
||||||
}
|
}
|
||||||
|
|||||||
15
debug.go
Normal file
15
debug.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package wand
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.squareroundforest.org/arpio/notation"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func debug(a ...any) {
|
||||||
|
if !testing.Testing() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
notation.Fprintlnw(os.Stderr, a...)
|
||||||
|
}
|
||||||
17
docreflect_test.go
Normal file
17
docreflect_test.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
Generated with https://code.squareroundforest.org/arpio/docreflect
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package wand_test
|
||||||
|
import "code.squareroundforest.org/arpio/docreflect"
|
||||||
|
func init() {
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib", "")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Bar", "\nfunc(out, a, b, c)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Baz", "\nfunc(o)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Foo", "Foo sums three numbers.\n\nfunc(a, b, c)")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Options", "")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Options.Duration", "")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Options.Foo", "")
|
||||||
|
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Options.Time", "")
|
||||||
|
}
|
||||||
91
env_test.go
91
env_test.go
@ -1,6 +1,5 @@
|
|||||||
package wand
|
package wand
|
||||||
|
|
||||||
/*
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
@ -23,22 +22,80 @@ func TestEnv(t *testing.T) {
|
|||||||
return strings.Join([]string{strings.Join(m.One, ","), strings.Join(m.SecondVar, ",")}, ";")
|
return strings.Join([]string{strings.Join(m.One, ","), strings.Join(m.SecondVar, ",")}, ";")
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("none match app prefix", testExec(t, ff, "SOME_VAR=foo;SOME_OTHER=bar", "baz", "", ""))
|
t.Run(
|
||||||
t.Run("common environment var casing", testExec(t, ff, "FOO_ONE=bar;FOO_SECOND_VAR=baz", "foo", "", "barbaz"))
|
"none match app prefix",
|
||||||
t.Run("camel casing", testExec(t, ff, "fooOne=bar;fooSecondVar=baz", "foo", "", "barbaz"))
|
testExec(testCase{impl: ff, env: "ONE=foo;SECOND_VAR=bar", command: "baz"}, "", ""),
|
||||||
t.Run("empty env var", testExec(t, ff, "fooOne=bar;fooSecondVar=", "foo", "", "bar"))
|
)
|
||||||
t.Run("multipart app name", testExec(t, ff, "fooBarOne=baz;FOO_BAR_SECOND_VAR=qux", "foo-bar", "", "bazqux"))
|
|
||||||
t.Run("invalid env var", testExec(t, ff, "fooOne=bar;fooSecondVar=baz;fooQux", "foo", "", "barbaz"))
|
t.Run(
|
||||||
t.Run("eq in value", testExec(t, ff, "fooOne=bar=baz", "foo", "", "bar=baz"))
|
"common environment var casing",
|
||||||
t.Run("keeps original name", testExec(t, fi, "FOO_ONE=bar", "foo", "FOO_ONE", ""))
|
testExec(testCase{impl: ff, env: "FOO_ONE=bar;FOO_SECOND_VAR=baz", command: "foo"}, "", "barbaz"),
|
||||||
t.Run("keeps original name, last wins on conflict", testExec(t, fi, "FOO_ONE=bar;fooOne=baz", "foo", "fooOne", ""))
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"camel casing",
|
||||||
|
testExec(testCase{impl: ff, env: "fooOne=bar;fooSecondVar=baz", command: "foo"}, "", "barbaz"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"empty env var",
|
||||||
|
testExec(testCase{impl: ff, env: "fooOne=bar;fooSecondVar=", command: "foo"}, "", "bar"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"multipart app name",
|
||||||
|
testExec(testCase{impl: ff, env: "fooBarOne=baz;FOO_BAR_SECOND_VAR=qux", command: "foo-bar"}, "", "bazqux"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"invalid env var",
|
||||||
|
testExec(testCase{impl: ff, env: "fooOne=bar;fooSecondVar=baz;fooQux", command: "foo"}, "", "barbaz"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"eq in value",
|
||||||
|
testExec(testCase{impl: ff, env: "fooOne=bar=baz", command: "foo"}, "", "bar=baz"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"keeps original name",
|
||||||
|
testExec(testCase{impl: fi, env: "FOO_ONE=bar", command: "foo"}, "FOO_ONE", ""),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"keeps original name, last wins on conflict",
|
||||||
|
testExec(testCase{impl: fi, env: "FOO_ONE=bar;fooOne=baz", command: "foo"}, "fooOne", ""),
|
||||||
|
)
|
||||||
|
|
||||||
t.Run("multiple values", func(t *testing.T) {
|
t.Run("multiple values", func(t *testing.T) {
|
||||||
t.Run("2", testExec(t, fm, "fooOne=bar:baz", "foo", "", "bar,baz;"))
|
t.Run(
|
||||||
t.Run("3", testExec(t, fm, "fooOne=bar:baz:qux", "foo", "", "bar,baz,qux;"))
|
"2",
|
||||||
t.Run("with empty", testExec(t, fm, "fooOne=bar:baz::qux:", "foo", "", "bar,baz,,qux,;"))
|
testExec(testCase{impl: fm, env: "fooOne=bar:baz", command: "foo"}, "", "bar,baz;"),
|
||||||
t.Run("escape", testExec(t, fm, "fooOne=bar\\:baz", "foo", "", "bar:baz;"))
|
)
|
||||||
t.Run("escape char", testExec(t, fm, "fooOne=bar\\\\:baz", "foo", "", "bar\\,baz;"))
|
|
||||||
t.Run("escape char last", testExec(t, fm, "fooOne=bar\\", "foo", "", "bar;"))
|
t.Run(
|
||||||
|
"3",
|
||||||
|
testExec(testCase{impl: fm, env: "fooOne=bar:baz:qux", command: "foo"}, "", "bar,baz,qux;"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"with empty",
|
||||||
|
testExec(testCase{impl: fm, env: "fooOne=bar:baz::qux:", command: "foo"}, "", "bar,baz,,qux,;"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"escape",
|
||||||
|
testExec(testCase{impl: fm, env: "fooOne=bar\\:baz", command: "foo"}, "", "bar:baz;"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"escape char",
|
||||||
|
testExec(testCase{impl: fm, env: "fooOne=bar\\\\:baz", command: "foo"}, "", "bar\\,baz;"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"escape char last",
|
||||||
|
testExec(testCase{impl: fm, env: "fooOne=bar\\", command: "foo"}, "", "bar;"),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|||||||
8
exec.go
8
exec.go
@ -37,9 +37,10 @@ func exec(stdin io.Reader, stdout, stderr io.Writer, exit func(int), cmd Cmd, co
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
e := readEnv(cmd.name, env)
|
// will need root command for the config and the env:
|
||||||
|
rootCmd := cmd
|
||||||
cmd, fullCmd, args := selectCommand(cmd, args[1:])
|
cmd, fullCmd, args := selectCommand(cmd, args[1:])
|
||||||
if cmd.isHelp {
|
if cmd.helpFor != nil {
|
||||||
if err := showHelp(stdout, cmd, conf, fullCmd); err != nil {
|
if err := showHelp(stdout, cmd, conf, fullCmd); err != nil {
|
||||||
fmt.Fprintln(stderr, err)
|
fmt.Fprintln(stderr, err)
|
||||||
exit(1)
|
exit(1)
|
||||||
@ -79,13 +80,14 @@ func exec(stdin io.Reader, stdout, stderr io.Writer, exit func(int), cmd Cmd, co
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := readConfig(cmd, cl, conf)
|
c, err := readConfig(rootCmd, cl, conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(stderr, "configuration error: %v", err)
|
fmt.Fprintf(stderr, "configuration error: %v", err)
|
||||||
exit(1)
|
exit(1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e := readEnv(rootCmd.name, env)
|
||||||
if err := validateInput(cmd, conf, c, e, cl); err != nil {
|
if err := validateInput(cmd, conf, c, e, cl); err != nil {
|
||||||
fmt.Fprintln(stderr, err)
|
fmt.Fprintln(stderr, err)
|
||||||
suggestHelp(stderr, cmd, fullCmd)
|
suggestHelp(stderr, cmd, fullCmd)
|
||||||
|
|||||||
49
exec_test.go
49
exec_test.go
@ -9,11 +9,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
impl any
|
impl any
|
||||||
stdin string
|
stdin string
|
||||||
conf string
|
conf string
|
||||||
env string
|
mergeConf []string
|
||||||
command string
|
env string
|
||||||
|
command string
|
||||||
|
contains bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func testExec(test testCase, err string, expect ...string) func(*testing.T) {
|
func testExec(test testCase, err string, expect ...string) func(*testing.T) {
|
||||||
@ -31,7 +33,22 @@ func testExec(test testCase, err string, expect ...string) func(*testing.T) {
|
|||||||
cmd := wrap(test.impl)
|
cmd := wrap(test.impl)
|
||||||
e := strings.Split(test.env, ";")
|
e := strings.Split(test.env, ";")
|
||||||
a := strings.Split(test.command, " ")
|
a := strings.Split(test.command, " ")
|
||||||
exec(stdinr, stdout, stderr, exit, cmd, Config{test: test.conf}, e, a)
|
|
||||||
|
var conf Config
|
||||||
|
if test.conf != "" && len(test.mergeConf) > 0 {
|
||||||
|
t.Fatal("test error: conflicting test config")
|
||||||
|
} else if test.conf != "" {
|
||||||
|
conf = Config{test: test.conf}
|
||||||
|
} else if len(test.mergeConf) > 0 {
|
||||||
|
var c []Config
|
||||||
|
for _, cs := range test.mergeConf {
|
||||||
|
c = append(c, Config{test: cs})
|
||||||
|
}
|
||||||
|
|
||||||
|
conf = MergeConfig(c...)
|
||||||
|
}
|
||||||
|
|
||||||
|
exec(stdinr, stdout, stderr, exit, cmd, conf, e, a)
|
||||||
if exitCode != 0 && err == "" {
|
if exitCode != 0 && err == "" {
|
||||||
t.Fatal("non-zero exit code:", stderr.String())
|
t.Fatal("non-zero exit code:", stderr.String())
|
||||||
}
|
}
|
||||||
@ -58,8 +75,24 @@ func testExec(test testCase, err string, expect ...string) func(*testing.T) {
|
|||||||
output = output + "\n"
|
output = output + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
if output != strings.Join(expstr, "\n")+"\n" {
|
checkOutput := func() bool {
|
||||||
t.Fatal("unexpected output:", stdout.String())
|
return output == strings.Join(expstr, "\n")+"\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.contains {
|
||||||
|
checkOutput = func() bool {
|
||||||
|
for _, e := range expstr {
|
||||||
|
if !strings.Contains(output, e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !checkOutput() {
|
||||||
|
t.Fatal("unexpected output:", output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
go.mod
4
go.mod
@ -3,8 +3,8 @@ module code.squareroundforest.org/arpio/wand
|
|||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
code.squareroundforest.org/arpio/bind v0.0.0-20250903223305-8683d8ba4074
|
code.squareroundforest.org/arpio/bind v0.0.0-20250903234821-f3e17035cd36
|
||||||
code.squareroundforest.org/arpio/docreflect v0.0.0-20250831183400-d26ecc663a30
|
code.squareroundforest.org/arpio/docreflect v0.0.0-20250904132730-afd27063724e
|
||||||
code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2
|
code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2
|
||||||
code.squareroundforest.org/arpio/treerack v0.0.0-20250820014405-1d956dcc6610
|
code.squareroundforest.org/arpio/treerack v0.0.0-20250820014405-1d956dcc6610
|
||||||
github.com/iancoleman/strcase v0.3.0
|
github.com/iancoleman/strcase v0.3.0
|
||||||
|
|||||||
4
go.sum
4
go.sum
@ -2,8 +2,12 @@ code.squareroundforest.org/arpio/bind v0.0.0-20250901011104-bcadfd8b71fc h1:nu5Y
|
|||||||
code.squareroundforest.org/arpio/bind v0.0.0-20250901011104-bcadfd8b71fc/go.mod h1:tTCmCwFABKNm3PO0Dclsp4zWhNQFTfg9+uSrgoarZFI=
|
code.squareroundforest.org/arpio/bind v0.0.0-20250901011104-bcadfd8b71fc/go.mod h1:tTCmCwFABKNm3PO0Dclsp4zWhNQFTfg9+uSrgoarZFI=
|
||||||
code.squareroundforest.org/arpio/bind v0.0.0-20250903223305-8683d8ba4074 h1:OTzn0dMou+6m2rw70g7fIylQLHUTu75noAX3lbCYMqw=
|
code.squareroundforest.org/arpio/bind v0.0.0-20250903223305-8683d8ba4074 h1:OTzn0dMou+6m2rw70g7fIylQLHUTu75noAX3lbCYMqw=
|
||||||
code.squareroundforest.org/arpio/bind v0.0.0-20250903223305-8683d8ba4074/go.mod h1:tTCmCwFABKNm3PO0Dclsp4zWhNQFTfg9+uSrgoarZFI=
|
code.squareroundforest.org/arpio/bind v0.0.0-20250903223305-8683d8ba4074/go.mod h1:tTCmCwFABKNm3PO0Dclsp4zWhNQFTfg9+uSrgoarZFI=
|
||||||
|
code.squareroundforest.org/arpio/bind v0.0.0-20250903234821-f3e17035cd36 h1:8TB3ABJVV0eEdnWl+dJ3Hg4lGe+BlgNPgcW5p9yZnrQ=
|
||||||
|
code.squareroundforest.org/arpio/bind v0.0.0-20250903234821-f3e17035cd36/go.mod h1:tTCmCwFABKNm3PO0Dclsp4zWhNQFTfg9+uSrgoarZFI=
|
||||||
code.squareroundforest.org/arpio/docreflect v0.0.0-20250831183400-d26ecc663a30 h1:QUCgxUEA5/ng7GwRnzb/WezmFQXSHXl48GdLJc0KC5k=
|
code.squareroundforest.org/arpio/docreflect v0.0.0-20250831183400-d26ecc663a30 h1:QUCgxUEA5/ng7GwRnzb/WezmFQXSHXl48GdLJc0KC5k=
|
||||||
code.squareroundforest.org/arpio/docreflect v0.0.0-20250831183400-d26ecc663a30/go.mod h1:/3xQI36oJG8qLBxT2fSS61P5/+i1T64fTX9GHRh8XhA=
|
code.squareroundforest.org/arpio/docreflect v0.0.0-20250831183400-d26ecc663a30/go.mod h1:/3xQI36oJG8qLBxT2fSS61P5/+i1T64fTX9GHRh8XhA=
|
||||||
|
code.squareroundforest.org/arpio/docreflect v0.0.0-20250904132730-afd27063724e h1:f7wtGAmuTYH/VTn92sBTtKhs463q+DTtW2yKgst2kl8=
|
||||||
|
code.squareroundforest.org/arpio/docreflect v0.0.0-20250904132730-afd27063724e/go.mod h1:/3xQI36oJG8qLBxT2fSS61P5/+i1T64fTX9GHRh8XhA=
|
||||||
code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2 h1:S4mjQHL70CuzFg1AGkr0o0d+4M+ZWM0sbnlYq6f0b3I=
|
code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2 h1:S4mjQHL70CuzFg1AGkr0o0d+4M+ZWM0sbnlYq6f0b3I=
|
||||||
code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2/go.mod h1:ait4Fvg9o0+bq5hlxi9dAcPL5a+/sr33qsZPNpToMLY=
|
code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2/go.mod h1:ait4Fvg9o0+bq5hlxi9dAcPL5a+/sr33qsZPNpToMLY=
|
||||||
code.squareroundforest.org/arpio/treerack v0.0.0-20250820014405-1d956dcc6610 h1:I0jebdyQQfqJcwq2lT/TkUPBU8secHa5xZ+VzOdYVsw=
|
code.squareroundforest.org/arpio/treerack v0.0.0-20250820014405-1d956dcc6610 h1:I0jebdyQQfqJcwq2lT/TkUPBU8secHa5xZ+VzOdYVsw=
|
||||||
|
|||||||
22
help.go
22
help.go
@ -68,25 +68,27 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func help(sf []string) Cmd {
|
func help(cmd Cmd) Cmd {
|
||||||
return Cmd{
|
return Cmd{
|
||||||
name: "help",
|
name: "help",
|
||||||
isHelp: true,
|
helpFor: &cmd,
|
||||||
shortForms: sf,
|
shortForms: cmd.shortForms,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func insertHelp(cmd Cmd) Cmd {
|
func insertHelp(cmd Cmd) Cmd {
|
||||||
var hasHelpCmd bool
|
var hasHelpCmd bool
|
||||||
for i, sc := range cmd.subcommands {
|
for i, sc := range cmd.subcommands {
|
||||||
cmd.subcommands[i] = insertHelp(sc)
|
if sc.name == "help" {
|
||||||
if cmd.name == "help" {
|
|
||||||
hasHelpCmd = true
|
hasHelpCmd = true
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd.subcommands[i] = insertHelp(sc)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasHelpCmd && cmd.version == "" {
|
if !hasHelpCmd && cmd.version == "" {
|
||||||
cmd.subcommands = append(cmd.subcommands, help(cmd.shortForms))
|
cmd.subcommands = append(cmd.subcommands, help(cmd))
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
@ -94,7 +96,7 @@ func insertHelp(cmd Cmd) Cmd {
|
|||||||
|
|
||||||
func hasHelpSubcommand(cmd Cmd) bool {
|
func hasHelpSubcommand(cmd Cmd) bool {
|
||||||
for _, sc := range cmd.subcommands {
|
for _, sc := range cmd.subcommands {
|
||||||
if sc.isHelp {
|
if sc.helpFor != nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -312,7 +314,7 @@ func constructDoc(cmd Cmd, conf Config, fullCommand []string) doc {
|
|||||||
description: constructDescription(cmd),
|
description: constructDescription(cmd),
|
||||||
hasImplementation: cmd.impl != nil,
|
hasImplementation: cmd.impl != nil,
|
||||||
isDefault: cmd.isDefault,
|
isDefault: cmd.isDefault,
|
||||||
isHelp: cmd.isHelp,
|
isHelp: cmd.helpFor != nil,
|
||||||
isVersion: cmd.version != "",
|
isVersion: cmd.version != "",
|
||||||
hasHelpSubcommand: hasHelpSubcommand(cmd),
|
hasHelpSubcommand: hasHelpSubcommand(cmd),
|
||||||
hasHelpOption: !hasCustomHelpOption(cmd),
|
hasHelpOption: !hasCustomHelpOption(cmd),
|
||||||
@ -327,6 +329,10 @@ func constructDoc(cmd Cmd, conf Config, fullCommand []string) doc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func showHelp(out io.Writer, cmd Cmd, conf Config, fullCommand []string) error {
|
func showHelp(out io.Writer, cmd Cmd, conf Config, fullCommand []string) error {
|
||||||
|
if cmd.helpFor != nil {
|
||||||
|
cmd = *cmd.helpFor
|
||||||
|
}
|
||||||
|
|
||||||
doc := constructDoc(cmd, conf, fullCommand)
|
doc := constructDoc(cmd, conf, fullCommand)
|
||||||
return formatHelp(out, doc)
|
return formatHelp(out, doc)
|
||||||
}
|
}
|
||||||
|
|||||||
18
ini.treerack
18
ini.treerack
@ -1,10 +1,8 @@
|
|||||||
whitespace:ws = [ \b\f\r\t\v];
|
whitespace:ws = [ \b\f\r\t\v];
|
||||||
comment-line:alias = "#" [^\n]*;
|
comment-line:alias = "#" [^\n]*;
|
||||||
comment = comment-line ("\n" comment-line)*;
|
key:nows = [a-zA-Z_][a-zA-Z_0-9\-]*;
|
||||||
quoted:alias:nows = "\"" ([^\\"] | "\\" .)* "\"";
|
quoted:nows = "\"" ([^\\"] | "\\" .)* "\"";
|
||||||
word:alias:nows = [a-zA-Z_]([a-zA-Z_0-9\-] | "\\" .)*;
|
value-chars:nows = ([^\\"\n=# \b\f\r\t\v] | "\\" .)*;
|
||||||
key = word | quoted;
|
value = value-chars | quoted;
|
||||||
value-chars:alias:nows = ([^\\"\n=# \b\f\r\t\v] | "\\" .)+;
|
key-val = key ("=" value)? comment-line?;
|
||||||
value = value-chars+ | quoted;
|
doc:root = "\n"* (key-val | comment-line) ("\n"+ (key-val | comment-line))* "\n"*;
|
||||||
key-val = (comment "\n")? key ("=" value?)? comment-line?;
|
|
||||||
doc:root = (key-val | comment-line | "\n")*;
|
|
||||||
|
|||||||
383
iniparser.gen.go
383
iniparser.gen.go
File diff suppressed because one or more lines are too long
18
input.go
18
input.go
@ -51,27 +51,35 @@ func validateEnv(cmd Cmd, e env) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func validateOptions(cmd Cmd, o []option, conf Config) error {
|
func validateOptions(cmd Cmd, o []option, conf Config) error {
|
||||||
ml := make(map[string]string)
|
|
||||||
ms := make(map[string]string)
|
ms := make(map[string]string)
|
||||||
|
ml := make(map[string]string)
|
||||||
for i := 0; i < len(cmd.shortForms); i += 2 {
|
for i := 0; i < len(cmd.shortForms); i += 2 {
|
||||||
l, s := cmd.shortForms[i], cmd.shortForms[i+1]
|
s, l := cmd.shortForms[i], cmd.shortForms[i+1]
|
||||||
ml[l] = s
|
|
||||||
ms[s] = l
|
ms[s] = l
|
||||||
|
ml[l] = s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var names []string
|
||||||
mo := make(map[string][]option)
|
mo := make(map[string][]option)
|
||||||
for _, oi := range o {
|
for _, oi := range o {
|
||||||
n := oi.name
|
n := oi.name
|
||||||
if ln, ok := ms[n]; ok && oi.shortForm {
|
if oi.shortForm {
|
||||||
|
ln, ok := ms[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("option not supported: -%s", n)
|
||||||
|
}
|
||||||
|
|
||||||
n = ln
|
n = ln
|
||||||
}
|
}
|
||||||
|
|
||||||
|
names = append(names, n)
|
||||||
mo[n] = append(mo[n], oi)
|
mo[n] = append(mo[n], oi)
|
||||||
}
|
}
|
||||||
|
|
||||||
hasConfigOption := hasConfigFromOption(conf)
|
hasConfigOption := hasConfigFromOption(conf)
|
||||||
mf := mapFields(cmd.impl)
|
mf := mapFields(cmd.impl)
|
||||||
for n, os := range mo {
|
for _, n := range names {
|
||||||
|
os := mo[n]
|
||||||
en := "--" + n
|
en := "--" + n
|
||||||
if sn, ok := ml[n]; ok {
|
if sn, ok := ml[n]; ok {
|
||||||
en += ", -" + sn
|
en += ", -" + sn
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
# test config
|
|
||||||
|
|
||||||
# another comment
|
|
||||||
foo_bar_baz = 42 # a comment
|
|
||||||
foo
|
|
||||||
foo_bar = bar
|
|
||||||
|
|
||||||
foo_bar = baz
|
|
||||||
25
internal/tests/testlib/lib.go
Normal file
25
internal/tests/testlib/lib.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package testlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Foo int
|
||||||
|
Duration time.Duration
|
||||||
|
Time time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foo sums three numbers.
|
||||||
|
func Foo(a, b, c int) int {
|
||||||
|
return a + b + c
|
||||||
|
}
|
||||||
|
|
||||||
|
func Bar(out io.Writer, a, b, c int) int {
|
||||||
|
return a + b + c
|
||||||
|
}
|
||||||
|
|
||||||
|
func Baz(o Options) int {
|
||||||
|
return o.Foo
|
||||||
|
}
|
||||||
14
lib.go
14
lib.go
@ -8,6 +8,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// the parsing errors might be tricky
|
||||||
type Config struct {
|
type Config struct {
|
||||||
file func(Cmd) *file
|
file func(Cmd) *file
|
||||||
merge []Config
|
merge []Config
|
||||||
@ -25,11 +26,11 @@ type Cmd struct {
|
|||||||
minPositional int
|
minPositional int
|
||||||
maxPositional int
|
maxPositional int
|
||||||
shortForms []string
|
shortForms []string
|
||||||
isHelp bool
|
helpFor *Cmd
|
||||||
version string
|
version string
|
||||||
}
|
}
|
||||||
|
|
||||||
// name needs to be valid symbol. The application name should also be a valid symbol,
|
// name needs to be a valid symbol. The application name should also be a valid symbol,
|
||||||
// though not mandatory. If it is not, the environment variables may not work properly.
|
// though not mandatory. If it is not, the environment variables may not work properly.
|
||||||
func Command(name string, impl any, subcmds ...Cmd) Cmd {
|
func Command(name string, impl any, subcmds ...Cmd) Cmd {
|
||||||
return Cmd{name: name, impl: impl, subcommands: subcmds}
|
return Cmd{name: name, impl: impl, subcommands: subcmds}
|
||||||
@ -90,6 +91,13 @@ func OptionalConfig(conf Config) Config {
|
|||||||
return conf
|
return conf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ConfigFile(name string) Config {
|
||||||
|
return Config{
|
||||||
|
file: func(Cmd) *file { return fileReader(name) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the config will consider the command name as in the arguments
|
||||||
func Etc() Config {
|
func Etc() Config {
|
||||||
return OptionalConfig(Config{
|
return OptionalConfig(Config{
|
||||||
file: func(cmd Cmd) *file {
|
file: func(cmd Cmd) *file {
|
||||||
@ -98,6 +106,7 @@ func Etc() Config {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the config will consider the command name as in the arguments
|
||||||
func UserConfig() Config {
|
func UserConfig() Config {
|
||||||
return OptionalConfig(
|
return OptionalConfig(
|
||||||
MergeConfig(
|
MergeConfig(
|
||||||
@ -123,6 +132,7 @@ func SystemConfig() Config {
|
|||||||
return MergeConfig(Etc(), UserConfig(), ConfigFromOption())
|
return MergeConfig(Etc(), UserConfig(), ConfigFromOption())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the env will consider the command name as in the arguments
|
||||||
func Exec(impl any, conf ...Config) {
|
func Exec(impl any, conf ...Config) {
|
||||||
exec(os.Stdin, os.Stdout, os.Stderr, os.Exit, wrap(impl), MergeConfig(conf...), os.Environ(), os.Args)
|
exec(os.Stdin, os.Stdout, os.Stderr, os.Exit, wrap(impl), MergeConfig(conf...), os.Environ(), os.Args)
|
||||||
}
|
}
|
||||||
|
|||||||
135
reflect.go
135
reflect.go
@ -49,45 +49,6 @@ func or[T any](p ...func(T) bool) func(T) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func circularChecked(visited map[reflect.Type]bool, t reflect.Type) bool {
|
|
||||||
if t == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if visited[t] {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if visited == nil {
|
|
||||||
visited = make(map[reflect.Type]bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
visited[t] = true
|
|
||||||
switch t.Kind() {
|
|
||||||
case reflect.Pointer, reflect.Slice:
|
|
||||||
return circularChecked(visited, t.Elem())
|
|
||||||
case reflect.Struct:
|
|
||||||
for i := 0; i < t.NumField(); i++ {
|
|
||||||
svisited := make(map[reflect.Type]bool)
|
|
||||||
for t := range visited {
|
|
||||||
svisited[t] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if circularChecked(svisited, t.Field(i).Type) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func circular(t reflect.Type) bool {
|
|
||||||
return circularChecked(nil, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func unpackType(t reflect.Type) reflect.Type {
|
func unpackType(t reflect.Type) reflect.Type {
|
||||||
if t == nil {
|
if t == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -237,10 +198,7 @@ func mapFields(f any) map[string][]bind.Field {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func boolFields(f []bind.Field) []bind.Field {
|
func boolFields(f []bind.Field) []bind.Field {
|
||||||
return filter(
|
return filter(f, func(f bind.Field) bool { return f.Type() == bind.Bool })
|
||||||
fields(f),
|
|
||||||
func(f bind.Field) bool { return f.Type() == bind.Bool },
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func positional(f any) ([]reflect.Type, bool) {
|
func positional(f any) ([]reflect.Type, bool) {
|
||||||
@ -282,93 +240,28 @@ func ioParameters(f any) ([]reflect.Type, []reflect.Type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func bindable(t reflect.Type) bool {
|
func bindable(t reflect.Type) bool {
|
||||||
if t == nil {
|
return bind.BindableType(t)
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if circular(t) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
t = unpackType(t)
|
|
||||||
if isTime(t) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if isStruct(t) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch t.Kind() {
|
|
||||||
case reflect.Bool,
|
|
||||||
reflect.Int,
|
|
||||||
reflect.Int8,
|
|
||||||
reflect.Int16,
|
|
||||||
reflect.Int32,
|
|
||||||
reflect.Int64,
|
|
||||||
reflect.Uint,
|
|
||||||
reflect.Uint8,
|
|
||||||
reflect.Uint16,
|
|
||||||
reflect.Uint32,
|
|
||||||
reflect.Uint64,
|
|
||||||
reflect.Float32,
|
|
||||||
reflect.Float64,
|
|
||||||
reflect.String:
|
|
||||||
return true
|
|
||||||
case reflect.Interface:
|
|
||||||
return t.NumMethod() == 0
|
|
||||||
case reflect.Slice:
|
|
||||||
return bindable(t.Elem())
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func scalarTypeString(t bind.FieldType) string {
|
func scalarTypeString(t bind.FieldType) string {
|
||||||
r := reflect.TypeOf(t)
|
switch t {
|
||||||
p := strings.Split(r.Name(), ".")
|
case bind.Duration:
|
||||||
n := p[len(p)-1]
|
return "duration"
|
||||||
n = strings.ToLower(n)
|
case bind.Time:
|
||||||
return n
|
return "time"
|
||||||
|
default:
|
||||||
|
return strings.ToLower(reflect.Kind(t).String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func canScan(t bind.FieldType, v any) bool {
|
func canScan(t bind.FieldType, v any) bool {
|
||||||
switch t {
|
_, ok := bind.CreateAndBindScalarFieldType(t, v)
|
||||||
case bind.Any:
|
return ok
|
||||||
return true
|
|
||||||
case bind.Bool:
|
|
||||||
_, ok := bind.CreateAndBindScalar[bool](v)
|
|
||||||
return ok
|
|
||||||
case bind.Int, bind.Int8, bind.Int16, bind.Int32, bind.Int64:
|
|
||||||
_, ok := bind.CreateAndBindScalar[int](v)
|
|
||||||
return ok
|
|
||||||
case bind.Uint, bind.Uint8, bind.Uint16, bind.Uint32, bind.Uint64:
|
|
||||||
_, ok := bind.CreateAndBindScalar[uint](v)
|
|
||||||
return ok
|
|
||||||
case bind.Float32, bind.Float64:
|
|
||||||
_, ok := bind.CreateAndBindScalar[float64](v)
|
|
||||||
return ok
|
|
||||||
case bind.String:
|
|
||||||
_, ok := bind.CreateAndBindScalar[string](v)
|
|
||||||
return ok
|
|
||||||
case bind.Duration:
|
|
||||||
_, ok := bind.CreateAndBindScalar[time.Duration](v)
|
|
||||||
return ok
|
|
||||||
case bind.Time:
|
|
||||||
_, ok := bind.CreateAndBindScalar[time.Time](v)
|
|
||||||
return ok
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func canScanType(t reflect.Type, v any) bool {
|
func canScanType(t reflect.Type, v any) bool {
|
||||||
if t == nil {
|
_, ok := bind.CreateAndBindScalarFor(t, v)
|
||||||
return false
|
return ok
|
||||||
}
|
|
||||||
|
|
||||||
r := allocate(reflect.PointerTo(t))
|
|
||||||
return bind.BindScalar(r.Interface(), v)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func allocate(t reflect.Type) reflect.Value {
|
func allocate(t reflect.Type) reflect.Value {
|
||||||
|
|||||||
@ -2,8 +2,10 @@ package wand
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"code.squareroundforest.org/arpio/wand/internal/tests/testlib"
|
||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestReflect(t *testing.T) {
|
func TestReflect(t *testing.T) {
|
||||||
@ -12,7 +14,10 @@ func TestReflect(t *testing.T) {
|
|||||||
t.Run("slice", testExec(testCase{impl: f, command: "foo 42"}, "", "42"))
|
t.Run("slice", testExec(testCase{impl: f, command: "foo 42"}, "", "42"))
|
||||||
ps := func(a *[]*[]int) int { return (*((*a)[0]))[0] }
|
ps := func(a *[]*[]int) int { return (*((*a)[0]))[0] }
|
||||||
t.Run("pointer and slice", testExec(testCase{impl: ps, command: "foo 42"}, "", "42"))
|
t.Run("pointer and slice", testExec(testCase{impl: ps, command: "foo 42"}, "", "42"))
|
||||||
type s struct{Foo int; Bar *s}
|
type s struct {
|
||||||
|
Foo int
|
||||||
|
Bar *s
|
||||||
|
}
|
||||||
c := func(v s) int { return v.Foo }
|
c := func(v s) int { return v.Foo }
|
||||||
t.Run("circular type", testExec(testCase{impl: c, command: "foo --foo 42"}, "unsupported parameter type", ""))
|
t.Run("circular type", testExec(testCase{impl: c, command: "foo --foo 42"}, "unsupported parameter type", ""))
|
||||||
fp := func(a int) int { return a }
|
fp := func(a int) int { return a }
|
||||||
@ -89,14 +94,52 @@ func TestReflect(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("compatible types", func(t *testing.T) {
|
t.Run("compatible types", func(t *testing.T) {
|
||||||
type s0 struct{FooBar int; Foo struct {Bar string}}
|
type s0 struct {
|
||||||
type s1 struct{FooBar int; Foo struct {Bar int}}
|
FooBar int
|
||||||
|
Foo struct{ Bar string }
|
||||||
|
}
|
||||||
|
type s1 struct {
|
||||||
|
FooBar int
|
||||||
|
Foo struct{ Bar int }
|
||||||
|
}
|
||||||
f := func(a s0) int { return a.FooBar + len(a.Foo.Bar) }
|
f := func(a s0) int { return a.FooBar + len(a.Foo.Bar) }
|
||||||
g := func(a s1) int { return a.FooBar + a.Foo.Bar }
|
g := func(a s1) int { return a.FooBar + a.Foo.Bar }
|
||||||
t.Run("incompatible", testExec(testCase{impl: f, command: "foo --foo-bar 42"}, "duplicate fields with different types", ""))
|
t.Run("incompatible", testExec(testCase{impl: f, command: "foo --foo-bar 42"}, "duplicate fields with different types", ""))
|
||||||
t.Run("compatible", testExec(testCase{impl: g, command: "foo --foo-bar 42"}, "", "84"))
|
t.Run("compatible", testExec(testCase{impl: g, command: "foo --foo-bar 42"}, "", "84"))
|
||||||
type s2 struct{FooBar any; Foo struct {Bar int}}
|
type s2 struct {
|
||||||
|
FooBar any
|
||||||
|
Foo struct{ Bar int }
|
||||||
|
}
|
||||||
h := func(a s2) int { return len(a.FooBar.(string)) + a.Foo.Bar }
|
h := func(a s2) int { return len(a.FooBar.(string)) + a.Foo.Bar }
|
||||||
t.Run("any interface", testExec(testCase{impl: h, command: "foo --foo-bar 42"}, "", "44"))
|
t.Run("any interface", testExec(testCase{impl: h, command: "foo --foo-bar 42"}, "", "44"))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("bind failure", func(t *testing.T) {
|
||||||
|
f := func(s struct{ Foo time.Duration }) time.Duration { return s.Foo }
|
||||||
|
t.Run("no parse", testExec(testCase{impl: f, conf: "bar=baz", command: "foo"}, "", "0s"))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("indices of positional parameters", func(t *testing.T) {
|
||||||
|
t.Run(
|
||||||
|
"show function params",
|
||||||
|
testExec(testCase{impl: testlib.Foo, command: "foo help", contains: true}, "", "foo help"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"skip non-positional params",
|
||||||
|
testExec(testCase{impl: testlib.Bar, command: "bar help", contains: true}, "", "bar help"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("scalar type string", func(t *testing.T) {
|
||||||
|
t.Run(
|
||||||
|
"show help with options",
|
||||||
|
testExec(
|
||||||
|
testCase{impl: testlib.Baz, command: "baz help", contains: true},
|
||||||
|
"",
|
||||||
|
"baz help",
|
||||||
|
"--foo int",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user