Commit 417d0d4c authored by Drew Parsons's avatar Drew Parsons

New upstream version 0.0.0-20180109-2146c8d

parent 44df4f86
MIT License
Copyright (c) 2016 Matt Joiner
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# tagflag
[![GoDoc](https://godoc.org/github.com/anacrolix/tagflag?status.svg)](https://godoc.org/github.com/anacrolix/tagflag)
See the thorough package documentation.
package tagflag
import (
"fmt"
"reflect"
)
type arg struct {
arity arity
name string
help string
value reflect.Value
}
func (me arg) hasZeroValue() bool {
return reflect.DeepEqual(
reflect.Zero(me.value.Type()).Interface(),
me.value.Interface())
}
func (me arg) marshal(s string, explicitValue bool) error {
m := valueMarshaler(me.value)
if !explicitValue && m.RequiresExplicitValue() {
return userError{fmt.Sprintf("explicit value required (%s%s=VALUE)", flagPrefix, me.name)}
}
return valueMarshaler(me.value).Marshal(me.value, s)
}
package tagflag
import (
"net"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
)
func TestEqualZeroArgValue(t *testing.T) {
a := arg{value: reflect.ValueOf(net.IP(nil))}
assert.True(t, a.hasZeroValue())
b := arg{value: reflect.ValueOf(net.ParseIP("127.0.0.1"))}
assert.False(t, b.hasZeroValue())
}
package tagflag
import (
"fmt"
"reflect"
)
const infArity = 1000
type arity struct {
min, max int
}
func fieldArity(v reflect.Value, sf reflect.StructField) (arity arity) {
arity.min = 1
arity.max = 1
if v.Kind() == reflect.Slice {
arity.max = infArity
}
if sf.Tag.Get("arity") != "" {
switch sf.Tag.Get("arity") {
case "?":
arity.min = 0
case "*":
arity.min = 0
arity.max = infArity
case "+":
arity.max = infArity
default:
panic(fmt.Sprintf("unhandled arity tag: %q", sf.Tag.Get("arity")))
}
}
return
}
package tagflag
import (
"net"
"net/url"
"reflect"
"time"
)
var builtinMarshalers = map[reflect.Type]marshaler{}
// Convenience function to allow adding marshalers using typed functions.
// marshalFunc is of type func(arg string) T or func(arg string) (T, error),
// where T is the type the function can marshal.
func addBuiltinDynamicMarshaler(marshalFunc interface{}, explicitValueRequired bool) {
marshalFuncValue := reflect.ValueOf(marshalFunc)
marshalType := marshalFuncValue.Type().Out(0)
builtinMarshalers[marshalType] = dynamicMarshaler{
marshal: func(marshalValue reflect.Value, arg string) error {
out := marshalFuncValue.Call([]reflect.Value{reflect.ValueOf(arg)})
marshalValue.Set(out[0])
if len(out) > 1 {
i := out[1].Interface()
if i != nil {
return i.(error)
}
}
return nil
},
explicitValueRequired: explicitValueRequired,
}
}
func init() {
// These are some simple builtin types that are nice to be handled without
// wrappers that implement Marshaler. Note that if they return pointer
// types, those must be used in the flag struct, because there's no way to
// know that nothing depends on the address returned.
addBuiltinDynamicMarshaler(func(urlStr string) (*url.URL, error) {
return url.Parse(urlStr)
}, false)
// Empty strings for this type are valid, so we enforce that the value is
// explicit (=), so that the user knows what they're getting into.
addBuiltinDynamicMarshaler(func(s string) (*net.TCPAddr, error) {
if s == "" {
return nil, nil
}
return net.ResolveTCPAddr("tcp", s)
}, true)
addBuiltinDynamicMarshaler(func(s string) (time.Duration, error) {
return time.ParseDuration(s)
}, false)
addBuiltinDynamicMarshaler(func(s string) net.IP {
return net.ParseIP(s)
}, false)
}
package tagflag
import "github.com/dustin/go-humanize"
// A nice builtin type that will marshal human readable byte quantities to
// int64. For example 100GB. See https://godoc.org/github.com/dustin/go-humanize.
type Bytes int64
var _ Marshaler = new(Bytes)
func (me *Bytes) Marshal(s string) (err error) {
ui64, err := humanize.ParseBytes(s)
if err != nil {
return
}
*me = Bytes(ui64)
return
}
func (Bytes) RequiresExplicitValue() bool {
return false
}
func (me Bytes) Int64() int64 {
return int64(me)
}
func (me Bytes) String() string {
return humanize.Bytes(uint64(me))
}
// Package tagflag uses reflection to derive flags and positional arguments to a
// program, and parses and sets them from a slice of arguments.
//
// For example:
// var opts struct {
// Mmap bool `help:"memory-map torrent data"`
// TestPeer []*net.TCPAddr `help:"addresses of some starting peers"`
// tagflag.StartPos // Marks beginning of positional arguments.
// Torrent []string `arity:"+" help:"torrent file path or magnet uri"`
// }
// tagflag.Parse(&opts)
//
// Supported tags include:
// help: a line of text to show after the option
// arity: defaults to 1. the number of arguments a field requires, or ? for one
// optional argument, + for one or more, or * for zero or more.
//
// MarshalArgs is called on fields that implement ArgsMarshaler. A number of
// arguments matching the arity of the field are passed if possible.
//
// Slices will collect successive values, within the provided arity constraints.
//
// A few helpful types have builtin marshallers, for example Bytes,
// *net.TCPAddr, *url.URL, time.Duration, and net.IP.
//
// Flags are strictly passed with the form -K or -K=V. No space between -K and
// the value is allowed. This allows positional arguments to be mixed in with
// flags, and prevents any confusion due to some flags occasionally not taking
// values. A `--` will terminate flag parsing, and treat all further arguments
// as positional.
//
// A builtin help and usage printer are provided, and activated when passing
// -h or -help.
//
// Flag and positional argument names are automatically munged to fit the
// standard scheme within tagflag.
package tagflag
package tagflag
type userError struct {
msg string
}
func (ue userError) Error() string {
return ue.msg
}
package tagflag
import "fmt"
var ErrFieldsAfterExcessArgs = fmt.Errorf("field(s) after %T", ExcessArgs{})
type ExcessArgs []string
package tagflag
import (
"fmt"
"reflect"
"strconv"
)
type Marshaler interface {
Marshal(in string) error
RequiresExplicitValue() bool
}
type marshaler interface {
Marshal(reflect.Value, string) error
RequiresExplicitValue() bool
}
type dynamicMarshaler struct {
explicitValueRequired bool
marshal func(reflect.Value, string) error
}
func (me dynamicMarshaler) Marshal(v reflect.Value, s string) error {
return me.marshal(v, s)
}
func (me dynamicMarshaler) RequiresExplicitValue() bool {
return me.explicitValueRequired
}
// The fallback marshaler, that attempts to use fmt.Sscan, and recursion to
// sort marshal types.
type defaultMarshaler struct{}
func (defaultMarshaler) Marshal(v reflect.Value, s string) error {
if v.Kind() == reflect.Slice {
n := reflect.New(v.Type().Elem())
m := valueMarshaler(n.Elem())
if m == nil {
return fmt.Errorf("can't marshal type %s", n.Elem().Type())
}
err := m.Marshal(n.Elem(), s)
if err != nil {
return err
}
v.Set(reflect.Append(v, n.Elem()))
return nil
}
switch v.Kind() {
case reflect.Int:
x, err := strconv.ParseInt(s, 0, 0)
v.SetInt(x)
return err
case reflect.Uint:
x, err := strconv.ParseUint(s, 0, 0)
v.SetUint(x)
return err
case reflect.Int64:
x, err := strconv.ParseInt(s, 0, 64)
v.SetInt(x)
return err
case reflect.String:
v.SetString(s)
return nil
default:
return fmt.Errorf("unhandled builtin type: %s", v.Type().String())
}
}
func (defaultMarshaler) RequiresExplicitValue() bool {
return true
}
package tagflag
import (
"reflect"
"regexp"
"strconv"
"strings"
"github.com/bradfitz/iter"
)
const flagPrefix = "-"
// Walks the fields of the given struct, calling the function with the value
// and StructField for each field. Returning true from the function will halt
// traversal.
func foreachStructField(_struct reflect.Value, f func(fv reflect.Value, sf reflect.StructField) (stop bool)) {
t := _struct.Type()
for i := range iter.N(t.NumField()) {
sf := t.Field(i)
fv := _struct.Field(i)
if f(fv, sf) {
break
}
}
}
func canMarshal(f reflect.Value) bool {
return valueMarshaler(f) != nil
}
// Returns a marshaler for the given value, or nil if there isn't one.
func valueMarshaler(v reflect.Value) marshaler {
if v.CanAddr() {
if am, ok := v.Addr().Interface().(Marshaler); ok {
return dynamicMarshaler{
marshal: func(_ reflect.Value, s string) error { return am.Marshal(s) },
explicitValueRequired: am.RequiresExplicitValue(),
}
}
}
if bm, ok := builtinMarshalers[v.Type()]; ok {
return bm
}
switch v.Kind() {
case reflect.Ptr, reflect.Struct:
return nil
case reflect.Bool:
return dynamicMarshaler{
marshal: func(v reflect.Value, s string) error {
if s == "" {
v.SetBool(true)
return nil
}
b, err := strconv.ParseBool(s)
v.SetBool(b)
return err
},
explicitValueRequired: false,
}
}
return defaultMarshaler{}
}
// Turn a struct field name into a flag name. In particular this lower cases
// leading acronyms, and the first capital letter.
func fieldFlagName(fieldName string) flagNameComponent {
return flagNameComponent(func() string {
// TCP
if ss := regexp.MustCompile("^[[:upper:]]{2,}$").FindStringSubmatch(fieldName); ss != nil {
return strings.ToLower(ss[0])
}
// TCPAddr
if ss := regexp.MustCompile("^([[:upper:]]+)([[:upper:]][^[:upper:]].*?)$").FindStringSubmatch(fieldName); ss != nil {
return strings.ToLower(ss[1]) + ss[2]
}
// Addr
if ss := regexp.MustCompile("^([[:upper:]])(.*)$").FindStringSubmatch(fieldName); ss != nil {
return strings.ToLower(ss[1]) + ss[2]
}
panic(fieldName)
}())
}
package tagflag
type parseOpt func(p *parser)
// Don't perform default behaviour if -h or -help are passed.
func NoDefaultHelp() parseOpt {
return func(p *parser) {
p.noDefaultHelp = true
}
}
// Provides a description for the program to be shown in the usage message.
func Description(desc string) parseOpt {
return func(p *parser) {
p.description = desc
}
}
func Program(name string) parseOpt {
return func(p *parser) {
p.program = name
}
}
package tagflag
import (
"fmt"
"log"
"reflect"
"strings"
"github.com/anacrolix/missinggo/slices"
"github.com/huandu/xstrings"
)
type parser struct {
// The value from which the parser is built, and values are assigned.
cmd interface{}
// Disables the default handling of -h and -help.
noDefaultHelp bool
program string
description string
posArgs []arg
// Maps -K=V to map[K]arg(V)
flags map[string]arg
excess *ExcessArgs
// Count of positional arguments parsed so far. Used to locate the next
// positional argument where it's non-trivial (non-unity arity).
numPos int
}
func (p *parser) hasOptions() bool {
return len(p.flags) != 0
}
func (p *parser) parse(args []string) (err error) {
posOnly := false
for len(args) != 0 {
if p.excess != nil && p.nextPosArg() == nil {
*p.excess = args
return
}
a := args[0]
args = args[1:]
if !posOnly && a == "--" {
posOnly = true
continue
}
if !posOnly && isFlag(a) {
err = p.parseFlag(a[1:])
} else {
err = p.parsePos(a)
}
if err != nil {
return
}
}
if p.numPos < p.minPos() {
return userError{fmt.Sprintf("missing argument: %q", p.indexPosArg(p.numPos).name)}
}
return
}
func (p *parser) minPos() (min int) {
for _, arg := range p.posArgs {
min += arg.arity.min
}
return
}
func newParser(cmd interface{}, opts ...parseOpt) (p *parser, err error) {
p = &parser{
cmd: cmd,
}
for _, opt := range opts {
opt(p)
}
err = p.parseCmd()
return
}
func (p *parser) parseCmd() error {
if p.cmd == nil {
return nil
}
s := reflect.ValueOf(p.cmd).Elem()
if s.Kind() != reflect.Struct {
return fmt.Errorf("expected struct got %s", s.Type())
}
return p.parseStruct(reflect.ValueOf(p.cmd).Elem(), nil)
}
// Positional arguments are marked per struct.
func (p *parser) parseStruct(st reflect.Value, path []flagNameComponent) (err error) {
posStarted := false
foreachStructField(st, func(f reflect.Value, sf reflect.StructField) (stop bool) {
if !posStarted && f.Type() == reflect.TypeOf(StartPos{}) {
posStarted = true
return false
}
if f.Type() == reflect.TypeOf(ExcessArgs{}) {
p.excess = f.Addr().Interface().(*ExcessArgs)
return false
}
if sf.PkgPath != "" {
return false
}
if p.excess != nil {
err = ErrFieldsAfterExcessArgs
return true
}
if canMarshal(f) {
if posStarted {
err = p.addPos(f, sf, path)
} else {
err = p.addFlag(f, sf, path)
if err != nil {
err = fmt.Errorf("error adding flag in %s: %s", st.Type(), err)
}
}
return err != nil
}
if f.Kind() == reflect.Struct {
if canMarshal(f.Addr()) {
err = fmt.Errorf("field %q has type %s, did you mean to use %s?", sf.Name, f.Type(), f.Addr().Type())
return true
}
err = p.parseStruct(f, append(path, structFieldFlagNameComponent(sf)))
return err != nil
}
err = fmt.Errorf("field has bad type: %v", f.Type())
return true
})
return
}
func newArg(v reflect.Value, sf reflect.StructField, name string) arg {
return arg{
arity: fieldArity(v, sf),
value: v,
name: name,
help: sf.Tag.Get("help"),
}
}
func (p *parser) addPos(f reflect.Value, sf reflect.StructField, path []flagNameComponent) error {
p.posArgs = append(p.posArgs, newArg(f, sf, strings.ToUpper(xstrings.ToSnakeCase(sf.Name))))
return nil
}
func flagName(comps []flagNameComponent) string {
var ss []string
slices.MakeInto(&ss, comps)
return strings.Join(ss, ".")
}
func (p *parser) addFlag(f reflect.Value, sf reflect.StructField, path []flagNameComponent) error {
name := flagName(append(path, structFieldFlagNameComponent(sf)))
if _, ok := p.flags[name]; ok {
return fmt.Errorf("flag %q defined more than once", name)
}
if p.flags == nil {
p.flags = make(map[string]arg)
}
p.flags[name] = newArg(f, sf, name)
return nil
}
func isFlag(arg string) bool {
return len(arg) > 1 && arg[0] == '-'
}
func (p *parser) parseFlag(s string) error {
i := strings.IndexByte(s, '=')
k := s
v := ""
if i != -1 {
k = s[:i]
v = s[i+1:]
}
flag, ok := p.flags[k]
if !ok {
if (k == "help" || k == "h") && !p.noDefaultHelp {
return ErrDefaultHelp
}
return userError{fmt.Sprintf("unknown flag: %q", k)}
}
err := flag.marshal(v, i != -1)
if err != nil {
return fmt.Errorf("error setting flag %q: %s", k, err)
}
return nil
}
func (p *parser) indexPosArg(i int) *arg {
for _, arg := range p.posArgs {
if i < arg.arity.max {
return &arg
}
i -= arg.arity.max
}
return nil
}
func (p *parser) nextPosArg() *arg {
return p.indexPosArg(p.numPos)
}
func (p *parser) parsePos(s string) (err error) {
arg := p.nextPosArg()
if arg == nil {
return userError{fmt.Sprintf("excess argument: %q", s)}
}
err = arg.marshal(s, true)
if err != nil {
return
}
p.numPos++
return
}
type flagNameComponent string
func structFieldFlagNameComponent(sf reflect.StructField) flagNameComponent {
name := sf.Tag.Get("name")
if name != "" {
return flagNameComponent(name)
}
return fieldFlagName(sf.Name)
}
// Gets the reflect.Value for the nth positional argument.
func posIndexValue(v reflect.Value, _i int) (ret reflect.Value, i int) {
i = _i
log.Println("posIndexValue", v.Type(), i)
switch v.Kind() {
case reflect.Ptr:
return posIndexValue(v.Elem(), i)
case reflect.Struct:
posStarted := false
foreachStructField(v, func(fv reflect.Value, sf reflect.StructField) bool {
log.Println("posIndexValue struct field", fv, sf)
if !posStarted {
if fv.Type() == reflect.TypeOf(StartPos{}) {
// log.Println("posStarted")
posStarted = true
}
return true
}
ret, i = posIndexValue(fv, i)
if ret.IsValid() {
return false
}
return true
})
return
case reflect.Slice:
ret = v
return
default:
if i == 0 {
ret = v
return
}
i--
return
}
}
func (p *parser) posWithHelp() (ret []arg) {
for _, a := range p.posArgs {
if a.help != "" {
ret = append(ret, a)
}
}
return
}
package tagflag
import (
"errors"
"fmt"
"os"
"path/filepath"
"reflect"
)
// Struct fields after this one are considered positional arguments.
type StartPos struct{}
// Default help flag was provided, and should be handled.
var ErrDefaultHelp = errors.New("help flag")
// Parses given arguments, returning any error.
func ParseErr(cmd interface{}, args []string, opts ...parseOpt) (err error) {
p, err := newParser(cmd, opts...)
if err != nil {
return
}
return p.parse(args)
}
// Parses the command-line arguments, exiting the process appropriately on
// errors or if usage is printed.
func Parse(cmd interface{}, opts ...parseOpt) {
opts = append([]parseOpt{Program(filepath.Base(os.Args[0]))}, opts...)
ParseArgs(cmd, os.Args[1:], opts...)
}
func ParseArgs(cmd interface{}, args []string, opts ...parseOpt) {
p, err := newParser(cmd, opts...)
if err == nil {