structured flags as alternative to config #76
This commit is contained in:
+9
-3
@@ -1,8 +1,15 @@
|
||||
language: go
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
go:
|
||||
- 1.6.x
|
||||
- 1.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
|
||||
install:
|
||||
# go-flags
|
||||
@@ -10,8 +17,7 @@ install:
|
||||
- go build -v ./...
|
||||
|
||||
# linting
|
||||
- go get github.com/golang/lint
|
||||
- go install github.com/golang/lint/golint
|
||||
- go get github.com/golang/lint/golint
|
||||
|
||||
# code coverage
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
|
||||
-1
@@ -110,7 +110,6 @@ args, err := flags.ParseArgs(&opts, args)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Verbosity: %v\n", opts.Verbose)
|
||||
|
||||
+12
-2
@@ -5,7 +5,6 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Command represents an application command. Commands can be added to the
|
||||
@@ -229,7 +228,17 @@ func (c *Command) scanSubcommandHandler(parentg *Group) scanHandler {
|
||||
subcommand := mtag.Get("command")
|
||||
|
||||
if len(subcommand) != 0 {
|
||||
ptrval := reflect.NewAt(realval.Type(), unsafe.Pointer(realval.UnsafeAddr()))
|
||||
var ptrval reflect.Value
|
||||
|
||||
if realval.Kind() == reflect.Ptr {
|
||||
ptrval = realval
|
||||
|
||||
if ptrval.IsNil() {
|
||||
ptrval.Set(reflect.New(ptrval.Type().Elem()))
|
||||
}
|
||||
} else {
|
||||
ptrval = realval.Addr()
|
||||
}
|
||||
|
||||
shortDescription := mtag.Get("description")
|
||||
longDescription := mtag.Get("long-description")
|
||||
@@ -237,6 +246,7 @@ func (c *Command) scanSubcommandHandler(parentg *Group) scanHandler {
|
||||
aliases := mtag.GetMany("alias")
|
||||
|
||||
subc, err := c.AddCommand(subcommand, shortDescription, longDescription, ptrval.Interface())
|
||||
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
+4
@@ -125,6 +125,10 @@ The following is a list of tags for struct fields supported by go-flags:
|
||||
gets prepended to every option's long name and
|
||||
subgroup's namespace of this group, separated by
|
||||
the parser's namespace delimiter (optional)
|
||||
env-namespace: when specified on a group struct field, the env-namespace
|
||||
gets prepended to every option's env key and
|
||||
subgroup's env-namespace of this group, separated by
|
||||
the parser's env-namespace delimiter (optional)
|
||||
command: when specified on a struct field, makes the struct
|
||||
field a (sub)command with the given name (optional)
|
||||
subcommands-optional: when specified on a command struct field, makes
|
||||
|
||||
+17
-2
@@ -9,7 +9,6 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ErrNotPointerToStruct indicates that a provided data container is not
|
||||
@@ -35,6 +34,9 @@ type Group struct {
|
||||
// The namespace of the group
|
||||
Namespace string
|
||||
|
||||
// The environment namespace of the group
|
||||
EnvNamespace string
|
||||
|
||||
// If true, the group is not displayed in the help or man page
|
||||
Hidden bool
|
||||
|
||||
@@ -338,15 +340,28 @@ func (g *Group) scanSubGroupHandler(realval reflect.Value, sfield *reflect.Struc
|
||||
subgroup := mtag.Get("group")
|
||||
|
||||
if len(subgroup) != 0 {
|
||||
ptrval := reflect.NewAt(realval.Type(), unsafe.Pointer(realval.UnsafeAddr()))
|
||||
var ptrval reflect.Value
|
||||
|
||||
if realval.Kind() == reflect.Ptr {
|
||||
ptrval = realval
|
||||
|
||||
if ptrval.IsNil() {
|
||||
ptrval.Set(reflect.New(ptrval.Type()))
|
||||
}
|
||||
} else {
|
||||
ptrval = realval.Addr()
|
||||
}
|
||||
|
||||
description := mtag.Get("description")
|
||||
|
||||
group, err := g.AddGroup(subgroup, description, ptrval.Interface())
|
||||
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
group.Namespace = mtag.Get("namespace")
|
||||
group.EnvNamespace = mtag.Get("env-namespace")
|
||||
group.Hidden = mtag.Get("hidden") != ""
|
||||
|
||||
return true, nil
|
||||
|
||||
+3
-3
@@ -225,12 +225,12 @@ func (p *Parser) writeHelpOption(writer *bufio.Writer, option *Option, info alig
|
||||
}
|
||||
|
||||
var envDef string
|
||||
if option.EnvDefaultKey != "" {
|
||||
if option.EnvKeyWithNamespace() != "" {
|
||||
var envPrintable string
|
||||
if runtime.GOOS == "windows" {
|
||||
envPrintable = "%" + option.EnvDefaultKey + "%"
|
||||
envPrintable = "%" + option.EnvKeyWithNamespace() + "%"
|
||||
} else {
|
||||
envPrintable = "$" + option.EnvDefaultKey
|
||||
envPrintable = "$" + option.EnvKeyWithNamespace()
|
||||
}
|
||||
envDef = fmt.Sprintf(" [%s]", envPrintable)
|
||||
}
|
||||
|
||||
+3
-3
@@ -83,11 +83,11 @@ func writeManPageOptions(wr io.Writer, grp *Group) {
|
||||
|
||||
if len(opt.Default) != 0 {
|
||||
fmt.Fprintf(wr, " <default: \\fI%s\\fR>", manQuote(strings.Join(quoteV(opt.Default), ", ")))
|
||||
} else if len(opt.EnvDefaultKey) != 0 {
|
||||
} else if len(opt.EnvKeyWithNamespace()) != 0 {
|
||||
if runtime.GOOS == "windows" {
|
||||
fmt.Fprintf(wr, " <default: \\fI%%%s%%\\fR>", manQuote(opt.EnvDefaultKey))
|
||||
fmt.Fprintf(wr, " <default: \\fI%%%s%%\\fR>", manQuote(opt.EnvKeyWithNamespace()))
|
||||
} else {
|
||||
fmt.Fprintf(wr, " <default: \\fI$%s\\fR>", manQuote(opt.EnvDefaultKey))
|
||||
fmt.Fprintf(wr, " <default: \\fI$%s\\fR>", manQuote(opt.EnvKeyWithNamespace()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+55
-7
@@ -3,9 +3,9 @@ package flags
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
@@ -139,6 +139,57 @@ func (option *Option) LongNameWithNamespace() string {
|
||||
return longName
|
||||
}
|
||||
|
||||
// EnvKeyWithNamespace returns the option's env key with the group namespaces
|
||||
// prepended by walking up the option's group tree. Namespaces and the env key
|
||||
// itself are separated by the parser's namespace delimiter. If the env key is
|
||||
// empty an empty string is returned.
|
||||
func (option *Option) EnvKeyWithNamespace() string {
|
||||
if len(option.EnvDefaultKey) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// fetch the namespace delimiter from the parser which is always at the
|
||||
// end of the group hierarchy
|
||||
namespaceDelimiter := ""
|
||||
g := option.group
|
||||
|
||||
for {
|
||||
if p, ok := g.parent.(*Parser); ok {
|
||||
namespaceDelimiter = p.EnvNamespaceDelimiter
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
switch i := g.parent.(type) {
|
||||
case *Command:
|
||||
g = i.Group
|
||||
case *Group:
|
||||
g = i
|
||||
}
|
||||
}
|
||||
|
||||
// concatenate long name with namespace
|
||||
key := option.EnvDefaultKey
|
||||
g = option.group
|
||||
|
||||
for g != nil {
|
||||
if g.EnvNamespace != "" {
|
||||
key = g.EnvNamespace + namespaceDelimiter + key
|
||||
}
|
||||
|
||||
switch i := g.parent.(type) {
|
||||
case *Command:
|
||||
g = i.Group
|
||||
case *Group:
|
||||
g = i
|
||||
case *Parser:
|
||||
g = nil
|
||||
}
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
// String converts an option to a human friendly readable string describing the
|
||||
// option.
|
||||
func (option *Option) String() string {
|
||||
@@ -260,13 +311,10 @@ func (option *Option) empty() {
|
||||
func (option *Option) clearDefault() {
|
||||
usedDefault := option.Default
|
||||
|
||||
if envKey := option.EnvDefaultKey; envKey != "" {
|
||||
// os.Getenv() makes no distinction between undefined and
|
||||
// empty values, so we use syscall.Getenv()
|
||||
if value, ok := syscall.Getenv(envKey); ok {
|
||||
if envKey := option.EnvKeyWithNamespace(); envKey != "" {
|
||||
if value, ok := os.LookupEnv(envKey); ok {
|
||||
if option.EnvDefaultDelim != "" {
|
||||
usedDefault = strings.Split(value,
|
||||
option.EnvDefaultDelim)
|
||||
usedDefault = strings.Split(value, option.EnvDefaultDelim)
|
||||
} else {
|
||||
usedDefault = []string{value}
|
||||
}
|
||||
|
||||
+7
-3
@@ -29,6 +29,9 @@ type Parser struct {
|
||||
// NamespaceDelimiter separates group namespaces and option long names
|
||||
NamespaceDelimiter string
|
||||
|
||||
// EnvNamespaceDelimiter separates group env namespaces and env keys
|
||||
EnvNamespaceDelimiter string
|
||||
|
||||
// UnknownOptionsHandler is a function which gets called when the parser
|
||||
// encounters an unknown option. The function receives the unknown option
|
||||
// name, a SplitArgument which specifies its value if set with an argument
|
||||
@@ -170,9 +173,10 @@ func NewParser(data interface{}, options Options) *Parser {
|
||||
// be added to this parser by using AddGroup and AddCommand.
|
||||
func NewNamedParser(appname string, options Options) *Parser {
|
||||
p := &Parser{
|
||||
Command: newCommand(appname, "", "", nil),
|
||||
Options: options,
|
||||
NamespaceDelimiter: ".",
|
||||
Command: newCommand(appname, "", "", nil),
|
||||
Options: options,
|
||||
NamespaceDelimiter: ".",
|
||||
EnvNamespaceDelimiter: "_",
|
||||
}
|
||||
|
||||
p.Command.parent = p
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// +build !windows,!plan9,!solaris
|
||||
// +build !windows,!plan9,!solaris,!appengine
|
||||
|
||||
package flags
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// +build windows plan9 solaris
|
||||
// +build windows plan9 solaris appengine
|
||||
|
||||
package flags
|
||||
|
||||
|
||||
Generated
Vendored
Generated
Vendored
Reference in New Issue
Block a user