pax_global_header00006660000000000000000000000064132251402600014505gustar00rootroot0000000000000052 comment=2146c8d41bf09f7aaef804137f2ae963088b6209 tagflag-2146c8d41bf09f7aaef804137f2ae963088b6209/000077500000000000000000000000001322514026000177615ustar00rootroot00000000000000tagflag-2146c8d41bf09f7aaef804137f2ae963088b6209/LICENSE000066400000000000000000000020541322514026000207670ustar00rootroot00000000000000MIT 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-2146c8d41bf09f7aaef804137f2ae963088b6209/README.md000066400000000000000000000002521322514026000212370ustar00rootroot00000000000000# tagflag [![GoDoc](https://godoc.org/github.com/anacrolix/tagflag?status.svg)](https://godoc.org/github.com/anacrolix/tagflag) See the thorough package documentation. tagflag-2146c8d41bf09f7aaef804137f2ae963088b6209/arg.go000066400000000000000000000010561322514026000210630ustar00rootroot00000000000000package 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) } tagflag-2146c8d41bf09f7aaef804137f2ae963088b6209/arg_test.go000066400000000000000000000004761322514026000221270ustar00rootroot00000000000000package 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()) } tagflag-2146c8d41bf09f7aaef804137f2ae963088b6209/arity.go000066400000000000000000000010431322514026000214360ustar00rootroot00000000000000package 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 } tagflag-2146c8d41bf09f7aaef804137f2ae963088b6209/builtin.go000066400000000000000000000033731322514026000217640ustar00rootroot00000000000000package 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) } tagflag-2146c8d41bf09f7aaef804137f2ae963088b6209/bytes.go000066400000000000000000000011201322514026000214300ustar00rootroot00000000000000package 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)) } tagflag-2146c8d41bf09f7aaef804137f2ae963088b6209/doc.go000066400000000000000000000032321322514026000210550ustar00rootroot00000000000000// 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 tagflag-2146c8d41bf09f7aaef804137f2ae963088b6209/errors.go000066400000000000000000000001561322514026000216260ustar00rootroot00000000000000package tagflag type userError struct { msg string } func (ue userError) Error() string { return ue.msg } tagflag-2146c8d41bf09f7aaef804137f2ae963088b6209/excess.go000066400000000000000000000002061322514026000216000ustar00rootroot00000000000000package tagflag import "fmt" var ErrFieldsAfterExcessArgs = fmt.Errorf("field(s) after %T", ExcessArgs{}) type ExcessArgs []string tagflag-2146c8d41bf09f7aaef804137f2ae963088b6209/marshalers.go000066400000000000000000000030031322514026000224450ustar00rootroot00000000000000package 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 } tagflag-2146c8d41bf09f7aaef804137f2ae963088b6209/misc.go000066400000000000000000000041531322514026000212460ustar00rootroot00000000000000package 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) }()) } tagflag-2146c8d41bf09f7aaef804137f2ae963088b6209/parseopt.go000066400000000000000000000007121322514026000221450ustar00rootroot00000000000000package 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 } } tagflag-2146c8d41bf09f7aaef804137f2ae963088b6209/parser.go000066400000000000000000000134761322514026000216170ustar00rootroot00000000000000package 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 } tagflag-2146c8d41bf09f7aaef804137f2ae963088b6209/tagflag.go000066400000000000000000000024611322514026000217200ustar00rootroot00000000000000package 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 { err = p.parse(args) } if err == ErrDefaultHelp { p.printUsage(os.Stderr) os.Exit(0) } if err != nil { fmt.Fprintf(os.Stderr, "tagflag: %s\n", err) if _, ok := err.(userError); ok { os.Exit(2) } os.Exit(1) } } func Unmarshal(arg string, v interface{}) error { _v := reflect.ValueOf(v).Elem() m := valueMarshaler(_v) if m == nil { return fmt.Errorf("can't unmarshal to type %s", _v.Type()) } return m.Marshal(_v, arg) } tagflag-2146c8d41bf09f7aaef804137f2ae963088b6209/tagflag_test.go000066400000000000000000000164221322514026000227610ustar00rootroot00000000000000package tagflag import ( "log" "net" "os" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestBasic(t *testing.T) { type simpleCmd struct { Verbose bool `name:"v"` StartPos Arg string } for _, _case := range []struct { expected simpleCmd err error args []string }{ { simpleCmd{Verbose: true, Arg: "test"}, nil, []string{"-v", "test"}, }, { simpleCmd{Verbose: false, Arg: "hello"}, nil, []string{"hello"}, }, { simpleCmd{}, userError{`excess argument: "world"`}, []string{"hello", "world"}, }, { simpleCmd{Arg: "hello, world"}, nil, []string{"hello, world"}, }, { simpleCmd{}, userError{`excess argument: "answer = 42"`}, []string{"hello, world", "answer = 42"}, }, { simpleCmd{}, userError{`missing argument: "ARG"`}, []string{"-v"}, }, { simpleCmd{}, userError{`unknown flag: "no"`}, []string{"-no"}, }, } { var actual simpleCmd err := ParseErr(&actual, _case.args) assert.EqualValues(t, _case.err, err) if _case.err != nil || _case.err != err { // The value we got doesn't matter. continue } assert.EqualValues(t, _case.expected, actual) } } func TestNotBasic(t *testing.T) { type cmd struct { Seed bool NoUpload bool ListenAddr string DataDir string `name:"d"` StartPos Torrent []string `arity:"+"` } for _, _case := range []struct { args []string err error expected cmd }{ {nil, userError{`missing argument: "TORRENT"`}, cmd{}}, { []string{"-seed"}, userError{`missing argument: "TORRENT"`}, cmd{}, }, { []string{"-seed", "a.torrent", "b.torrent"}, nil, cmd{ Torrent: []string{"a.torrent", "b.torrent"}, Seed: true, }, }, { []string{"-listenAddr=1.2.3.4:80", "a.torrent", "b.torrent"}, nil, cmd{ ListenAddr: "1.2.3.4:80", Torrent: []string{"a.torrent", "b.torrent"}, }, }, { []string{"-d=/tmp", "a.torrent", "b.torrent", "-listenAddr=1.2.3.4:80"}, nil, cmd{ DataDir: "/tmp", ListenAddr: "1.2.3.4:80", Torrent: []string{"a.torrent", "b.torrent"}, }, }, { []string{"-noUpload=true", "-noUpload=false", "a.torrent"}, nil, cmd{ NoUpload: false, Torrent: []string{"a.torrent"}, }, }, } { var actual cmd err := ParseErr(&actual, _case.args) assert.EqualValues(t, _case.err, err) if _case.err != nil { continue } assert.EqualValues(t, _case.expected, actual) } } func TestBadCommand(t *testing.T) { // assert.Error(t, ParseErr(struct{}{}, nil)) assert.NoError(t, ParseErr(new(struct{}), nil)) assert.NoError(t, ParseErr(nil, nil)) } func TestVarious(t *testing.T) { a := &struct { StartPos A string `arity:"?"` }{} assert.NoError(t, ParseErr(a, nil)) assert.NoError(t, ParseErr(a, []string{"a"})) assert.EqualValues(t, "a", a.A) assert.EqualError(t, ParseErr(a, []string{"a", "b"}), `excess argument: "b"`) } func TestUint(t *testing.T) { var a struct { A uint } assert.Error(t, ParseErr(&a, []string{"-a"})) assert.Error(t, ParseErr(&a, []string{"-a", "-1"})) assert.NoError(t, ParseErr(&a, []string{"-a=42"})) } func TestBasicPositionalArities(t *testing.T) { type cmd struct { C bool StartPos A string B int64 `arity:"?"` D []string `arity:"*"` } for _, _case := range []struct { args []string err error expected cmd }{ // {nil, userError{`missing argument: "A"`}, cmd{}}, {[]string{"abc"}, nil, cmd{A: "abc"}}, {[]string{"abc", "123"}, nil, cmd{A: "abc", B: 123}}, {[]string{"abc", "123", "first"}, nil, cmd{A: "abc", B: 123, D: []string{"first"}}}, {[]string{"abc", "123", "first", "second"}, nil, cmd{A: "abc", B: 123, D: []string{"first", "second"}}}, {[]string{"abc", "123", "-c", "first", "second"}, nil, cmd{A: "abc", B: 123, C: true, D: []string{"first", "second"}}}, } { var actual cmd err := ParseErr(&actual, _case.args) assert.EqualValues(t, _case.err, err) if _case.err != nil { continue } assert.EqualValues(t, _case.expected, actual) } } func TestBytes(t *testing.T) { var cmd struct { B Bytes } err := ParseErr(&cmd, []string{"-b=100g"}) assert.NoError(t, err) assert.EqualValues(t, 100e9, cmd.B) } func TestPtrToCustom(t *testing.T) { var cmd struct { Addr *net.TCPAddr } err := ParseErr(&cmd, []string{"-addr=:443"}) assert.NoError(t, err) assert.EqualValues(t, ":443", cmd.Addr.String()) err = ParseErr(&cmd, []string{"-addr="}) assert.NoError(t, err) assert.Nil(t, cmd.Addr) } func TestResolveTCPAddr(t *testing.T) { addr, err := net.ResolveTCPAddr("tcp", "") t.Log(addr, err) } func TestMain(m *testing.M) { log.SetFlags(log.Lshortfile) os.Exit(m.Run()) } func TestDefaultLongFlagName(t *testing.T) { assert.EqualValues(t, "noUpload", fieldFlagName("NoUpload")) assert.EqualValues(t, "dht", fieldFlagName("DHT")) assert.EqualValues(t, "noIPv6", fieldFlagName("NoIPv6")) assert.EqualValues(t, "tcpAddr", fieldFlagName("TCPAddr")) assert.EqualValues(t, "addr", fieldFlagName("Addr")) assert.EqualValues(t, "v", fieldFlagName("V")) assert.EqualValues(t, "a", fieldFlagName("A")) } func TestPrintUsage(t *testing.T) { err := ParseErr(nil, []string{"-h"}) assert.Equal(t, ErrDefaultHelp, err) err = ParseErr(nil, []string{"-help"}) assert.Equal(t, ErrDefaultHelp, err) } func TestParseUnnamedTypes(t *testing.T) { var cmd1 struct { A []byte B bool } assert.NoError(t, ParseErr(&cmd1, nil)) type B []byte var cmd2 struct { A B } ParseErr(&cmd2, nil) type C bool var cmd3 struct { A C } ParseErr(&cmd3, nil) } func TestPosArgSlice(t *testing.T) { var cmd1 struct { StartPos Args []string } require.NoError(t, ParseErr(&cmd1, []string{"a", "b", "c"})) assert.EqualValues(t, []string{"a", "b", "c"}, cmd1.Args) } func TestUnmarshallableTypes(t *testing.T) { var cmd1 struct { Wtf *int } assert.Contains(t, ParseErr(&cmd1, []string{"-wtf=yo"}).Error(), "*int") } func TestTCPAddrNoExplicitValue(t *testing.T) { var cmd struct { Addr *net.TCPAddr } assert.Error(t, ParseErr(&cmd, []string{"-addr"})) assert.NoError(t, ParseErr(&cmd, []string{"-addr="})) } func TestUnexportedStructField(t *testing.T) { var cmd struct { badField bool } assert.NoError(t, ParseErr(&cmd, nil)) assert.EqualError(t, ParseErr(&cmd, []string{"-badField"}), `unknown flag: "badField"`) } func TestExcessArgsEmpty(t *testing.T) { var cmd struct { ExcessArgs } require.NoError(t, ParseErr(&cmd, nil)) assert.Len(t, cmd.ExcessArgs, 0) } func TestExcessArgs(t *testing.T) { var cmd struct { ExcessArgs } excess := []string{"yo", "-addr=hi"} require.NoError(t, ParseErr(&cmd, excess)) assert.EqualValues(t, excess, cmd.ExcessArgs) } func TestExcessArgsComplex(t *testing.T) { var cmd struct { Verbose bool `name:"v"` StartPos Command string ExcessArgs } excess := []string{"-addr=hi"} require.NoError(t, ParseErr(&cmd, append([]string{"-v", "serve"}, excess...))) assert.EqualValues(t, excess, cmd.ExcessArgs) } func TestFieldAfterExcessArgs(t *testing.T) { var cmd struct { ExcessArgs Badness string } require.EqualValues(t, ErrFieldsAfterExcessArgs, ParseErr(&cmd, nil)) } func TestSliceOfUnmarshallableStruct(t *testing.T) { var cmd struct { StartPos Complex []struct{} } require.EqualError(t, ParseErr(&cmd, []string{"herp"}), "can't marshal type struct {}") } tagflag-2146c8d41bf09f7aaef804137f2ae963088b6209/usage.go000066400000000000000000000035341322514026000214210ustar00rootroot00000000000000package tagflag import ( "fmt" "io" "text/tabwriter" "github.com/anacrolix/missinggo" "github.com/anacrolix/missinggo/slices" ) func (p *parser) printUsage(w io.Writer) { fmt.Fprintf(w, "Usage:\n %s", p.program) if p.hasOptions() { fmt.Fprintf(w, " [OPTIONS...]") } for _, arg := range p.posArgs { fs := func() string { switch arg.arity { case arity{0, 1}: return "[%s]" case arity{1, infArity}: return "%s..." case arity{0, infArity}: return "[%s...]" default: return "<%s>" } }() // if arg.arity != arity{1,1} { fmt.Fprintf(w, " "+fs, arg.name) // } // if arg.arity > 1 { // for range iter.N(int(arg.arity - 1)) { // fmt.Fprintf(w, " "+fs, arg.name) // } // } } fmt.Fprintf(w, "\n") if p.description != "" { fmt.Fprintf(w, "\n%s\n", missinggo.Unchomp(p.description)) } if awd := p.posWithHelp(); len(awd) != 0 { fmt.Fprintf(w, "Arguments:\n") tw := newUsageTabwriter(w) for _, a := range awd { fmt.Fprintf(tw, " %s\t(%s)\t%s\n", a.name, a.value.Type(), a.help) } tw.Flush() } var opts []arg for _, v := range p.flags { opts = append(opts, v) } slices.Sort(opts, func(left, right arg) bool { return left.name < right.name }) writeOptionUsage(w, opts) } func newUsageTabwriter(w io.Writer) *tabwriter.Writer { return tabwriter.NewWriter(w, 8, 2, 3, ' ', 0) } func writeOptionUsage(w io.Writer, flags []arg) { if len(flags) == 0 { return } fmt.Fprintf(w, "Options:\n") tw := newUsageTabwriter(w) for _, f := range flags { fmt.Fprint(tw, " ") fmt.Fprintf(tw, "%s%s", flagPrefix, f.name) help := f.help if !f.hasZeroValue() { _default := fmt.Sprintf("Default: %v", f.value) if help == "" { help = _default } else { help = fmt.Sprintf("%s (%s)", help, _default) } } fmt.Fprintf(tw, "\t(%s)\t%s\n", f.value.Type(), help) } tw.Flush() }