From 91daaabd56964d0cffae99825e4696f3f70a3857 Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Sat, 24 Apr 2021 22:55:58 -0700 Subject: [PATCH] flag: add TextVar function The TextVar function makes it easier to integrate the flag package with any Go type that implements encoding.Text{Marshaler,Unmarshaler}. Fixes #45754 Change-Id: Id23c37d59cf8c9699a7943a22ce27a45eb685c0f Reviewed-on: https://go-review.googlesource.com/c/go/+/313329 Trust: Joseph Tsai Reviewed-by: Ian Lance Taylor Run-TryBot: Joseph Tsai TryBot-Result: Gopher Robot --- api/next.txt | 2 ++ src/flag/example_textvar_test.go | 35 ++++++++++++++++++++ src/flag/flag.go | 56 ++++++++++++++++++++++++++++++++ src/go/build/deps_test.go | 5 +-- 4 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 src/flag/example_textvar_test.go diff --git a/api/next.txt b/api/next.txt index 148cbffbfe..a0f2bed8d1 100644 --- a/api/next.txt +++ b/api/next.txt @@ -3,5 +3,7 @@ pkg encoding/binary, type AppendByteOrder interface, AppendUint16([]uint8, uint1 pkg encoding/binary, type AppendByteOrder interface, AppendUint32([]uint8, uint32) []uint8 pkg encoding/binary, type AppendByteOrder interface, AppendUint64([]uint8, uint64) []uint8 pkg encoding/binary, type AppendByteOrder interface, String() string +pkg flag, func TextVar(encoding.TextUnmarshaler, string, encoding.TextMarshaler, string) +pkg flag, method (*FlagSet) TextVar(encoding.TextUnmarshaler, string, encoding.TextMarshaler, string) pkg net/url, func JoinPath(string, ...string) (string, error) pkg net/url, method (*URL) JoinPath(...string) *URL diff --git a/src/flag/example_textvar_test.go b/src/flag/example_textvar_test.go new file mode 100644 index 0000000000..8b8cbf6b6c --- /dev/null +++ b/src/flag/example_textvar_test.go @@ -0,0 +1,35 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package flag_test + +import ( + "flag" + "fmt" + "net" + "os" +) + +func ExampleTextVar() { + fs := flag.NewFlagSet("ExampleTextVar", flag.ContinueOnError) + fs.SetOutput(os.Stdout) + var ip net.IP + fs.TextVar(&ip, "ip", net.IPv4(192, 168, 0, 100), "`IP address` to parse") + fs.Parse([]string{"-ip", "127.0.0.1"}) + fmt.Printf("{ip: %v}\n\n", ip) + + // 256 is not a valid IPv4 component + ip = nil + fs.Parse([]string{"-ip", "256.0.0.1"}) + fmt.Printf("{ip: %v}\n\n", ip) + + // Output: + // {ip: 127.0.0.1} + // + // invalid value "256.0.0.1" for flag -ip: invalid IP address: 256.0.0.1 + // Usage of ExampleTextVar: + // -ip IP address + // IP address to parse (default 192.168.0.100) + // {ip: } +} diff --git a/src/flag/flag.go b/src/flag/flag.go index 4e2af450c5..c27a144434 100644 --- a/src/flag/flag.go +++ b/src/flag/flag.go @@ -68,6 +68,7 @@ package flag import ( + "encoding" "errors" "fmt" "io" @@ -278,6 +279,43 @@ func (d *durationValue) Get() any { return time.Duration(*d) } func (d *durationValue) String() string { return (*time.Duration)(d).String() } +// -- encoding.TextUnmarshaler Value +type textValue struct{ p encoding.TextUnmarshaler } + +func newTextValue(val encoding.TextMarshaler, p encoding.TextUnmarshaler) textValue { + ptrVal := reflect.ValueOf(p) + if ptrVal.Kind() != reflect.Ptr { + panic("variable value type must be a pointer") + } + defVal := reflect.ValueOf(val) + if defVal.Kind() == reflect.Ptr { + defVal = defVal.Elem() + } + if defVal.Type() != ptrVal.Type().Elem() { + panic(fmt.Sprintf("default type does not match variable type: %v != %v", defVal.Type(), ptrVal.Type().Elem())) + } + ptrVal.Elem().Set(defVal) + return textValue{p} +} + +func (v textValue) Set(s string) error { + return v.p.UnmarshalText([]byte(s)) +} + +func (v textValue) Get() interface{} { + return v.p +} + +func (v textValue) String() string { + if m, ok := v.p.(encoding.TextMarshaler); ok { + if b, err := m.MarshalText(); err == nil { + return string(b) + } + } + return "" +} + +// -- func Value type funcValue func(string) error func (f funcValue) Set(s string) error { return f(s) } @@ -838,6 +876,24 @@ func Duration(name string, value time.Duration, usage string) *time.Duration { return CommandLine.Duration(name, value, usage) } +// TextVar defines a flag with a specified name, default value, and usage string. +// The argument p must be a pointer to a variable that will hold the value +// of the flag, and p must implement encoding.TextUnmarshaler. +// If the flag is used, the flag value will be passed to p's UnmarshalText method. +// The type of the default value must be the same as the type of p. +func (f *FlagSet) TextVar(p encoding.TextUnmarshaler, name string, value encoding.TextMarshaler, usage string) { + f.Var(newTextValue(value, p), name, usage) +} + +// TextVar defines a flag with a specified name, default value, and usage string. +// The argument p must be a pointer to a variable that will hold the value +// of the flag, and p must implement encoding.TextUnmarshaler. +// If the flag is used, the flag value will be passed to p's UnmarshalText method. +// The type of the default value must be the same as the type of p. +func TextVar(p encoding.TextUnmarshaler, name string, value encoding.TextMarshaler, usage string) { + CommandLine.Var(newTextValue(value, p), name, usage) +} + // Func defines a flag with the specified name and usage string. // Each time the flag is seen, fn is called with the value of the flag. // If fn returns a non-nil error, it will be treated as a flag value parsing error. diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 6ce872e297..6b2c2933f8 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -212,8 +212,7 @@ var depsRules = ` # Misc packages needing only FMT. FMT - < flag, - html, + < html, mime/quotedprintable, net/internal/socktest, net/url, @@ -230,6 +229,8 @@ var depsRules = ` < encoding/binary < encoding/base32, encoding/base64; + FMT, encoding < flag; + fmt !< encoding/base32, encoding/base64; FMT, encoding/base32, encoding/base64