package wand import ( "fmt" "strings" "testing" ) func TestCommandLine(t *testing.T) { type f struct { One string SecondField int } ff := func(f f) string { return f.One + fmt.Sprint(f.SecondField) } type b struct{ One, Two, Three, Four bool } fb := func(b b) string { return fmt.Sprintf("%t;%t;%t;%t", b.One, b.Two, b.Three, b.Four) } fbp := func(b b, p ...string) string { o := fb(b) if len(p) == 0 { return o } s := []string{o} for _, pi := range p { s = append(s, pi) } return strings.Join(s, ";") } type m struct { One, Two bool Three string } fm := func(m m) string { return fmt.Sprintf("%t;%t;%s", m.One, m.Two, m.Three) } type l struct { One []bool Two []string } fl := func(l l) string { var sb []string for _, b := range l.One { sb = append(sb, fmt.Sprint(b)) } return strings.Join([]string{strings.Join(sb, ","), strings.Join(l.Two, ",")}, ";") } type lb struct{ One, Two, Three []bool } flb := func(lb lb) string { var s []string for _, b := range [][]bool{lb.One, lb.Two, lb.Three} { var sb []string for _, bi := range b { sb = append(sb, fmt.Sprint(bi)) } s = append(s, strings.Join(sb, ",")) } return strings.Join(s, ";") } fp := func(f f, a ...string) string { o := ff(f) return fmt.Sprintf("%s;%s", o, strings.Join(a, ",")) } type d struct{ One2 bool } fd := func(d d) string { return fmt.Sprint(d.One2) } t.Run("no args", testExec(testCase{impl: ff, command: "foo"}, "", "0")) t.Run("basic options", func(t *testing.T) { t.Run("space", testExec(testCase{impl: ff, command: "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( "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( "bools, short", testExec( testCase{ impl: ShortForm(Command("foo", fl), "a", "one"), command: "foo -a -a -a", }, "", "true,true,true;", ), ) 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( "short", testExec( testCase{ impl: ShortForm(Command("foo", fb), "a", "one"), command: "foo -a", }, "", "true;false;false;false", ), ) t.Run( "short, multiple", testExec( testCase{ impl: ShortForm(Command("foo", fb), "a", "one", "b", "two", "c", "three"), command: "foo -a -b -c", }, "", "true;true;true;false", ), ) t.Run( "short, combined", testExec( testCase{ impl: ShortForm(Command("foo", fb), "a", "one", "b", "two", "c", "three"), command: "foo -abc", }, "", "true;true;true;false", ), ) t.Run( "short, combined, multiple", 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( "short, multiple values", testExec( testCase{ impl: ShortForm(Command("foo", flb), "a", "one", "b", "two", "c", "three"), command: "foo -aba -cab", }, "", "true,true,true;true,true;true", ), ) t.Run( "long", testExec( testCase{ 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( "short, implicit", testExec( testCase{ impl: ShortForm(Command("foo", fb), "a", "one"), command: "foo -a", }, "", "true;false;false;false"), ) t.Run( "short, explicit", 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( "basic", testExec( testCase{ impl: fp, command: "foo bar baz", }, "", "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) { type s struct { Foo bool Bar []bool Qux bool Quux string } fs := func(s s, a1, a2 string) string { var sbar []string for _, b := range s.Bar { sbar = append(sbar, fmt.Sprint(b)) } return fmt.Sprintf("%t;%s;%t;%s;%s;%s", s.Foo, strings.Join(sbar, ","), s.Qux, s.Quux, a1, a2) } t.Run( "full", testExec( testCase{ impl: ShortForm(Command("foo", fs), "a", "foo", "b", "bar"), command: "foo -ab --bar baz -b --qux --quux corge -- grault", }, "", "true;true,true,true;true;corge;baz;grault", ), ) }) t.Run("expected or unexpected", func(t *testing.T) { t.Run( "capital letters", testExec( testCase{ impl: fp, command: "foo --One bar", }, "", "0;--One,bar", ), ) t.Run( "digit in option name", 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( "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( "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", ), ) }) }