diff --git a/.palantir/go-version b/.palantir/go-version index 952a187d..06e64096 100644 --- a/.palantir/go-version +++ b/.palantir/go-version @@ -1 +1 @@ -go1.20.7 \ No newline at end of file +go1.21.1 \ No newline at end of file diff --git a/generated_src/internal/amalgomated_flag/example_func_test.go b/generated_src/internal/amalgomated_flag/example_func_test.go index 7c30c5e7..ac9f9858 100644 --- a/generated_src/internal/amalgomated_flag/example_func_test.go +++ b/generated_src/internal/amalgomated_flag/example_func_test.go @@ -39,3 +39,19 @@ func ExampleFunc() { // IP address to parse // {ip: , loopback: false} } + +func ExampleBoolFunc() { + fs := flag.NewFlagSet("ExampleBoolFunc", flag.ContinueOnError) + fs.SetOutput(os.Stdout) + + fs.BoolFunc("log", "logs a dummy message", func(s string) error { + fmt.Println("dummy message:", s) + return nil + }) + fs.Parse([]string{"-log"}) + fs.Parse([]string{"-log=0"}) + + // Output: + // dummy message: true + // dummy message: 0 +} diff --git a/generated_src/internal/amalgomated_flag/flag.go b/generated_src/internal/amalgomated_flag/flag.go index ef3cf29c..9d3e8d32 100644 --- a/generated_src/internal/amalgomated_flag/flag.go +++ b/generated_src/internal/amalgomated_flag/flag.go @@ -89,6 +89,7 @@ import ( "io" "os" "reflect" + "runtime" "sort" "strconv" "strings" @@ -337,6 +338,15 @@ func (f funcValue) Set(s string) error { return f(s) } func (f funcValue) String() string { return "" } +// -- boolFunc Value +type boolFuncValue func(string) error + +func (f boolFuncValue) Set(s string) error { return f(s) } + +func (f boolFuncValue) String() string { return "" } + +func (f boolFuncValue) IsBoolFlag() bool { return true } + // Value is the interface to the dynamic value stored in a flag. // (The default value is represented as a string.) // @@ -390,7 +400,8 @@ type FlagSet struct { formal map[string]*Flag args []string // arguments after flags errorHandling ErrorHandling - output io.Writer // nil means stderr; use Output() accessor + output io.Writer // nil means stderr; use Output() accessor + undef map[string]string // flags which didn't exist at the time of Set } // A Flag represents the state of a flag. @@ -481,8 +492,29 @@ func Lookup(name string) *Flag { // Set sets the value of the named flag. func (f *FlagSet) Set(name, value string) error { + return f.set(name, value) +} +func (f *FlagSet) set(name, value string) error { flag, ok := f.formal[name] if !ok { + // Remember that a flag that isn't defined is being set. + // We return an error in this case, but in addition if + // subsequently that flag is defined, we want to panic + // at the definition point. + // This is a problem which occurs if both the definition + // and the Set call are in init code and for whatever + // reason the init code changes evaluation order. + // See issue 57411. + _, file, line, ok := runtime.Caller(2) + if !ok { + file = "?" + line = 0 + } + if f.undef == nil { + f.undef = map[string]string{} + } + f.undef[name] = fmt.Sprintf("%s:%d", file, line) + return fmt.Errorf("no such flag -%v", name) } err := flag.Value.Set(value) @@ -498,7 +530,7 @@ func (f *FlagSet) Set(name, value string) error { // Set sets the value of the named command-line flag. func Set(name, value string) error { - return CommandLine.Set(name, value) + return CommandLine.set(name, value) } // isZeroValue determines whether the string represents the zero @@ -955,6 +987,20 @@ func Func(name, usage string, fn func(string) error) { CommandLine.Func(name, usage, fn) } +// BoolFunc defines a flag with the specified name and usage string without requiring values. +// 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) BoolFunc(name, usage string, fn func(string) error) { + f.Var(boolFuncValue(fn), name, usage) +} + +// BoolFunc defines a flag with the specified name and usage string without requiring values. +// 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 BoolFunc(name, usage string, fn func(string) error) { + CommandLine.BoolFunc(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 @@ -981,6 +1027,9 @@ func (f *FlagSet) Var(value Value, name string, usage string) { } panic(msg) // Happens only if flags are declared with identical names } + if pos := f.undef[name]; pos != "" { + panic(fmt.Sprintf("flag %s set at %s before being defined", name, pos)) + } if f.formal == nil { f.formal = make(map[string]*Flag) } diff --git a/generated_src/internal/amalgomated_flag/flag_test.go b/generated_src/internal/amalgomated_flag/flag_test.go index 17551684..57c88f00 100644 --- a/generated_src/internal/amalgomated_flag/flag_test.go +++ b/generated_src/internal/amalgomated_flag/flag_test.go @@ -12,6 +12,7 @@ import ( "io" "os" "os/exec" + "regexp" "runtime" "sort" "strconv" @@ -38,6 +39,7 @@ func TestEverything(t *testing.T) { Float64("test_float64", 0, "float64 value") Duration("test_duration", 0, "time.Duration value") Func("test_func", "func value", func(string) error { return nil }) + BoolFunc("test_boolfunc", "func", func(string) error { return nil }) m := make(map[string]*Flag) desired := "0" @@ -54,6 +56,8 @@ func TestEverything(t *testing.T) { ok = true case f.Name == "test_func" && f.Value.String() == "": ok = true + case f.Name == "test_boolfunc" && f.Value.String() == "": + ok = true } if !ok { t.Error("Visit: bad value", f.Value.String(), "for", f.Name) @@ -61,7 +65,7 @@ func TestEverything(t *testing.T) { } } VisitAll(visitor) - if len(m) != 9 { + if len(m) != 10 { t.Error("VisitAll misses some flags") for k, v := range m { t.Log(k, *v) @@ -85,9 +89,10 @@ func TestEverything(t *testing.T) { Set("test_float64", "1") Set("test_duration", "1s") Set("test_func", "1") + Set("test_boolfunc", "") desired = "1" Visit(visitor) - if len(m) != 9 { + if len(m) != 10 { t.Error("Visit fails after set") for k, v := range m { t.Log(k, *v) @@ -722,7 +727,7 @@ func mustPanic(t *testing.T, testName string, expected string, f func()) { case nil: t.Errorf("%s\n: expected panic(%q), but did not panic", testName, expected) case string: - if msg != expected { + if ok, _ := regexp.MatchString(expected, msg); !ok { t.Errorf("%s\n: expected panic(%q), but got panic(%q)", testName, expected, msg) } default: @@ -797,3 +802,57 @@ func TestRedefinedFlags(t *testing.T) { } } } + +func TestUserDefinedBoolFunc(t *testing.T) { + flags := NewFlagSet("test", ContinueOnError) + flags.SetOutput(io.Discard) + var ss []string + flags.BoolFunc("v", "usage", func(s string) error { + ss = append(ss, s) + return nil + }) + if err := flags.Parse([]string{"-v", "", "-v", "1", "-v=2"}); err != nil { + t.Error(err) + } + if len(ss) != 1 { + t.Fatalf("got %d args; want 1 arg", len(ss)) + } + want := "[true]" + if got := fmt.Sprint(ss); got != want { + t.Errorf("got %q; want %q", got, want) + } + // 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 BoolFunc error + flags = NewFlagSet("test", ContinueOnError) + flags.SetOutput(io.Discard) + flags.BoolFunc("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", ""}); err == nil { + t.Error("got err == nil; want err != nil") + } else if errMsg := err.Error(); !strings.Contains(errMsg, "test error") { + t.Errorf(`got %q; error should contain "test error"`, errMsg) + } +} + +func TestDefineAfterSet(t *testing.T) { + flags := NewFlagSet("test", ContinueOnError) + // Set by itself doesn't panic. + flags.Set("myFlag", "value") + + // Define-after-set panics. + mustPanic(t, "DefineAfterSet", "flag myFlag set at .*/flag_test.go:.* before being defined", func() { + _ = flags.String("myFlag", "default", "usage") + }) +} diff --git a/go.mod b/go.mod index 8d03c767..cb871d52 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/palantir/godel-okgo-asset-ineffassign -go 1.20 +go 1.21 require ( github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8 diff --git a/go.sum b/go.sum index 82453455..15b4629b 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,7 @@ github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8 h1:PVRE9d4AQKmbelZ7emNig1+NT27DUmKZn5qXxfio54U= github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -33,6 +34,7 @@ github.com/klauspost/pgzip v1.2.1 h1:oIPZROsWuPHpOdMVWLuJZXwgjhrW8r1yEX8UqMyeNHM github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -69,6 +71,7 @@ github.com/palantir/pkg v1.1.0/go.mod h1:KC9srP/9ssWRxBxFCIqhUGC4Jt7OJkWRz0Iqehu github.com/palantir/pkg/cobracli v1.2.0 h1:hANp5fUB5cX90SVri97Apz4xB3BqnZw0gP2jMQ34G8Y= github.com/palantir/pkg/cobracli v1.2.0/go.mod h1:8poQy1FM1lISezW7Czmhmj7I0xEVpuYpv/ywgsAzv0Y= github.com/palantir/pkg/gittest v1.2.0 h1:RPINGtLh+Mlt+o1M/nRQlTPrt4CP5tJOSMgeB5vxerM= +github.com/palantir/pkg/gittest v1.2.0/go.mod h1:z2eq/rEzUDmNR8rvJCb+/XZ7w9/nxRCAKvYjMYW/w/w= github.com/palantir/pkg/matcher v1.2.0 h1:h4IeYPSQGWIdi1Qh7QSzWATv0+2coTaaCiozYtPWBks= github.com/palantir/pkg/matcher v1.2.0/go.mod h1:JUH9L+Cmjv2U87y+1Ov5KKLmMbgHtESCTrPq5MyWeVM= github.com/palantir/pkg/pkgpath v1.2.0 h1:6JDFKFZZsiVBiek47OB7363oIAIjQNFkTsPoEtJpWzY= @@ -110,6 +113,7 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -137,6 +141,7 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T 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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=