New Upstream Release - golang-github-anacrolix-tagflag

Ready changes

Summary

Merged new upstream version: 0.0.0-20230507-45431b4+ds (was: 0.0.0-20220926-a6f8d8a+ds).

Resulting package

Built on 2023-06-03T00:04 (took 6m42s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases golang-github-anacrolix-tagflag-dev

Lintian Result

Diff

diff --git a/README.md b/README.md
index bf7ee54..f53ebef 100644
--- a/README.md
+++ b/README.md
@@ -2,4 +2,9 @@
 
 [![GoDoc](https://godoc.org/github.com/anacrolix/tagflag?status.svg)](https://godoc.org/github.com/anacrolix/tagflag)
 
-See the thorough package documentation.
+See the thorough package documentation on [GoDoc](https://godoc.org/github.com/anacrolix/tagflag). I know you want to see this in the README, but just go to the GoDoc.
+
+## Similar projects
+
+ * https://github.com/jaffee/commandeer 
+ * https://github.com/octago/sflags
diff --git a/arg.go b/arg.go
index b6e417b..0dc36df 100644
--- a/arg.go
+++ b/arg.go
@@ -19,9 +19,9 @@ func (me arg) hasZeroValue() bool {
 }
 
 func (me arg) marshal(s string, explicitValue bool) error {
-	m := valueMarshaler(me.value)
-	if !explicitValue && m.RequiresExplicitValue() {
+	m := valueMarshaler(me.value.Type())
+	if m.RequiresExplicitValue() && !explicitValue {
 		return userError{fmt.Sprintf("explicit value required (%s%s=VALUE)", flagPrefix, me.name)}
 	}
-	return valueMarshaler(me.value).Marshal(me.value, s)
+	return m.Marshal(me.value, s)
 }
diff --git a/builtin.go b/builtin.go
index f05f9dc..46d21f2 100644
--- a/builtin.go
+++ b/builtin.go
@@ -1,10 +1,16 @@
 package tagflag
 
 import (
+	"errors"
+	"fmt"
 	"net"
 	"net/url"
 	"reflect"
+	"strconv"
+	"strings"
 	"time"
+
+	"golang.org/x/xerrors"
 )
 
 var builtinMarshalers = map[reflect.Type]marshaler{}
@@ -45,12 +51,61 @@ func init() {
 		if s == "" {
 			return nil, nil
 		}
-		return net.ResolveTCPAddr("tcp", s)
+		var ret net.TCPAddr
+		retAlt, err := parseIpPortZone(s)
+		if err != nil {
+			return nil, err
+		}
+		ret = net.TCPAddr(retAlt)
+		return &ret, err
 	}, true)
 	addBuiltinDynamicMarshaler(func(s string) (time.Duration, error) {
 		return time.ParseDuration(s)
 	}, false)
-	addBuiltinDynamicMarshaler(func(s string) net.IP {
-		return net.ParseIP(s)
+	addBuiltinDynamicMarshaler(func(s string) (ip net.IP, err error) {
+		ip = net.ParseIP(s)
+		if ip == nil {
+			err = fmt.Errorf("failed to parse IP")
+		}
+		return
 	}, false)
 }
+
+func parseIpAddr(host string) (ret net.IPAddr, err error) {
+	ss := strings.SplitN(host, "%", 2)
+	ret.IP = net.ParseIP(ss[0])
+	if ret.IP == nil && ss[0] != "" {
+		err = errors.New("error parsing IP")
+		return
+	}
+	if len(ss) >= 2 {
+		ret.Zone = ss[1]
+	}
+	return
+}
+
+type ipPortZone struct {
+	IP   net.IP
+	Port int
+	Zone string
+}
+
+func parseIpPortZone(hostport string) (ret ipPortZone, err error) {
+	host, port, err := net.SplitHostPort(hostport)
+	if err != nil {
+		return
+	}
+	portInt64, err := strconv.ParseInt(port, 10, 0)
+	if err != nil {
+		return
+	}
+	ret.Port = int(portInt64)
+	ipAddr, err := parseIpAddr(host)
+	if err != nil {
+		err = xerrors.Errorf("parsing host %q: %w", host, err)
+		return
+	}
+	ret.IP = ipAddr.IP
+	ret.Zone = ipAddr.Zone
+	return
+}
diff --git a/bytes.go b/bytes.go
index c34f5d2..f8ba1d2 100644
--- a/bytes.go
+++ b/bytes.go
@@ -1,12 +1,19 @@
 package tagflag
 
-import "github.com/dustin/go-humanize"
+import (
+	"encoding"
+
+	"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)
+var (
+	_ Marshaler                = (*Bytes)(nil)
+	_ encoding.TextUnmarshaler = (*Bytes)(nil)
+)
 
 func (me *Bytes) Marshal(s string) (err error) {
 	ui64, err := humanize.ParseBytes(s)
@@ -17,7 +24,11 @@ func (me *Bytes) Marshal(s string) (err error) {
 	return
 }
 
-func (Bytes) RequiresExplicitValue() bool {
+func (me *Bytes) UnmarshalText(text []byte) error {
+	return me.Marshal(string(text))
+}
+
+func (*Bytes) RequiresExplicitValue() bool {
 	return false
 }
 
diff --git a/cases_test.go b/cases_test.go
new file mode 100644
index 0000000..fd55b01
--- /dev/null
+++ b/cases_test.go
@@ -0,0 +1,53 @@
+package tagflag
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+type parseCase struct {
+	args     []string
+	err      func(*testing.T, error)
+	expected interface{}
+}
+
+func noErrorCase(expected interface{}, args ...string) parseCase {
+	return parseCase{args: args, expected: expected}
+}
+
+func errorCase(err error, args ...string) parseCase {
+	return parseCase{
+		args: args,
+		err: func(t *testing.T, actualErr error) {
+			assert.EqualValues(t, err, actualErr)
+		},
+	}
+}
+
+func anyErrorCase(args ...string) parseCase {
+	return parseCase{
+		args: args,
+		err: func(t *testing.T, err error) {
+			assert.Error(t, err)
+		},
+	}
+}
+
+func (me parseCase) Run(t *testing.T, newCmd func() interface{}) {
+	cmd := newCmd()
+	err := ParseErr(cmd, me.args)
+	if me.err == nil {
+		assert.NoError(t, err)
+		assert.EqualValues(t, me.expected, reflect.ValueOf(cmd).Elem().Interface(), "%v", me)
+	} else {
+		me.err(t, err)
+	}
+}
+
+func RunCases(t *testing.T, cases []parseCase, newCmd func() interface{}) {
+	for _, _case := range cases {
+		_case.Run(t, newCmd)
+	}
+}
diff --git a/debian/changelog b/debian/changelog
index 25776a1..c7de528 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+golang-github-anacrolix-tagflag (0.0.0-20230507-45431b4+ds-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Fri, 02 Jun 2023 23:58:38 -0000
+
 golang-github-anacrolix-tagflag (0.0.0-20180109-2146c8d-4) unstable; urgency=medium
 
   [ Debian Janitor ]
diff --git a/excess.go b/excess.go
index 25467ea..0e60770 100644
--- a/excess.go
+++ b/excess.go
@@ -2,6 +2,8 @@ package tagflag
 
 import "fmt"
 
+// The error returned if there are fields in a struct after ExcessArgs.
 var ErrFieldsAfterExcessArgs = fmt.Errorf("field(s) after %T", ExcessArgs{})
 
+// This should be added to the end of a struct to soak up any arguments that didn't fit sooner.
 type ExcessArgs []string
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..261b498
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,16 @@
+module github.com/anacrolix/tagflag
+
+go 1.12
+
+require (
+	github.com/anacrolix/missinggo v1.3.0 // indirect
+	github.com/anacrolix/missinggo/v2 v2.6.0
+	github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8
+	github.com/dustin/go-humanize v1.0.0
+	github.com/huandu/xstrings v1.3.2
+	github.com/kr/pretty v0.3.0 // indirect
+	github.com/pkg/errors v0.9.1
+	github.com/rogpeppe/go-internal v1.8.0 // indirect
+	github.com/stretchr/testify v1.7.0
+	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..8410312
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,259 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk=
+crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w=
+github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI=
+github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
+github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
+github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
+github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
+github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4=
+github.com/anacrolix/log v0.3.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
+github.com/anacrolix/log v0.6.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
+github.com/anacrolix/missinggo v1.1.0/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo=
+github.com/anacrolix/missinggo v1.1.2-0.20190815015349-b888af804467/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo=
+github.com/anacrolix/missinggo v1.2.1/go.mod h1:J5cMhif8jPmFoC3+Uvob3OXXNIhOUikzMt+uUjeM21Y=
+github.com/anacrolix/missinggo v1.3.0 h1:06HlMsudotL7BAELRZs0yDZ4yVXsHXGi323QBjAVASw=
+github.com/anacrolix/missinggo v1.3.0/go.mod h1:bqHm8cE8xr+15uVfMG3BFui/TxyB6//H5fwlq/TeqMc=
+github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5urunoLS0Cbvb4V0uMQ=
+github.com/anacrolix/missinggo/v2 v2.2.0/go.mod h1:o0jgJoYOyaoYQ4E2ZMISVa9c88BbUBVQQW4QeRkNCGY=
+github.com/anacrolix/missinggo/v2 v2.5.1/go.mod h1:WEjqh2rmKECd0t1VhQkLGTdIWXO6f6NLjp5GlMZ+6FA=
+github.com/anacrolix/missinggo/v2 v2.6.0 h1:kHkn6nLy1isWYV4mthZX8itV1bRd2mwFVuXrxzJ4VX0=
+github.com/anacrolix/missinggo/v2 v2.6.0/go.mod h1:2IZIvmRTizALNYFYXsPR7ofXPzJgyBpKZ4kMqMEICkI=
+github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg=
+github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
+github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
+github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
+github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
+github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
+github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=
+github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
+github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
+github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
+github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
+github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
+github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
+github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
+github.com/glycerine/goconvey v0.0.0-20190315024820-982ee783a72e/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
+github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
+github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
+github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
+github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
+github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
+github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
+github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
+github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
+github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
+github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
+github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
+github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
+github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
+github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
+github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
+github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
+go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/marshalers.go b/marshalers.go
index 0e8b05d..b932b84 100644
--- a/marshalers.go
+++ b/marshalers.go
@@ -4,10 +4,14 @@ import (
 	"fmt"
 	"reflect"
 	"strconv"
+
+	"github.com/pkg/errors"
 )
 
+// TODO: Perhaps this should embed encoding.TextUnmarshaler instead.
 type Marshaler interface {
 	Marshal(in string) error
+	// Must have and ignore a pointer receiver.
 	RequiresExplicitValue() bool
 }
 
@@ -34,9 +38,10 @@ func (me dynamicMarshaler) RequiresExplicitValue() bool {
 type defaultMarshaler struct{}
 
 func (defaultMarshaler) Marshal(v reflect.Value, s string) error {
-	if v.Kind() == reflect.Slice {
+	switch v.Kind() {
+	case reflect.Slice:
 		n := reflect.New(v.Type().Elem())
-		m := valueMarshaler(n.Elem())
+		m := valueMarshaler(n.Elem().Type())
 		if m == nil {
 			return fmt.Errorf("can't marshal type %s", n.Elem().Type())
 		}
@@ -46,8 +51,6 @@ func (defaultMarshaler) Marshal(v reflect.Value, s string) error {
 		}
 		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)
@@ -63,6 +66,26 @@ func (defaultMarshaler) Marshal(v reflect.Value, s string) error {
 	case reflect.String:
 		v.SetString(s)
 		return nil
+	case reflect.Array:
+		if v.Type().Elem().Kind() == reflect.Uint8 {
+			if len(s) == 2*v.Len() {
+				var sl []byte = v.Slice(0, v.Len()).Interface().([]byte)
+				// log.Println(cap(sl), len(sl))
+				// hex.DecodeString(s)
+				_, err := fmt.Sscanf(s, "%x", &sl)
+				// log.Println(cap(sl), sl, err)
+				if err != nil {
+					return errors.Wrapf(err, "scanning hex")
+				}
+				// Seems the slice moves as part of the decoding :|
+				reflect.Copy(v, reflect.ValueOf(sl))
+			} else {
+				return errors.Errorf("argument has unhandled length %d", len(s))
+			}
+			return nil
+		} else {
+			return errors.Errorf("unhandled array elem type: %s", v.Type().String())
+		}
 	default:
 		return fmt.Errorf("unhandled builtin type: %s", v.Type().String())
 	}
@@ -71,3 +94,17 @@ func (defaultMarshaler) Marshal(v reflect.Value, s string) error {
 func (defaultMarshaler) RequiresExplicitValue() bool {
 	return true
 }
+
+type ptrMarshaler struct {
+	inner marshaler
+}
+
+func (me ptrMarshaler) Marshal(v reflect.Value, s string) error {
+	elemValue := reflect.New(v.Type().Elem())
+	v.Set(elemValue)
+	return me.inner.Marshal(elemValue.Elem(), s)
+}
+
+func (me ptrMarshaler) RequiresExplicitValue() bool {
+	return false
+}
diff --git a/misc.go b/misc.go
index bf3b4c9..326fb37 100644
--- a/misc.go
+++ b/misc.go
@@ -2,9 +2,8 @@ package tagflag
 
 import (
 	"reflect"
-	"regexp"
 	"strconv"
-	"strings"
+	"unicode"
 
 	"github.com/bradfitz/iter"
 )
@@ -26,24 +25,30 @@ func foreachStructField(_struct reflect.Value, f func(fv reflect.Value, sf refle
 }
 
 func canMarshal(f reflect.Value) bool {
-	return valueMarshaler(f) != nil
+	return valueMarshaler(f.Type()) != 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(),
-			}
+func valueMarshaler(t reflect.Type) marshaler {
+	if zm, ok := reflect.Zero(reflect.PtrTo(t)).Interface().(Marshaler); ok {
+		return dynamicMarshaler{
+			marshal: func(v reflect.Value, s string) error {
+				return v.Addr().Interface().(Marshaler).Marshal(s)
+			},
+			explicitValueRequired: zm.RequiresExplicitValue(),
 		}
 	}
-	if bm, ok := builtinMarshalers[v.Type()]; ok {
+	if bm, ok := builtinMarshalers[t]; ok {
 		return bm
 	}
-	switch v.Kind() {
-	case reflect.Ptr, reflect.Struct:
+	switch t.Kind() {
+	case reflect.Ptr:
+		m := valueMarshaler(t.Elem())
+		if m == nil {
+			return nil
+		}
+		return ptrMarshaler{m}
+	case reflect.Struct:
 		return nil
 	case reflect.Bool:
 		return dynamicMarshaler{
@@ -65,19 +70,16 @@ func valueMarshaler(v reflect.Value) marshaler {
 // 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]
+	return flagNameComponent(func() (ret []rune) {
+		fieldNameRunes := []rune(fieldName)
+		for i, r := range fieldNameRunes {
+			prevUpper := func() bool { return unicode.IsUpper(fieldNameRunes[i-1]) }
+			nextUpper := func() bool { return unicode.IsUpper(fieldNameRunes[i+1]) }
+			if i == 0 || (prevUpper() && (i == len(fieldNameRunes)-1 || nextUpper())) {
+				r = unicode.ToLower(r)
+			}
+			ret = append(ret, r)
 		}
-		panic(fieldName)
+		return
 	}())
 }
diff --git a/parseopt.go b/parseopt.go
index fb3124d..f3c8da2 100644
--- a/parseopt.go
+++ b/parseopt.go
@@ -1,23 +1,35 @@
 package tagflag
 
-type parseOpt func(p *parser)
+type parseOpt func(p *Parser)
 
 // Don't perform default behaviour if -h or -help are passed.
 func NoDefaultHelp() parseOpt {
-	return func(p *parser) {
+	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) {
+	return func(p *Parser) {
 		p.description = desc
 	}
 }
 
 func Program(name string) parseOpt {
-	return func(p *parser) {
+	return func(p *Parser) {
 		p.program = name
 	}
 }
+
+func ParseIntermixed(enabled bool) parseOpt {
+	return func(p *Parser) {
+		p.parseIntermixed = enabled
+	}
+}
+
+func Parent(parent *Parser) parseOpt {
+	return func(p *Parser) {
+		p.parent = parent
+	}
+}
diff --git a/parser.go b/parser.go
index 675a0e9..27a8586 100644
--- a/parser.go
+++ b/parser.go
@@ -2,21 +2,26 @@ package tagflag
 
 import (
 	"fmt"
-	"log"
 	"reflect"
 	"strings"
 
-	"github.com/anacrolix/missinggo/slices"
+	"github.com/anacrolix/missinggo/v2/slices"
 	"github.com/huandu/xstrings"
+	"golang.org/x/xerrors"
 )
 
-type parser struct {
-	// The value from which the parser is built, and values are assigned.
+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
+	// Whether the first non-option argument requires that all further arguments are to be treated
+	// as positional.
+	parseIntermixed bool
+	// The Parser that preceded this one, such as in sub-command relationship.
+	parent *Parser
 
 	posArgs []arg
 	// Maps -K=V to map[K]arg(V)
@@ -28,11 +33,11 @@ type parser struct {
 	numPos int
 }
 
-func (p *parser) hasOptions() bool {
+func (p *Parser) hasOptions() bool {
 	return len(p.flags) != 0
 }
 
-func (p *parser) parse(args []string) (err error) {
+func (p *Parser) parse(args []string) (err error) {
 	posOnly := false
 	for len(args) != 0 {
 		if p.excess != nil && p.nextPosArg() == nil {
@@ -47,8 +52,14 @@ func (p *parser) parse(args []string) (err error) {
 		}
 		if !posOnly && isFlag(a) {
 			err = p.parseFlag(a[1:])
+			if err != nil {
+				err = xerrors.Errorf("parsing flag %q: %w", a[1:], err)
+			}
 		} else {
 			err = p.parsePos(a)
+			if !p.parseIntermixed {
+				posOnly = true
+			}
 		}
 		if err != nil {
 			return
@@ -60,16 +71,17 @@ func (p *parser) parse(args []string) (err error) {
 	return
 }
 
-func (p *parser) minPos() (min int) {
+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,
+func newParser(cmd interface{}, opts ...parseOpt) (p *Parser, err error) {
+	p = &Parser{
+		cmd:             cmd,
+		parseIntermixed: true,
 	}
 	for _, opt := range opts {
 		opt(p)
@@ -78,19 +90,22 @@ func newParser(cmd interface{}, opts ...parseOpt) (p *parser, err error) {
 	return
 }
 
-func (p *parser) parseCmd() error {
+func (p *Parser) parseCmd() error {
 	if p.cmd == nil {
 		return nil
 	}
 	s := reflect.ValueOf(p.cmd).Elem()
+	for s.Kind() == reflect.Interface {
+		s = s.Elem()
+	}
 	if s.Kind() != reflect.Struct {
 		return fmt.Errorf("expected struct got %s", s.Type())
 	}
-	return p.parseStruct(reflect.ValueOf(p.cmd).Elem(), nil)
+	return p.parseStruct(s, nil)
 }
 
 // Positional arguments are marked per struct.
-func (p *parser) parseStruct(st reflect.Value, path []flagNameComponent) (err error) {
+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{}) {
@@ -119,13 +134,15 @@ func (p *parser) parseStruct(st reflect.Value, path []flagNameComponent) (err er
 			}
 			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
+		var parsed bool
+		parsed, err = p.parseEmbeddedStruct(f, sf, path)
+		if err != nil {
+			err = fmt.Errorf("parsing embedded struct: %w", err)
+			stop = true
+			return
+		}
+		if parsed {
+			return false
 		}
 		err = fmt.Errorf("field has bad type: %v", f.Type())
 		return true
@@ -133,6 +150,25 @@ func (p *parser) parseStruct(st reflect.Value, path []flagNameComponent) (err er
 	return
 }
 
+func (p *Parser) parseEmbeddedStruct(f reflect.Value, sf reflect.StructField, path []flagNameComponent) (parsed bool, err error) {
+	if f.Kind() == reflect.Ptr {
+		f = f.Elem()
+	}
+	if f.Kind() != reflect.Struct {
+		return
+	}
+	if canMarshal(f.Addr()) {
+		err = fmt.Errorf("field %q has type %s, but %s is marshalable", sf.Name, f.Type(), f.Addr().Type())
+		return
+	}
+	parsed = true
+	if !sf.Anonymous {
+		path = append(path, structFieldFlagNameComponent(sf))
+	}
+	err = p.parseStruct(f, path)
+	return
+}
+
 func newArg(v reflect.Value, sf reflect.StructField, name string) arg {
 	return arg{
 		arity: fieldArity(v, sf),
@@ -142,7 +178,7 @@ func newArg(v reflect.Value, sf reflect.StructField, name string) arg {
 	}
 }
 
-func (p *parser) addPos(f reflect.Value, sf reflect.StructField, path []flagNameComponent) error {
+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
 }
@@ -153,7 +189,7 @@ func flagName(comps []flagNameComponent) string {
 	return strings.Join(ss, ".")
 }
 
-func (p *parser) addFlag(f reflect.Value, sf reflect.StructField, path []flagNameComponent) error {
+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)
@@ -169,7 +205,7 @@ func isFlag(arg string) bool {
 	return len(arg) > 1 && arg[0] == '-'
 }
 
-func (p *parser) parseFlag(s string) error {
+func (p *Parser) parseFlag(s string) error {
 	i := strings.IndexByte(s, '=')
 	k := s
 	v := ""
@@ -186,12 +222,12 @@ func (p *parser) parseFlag(s string) error {
 	}
 	err := flag.marshal(v, i != -1)
 	if err != nil {
-		return fmt.Errorf("error setting flag %q: %s", k, err)
+		return xerrors.Errorf("parsing value %q for flag %q: %w", v, k, err)
 	}
 	return nil
 }
 
-func (p *parser) indexPosArg(i int) *arg {
+func (p *Parser) indexPosArg(i int) *arg {
 	for _, arg := range p.posArgs {
 		if i < arg.arity.max {
 			return &arg
@@ -201,11 +237,11 @@ func (p *parser) indexPosArg(i int) *arg {
 	return nil
 }
 
-func (p *parser) nextPosArg() *arg {
+func (p *Parser) nextPosArg() *arg {
 	return p.indexPosArg(p.numPos)
 }
 
-func (p *parser) parsePos(s string) (err error) {
+func (p *Parser) parsePos(s string) (err error) {
 	arg := p.nextPosArg()
 	if arg == nil {
 		return userError{fmt.Sprintf("excess argument: %q", s)}
@@ -228,45 +264,7 @@ func structFieldFlagNameComponent(sf reflect.StructField) flagNameComponent {
 	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) {
+func (p *Parser) posWithHelp() (ret []arg) {
 	for _, a := range p.posArgs {
 		if a.help != "" {
 			ret = append(ret, a)
diff --git a/tagflag.go b/tagflag.go
index 069ead8..aa25d1e 100644
--- a/tagflag.go
+++ b/tagflag.go
@@ -6,6 +6,8 @@ import (
 	"os"
 	"path/filepath"
 	"reflect"
+
+	"golang.org/x/xerrors"
 )
 
 // Struct fields after this one are considered positional arguments.
@@ -25,32 +27,34 @@ func ParseErr(cmd interface{}, args []string, opts ...parseOpt) (err error) {
 
 // Parses the command-line arguments, exiting the process appropriately on
 // errors or if usage is printed.
-func Parse(cmd interface{}, opts ...parseOpt) {
+func Parse(cmd interface{}, opts ...parseOpt) *Parser {
 	opts = append([]parseOpt{Program(filepath.Base(os.Args[0]))}, opts...)
-	ParseArgs(cmd, os.Args[1:], opts...)
+	return ParseArgs(cmd, os.Args[1:], opts...)
 }
 
-func ParseArgs(cmd interface{}, args []string, opts ...parseOpt) {
+// Like Parse, but operates on the given args instead.
+func ParseArgs(cmd interface{}, args []string, opts ...parseOpt) *Parser {
 	p, err := newParser(cmd, opts...)
 	if err == nil {
 		err = p.parse(args)
 	}
-	if err == ErrDefaultHelp {
-		p.printUsage(os.Stderr)
+	if xerrors.Is(err, ErrDefaultHelp) {
+		p.printUsage(os.Stdout)
 		os.Exit(0)
 	}
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "tagflag: %s\n", err)
+		fmt.Fprintf(os.Stderr, "tagflag: error parsing args: %v\n", err)
 		if _, ok := err.(userError); ok {
 			os.Exit(2)
 		}
 		os.Exit(1)
 	}
+	return p
 }
 
 func Unmarshal(arg string, v interface{}) error {
 	_v := reflect.ValueOf(v).Elem()
-	m := valueMarshaler(_v)
+	m := valueMarshaler(_v.Type())
 	if m == nil {
 		return fmt.Errorf("can't unmarshal to type %s", _v.Type())
 	}
diff --git a/tagflag_test.go b/tagflag_test.go
index e49a8ca..708c779 100644
--- a/tagflag_test.go
+++ b/tagflag_test.go
@@ -4,10 +4,12 @@ import (
 	"log"
 	"net"
 	"os"
+	"reflect"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	"golang.org/x/xerrors"
 )
 
 func TestBasic(t *testing.T) {
@@ -59,7 +61,7 @@ func TestBasic(t *testing.T) {
 	} {
 		var actual simpleCmd
 		err := ParseErr(&actual, _case.args)
-		assert.EqualValues(t, _case.err, err)
+		assert.True(t, xerrors.Is(err, _case.err))
 		if _case.err != nil || _case.err != err {
 			// The value we got doesn't matter.
 			continue
@@ -69,6 +71,7 @@ func TestBasic(t *testing.T) {
 }
 
 func TestNotBasic(t *testing.T) {
+	t.Skip("outdated use of parseCase")
 	type cmd struct {
 		Seed       bool
 		NoUpload   bool
@@ -77,17 +80,8 @@ func TestNotBasic(t *testing.T) {
 		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{},
-		},
+	for _, _case := range []parseCase{
+		errorCase(userError{`missing argument: "TORRENT"`}, "-seed"),
 		{
 			[]string{"-seed", "a.torrent", "b.torrent"},
 			nil,
@@ -159,6 +153,7 @@ func TestUint(t *testing.T) {
 }
 
 func TestBasicPositionalArities(t *testing.T) {
+	t.Skip("outdated use of parseCase")
 	type cmd struct {
 		C bool
 		StartPos
@@ -166,11 +161,7 @@ func TestBasicPositionalArities(t *testing.T) {
 		B int64    `arity:"?"`
 		D []string `arity:"*"`
 	}
-	for _, _case := range []struct {
-		args     []string
-		err      error
-		expected cmd
-	}{
+	for _, _case := range []parseCase{
 		// {nil, userError{`missing argument: "A"`}, cmd{}},
 		{[]string{"abc"}, nil, cmd{A: "abc"}},
 		{[]string{"abc", "123"}, nil, cmd{A: "abc", B: 123}},
@@ -220,20 +211,26 @@ func TestMain(m *testing.M) {
 }
 
 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"))
+	f := func(expected, start string) {
+		assert.EqualValues(t, expected, fieldFlagName(start), start)
+	}
+	f("noUpload", "NoUpload")
+	f("dht", "DHT")
+	f("noIPv6", "NoIPv6")
+	f("noIpv6", "NoIpv6")
+	f("tcpAddr", "TCPAddr")
+	f("addr", "Addr")
+	f("v", "V")
+	f("a", "A")
+	f("redisUrl", "RedisURL")
+	f("redisUrl", "redisURL")
 }
 
 func TestPrintUsage(t *testing.T) {
 	err := ParseErr(nil, []string{"-h"})
-	assert.Equal(t, ErrDefaultHelp, err)
+	assert.True(t, xerrors.Is(err, ErrDefaultHelp), "%#v", err)
 	err = ParseErr(nil, []string{"-help"})
-	assert.Equal(t, ErrDefaultHelp, err)
+	assert.True(t, xerrors.Is(err, ErrDefaultHelp))
 }
 
 func TestParseUnnamedTypes(t *testing.T) {
@@ -263,13 +260,6 @@ func TestPosArgSlice(t *testing.T) {
 	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
@@ -283,7 +273,9 @@ func TestUnexportedStructField(t *testing.T) {
 		badField bool
 	}
 	assert.NoError(t, ParseErr(&cmd, nil))
-	assert.EqualError(t, ParseErr(&cmd, []string{"-badField"}), `unknown flag: "badField"`)
+	var ue userError
+	require.True(t, xerrors.As(ParseErr(&cmd, []string{"-badField"}), &ue))
+	assert.EqualValues(t, userError{`unknown flag: "badField"`}, ue)
 }
 
 func TestExcessArgsEmpty(t *testing.T) {
@@ -329,5 +321,46 @@ func TestSliceOfUnmarshallableStruct(t *testing.T) {
 		Complex []struct{}
 	}
 	require.EqualError(t, ParseErr(&cmd, []string{"herp"}), "can't marshal type struct {}")
+}
 
+func newStruct(ref interface{}) func() interface{} {
+	return func() interface{} {
+		return reflect.New(reflect.TypeOf(ref)).Interface()
+	}
+}
+
+func TestBasicPointer(t *testing.T) {
+	type cmd struct {
+		Maybe *bool
+	}
+	_true := true
+	RunCases(t, []parseCase{
+		noErrorCase(cmd{}),
+		errorCase(userError{`excess argument: "nope"`}, "nope"),
+		noErrorCase(cmd{Maybe: &_true}, "-maybe=true"),
+	}, newStruct(cmd{}))
+}
+
+func TestMarshalByteArray(t *testing.T) {
+	type cmd struct {
+		StartPos
+		Bs [2]byte
+	}
+	RunCases(t, []parseCase{
+		noErrorCase(cmd{Bs: func() (ret [2]byte) { copy(ret[:], "AB"); return }()}, "4142"),
+		anyErrorCase("41424"),
+		// anyErrorCase("4142"),
+	}, newStruct(cmd{}))
+}
+
+func TestMarshalStruct(t *testing.T) {
+	type InternalStruct struct {
+		A bool
+	}
+	var cmd struct {
+		Struct InternalStruct
+		StartPos
+		StructPos InternalStruct
+	}
+	ParseErr(&cmd, []string{"-struct", "structpos"})
 }
diff --git a/usage.go b/usage.go
index 2597fcf..4754e80 100644
--- a/usage.go
+++ b/usage.go
@@ -5,14 +5,13 @@ import (
 	"io"
 	"text/tabwriter"
 
-	"github.com/anacrolix/missinggo"
-	"github.com/anacrolix/missinggo/slices"
+	"github.com/anacrolix/missinggo/v2"
+	"github.com/anacrolix/missinggo/v2/slices"
 )
 
-func (p *parser) printUsage(w io.Writer) {
-	fmt.Fprintf(w, "Usage:\n  %s", p.program)
-	if p.hasOptions() {
-		fmt.Fprintf(w, " [OPTIONS...]")
+func (p *Parser) printPosArgUsage(w io.Writer) {
+	if p.parent != nil {
+		p.parent.printPosArgUsage(w)
 	}
 	for _, arg := range p.posArgs {
 		fs := func() string {
@@ -36,6 +35,14 @@ func (p *parser) printUsage(w io.Writer) {
 		//  }
 		// }
 	}
+}
+
+func (p *Parser) printUsage(w io.Writer) {
+	fmt.Fprintf(w, "Usage:\n  %s", p.program)
+	if p.hasOptions() {
+		fmt.Fprintf(w, " [OPTIONS...]")
+	}
+	p.printPosArgUsage(w)
 	fmt.Fprintf(w, "\n")
 	if p.description != "" {
 		fmt.Fprintf(w, "\n%s\n", missinggo.Unchomp(p.description))
@@ -73,12 +80,10 @@ func writeOptionUsage(w io.Writer, flags []arg) {
 		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)
+			if help != "" {
+				help += " "
 			}
+			help += fmt.Sprintf("(Default: %v)", f.value)
 		}
 		fmt.Fprintf(tw, "\t(%s)\t%s\n", f.value.Type(), help)
 	}

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/share/gocode/src/github.com/anacrolix/tagflag/cases_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/anacrolix/tagflag/go.mod
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/anacrolix/tagflag/go.sum

No differences were encountered in the control files

More details

Full run details