diff --git a/Makefile b/Makefile index ee330e8..b241fe8 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,15 @@ -tests: +build: + go generate ./... + go build + +tests: build go test -v ./... -lint: +lint: build golangci-lint run -cover: +cover: build go test ./... -coverprofile cover.out go tool cover -func cover.out -.phony: tests cover +.phony: build tests lint cover diff --git a/README.md b/README.md index 9770b84..0d3b97f 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,57 @@ cmd := cli.NewCommand( If you run this command, `cmd.Run([]string{})`, it will print “hello, world!”. -The `interface{}` argument is where the action gets its arguments. To supply command arguments, you must define a type and one more function. +If you want to make it a complete program, you just put the code in `main` and use `os.Args[1:]` as the arguments: + +```go +package main + +import ( + "fmt" + "os" + + "github.com/mailund/cli" +) + +func main() { + cmd := cli.NewCommand( + cli.CommandSpec{ + Name: "hello", + Long: "Prints hello world", + Action: func(_ interface{}) { fmt.Println("hello, world!") }, + }) + + cmd.Run(os.Args[1:]) +} +``` + +I changed the `Short` paramemter to `Long` here, since that is what `cli` uses to give full information about a command, rather than listing what subcommands do, but it wouldn't matter in this case. If `Long` isn't provided, it will use `Short`. + +If you run the program without any arguments, you get `"hello, world!"` back. Assuming you compiled the program into the executable `hello`, you get: + +```sh +> hello +hello, world! +``` + +If you call the program with the `-h` or `-help` flag, you get the following usage information: + +```sh +> hello -h +Usage: hello [options] + +Prints hello world + +Options: + -help + show help for hello +``` + +If you call it with an unkown option, so anything but the help flag, or with any arguments, you get an error message and the same usage information. As we have specified the command, it doesn't take any arguments, so `cli` consider it an error if it gets any. + +## Command arguments + +The `interface{}` argument to the `Action` is where the action gets its arguments. To supply command arguments, you must define a type and one more function. Let’s say we want a command that adds two numbers. For that, we need to arguments, and we will make them positional. We create a type for the arguments that looks like this: @@ -154,6 +204,40 @@ cmd := cli.NewCommand( Simply setting `argv.Round = true` before we return from `Init` will make `true` the default value for the flag. +You can use any of the types `string`, `bool`, `int`, `uint`, `int8`, `int16`, `int32`, `int64`, `uint8`, `uint16`, `uint32`, `uint64`, `float32`, `float64`, `complex64` and `complex128` for flags and positional arguments. That's all the non-composite types except `uintptr` and unsafe pointers, which I wouldn't know how to handle generically... + +Slice types with the same underlying types will be consider variadic arguments, and you can use those for positional arguments as long as there is only one variadic parameter per command, and provided that the command does not have sub-commands (see below). + +Any type that implements the interface + +```go +type PosValue interface { + Set(string) error +} +``` + +for positional values or + +```go +type FlagValue interface { + String() string + Set(string) error +} +``` + +as pointer-receivers will also work. Types that implement + +```go +type VariadicValue interface { + Set([]string) error +} +``` + +can be used as variadic parameters. + + +## Subcommands + You can nest commands to make subcommands. Say we want a tool that can do both addition and multiplication. We can create a command with two subcommands to achieve this. The straightforward way to do this is to give a command a `Subcommands` in its specification. It can look like this: ```go @@ -193,7 +277,107 @@ calc := cli.NewCommand( }) ``` -Now `calc` has two subcommands, and you can invoke addition with `calc.Run([]string{"add", "2", "3"})` and multiplication with `calc.Run([]string{"must", "2", "3"})`. On the command line it would, of course, look like: +Now `calc` has two subcommands, and you can invoke addition with `calc.Run([]string{"add", "2", "3"})` and multiplication with `calc.Run([]string{"must", "2", "3"})`. + +Turn it into a complete program: + +```go +package main + +import ( + "fmt" + "os" + + "github.com/mailund/cli" +) + +type CalcArgs struct { + X int `pos:"x" descr:"first addition argument"` + Y int `pos:"y" descr:"first addition argument"` +} + +func main() { + add := cli.NewCommand( + cli.CommandSpec{ + Name: "add", + Short: "adds two floating point arguments", + Long: "", + Init: func() interface{} { return new(CalcArgs) }, + Action: func(args interface{}) { + fmt.Printf("Result: %d\n", args.(*CalcArgs).X+args.(*CalcArgs).Y) + }, + }) + + mult := cli.NewCommand( + cli.CommandSpec{ + Name: "mult", + Short: "multiplies two floating point arguments", + Long: "", + Init: func() interface{} { return new(CalcArgs) }, + Action: func(args interface{}) { + fmt.Printf("Result: %d\n", args.(*CalcArgs).X*args.(*CalcArgs).Y) + }, + }) + + calc := cli.NewCommand( + cli.CommandSpec{ + Name: "calc", + Short: "does calculations", + Long: "", + Subcommands: []*cli.Command{add, mult}, + }) + + calc.Run(os.Args[1:]) +} +``` + +compile it into an executable called `calc`, and then on the command line you can get information about how to use it using the `-h` flag + +```sh +> calc -h +Usage: calc [options] cmd ... + + + +Options: + -help + show help for calc + +Arguments: + cmd + sub-command to call + ... + argument for sub-commands + +Commands: + add + adds two floating point arguments + mult + multiplies two floating point arguments +``` + +You get the long description (which doesn't say much here, admittedly), then the options and arguments, since the `calc` command has subcommands those are `cmd` and `...`, and a list of the subcommands with their short description. + +You can get help about the subcommands by providing those with the `-h` flag (or calling them incorrectly). To get help about `add`, we can use: + +```sh +> calc add -h +Usage: add [options] x y + + + +Options: + -help + show help for add + +Arguments: + x + first addition argument + y + first addition argument +``` + +To invoke the commands, you do what experience has taught you and type the subcommand names after the main command: ```sh > calc add 2 3 @@ -271,3 +455,142 @@ menu/bar/y ``` so it all works out. + +## Callbacks + +Instead of using parameters as values to set, you can install callback functions that will be called when the user provide a flag, or called on positional arguments. There are six types of functions you can use as callbacks, in different situations. + +```go +type ( + BCB = func() + BCBI = func(interface{}) + + BCBE = func() error + BCBIE = func(interface{}) error + + CB = func(string) error + CBI = func(string, interface{}) error + + VCB = func([]string) error + VCBI = func([]string, interface{}) error +) +``` + +The first four, `BCB`, `BCBI`, `BCBE` and `BCBIE` are "boolean" callbacks, in the sense that they work like boolean flags. Those are command line flags that do not take any arguments, and they can only be used as flags. When they are, a flag, `-f` without arguments, will call the function. A `BCB` and `BCBE` flag will be called without arguments, of course, and a `BCBI` or `BCBIE` function will be called with the command line's argument structure, as returned from the `Init` function. The `-E` functions return an error and the other two do not. Callbacks that do not receive any input might not want to implement error handling, so `cli` support both boolean callbacks with and without an error return value. The functions that receive input should all return an `error` (although I might change that in the future). + +The `CB` and `CBI` functions work with both flags and positional arguments. For flats, `-f=arg` or `-f arg`, the `arg` string is passed as the first argument, and for positional arguments, whatever argument is on the command line will be the callback's argument. They differ only in the second argument to functions of type `CBI`, which will be the command's argument-struct when the function is called (so values set before the function is called can be found in the struct, but flags and positional arguments that come after will not have been set yet). + +The `VCB` and `VCBI` functions work the same as `CB` and `CBI` but take multiple arguments in the form of a string slice and can only be used for variadic parameters. + +The example below shows callbacks and `Value` interfaces in action. We define a type, `Validator` that holds a string that must be validated according to some rules that are quite simple in this example, but you might be able to imagine something more complex. + +We have two rules for validating the string in `Validator`, we might require that it is all uppercase or all lowercase, and we have functions to check this, and two methods for setting the rule. If we don't set a rule, then any string is valid. + +If we implement the `PosValue` interface mentioned above, i.e. we implement a `Set(string) error` method, then we can use a `Validator` a positional argument, so we do that. + +After that, we can make the type for command line arguments. We have a positional argument for the `Validator`, so it will get a positional argument, and we provide two flags to choose between the validation rules. If we don't provide a flag, we get the default (no checking), if we use `-u` we validate that the positional argument is all uppercase, and if we provide `-l` we validate that the argument is all lowercase. + +The two callbacks have signature `func()` (the `BCB` type above), so they are valid types for `cli`. We put the methods from the validator the arguments holds in the fields. When we write `args.Val.setUpperValidator` we have already bound the receiver, so the method becomes a function that doesn't take any arguments, and thus we can use it as the field in the structure. + +```go +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/mailund/cli" + "github.com/mailund/cli/interfaces" +) + +type Validator struct { + validator func(string) bool + x string +} + +func upperValidator(x string) bool { return strings.ToUpper(x) == x } +func lowerValidator(x string) bool { return strings.ToLower(x) == x } + +func (val *Validator) setUpperValidator() { + val.validator = upperValidator +} + +func (val *Validator) setLowerValidator() { + val.validator = lowerValidator +} + +// Implementing Set(string) error means we can use a Validator as a +// positional argument +func (val *Validator) Set(x string) error { + if val.validator != nil && !val.validator(x) { + return interfaces.ParseErrorf("'%s' is not a valid string", x) + } + val.x = x + return nil +} + +type Args struct { + Upper func() `flag:"u" descr:"sets the validator to upper"` + Lower func() `flag:"l" descr:"sets the validator to lower"` + Val Validator `pos:"string" descr:"string we might do something to, if valid"` +} + +// Now we have everything ready to set up the command. +func Init() interface{} { + args := Args{} + args.Upper = args.Val.setUpperValidator + args.Lower = args.Val.setLowerValidator + return &args +} + +func Action(args interface{}) { + fmt.Println("A valid string:", args.(*Args).Val.x) +} + +func main() { + cmd := cli.NewCommand( + cli.CommandSpec{ + Name: "validator", + Long: "Will only accept valid strings", + Init: Init, + Action: Action, + }) + cmd.Run(os.Args[1:]) +} +``` + +Run it with `-h` to see a usage message: + +```sh +> validate -h +Usage: validator [options] string + +Will only accept valid strings + +Options: + -help show help for validator + -l sets the validator to lower + -u sets the validator to upper + +Arguments: + string + string we might do something to, if valid +``` + +Without any flags, the program will accept any string: + +```sh +> validate Foo +A valid string: Foo +``` + +but set a validator, and it will complain + +```sh +validate -l Foo +Error parsing parameter string='Foo', 'Foo' is not a valid string. + +> validate -l foo +A valid string: foo +``` diff --git a/commands.go b/commands.go index 7dca8a2..83aa08d 100644 --- a/commands.go +++ b/commands.go @@ -8,7 +8,8 @@ import ( "sort" "github.com/mailund/cli/internal/failure" - "github.com/mailund/cli/params" + "github.com/mailund/cli/internal/params" + "github.com/mailund/cli/internal/vals" ) // CommandSpec defines a commandline command or subcommand @@ -141,12 +142,11 @@ func NewCommandError(spec CommandSpec) (*Command, error) { //nolint:gocritic // CommandSpec: spec, flags: flag.NewFlagSet(spec.Name, flag.ExitOnError), params: params.NewParamSet(spec.Name, flag.ExitOnError)} - allowVariadic := len(spec.Subcommands) == 0 // only allow variadics if we don't have subcommands if spec.Init != nil { cmd.argv = spec.Init() - if err := connectSpecsFlagsAndParams(cmd.flags, cmd.params, cmd.argv, allowVariadic); err != nil { + if err := connectSpecsFlagsAndParams(cmd, cmd.argv); err != nil { return nil, err } } @@ -171,8 +171,8 @@ func NewCommandError(spec CommandSpec) (*Command, error) { //nolint:gocritic // cmd.subcommands[sub.Name] = sub } - cmd.params.StringVar(&cmd.command, "cmd", "sub-command to call") - cmd.params.VariadicStringVar(&cmd.cmdArgs, "...", "argument for sub-commands", 0) + cmd.params.Var((*vals.StringValue)(&cmd.command), "cmd", "sub-command to call") + cmd.params.VariadicVar((*vals.VariadicStringValue)(&cmd.cmdArgs), "...", "argument for sub-commands", 0) } return cmd, nil diff --git a/commands_test.go b/commands_test.go index 9d52615..d666fca 100644 --- a/commands_test.go +++ b/commands_test.go @@ -2,6 +2,7 @@ package cli_test import ( "flag" + "reflect" "regexp" "strings" "testing" @@ -20,7 +21,7 @@ func TestNewCommand(t *testing.T) { func TestNewCommandError(t *testing.T) { type Invalid struct { - X complex128 `flag:"x"` + X uintptr `flag:"x"` } _, err := cli.NewCommandError(cli.CommandSpec{ @@ -56,7 +57,7 @@ long Options: -foo - string set foo + value set foo -help show help for name @@ -90,7 +91,7 @@ short Options: -foo - string set foo + value set foo -help show help for name @@ -148,7 +149,7 @@ func TestOption(t *testing.T) { t.Error("The command shouldn't be called") } - if errmsg := builder.String(); !strings.HasPrefix(errmsg, `invalid value "foo" for flag -x: parse error`) { + if errmsg := builder.String(); !strings.HasPrefix(errmsg, `invalid value "foo" for flag -x: argument "foo" cannot be parsed as int`) { t.Errorf("Unexpected error msg: %s", errmsg) } @@ -318,7 +319,7 @@ func TestCommandPanic(t *testing.T) { defer func() { _ = recover() }() type Invalid struct { - X complex128 `pos:"invalid type"` + X uintptr `pos:"invalid type"` } _ = cli.NewCommand(cli.CommandSpec{ @@ -385,3 +386,58 @@ func TestVariadicArgsWithSubcommands(t *testing.T) { t.Errorf("Unexpected error message: '%s'\n", err.Error()) } } + +type ValueInterface struct { + x string + i int +} + +func (val *ValueInterface) Set(s string) error { + val.x = s + val.i = len(s) + + return nil +} + +func (val *ValueInterface) String() string { + return val.x +} + +type VariadicValueInterface struct { + x []string + i int +} + +func (val *VariadicValueInterface) Set(xs []string) error { + val.x = xs + val.i = len(xs) + + return nil +} + +func TestValueInterface(t *testing.T) { + var args = struct { + Flag ValueInterface `flag:"value"` + Pos ValueInterface `pos:"value"` + Var VariadicValueInterface `pos:"var"` + }{} + + cmd := cli.NewCommand( + cli.CommandSpec{ + Init: func() interface{} { return &args }, + }) + + cmd.Run([]string{"--value=foobar", "qux", "the", "rest", "of", "the", "args"}) + + if args.Flag.x != "foobar" || args.Flag.i != 6 { + t.Error("The flag wasn't set correctly") + } + + if args.Pos.x != "qux" || args.Pos.i != 3 { + t.Error("The positional wasn't set correctly") + } + + if args.Var.i != 5 || !reflect.DeepEqual(args.Var.x, []string{"the", "rest", "of", "the", "args"}) { + t.Error("Variadic argument wasn't set correctly") + } +} diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..90c27fe --- /dev/null +++ b/go.sum @@ -0,0 +1,29 @@ +github.com/joncalhoun/pipe v0.0.0-20170510025636-72505674a733 h1:/wtaMDeVpoAUkqZl/GT3lvM9nDBmRApu/Uvl7EUc9Ao= +github.com/joncalhoun/pipe v0.0.0-20170510025636-72505674a733/go.mod h1:2MNFZhLx2HMHTN4xKH6FhpoQWqmD8Ato8QOE2hp5hY4= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.4 h1:cVngSRcfgyZCzys3KYOpCFa+4dqX/Oub9tAq00ttGVs= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/params/errors.go b/interfaces/errors.go similarity index 50% rename from params/errors.go rename to interfaces/errors.go index b3ea79b..eed0167 100644 --- a/params/errors.go +++ b/interfaces/errors.go @@ -1,4 +1,4 @@ -package params +package interfaces import "fmt" @@ -18,3 +18,20 @@ func ParseErrorf(format string, args ...interface{}) *ParseError { func (err *ParseError) Error() string { return err.Message } + +// SpecError is the error type returned if there are problems with a +// specification +type SpecError struct { + Message string +} + +// SpecErrorf creates a SpecError from a format string and arguments +func SpecErrorf(format string, args ...interface{}) *SpecError { + return &SpecError{fmt.Sprintf(format, args...)} +} + +// Error returns a string representation of a SpecError, implementing +// the error interface. +func (err *SpecError) Error() string { + return err.Message +} diff --git a/interfaces/inter.go b/interfaces/inter.go new file mode 100644 index 0000000..1bde0cb --- /dev/null +++ b/interfaces/inter.go @@ -0,0 +1,19 @@ +// interfaces implements public interfaces for functionality buried +// deeper in the internal packages +package interfaces + +// PosValue is the interface that positional arguments must implement +type PosValue interface { + Set(string) error +} + +// VariadicValue is the interface for variadic positional arguments +type VariadicValue interface { + Set([]string) error +} + +// FlagValue is the interface that flag arguments must implement +type FlagValue interface { + String() string + Set(string) error +} diff --git a/params/params.go b/internal/params/params.go similarity index 100% rename from params/params.go rename to internal/params/params.go diff --git a/internal/params/paramset.go b/internal/params/paramset.go new file mode 100644 index 0000000..54b7d8f --- /dev/null +++ b/internal/params/paramset.go @@ -0,0 +1,256 @@ +package params + +import ( + "flag" + "fmt" + "io" + "os" + "strings" + + "github.com/mailund/cli/interfaces" + "github.com/mailund/cli/internal/failure" +) + +type ( + /* ErrorHandling flags for determining how the argument + parser should handle errors. + + The error handling works the same as for the flag package, + except that ParamSet's only support two flags: + + - ExitOnError: Terminate the program on error. + - ContinueOnError: Report the error as an error object. + */ + ErrorHandling = flag.ErrorHandling +) + +const ( + // ContinueOnError means that parsing will return an error + // rather than terminate the program. + ContinueOnError = flag.ContinueOnError + // ExitOnError means that parsing will exit the program + // (using os.Exit(2)) if the parser fails. + ExitOnError = flag.ExitOnError + // PanicOnError means we raise a panic on errors + PanicOnError = flag.PanicOnError +) + +// Param holds positional parameter information. +type Param struct { + // Name is the short name used for a parameter + Name string + // Desc is a short description of the parameter + Desc string + // Encapsulated value + Value interfaces.PosValue +} + +// VariadicParam holds information about a variadic argument. +type VariadicParam struct { + // Name is the short name used for a parameter + Name string + // Desc is a short description of the parameter + Desc string + // Min is the minimum number of parameters that the variadic + // parameter takes. + Min int + // Encapsulated value + Value interfaces.VariadicValue +} + +// ParamSet contains a list of specified parameters for a +// commandline tool and parsers for parsing commandline +// arguments. +type ParamSet struct { + // Name is a name used when printing usage information + // for a parameter set. + Name string + // Usage is a function you can assign to for changing + // the help info. + Usage func() + // ErrorFlag Controls how we deal with parsing errors + ErrorFlag ErrorHandling + + params []*Param + out io.Writer + + // last parameter, used for variadic arguments + last *VariadicParam +} + +// SetFlag sets the error handling flag. +func (p *ParamSet) SetFlag(f ErrorHandling) { + p.ErrorFlag = f +} + +// SetOutput specifies where usage output and error messages should +// be written to. +// +// Parameters: +// - out: a writer object. The default, if you do not set a new +// variable is os.Stderr. +func (p *ParamSet) SetOutput(out io.Writer) { p.out = out } + +// Output returns the output stream where messages are written to +func (p *ParamSet) Output() io.Writer { return p.out } + +// PrintDefaults prints a description of the parameters +func (p *ParamSet) PrintDefaults() { + if p.NParams() == 0 && p.last == nil { + return // nothing to print... + } + + fmt.Fprintf(p.Output(), "Arguments:\n") + + for _, par := range p.params { + fmt.Fprintf(p.Output(), " %s\n\t%s\n", par.Name, par.Desc) + } + + if p.last != nil { + fmt.Fprintf(p.Output(), " %s\n\t%s\n", p.last.Name, p.last.Desc) + } +} + +// NParams returns the number of parameters in the set, excluding the last variadic +// parameter if there is one. You can test for whether there is a variadic argument using +// p.Variadic() != nil. +func (p *ParamSet) NParams() int { + return len(p.params) +} + +// Param returns the parameter at position i. +func (p *ParamSet) Param(i int) *Param { + return p.params[i] +} + +// Variadic returns the last variadic parameter, if there is one. +func (p *ParamSet) Variadic() *VariadicParam { + return p.last +} + +// NewParamSet creates a new parameter set. +// +// Parameters: +// - name: The name for the parameter set. Used when printing +// usage and error information. +// - errflat: Controls the error handling. If ExitOnError, parse +// errors will terminate the program (os.Exit(1)); if ContinueOnError +// the parsing will return an error instead. +func NewParamSet(name string, errflag ErrorHandling) *ParamSet { + argset := &ParamSet{ + Name: name, + ErrorFlag: errflag, + params: []*Param{}, + out: os.Stderr} + argset.Usage = argset.PrintDefaults + + return argset +} + +// ShortUsage returns a string used for printing the usage +// of a parameter set. +func (p *ParamSet) ShortUsage() string { + names := make([]string, len(p.params)) + for i, param := range p.params { + names[i] = param.Name + } + + namesUsage := strings.Join(names, " ") + + if p.last != nil { + namesUsage += " " + p.last.Name + } + + return namesUsage +} + +// Parse parses arguments against parameters. +// +// Parameters: +// - args: A slice of strings from a command line. +// +// If the parameter set has error handling flag ExitOnError, +// a parsing error will terminate the program (os.Exit(1)). +// If the parameter set has error handling flag ContinueOnError, +// it will return an error instead. If all goes well, it will +// return nil. +func (p *ParamSet) Parse(args []string) error { + minParams := len(p.params) + if p.last != nil { + minParams += p.last.Min + } + + if len(args) < minParams { + if p.ErrorFlag == ExitOnError { + fmt.Fprintf(p.Output(), + "Too few arguments for command '%s'\n\n", + p.Name) + p.Usage() + failure.Failure() + } + + return interfaces.ParseErrorf("too few arguments") + } + + if p.last == nil && len(args) > len(p.params) { + if p.ErrorFlag == ExitOnError { + fmt.Fprintf(p.Output(), + "Too many arguments for command '%s'\n\n", + p.Name) + p.Usage() + failure.Failure() + } + + return interfaces.ParseErrorf("too many arguments") + } + + for i, par := range p.params { + if err := par.Value.Set(args[i]); err != nil { + if p.ErrorFlag == flag.ExitOnError { + fmt.Fprintf(p.Output(), "Error parsing parameter %s='%s', %s.\n", + par.Name, args[i], err.Error()) + failure.Failure() + } + + return interfaces.ParseErrorf("error parsing parameter %s='%s'", par.Name, args[i]) + } + } + + if p.last != nil { + rest := args[len(p.params):] + if err := p.last.Value.Set(rest); err != nil { + if p.ErrorFlag == ExitOnError { + fmt.Fprintf(p.Output(), "Error parsing parameters %s='%v', %s.\n", + p.last.Name, rest, err.Error()) + failure.Failure() + } + + return interfaces.ParseErrorf("error parsing parameters %s='%v'", p.last.Name, rest) + } + } + + return nil +} + +// Var adds a new PosValue variable to the parameter set. +// +// Parameters: +// - val: a variable where the parsed argument should be written. +// - name: Name of the argument, used when printing usage. +// - desc: Description of the argument. Used when printing usage. +func (p *ParamSet) Var(val interfaces.PosValue, name, desc string) { + p.params = append(p.params, &Param{name, desc, val}) +} + +// VariadicVar install a variadic argument +// as the last parameter(s) for the parameter set. +// +// Parameters: +// - val: A variable that will hold the parsed arguments. +// - name: Name of the argument, used when printing usage. +// - desc: Description of the argument. Used when printing usage. +// - min: The minimum number of arguments that the command line must +// have for this parameter. +func (p *ParamSet) VariadicVar(val interfaces.VariadicValue, name, desc string, min int) { + p.last = &VariadicParam{Name: name, Desc: desc, Min: min, Value: val} +} diff --git a/params/paramset_test.go b/internal/params/paramset_test.go similarity index 79% rename from params/paramset_test.go rename to internal/params/paramset_test.go index 13a2585..4909809 100644 --- a/params/paramset_test.go +++ b/internal/params/paramset_test.go @@ -7,7 +7,8 @@ import ( "testing" "github.com/mailund/cli/internal/failure" - "github.com/mailund/cli/params" + "github.com/mailund/cli/internal/params" + "github.com/mailund/cli/internal/vals" ) func TestMakeParamSet(t *testing.T) { @@ -23,19 +24,22 @@ func TestShortUsage(t *testing.T) { t.Errorf(`Short usage of empty paramset should be ""`) } - p.String("foo", "") + s := "foo" + p.Var((*vals.StringValue)(&s), "foo", "") if p.ShortUsage() != "foo" { t.Errorf(`Short usage of with one "foo" parameter should be "foo"`) } - p.String("bar", "") + p.Var((*vals.StringValue)(&s), "bar", "") if p.ShortUsage() != "foo bar" { t.Errorf(`Short usage of with parameters "foo" and "bar" should be "foo bar"`) } - p.VariadicString("...", "", 0) + // just testing that I can get the vals.VariadicStringValue this way... + x := vals.AsVariadicValue(reflect.ValueOf(&([]string{}))) + p.VariadicVar(x, "...", "", 0) if p.ShortUsage() != "foo bar ..." { t.Errorf(`Short usage of with parameters "foo", "bar" and moreshould be "foo bar ..."`) @@ -45,12 +49,12 @@ func TestShortUsage(t *testing.T) { func TestStringParam(t *testing.T) { var ( x string - y *string + y string ) p := params.NewParamSet("test", params.ExitOnError) - p.StringVar(&x, "x", "") - y = p.String("y", "") + p.Var((*vals.StringValue)(&x), "x", "") + p.Var((*vals.StringValue)(&y), "y", "") _ = p.Parse([]string{"foo", "bar"}) @@ -58,8 +62,8 @@ func TestStringParam(t *testing.T) { t.Errorf(`Expected var x to hold "foo"`) } - if *y != "bar" { - t.Errorf(`Expected var *y to hold "bar"`) + if y != "bar" { + t.Errorf(`Expected var y to hold "bar"`) } } @@ -80,10 +84,10 @@ func TestPrintDefault(t *testing.T) { builder = new(strings.Builder) p.SetOutput(builder) - var x string + var x, y string - p.StringVar(&x, "x", "an x") - p.String("y", "a y") + p.Var((*vals.StringValue)(&x), "x", "an x") + p.Var((*vals.StringValue)(&y), "y", "a y") p.PrintDefaults() @@ -106,11 +110,14 @@ func TestPrintDefaultVariadic(t *testing.T) { p := params.NewParamSet("test", params.ExitOnError) p.SetOutput(builder) - var x string + var ( + x, y vals.StringValue + z = []string{} + ) - p.StringVar(&x, "x", "an x") - p.String("y", "a y") - p.VariadicString("...", "arguments for ...", 0) + p.Var(&x, "x", "an x") + p.Var(&y, "y", "a y") + p.VariadicVar((*vals.VariadicStringValue)(&z), "...", "arguments for ...", 0) p.PrintDefaults() expected := `Arguments: @@ -131,21 +138,27 @@ func TestPrintDefaultVariadic(t *testing.T) { func TestParseVariadic(t *testing.T) { p := params.NewParamSet("test", params.ExitOnError) - p.String("x", "") - rest := p.VariadicString("...", "arguments for ...", 0) + var ( + x vals.StringValue + rest = []string{} + ) + + p.Var(&x, "x", "") + p.VariadicVar((*vals.VariadicStringValue)(&rest), "...", "arguments for ...", 0) args := []string{"for x", "y", "z"} _ = p.Parse(args) - if !reflect.DeepEqual(*rest, []string{"y", "z"}) { + if !reflect.DeepEqual(rest, []string{"y", "z"}) { t.Fatalf("The parser ate more than it should!") } } func TestFailure(t *testing.T) { //nolint:funlen // A test function can have as many statements as it likes p := params.NewParamSet("test", params.ExitOnError) + x := vals.StringValue("") - p.String("x", "") + p.Var(&x, "x", "") var failed = false @@ -194,7 +207,9 @@ func TestFailure(t *testing.T) { //nolint:funlen // A test function can have as } // Add a variadic that wants at least one argument - p.VariadicString("y", "", 1) + y := []string{} + + p.VariadicVar((*vals.VariadicStringValue)(&y), "y", "", 1) failed = false builder = new(strings.Builder) @@ -247,10 +262,11 @@ func TestFailureContinue(t *testing.T) { failure.Failure = func() { failed = true } p := params.NewParamSet("test", params.ContinueOnError) + x := vals.StringValue("") builder := new(strings.Builder) p.SetOutput(builder) - p.String("x", "") + p.Var(&x, "x", "") if err := p.Parse([]string{}); err == nil { t.Fatalf("expected an error from the parse error") @@ -272,13 +288,14 @@ func TestFailureSetFlag(t *testing.T) { // create a paramset that will crash on errors p := params.NewParamSet("test", params.ExitOnError) + x := vals.StringValue("") // but then change the flag p.SetFlag(params.ContinueOnError) builder := new(strings.Builder) p.SetOutput(builder) - p.String("x", "") + p.Var(&x, "x", "") if err := p.Parse([]string{}); err == nil { t.Fatalf("expected an error from the parse error") @@ -307,7 +324,7 @@ func TestFuncCallback(t *testing.T) { return nil } - p.Func("foo", "", f) + p.Var(vals.FuncValue(f), "foo", "") _ = p.Parse([]string{"arg"}) @@ -324,11 +341,13 @@ func TestFuncCallbackError(t *testing.T) { builder := new(strings.Builder) p.SetOutput(builder) - p.Func("foo", "", + f := vals.FuncValue( func(arg string) error { return errors.New("foo failed to bar") //nolint:goerr113 // Testing error }) + p.Var(f, "foo", "") + if err := p.Parse([]string{"arg"}); err == nil { t.Fatalf("Expected an error") } @@ -350,10 +369,14 @@ func TestVariadicFuncError(t *testing.T) { builder := new(strings.Builder) p.SetOutput(builder) - p.VariadicFunc("foo", "", 0, - func(args []string) error { + var ( + f = func(args []string) error { return errors.New("foo failed to bar") //nolint:goerr113 // Testing error - }) + } + vf = vals.VariadicFuncValue(f) + ) + + p.VariadicVar(vf, "foo", "", 0) if err := p.Parse([]string{"arg"}); err == nil { t.Fatalf("Expected an error") @@ -373,12 +396,13 @@ func TestInt(t *testing.T) { failure.Failure = func() { failed = true } p := params.NewParamSet("test", params.ExitOnError) - ip := p.Int("i", "int") + i := vals.IntValue(0) + p.Var(&i, "i", "int") _ = p.Parse([]string{"42"}) - if *ip != 42 { - t.Errorf("Parse error, ip is %d", *ip) + if i != 42 { + t.Errorf("Parse error, i is %d", i) } builder := new(strings.Builder) @@ -402,30 +426,31 @@ func TestBool(t *testing.T) { failure.Failure = func() { failed = true } p := params.NewParamSet("test", params.ExitOnError) - bp := p.Bool("var", "") + b := vals.BoolValue(false) + p.Var(&b, "var", "") _ = p.Parse([]string{"1"}) - if !*bp { - t.Errorf("Parse error, val is %t", *bp) + if !b { + t.Errorf("Parse error, val is %t", b) } _ = p.Parse([]string{"0"}) - if *bp { - t.Errorf("Parse error, val is %t", *bp) + if b { + t.Errorf("Parse error, val is %t", b) } _ = p.Parse([]string{"false"}) - if *bp { - t.Errorf("Parse error, val is %t", *bp) + if b { + t.Errorf("Parse error, val is %t", b) } _ = p.Parse([]string{"true"}) - if !*bp { - t.Errorf("Parse error, val is %t", *bp) + if !b { + t.Errorf("Parse error, val is %t", b) } builder := new(strings.Builder) @@ -449,12 +474,13 @@ func TestFloat(t *testing.T) { failure.Failure = func() { failed = true } p := params.NewParamSet("test", params.ExitOnError) - x := p.Float("var", "") + x := vals.Float64Value(0.0) + p.Var(&x, "var", "") _ = p.Parse([]string{"3.14"}) - if *x != 3.14 { - t.Errorf("Parse error, x is %f", *x) + if x != 3.14 { + t.Errorf("Parse error, x is %f", x) } builder := new(strings.Builder) @@ -475,31 +501,37 @@ func TestFloat(t *testing.T) { func TestVariadicStrings(t *testing.T) { p := params.NewParamSet("test", params.ExitOnError) - x := p.String("x", "") - res := p.VariadicString("x [x...]", "", 0) + x := vals.StringValue("") args := []string{"x", "y", "z"} + res := []string{} + + p.Var(&x, "x", "") + p.VariadicVar((*vals.VariadicStringValue)(&res), "x [x...]", "", 0) _ = p.Parse(args) - if *x != "x" { - t.Errorf("Argument x should be x, is %s", *x) + if x != "x" { + t.Errorf("Argument x should be x, is %s", x) } - if !reflect.DeepEqual(args[1:], *res) { - t.Errorf("Variadic argument got %v, expected [y, z]", *res) + if !reflect.DeepEqual(args[1:], res) { + t.Errorf("Variadic argument got %v, expected [y, z]", res) } } func TestVariadicBools(t *testing.T) { p := params.NewParamSet("test", params.ExitOnError) - res := p.VariadicBool("x [x...]", "", 0) + res := []bool{} + + p.VariadicVar((*vals.VariadicBoolValue)(&res), "x [x...]", "", 0) + args := []string{"1", "true", "0", "false", "t", "FALSE"} expected := []bool{true, true, false, false, true, false} _ = p.Parse(args) - if !reflect.DeepEqual(*res, expected) { - t.Errorf("Variadic argument got %v, expected %v", *res, expected) + if !reflect.DeepEqual(res, expected) { + t.Errorf("Variadic argument got %v, expected %v", res, expected) } // Testing errors @@ -513,21 +545,24 @@ func TestVariadicBools(t *testing.T) { t.Error("Expected a parser failure") } - if errmsg := builder.String(); !strings.HasSuffix(errmsg, "cannot parse 'foo' as boolean.\n") { + if errmsg := builder.String(); !strings.HasSuffix(errmsg, "cannot parse 'foo' as bool.\n") { t.Errorf("Unexpected error message: '%s'\n", errmsg) } } func TestVariadicInts(t *testing.T) { p := params.NewParamSet("test", params.ExitOnError) - res := p.VariadicInt("x [x...]", "", 0) + res := []int{} + + p.VariadicVar((*vals.VariadicIntValue)(&res), "x [x...]", "", 0) + args := []string{"1", "2", "3", "-4", "0x05", "6"} expected := []int{1, 2, 3, -4, 5, 6} _ = p.Parse(args) - if !reflect.DeepEqual(*res, expected) { - t.Errorf("Variadic argument got %v, expected %v", *res, expected) + if !reflect.DeepEqual(res, expected) { + t.Errorf("Variadic argument got %v, expected %v", res, expected) } // Testing errors @@ -542,21 +577,24 @@ func TestVariadicInts(t *testing.T) { t.Error("Expected a parser failure") } - if errmsg := builder.String(); !strings.HasSuffix(errmsg, "cannot parse 'foo' as integer.\n") { + if errmsg := builder.String(); !strings.HasSuffix(errmsg, "cannot parse 'foo' as int.\n") { t.Errorf("Unexpected error message: '%s'\n", errmsg) } } func TestVariadicFloats(t *testing.T) { p := params.NewParamSet("test", params.ExitOnError) - res := p.VariadicFloat("x [x...]", "", 0) + res := []float64{} + + p.VariadicVar((*vals.VariadicFloat64Value)(&res), "x [x...]", "", 0) + args := []string{"1", "0.2", "3e4", "-4.1", "3.14"} expected := []float64{1.0, 0.2, 3e4, -4.1, 3.14} _ = p.Parse(args) - if !reflect.DeepEqual(*res, expected) { - t.Errorf("Variadic argument got %v, expected %v", *res, expected) + if !reflect.DeepEqual(res, expected) { + t.Errorf("Variadic argument got %v, expected %v", res, expected) } // Testing errors @@ -570,7 +608,7 @@ func TestVariadicFloats(t *testing.T) { t.Error("Expected a parser failure") } - if errmsg := builder.String(); !strings.HasSuffix(errmsg, "cannot parse 'foo' as float.\n") { + if errmsg := builder.String(); !strings.HasSuffix(errmsg, "cannot parse 'foo' as float64.\n") { t.Errorf("Unexpected error message: '%s'\n", errmsg) } } @@ -578,9 +616,17 @@ func TestVariadicFloats(t *testing.T) { func TestParamVariadic(t *testing.T) { p := params.NewParamSet("p", params.ExitOnError) - _ = p.Int("i", "int") - _ = p.Bool("b", "bool") - _ = p.VariadicString("s", "strings", 0) + var ( + i vals.IntValue + b vals.BoolValue + ) + + p.Var(&i, "i", "int") + p.Var(&b, "b", "bool") + + res := []string{} + + p.VariadicVar((*vals.VariadicStringValue)(&res), "s", "strings", 0) if p.NParams() != 2 { t.Fatalf("Expected two paramteres, but paramset says there are %d", p.NParams()) diff --git a/internal/vals/callbacks.go b/internal/vals/callbacks.go new file mode 100644 index 0000000..ee5443b --- /dev/null +++ b/internal/vals/callbacks.go @@ -0,0 +1,110 @@ +package vals + +import ( + "reflect" + + "github.com/mailund/cli/interfaces" +) + +// Function callbacks are special values; we don't need to generate them +// since it is easier just to write the little code there is in the first place +type FuncBoolValue func() error + +func (f FuncBoolValue) Set(_ string) error { return f() } +func (f FuncBoolValue) String() string { return "" } +func (f FuncBoolValue) IsBoolFlag() bool { return true } + +type FuncValue func(string) error + +func (f FuncValue) Set(x string) error { return f(x) } +func (f FuncValue) String() string { return "" } + +type VariadicFuncValue func([]string) error + +func (f VariadicFuncValue) Set(x []string) error { return f(x) } + +type ( + BCB = func() + BCBI = func(interface{}) + BCBE = func() error + BCBIE = func(interface{}) error + + CB = func(string) error + CBI = func(string, interface{}) error + + VCB = func([]string) error + VCBI = func([]string, interface{}) error + + CallbackWrapper func(*reflect.Value, interface{}) interfaces.FlagValue + VariadicCallbackWrapper func(*reflect.Value, interface{}) interfaces.VariadicValue +) + +func BCBConstructor(val *reflect.Value, i interface{}) interfaces.FlagValue { + f, _ := val.Interface().(BCB) // If we call this func, we know we have the right type + return FuncBoolValue(func() error { f(); return nil }) +} + +func BCBIConstructor(val *reflect.Value, i interface{}) interfaces.FlagValue { + f, _ := val.Interface().(BCBI) // If we call this func, we know we have the right type + return FuncBoolValue(func() error { f(i); return nil }) +} + +func BCBEConstructor(val *reflect.Value, i interface{}) interfaces.FlagValue { + f, _ := val.Interface().(BCBE) // If we call this func, we know we have the right type + return FuncBoolValue(f) +} + +func BCBIEConstructor(val *reflect.Value, i interface{}) interfaces.FlagValue { + f, _ := val.Interface().(BCBIE) // If we call this func, we know we have the right type + return FuncBoolValue(func() error { return f(i) }) +} + +func CBConstructor(val *reflect.Value, i interface{}) interfaces.FlagValue { + f, _ := val.Interface().(CB) // If we call this func, we know we have the right type + return FuncValue(f) +} + +func CBIConstructor(val *reflect.Value, i interface{}) interfaces.FlagValue { + f, _ := val.Interface().(CBI) // If we call this func, we know we have the right type + return FuncValue(func(x string) error { return f(x, i) }) +} + +func VCBConstructor(val *reflect.Value, i interface{}) interfaces.VariadicValue { + f, _ := val.Interface().(VCB) // If we call this func, we know we have the right type + return VariadicFuncValue(f) +} + +func VCBIConstructor(val *reflect.Value, i interface{}) interfaces.VariadicValue { + f, _ := val.Interface().(VCBI) // If we call this func, we know we have the right type + return VariadicFuncValue(func(x []string) error { return f(x, i) }) +} + +var CallbackWrappers = map[reflect.Type]CallbackWrapper{ + reflect.TypeOf(BCB(nil)): BCBConstructor, + reflect.TypeOf(BCBI(nil)): BCBIConstructor, + reflect.TypeOf(BCBE(nil)): BCBEConstructor, + reflect.TypeOf(BCBIE(nil)): BCBIEConstructor, + reflect.TypeOf(CB(nil)): CBConstructor, + reflect.TypeOf(CBI(nil)): CBIConstructor, +} + +var VariadicCallbackWrappers = map[reflect.Type]VariadicCallbackWrapper{ + reflect.TypeOf(VCB(nil)): VCBConstructor, + reflect.TypeOf(VCBI(nil)): VCBIConstructor, +} + +func AsCallback(val *reflect.Value, i interface{}) interfaces.FlagValue { + if wrap, ok := CallbackWrappers[val.Type()]; ok { + return wrap(val, i) + } + + return nil // couldn't convert +} + +func AsVariadicCallback(val *reflect.Value, i interface{}) interfaces.VariadicValue { + if wrap, ok := VariadicCallbackWrappers[val.Type()]; ok { + return wrap(val, i) + } + + return nil // couldn't convert +} diff --git a/internal/vals/callbacks_test.go b/internal/vals/callbacks_test.go new file mode 100644 index 0000000..da485da --- /dev/null +++ b/internal/vals/callbacks_test.go @@ -0,0 +1,167 @@ +package vals_test + +import ( + "reflect" + "strings" + "testing" + + "github.com/mailund/cli/internal/vals" +) + +func TestCallbacks(t *testing.T) { + var x, y string + + f := reflect.ValueOf( + func(s string) error { x = s; return nil }) + g := reflect.ValueOf( + func(s string, i interface{}) error { y = s + i.(string); return nil }) + + fv := vals.AsCallback(&f, nil) + gv := vals.AsCallback(&g, "bar") + + if err := fv.Set("foo"); err != nil { + t.Fatal("Error calling f") + } + + if err := gv.Set("foo"); err != nil { + t.Fatal("Error calling g") + } + + if x != "foo" { //nolint:goconst // no, foo should not be a constant + t.Error("f didn't work") + } + + if y != "foobar" { + t.Error("g didn't work") + } + + if fv.String() != "" || gv.String() != "" { + t.Error("Functions should have empty default strings") + } +} + +func TestBoolCallbacks(t *testing.T) { + var x, y string + + f := reflect.ValueOf(func() { x = "foo" }) + g := reflect.ValueOf(func(i interface{}) { y = "foo" + i.(string) }) + + fv := vals.AsCallback(&f, nil) + gv := vals.AsCallback(&g, "bar") + + if h, ok := fv.(vals.FuncBoolValue); !ok { + t.Error("We should have a bool function here") + } else if !h.IsBoolFlag() { + t.Error("We should have IsBoolFlag()") + } + + if h, ok := gv.(vals.FuncBoolValue); !ok { + t.Error("We should have a bool function here") + } else if !h.IsBoolFlag() { + t.Error("We should have IsBool()") + } + + if err := fv.Set("foo"); err != nil { + t.Fatal("Error calling f") + } + + if err := gv.Set("foo"); err != nil { + t.Fatal("Error calling g") + } + + if x != "foo" { + t.Error("f didn't work") + } + + if y != "foobar" { + t.Error("g didn't work") + } + + if fv.String() != "" || gv.String() != "" { + t.Error("Functions should have empty default strings") + } +} + +func TestBoolECallbacks(t *testing.T) { + var x, y string + + f := reflect.ValueOf( + func() error { x = "foo"; return nil }) + g := reflect.ValueOf( + func(i interface{}) error { y = "foo" + i.(string); return nil }) + + fv := vals.AsCallback(&f, nil) + gv := vals.AsCallback(&g, "bar") + + if h, ok := fv.(vals.FuncBoolValue); !ok { + t.Error("We should have a bool function here") + } else if !h.IsBoolFlag() { + t.Error("We should have IsBoolFlag()") + } + + if h, ok := gv.(vals.FuncBoolValue); !ok { + t.Error("We should have a bool function here") + } else if !h.IsBoolFlag() { + t.Error("We should have IsBool()") + } + + if err := fv.Set("foo"); err != nil { + t.Fatal("Error calling f") + } + + if err := gv.Set("foo"); err != nil { + t.Fatal("Error calling g") + } + + if x != "foo" { + t.Error("f didn't work") + } + + if y != "foobar" { + t.Error("g didn't work") + } + + if fv.String() != "" || gv.String() != "" { + t.Error("Functions should have empty default strings") + } +} + +func TestVariadicCallbacks(t *testing.T) { + var x, y string + + f := reflect.ValueOf( + func(s []string) error { x = strings.Join(s, ""); return nil }) + g := reflect.ValueOf( + func(s []string, i interface{}) error { y = strings.Join(s, "") + i.(string); return nil }) + + fv := vals.AsVariadicCallback(&f, nil) + gv := vals.AsVariadicCallback(&g, "baz") + + if err := fv.Set([]string{"foo", "bar"}); err != nil { + t.Fatal("Error calling f") + } + + if err := gv.Set([]string{"foo", "bar"}); err != nil { + t.Fatal("Error calling g") + } + + if x != "foobar" { + t.Error("h didn't work") + } + + if y != "foobarbaz" { + t.Error("i didn't work") + } +} + +func TestWrongSignature(t *testing.T) { + f := reflect.ValueOf(func(int) error { return nil }) + + if vals.AsCallback(&f, nil) != nil { + t.Error("Unexpected callback") + } + + if vals.AsVariadicCallback(&f, nil) != nil { + t.Error("Unexpected callback") + } +} diff --git a/internal/vals/gen/genvals.go b/internal/vals/gen/genvals.go new file mode 100644 index 0000000..6c28508 --- /dev/null +++ b/internal/vals/gen/genvals.go @@ -0,0 +1,373 @@ +package main + +import ( + "fmt" + "os" + "strings" + "text/template" +) + +type typeSpec struct { + TypeName string + Parse string + Format string + + SetInput string + SetOutput string + CantFail bool + SetFailInput string + + VarInput string + VarOutput string + VarFailInput string +} + +var valTypes = []typeSpec{ + { + TypeName: "string", + Parse: "x", + Format: "string(*val)", + SetInput: "foo", + SetOutput: "foo", + CantFail: true, + VarInput: `"foo", "bar", "baz"`, + VarOutput: `"foo", "bar", "baz"`, + }, + { + TypeName: "bool", + Parse: "strconv.ParseBool(x)", + Format: "strconv.FormatBool(bool(*val))", + SetInput: "true", + SetOutput: "true", + SetFailInput: "foo", + VarInput: `"true", "false", "true"`, + VarOutput: `true, false, true`, + VarFailInput: `"foo"`, + }, + + { + TypeName: "int", + Parse: "strconv.ParseInt(x, 0, strconv.IntSize)", + Format: "strconv.Itoa(int(*val))", + SetInput: "42", + SetOutput: "42", + SetFailInput: "foo", + VarInput: `"-1", "2", "-3"`, + VarOutput: `-1, 2, -3`, + VarFailInput: `"foo"`, + }, + { + TypeName: "int8", + Parse: "strconv.ParseInt(x, 0, 8)", + Format: "strconv.Itoa(int(*val))", + SetInput: "42", + SetOutput: "42", + SetFailInput: "foo", + VarInput: `"-1", "2", "-3"`, + VarOutput: `-1, 2, -3`, + VarFailInput: `"foo"`, + }, + { + TypeName: "int16", + Parse: "strconv.ParseInt(x, 0, 16)", + Format: "strconv.Itoa(int(*val))", + SetInput: "42", + SetOutput: "42", + SetFailInput: "foo", + VarInput: `"-1", "2", "-3"`, + VarOutput: `-1, 2, -3`, + VarFailInput: `"foo"`, + }, + { + TypeName: "int32", + Parse: "strconv.ParseInt(x, 0, 32)", + Format: "strconv.Itoa(int(*val))", + SetInput: "42", + SetOutput: "42", + SetFailInput: "foo", + VarInput: `"-1", "2", "-3"`, + VarOutput: `-1, 2, -3`, + VarFailInput: `"foo"`, + }, + { + TypeName: "int64", + Parse: "strconv.ParseInt(x, 0, 64)", + Format: "strconv.Itoa(int(*val))", + SetInput: "42", + SetOutput: "42", + SetFailInput: "foo", + VarInput: `"-1", "2", "-3"`, + VarOutput: `-1, 2, -3`, + VarFailInput: `"foo"`, + }, + + { + TypeName: "uint", + Parse: "strconv.ParseUint(x, 0, strconv.IntSize)", + Format: "strconv.FormatUint(uint64(*val), 10)", + SetInput: "42", + SetOutput: "42", + SetFailInput: "-1", + VarInput: `"1", "2", "3"`, + VarOutput: `1, 2, 3`, + VarFailInput: `"-1"`, + }, + { + TypeName: "uint8", + Parse: "strconv.ParseUint(x, 0, 8)", + Format: "strconv.FormatUint(uint64(*val), 10)", + SetInput: "42", + SetOutput: "42", + SetFailInput: "-1", + VarInput: `"1", "2", "3"`, + VarOutput: `1, 2, 3`, + VarFailInput: `"-1"`, + }, + { + TypeName: "uint16", + Parse: "strconv.ParseUint(x, 0, 16)", + Format: "strconv.FormatUint(uint64(*val), 10)", + SetInput: "42", + SetOutput: "42", + SetFailInput: "-1", + VarInput: `"1", "2", "3"`, + VarOutput: `1, 2, 3`, + VarFailInput: `"-1"`, + }, + { + TypeName: "uint32", + Parse: "strconv.ParseUint(x, 0, 32)", + Format: "strconv.FormatUint(uint64(*val), 10)", + SetInput: "42", + SetOutput: "42", + SetFailInput: "-1", + VarInput: `"1", "2", "3"`, + VarOutput: `1, 2, 3`, + VarFailInput: `"-1"`, + }, + { + TypeName: "uint64", + Parse: "strconv.ParseUint(x, 0, 64)", + Format: "strconv.FormatUint(uint64(*val), 10)", + SetInput: "42", + SetOutput: "42", + SetFailInput: "-1", + VarInput: `"1", "2", "3"`, + VarOutput: `1, 2, 3`, + VarFailInput: `"-1"`, + }, + + { + TypeName: "float32", + Parse: "strconv.ParseFloat(x, 32)", + Format: "strconv.FormatFloat(float64(*val), 'g', -1, 32)", + SetInput: "3.14", + SetOutput: "3.14", + SetFailInput: "foo", + VarInput: `"0.1", "0.2", "0.3"`, + VarOutput: `0.1, 0.2, 0.3`, + VarFailInput: `"foo"`, + }, + { + TypeName: "float64", + Parse: "strconv.ParseFloat(x, 64)", + Format: "strconv.FormatFloat(float64(*val), 'g', -1, 64)", + SetInput: "3.14", + SetOutput: "3.14", + SetFailInput: "foo", + VarInput: `"0.1", "0.2", "0.3"`, + VarOutput: `0.1, 0.2, 0.3`, + VarFailInput: `"foo"`, + }, + + { + TypeName: "complex64", + Parse: "strconv.ParseComplex(x, 64)", + Format: "strconv.FormatComplex(complex128(*val), 'g', -1, 64)", + SetInput: "(3.14+42i)", + SetOutput: "(3.14+42i)", + SetFailInput: "foo", + VarInput: `"0.1+0.2i", "0.2+0.3i", "0.3+0.4i"`, + VarOutput: `0.1+0.2i, 0.2+0.3i, 0.3+0.4i`, + VarFailInput: `"foo"`, + }, + { + TypeName: "complex128", + Parse: "strconv.ParseComplex(x, 128)", + Format: "strconv.FormatComplex(complex128(*val), 'g', -1, 128)", + SetInput: "(3.14+42i)", + SetOutput: "(3.14+42i)", + SetFailInput: "foo", + VarInput: `"0.1+0.2i", "0.2+0.3i", "0.3+0.4i"`, + VarOutput: `0.1+0.2i, 0.2+0.3i, 0.3+0.4i`, + VarFailInput: `"foo"`, + }, +} + +var valsTemplate = ` +type {{TypeName .TypeName}} {{.TypeName}} + +func (val *{{TypeName .TypeName}}) Set(x string) error { + {{if .CantFail}}*val = {{TypeName .TypeName}}({{.Parse}}) + return nil{{else}}v, err := {{.Parse}} + if err != nil { + err = interfaces.ParseErrorf("argument \"%s\" cannot be parsed as {{.TypeName}}", x) + } else { + *val = {{TypeName .TypeName}}(v) + } + + return err{{end}} +} + +func (val *{{TypeName .TypeName}}) String() string { + return {{.Format}} +} + +func {{TypeName .TypeName}}Constructor(val reflect.Value) interfaces.FlagValue { + return (*{{TypeName .TypeName}})(val.Interface().(*{{.TypeName}})) +} + +type {{VariadicTypeName .TypeName}} []{{.TypeName}} + +func (vals *{{VariadicTypeName .TypeName}}) Set(xs []string) error { + *vals = make([]{{.TypeName}}, len(xs)) + + for i, x := range xs { + {{if .CantFail}}(*vals)[i] = {{.TypeName}}({{.Parse}}){{else}}val, err := {{.Parse}} + if err != nil { + return interfaces.ParseErrorf("cannot parse '%s' as {{.TypeName}}", x) + } + + (*vals)[i] = {{.TypeName}}(val){{end}} + } + + return nil +} + +func {{VariadicTypeName .TypeName}}Constructor(val reflect.Value) interfaces.VariadicValue { + return (*{{VariadicTypeName .TypeName}})(val.Interface().(*[]{{.TypeName}})) +} +` + +var tableTemplate = "\tValsConstructors[reflect.TypeOf((*{{.TypeName}})(nil))] = {{TypeName .TypeName}}Constructor\n" + + "\tVarValsConstructors[reflect.TypeOf((*[]{{.TypeName}})(nil))] = {{VariadicTypeName .TypeName}}Constructor\n" + +var testTemplate = ` +func Test{{TypeName .TypeName}}(t *testing.T) { + var ( + x {{.TypeName}} + val = vals.AsFlagValue(reflect.ValueOf(&x)) + ) + + if val == nil { + t.Fatal("val should not be nil") + } + + if err := val.Set("{{.SetInput}}"); err != nil { + t.Error("error setting val to {{.SetInput}}") + } + + if val.String() != "{{.SetOutput}}" { + t.Errorf("Unexpected string value for val: %s", val.String()) + }{{ if (not .CantFail) }} + + if err := val.Set("{{.SetFailInput}}"); err == nil { + t.Error("val.Set() should fail this time") + }{{end}} +} + +func Test{{VariadicTypeName .TypeName}}(t *testing.T) { + var ( + x []{{.TypeName}} + vv = vals.AsVariadicValue(reflect.ValueOf(&x)) + ) + + if vv == nil { + t.Fatal("vv should not be nil") + } + + if err := vv.Set([]string{ {{.VarInput}} }); err != nil { + t.Error("vv.Set should not fail") + } + + if !reflect.DeepEqual(x, []{{.TypeName}}{ {{.VarOutput}} }) { + t.Error("x holds the wrong value") + }{{ if (not .CantFail) }} + + if err := vv.Set([]string{ {{.VarFailInput}} }); err == nil { + t.Error("vv.Set() should fail this time") + }{{end}} +} +` + +var ( + funcMap = template.FuncMap{ + "TypeName": func(name string) string { return strings.Title(name) + "Value" }, + "VariadicTypeName": func(name string) string { return "Variadic" + strings.Title(name) + "Value" }, + } + + valsFuncs = template.Must(template.New("val").Funcs(funcMap).Parse(valsTemplate)) + tableInit = template.Must(template.New("val").Funcs(funcMap).Parse(tableTemplate)) + tests = template.Must(template.New("val").Funcs(funcMap).Parse(testTemplate)) +) + +func genvals(fname string) { + f, err := os.Create(fname) + if err != nil { + panic(err) + } + + defer f.Close() + + fmt.Fprintln(f, `// Code generated by gen/genvals.go DO NOT EDIT. +package vals + +import ( + "reflect" + "strconv" + + "github.com/mailund/cli/interfaces" +)`) + + for i := 0; i < len(valTypes); i++ { + _ = valsFuncs.Execute(f, valTypes[i]) + } + + fmt.Fprintln(f, "\nfunc init() {") + + for i := 0; i < len(valTypes); i++ { + _ = tableInit.Execute(f, valTypes[i]) + } + + fmt.Fprintln(f, "}") +} + +func genvalsTest(fname string) { + f, err := os.Create(fname) + if err != nil { + panic(err) + } + + defer f.Close() + + fmt.Fprintln(f, `// Code generated by gen/genvals.go DO NOT EDIT. +package vals_test + +import ( + "reflect" + "testing" + + "github.com/mailund/cli/internal/vals" +)`) + + for i := 0; i < len(valTypes); i++ { + _ = tests.Execute(f, valTypes[i]) + } +} + +// I would love to pipe the result through gofmt and goimports, +// but fucking go won't let me install goimports. Stupid stupid stupid go. +func main() { + genvals(os.Args[1] + ".go") + genvalsTest(os.Args[1] + "_test.go") +} diff --git a/internal/vals/genvals.go b/internal/vals/genvals.go new file mode 100644 index 0000000..b8fe484 --- /dev/null +++ b/internal/vals/genvals.go @@ -0,0 +1,705 @@ +// Code generated by gen/genvals.go DO NOT EDIT. +package vals + +import ( + "reflect" + "strconv" + + "github.com/mailund/cli/interfaces" +) + +type StringValue string + +func (val *StringValue) Set(x string) error { + *val = StringValue(x) + return nil +} + +func (val *StringValue) String() string { + return string(*val) +} + +func StringValueConstructor(val reflect.Value) interfaces.FlagValue { + return (*StringValue)(val.Interface().(*string)) +} + +type VariadicStringValue []string + +func (vals *VariadicStringValue) Set(xs []string) error { + *vals = make([]string, len(xs)) + + for i, x := range xs { + (*vals)[i] = string(x) + } + + return nil +} + +func VariadicStringValueConstructor(val reflect.Value) interfaces.VariadicValue { + return (*VariadicStringValue)(val.Interface().(*[]string)) +} + +type BoolValue bool + +func (val *BoolValue) Set(x string) error { + v, err := strconv.ParseBool(x) + if err != nil { + err = interfaces.ParseErrorf("argument \"%s\" cannot be parsed as bool", x) + } else { + *val = BoolValue(v) + } + + return err +} + +func (val *BoolValue) String() string { + return strconv.FormatBool(bool(*val)) +} + +func BoolValueConstructor(val reflect.Value) interfaces.FlagValue { + return (*BoolValue)(val.Interface().(*bool)) +} + +type VariadicBoolValue []bool + +func (vals *VariadicBoolValue) Set(xs []string) error { + *vals = make([]bool, len(xs)) + + for i, x := range xs { + val, err := strconv.ParseBool(x) + if err != nil { + return interfaces.ParseErrorf("cannot parse '%s' as bool", x) + } + + (*vals)[i] = bool(val) + } + + return nil +} + +func VariadicBoolValueConstructor(val reflect.Value) interfaces.VariadicValue { + return (*VariadicBoolValue)(val.Interface().(*[]bool)) +} + +type IntValue int + +func (val *IntValue) Set(x string) error { + v, err := strconv.ParseInt(x, 0, strconv.IntSize) + if err != nil { + err = interfaces.ParseErrorf("argument \"%s\" cannot be parsed as int", x) + } else { + *val = IntValue(v) + } + + return err +} + +func (val *IntValue) String() string { + return strconv.Itoa(int(*val)) +} + +func IntValueConstructor(val reflect.Value) interfaces.FlagValue { + return (*IntValue)(val.Interface().(*int)) +} + +type VariadicIntValue []int + +func (vals *VariadicIntValue) Set(xs []string) error { + *vals = make([]int, len(xs)) + + for i, x := range xs { + val, err := strconv.ParseInt(x, 0, strconv.IntSize) + if err != nil { + return interfaces.ParseErrorf("cannot parse '%s' as int", x) + } + + (*vals)[i] = int(val) + } + + return nil +} + +func VariadicIntValueConstructor(val reflect.Value) interfaces.VariadicValue { + return (*VariadicIntValue)(val.Interface().(*[]int)) +} + +type Int8Value int8 + +func (val *Int8Value) Set(x string) error { + v, err := strconv.ParseInt(x, 0, 8) + if err != nil { + err = interfaces.ParseErrorf("argument \"%s\" cannot be parsed as int8", x) + } else { + *val = Int8Value(v) + } + + return err +} + +func (val *Int8Value) String() string { + return strconv.Itoa(int(*val)) +} + +func Int8ValueConstructor(val reflect.Value) interfaces.FlagValue { + return (*Int8Value)(val.Interface().(*int8)) +} + +type VariadicInt8Value []int8 + +func (vals *VariadicInt8Value) Set(xs []string) error { + *vals = make([]int8, len(xs)) + + for i, x := range xs { + val, err := strconv.ParseInt(x, 0, 8) + if err != nil { + return interfaces.ParseErrorf("cannot parse '%s' as int8", x) + } + + (*vals)[i] = int8(val) + } + + return nil +} + +func VariadicInt8ValueConstructor(val reflect.Value) interfaces.VariadicValue { + return (*VariadicInt8Value)(val.Interface().(*[]int8)) +} + +type Int16Value int16 + +func (val *Int16Value) Set(x string) error { + v, err := strconv.ParseInt(x, 0, 16) + if err != nil { + err = interfaces.ParseErrorf("argument \"%s\" cannot be parsed as int16", x) + } else { + *val = Int16Value(v) + } + + return err +} + +func (val *Int16Value) String() string { + return strconv.Itoa(int(*val)) +} + +func Int16ValueConstructor(val reflect.Value) interfaces.FlagValue { + return (*Int16Value)(val.Interface().(*int16)) +} + +type VariadicInt16Value []int16 + +func (vals *VariadicInt16Value) Set(xs []string) error { + *vals = make([]int16, len(xs)) + + for i, x := range xs { + val, err := strconv.ParseInt(x, 0, 16) + if err != nil { + return interfaces.ParseErrorf("cannot parse '%s' as int16", x) + } + + (*vals)[i] = int16(val) + } + + return nil +} + +func VariadicInt16ValueConstructor(val reflect.Value) interfaces.VariadicValue { + return (*VariadicInt16Value)(val.Interface().(*[]int16)) +} + +type Int32Value int32 + +func (val *Int32Value) Set(x string) error { + v, err := strconv.ParseInt(x, 0, 32) + if err != nil { + err = interfaces.ParseErrorf("argument \"%s\" cannot be parsed as int32", x) + } else { + *val = Int32Value(v) + } + + return err +} + +func (val *Int32Value) String() string { + return strconv.Itoa(int(*val)) +} + +func Int32ValueConstructor(val reflect.Value) interfaces.FlagValue { + return (*Int32Value)(val.Interface().(*int32)) +} + +type VariadicInt32Value []int32 + +func (vals *VariadicInt32Value) Set(xs []string) error { + *vals = make([]int32, len(xs)) + + for i, x := range xs { + val, err := strconv.ParseInt(x, 0, 32) + if err != nil { + return interfaces.ParseErrorf("cannot parse '%s' as int32", x) + } + + (*vals)[i] = int32(val) + } + + return nil +} + +func VariadicInt32ValueConstructor(val reflect.Value) interfaces.VariadicValue { + return (*VariadicInt32Value)(val.Interface().(*[]int32)) +} + +type Int64Value int64 + +func (val *Int64Value) Set(x string) error { + v, err := strconv.ParseInt(x, 0, 64) + if err != nil { + err = interfaces.ParseErrorf("argument \"%s\" cannot be parsed as int64", x) + } else { + *val = Int64Value(v) + } + + return err +} + +func (val *Int64Value) String() string { + return strconv.Itoa(int(*val)) +} + +func Int64ValueConstructor(val reflect.Value) interfaces.FlagValue { + return (*Int64Value)(val.Interface().(*int64)) +} + +type VariadicInt64Value []int64 + +func (vals *VariadicInt64Value) Set(xs []string) error { + *vals = make([]int64, len(xs)) + + for i, x := range xs { + val, err := strconv.ParseInt(x, 0, 64) + if err != nil { + return interfaces.ParseErrorf("cannot parse '%s' as int64", x) + } + + (*vals)[i] = int64(val) + } + + return nil +} + +func VariadicInt64ValueConstructor(val reflect.Value) interfaces.VariadicValue { + return (*VariadicInt64Value)(val.Interface().(*[]int64)) +} + +type UintValue uint + +func (val *UintValue) Set(x string) error { + v, err := strconv.ParseUint(x, 0, strconv.IntSize) + if err != nil { + err = interfaces.ParseErrorf("argument \"%s\" cannot be parsed as uint", x) + } else { + *val = UintValue(v) + } + + return err +} + +func (val *UintValue) String() string { + return strconv.FormatUint(uint64(*val), 10) +} + +func UintValueConstructor(val reflect.Value) interfaces.FlagValue { + return (*UintValue)(val.Interface().(*uint)) +} + +type VariadicUintValue []uint + +func (vals *VariadicUintValue) Set(xs []string) error { + *vals = make([]uint, len(xs)) + + for i, x := range xs { + val, err := strconv.ParseUint(x, 0, strconv.IntSize) + if err != nil { + return interfaces.ParseErrorf("cannot parse '%s' as uint", x) + } + + (*vals)[i] = uint(val) + } + + return nil +} + +func VariadicUintValueConstructor(val reflect.Value) interfaces.VariadicValue { + return (*VariadicUintValue)(val.Interface().(*[]uint)) +} + +type Uint8Value uint8 + +func (val *Uint8Value) Set(x string) error { + v, err := strconv.ParseUint(x, 0, 8) + if err != nil { + err = interfaces.ParseErrorf("argument \"%s\" cannot be parsed as uint8", x) + } else { + *val = Uint8Value(v) + } + + return err +} + +func (val *Uint8Value) String() string { + return strconv.FormatUint(uint64(*val), 10) +} + +func Uint8ValueConstructor(val reflect.Value) interfaces.FlagValue { + return (*Uint8Value)(val.Interface().(*uint8)) +} + +type VariadicUint8Value []uint8 + +func (vals *VariadicUint8Value) Set(xs []string) error { + *vals = make([]uint8, len(xs)) + + for i, x := range xs { + val, err := strconv.ParseUint(x, 0, 8) + if err != nil { + return interfaces.ParseErrorf("cannot parse '%s' as uint8", x) + } + + (*vals)[i] = uint8(val) + } + + return nil +} + +func VariadicUint8ValueConstructor(val reflect.Value) interfaces.VariadicValue { + return (*VariadicUint8Value)(val.Interface().(*[]uint8)) +} + +type Uint16Value uint16 + +func (val *Uint16Value) Set(x string) error { + v, err := strconv.ParseUint(x, 0, 16) + if err != nil { + err = interfaces.ParseErrorf("argument \"%s\" cannot be parsed as uint16", x) + } else { + *val = Uint16Value(v) + } + + return err +} + +func (val *Uint16Value) String() string { + return strconv.FormatUint(uint64(*val), 10) +} + +func Uint16ValueConstructor(val reflect.Value) interfaces.FlagValue { + return (*Uint16Value)(val.Interface().(*uint16)) +} + +type VariadicUint16Value []uint16 + +func (vals *VariadicUint16Value) Set(xs []string) error { + *vals = make([]uint16, len(xs)) + + for i, x := range xs { + val, err := strconv.ParseUint(x, 0, 16) + if err != nil { + return interfaces.ParseErrorf("cannot parse '%s' as uint16", x) + } + + (*vals)[i] = uint16(val) + } + + return nil +} + +func VariadicUint16ValueConstructor(val reflect.Value) interfaces.VariadicValue { + return (*VariadicUint16Value)(val.Interface().(*[]uint16)) +} + +type Uint32Value uint32 + +func (val *Uint32Value) Set(x string) error { + v, err := strconv.ParseUint(x, 0, 32) + if err != nil { + err = interfaces.ParseErrorf("argument \"%s\" cannot be parsed as uint32", x) + } else { + *val = Uint32Value(v) + } + + return err +} + +func (val *Uint32Value) String() string { + return strconv.FormatUint(uint64(*val), 10) +} + +func Uint32ValueConstructor(val reflect.Value) interfaces.FlagValue { + return (*Uint32Value)(val.Interface().(*uint32)) +} + +type VariadicUint32Value []uint32 + +func (vals *VariadicUint32Value) Set(xs []string) error { + *vals = make([]uint32, len(xs)) + + for i, x := range xs { + val, err := strconv.ParseUint(x, 0, 32) + if err != nil { + return interfaces.ParseErrorf("cannot parse '%s' as uint32", x) + } + + (*vals)[i] = uint32(val) + } + + return nil +} + +func VariadicUint32ValueConstructor(val reflect.Value) interfaces.VariadicValue { + return (*VariadicUint32Value)(val.Interface().(*[]uint32)) +} + +type Uint64Value uint64 + +func (val *Uint64Value) Set(x string) error { + v, err := strconv.ParseUint(x, 0, 64) + if err != nil { + err = interfaces.ParseErrorf("argument \"%s\" cannot be parsed as uint64", x) + } else { + *val = Uint64Value(v) + } + + return err +} + +func (val *Uint64Value) String() string { + return strconv.FormatUint(uint64(*val), 10) +} + +func Uint64ValueConstructor(val reflect.Value) interfaces.FlagValue { + return (*Uint64Value)(val.Interface().(*uint64)) +} + +type VariadicUint64Value []uint64 + +func (vals *VariadicUint64Value) Set(xs []string) error { + *vals = make([]uint64, len(xs)) + + for i, x := range xs { + val, err := strconv.ParseUint(x, 0, 64) + if err != nil { + return interfaces.ParseErrorf("cannot parse '%s' as uint64", x) + } + + (*vals)[i] = uint64(val) + } + + return nil +} + +func VariadicUint64ValueConstructor(val reflect.Value) interfaces.VariadicValue { + return (*VariadicUint64Value)(val.Interface().(*[]uint64)) +} + +type Float32Value float32 + +func (val *Float32Value) Set(x string) error { + v, err := strconv.ParseFloat(x, 32) + if err != nil { + err = interfaces.ParseErrorf("argument \"%s\" cannot be parsed as float32", x) + } else { + *val = Float32Value(v) + } + + return err +} + +func (val *Float32Value) String() string { + return strconv.FormatFloat(float64(*val), 'g', -1, 32) +} + +func Float32ValueConstructor(val reflect.Value) interfaces.FlagValue { + return (*Float32Value)(val.Interface().(*float32)) +} + +type VariadicFloat32Value []float32 + +func (vals *VariadicFloat32Value) Set(xs []string) error { + *vals = make([]float32, len(xs)) + + for i, x := range xs { + val, err := strconv.ParseFloat(x, 32) + if err != nil { + return interfaces.ParseErrorf("cannot parse '%s' as float32", x) + } + + (*vals)[i] = float32(val) + } + + return nil +} + +func VariadicFloat32ValueConstructor(val reflect.Value) interfaces.VariadicValue { + return (*VariadicFloat32Value)(val.Interface().(*[]float32)) +} + +type Float64Value float64 + +func (val *Float64Value) Set(x string) error { + v, err := strconv.ParseFloat(x, 64) + if err != nil { + err = interfaces.ParseErrorf("argument \"%s\" cannot be parsed as float64", x) + } else { + *val = Float64Value(v) + } + + return err +} + +func (val *Float64Value) String() string { + return strconv.FormatFloat(float64(*val), 'g', -1, 64) +} + +func Float64ValueConstructor(val reflect.Value) interfaces.FlagValue { + return (*Float64Value)(val.Interface().(*float64)) +} + +type VariadicFloat64Value []float64 + +func (vals *VariadicFloat64Value) Set(xs []string) error { + *vals = make([]float64, len(xs)) + + for i, x := range xs { + val, err := strconv.ParseFloat(x, 64) + if err != nil { + return interfaces.ParseErrorf("cannot parse '%s' as float64", x) + } + + (*vals)[i] = float64(val) + } + + return nil +} + +func VariadicFloat64ValueConstructor(val reflect.Value) interfaces.VariadicValue { + return (*VariadicFloat64Value)(val.Interface().(*[]float64)) +} + +type Complex64Value complex64 + +func (val *Complex64Value) Set(x string) error { + v, err := strconv.ParseComplex(x, 64) + if err != nil { + err = interfaces.ParseErrorf("argument \"%s\" cannot be parsed as complex64", x) + } else { + *val = Complex64Value(v) + } + + return err +} + +func (val *Complex64Value) String() string { + return strconv.FormatComplex(complex128(*val), 'g', -1, 64) +} + +func Complex64ValueConstructor(val reflect.Value) interfaces.FlagValue { + return (*Complex64Value)(val.Interface().(*complex64)) +} + +type VariadicComplex64Value []complex64 + +func (vals *VariadicComplex64Value) Set(xs []string) error { + *vals = make([]complex64, len(xs)) + + for i, x := range xs { + val, err := strconv.ParseComplex(x, 64) + if err != nil { + return interfaces.ParseErrorf("cannot parse '%s' as complex64", x) + } + + (*vals)[i] = complex64(val) + } + + return nil +} + +func VariadicComplex64ValueConstructor(val reflect.Value) interfaces.VariadicValue { + return (*VariadicComplex64Value)(val.Interface().(*[]complex64)) +} + +type Complex128Value complex128 + +func (val *Complex128Value) Set(x string) error { + v, err := strconv.ParseComplex(x, 128) + if err != nil { + err = interfaces.ParseErrorf("argument \"%s\" cannot be parsed as complex128", x) + } else { + *val = Complex128Value(v) + } + + return err +} + +func (val *Complex128Value) String() string { + return strconv.FormatComplex(complex128(*val), 'g', -1, 128) +} + +func Complex128ValueConstructor(val reflect.Value) interfaces.FlagValue { + return (*Complex128Value)(val.Interface().(*complex128)) +} + +type VariadicComplex128Value []complex128 + +func (vals *VariadicComplex128Value) Set(xs []string) error { + *vals = make([]complex128, len(xs)) + + for i, x := range xs { + val, err := strconv.ParseComplex(x, 128) + if err != nil { + return interfaces.ParseErrorf("cannot parse '%s' as complex128", x) + } + + (*vals)[i] = complex128(val) + } + + return nil +} + +func VariadicComplex128ValueConstructor(val reflect.Value) interfaces.VariadicValue { + return (*VariadicComplex128Value)(val.Interface().(*[]complex128)) +} + +func init() { + ValsConstructors[reflect.TypeOf((*string)(nil))] = StringValueConstructor + VarValsConstructors[reflect.TypeOf((*[]string)(nil))] = VariadicStringValueConstructor + ValsConstructors[reflect.TypeOf((*bool)(nil))] = BoolValueConstructor + VarValsConstructors[reflect.TypeOf((*[]bool)(nil))] = VariadicBoolValueConstructor + ValsConstructors[reflect.TypeOf((*int)(nil))] = IntValueConstructor + VarValsConstructors[reflect.TypeOf((*[]int)(nil))] = VariadicIntValueConstructor + ValsConstructors[reflect.TypeOf((*int8)(nil))] = Int8ValueConstructor + VarValsConstructors[reflect.TypeOf((*[]int8)(nil))] = VariadicInt8ValueConstructor + ValsConstructors[reflect.TypeOf((*int16)(nil))] = Int16ValueConstructor + VarValsConstructors[reflect.TypeOf((*[]int16)(nil))] = VariadicInt16ValueConstructor + ValsConstructors[reflect.TypeOf((*int32)(nil))] = Int32ValueConstructor + VarValsConstructors[reflect.TypeOf((*[]int32)(nil))] = VariadicInt32ValueConstructor + ValsConstructors[reflect.TypeOf((*int64)(nil))] = Int64ValueConstructor + VarValsConstructors[reflect.TypeOf((*[]int64)(nil))] = VariadicInt64ValueConstructor + ValsConstructors[reflect.TypeOf((*uint)(nil))] = UintValueConstructor + VarValsConstructors[reflect.TypeOf((*[]uint)(nil))] = VariadicUintValueConstructor + ValsConstructors[reflect.TypeOf((*uint8)(nil))] = Uint8ValueConstructor + VarValsConstructors[reflect.TypeOf((*[]uint8)(nil))] = VariadicUint8ValueConstructor + ValsConstructors[reflect.TypeOf((*uint16)(nil))] = Uint16ValueConstructor + VarValsConstructors[reflect.TypeOf((*[]uint16)(nil))] = VariadicUint16ValueConstructor + ValsConstructors[reflect.TypeOf((*uint32)(nil))] = Uint32ValueConstructor + VarValsConstructors[reflect.TypeOf((*[]uint32)(nil))] = VariadicUint32ValueConstructor + ValsConstructors[reflect.TypeOf((*uint64)(nil))] = Uint64ValueConstructor + VarValsConstructors[reflect.TypeOf((*[]uint64)(nil))] = VariadicUint64ValueConstructor + ValsConstructors[reflect.TypeOf((*float32)(nil))] = Float32ValueConstructor + VarValsConstructors[reflect.TypeOf((*[]float32)(nil))] = VariadicFloat32ValueConstructor + ValsConstructors[reflect.TypeOf((*float64)(nil))] = Float64ValueConstructor + VarValsConstructors[reflect.TypeOf((*[]float64)(nil))] = VariadicFloat64ValueConstructor + ValsConstructors[reflect.TypeOf((*complex64)(nil))] = Complex64ValueConstructor + VarValsConstructors[reflect.TypeOf((*[]complex64)(nil))] = VariadicComplex64ValueConstructor + ValsConstructors[reflect.TypeOf((*complex128)(nil))] = Complex128ValueConstructor + VarValsConstructors[reflect.TypeOf((*[]complex128)(nil))] = VariadicComplex128ValueConstructor +} diff --git a/internal/vals/genvals_test.go b/internal/vals/genvals_test.go new file mode 100644 index 0000000..eee541f --- /dev/null +++ b/internal/vals/genvals_test.go @@ -0,0 +1,737 @@ +// Code generated by gen/genvals.go DO NOT EDIT. +package vals_test + +import ( + "reflect" + "testing" + + "github.com/mailund/cli/internal/vals" +) + +func TestStringValue(t *testing.T) { + var ( + x string + val = vals.AsFlagValue(reflect.ValueOf(&x)) + ) + + if val == nil { + t.Fatal("val should not be nil") + } + + if err := val.Set("foo"); err != nil { + t.Error("error setting val to foo") + } + + if val.String() != "foo" { + t.Errorf("Unexpected string value for val: %s", val.String()) + } +} + +func TestVariadicStringValue(t *testing.T) { + var ( + x []string + vv = vals.AsVariadicValue(reflect.ValueOf(&x)) + ) + + if vv == nil { + t.Fatal("vv should not be nil") + } + + if err := vv.Set([]string{ "foo", "bar", "baz" }); err != nil { + t.Error("vv.Set should not fail") + } + + if !reflect.DeepEqual(x, []string{ "foo", "bar", "baz" }) { + t.Error("x holds the wrong value") + } +} + +func TestBoolValue(t *testing.T) { + var ( + x bool + val = vals.AsFlagValue(reflect.ValueOf(&x)) + ) + + if val == nil { + t.Fatal("val should not be nil") + } + + if err := val.Set("true"); err != nil { + t.Error("error setting val to true") + } + + if val.String() != "true" { + t.Errorf("Unexpected string value for val: %s", val.String()) + } + + if err := val.Set("foo"); err == nil { + t.Error("val.Set() should fail this time") + } +} + +func TestVariadicBoolValue(t *testing.T) { + var ( + x []bool + vv = vals.AsVariadicValue(reflect.ValueOf(&x)) + ) + + if vv == nil { + t.Fatal("vv should not be nil") + } + + if err := vv.Set([]string{ "true", "false", "true" }); err != nil { + t.Error("vv.Set should not fail") + } + + if !reflect.DeepEqual(x, []bool{ true, false, true }) { + t.Error("x holds the wrong value") + } + + if err := vv.Set([]string{ "foo" }); err == nil { + t.Error("vv.Set() should fail this time") + } +} + +func TestIntValue(t *testing.T) { + var ( + x int + val = vals.AsFlagValue(reflect.ValueOf(&x)) + ) + + if val == nil { + t.Fatal("val should not be nil") + } + + if err := val.Set("42"); err != nil { + t.Error("error setting val to 42") + } + + if val.String() != "42" { + t.Errorf("Unexpected string value for val: %s", val.String()) + } + + if err := val.Set("foo"); err == nil { + t.Error("val.Set() should fail this time") + } +} + +func TestVariadicIntValue(t *testing.T) { + var ( + x []int + vv = vals.AsVariadicValue(reflect.ValueOf(&x)) + ) + + if vv == nil { + t.Fatal("vv should not be nil") + } + + if err := vv.Set([]string{ "-1", "2", "-3" }); err != nil { + t.Error("vv.Set should not fail") + } + + if !reflect.DeepEqual(x, []int{ -1, 2, -3 }) { + t.Error("x holds the wrong value") + } + + if err := vv.Set([]string{ "foo" }); err == nil { + t.Error("vv.Set() should fail this time") + } +} + +func TestInt8Value(t *testing.T) { + var ( + x int8 + val = vals.AsFlagValue(reflect.ValueOf(&x)) + ) + + if val == nil { + t.Fatal("val should not be nil") + } + + if err := val.Set("42"); err != nil { + t.Error("error setting val to 42") + } + + if val.String() != "42" { + t.Errorf("Unexpected string value for val: %s", val.String()) + } + + if err := val.Set("foo"); err == nil { + t.Error("val.Set() should fail this time") + } +} + +func TestVariadicInt8Value(t *testing.T) { + var ( + x []int8 + vv = vals.AsVariadicValue(reflect.ValueOf(&x)) + ) + + if vv == nil { + t.Fatal("vv should not be nil") + } + + if err := vv.Set([]string{ "-1", "2", "-3" }); err != nil { + t.Error("vv.Set should not fail") + } + + if !reflect.DeepEqual(x, []int8{ -1, 2, -3 }) { + t.Error("x holds the wrong value") + } + + if err := vv.Set([]string{ "foo" }); err == nil { + t.Error("vv.Set() should fail this time") + } +} + +func TestInt16Value(t *testing.T) { + var ( + x int16 + val = vals.AsFlagValue(reflect.ValueOf(&x)) + ) + + if val == nil { + t.Fatal("val should not be nil") + } + + if err := val.Set("42"); err != nil { + t.Error("error setting val to 42") + } + + if val.String() != "42" { + t.Errorf("Unexpected string value for val: %s", val.String()) + } + + if err := val.Set("foo"); err == nil { + t.Error("val.Set() should fail this time") + } +} + +func TestVariadicInt16Value(t *testing.T) { + var ( + x []int16 + vv = vals.AsVariadicValue(reflect.ValueOf(&x)) + ) + + if vv == nil { + t.Fatal("vv should not be nil") + } + + if err := vv.Set([]string{ "-1", "2", "-3" }); err != nil { + t.Error("vv.Set should not fail") + } + + if !reflect.DeepEqual(x, []int16{ -1, 2, -3 }) { + t.Error("x holds the wrong value") + } + + if err := vv.Set([]string{ "foo" }); err == nil { + t.Error("vv.Set() should fail this time") + } +} + +func TestInt32Value(t *testing.T) { + var ( + x int32 + val = vals.AsFlagValue(reflect.ValueOf(&x)) + ) + + if val == nil { + t.Fatal("val should not be nil") + } + + if err := val.Set("42"); err != nil { + t.Error("error setting val to 42") + } + + if val.String() != "42" { + t.Errorf("Unexpected string value for val: %s", val.String()) + } + + if err := val.Set("foo"); err == nil { + t.Error("val.Set() should fail this time") + } +} + +func TestVariadicInt32Value(t *testing.T) { + var ( + x []int32 + vv = vals.AsVariadicValue(reflect.ValueOf(&x)) + ) + + if vv == nil { + t.Fatal("vv should not be nil") + } + + if err := vv.Set([]string{ "-1", "2", "-3" }); err != nil { + t.Error("vv.Set should not fail") + } + + if !reflect.DeepEqual(x, []int32{ -1, 2, -3 }) { + t.Error("x holds the wrong value") + } + + if err := vv.Set([]string{ "foo" }); err == nil { + t.Error("vv.Set() should fail this time") + } +} + +func TestInt64Value(t *testing.T) { + var ( + x int64 + val = vals.AsFlagValue(reflect.ValueOf(&x)) + ) + + if val == nil { + t.Fatal("val should not be nil") + } + + if err := val.Set("42"); err != nil { + t.Error("error setting val to 42") + } + + if val.String() != "42" { + t.Errorf("Unexpected string value for val: %s", val.String()) + } + + if err := val.Set("foo"); err == nil { + t.Error("val.Set() should fail this time") + } +} + +func TestVariadicInt64Value(t *testing.T) { + var ( + x []int64 + vv = vals.AsVariadicValue(reflect.ValueOf(&x)) + ) + + if vv == nil { + t.Fatal("vv should not be nil") + } + + if err := vv.Set([]string{ "-1", "2", "-3" }); err != nil { + t.Error("vv.Set should not fail") + } + + if !reflect.DeepEqual(x, []int64{ -1, 2, -3 }) { + t.Error("x holds the wrong value") + } + + if err := vv.Set([]string{ "foo" }); err == nil { + t.Error("vv.Set() should fail this time") + } +} + +func TestUintValue(t *testing.T) { + var ( + x uint + val = vals.AsFlagValue(reflect.ValueOf(&x)) + ) + + if val == nil { + t.Fatal("val should not be nil") + } + + if err := val.Set("42"); err != nil { + t.Error("error setting val to 42") + } + + if val.String() != "42" { + t.Errorf("Unexpected string value for val: %s", val.String()) + } + + if err := val.Set("-1"); err == nil { + t.Error("val.Set() should fail this time") + } +} + +func TestVariadicUintValue(t *testing.T) { + var ( + x []uint + vv = vals.AsVariadicValue(reflect.ValueOf(&x)) + ) + + if vv == nil { + t.Fatal("vv should not be nil") + } + + if err := vv.Set([]string{ "1", "2", "3" }); err != nil { + t.Error("vv.Set should not fail") + } + + if !reflect.DeepEqual(x, []uint{ 1, 2, 3 }) { + t.Error("x holds the wrong value") + } + + if err := vv.Set([]string{ "-1" }); err == nil { + t.Error("vv.Set() should fail this time") + } +} + +func TestUint8Value(t *testing.T) { + var ( + x uint8 + val = vals.AsFlagValue(reflect.ValueOf(&x)) + ) + + if val == nil { + t.Fatal("val should not be nil") + } + + if err := val.Set("42"); err != nil { + t.Error("error setting val to 42") + } + + if val.String() != "42" { + t.Errorf("Unexpected string value for val: %s", val.String()) + } + + if err := val.Set("-1"); err == nil { + t.Error("val.Set() should fail this time") + } +} + +func TestVariadicUint8Value(t *testing.T) { + var ( + x []uint8 + vv = vals.AsVariadicValue(reflect.ValueOf(&x)) + ) + + if vv == nil { + t.Fatal("vv should not be nil") + } + + if err := vv.Set([]string{ "1", "2", "3" }); err != nil { + t.Error("vv.Set should not fail") + } + + if !reflect.DeepEqual(x, []uint8{ 1, 2, 3 }) { + t.Error("x holds the wrong value") + } + + if err := vv.Set([]string{ "-1" }); err == nil { + t.Error("vv.Set() should fail this time") + } +} + +func TestUint16Value(t *testing.T) { + var ( + x uint16 + val = vals.AsFlagValue(reflect.ValueOf(&x)) + ) + + if val == nil { + t.Fatal("val should not be nil") + } + + if err := val.Set("42"); err != nil { + t.Error("error setting val to 42") + } + + if val.String() != "42" { + t.Errorf("Unexpected string value for val: %s", val.String()) + } + + if err := val.Set("-1"); err == nil { + t.Error("val.Set() should fail this time") + } +} + +func TestVariadicUint16Value(t *testing.T) { + var ( + x []uint16 + vv = vals.AsVariadicValue(reflect.ValueOf(&x)) + ) + + if vv == nil { + t.Fatal("vv should not be nil") + } + + if err := vv.Set([]string{ "1", "2", "3" }); err != nil { + t.Error("vv.Set should not fail") + } + + if !reflect.DeepEqual(x, []uint16{ 1, 2, 3 }) { + t.Error("x holds the wrong value") + } + + if err := vv.Set([]string{ "-1" }); err == nil { + t.Error("vv.Set() should fail this time") + } +} + +func TestUint32Value(t *testing.T) { + var ( + x uint32 + val = vals.AsFlagValue(reflect.ValueOf(&x)) + ) + + if val == nil { + t.Fatal("val should not be nil") + } + + if err := val.Set("42"); err != nil { + t.Error("error setting val to 42") + } + + if val.String() != "42" { + t.Errorf("Unexpected string value for val: %s", val.String()) + } + + if err := val.Set("-1"); err == nil { + t.Error("val.Set() should fail this time") + } +} + +func TestVariadicUint32Value(t *testing.T) { + var ( + x []uint32 + vv = vals.AsVariadicValue(reflect.ValueOf(&x)) + ) + + if vv == nil { + t.Fatal("vv should not be nil") + } + + if err := vv.Set([]string{ "1", "2", "3" }); err != nil { + t.Error("vv.Set should not fail") + } + + if !reflect.DeepEqual(x, []uint32{ 1, 2, 3 }) { + t.Error("x holds the wrong value") + } + + if err := vv.Set([]string{ "-1" }); err == nil { + t.Error("vv.Set() should fail this time") + } +} + +func TestUint64Value(t *testing.T) { + var ( + x uint64 + val = vals.AsFlagValue(reflect.ValueOf(&x)) + ) + + if val == nil { + t.Fatal("val should not be nil") + } + + if err := val.Set("42"); err != nil { + t.Error("error setting val to 42") + } + + if val.String() != "42" { + t.Errorf("Unexpected string value for val: %s", val.String()) + } + + if err := val.Set("-1"); err == nil { + t.Error("val.Set() should fail this time") + } +} + +func TestVariadicUint64Value(t *testing.T) { + var ( + x []uint64 + vv = vals.AsVariadicValue(reflect.ValueOf(&x)) + ) + + if vv == nil { + t.Fatal("vv should not be nil") + } + + if err := vv.Set([]string{ "1", "2", "3" }); err != nil { + t.Error("vv.Set should not fail") + } + + if !reflect.DeepEqual(x, []uint64{ 1, 2, 3 }) { + t.Error("x holds the wrong value") + } + + if err := vv.Set([]string{ "-1" }); err == nil { + t.Error("vv.Set() should fail this time") + } +} + +func TestFloat32Value(t *testing.T) { + var ( + x float32 + val = vals.AsFlagValue(reflect.ValueOf(&x)) + ) + + if val == nil { + t.Fatal("val should not be nil") + } + + if err := val.Set("3.14"); err != nil { + t.Error("error setting val to 3.14") + } + + if val.String() != "3.14" { + t.Errorf("Unexpected string value for val: %s", val.String()) + } + + if err := val.Set("foo"); err == nil { + t.Error("val.Set() should fail this time") + } +} + +func TestVariadicFloat32Value(t *testing.T) { + var ( + x []float32 + vv = vals.AsVariadicValue(reflect.ValueOf(&x)) + ) + + if vv == nil { + t.Fatal("vv should not be nil") + } + + if err := vv.Set([]string{ "0.1", "0.2", "0.3" }); err != nil { + t.Error("vv.Set should not fail") + } + + if !reflect.DeepEqual(x, []float32{ 0.1, 0.2, 0.3 }) { + t.Error("x holds the wrong value") + } + + if err := vv.Set([]string{ "foo" }); err == nil { + t.Error("vv.Set() should fail this time") + } +} + +func TestFloat64Value(t *testing.T) { + var ( + x float64 + val = vals.AsFlagValue(reflect.ValueOf(&x)) + ) + + if val == nil { + t.Fatal("val should not be nil") + } + + if err := val.Set("3.14"); err != nil { + t.Error("error setting val to 3.14") + } + + if val.String() != "3.14" { + t.Errorf("Unexpected string value for val: %s", val.String()) + } + + if err := val.Set("foo"); err == nil { + t.Error("val.Set() should fail this time") + } +} + +func TestVariadicFloat64Value(t *testing.T) { + var ( + x []float64 + vv = vals.AsVariadicValue(reflect.ValueOf(&x)) + ) + + if vv == nil { + t.Fatal("vv should not be nil") + } + + if err := vv.Set([]string{ "0.1", "0.2", "0.3" }); err != nil { + t.Error("vv.Set should not fail") + } + + if !reflect.DeepEqual(x, []float64{ 0.1, 0.2, 0.3 }) { + t.Error("x holds the wrong value") + } + + if err := vv.Set([]string{ "foo" }); err == nil { + t.Error("vv.Set() should fail this time") + } +} + +func TestComplex64Value(t *testing.T) { + var ( + x complex64 + val = vals.AsFlagValue(reflect.ValueOf(&x)) + ) + + if val == nil { + t.Fatal("val should not be nil") + } + + if err := val.Set("(3.14+42i)"); err != nil { + t.Error("error setting val to (3.14+42i)") + } + + if val.String() != "(3.14+42i)" { + t.Errorf("Unexpected string value for val: %s", val.String()) + } + + if err := val.Set("foo"); err == nil { + t.Error("val.Set() should fail this time") + } +} + +func TestVariadicComplex64Value(t *testing.T) { + var ( + x []complex64 + vv = vals.AsVariadicValue(reflect.ValueOf(&x)) + ) + + if vv == nil { + t.Fatal("vv should not be nil") + } + + if err := vv.Set([]string{ "0.1+0.2i", "0.2+0.3i", "0.3+0.4i" }); err != nil { + t.Error("vv.Set should not fail") + } + + if !reflect.DeepEqual(x, []complex64{ 0.1+0.2i, 0.2+0.3i, 0.3+0.4i }) { + t.Error("x holds the wrong value") + } + + if err := vv.Set([]string{ "foo" }); err == nil { + t.Error("vv.Set() should fail this time") + } +} + +func TestComplex128Value(t *testing.T) { + var ( + x complex128 + val = vals.AsFlagValue(reflect.ValueOf(&x)) + ) + + if val == nil { + t.Fatal("val should not be nil") + } + + if err := val.Set("(3.14+42i)"); err != nil { + t.Error("error setting val to (3.14+42i)") + } + + if val.String() != "(3.14+42i)" { + t.Errorf("Unexpected string value for val: %s", val.String()) + } + + if err := val.Set("foo"); err == nil { + t.Error("val.Set() should fail this time") + } +} + +func TestVariadicComplex128Value(t *testing.T) { + var ( + x []complex128 + vv = vals.AsVariadicValue(reflect.ValueOf(&x)) + ) + + if vv == nil { + t.Fatal("vv should not be nil") + } + + if err := vv.Set([]string{ "0.1+0.2i", "0.2+0.3i", "0.3+0.4i" }); err != nil { + t.Error("vv.Set should not fail") + } + + if !reflect.DeepEqual(x, []complex128{ 0.1+0.2i, 0.2+0.3i, 0.3+0.4i }) { + t.Error("x holds the wrong value") + } + + if err := vv.Set([]string{ "foo" }); err == nil { + t.Error("vv.Set() should fail this time") + } +} diff --git a/internal/vals/vals.go b/internal/vals/vals.go new file mode 100644 index 0000000..60395d0 --- /dev/null +++ b/internal/vals/vals.go @@ -0,0 +1,58 @@ +// Implementations of Values interfaces for wrapping flags and positional +// arguments +package vals + +import ( + "reflect" + + "github.com/mailund/cli/interfaces" +) + +//go:generate go run gen/genvals.go genvals + +// We explicitly write this method so boolean flags do not need arguments +func (val *BoolValue) IsBoolFlag() bool { return true } + +type ValConstructor func(reflect.Value) interfaces.FlagValue + +var ValsConstructors = map[reflect.Type]ValConstructor{} + +func AsFlagValue(val reflect.Value) interfaces.FlagValue { + if cast, ok := val.Interface().(interfaces.FlagValue); ok { + return cast + } + + if cons, ok := ValsConstructors[val.Type()]; ok { + return cons(val) + } + + return nil +} + +func AsPosValue(val reflect.Value) interfaces.PosValue { + if cast, ok := val.Interface().(interfaces.PosValue); ok { + return cast + } + + if cons, ok := ValsConstructors[val.Type()]; ok { + return cons(val) + } + + return nil +} + +type VarValConstructor func(reflect.Value) interfaces.VariadicValue + +var VarValsConstructors = map[reflect.Type]VarValConstructor{} + +func AsVariadicValue(val reflect.Value) interfaces.VariadicValue { + if cast, ok := val.Interface().(interfaces.VariadicValue); ok { + return cast + } + + if cons, ok := VarValsConstructors[val.Type()]; ok { + return cons(val) + } + + return nil +} diff --git a/internal/vals/vals_test.go b/internal/vals/vals_test.go new file mode 100644 index 0000000..659c417 --- /dev/null +++ b/internal/vals/vals_test.go @@ -0,0 +1,257 @@ +package vals_test + +import ( + "flag" + "reflect" + "strings" + "testing" + + "github.com/mailund/cli/interfaces" + "github.com/mailund/cli/internal/vals" +) + +func simplifyUsage(x string) string { + return strings.Join(strings.Fields(strings.TrimSpace(x)), " ") +} + +func TestBoolFlag(t *testing.T) { + var b vals.BoolValue + + if !b.IsBoolFlag() { + t.Fatal("boolean values should work as boolean flags") + } +} + +func TestValsFlags(t *testing.T) { + var ( + i vals.IntValue = 42 + s vals.StringValue = "foo" + f = flag.NewFlagSet("test", flag.ContinueOnError) + ) + + f.Var(&i, "i", "an integer") + f.Var(&s, "s", "a string") + + builder := new(strings.Builder) + f.SetOutput(builder) + f.PrintDefaults() + + usage := simplifyUsage(builder.String()) + expected := strings.Join( + []string{ + "-i value an integer (default 42)", + "-s value a string (default foo)", + }, " ") + + if usage != expected { + t.Errorf("Unexpected usage string:\n'%s'\n", usage) + } + + err := f.Parse([]string{"-i=13", "-s", "bar"}) + + if err != nil { + t.Fatal("Didn't expect parsing to return an error") + } + + if i != 13 { + t.Errorf("Expected i to have the value 13, it has %d", i) + } + + if s != "bar" { + t.Errorf("Expected s to have the value bar, it has %s", s) + } +} + +type TestValue struct{} + +func (t TestValue) String() string { return "" } +func (t TestValue) Set(string) error { return nil } + +func TestAsValue(t *testing.T) { + var ( + // Implements the interface (if we use its address) + iv vals.IntValue + + // doesn't implement the interface, so we will wrap it + i = 42 + + // implements the interface, so we should get it back unchanged + tv TestValue + + // cannot be translated into a value + m map[int]int + + pval interfaces.PosValue + fval interfaces.FlagValue + ) + + pval = vals.AsPosValue(reflect.ValueOf(&i)) + if pval == nil { + t.Fatal("We should have wrapped int") + } + + fval = vals.AsFlagValue(reflect.ValueOf(&i)) + if fval == nil { + t.Fatal("We should have wrapped int") + } + + if cast, ok := fval.(*vals.IntValue); !ok { + t.Error("Unexpected type for wrapped integer") + } else if i != int(*cast) { + t.Error("Expected value and i to hold the same value") + } + + fval = vals.AsFlagValue(reflect.ValueOf(&iv)) + if fval == nil || !reflect.DeepEqual(fval, &iv) { + t.Fatal("As pointer receiver, iv should implement the interface") + } + + fval = vals.AsFlagValue(reflect.ValueOf(tv)) + if fval == nil || !reflect.DeepEqual(fval, tv) { + t.Fatal("We should have gotten tv back unchanged") + } + + pval = vals.AsPosValue(reflect.ValueOf(tv)) + if pval == nil || !reflect.DeepEqual(pval, tv) { + t.Fatal("We should have gotten tv back unchanged") + } + + fval = vals.AsFlagValue(reflect.ValueOf(&m)) + if fval != nil { + t.Fatal("The map should not be wrapped") + } + + pval = vals.AsPosValue(reflect.ValueOf(&m)) + if pval != nil { + t.Fatal("The map should not be wrapped") + } +} + +func TestVariadic(t *testing.T) { + ints := []int{} + vi := (*vals.VariadicIntValue)(&ints) + + err := vi.Set([]string{"1", "2", "3"}) + if err != nil { + t.Fatalf("Did not expect errors, but got %s", err.Error()) + } + + if len(*vi) != 3 { + t.Fatal("Expected *vi to have three values now") + } + + for i := 0; i < 3; i++ { + if (*vi)[i] != i+1 { + t.Errorf("Unexpected (*vi)[%d] = %d", i+1, (*vi)[i]) + } + } +} + +func TestVariadic2(t *testing.T) { + ints := []int{} + vi := vals.VariadicIntValueConstructor(reflect.ValueOf(&ints)) + + if vi == nil { + t.Fatal("Wrapping created nil") + } + + vii, ok := vi.(*vals.VariadicIntValue) + if !ok { + t.Fatal("We should be able to cast to *vals.VariadicIntValue") + } + + err := vi.Set([]string{"1", "2", "3"}) + if err != nil { + t.Fatalf("Did not expect errors, but got %s", err.Error()) + } + + if len(*vii) != 3 { + t.Fatal("Expected *vi to have three values now") + } + + for i := 0; i < 3; i++ { + if (*vii)[i] != i+1 { + t.Errorf("Unexpected (*vi)[%d] = %d", i+1, (*vii)[i]) + } + } +} + +func TestVariadic3(t *testing.T) { + ints := []int{} + vi := vals.AsVariadicValue(reflect.ValueOf(&ints)) + + if vi == nil { + t.Fatal("Wrapping created nil") + } + + vii, ok := vi.(*vals.VariadicIntValue) + if !ok { + t.Fatal("We should be able to cast to *vals.VariadicIntValue") + } + + err := vi.Set([]string{"1", "2", "3"}) + if err != nil { + t.Fatalf("Did not expect errors, but got %s", err.Error()) + } + + if len(*vii) != 3 { + t.Fatal("Expected *vi to have three values now") + } + + for i := 0; i < 3; i++ { + if (*vii)[i] != i+1 { + t.Errorf("Unexpected (*vi)[%d] = %d", i+1, (*vii)[i]) + } + } +} + +type VTestValue struct{} + +func (t VTestValue) Set([]string) error { return nil } + +func TestAsVariadicValue(t *testing.T) { + var ( + // Implements the interface (if we use its address) + iv vals.VariadicIntValue + + // doesn't implement the interface, so we will wrap it + i = []int{} + + // implements the interface, so we should get it back unchanged + tv VTestValue + + // cannot be translated into a value + m map[int]int + + val interfaces.VariadicValue + ) + + val = vals.AsVariadicValue(reflect.ValueOf(&i)) + if val == nil { + t.Fatal("We should have wrapped []int") + } + + if cast, ok := val.(*vals.VariadicIntValue); !ok { + t.Error("Unexpected type for wrapped integer") + } else { + j := (*[]int)(cast) + if !reflect.DeepEqual(i, *j) { + t.Error("Expected j and i to be equal") + } + } + + val = vals.AsVariadicValue(reflect.ValueOf(&iv)) + if val == nil || !reflect.DeepEqual(val, &iv) { + t.Fatal("As pointer receiver, iv should implement the interface") + } + + val = vals.AsVariadicValue(reflect.ValueOf(tv)) + if val == nil || !reflect.DeepEqual(val, tv) { + t.Fatal("We should have gotten tv back unchanged") + } + + val = vals.AsVariadicValue(reflect.ValueOf(&m)) + if val != nil { + t.Fatal("The map should not be wrapped") + } +} diff --git a/params/paramset.go b/params/paramset.go deleted file mode 100644 index 6178b7c..0000000 --- a/params/paramset.go +++ /dev/null @@ -1,604 +0,0 @@ -package params - -import ( - "flag" - "fmt" - "io" - "os" - "strconv" - "strings" - - "github.com/mailund/cli/internal/failure" -) - -type ( - /* ErrorHandling flags for determining how the argument - parser should handle errors. - - The error handling works the same as for the flag package, - except that ParamSet's only support two flags: - - - ExitOnError: Terminate the program on error. - - ContinueOnError: Report the error as an error object. - */ - ErrorHandling = flag.ErrorHandling -) - -const ( - // ContinueOnError means that parsing will return an error - // rather than terminate the program. - ContinueOnError = flag.ContinueOnError - // ExitOnError means that parsing will exit the program - // (using os.Exit(2)) if the parser fails. - ExitOnError = flag.ExitOnError - // PanicOnError means we raise a panic on errors - PanicOnError = flag.PanicOnError -) - -// Param holds positional parameter information. -type Param struct { - // Name is the short name used for a parameter - Name string - // Desc is a short description of the parameter - Desc string - - parser func(string) error -} - -// VariadicParam holds information about a variadic argument. -type VariadicParam struct { - // Name is the short name used for a parameter - Name string - // Desc is a short description of the parameter - Desc string - // Min is the minimum number of parameters that the variadic - // parameter takes. - Min int - - parser func([]string) error -} - -// ParamSet contains a list of specified parameters for a -// commandline tool and parsers for parsing commandline -// arguments. -type ParamSet struct { - // Name is a name used when printing usage information - // for a parameter set. - Name string - // Usage is a function you can assign to for changing - // the help info. - Usage func() - // ErrorFlag Controls how we deal with parsing errors - ErrorFlag ErrorHandling - - params []*Param - out io.Writer - - // last parameter, used for variadic arguments - last *VariadicParam -} - -// SetFlag sets the error handling flag. -func (p *ParamSet) SetFlag(f ErrorHandling) { - p.ErrorFlag = f -} - -// SetOutput specifies where usage output and error messages should -// be written to. -// -// Parameters: -// - out: a writer object. The default, if you do not set a new -// variable is os.Stderr. -func (p *ParamSet) SetOutput(out io.Writer) { p.out = out } - -// Output returns the output stream where messages are written to -func (p *ParamSet) Output() io.Writer { return p.out } - -// PrintDefaults prints a description of the parameters -func (p *ParamSet) PrintDefaults() { - if p.NParams() == 0 && p.last == nil { - return // nothing to print... - } - - fmt.Fprintf(p.Output(), "Arguments:\n") - - for _, par := range p.params { - fmt.Fprintf(p.Output(), " %s\n\t%s\n", par.Name, par.Desc) - } - - if p.last != nil { - fmt.Fprintf(p.Output(), " %s\n\t%s\n", p.last.Name, p.last.Desc) - } -} - -// NParams returns the number of parameters in the set, excluding the last variadic -// parameter if there is one. You can test for whether there is a variadic argument using -// p.Variadic() != nil. -func (p *ParamSet) NParams() int { - return len(p.params) -} - -// Param returns the parameter at position i. -func (p *ParamSet) Param(i int) *Param { - return p.params[i] -} - -// Variadic returns the last variadic parameter, if there is one. -func (p *ParamSet) Variadic() *VariadicParam { - return p.last -} - -// NewParamSet creates a new parameter set. -// -// Parameters: -// - name: The name for the parameter set. Used when printing -// usage and error information. -// - errflat: Controls the error handling. If ExitOnError, parse -// errors will terminate the program (os.Exit(1)); if ContinueOnError -// the parsing will return an error instead. -func NewParamSet(name string, errflag ErrorHandling) *ParamSet { - argset := &ParamSet{ - Name: name, - ErrorFlag: errflag, - params: []*Param{}, - out: os.Stderr} - argset.Usage = argset.PrintDefaults - - return argset -} - -// ShortUsage returns a string used for printing the usage -// of a parameter set. -func (p *ParamSet) ShortUsage() string { - names := make([]string, len(p.params)) - for i, param := range p.params { - names[i] = param.Name - } - - namesUsage := strings.Join(names, " ") - - if p.last != nil { - namesUsage += " " + p.last.Name - } - - return namesUsage -} - -// Parse parses arguments against parameters. -// -// Parameters: -// - args: A slice of strings from a command line. -// -// If the parameter set has error handling flag ExitOnError, -// a parsing error will terminate the program (os.Exit(1)). -// If the parameter set has error handling flag ContinueOnError, -// it will return an error instead. If all goes well, it will -// return nil. -func (p *ParamSet) Parse(args []string) error { - minParams := len(p.params) - if p.last != nil { - minParams += p.last.Min - } - - if len(args) < minParams { - if p.ErrorFlag == ExitOnError { - fmt.Fprintf(p.Output(), - "Too few arguments for command '%s'\n\n", - p.Name) - p.Usage() - failure.Failure() - } - - return ParseErrorf("too few arguments") - } - - if p.last == nil && len(args) > len(p.params) { - if p.ErrorFlag == ExitOnError { - fmt.Fprintf(p.Output(), - "Too many arguments for command '%s'\n\n", - p.Name) - p.Usage() - failure.Failure() - } - - return ParseErrorf("too many arguments") - } - - for i, par := range p.params { - if err := par.parser(args[i]); err != nil { - if p.ErrorFlag == flag.ExitOnError { - fmt.Fprintf(p.Output(), "Error parsing parameter %s='%s', %s.\n", - par.Name, args[i], err.Error()) - failure.Failure() - } - - return ParseErrorf("error parsing parameter %s='%s'", par.Name, args[i]) - } - } - - if p.last != nil { - rest := args[len(p.params):] - if err := p.last.parser(rest); err != nil { - if p.ErrorFlag == ExitOnError { - fmt.Fprintf(p.Output(), "Error parsing parameters %s='%v', %s.\n", - p.last.Name, rest, err.Error()) - failure.Failure() - } - - return ParseErrorf("error parsing parameters %s='%v'", p.last.Name, rest) - } - } - - return nil -} - -func stringParser(target *string) func(string) error { - return func(arg string) error { - *target = arg - return nil - } -} - -func intParser(target *int) func(string) error { - return func(arg string) error { - val, err := strconv.ParseInt(arg, 0, 64) //nolint:gomnd // 64 bits is not magic - if err != nil { - return ParseErrorf("argument `%s` cannot be parsed as an integer", arg) - } - - *target = int(val) - - return nil - } -} - -func boolParser(target *bool) func(string) error { - return func(arg string) error { - val, err := strconv.ParseBool(arg) - if err != nil { - return ParseErrorf("argument `%s` cannot be parsed as a bool", arg) - } - - *target = val - - return nil - } -} - -func floatParser(target *float64) func(string) error { - return func(arg string) error { - val, err := strconv.ParseFloat(arg, 64) //nolint:gomnd // 64 bits is not magic - if err != nil { - return ParseErrorf("argument `%s` cannot be parsed as a float", arg) - } - - *target = val - - return nil - } -} - -// StringVar appends a string argument to the set. If the -// parameter set is parsed successfully, the parsed value for this -// parameter will have been written to target. -// -// Parameters: -// - target: Pointer to where the parsed argument should be written. -// - name: Name of the argument, used when printing usage. -// - desc: Description of the argument. Used when printing usage. -func (p *ParamSet) StringVar(target *string, name, desc string) { - p.Func(name, desc, stringParser(target)) -} - -// String appends a string argument to the set and returns -// a pointer to the new variable's target. If the -// parameter set is parsed successfully, the parsed value for this -// parameter will have been written to target. -// -// Parameters: -// - name: Name of the argument, used when printing usage. -// - desc: Description of the argument. Used when printing usage. -func (p *ParamSet) String(name, desc string) *string { - var x string - - p.StringVar(&x, name, desc) - - return &x -} - -// IntVar appends an integer argument to the set. If the -// parameter set is parsed successfully, the parsed value for this -// parameter will have been written to target. -// -// Parameters: -// - target: Pointer to where the parsed argument should be written. -// - name: Name of the argument, used when printing usage. -// - desc: Description of the argument. Used when printing usage. -func (p *ParamSet) IntVar(target *int, name, desc string) { - p.Func(name, desc, intParser(target)) -} - -// Int appends an integer argument to the set and returns -// a pointer to the new variable's target. If the -// parameter set is parsed successfully, the parsed value for this -// parameter will have been written to target. -// -// Parameters: -// - name: Name of the argument, used when printing usage. -// - desc: Description of the argument. Used when printing usage. -func (p *ParamSet) Int(name, desc string) *int { - var x int - - p.IntVar(&x, name, desc) - - return &x -} - -// BoolVar appends a boolean argument to the set. If the -// parameter set is parsed successfully, the parsed value for this -// parameter will have been written to target. -// -// Parameters: -// - target: Pointer to where the parsed argument should be written. -// - name: Name of the argument, used when printing usage. -// - desc: Description of the argument. Used when printing usage. -func (p *ParamSet) BoolVar(target *bool, name, desc string) { - p.Func(name, desc, boolParser(target)) -} - -// Bool appends a boolean argument to the set and returns -// a pointer to the new variable's target. If the -// parameter set is parsed successfully, the parsed value for this -// parameter will have been written to target. -// -// Parameters: -// - name: Name of the argument, used when printing usage. -// - desc: Description of the argument. Used when printing usage. -func (p *ParamSet) Bool(name, desc string) *bool { - var x bool - - p.BoolVar(&x, name, desc) - - return &x -} - -// FloatVar appends a floating point argument to the set. If the -// parameter set is parsed successfully, the parsed value for this -// parameter will have been written to target. -// -// Parameters: -// - target: Pointer to where the parsed argument should be written. -// - name: Name of the argument, used when printing usage. -// - desc: Description of the argument. Used when printing usage. -func (p *ParamSet) FloatVar(target *float64, name, desc string) { - p.Func(name, desc, floatParser(target)) -} - -// Float appends a floating point argument to the set and returns -// a pointer to the new variable's target. If the -// parameter set is parsed successfully, the parsed value for this -// parameter will have been written to target. -// -// Parameters: -// - name: Name of the argument, used when printing usage. -// - desc: Description of the argument. Used when printing usage. -func (p *ParamSet) Float(name, desc string) *float64 { - var x float64 - - p.FloatVar(&x, name, desc) - - return &x -} - -// Func appends a callback function as an argument. When -// the parameter set is parsed, this function will be called. -// If the function returns nil, the parser assumes that all -// went well; if it returns a non-nil error, it handles the -// parsing as an error. -// -// Parameters: -// - name: Name of the argument, used when printing usage. -// - desc: Description of the argument. Used when printing usage. -// - fn: Callback function, invoked when the arguments are parsed. -func (p *ParamSet) Func(name, desc string, fn func(string) error) { - p.params = append(p.params, &Param{name, desc, fn}) -} - -func variadicStringParser(target *[]string) func([]string) error { - return func(args []string) error { - *target = args - return nil - } -} - -func variadicBoolParser(target *[]bool) func([]string) error { - return func(args []string) error { - res := make([]bool, len(args)) - - for i, x := range args { - val, err := strconv.ParseBool(x) - if err != nil { - return ParseErrorf("cannot parse '%s' as boolean", x) - } - - res[i] = val - } - - *target = res - - return nil - } -} - -func variadicIntParser(target *[]int) func([]string) error { - return func(args []string) error { - res := make([]int, len(args)) - - for i, x := range args { - val, err := strconv.ParseInt(x, 0, 64) //nolint:gomnd // 64 bits is not magic - if err != nil { - return ParseErrorf("cannot parse '%s' as integer", x) - } - - res[i] = int(val) - } - - *target = res - - return nil - } -} - -func variadicFloatParser(target *[]float64) func([]string) error { - return func(args []string) error { - res := make([]float64, len(args)) - - for i, x := range args { - val, err := strconv.ParseFloat(x, 64) //nolint:gomnd // 64 bits is not magic - if err != nil { - return ParseErrorf("cannot parse '%s' as float", x) - } - - res[i] = val - } - - *target = res - - return nil - } -} - -// VariadicStringVar install a variadic string argument -// as the last parameter(s) for the parameter set. -// -// Parameters: -// - target: Pointer to where the parsed arguments should be written. -// - name: Name of the argument, used when printing usage. -// - desc: Description of the argument. Used when printing usage. -// - min: The minimum number of arguments that the command line must -// have for this parameter. -func (p *ParamSet) VariadicStringVar(target *[]string, name, desc string, min int) { - p.VariadicFunc(name, desc, min, variadicStringParser(target)) -} - -// VariadicString install a variadic string argument -// as the last parameter(s) for the parameter set. It returns a pointer -// to where the parsed values will go if parsing is succesfull. -// -// Parameters: -// - name: Name of the argument, used when printing usage. -// - desc: Description of the argument. Used when printing usage. -// - min: The minimum number of arguments that the command line must -// have for this parameter. -func (p *ParamSet) VariadicString(name, desc string, min int) *[]string { - var x = []string{} - - p.VariadicStringVar(&x, name, desc, min) - - return &x -} - -// VariadicBoolVar install a variadic bool argument -// as the last parameter(s) for the parameter set. -// -// Parameters: -// - target: Pointer to where the parsed arguments should be written. -// - name: Name of the argument, used when printing usage. -// - desc: Description of the argument. Used when printing usage. -// - min: The minimum number of arguments that the command line must -// have for this parameter. -func (p *ParamSet) VariadicBoolVar(target *[]bool, name, desc string, min int) { - p.VariadicFunc(name, desc, min, variadicBoolParser(target)) -} - -// VariadicBool install a variadic bool argument -// as the last parameter(s) for the parameter set. It returns a pointer -// to where the parsed values will go if parsing is succesfull. -// -// Parameters: -// - name: Name of the argument, used when printing usage. -// - desc: Description of the argument. Used when printing usage. -// - min: The minimum number of arguments that the command line must -// have for this parameter. -func (p *ParamSet) VariadicBool(name, desc string, min int) *[]bool { - var x = []bool{} - - p.VariadicBoolVar(&x, name, desc, min) - - return &x -} - -// VariadicIntVar install a variadic int argument -// as the last parameter(s) for the parameter set. -// -// Parameters: -// - target: Pointer to where the parsed arguments should be written. -// - name: Name of the argument, used when printing usage. -// - desc: Description of the argument. Used when printing usage. -// - min: The minimum number of arguments that the command line must -// have for this parameter. -func (p *ParamSet) VariadicIntVar(target *[]int, name, desc string, min int) { - p.VariadicFunc(name, desc, min, variadicIntParser(target)) -} - -// VariadicInt install a variadic int argument -// as the last parameter(s) for the parameter set. It returns a pointer -// to where the parsed values will go if parsing is succesfull. -// -// Parameters: -// - name: Name of the argument, used when printing usage. -// - desc: Description of the argument. Used when printing usage. -// - min: The minimum number of arguments that the command line must -// have for this parameter. -func (p *ParamSet) VariadicInt(name, desc string, min int) *[]int { - var x = []int{} - - p.VariadicIntVar(&x, name, desc, min) - - return &x -} - -// VariadicFloatVar install a variadic float argument -// as the last parameter(s) for the parameter set. -// -// Parameters: -// - target: Pointer to where the parsed arguments should be written. -// - name: Name of the argument, used when printing usage. -// - desc: Description of the argument. Used when printing usage. -// - min: The minimum number of arguments that the command line must -// have for this parameter. -func (p *ParamSet) VariadicFloatVar(target *[]float64, name, desc string, min int) { - p.VariadicFunc(name, desc, min, variadicFloatParser(target)) -} - -// VariadicFloat install a variadic float argument -// as the last parameter(s) for the parameter set. It returns a pointer -// to where the parsed values will go if parsing is succesfull. -// -// Parameters: -// - name: Name of the argument, used when printing usage. -// - desc: Description of the argument. Used when printing usage. -// - min: The minimum number of arguments that the command line must -// have for this parameter. -func (p *ParamSet) VariadicFloat(name, desc string, min int) *[]float64 { - var x = []float64{} - - p.VariadicFloatVar(&x, name, desc, min) - - return &x -} - -// VariadicFunc install a variadic callback function -// as the last parameter(s) for the parameter set. The callback will be -// invoked when the parser reaches the end of the normal parameters. -// -// Parameters: -// - name: Name of the argument, used when printing usage. -// - desc: Description of the argument. Used when printing usage. -// - min: The minimum number of arguments that the command line must -// have for this parameter. -// - fn: Callback function invoked on the last arguments. If parsing is -// succesfull it should return nil, otherwise a non-nil error. -func (p *ParamSet) VariadicFunc(name, desc string, min int, fn func([]string) error) { - p.last = &VariadicParam{Name: name, Desc: desc, Min: min, parser: fn} -} diff --git a/params/paramset_examples_test.go b/params/paramset_examples_test.go deleted file mode 100644 index e0de279..0000000 --- a/params/paramset_examples_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package params_test - -import ( - "fmt" - "os" - - "github.com/mailund/cli/params" -) - -func ExampleParamSet() { - p := params.NewParamSet("name", params.ExitOnError) - - // Variables we want to parse - var ( - i int - b bool - ) - - p.IntVar(&i, "I", "an integer argument") - p.BoolVar(&b, "B", "a boolean argument") - f := p.Float("F", "a float argument") - s := p.String("S", "a string argument") - - // When we parse the parameter set, i, b, f, and s will - // get the parameters from the list of arguments. - _ = p.Parse([]string{"42", "t", "3.14", "foo"}) - - fmt.Printf("I=%d, B=%t, F=%f, S='%s'\n", i, b, *f, *s) - - // Output: I=42, B=true, F=3.140000, S='foo' -} - -func ExampleParamSet_second() { //nolint:funlen // Example functions can be long as well... (for now) - p := params.NewParamSet("sum", params.ExitOnError) - - // Variables we want to parse - var ( - op string - args []float64 - ) - - p.StringVar(&op, "op", "operation to perform") - p.VariadicFloatVar(&args, "args", "arg [args...]", 0) - - _ = p.Parse([]string{"+", "0.42", "3.14", "0.3"}) - - switch op { - case "+": - res := 0.0 - for _, x := range args { - res += x - } - - fmt.Printf("Result: %f\n", res) - - case "*": - res := 1.0 - for _, x := range args { - res *= x - } - - fmt.Printf("Result: %f\n", res) - - default: - fmt.Fprintf(os.Stderr, "I didn't implement any more ops!\n") - os.Exit(2) - } - - _ = p.Parse([]string{"*", "0.42", "3.14", "0.3"}) - - switch op { - case "+": - res := 0.0 - for _, x := range args { - res += x - } - - fmt.Printf("Result: %f\n", res) - - case "*": - res := 1.0 - for _, x := range args { - res *= x - } - - fmt.Printf("Result: %f\n", res) - - default: - fmt.Fprintf(os.Stderr, "I didn't implement any more ops!\n") - os.Exit(2) - } - - // Output: Result: 3.860000 - // Result: 0.395640 -} diff --git a/spec_callbacks_test.go b/spec_callbacks_test.go new file mode 100644 index 0000000..46ca5b9 --- /dev/null +++ b/spec_callbacks_test.go @@ -0,0 +1,156 @@ +package cli_test + +import ( + "strings" + "testing" + + "github.com/mailund/cli" +) + +type FlagsCallbacks struct { + A int // not part of the specs + + Fcb func(string) error `flag:"cb"` + Fcbi func(string, interface{}) error `flag:"cbi"` +} + +type PosCallbacks1 struct { + Pcb func(string) error `pos:"cb"` + Vcb func([]string) error `pos:"vcb"` +} + +type PosCallbacks2 struct { + A, B int // not part of the specs + + Pcbi func(string, interface{}) error `pos:"cbi"` + Vcbi func([]string, interface{}) error `pos:"vcbi"` +} + +func TestFlagCallbacks(t *testing.T) { + var ( + x, y string + ) + + args := &FlagsCallbacks{ + Fcb: func(s string) error { + x = s + return nil + }, + Fcbi: func(s string, i interface{}) error { + args, ok := i.(*FlagsCallbacks) + if !ok { + t.Fatal("Fcbi was not called with the right interface") + } + + y = s + args.A = 13 + + return nil + }, + } + + cmd := cli.NewCommand( + cli.CommandSpec{ + Init: func() interface{} { return args }, + }) + + cmd.Run([]string{"-cb=foo", "-cbi=bar"}) + + if x != "foo" { + t.Error("x was not set correctly") + } + + if y != "bar" { + t.Error("y was not set correctly") + } + + if args.A != 13 { + t.Error("A was not set correctly") + } +} + +func TestPosCallbacks1(t *testing.T) { + var ( + x, y string + ) + + args := &PosCallbacks1{ + Pcb: func(s string) error { + x = s + return nil + }, + Vcb: func(s []string) error { + y = strings.Join(s, "") + return nil + }, + } + + cmd := cli.NewCommand( + cli.CommandSpec{ + Init: func() interface{} { return args }, + }) + + cmd.Run([]string{"foo", "bar", "baz"}) + + if x != "foo" { + t.Error("x was not set correctly") + } + + if y != "barbaz" { + t.Error("y was not set correctly") + } +} + +func TestPosCallbacks2(t *testing.T) { + var ( + x, y string + ) + + args := &PosCallbacks2{ + Pcbi: func(s string, i interface{}) error { + args, ok := i.(*PosCallbacks2) + if !ok { + t.Fatal("pcbi called with wrong interface") + } + + args.A = 13 + x = s + + return nil + }, + Vcbi: func(s []string, i interface{}) error { + args, ok := i.(*PosCallbacks2) + if !ok { + t.Fatal("vcbi called with wrong interface") + } + + args.B = 42 + y = strings.Join(s, "") + + return nil + }, + } + + cmd := cli.NewCommand( + cli.CommandSpec{ + Init: func() interface{} { return args }, + }) + + cmd.Run([]string{"foo", "bar", "baz"}) + + if x != "foo" { + t.Error("x was not set correctly") + } + + if y != "barbaz" { + t.Error("y was not set correctly") + } + + if args.A != 13 { + t.Error("A was not set correctly") + } + + if args.B != 42 { + t.Error("B was not set correctly") + } +} diff --git a/specs.go b/specs.go index 0644819..4f70fb5 100644 --- a/specs.go +++ b/specs.go @@ -1,101 +1,44 @@ package cli import ( - "flag" - "fmt" "reflect" "strconv" - "github.com/mailund/cli/params" + "github.com/mailund/cli/interfaces" + "github.com/mailund/cli/internal/vals" ) -// SpecError is the error type returned if there are problems with a -// specification -type SpecError struct { - Message string -} - -// SpecErrorf creates a SpecError from a format string and arguments -func SpecErrorf(format string, args ...interface{}) *SpecError { - return &SpecError{fmt.Sprintf(format, args...)} -} - -// Error returns a string representation of a SpecError, implementing -// the error interface. -func (err *SpecError) Error() string { - return err.Message -} - -// FIXME: there must be a better way of getting the reflection type -// of a function type... -var uglyHack func(string) error // just a way to get the signature -var callbackSignature = reflect.TypeOf(uglyHack) - -func setFlag(f *flag.FlagSet, name string, tfield *reflect.StructField, vfield *reflect.Value) error { - switch tfield.Type.Kind() { - case reflect.Bool: - f.BoolVar(vfield.Addr().Interface().(*bool), name, vfield.Bool(), tfield.Tag.Get("descr")) - - case reflect.Int: - f.IntVar(vfield.Addr().Interface().(*int), name, int(vfield.Int()), tfield.Tag.Get("descr")) - - case reflect.Float64: - f.Float64Var(vfield.Addr().Interface().(*float64), name, vfield.Float(), tfield.Tag.Get("descr")) - - case reflect.String: - f.StringVar(vfield.Addr().Interface().(*string), name, vfield.String(), tfield.Tag.Get("descr")) - - case reflect.Func: - if tfield.Type != callbackSignature { - return SpecErrorf("callbacks must have signature func(string) error") - } +func setFlag(cmd *Command, argv interface{}, name string, tfield *reflect.StructField, vfield *reflect.Value) error { + if val := vals.AsFlagValue(vfield.Addr()); val != nil { + cmd.flags.Var(val, name, tfield.Tag.Get("descr")) + return nil + } + if tfield.Type.Kind() == reflect.Func { if vfield.IsNil() { - return SpecErrorf("callbacks cannot be nil") + return interfaces.SpecErrorf("callbacks cannot be nil") } - f.Func(name, tfield.Tag.Get("descr"), vfield.Interface().(func(string) error)) + if val := vals.AsCallback(vfield, argv); val != nil { + cmd.flags.Var(val, name, tfield.Tag.Get("descr")) + return nil + } - default: - return SpecErrorf("unsupported type for flag %s: %q", name, tfield.Type.Kind()) + return interfaces.SpecErrorf("incorrect signature for callbacks: %q", tfield.Type) } - return nil + return interfaces.SpecErrorf("unsupported type for flag %s: %q", name, tfield.Type.Kind()) } -func setParam(p *params.ParamSet, name string, tfield *reflect.StructField, vfield *reflect.Value) error { - switch tfield.Type.Kind() { - case reflect.Bool: - p.BoolVar(vfield.Addr().Interface().(*bool), name, tfield.Tag.Get("descr")) - - case reflect.Int: - p.IntVar(vfield.Addr().Interface().(*int), name, tfield.Tag.Get("descr")) - - case reflect.Float64: - p.FloatVar(vfield.Addr().Interface().(*float64), name, tfield.Tag.Get("descr")) - - case reflect.String: - p.StringVar(vfield.Addr().Interface().(*string), name, tfield.Tag.Get("descr")) - - case reflect.Func: - if tfield.Type != callbackSignature { - return SpecErrorf("callbacks must have signature func(string) error") - } - - if vfield.IsNil() { - return SpecErrorf("callbacks cannot be nil") - } - - p.Func(name, tfield.Tag.Get("descr"), vfield.Interface().(func(string) error)) - - default: - return SpecErrorf("unsupported type for parameter %s: %q", name, tfield.Type.Kind()) +func setVariadic(cmd *Command, name string, val interfaces.VariadicValue, tfield *reflect.StructField) error { + if len(cmd.Subcommands) > 0 { + return interfaces.SpecErrorf("a command with subcommands cannot have variadic parameters") } - return nil -} + if cmd.params.Variadic() != nil { + return interfaces.SpecErrorf("a command spec cannot contain more than one variadic parameter") + } -func setVariadicParam(p *params.ParamSet, name string, tfield *reflect.StructField, vfield *reflect.Value) error { var ( min int err error @@ -104,60 +47,60 @@ func setVariadicParam(p *params.ParamSet, name string, tfield *reflect.StructFie if minTag := tfield.Tag.Get("min"); minTag == "" { min = 0 } else if min, err = strconv.Atoi(tfield.Tag.Get("min")); err != nil { - return SpecErrorf("unexpected min value for variadic parameter %s: %s", name, minTag) + return interfaces.SpecErrorf("unexpected min value for variadic parameter %s: %s", name, minTag) } - switch tfield.Type.Elem().Kind() { - case reflect.Bool: - p.VariadicBoolVar(vfield.Addr().Interface().(*[]bool), name, tfield.Tag.Get("descr"), min) + cmd.params.VariadicVar(val, name, tfield.Tag.Get("descr"), min) - case reflect.Int: - p.VariadicIntVar(vfield.Addr().Interface().(*[]int), name, tfield.Tag.Get("descr"), min) + return nil +} - case reflect.Float64: - p.VariadicFloatVar(vfield.Addr().Interface().(*[]float64), name, tfield.Tag.Get("descr"), min) +func setParam(cmd *Command, argv interface{}, name string, tfield *reflect.StructField, vfield *reflect.Value) error { + if val := vals.AsPosValue(vfield.Addr()); val != nil { + cmd.params.Var(val, name, tfield.Tag.Get("descr")) + return nil + } - case reflect.String: - p.VariadicStringVar(vfield.Addr().Interface().(*[]string), name, tfield.Tag.Get("descr"), min) + if val := vals.AsVariadicValue(vfield.Addr()); val != nil { + return setVariadic(cmd, name, val, tfield) + } - default: - return SpecErrorf("unsupported slice type for parameter %s: %q", name, tfield.Type.Elem().Kind()) + if tfield.Type.Kind() == reflect.Func { + if vfield.IsNil() { + return interfaces.SpecErrorf("callbacks cannot be nil") + } + + if val := vals.AsCallback(vfield, argv); val != nil { + cmd.params.Var(val, name, tfield.Tag.Get("descr")) + return nil + } + + if val := vals.AsVariadicCallback(vfield, argv); val != nil { + return setVariadic(cmd, name, val, tfield) + } + + return interfaces.SpecErrorf("incorrect signature for callbacks: %q", tfield.Type) } - return nil + return interfaces.SpecErrorf("unsupported type for parameter %s: %q", name, tfield.Type.Kind()) } -func connectSpecsFlagsAndParams(f *flag.FlagSet, p *params.ParamSet, argv interface{}, allowVariadic bool) error { +func connectSpecsFlagsAndParams(cmd *Command, argv interface{}) error { reflectVal := reflect.Indirect(reflect.ValueOf(argv)) reflectTyp := reflectVal.Type() - seenVariadic := false for i := 0; i < reflectTyp.NumField(); i++ { tfield := reflectTyp.Field(i) vfield := reflectVal.Field(i) if name, isFlag := tfield.Tag.Lookup("flag"); isFlag { - if err := setFlag(f, name, &tfield, &vfield); err != nil { + if err := setFlag(cmd, argv, name, &tfield, &vfield); err != nil { return err } } if name, isPos := tfield.Tag.Lookup("pos"); isPos { - if tfield.Type.Kind() == reflect.Slice { - if !allowVariadic { - return SpecErrorf("a command with subcommands cannot have variadic parameters") - } - - if seenVariadic { - return SpecErrorf("a command spec cannot contain more than one variadic parameter") - } - - seenVariadic = true - - if err := setVariadicParam(p, name, &tfield, &vfield); err != nil { - return err - } - } else if err := setParam(p, name, &tfield, &vfield); err != nil { + if err := setParam(cmd, argv, name, &tfield, &vfield); err != nil { return err } } diff --git a/specs_internal_test.go b/specs_internal_test.go index 865bccf..9d40b72 100644 --- a/specs_internal_test.go +++ b/specs_internal_test.go @@ -7,7 +7,8 @@ import ( "strconv" "testing" - "github.com/mailund/cli/params" + "github.com/mailund/cli/interfaces" + "github.com/mailund/cli/internal/params" ) func checkFlags(t *testing.T, f *flag.FlagSet, argv interface{}) { @@ -210,7 +211,7 @@ func Test_prepareSpecs(t *testing.T) { //nolint:funlen // Test functions can be }), true, }, - err: SpecErrorf(`unsupported type for flag b: "slice"`), + err: interfaces.SpecErrorf(`unsupported type for flag b: "slice"`), }, { name: "Unsupported parameter type", @@ -218,11 +219,11 @@ func Test_prepareSpecs(t *testing.T) { //nolint:funlen // Test functions can be flag.NewFlagSet("test", flag.ExitOnError), params.NewParamSet("test", flag.ExitOnError), new(struct { - B complex128 `pos:"b"` + B uintptr `pos:"b"` }), true, }, - err: SpecErrorf(`unsupported type for parameter b: "complex128"`), + err: interfaces.SpecErrorf(`unsupported type for parameter b: "uintptr"`), }, { @@ -279,21 +280,9 @@ func Test_prepareSpecs(t *testing.T) { //nolint:funlen // Test functions can be }), true, }, - err: SpecErrorf(`unexpected min value for variadic parameter x: not an int`), + err: interfaces.SpecErrorf(`unexpected min value for variadic parameter x: not an int`), }, - { - name: "Unsupported variadic parameter type", - args: args{ - flag.NewFlagSet("test", flag.ExitOnError), - params.NewParamSet("test", flag.ExitOnError), - new(struct { - B []func(x, y int) int `pos:"b"` - }), - true, - }, - err: SpecErrorf(`unsupported slice type for parameter b: "func"`), - }, { name: "More than one variadic", args: args{ @@ -305,7 +294,7 @@ func Test_prepareSpecs(t *testing.T) { //nolint:funlen // Test functions can be }), true, }, - err: SpecErrorf("a command spec cannot contain more than one variadic parameter"), + err: interfaces.SpecErrorf("a command spec cannot contain more than one variadic parameter"), }, { @@ -318,31 +307,39 @@ func Test_prepareSpecs(t *testing.T) { //nolint:funlen // Test functions can be }), true, }, - err: SpecErrorf("callbacks cannot be nil"), + err: interfaces.SpecErrorf("callbacks cannot be nil"), }, { name: "Flag callback wrong signature 1", args: args{ flag.NewFlagSet("test", flag.ExitOnError), params.NewParamSet("test", flag.ExitOnError), - new(struct { - A func(int) error `flag:"a"` - }), + func() interface{} { + x := new(struct { + A func(int) error `flag:"a"` + }) + x.A = func(int) error { return nil } + return x + }(), true, }, - err: SpecErrorf("callbacks must have signature func(string) error"), + err: interfaces.SpecErrorf(`incorrect signature for callbacks: "func(int) error"`), }, { name: "Flag callback wrong signature 2", args: args{ flag.NewFlagSet("test", flag.ExitOnError), params.NewParamSet("test", flag.ExitOnError), - new(struct { - A func(string) `flag:"a"` - }), + func() interface{} { + x := new(struct { + A func(string) `flag:"a"` + }) + x.A = func(string) {} + return x + }(), true, }, - err: SpecErrorf("callbacks must have signature func(string) error"), + err: interfaces.SpecErrorf(`incorrect signature for callbacks: "func(string)"`), }, { name: "Flag callbacks non-nil", @@ -379,31 +376,39 @@ func Test_prepareSpecs(t *testing.T) { //nolint:funlen // Test functions can be }), true, }, - err: SpecErrorf("callbacks cannot be nil"), + err: interfaces.SpecErrorf("callbacks cannot be nil"), }, { name: "Params callback wrong signature 1", args: args{ flag.NewFlagSet("test", flag.ExitOnError), params.NewParamSet("test", flag.ExitOnError), - new(struct { - A func(int) error `pos:"a"` - }), + func() interface{} { + x := new(struct { + A func(int) error `pos:"a"` + }) + x.A = func(int) error { return nil } + return x + }(), true, }, - err: SpecErrorf("callbacks must have signature func(string) error"), + err: interfaces.SpecErrorf(`incorrect signature for callbacks: "func(int) error"`), }, { name: "Params callback wrong signature 2", args: args{ flag.NewFlagSet("test", flag.ExitOnError), params.NewParamSet("test", flag.ExitOnError), - new(struct { - A func(string) `pos:"a"` - }), + func() interface{} { + x := new(struct { + A func(string) `pos:"a"` + }) + x.A = func(string) {} + return x + }(), true, }, - err: SpecErrorf("callbacks must have signature func(string) error"), + err: interfaces.SpecErrorf(`incorrect signature for callbacks: "func(string)"`), }, { name: "Param callbacks non-nil", @@ -433,9 +438,10 @@ func Test_prepareSpecs(t *testing.T) { //nolint:funlen // Test functions can be for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := connectSpecsFlagsAndParams(tt.args.f, tt.args.p, tt.args.argv, tt.args.allowVariadic); err != nil { + cmd := &Command{flags: tt.args.f, params: tt.args.p} + if err := connectSpecsFlagsAndParams(cmd, tt.args.argv); err != nil { if tt.err == nil { - t.Fatalf("Got an error, but did not expect one") + t.Fatalf("Got an error, but did not expect one. Got: %s", err.Error()) } // FIXME: This is a bit vulnerable. I'm checking the string in the errors. I should // add parameters to the error type so I could check without expecting error messages diff --git a/test_programs/calc/calc.go b/test_programs/calc/calc.go new file mode 100644 index 0000000..a9f54a9 --- /dev/null +++ b/test_programs/calc/calc.go @@ -0,0 +1,47 @@ +package main + +import ( + "fmt" + "os" + + "github.com/mailund/cli" +) + +type CalcArgs struct { + X int `pos:"x" descr:"first addition argument"` + Y int `pos:"y" descr:"first addition argument"` +} + +func main() { + add := cli.NewCommand( + cli.CommandSpec{ + Name: "add", + Short: "adds two floating point arguments", + Long: "", + Init: func() interface{} { return new(CalcArgs) }, + Action: func(args interface{}) { + fmt.Printf("Result: %d\n", args.(*CalcArgs).X+args.(*CalcArgs).Y) + }, + }) + + mult := cli.NewCommand( + cli.CommandSpec{ + Name: "mult", + Short: "multiplies two floating point arguments", + Long: "", + Init: func() interface{} { return new(CalcArgs) }, + Action: func(args interface{}) { + fmt.Printf("Result: %d\n", args.(*CalcArgs).X*args.(*CalcArgs).Y) + }, + }) + + calc := cli.NewCommand( + cli.CommandSpec{ + Name: "calc", + Short: "does calculations", + Long: "", + Subcommands: []*cli.Command{add, mult}, + }) + + calc.Run(os.Args[1:]) +} diff --git a/test_programs/helloworld/helloworld.go b/test_programs/helloworld/helloworld.go new file mode 100644 index 0000000..017080e --- /dev/null +++ b/test_programs/helloworld/helloworld.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "os" + + "github.com/mailund/cli" +) + +func main() { + cmd := cli.NewCommand( + cli.CommandSpec{ + Name: "hello", + Long: "Prints hello world", + Action: func(_ interface{}) { fmt.Println("hello, world!") }, + }) + + cmd.Run(os.Args[1:]) +} diff --git a/test_programs/validate/validate b/test_programs/validate/validate new file mode 100755 index 0000000..e162af4 Binary files /dev/null and b/test_programs/validate/validate differ diff --git a/test_programs/validate/validate.go b/test_programs/validate/validate.go new file mode 100644 index 0000000..5d34943 --- /dev/null +++ b/test_programs/validate/validate.go @@ -0,0 +1,68 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/mailund/cli" + "github.com/mailund/cli/interfaces" +) + +type Validator struct { + validator func(string) bool + x string +} + +func upperValidator(x string) bool { return strings.ToUpper(x) == x } +func lowerValidator(x string) bool { return strings.ToLower(x) == x } + +func (val *Validator) setUpperValidator() { + val.validator = upperValidator +} + +func (val *Validator) setLowerValidator() { + val.validator = lowerValidator +} + +// Implementing Set(string) error means we can use a Validator as a +// positional argument +func (val *Validator) Set(x string) error { + if val.validator != nil && !val.validator(x) { + return interfaces.ParseErrorf("'%s' is not a valid string", x) + } + + val.x = x + + return nil +} + +type Args struct { + Upper func() `flag:"u" descr:"sets the validator to upper"` + Lower func() `flag:"l" descr:"sets the validator to lower"` + Val Validator `pos:"string" descr:"string we might do something to, if valid"` +} + +// Now we have everything ready to set up the command. +func Init() interface{} { + args := Args{} + args.Upper = args.Val.setUpperValidator + args.Lower = args.Val.setLowerValidator + + return &args +} + +func Action(args interface{}) { + fmt.Println("A valid string:", args.(*Args).Val.x) +} + +func main() { + cmd := cli.NewCommand( + cli.CommandSpec{ + Name: "validator", + Long: "Will only accept valid strings", + Init: Init, + Action: Action, + }) + cmd.Run(os.Args[1:]) +}