diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:23:18 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:23:18 +0000 |
commit | 43a123c1ae6613b3efeed291fa552ecd909d3acf (patch) | |
tree | fd92518b7024bc74031f78a1cf9e454b65e73665 /src/flag | |
parent | Initial commit. (diff) | |
download | golang-1.20-43a123c1ae6613b3efeed291fa552ecd909d3acf.tar.xz golang-1.20-43a123c1ae6613b3efeed291fa552ecd909d3acf.zip |
Adding upstream version 1.20.14.upstream/1.20.14upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/flag')
-rw-r--r-- | src/flag/example_func_test.go | 41 | ||||
-rw-r--r-- | src/flag/example_test.go | 85 | ||||
-rw-r--r-- | src/flag/example_textvar_test.go | 35 | ||||
-rw-r--r-- | src/flag/example_value_test.go | 44 | ||||
-rw-r--r-- | src/flag/export_test.go | 24 | ||||
-rw-r--r-- | src/flag/flag.go | 1182 | ||||
-rw-r--r-- | src/flag/flag_test.go | 799 |
7 files changed, 2210 insertions, 0 deletions
diff --git a/src/flag/example_func_test.go b/src/flag/example_func_test.go new file mode 100644 index 0000000..7c30c5e --- /dev/null +++ b/src/flag/example_func_test.go @@ -0,0 +1,41 @@ +// Copyright 2020 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 ( + "errors" + "flag" + "fmt" + "net" + "os" +) + +func ExampleFunc() { + fs := flag.NewFlagSet("ExampleFunc", flag.ContinueOnError) + fs.SetOutput(os.Stdout) + var ip net.IP + fs.Func("ip", "`IP address` to parse", func(s string) error { + ip = net.ParseIP(s) + if ip == nil { + return errors.New("could not parse IP") + } + return nil + }) + fs.Parse([]string{"-ip", "127.0.0.1"}) + fmt.Printf("{ip: %v, loopback: %t}\n\n", ip, ip.IsLoopback()) + + // 256 is not a valid IPv4 component + fs.Parse([]string{"-ip", "256.0.0.1"}) + fmt.Printf("{ip: %v, loopback: %t}\n\n", ip, ip.IsLoopback()) + + // Output: + // {ip: 127.0.0.1, loopback: true} + // + // invalid value "256.0.0.1" for flag -ip: could not parse IP + // Usage of ExampleFunc: + // -ip IP address + // IP address to parse + // {ip: <nil>, loopback: false} +} diff --git a/src/flag/example_test.go b/src/flag/example_test.go new file mode 100644 index 0000000..088447d --- /dev/null +++ b/src/flag/example_test.go @@ -0,0 +1,85 @@ +// Copyright 2012 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. + +// These examples demonstrate more intricate uses of the flag package. +package flag_test + +import ( + "errors" + "flag" + "fmt" + "strings" + "time" +) + +// Example 1: A single string flag called "species" with default value "gopher". +var species = flag.String("species", "gopher", "the species we are studying") + +// Example 2: Two flags sharing a variable, so we can have a shorthand. +// The order of initialization is undefined, so make sure both use the +// same default value. They must be set up with an init function. +var gopherType string + +func init() { + const ( + defaultGopher = "pocket" + usage = "the variety of gopher" + ) + flag.StringVar(&gopherType, "gopher_type", defaultGopher, usage) + flag.StringVar(&gopherType, "g", defaultGopher, usage+" (shorthand)") +} + +// Example 3: A user-defined flag type, a slice of durations. +type interval []time.Duration + +// String is the method to format the flag's value, part of the flag.Value interface. +// The String method's output will be used in diagnostics. +func (i *interval) String() string { + return fmt.Sprint(*i) +} + +// Set is the method to set the flag value, part of the flag.Value interface. +// Set's argument is a string to be parsed to set the flag. +// It's a comma-separated list, so we split it. +func (i *interval) Set(value string) error { + // If we wanted to allow the flag to be set multiple times, + // accumulating values, we would delete this if statement. + // That would permit usages such as + // -deltaT 10s -deltaT 15s + // and other combinations. + if len(*i) > 0 { + return errors.New("interval flag already set") + } + for _, dt := range strings.Split(value, ",") { + duration, err := time.ParseDuration(dt) + if err != nil { + return err + } + *i = append(*i, duration) + } + return nil +} + +// Define a flag to accumulate durations. Because it has a special type, +// we need to use the Var function and therefore create the flag during +// init. + +var intervalFlag interval + +func init() { + // Tie the command-line flag to the intervalFlag variable and + // set a usage message. + flag.Var(&intervalFlag, "deltaT", "comma-separated list of intervals to use between events") +} + +func Example() { + // All the interesting pieces are with the variables declared above, but + // to enable the flag package to see the flags defined there, one must + // execute, typically at the start of main (not init!): + // flag.Parse() + // We don't call it here because this code is a function called "Example" + // that is part of the testing suite for the package, which has already + // parsed the flags. When viewed at pkg.go.dev, however, the function is + // renamed to "main" and it could be run as a standalone example. +} diff --git a/src/flag/example_textvar_test.go b/src/flag/example_textvar_test.go new file mode 100644 index 0000000..8b8cbf6 --- /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: <nil>} +} diff --git a/src/flag/example_value_test.go b/src/flag/example_value_test.go new file mode 100644 index 0000000..9d464c6 --- /dev/null +++ b/src/flag/example_value_test.go @@ -0,0 +1,44 @@ +// Copyright 2018 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/url" +) + +type URLValue struct { + URL *url.URL +} + +func (v URLValue) String() string { + if v.URL != nil { + return v.URL.String() + } + return "" +} + +func (v URLValue) Set(s string) error { + if u, err := url.Parse(s); err != nil { + return err + } else { + *v.URL = *u + } + return nil +} + +var u = &url.URL{} + +func ExampleValue() { + fs := flag.NewFlagSet("ExampleValue", flag.ExitOnError) + fs.Var(&URLValue{u}, "url", "URL to parse") + + fs.Parse([]string{"-url", "https://golang.org/pkg/flag/"}) + fmt.Printf(`{scheme: %q, host: %q, path: %q}`, u.Scheme, u.Host, u.Path) + + // Output: + // {scheme: "https", host: "golang.org", path: "/pkg/flag/"} +} diff --git a/src/flag/export_test.go b/src/flag/export_test.go new file mode 100644 index 0000000..9ef93ed --- /dev/null +++ b/src/flag/export_test.go @@ -0,0 +1,24 @@ +// Copyright 2010 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 + +import ( + "io" + "os" +) + +// Additional routines compiled into the package only during testing. + +var DefaultUsage = Usage + +// ResetForTesting clears all flag state and sets the usage function as directed. +// After calling ResetForTesting, parse errors in flag handling will not +// exit the program. +func ResetForTesting(usage func()) { + CommandLine = NewFlagSet(os.Args[0], ContinueOnError) + CommandLine.SetOutput(io.Discard) + CommandLine.Usage = commandLineUsage + Usage = usage +} diff --git a/src/flag/flag.go b/src/flag/flag.go new file mode 100644 index 0000000..ef3cf29 --- /dev/null +++ b/src/flag/flag.go @@ -0,0 +1,1182 @@ +// Copyright 2009 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 implements command-line flag parsing. + +# Usage + +Define flags using flag.String(), Bool(), Int(), etc. + +This declares an integer flag, -n, stored in the pointer nFlag, with type *int: + + import "flag" + var nFlag = flag.Int("n", 1234, "help message for flag n") + +If you like, you can bind the flag to a variable using the Var() functions. + + var flagvar int + func init() { + flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname") + } + +Or you can create custom flags that satisfy the Value interface (with +pointer receivers) and couple them to flag parsing by + + flag.Var(&flagVal, "name", "help message for flagname") + +For such flags, the default value is just the initial value of the variable. + +After all flags are defined, call + + flag.Parse() + +to parse the command line into the defined flags. + +Flags may then be used directly. If you're using the flags themselves, +they are all pointers; if you bind to variables, they're values. + + fmt.Println("ip has value ", *ip) + fmt.Println("flagvar has value ", flagvar) + +After parsing, the arguments following the flags are available as the +slice flag.Args() or individually as flag.Arg(i). +The arguments are indexed from 0 through flag.NArg()-1. + +# Command line flag syntax + +The following forms are permitted: + + -flag + --flag // double dashes are also permitted + -flag=x + -flag x // non-boolean flags only + +One or two dashes may be used; they are equivalent. +The last form is not permitted for boolean flags because the +meaning of the command + + cmd -x * + +where * is a Unix shell wildcard, will change if there is a file +called 0, false, etc. You must use the -flag=false form to turn +off a boolean flag. + +Flag parsing stops just before the first non-flag argument +("-" is a non-flag argument) or after the terminator "--". + +Integer flags accept 1234, 0664, 0x1234 and may be negative. +Boolean flags may be: + + 1, 0, t, f, T, F, true, false, TRUE, FALSE, True, False + +Duration flags accept any input valid for time.ParseDuration. + +The default set of command-line flags is controlled by +top-level functions. The FlagSet type allows one to define +independent sets of flags, such as to implement subcommands +in a command-line interface. The methods of FlagSet are +analogous to the top-level functions for the command-line +flag set. +*/ +package flag + +import ( + "encoding" + "errors" + "fmt" + "io" + "os" + "reflect" + "sort" + "strconv" + "strings" + "time" +) + +// ErrHelp is the error returned if the -help or -h flag is invoked +// but no such flag is defined. +var ErrHelp = errors.New("flag: help requested") + +// errParse is returned by Set if a flag's value fails to parse, such as with an invalid integer for Int. +// It then gets wrapped through failf to provide more information. +var errParse = errors.New("parse error") + +// errRange is returned by Set if a flag's value is out of range. +// It then gets wrapped through failf to provide more information. +var errRange = errors.New("value out of range") + +func numError(err error) error { + ne, ok := err.(*strconv.NumError) + if !ok { + return err + } + if ne.Err == strconv.ErrSyntax { + return errParse + } + if ne.Err == strconv.ErrRange { + return errRange + } + return err +} + +// -- bool Value +type boolValue bool + +func newBoolValue(val bool, p *bool) *boolValue { + *p = val + return (*boolValue)(p) +} + +func (b *boolValue) Set(s string) error { + v, err := strconv.ParseBool(s) + if err != nil { + err = errParse + } + *b = boolValue(v) + return err +} + +func (b *boolValue) Get() any { return bool(*b) } + +func (b *boolValue) String() string { return strconv.FormatBool(bool(*b)) } + +func (b *boolValue) IsBoolFlag() bool { return true } + +// optional interface to indicate boolean flags that can be +// supplied without "=value" text +type boolFlag interface { + Value + IsBoolFlag() bool +} + +// -- int Value +type intValue int + +func newIntValue(val int, p *int) *intValue { + *p = val + return (*intValue)(p) +} + +func (i *intValue) Set(s string) error { + v, err := strconv.ParseInt(s, 0, strconv.IntSize) + if err != nil { + err = numError(err) + } + *i = intValue(v) + return err +} + +func (i *intValue) Get() any { return int(*i) } + +func (i *intValue) String() string { return strconv.Itoa(int(*i)) } + +// -- int64 Value +type int64Value int64 + +func newInt64Value(val int64, p *int64) *int64Value { + *p = val + return (*int64Value)(p) +} + +func (i *int64Value) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 64) + if err != nil { + err = numError(err) + } + *i = int64Value(v) + return err +} + +func (i *int64Value) Get() any { return int64(*i) } + +func (i *int64Value) String() string { return strconv.FormatInt(int64(*i), 10) } + +// -- uint Value +type uintValue uint + +func newUintValue(val uint, p *uint) *uintValue { + *p = val + return (*uintValue)(p) +} + +func (i *uintValue) Set(s string) error { + v, err := strconv.ParseUint(s, 0, strconv.IntSize) + if err != nil { + err = numError(err) + } + *i = uintValue(v) + return err +} + +func (i *uintValue) Get() any { return uint(*i) } + +func (i *uintValue) String() string { return strconv.FormatUint(uint64(*i), 10) } + +// -- uint64 Value +type uint64Value uint64 + +func newUint64Value(val uint64, p *uint64) *uint64Value { + *p = val + return (*uint64Value)(p) +} + +func (i *uint64Value) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 64) + if err != nil { + err = numError(err) + } + *i = uint64Value(v) + return err +} + +func (i *uint64Value) Get() any { return uint64(*i) } + +func (i *uint64Value) String() string { return strconv.FormatUint(uint64(*i), 10) } + +// -- string Value +type stringValue string + +func newStringValue(val string, p *string) *stringValue { + *p = val + return (*stringValue)(p) +} + +func (s *stringValue) Set(val string) error { + *s = stringValue(val) + return nil +} + +func (s *stringValue) Get() any { return string(*s) } + +func (s *stringValue) String() string { return string(*s) } + +// -- float64 Value +type float64Value float64 + +func newFloat64Value(val float64, p *float64) *float64Value { + *p = val + return (*float64Value)(p) +} + +func (f *float64Value) Set(s string) error { + v, err := strconv.ParseFloat(s, 64) + if err != nil { + err = numError(err) + } + *f = float64Value(v) + return err +} + +func (f *float64Value) Get() any { return float64(*f) } + +func (f *float64Value) String() string { return strconv.FormatFloat(float64(*f), 'g', -1, 64) } + +// -- time.Duration Value +type durationValue time.Duration + +func newDurationValue(val time.Duration, p *time.Duration) *durationValue { + *p = val + return (*durationValue)(p) +} + +func (d *durationValue) Set(s string) error { + v, err := time.ParseDuration(s) + if err != nil { + err = errParse + } + *d = durationValue(v) + return err +} + +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) } + +func (f funcValue) String() string { return "" } + +// Value is the interface to the dynamic value stored in a flag. +// (The default value is represented as a string.) +// +// If a Value has an IsBoolFlag() bool method returning true, +// the command-line parser makes -name equivalent to -name=true +// rather than using the next command-line argument. +// +// Set is called once, in command line order, for each flag present. +// The flag package may call the String method with a zero-valued receiver, +// such as a nil pointer. +type Value interface { + String() string + Set(string) error +} + +// Getter is an interface that allows the contents of a Value to be retrieved. +// It wraps the Value interface, rather than being part of it, because it +// appeared after Go 1 and its compatibility rules. All Value types provided +// by this package satisfy the Getter interface, except the type used by Func. +type Getter interface { + Value + Get() any +} + +// ErrorHandling defines how FlagSet.Parse behaves if the parse fails. +type ErrorHandling int + +// These constants cause FlagSet.Parse to behave as described if the parse fails. +const ( + ContinueOnError ErrorHandling = iota // Return a descriptive error. + ExitOnError // Call os.Exit(2) or for -h/-help Exit(0). + PanicOnError // Call panic with a descriptive error. +) + +// A FlagSet represents a set of defined flags. The zero value of a FlagSet +// has no name and has ContinueOnError error handling. +// +// Flag names must be unique within a FlagSet. An attempt to define a flag whose +// name is already in use will cause a panic. +type FlagSet struct { + // Usage is the function called when an error occurs while parsing flags. + // The field is a function (not a method) that may be changed to point to + // a custom error handler. What happens after Usage is called depends + // on the ErrorHandling setting; for the command line, this defaults + // to ExitOnError, which exits the program after calling Usage. + Usage func() + + name string + parsed bool + actual map[string]*Flag + formal map[string]*Flag + args []string // arguments after flags + errorHandling ErrorHandling + output io.Writer // nil means stderr; use Output() accessor +} + +// A Flag represents the state of a flag. +type Flag struct { + Name string // name as it appears on command line + Usage string // help message + Value Value // value as set + DefValue string // default value (as text); for usage message +} + +// sortFlags returns the flags as a slice in lexicographical sorted order. +func sortFlags(flags map[string]*Flag) []*Flag { + result := make([]*Flag, len(flags)) + i := 0 + for _, f := range flags { + result[i] = f + i++ + } + sort.Slice(result, func(i, j int) bool { + return result[i].Name < result[j].Name + }) + return result +} + +// Output returns the destination for usage and error messages. os.Stderr is returned if +// output was not set or was set to nil. +func (f *FlagSet) Output() io.Writer { + if f.output == nil { + return os.Stderr + } + return f.output +} + +// Name returns the name of the flag set. +func (f *FlagSet) Name() string { + return f.name +} + +// ErrorHandling returns the error handling behavior of the flag set. +func (f *FlagSet) ErrorHandling() ErrorHandling { + return f.errorHandling +} + +// SetOutput sets the destination for usage and error messages. +// If output is nil, os.Stderr is used. +func (f *FlagSet) SetOutput(output io.Writer) { + f.output = output +} + +// VisitAll visits the flags in lexicographical order, calling fn for each. +// It visits all flags, even those not set. +func (f *FlagSet) VisitAll(fn func(*Flag)) { + for _, flag := range sortFlags(f.formal) { + fn(flag) + } +} + +// VisitAll visits the command-line flags in lexicographical order, calling +// fn for each. It visits all flags, even those not set. +func VisitAll(fn func(*Flag)) { + CommandLine.VisitAll(fn) +} + +// Visit visits the flags in lexicographical order, calling fn for each. +// It visits only those flags that have been set. +func (f *FlagSet) Visit(fn func(*Flag)) { + for _, flag := range sortFlags(f.actual) { + fn(flag) + } +} + +// Visit visits the command-line flags in lexicographical order, calling fn +// for each. It visits only those flags that have been set. +func Visit(fn func(*Flag)) { + CommandLine.Visit(fn) +} + +// Lookup returns the Flag structure of the named flag, returning nil if none exists. +func (f *FlagSet) Lookup(name string) *Flag { + return f.formal[name] +} + +// Lookup returns the Flag structure of the named command-line flag, +// returning nil if none exists. +func Lookup(name string) *Flag { + return CommandLine.formal[name] +} + +// Set sets the value of the named flag. +func (f *FlagSet) Set(name, value string) error { + flag, ok := f.formal[name] + if !ok { + return fmt.Errorf("no such flag -%v", name) + } + err := flag.Value.Set(value) + if err != nil { + return err + } + if f.actual == nil { + f.actual = make(map[string]*Flag) + } + f.actual[name] = flag + return nil +} + +// Set sets the value of the named command-line flag. +func Set(name, value string) error { + return CommandLine.Set(name, value) +} + +// isZeroValue determines whether the string represents the zero +// value for a flag. +func isZeroValue(flag *Flag, value string) (ok bool, err error) { + // Build a zero value of the flag's Value type, and see if the + // result of calling its String method equals the value passed in. + // This works unless the Value type is itself an interface type. + typ := reflect.TypeOf(flag.Value) + var z reflect.Value + if typ.Kind() == reflect.Pointer { + z = reflect.New(typ.Elem()) + } else { + z = reflect.Zero(typ) + } + // Catch panics calling the String method, which shouldn't prevent the + // usage message from being printed, but that we should report to the + // user so that they know to fix their code. + defer func() { + if e := recover(); e != nil { + if typ.Kind() == reflect.Pointer { + typ = typ.Elem() + } + err = fmt.Errorf("panic calling String method on zero %v for flag %s: %v", typ, flag.Name, e) + } + }() + return value == z.Interface().(Value).String(), nil +} + +// UnquoteUsage extracts a back-quoted name from the usage +// string for a flag and returns it and the un-quoted usage. +// Given "a `name` to show" it returns ("name", "a name to show"). +// If there are no back quotes, the name is an educated guess of the +// type of the flag's value, or the empty string if the flag is boolean. +func UnquoteUsage(flag *Flag) (name string, usage string) { + // Look for a back-quoted name, but avoid the strings package. + usage = flag.Usage + for i := 0; i < len(usage); i++ { + if usage[i] == '`' { + for j := i + 1; j < len(usage); j++ { + if usage[j] == '`' { + name = usage[i+1 : j] + usage = usage[:i] + name + usage[j+1:] + return name, usage + } + } + break // Only one back quote; use type name. + } + } + // No explicit name, so use type if we can find one. + name = "value" + switch fv := flag.Value.(type) { + case boolFlag: + if fv.IsBoolFlag() { + name = "" + } + case *durationValue: + name = "duration" + case *float64Value: + name = "float" + case *intValue, *int64Value: + name = "int" + case *stringValue: + name = "string" + case *uintValue, *uint64Value: + name = "uint" + } + return +} + +// PrintDefaults prints, to standard error unless configured otherwise, the +// default values of all defined command-line flags in the set. See the +// documentation for the global function PrintDefaults for more information. +func (f *FlagSet) PrintDefaults() { + var isZeroValueErrs []error + f.VisitAll(func(flag *Flag) { + var b strings.Builder + fmt.Fprintf(&b, " -%s", flag.Name) // Two spaces before -; see next two comments. + name, usage := UnquoteUsage(flag) + if len(name) > 0 { + b.WriteString(" ") + b.WriteString(name) + } + // Boolean flags of one ASCII letter are so common we + // treat them specially, putting their usage on the same line. + if b.Len() <= 4 { // space, space, '-', 'x'. + b.WriteString("\t") + } else { + // Four spaces before the tab triggers good alignment + // for both 4- and 8-space tab stops. + b.WriteString("\n \t") + } + b.WriteString(strings.ReplaceAll(usage, "\n", "\n \t")) + + // Print the default value only if it differs to the zero value + // for this flag type. + if isZero, err := isZeroValue(flag, flag.DefValue); err != nil { + isZeroValueErrs = append(isZeroValueErrs, err) + } else if !isZero { + if _, ok := flag.Value.(*stringValue); ok { + // put quotes on the value + fmt.Fprintf(&b, " (default %q)", flag.DefValue) + } else { + fmt.Fprintf(&b, " (default %v)", flag.DefValue) + } + } + fmt.Fprint(f.Output(), b.String(), "\n") + }) + // If calling String on any zero flag.Values triggered a panic, print + // the messages after the full set of defaults so that the programmer + // knows to fix the panic. + if errs := isZeroValueErrs; len(errs) > 0 { + fmt.Fprintln(f.Output()) + for _, err := range errs { + fmt.Fprintln(f.Output(), err) + } + } +} + +// PrintDefaults prints, to standard error unless configured otherwise, +// a usage message showing the default settings of all defined +// command-line flags. +// For an integer valued flag x, the default output has the form +// +// -x int +// usage-message-for-x (default 7) +// +// The usage message will appear on a separate line for anything but +// a bool flag with a one-byte name. For bool flags, the type is +// omitted and if the flag name is one byte the usage message appears +// on the same line. The parenthetical default is omitted if the +// default is the zero value for the type. The listed type, here int, +// can be changed by placing a back-quoted name in the flag's usage +// string; the first such item in the message is taken to be a parameter +// name to show in the message and the back quotes are stripped from +// the message when displayed. For instance, given +// +// flag.String("I", "", "search `directory` for include files") +// +// the output will be +// +// -I directory +// search directory for include files. +// +// To change the destination for flag messages, call CommandLine.SetOutput. +func PrintDefaults() { + CommandLine.PrintDefaults() +} + +// defaultUsage is the default function to print a usage message. +func (f *FlagSet) defaultUsage() { + if f.name == "" { + fmt.Fprintf(f.Output(), "Usage:\n") + } else { + fmt.Fprintf(f.Output(), "Usage of %s:\n", f.name) + } + f.PrintDefaults() +} + +// NOTE: Usage is not just defaultUsage(CommandLine) +// because it serves (via godoc flag Usage) as the example +// for how to write your own usage function. + +// Usage prints a usage message documenting all defined command-line flags +// to CommandLine's output, which by default is os.Stderr. +// It is called when an error occurs while parsing flags. +// The function is a variable that may be changed to point to a custom function. +// By default it prints a simple header and calls PrintDefaults; for details about the +// format of the output and how to control it, see the documentation for PrintDefaults. +// Custom usage functions may choose to exit the program; by default exiting +// happens anyway as the command line's error handling strategy is set to +// ExitOnError. +var Usage = func() { + fmt.Fprintf(CommandLine.Output(), "Usage of %s:\n", os.Args[0]) + PrintDefaults() +} + +// NFlag returns the number of flags that have been set. +func (f *FlagSet) NFlag() int { return len(f.actual) } + +// NFlag returns the number of command-line flags that have been set. +func NFlag() int { return len(CommandLine.actual) } + +// Arg returns the i'th argument. Arg(0) is the first remaining argument +// after flags have been processed. Arg returns an empty string if the +// requested element does not exist. +func (f *FlagSet) Arg(i int) string { + if i < 0 || i >= len(f.args) { + return "" + } + return f.args[i] +} + +// Arg returns the i'th command-line argument. Arg(0) is the first remaining argument +// after flags have been processed. Arg returns an empty string if the +// requested element does not exist. +func Arg(i int) string { + return CommandLine.Arg(i) +} + +// NArg is the number of arguments remaining after flags have been processed. +func (f *FlagSet) NArg() int { return len(f.args) } + +// NArg is the number of arguments remaining after flags have been processed. +func NArg() int { return len(CommandLine.args) } + +// Args returns the non-flag arguments. +func (f *FlagSet) Args() []string { return f.args } + +// Args returns the non-flag command-line arguments. +func Args() []string { return CommandLine.args } + +// BoolVar defines a bool flag with specified name, default value, and usage string. +// The argument p points to a bool variable in which to store the value of the flag. +func (f *FlagSet) BoolVar(p *bool, name string, value bool, usage string) { + f.Var(newBoolValue(value, p), name, usage) +} + +// BoolVar defines a bool flag with specified name, default value, and usage string. +// The argument p points to a bool variable in which to store the value of the flag. +func BoolVar(p *bool, name string, value bool, usage string) { + CommandLine.Var(newBoolValue(value, p), name, usage) +} + +// Bool defines a bool flag with specified name, default value, and usage string. +// The return value is the address of a bool variable that stores the value of the flag. +func (f *FlagSet) Bool(name string, value bool, usage string) *bool { + p := new(bool) + f.BoolVar(p, name, value, usage) + return p +} + +// Bool defines a bool flag with specified name, default value, and usage string. +// The return value is the address of a bool variable that stores the value of the flag. +func Bool(name string, value bool, usage string) *bool { + return CommandLine.Bool(name, value, usage) +} + +// IntVar defines an int flag with specified name, default value, and usage string. +// The argument p points to an int variable in which to store the value of the flag. +func (f *FlagSet) IntVar(p *int, name string, value int, usage string) { + f.Var(newIntValue(value, p), name, usage) +} + +// IntVar defines an int flag with specified name, default value, and usage string. +// The argument p points to an int variable in which to store the value of the flag. +func IntVar(p *int, name string, value int, usage string) { + CommandLine.Var(newIntValue(value, p), name, usage) +} + +// Int defines an int flag with specified name, default value, and usage string. +// The return value is the address of an int variable that stores the value of the flag. +func (f *FlagSet) Int(name string, value int, usage string) *int { + p := new(int) + f.IntVar(p, name, value, usage) + return p +} + +// Int defines an int flag with specified name, default value, and usage string. +// The return value is the address of an int variable that stores the value of the flag. +func Int(name string, value int, usage string) *int { + return CommandLine.Int(name, value, usage) +} + +// Int64Var defines an int64 flag with specified name, default value, and usage string. +// The argument p points to an int64 variable in which to store the value of the flag. +func (f *FlagSet) Int64Var(p *int64, name string, value int64, usage string) { + f.Var(newInt64Value(value, p), name, usage) +} + +// Int64Var defines an int64 flag with specified name, default value, and usage string. +// The argument p points to an int64 variable in which to store the value of the flag. +func Int64Var(p *int64, name string, value int64, usage string) { + CommandLine.Var(newInt64Value(value, p), name, usage) +} + +// Int64 defines an int64 flag with specified name, default value, and usage string. +// The return value is the address of an int64 variable that stores the value of the flag. +func (f *FlagSet) Int64(name string, value int64, usage string) *int64 { + p := new(int64) + f.Int64Var(p, name, value, usage) + return p +} + +// Int64 defines an int64 flag with specified name, default value, and usage string. +// The return value is the address of an int64 variable that stores the value of the flag. +func Int64(name string, value int64, usage string) *int64 { + return CommandLine.Int64(name, value, usage) +} + +// UintVar defines a uint flag with specified name, default value, and usage string. +// The argument p points to a uint variable in which to store the value of the flag. +func (f *FlagSet) UintVar(p *uint, name string, value uint, usage string) { + f.Var(newUintValue(value, p), name, usage) +} + +// UintVar defines a uint flag with specified name, default value, and usage string. +// The argument p points to a uint variable in which to store the value of the flag. +func UintVar(p *uint, name string, value uint, usage string) { + CommandLine.Var(newUintValue(value, p), name, usage) +} + +// Uint defines a uint flag with specified name, default value, and usage string. +// The return value is the address of a uint variable that stores the value of the flag. +func (f *FlagSet) Uint(name string, value uint, usage string) *uint { + p := new(uint) + f.UintVar(p, name, value, usage) + return p +} + +// Uint defines a uint flag with specified name, default value, and usage string. +// The return value is the address of a uint variable that stores the value of the flag. +func Uint(name string, value uint, usage string) *uint { + return CommandLine.Uint(name, value, usage) +} + +// Uint64Var defines a uint64 flag with specified name, default value, and usage string. +// The argument p points to a uint64 variable in which to store the value of the flag. +func (f *FlagSet) Uint64Var(p *uint64, name string, value uint64, usage string) { + f.Var(newUint64Value(value, p), name, usage) +} + +// Uint64Var defines a uint64 flag with specified name, default value, and usage string. +// The argument p points to a uint64 variable in which to store the value of the flag. +func Uint64Var(p *uint64, name string, value uint64, usage string) { + CommandLine.Var(newUint64Value(value, p), name, usage) +} + +// Uint64 defines a uint64 flag with specified name, default value, and usage string. +// The return value is the address of a uint64 variable that stores the value of the flag. +func (f *FlagSet) Uint64(name string, value uint64, usage string) *uint64 { + p := new(uint64) + f.Uint64Var(p, name, value, usage) + return p +} + +// Uint64 defines a uint64 flag with specified name, default value, and usage string. +// The return value is the address of a uint64 variable that stores the value of the flag. +func Uint64(name string, value uint64, usage string) *uint64 { + return CommandLine.Uint64(name, value, usage) +} + +// StringVar defines a string flag with specified name, default value, and usage string. +// The argument p points to a string variable in which to store the value of the flag. +func (f *FlagSet) StringVar(p *string, name string, value string, usage string) { + f.Var(newStringValue(value, p), name, usage) +} + +// StringVar defines a string flag with specified name, default value, and usage string. +// The argument p points to a string variable in which to store the value of the flag. +func StringVar(p *string, name string, value string, usage string) { + CommandLine.Var(newStringValue(value, p), name, usage) +} + +// String defines a string flag with specified name, default value, and usage string. +// The return value is the address of a string variable that stores the value of the flag. +func (f *FlagSet) String(name string, value string, usage string) *string { + p := new(string) + f.StringVar(p, name, value, usage) + return p +} + +// String defines a string flag with specified name, default value, and usage string. +// The return value is the address of a string variable that stores the value of the flag. +func String(name string, value string, usage string) *string { + return CommandLine.String(name, value, usage) +} + +// Float64Var defines a float64 flag with specified name, default value, and usage string. +// The argument p points to a float64 variable in which to store the value of the flag. +func (f *FlagSet) Float64Var(p *float64, name string, value float64, usage string) { + f.Var(newFloat64Value(value, p), name, usage) +} + +// Float64Var defines a float64 flag with specified name, default value, and usage string. +// The argument p points to a float64 variable in which to store the value of the flag. +func Float64Var(p *float64, name string, value float64, usage string) { + CommandLine.Var(newFloat64Value(value, p), name, usage) +} + +// Float64 defines a float64 flag with specified name, default value, and usage string. +// The return value is the address of a float64 variable that stores the value of the flag. +func (f *FlagSet) Float64(name string, value float64, usage string) *float64 { + p := new(float64) + f.Float64Var(p, name, value, usage) + return p +} + +// Float64 defines a float64 flag with specified name, default value, and usage string. +// The return value is the address of a float64 variable that stores the value of the flag. +func Float64(name string, value float64, usage string) *float64 { + return CommandLine.Float64(name, value, usage) +} + +// DurationVar defines a time.Duration flag with specified name, default value, and usage string. +// The argument p points to a time.Duration variable in which to store the value of the flag. +// The flag accepts a value acceptable to time.ParseDuration. +func (f *FlagSet) DurationVar(p *time.Duration, name string, value time.Duration, usage string) { + f.Var(newDurationValue(value, p), name, usage) +} + +// DurationVar defines a time.Duration flag with specified name, default value, and usage string. +// The argument p points to a time.Duration variable in which to store the value of the flag. +// The flag accepts a value acceptable to time.ParseDuration. +func DurationVar(p *time.Duration, name string, value time.Duration, usage string) { + CommandLine.Var(newDurationValue(value, p), name, usage) +} + +// Duration defines a time.Duration flag with specified name, default value, and usage string. +// The return value is the address of a time.Duration variable that stores the value of the flag. +// The flag accepts a value acceptable to time.ParseDuration. +func (f *FlagSet) Duration(name string, value time.Duration, usage string) *time.Duration { + p := new(time.Duration) + f.DurationVar(p, name, value, usage) + return p +} + +// Duration defines a time.Duration flag with specified name, default value, and usage string. +// The return value is the address of a time.Duration variable that stores the value of the flag. +// The flag accepts a value acceptable to time.ParseDuration. +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. +func (f *FlagSet) Func(name, usage string, fn func(string) error) { + f.Var(funcValue(fn), 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. +func Func(name, usage string, fn func(string) error) { + CommandLine.Func(name, usage, fn) +} + +// Var defines a flag with the specified name and usage string. The type and +// value of the flag are represented by the first argument, of type Value, which +// typically holds a user-defined implementation of Value. For instance, the +// caller could create a flag that turns a comma-separated string into a slice +// of strings by giving the slice the methods of Value; in particular, Set would +// decompose the comma-separated string into the slice. +func (f *FlagSet) Var(value Value, name string, usage string) { + // Flag must not begin "-" or contain "=". + if strings.HasPrefix(name, "-") { + panic(f.sprintf("flag %q begins with -", name)) + } else if strings.Contains(name, "=") { + panic(f.sprintf("flag %q contains =", name)) + } + + // Remember the default value as a string; it won't change. + flag := &Flag{name, usage, value, value.String()} + _, alreadythere := f.formal[name] + if alreadythere { + var msg string + if f.name == "" { + msg = f.sprintf("flag redefined: %s", name) + } else { + msg = f.sprintf("%s flag redefined: %s", f.name, name) + } + panic(msg) // Happens only if flags are declared with identical names + } + if f.formal == nil { + f.formal = make(map[string]*Flag) + } + f.formal[name] = flag +} + +// Var defines a flag with the specified name and usage string. The type and +// value of the flag are represented by the first argument, of type Value, which +// typically holds a user-defined implementation of Value. For instance, the +// caller could create a flag that turns a comma-separated string into a slice +// of strings by giving the slice the methods of Value; in particular, Set would +// decompose the comma-separated string into the slice. +func Var(value Value, name string, usage string) { + CommandLine.Var(value, name, usage) +} + +// sprintf formats the message, prints it to output, and returns it. +func (f *FlagSet) sprintf(format string, a ...any) string { + msg := fmt.Sprintf(format, a...) + fmt.Fprintln(f.Output(), msg) + return msg +} + +// failf prints to standard error a formatted error and usage message and +// returns the error. +func (f *FlagSet) failf(format string, a ...any) error { + msg := f.sprintf(format, a...) + f.usage() + return errors.New(msg) +} + +// usage calls the Usage method for the flag set if one is specified, +// or the appropriate default usage function otherwise. +func (f *FlagSet) usage() { + if f.Usage == nil { + f.defaultUsage() + } else { + f.Usage() + } +} + +// parseOne parses one flag. It reports whether a flag was seen. +func (f *FlagSet) parseOne() (bool, error) { + if len(f.args) == 0 { + return false, nil + } + s := f.args[0] + if len(s) < 2 || s[0] != '-' { + return false, nil + } + numMinuses := 1 + if s[1] == '-' { + numMinuses++ + if len(s) == 2 { // "--" terminates the flags + f.args = f.args[1:] + return false, nil + } + } + name := s[numMinuses:] + if len(name) == 0 || name[0] == '-' || name[0] == '=' { + return false, f.failf("bad flag syntax: %s", s) + } + + // it's a flag. does it have an argument? + f.args = f.args[1:] + hasValue := false + value := "" + for i := 1; i < len(name); i++ { // equals cannot be first + if name[i] == '=' { + value = name[i+1:] + hasValue = true + name = name[0:i] + break + } + } + + flag, ok := f.formal[name] + if !ok { + if name == "help" || name == "h" { // special case for nice help message. + f.usage() + return false, ErrHelp + } + return false, f.failf("flag provided but not defined: -%s", name) + } + + if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() { // special case: doesn't need an arg + if hasValue { + if err := fv.Set(value); err != nil { + return false, f.failf("invalid boolean value %q for -%s: %v", value, name, err) + } + } else { + if err := fv.Set("true"); err != nil { + return false, f.failf("invalid boolean flag %s: %v", name, err) + } + } + } else { + // It must have a value, which might be the next argument. + if !hasValue && len(f.args) > 0 { + // value is the next arg + hasValue = true + value, f.args = f.args[0], f.args[1:] + } + if !hasValue { + return false, f.failf("flag needs an argument: -%s", name) + } + if err := flag.Value.Set(value); err != nil { + return false, f.failf("invalid value %q for flag -%s: %v", value, name, err) + } + } + if f.actual == nil { + f.actual = make(map[string]*Flag) + } + f.actual[name] = flag + return true, nil +} + +// Parse parses flag definitions from the argument list, which should not +// include the command name. Must be called after all flags in the FlagSet +// are defined and before flags are accessed by the program. +// The return value will be ErrHelp if -help or -h were set but not defined. +func (f *FlagSet) Parse(arguments []string) error { + f.parsed = true + f.args = arguments + for { + seen, err := f.parseOne() + if seen { + continue + } + if err == nil { + break + } + switch f.errorHandling { + case ContinueOnError: + return err + case ExitOnError: + if err == ErrHelp { + os.Exit(0) + } + os.Exit(2) + case PanicOnError: + panic(err) + } + } + return nil +} + +// Parsed reports whether f.Parse has been called. +func (f *FlagSet) Parsed() bool { + return f.parsed +} + +// Parse parses the command-line flags from os.Args[1:]. Must be called +// after all flags are defined and before flags are accessed by the program. +func Parse() { + // Ignore errors; CommandLine is set for ExitOnError. + CommandLine.Parse(os.Args[1:]) +} + +// Parsed reports whether the command-line flags have been parsed. +func Parsed() bool { + return CommandLine.Parsed() +} + +// CommandLine is the default set of command-line flags, parsed from os.Args. +// The top-level functions such as BoolVar, Arg, and so on are wrappers for the +// methods of CommandLine. +var CommandLine = NewFlagSet(os.Args[0], ExitOnError) + +func init() { + // Override generic FlagSet default Usage with call to global Usage. + // Note: This is not CommandLine.Usage = Usage, + // because we want any eventual call to use any updated value of Usage, + // not the value it has when this line is run. + CommandLine.Usage = commandLineUsage +} + +func commandLineUsage() { + Usage() +} + +// NewFlagSet returns a new, empty flag set with the specified name and +// error handling property. If the name is not empty, it will be printed +// in the default usage message and in error messages. +func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet { + f := &FlagSet{ + name: name, + errorHandling: errorHandling, + } + f.Usage = f.defaultUsage + return f +} + +// Init sets the name and error handling property for a flag set. +// By default, the zero FlagSet uses an empty name and the +// ContinueOnError error handling policy. +func (f *FlagSet) Init(name string, errorHandling ErrorHandling) { + f.name = name + f.errorHandling = errorHandling +} diff --git a/src/flag/flag_test.go b/src/flag/flag_test.go new file mode 100644 index 0000000..1755168 --- /dev/null +++ b/src/flag/flag_test.go @@ -0,0 +1,799 @@ +// Copyright 2009 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 ( + "bytes" + . "flag" + "fmt" + "internal/testenv" + "io" + "os" + "os/exec" + "runtime" + "sort" + "strconv" + "strings" + "testing" + "time" +) + +func boolString(s string) string { + if s == "0" { + return "false" + } + return "true" +} + +func TestEverything(t *testing.T) { + ResetForTesting(nil) + Bool("test_bool", false, "bool value") + Int("test_int", 0, "int value") + Int64("test_int64", 0, "int64 value") + Uint("test_uint", 0, "uint value") + Uint64("test_uint64", 0, "uint64 value") + String("test_string", "0", "string value") + Float64("test_float64", 0, "float64 value") + Duration("test_duration", 0, "time.Duration value") + Func("test_func", "func value", func(string) error { return nil }) + + m := make(map[string]*Flag) + desired := "0" + visitor := func(f *Flag) { + if len(f.Name) > 5 && f.Name[0:5] == "test_" { + m[f.Name] = f + ok := false + switch { + case f.Value.String() == desired: + ok = true + case f.Name == "test_bool" && f.Value.String() == boolString(desired): + ok = true + case f.Name == "test_duration" && f.Value.String() == desired+"s": + ok = true + case f.Name == "test_func" && f.Value.String() == "": + ok = true + } + if !ok { + t.Error("Visit: bad value", f.Value.String(), "for", f.Name) + } + } + } + VisitAll(visitor) + if len(m) != 9 { + t.Error("VisitAll misses some flags") + for k, v := range m { + t.Log(k, *v) + } + } + m = make(map[string]*Flag) + Visit(visitor) + if len(m) != 0 { + t.Errorf("Visit sees unset flags") + for k, v := range m { + t.Log(k, *v) + } + } + // Now set all flags + Set("test_bool", "true") + Set("test_int", "1") + Set("test_int64", "1") + Set("test_uint", "1") + Set("test_uint64", "1") + Set("test_string", "1") + Set("test_float64", "1") + Set("test_duration", "1s") + Set("test_func", "1") + desired = "1" + Visit(visitor) + if len(m) != 9 { + t.Error("Visit fails after set") + for k, v := range m { + t.Log(k, *v) + } + } + // Now test they're visited in sort order. + var flagNames []string + Visit(func(f *Flag) { flagNames = append(flagNames, f.Name) }) + if !sort.StringsAreSorted(flagNames) { + t.Errorf("flag names not sorted: %v", flagNames) + } +} + +func TestGet(t *testing.T) { + ResetForTesting(nil) + Bool("test_bool", true, "bool value") + Int("test_int", 1, "int value") + Int64("test_int64", 2, "int64 value") + Uint("test_uint", 3, "uint value") + Uint64("test_uint64", 4, "uint64 value") + String("test_string", "5", "string value") + Float64("test_float64", 6, "float64 value") + Duration("test_duration", 7, "time.Duration value") + + visitor := func(f *Flag) { + if len(f.Name) > 5 && f.Name[0:5] == "test_" { + g, ok := f.Value.(Getter) + if !ok { + t.Errorf("Visit: value does not satisfy Getter: %T", f.Value) + return + } + switch f.Name { + case "test_bool": + ok = g.Get() == true + case "test_int": + ok = g.Get() == int(1) + case "test_int64": + ok = g.Get() == int64(2) + case "test_uint": + ok = g.Get() == uint(3) + case "test_uint64": + ok = g.Get() == uint64(4) + case "test_string": + ok = g.Get() == "5" + case "test_float64": + ok = g.Get() == float64(6) + case "test_duration": + ok = g.Get() == time.Duration(7) + } + if !ok { + t.Errorf("Visit: bad value %T(%v) for %s", g.Get(), g.Get(), f.Name) + } + } + } + VisitAll(visitor) +} + +func TestUsage(t *testing.T) { + called := false + ResetForTesting(func() { called = true }) + if CommandLine.Parse([]string{"-x"}) == nil { + t.Error("parse did not fail for unknown flag") + } + if !called { + t.Error("did not call Usage for unknown flag") + } +} + +func testParse(f *FlagSet, t *testing.T) { + if f.Parsed() { + t.Error("f.Parse() = true before Parse") + } + boolFlag := f.Bool("bool", false, "bool value") + bool2Flag := f.Bool("bool2", false, "bool2 value") + intFlag := f.Int("int", 0, "int value") + int64Flag := f.Int64("int64", 0, "int64 value") + uintFlag := f.Uint("uint", 0, "uint value") + uint64Flag := f.Uint64("uint64", 0, "uint64 value") + stringFlag := f.String("string", "0", "string value") + float64Flag := f.Float64("float64", 0, "float64 value") + durationFlag := f.Duration("duration", 5*time.Second, "time.Duration value") + extra := "one-extra-argument" + args := []string{ + "-bool", + "-bool2=true", + "--int", "22", + "--int64", "0x23", + "-uint", "24", + "--uint64", "25", + "-string", "hello", + "-float64", "2718e28", + "-duration", "2m", + extra, + } + if err := f.Parse(args); err != nil { + t.Fatal(err) + } + if !f.Parsed() { + t.Error("f.Parse() = false after Parse") + } + if *boolFlag != true { + t.Error("bool flag should be true, is ", *boolFlag) + } + if *bool2Flag != true { + t.Error("bool2 flag should be true, is ", *bool2Flag) + } + if *intFlag != 22 { + t.Error("int flag should be 22, is ", *intFlag) + } + if *int64Flag != 0x23 { + t.Error("int64 flag should be 0x23, is ", *int64Flag) + } + if *uintFlag != 24 { + t.Error("uint flag should be 24, is ", *uintFlag) + } + if *uint64Flag != 25 { + t.Error("uint64 flag should be 25, is ", *uint64Flag) + } + if *stringFlag != "hello" { + t.Error("string flag should be `hello`, is ", *stringFlag) + } + if *float64Flag != 2718e28 { + t.Error("float64 flag should be 2718e28, is ", *float64Flag) + } + if *durationFlag != 2*time.Minute { + t.Error("duration flag should be 2m, is ", *durationFlag) + } + if len(f.Args()) != 1 { + t.Error("expected one argument, got", len(f.Args())) + } else if f.Args()[0] != extra { + t.Errorf("expected argument %q got %q", extra, f.Args()[0]) + } +} + +func TestParse(t *testing.T) { + ResetForTesting(func() { t.Error("bad parse") }) + testParse(CommandLine, t) +} + +func TestFlagSetParse(t *testing.T) { + testParse(NewFlagSet("test", ContinueOnError), t) +} + +// Declare a user-defined flag type. +type flagVar []string + +func (f *flagVar) String() string { + return fmt.Sprint([]string(*f)) +} + +func (f *flagVar) Set(value string) error { + *f = append(*f, value) + return nil +} + +func TestUserDefined(t *testing.T) { + var flags FlagSet + flags.Init("test", ContinueOnError) + flags.SetOutput(io.Discard) + var v flagVar + flags.Var(&v, "v", "usage") + if err := flags.Parse([]string{"-v", "1", "-v", "2", "-v=3"}); err != nil { + t.Error(err) + } + if len(v) != 3 { + t.Fatal("expected 3 args; got ", len(v)) + } + expect := "[1 2 3]" + if v.String() != expect { + t.Errorf("expected value %q got %q", expect, v.String()) + } +} + +func TestUserDefinedFunc(t *testing.T) { + flags := NewFlagSet("test", ContinueOnError) + flags.SetOutput(io.Discard) + var ss []string + flags.Func("v", "usage", func(s string) error { + ss = append(ss, s) + return nil + }) + if err := flags.Parse([]string{"-v", "1", "-v", "2", "-v=3"}); err != nil { + t.Error(err) + } + if len(ss) != 3 { + t.Fatal("expected 3 args; got ", len(ss)) + } + expect := "[1 2 3]" + if got := fmt.Sprint(ss); got != expect { + t.Errorf("expected value %q got %q", expect, got) + } + // test usage + var buf strings.Builder + flags.SetOutput(&buf) + flags.Parse([]string{"-h"}) + if usage := buf.String(); !strings.Contains(usage, "usage") { + t.Errorf("usage string not included: %q", usage) + } + // test Func error + flags = NewFlagSet("test", ContinueOnError) + flags.SetOutput(io.Discard) + flags.Func("v", "usage", func(s string) error { + return fmt.Errorf("test error") + }) + // flag not set, so no error + if err := flags.Parse(nil); err != nil { + t.Error(err) + } + // flag set, expect error + if err := flags.Parse([]string{"-v", "1"}); err == nil { + t.Error("expected error; got none") + } else if errMsg := err.Error(); !strings.Contains(errMsg, "test error") { + t.Errorf(`error should contain "test error"; got %q`, errMsg) + } +} + +func TestUserDefinedForCommandLine(t *testing.T) { + const help = "HELP" + var result string + ResetForTesting(func() { result = help }) + Usage() + if result != help { + t.Fatalf("got %q; expected %q", result, help) + } +} + +// Declare a user-defined boolean flag type. +type boolFlagVar struct { + count int +} + +func (b *boolFlagVar) String() string { + return fmt.Sprintf("%d", b.count) +} + +func (b *boolFlagVar) Set(value string) error { + if value == "true" { + b.count++ + } + return nil +} + +func (b *boolFlagVar) IsBoolFlag() bool { + return b.count < 4 +} + +func TestUserDefinedBool(t *testing.T) { + var flags FlagSet + flags.Init("test", ContinueOnError) + flags.SetOutput(io.Discard) + var b boolFlagVar + var err error + flags.Var(&b, "b", "usage") + if err = flags.Parse([]string{"-b", "-b", "-b", "-b=true", "-b=false", "-b", "barg", "-b"}); err != nil { + if b.count < 4 { + t.Error(err) + } + } + + if b.count != 4 { + t.Errorf("want: %d; got: %d", 4, b.count) + } + + if err == nil { + t.Error("expected error; got none") + } +} + +func TestUserDefinedBoolUsage(t *testing.T) { + var flags FlagSet + flags.Init("test", ContinueOnError) + var buf bytes.Buffer + flags.SetOutput(&buf) + var b boolFlagVar + flags.Var(&b, "b", "X") + b.count = 0 + // b.IsBoolFlag() will return true and usage will look boolean. + flags.PrintDefaults() + got := buf.String() + want := " -b\tX\n" + if got != want { + t.Errorf("false: want %q; got %q", want, got) + } + b.count = 4 + // b.IsBoolFlag() will return false and usage will look non-boolean. + flags.PrintDefaults() + got = buf.String() + want = " -b\tX\n -b value\n \tX\n" + if got != want { + t.Errorf("false: want %q; got %q", want, got) + } +} + +func TestSetOutput(t *testing.T) { + var flags FlagSet + var buf strings.Builder + flags.SetOutput(&buf) + flags.Init("test", ContinueOnError) + flags.Parse([]string{"-unknown"}) + if out := buf.String(); !strings.Contains(out, "-unknown") { + t.Logf("expected output mentioning unknown; got %q", out) + } +} + +// This tests that one can reset the flags. This still works but not well, and is +// superseded by FlagSet. +func TestChangingArgs(t *testing.T) { + ResetForTesting(func() { t.Fatal("bad parse") }) + oldArgs := os.Args + defer func() { os.Args = oldArgs }() + os.Args = []string{"cmd", "-before", "subcmd", "-after", "args"} + before := Bool("before", false, "") + if err := CommandLine.Parse(os.Args[1:]); err != nil { + t.Fatal(err) + } + cmd := Arg(0) + os.Args = Args() + after := Bool("after", false, "") + Parse() + args := Args() + + if !*before || cmd != "subcmd" || !*after || len(args) != 1 || args[0] != "args" { + t.Fatalf("expected true subcmd true [args] got %v %v %v %v", *before, cmd, *after, args) + } +} + +// Test that -help invokes the usage message and returns ErrHelp. +func TestHelp(t *testing.T) { + var helpCalled = false + fs := NewFlagSet("help test", ContinueOnError) + fs.Usage = func() { helpCalled = true } + var flag bool + fs.BoolVar(&flag, "flag", false, "regular flag") + // Regular flag invocation should work + err := fs.Parse([]string{"-flag=true"}) + if err != nil { + t.Fatal("expected no error; got ", err) + } + if !flag { + t.Error("flag was not set by -flag") + } + if helpCalled { + t.Error("help called for regular flag") + helpCalled = false // reset for next test + } + // Help flag should work as expected. + err = fs.Parse([]string{"-help"}) + if err == nil { + t.Fatal("error expected") + } + if err != ErrHelp { + t.Fatal("expected ErrHelp; got ", err) + } + if !helpCalled { + t.Fatal("help was not called") + } + // If we define a help flag, that should override. + var help bool + fs.BoolVar(&help, "help", false, "help flag") + helpCalled = false + err = fs.Parse([]string{"-help"}) + if err != nil { + t.Fatal("expected no error for defined -help; got ", err) + } + if helpCalled { + t.Fatal("help was called; should not have been for defined help flag") + } +} + +// zeroPanicker is a flag.Value whose String method panics if its dontPanic +// field is false. +type zeroPanicker struct { + dontPanic bool + v string +} + +func (f *zeroPanicker) Set(s string) error { + f.v = s + return nil +} + +func (f *zeroPanicker) String() string { + if !f.dontPanic { + panic("panic!") + } + return f.v +} + +const defaultOutput = ` -A for bootstrapping, allow 'any' type + -Alongflagname + disable bounds checking + -C a boolean defaulting to true (default true) + -D path + set relative path for local imports + -E string + issue 23543 (default "0") + -F number + a non-zero number (default 2.7) + -G float + a float that defaults to zero + -M string + a multiline + help + string + -N int + a non-zero int (default 27) + -O a flag + multiline help string (default true) + -V list + a list of strings (default [a b]) + -Z int + an int that defaults to zero + -ZP0 value + a flag whose String method panics when it is zero + -ZP1 value + a flag whose String method panics when it is zero + -maxT timeout + set timeout for dial + +panic calling String method on zero flag_test.zeroPanicker for flag ZP0: panic! +panic calling String method on zero flag_test.zeroPanicker for flag ZP1: panic! +` + +func TestPrintDefaults(t *testing.T) { + fs := NewFlagSet("print defaults test", ContinueOnError) + var buf strings.Builder + fs.SetOutput(&buf) + fs.Bool("A", false, "for bootstrapping, allow 'any' type") + fs.Bool("Alongflagname", false, "disable bounds checking") + fs.Bool("C", true, "a boolean defaulting to true") + fs.String("D", "", "set relative `path` for local imports") + fs.String("E", "0", "issue 23543") + fs.Float64("F", 2.7, "a non-zero `number`") + fs.Float64("G", 0, "a float that defaults to zero") + fs.String("M", "", "a multiline\nhelp\nstring") + fs.Int("N", 27, "a non-zero int") + fs.Bool("O", true, "a flag\nmultiline help string") + fs.Var(&flagVar{"a", "b"}, "V", "a `list` of strings") + fs.Int("Z", 0, "an int that defaults to zero") + fs.Var(&zeroPanicker{true, ""}, "ZP0", "a flag whose String method panics when it is zero") + fs.Var(&zeroPanicker{true, "something"}, "ZP1", "a flag whose String method panics when it is zero") + fs.Duration("maxT", 0, "set `timeout` for dial") + fs.PrintDefaults() + got := buf.String() + if got != defaultOutput { + t.Errorf("got:\n%q\nwant:\n%q", got, defaultOutput) + } +} + +// Issue 19230: validate range of Int and Uint flag values. +func TestIntFlagOverflow(t *testing.T) { + if strconv.IntSize != 32 { + return + } + ResetForTesting(nil) + Int("i", 0, "") + Uint("u", 0, "") + if err := Set("i", "2147483648"); err == nil { + t.Error("unexpected success setting Int") + } + if err := Set("u", "4294967296"); err == nil { + t.Error("unexpected success setting Uint") + } +} + +// Issue 20998: Usage should respect CommandLine.output. +func TestUsageOutput(t *testing.T) { + ResetForTesting(DefaultUsage) + var buf strings.Builder + CommandLine.SetOutput(&buf) + defer func(old []string) { os.Args = old }(os.Args) + os.Args = []string{"app", "-i=1", "-unknown"} + Parse() + const want = "flag provided but not defined: -i\nUsage of app:\n" + if got := buf.String(); got != want { + t.Errorf("output = %q; want %q", got, want) + } +} + +func TestGetters(t *testing.T) { + expectedName := "flag set" + expectedErrorHandling := ContinueOnError + expectedOutput := io.Writer(os.Stderr) + fs := NewFlagSet(expectedName, expectedErrorHandling) + + if fs.Name() != expectedName { + t.Errorf("unexpected name: got %s, expected %s", fs.Name(), expectedName) + } + if fs.ErrorHandling() != expectedErrorHandling { + t.Errorf("unexpected ErrorHandling: got %d, expected %d", fs.ErrorHandling(), expectedErrorHandling) + } + if fs.Output() != expectedOutput { + t.Errorf("unexpected output: got %#v, expected %#v", fs.Output(), expectedOutput) + } + + expectedName = "gopher" + expectedErrorHandling = ExitOnError + expectedOutput = os.Stdout + fs.Init(expectedName, expectedErrorHandling) + fs.SetOutput(expectedOutput) + + if fs.Name() != expectedName { + t.Errorf("unexpected name: got %s, expected %s", fs.Name(), expectedName) + } + if fs.ErrorHandling() != expectedErrorHandling { + t.Errorf("unexpected ErrorHandling: got %d, expected %d", fs.ErrorHandling(), expectedErrorHandling) + } + if fs.Output() != expectedOutput { + t.Errorf("unexpected output: got %v, expected %v", fs.Output(), expectedOutput) + } +} + +func TestParseError(t *testing.T) { + for _, typ := range []string{"bool", "int", "int64", "uint", "uint64", "float64", "duration"} { + fs := NewFlagSet("parse error test", ContinueOnError) + fs.SetOutput(io.Discard) + _ = fs.Bool("bool", false, "") + _ = fs.Int("int", 0, "") + _ = fs.Int64("int64", 0, "") + _ = fs.Uint("uint", 0, "") + _ = fs.Uint64("uint64", 0, "") + _ = fs.Float64("float64", 0, "") + _ = fs.Duration("duration", 0, "") + // Strings cannot give errors. + args := []string{"-" + typ + "=x"} + err := fs.Parse(args) // x is not a valid setting for any flag. + if err == nil { + t.Errorf("Parse(%q)=%v; expected parse error", args, err) + continue + } + if !strings.Contains(err.Error(), "invalid") || !strings.Contains(err.Error(), "parse error") { + t.Errorf("Parse(%q)=%v; expected parse error", args, err) + } + } +} + +func TestRangeError(t *testing.T) { + bad := []string{ + "-int=123456789012345678901", + "-int64=123456789012345678901", + "-uint=123456789012345678901", + "-uint64=123456789012345678901", + "-float64=1e1000", + } + for _, arg := range bad { + fs := NewFlagSet("parse error test", ContinueOnError) + fs.SetOutput(io.Discard) + _ = fs.Int("int", 0, "") + _ = fs.Int64("int64", 0, "") + _ = fs.Uint("uint", 0, "") + _ = fs.Uint64("uint64", 0, "") + _ = fs.Float64("float64", 0, "") + // Strings cannot give errors, and bools and durations do not return strconv.NumError. + err := fs.Parse([]string{arg}) + if err == nil { + t.Errorf("Parse(%q)=%v; expected range error", arg, err) + continue + } + if !strings.Contains(err.Error(), "invalid") || !strings.Contains(err.Error(), "value out of range") { + t.Errorf("Parse(%q)=%v; expected range error", arg, err) + } + } +} + +func TestExitCode(t *testing.T) { + testenv.MustHaveExec(t) + + magic := 123 + if os.Getenv("GO_CHILD_FLAG") != "" { + fs := NewFlagSet("test", ExitOnError) + if os.Getenv("GO_CHILD_FLAG_HANDLE") != "" { + var b bool + fs.BoolVar(&b, os.Getenv("GO_CHILD_FLAG_HANDLE"), false, "") + } + fs.Parse([]string{os.Getenv("GO_CHILD_FLAG")}) + os.Exit(magic) + } + + tests := []struct { + flag string + flagHandle string + expectExit int + }{ + { + flag: "-h", + expectExit: 0, + }, + { + flag: "-help", + expectExit: 0, + }, + { + flag: "-undefined", + expectExit: 2, + }, + { + flag: "-h", + flagHandle: "h", + expectExit: magic, + }, + { + flag: "-help", + flagHandle: "help", + expectExit: magic, + }, + } + + for _, test := range tests { + cmd := exec.Command(os.Args[0], "-test.run=TestExitCode") + cmd.Env = append( + os.Environ(), + "GO_CHILD_FLAG="+test.flag, + "GO_CHILD_FLAG_HANDLE="+test.flagHandle, + ) + cmd.Run() + got := cmd.ProcessState.ExitCode() + // ExitCode is either 0 or 1 on Plan 9. + if runtime.GOOS == "plan9" && test.expectExit != 0 { + test.expectExit = 1 + } + if got != test.expectExit { + t.Errorf("unexpected exit code for test case %+v \n: got %d, expect %d", + test, got, test.expectExit) + } + } +} + +func mustPanic(t *testing.T, testName string, expected string, f func()) { + t.Helper() + defer func() { + switch msg := recover().(type) { + case nil: + t.Errorf("%s\n: expected panic(%q), but did not panic", testName, expected) + case string: + if msg != expected { + t.Errorf("%s\n: expected panic(%q), but got panic(%q)", testName, expected, msg) + } + default: + t.Errorf("%s\n: expected panic(%q), but got panic(%T%v)", testName, expected, msg, msg) + } + }() + f() +} + +func TestInvalidFlags(t *testing.T) { + tests := []struct { + flag string + errorMsg string + }{ + { + flag: "-foo", + errorMsg: "flag \"-foo\" begins with -", + }, + { + flag: "foo=bar", + errorMsg: "flag \"foo=bar\" contains =", + }, + } + + for _, test := range tests { + testName := fmt.Sprintf("FlagSet.Var(&v, %q, \"\")", test.flag) + + fs := NewFlagSet("", ContinueOnError) + buf := &strings.Builder{} + fs.SetOutput(buf) + + mustPanic(t, testName, test.errorMsg, func() { + var v flagVar + fs.Var(&v, test.flag, "") + }) + if msg := test.errorMsg + "\n"; msg != buf.String() { + t.Errorf("%s\n: unexpected output: expected %q, bug got %q", testName, msg, buf) + } + } +} + +func TestRedefinedFlags(t *testing.T) { + tests := []struct { + flagSetName string + errorMsg string + }{ + { + flagSetName: "", + errorMsg: "flag redefined: foo", + }, + { + flagSetName: "fs", + errorMsg: "fs flag redefined: foo", + }, + } + + for _, test := range tests { + testName := fmt.Sprintf("flag redefined in FlagSet(%q)", test.flagSetName) + + fs := NewFlagSet(test.flagSetName, ContinueOnError) + buf := &strings.Builder{} + fs.SetOutput(buf) + + var v flagVar + fs.Var(&v, "foo", "") + + mustPanic(t, testName, test.errorMsg, func() { + fs.Var(&v, "foo", "") + }) + if msg := test.errorMsg + "\n"; msg != buf.String() { + t.Errorf("%s\n: unexpected output: expected %q, bug got %q", testName, msg, buf) + } + } +} |