Skip to content

Commit

Permalink
Bind --double-dash flags to untagged TitleCase fields
Browse files Browse the repository at this point in the history
  • Loading branch information
aviddiviner committed Dec 19, 2016
1 parent 830e3c4 commit 7042aa8
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 25 deletions.
64 changes: 39 additions & 25 deletions opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,7 @@ func (o Opts) Bind(v interface{}) error {
}
tag := field.Tag.Get("docopt")
if tag == "" {
n := strings.ToLower(field.Name)
untagged[n] = i
untagged[field.Name] = i
continue
}
for _, t := range strings.Split(tag, ",") {
Expand All @@ -141,38 +140,23 @@ func (o Opts) Bind(v interface{}) error {
}

// Get the index of the struct field to use, based on the option key.
// Returns -1 if nothing is matched.
getFieldIndex := func(key string) int {
// Second argument is true/false on whether something was matched.
getFieldIndex := func(key string) (int, bool) {
if i, ok := tagged[key]; ok {
return i
return i, true
}
switch {
case strings.HasPrefix(key, "--") && len(key[2:]) > 1:
if i, ok := untagged[strings.ToLower(key[2:])]; ok {
return i
}
case strings.HasPrefix(key, "-") && len(key[1:]) == 1:
if i, ok := untagged[strings.ToLower(key[1:])]; ok {
return i
}
case strings.HasPrefix(key, "<") && strings.HasSuffix(key, ">"):
if i, ok := untagged[strings.ToLower(key[1:len(key)-1])]; ok {
return i
}
default:
if i, ok := untagged[strings.ToLower(key)]; ok {
return i
}
if i, ok := untagged[guessUntaggedField(key)]; ok {
return i, true
}
return -1
return -1, false
}

indexMap := make(map[string]int) // Option keys to field index

// Pre-check that option keys are mapped to fields and fields are zero valued, before populating them.
for k := range o {
i := getFieldIndex(k)
if i < 0 {
i, ok := getFieldIndex(k)
if !ok {
if k == "--help" || k == "--version" { // Don't require these to be mapped.
continue
}
Expand Down Expand Up @@ -248,3 +232,33 @@ func (o Opts) Bind(v interface{}) error {
func isUnexportedField(field reflect.StructField) bool {
return !(field.PkgPath == "" && unicode.IsUpper(rune(field.Name[0])))
}

// Convert a string like "--my-special-flag" to "MySpecialFlag".
func titleCaseDashes(key string) string {
nextToUpper := true
mapFn := func(r rune) rune {
if r == '-' {
nextToUpper = true
return -1
}
if nextToUpper {
nextToUpper = false
return unicode.ToUpper(r)
}
return r
}
return strings.Map(mapFn, key)
}

// Best guess which field.Name in a struct to assign for an option key.
func guessUntaggedField(key string) string {
switch {
case strings.HasPrefix(key, "--") && len(key[2:]) > 1:
return titleCaseDashes(key[2:])
case strings.HasPrefix(key, "-") && len(key[1:]) == 1:
return titleCaseDashes(key[1:])
case strings.HasPrefix(key, "<") && strings.HasSuffix(key, ">"):
key = key[1 : len(key)-1]
}
return strings.Title(strings.ToLower(key))
}
18 changes: 18 additions & 0 deletions opts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,3 +318,21 @@ func TestBindDoubleDashTag(t *testing.T) {
t.Fail()
}
}

func TestBindHyphenatedTags(t *testing.T) {
var testParser = &Parser{HelpHandler: NoHelpHandler, SkipHelpFlags: true}
opts, err := testParser.ParseArgs("Usage: prog --opt-one=N --opt-two=N", []string{"--opt-one", "123", "--opt-two", "234"}, "")
if err != nil {
t.Fatal(err)
}
var opt struct {
OptOne string
OptTwo string
}
if err := opts.Bind(&opt); err != nil {
t.Fatal(err)
}
if opt.OptOne != "123" || opt.OptTwo != "234" {
t.Fail()
}
}

0 comments on commit 7042aa8

Please sign in to comment.