diff --git a/README.md b/README.md index 47bf1a2..89cc950 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ $ cat > gopher.go import ( "fmt" - "github.com/namsral/flag" + "github.com/robert-zaremba/flag" ) func main() { @@ -83,7 +83,7 @@ It's intended for projects which require a simple configuration made available t Example: ```go -import "github.com/namsral/flag" +import "github.com/robert-zaremba/flag" flag.String(flag.DefaultConfigFlagname, "", "path to config file") flag.Int("age", 24, "help message for age") @@ -161,7 +161,7 @@ age=33 For more examples see the [examples][] directory in the project repository. -[examples]: https://github.com/namsral/flag/tree/master/examples +[examples]: https://github.com/robert-zaremba/flag/tree/master/examples That's it. diff --git a/examples/gopher.go b/examples/gopher.go index 1030005..d7ba079 100644 --- a/examples/gopher.go +++ b/examples/gopher.go @@ -1,7 +1,7 @@ package main import ( - "github.com/namsral/flag" + "github.com/robert-zaremba/flag" "fmt" ) diff --git a/export_test.go b/export_test.go deleted file mode 100644 index 12d3dc7..0000000 --- a/export_test.go +++ /dev/null @@ -1,17 +0,0 @@ -// 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 "os" - -// Additional routines compiled into the package only during testing. - -// 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) - Usage = usage -} diff --git a/extras.go b/extras.go index f937899..8f4d110 100644 --- a/extras.go +++ b/extras.go @@ -152,7 +152,8 @@ func (f *FlagSet) ParseFile(path string) error { f.usage() return ErrHelp } - return f.failf("configuration variable provided but not defined: %s", name) + // ignore if extra flag is defined in the config file + continue } if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() { // special case: doesn't need an arg @@ -177,9 +178,5 @@ func (f *FlagSet) ParseFile(path string) error { f.actual[name] = flag } - if err := scanner.Err(); err != nil { - return err - } - - return nil + return scanner.Err() } diff --git a/extras_test.go b/extras_test.go index 8c378ed..0b32316 100644 --- a/extras_test.go +++ b/extras_test.go @@ -10,9 +10,17 @@ import ( "testing" "time" - . "github.com/namsral/flag" + . "github.com/robert-zaremba/flag" ) +// 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) + Usage = usage +} + // Test parsing a environment variables func TestParseEnv(t *testing.T) { diff --git a/flag.go b/flag.go index ac9d78a..3e985ea 100644 --- a/flag.go +++ b/flag.go @@ -871,7 +871,7 @@ func (f *FlagSet) parseOne() (bool, error) { } } m := f.formal - flag, alreadythere := m[name] // BUG + flag, alreadythere := m[name] if !alreadythere { if name == "help" || name == "h" { // special case for nice help message. f.usage() diff --git a/flag_test.go b/flag_test.go index 65ad4e9..14fa98e 100644 --- a/flag_test.go +++ b/flag_test.go @@ -13,7 +13,7 @@ import ( "testing" "time" - . "github.com/namsral/flag" + . "github.com/robert-zaremba/flag" ) func boolString(s string) string { @@ -379,6 +379,16 @@ func TestHelp(t *testing.T) { } } +const defaultOutput2 = " -A=false: for bootstrapping, allow 'any' type\n" + + " -Alongflagname=false: disable bounds checking\n" + + " -C=true: a boolean defaulting to true\n" + + " -D=\"\": set relative `path` for local imports\n" + + " -F=2.7: a non-zero `number`\n" + + " -G=0: a float that defaults to zero\n" + + " -N=27: a non-zero int\n" + + " -Z=0: an int that defaults to zero\n" + + " -maxT=0s: set `timeout` for dial\n" + const defaultOutput = ` -A for bootstrapping, allow 'any' type -Alongflagname disable bounds checking @@ -413,6 +423,6 @@ func TestPrintDefaults(t *testing.T) { fs.PrintDefaults() got := buf.String() if got != defaultOutput { - t.Errorf("got %q want %q\n", got, defaultOutput) + t.Errorf("got\n%q want\n%q\n", got, defaultOutput) } } diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1293227 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/robert-zaremba/flag + +go 1.13 + +require ( + github.com/robert-zaremba/checkers v1.0.1 + github.com/robert-zaremba/errstack v1.0.2 + github.com/robert-zaremba/go-bat v1.0.1 + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1fab869 --- /dev/null +++ b/go.sum @@ -0,0 +1,29 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= +github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +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/mozillazg/go-unidecode v0.1.1 h1:uiRy1s4TUqLbcROUrnCN/V85Jlli2AmDF6EeAXOeMHE= +github.com/mozillazg/go-unidecode v0.1.1/go.mod h1:fYMdhyjni9ZeEmS6OE/GJHDLsF8TQvIVDwYR/drR26Q= +github.com/namsral/flag v1.7.4-pre h1:b2ScHhoCUkbsq0d2C15Mv+VU8bl8hAXV8arnWiOHNZs= +github.com/namsral/flag v1.7.4-pre/go.mod h1:OXldTctbM6SWH1K899kPZcf65KxJiD7MsceFUpB5yDo= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/robert-zaremba/checkers v1.0.1 h1:AjxF5P+YkOeWsvFSbTcdk/0lvNDDdkzaM3HrEc4XSEE= +github.com/robert-zaremba/checkers v1.0.1/go.mod h1:wUVuqhZje9IKym5bZuW1nbA0GqRRgnYTMehly17F56Q= +github.com/robert-zaremba/errstack v1.0.2 h1:WSNVsQnd3YFLDBW1FysLkJxHkbRsbpgaYk7R8gBJyRQ= +github.com/robert-zaremba/errstack v1.0.2/go.mod h1:KCGDqMDzP5xgeKXM33WryQ8+Zj7EaxnP+J6t3OBhU14= +github.com/robert-zaremba/errstack v3.1.0+incompatible h1:wLAs0u9qr7u4s9UVtCAhx1I4dagUVx5d/MuYhK9sNr4= +github.com/robert-zaremba/errstack v3.1.0+incompatible/go.mod h1:YiMUuTTLstXOe9Ao1J0w0eTf8CaGS+q4WDIJaypSwVI= +github.com/robert-zaremba/go-bat v1.0.1 h1:5C/UryVjj0sKPvT4UPCI2gaIrsXR7sXASMM45ZxCcKo= +github.com/robert-zaremba/go-bat v1.0.1/go.mod h1:GYLn+EyFubUfo/yPrQsma+cri4vCjRC+Qlovomu6sWw= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/setup.go b/setup.go new file mode 100644 index 0000000..3f8f8bb --- /dev/null +++ b/setup.go @@ -0,0 +1,30 @@ +// 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 + +import ( + "fmt" + "os" +) + +// Setup parses flag and setups usage function. +// `positionalArgs` is a string representing positional args, eg: "arg1 arg2 arg3" +// `commands` if provided is a positional argument command description. +func Setup(version, positionalArgs string, commands ...string) { + Usage = func() { + fmt.Fprintln(os.Stderr, "Version: ", version) + if len(commands) != 0 { + fmt.Fprintf(os.Stderr, + "USAGE: %s %s\n\n", os.Args[0], positionalArgs) + fmt.Fprint(os.Stderr, "COMMANDS:", commands[0], "\n\n") + } else { + fmt.Fprintf(os.Stderr, + "USAGE: %s %s\n\n", os.Args[0], positionalArgs) + } + fmt.Fprintln(os.Stderr, "PARAMETERS:") + PrintDefaults() + } + Parse() +} diff --git a/srv.go b/srv.go new file mode 100644 index 0000000..4c47af8 --- /dev/null +++ b/srv.go @@ -0,0 +1,73 @@ +package flag + +import ( + "flag" + "net/url" + "os" + + "github.com/robert-zaremba/errstack" + bat "github.com/robert-zaremba/go-bat" +) + +// URL is a structure containing network connection details to a service +// The value should have the following structure: +// //username:password@host:port/directory +type URL struct { + url.URL +} + +// Set implements github.com/robert-zaremba/flag Value interface +func (a *URL) Set(value string) error { + u, err := url.Parse(value) + if err != nil { + return errstack.WrapAsReq(err, "Can't parse network address config") + } + a.URL = *u + return nil +} + +// Path represents a file in a filesystem +type Path struct { + Path string +} + +// Set implements github.com/robert-zaremba/flag Value interface +func (a *Path) Set(filePath string) error { + a.Path = filePath + return a.Check() +} + +// String implements github.com/robert-zaremba/flag Value interface +func (a *Path) String() string { + return a.Path +} + +// Check returns an error if it can't find the file +func (a Path) Check() error { + if a.Path == "" { + return errstack.NewReq("File path can't be empty") + } + _, err := os.Stat(a.Path) + return err +} + +// SrvFlags represents common server flags +type SrvFlags struct { + Production *bool + Port *string +} + +// NewSrvFlags setups common server flags +func NewSrvFlags() SrvFlags { + return SrvFlags{ + flag.Bool("production", false, "Run in production mode"), + flag.String("port", "8000", "The HTTP listening port"), + } +} + +// Check validates the flags. It may panic! +func (f SrvFlags) Check() error { + errb := errstack.NewBuilder() + bat.Atoi64Errp(*f.Port, errb.Putter("port")) + return errb.ToReqErr() +} diff --git a/srv_test.go b/srv_test.go new file mode 100644 index 0000000..7332efe --- /dev/null +++ b/srv_test.go @@ -0,0 +1,54 @@ +package flag + +import ( + "testing" + + rzcheck "github.com/robert-zaremba/checkers" + gocheck "gopkg.in/check.v1" +) + +// Hook up gocheck into the "go test" runner. +func Test(t *testing.T) { gocheck.TestingT(t) } + +type ExtraSuite struct{} + +func init() { + gocheck.Suite(&ExtraSuite{}) +} + +func (s *ExtraSuite) TestValidateDirExists(c *gocheck.C) { + ff := Path{} + err := ff.Set("/tmp") + c.Assert(err, gocheck.IsNil) + c.Assert(ff.String(), gocheck.Equals, "/tmp") +} + +func (s *ExtraSuite) TestValidatePermissionDenied(c *gocheck.C) { + ff := Path{} + _ = ff.Set("/root/secret") + err := ff.Check() + c.Assert(err, gocheck.ErrorMatches, "stat /root/secret: permission denied") +} + +func (s *ExtraSuite) TestValidateNoFile(c *gocheck.C) { + ff := Path{} + _ = ff.Set("") + err := ff.Check() + c.Assert(err, rzcheck.ErrorContains, "File path can't be empty") +} + +func (s *ExtraSuite) TestHandleBadDefaultWithPanic(c *gocheck.C) { + ff := Path{"hello-world"} + err := ff.Check() + c.Assert(err, gocheck.ErrorMatches, "stat hello-world: no such file or directory") +} + +func (s *ExtraSuite) TestHandleDefault(c *gocheck.C) { + ff := Path{"/tmp"} + c.Assert(ff.String(), gocheck.Equals, "/tmp") + ff = Path{"/"} + c.Assert(ff.String(), gocheck.Equals, "/") + err := ff.Set("/tmp") + c.Assert(err, gocheck.IsNil) + c.Assert(ff.String(), gocheck.Equals, "/tmp") +} diff --git a/validate.go b/validate.go new file mode 100644 index 0000000..97b952f --- /dev/null +++ b/validate.go @@ -0,0 +1,37 @@ +package flag + +import ( + "flag" + "fmt" + "strings" +) + +// Checker is an interface for type which has a Check function +type Checker interface { + Check() error +} + +// Validate runs flag checkers +func Validate(positionalArgs string, checkers ...Checker) error { + expected := strings.Fields(positionalArgs) + if len(expected) != flag.NArg() { + return fmt.Errorf("missing required positional arguments: %s. Number of args Provided: %d, expected: %d", + expected, flag.NArg(), len(expected)) + } + for _, c := range checkers { + if err := c.Check(); err != nil { + return err + } + } + return nil +} + +// CheckMany is a helper function to check many flag components +func CheckMany(checkers ...Checker) error { + for _, c := range checkers { + if err := c.Check(); err != nil { + return err + } + } + return nil +}