diff --git a/build.go b/build.go index ad4a767..13adda2 100644 --- a/build.go +++ b/build.go @@ -49,7 +49,7 @@ func buildNode(v reflect.Value, typ NodeType, seenFlags map[string]bool) *Node { name = strings.ToLower(dashedString(ft.Name)) } - tag := parseTag(fv, ft.Tag.Get("kong")) + tag := parseTag(fv, ft) // Nested structs are either commands or args. if ft.Type.Kind() == reflect.Struct && (tag.Cmd || tag.Arg) { diff --git a/kong_test.go b/kong_test.go index 7fbdbff..201615c 100644 --- a/kong_test.go +++ b/kong_test.go @@ -246,64 +246,6 @@ func TestMixedRequiredArgs(t *testing.T) { }) } -func TestDefaultValueForOptionalArg(t *testing.T) { - var cli struct { - Arg string `kong:"arg,optional,default='👌'"` - } - p := mustNew(t, &cli) - _, err := p.Parse(nil) - require.NoError(t, err) - require.Equal(t, "👌", cli.Arg) -} - -func TestNoValueInTag(t *testing.T) { - var cli struct { - Empty1 string `kong:"default"` - Empty2 string `kong:"default="` - } - p := mustNew(t, &cli) - _, err := p.Parse(nil) - require.NoError(t, err) - require.Equal(t, "", cli.Empty1) - require.Equal(t, "", cli.Empty2) -} - -func TestCommaInQuotes(t *testing.T) { - var cli struct { - Numbers string `kong:"default='1,2'"` - } - p := mustNew(t, &cli) - _, err := p.Parse(nil) - require.NoError(t, err) - require.Equal(t, "1,2", cli.Numbers) -} - -func TestBadString(t *testing.T) { - var cli struct { - Numbers string `kong:"default='yay'n"` - } - _, err := New(&cli) - require.Error(t, err) -} - -func TestNoQuoteEnd(t *testing.T) { - var cli struct { - Numbers string `kong:"default='yay"` - } - _, err := New(&cli) - require.Error(t, err) -} - -func TestEscapedQuote(t *testing.T) { - var cli struct { - DoYouKnow string `kong:"default='i don\\'t know'"` - } - p := mustNew(t, &cli) - _, err := p.Parse(nil) - require.NoError(t, err) - require.Equal(t, "i don't know", cli.DoYouKnow) -} - func TestInvalidDefaultErrors(t *testing.T) { var cli struct { Flag int `kong:"default='foo'"` diff --git a/tag.go b/tag.go index ed0d938..fe4f874 100644 --- a/tag.go +++ b/tag.go @@ -26,9 +26,15 @@ type Tag struct { items map[string]string } -func parseCSV(s string) map[string]string { - d := map[string]string{} +type tagChars struct { + sep, quote, assign rune +} +var kongChars = tagChars{sep: ',', quote: '\'', assign: '='} +var bareChars = tagChars{sep: ' ', quote: '"', assign: ':'} + +func parseCSV(s string, chr tagChars) map[string]string { + d := map[string]string{} key := []rune{} value := []rune{} quotes := false @@ -51,23 +57,23 @@ func parseCSV(s string) map[string]string { } else { eof = true } - if !quotes && r == ',' { + if !quotes && r == chr.sep { add() continue } - if r == '=' && inKey { + if r == chr.assign && inKey { inKey = false continue } if r == '\\' { - if next == '\'' { + if next == chr.quote { idx++ - r = '\'' + r = chr.quote } - } else if r == '\'' { + } else if r == chr.quote { if quotes { quotes = false - if next == ',' || eof { + if next == chr.sep || eof { continue } fail("%v has an unexpected char at pos %v", s, idx) @@ -91,7 +97,17 @@ func parseCSV(s string) map[string]string { return d } -func parseTag(fv reflect.Value, s string) *Tag { +func getTagInfo(ft reflect.StructField) (string, tagChars) { + s, ok := ft.Tag.Lookup("kong") + if ok { + return s, kongChars + } + + return string(ft.Tag), bareChars +} + +func parseTag(fv reflect.Value, ft reflect.StructField) *Tag { + s, chars := getTagInfo(ft) t := &Tag{ items: map[string]string{}, } @@ -99,7 +115,7 @@ func parseTag(fv reflect.Value, s string) *Tag { return t } - t.items = parseCSV(s) + t.items = parseCSV(s, chars) t.Cmd = t.Has("cmd") t.Arg = t.Has("arg") diff --git a/tag_test.go b/tag_test.go new file mode 100644 index 0000000..1decec5 --- /dev/null +++ b/tag_test.go @@ -0,0 +1,95 @@ +package kong + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDefaultValueForOptionalArg(t *testing.T) { + var cli struct { + Arg string `kong:"arg,optional,default='👌'"` + } + p := mustNew(t, &cli) + _, err := p.Parse(nil) + require.NoError(t, err) + require.Equal(t, "👌", cli.Arg) +} + +func TestNoValueInTag(t *testing.T) { + var cli struct { + Empty1 string `kong:"default"` + Empty2 string `kong:"default="` + } + p := mustNew(t, &cli) + _, err := p.Parse(nil) + require.NoError(t, err) + require.Equal(t, "", cli.Empty1) + require.Equal(t, "", cli.Empty2) +} + +func TestCommaInQuotes(t *testing.T) { + var cli struct { + Numbers string `kong:"default='1,2'"` + } + p := mustNew(t, &cli) + _, err := p.Parse(nil) + require.NoError(t, err) + require.Equal(t, "1,2", cli.Numbers) +} + +func TestBadString(t *testing.T) { + var cli struct { + Numbers string `kong:"default='yay'n"` + } + _, err := New(&cli) + require.Error(t, err) +} + +func TestNoQuoteEnd(t *testing.T) { + var cli struct { + Numbers string `kong:"default='yay"` + } + _, err := New(&cli) + require.Error(t, err) +} + +func TestEscapedQuote(t *testing.T) { + var cli struct { + DoYouKnow string `kong:"default='i don\\'t know'"` + } + p := mustNew(t, &cli) + _, err := p.Parse(nil) + require.NoError(t, err) + require.Equal(t, "i don't know", cli.DoYouKnow) +} + +func TestBareTags(t *testing.T) { + var cli struct { + Cmd struct { + Arg string `arg` + Flag string `required default:"👌"` + } `cmd` + } + + p := mustNew(t, &cli) + _, err := p.Parse([]string{"cmd", "arg", "--flag=hi"}) + require.NoError(t, err) + require.Equal(t, "hi", cli.Cmd.Flag) + require.Equal(t, "arg", cli.Cmd.Arg) +} + +func TestBareTagsWithJsonTag(t *testing.T) { + var cli struct { + Cmd struct { + Arg string `json:"-" optional arg` + Flag string `json:"best_flag" default:"\"'👌'\""` + } `cmd json:"CMD"` + } + + p := mustNew(t, &cli) + _, err := p.Parse([]string{"cmd"}) + require.NoError(t, err) + require.Equal(t, "\"'👌'\"", cli.Cmd.Flag) + require.Equal(t, "", cli.Cmd.Arg) +}