diff --git a/.drone.yml b/.drone.yml index 745ce29..97b9960 100644 --- a/.drone.yml +++ b/.drone.yml @@ -3,7 +3,7 @@ name: default steps: - name: golangci-lint - image: golangci/golangci-lint:v1.26 + image: golangci/golangci-lint:v1.49 volumes: - name: deps path: /go/src/mod diff --git a/.golangci.yml b/.golangci.yml index f504a9e..fa1a32c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -16,6 +16,9 @@ linters-settings: mnd: # don't include the "operation" and "assign" checks: argument,case,condition,return + ignored-functions: + - "strconv.*" + - "strings.*" govet: check-shadowing: true lll: @@ -24,9 +27,27 @@ linters-settings: suggest-new: true misspell: locale: US + varnamelen: + min-name-length: 2 + staticcheck: + checks: ["all","-SA1030"] linters: enable-all: true + disable: + - varcheck + - maligned + - scopelint + - nosnakecase + - ifshort + - golint + - interfacer + - structcheck + - deadcode + - exhaustivestruct + + - ireturn + - exhaustruct issues: # Excluding configuration per-path, per-linter, per-text and per-source @@ -34,3 +55,7 @@ issues: - path: _test\.go linters: - gomnd + - varnamelen + - path: input/variable + linters: + - dupl diff --git a/app.go b/app.go index d56af9e..1d24234 100644 --- a/app.go +++ b/app.go @@ -25,9 +25,7 @@ func WithInput(in input.Input) func(*App) { // WithSkipArgs sets how many arguments are passed. For example, you don't need to pass the name of a single command. func WithSkipArgs(l int) func(*App) { - return func(a *App) { - a.skipArgv = l - } + return WithInput(input.NewArgs(l)) } // WithExit sets exit callback by default os.Exit. @@ -39,44 +37,25 @@ func WithExit(f func(int)) func(*App) { // New creates and configure new console app. func New(opts ...func(*App)) *App { - a := &App{ + app := &App{ out: output.Stdout(), exit: os.Exit, + in: input.NewArgs(0), } for _, opt := range opts { - opt(a) - } - - if a.in == nil { - skip := 2 - - switch { - case a.skipArgv > 0 && len(os.Args) > a.skipArgv: - skip = a.skipArgv - case a.skipArgv > 0: - skip = len(os.Args) - case len(os.Args) == 1: - skip = 1 - case len(os.Args) > 1 && os.Args[1][0] == '-': - skip = 1 - } - - a.in = &input.Argv{ - Args: os.Args[skip:], - } + opt(app) } - return a + return app } // App is collection of command and configure env. type App struct { - cmds []*Command - out output.Output - in input.Input - skipArgv int - exit func(int) + cmds []*Command + out output.Output + in input.Input + exit func(int) } // Add add or replace command. diff --git a/app_test.go b/app_test.go index 6148c73..9e88c35 100644 --- a/app_test.go +++ b/app_test.go @@ -5,10 +5,9 @@ import ( "os" "gitoa.ru/go-4devs/console" - "gitoa.ru/go-4devs/console/example/pkg/command" ) -//nolint: lll +//nolint:lll func ExampleNew_help() { ctx := context.Background() os.Args = []string{ @@ -56,9 +55,18 @@ func ExampleNew_list() { console.New(console.WithExit(func(int) {})). Add( Command(), - command.Hello(), - command.Args(), - command.Namespace(), + &console.Command{ + Name: "fdevs:console:arg", + Description: "Understanding how Console Arguments and Options Are Handled", + }, + &console.Command{ + Name: "fdevs:console:hello", + Description: "example hello command", + }, + &console.Command{ + Name: "app:start", + Description: "example command in other namespace", + }, ). Execute(ctx) // Output: diff --git a/command.go b/command.go index 03abb77..10d66b1 100644 --- a/command.go +++ b/command.go @@ -17,38 +17,38 @@ type ( ) // WithPrepare append middleware for configuration command. -func WithPrepare(p ...Prepare) Option { +func WithPrepare(prepares ...Prepare) Option { return func(c *Command) { if c.Prepare != nil { - p = append([]Prepare{c.Prepare}, p...) + prepares = append([]Prepare{c.Prepare}, prepares...) } - c.Prepare = ChainPrepare(p...) + c.Prepare = ChainPrepare(prepares...) } } // WithHandle append middleware for executed command. -func WithHandle(h ...Handle) Option { +func WithHandle(handles ...Handle) Option { return func(c *Command) { if c.Handle != nil { - h = append([]Handle{c.Handle}, h...) + handles = append([]Handle{c.Handle}, handles...) } - c.Handle = ChainHandle(h...) + c.Handle = ChainHandle(handles...) } } // WithHidden sets hidden command. -func WithHidden(v bool) Option { +func WithHidden(hidden bool) Option { return func(c *Command) { - c.Hidden = v + c.Hidden = hidden } } // WithName sets name command. -func WithName(n string) Option { +func WithName(name string) Option { return func(c *Command) { - c.Name = n + c.Name = name } } @@ -125,13 +125,13 @@ func (c *Command) Init(ctx context.Context, cfg *input.Definition) error { // ChainPrepare creates middleware for configures command. func ChainPrepare(prepare ...Prepare) Prepare { - n := len(prepare) - if n == 1 { + num := len(prepare) + if num == 1 { return prepare[0] } - if n > 1 { - lastI := n - 1 + if num > 1 { + lastI := num - 1 return func(ctx context.Context, def *input.Definition, next Configure) error { var ( @@ -161,13 +161,13 @@ func ChainPrepare(prepare ...Prepare) Prepare { // ChainHandle creates middleware for executes command. func ChainHandle(handlers ...Handle) Handle { - n := len(handlers) - if n == 1 { + num := len(handlers) + if num == 1 { return handlers[0] } - if n > 1 { - lastI := n - 1 + if num > 1 { + lastI := num - 1 return func(ctx context.Context, in input.Input, out output.Output, next Action) error { var ( diff --git a/command_test.go b/command_test.go index 500a2c2..91e4207 100644 --- a/command_test.go +++ b/command_test.go @@ -8,17 +8,16 @@ import ( "time" "gitoa.ru/go-4devs/console" - "gitoa.ru/go-4devs/console/example/pkg/command" "gitoa.ru/go-4devs/console/input" "gitoa.ru/go-4devs/console/input/argument" "gitoa.ru/go-4devs/console/input/option" "gitoa.ru/go-4devs/console/output" ) -//nolint: gochecknoinits +//nolint:gochecknoinits func init() { console.MustRegister(Command().With(console.WithName("fdevs:console:test"))) - console.MustRegister(command.Args()) + console.MustRegister(Command().With(console.WithName("fdevs:console:arg"))) } func Command() *console.Command { @@ -38,10 +37,10 @@ func Command() *console.Command { Configure: func(ctx context.Context, def *input.Definition) error { def. SetArguments( - argument.New("test_argument", "test argument"), + argument.String("test_argument", "test argument"), ). SetOptions( - option.New("string", "array string", option.Array), + option.String("string", "array string", option.Array), option.Bool("bool", "test bool option"), option.Duration("duration", "test duration with default", option.Default(time.Second)), ) @@ -52,6 +51,8 @@ func Command() *console.Command { } func TestChainPrepare(t *testing.T) { + t.Parallel() + var cnt int32 ctx := context.Background() @@ -86,10 +87,14 @@ func TestChainPrepare(t *testing.T) { } func TestChainHandle(t *testing.T) { + t.Parallel() + var cnt int32 ctx := context.Background() - in := &input.Array{} + in := &input.Array{ + Map: input.Map{}, + } out := output.Stdout() handle := func(ctx context.Context, in input.Input, out output.Output, next console.Action) error { diff --git a/console.go b/console.go index 11d289f..a42346c 100644 --- a/console.go +++ b/console.go @@ -86,14 +86,14 @@ func verbose(ctx context.Context, in input.Input, out output.Output) output.Outp case in.Option(ctx, "quiet").Bool(): out = output.Quiet() default: - v := in.Option(ctx, "verbose").Bools() + verb := in.Option(ctx, "verbose").Bools() switch { - case len(v) == verboseInfo: + case len(verb) == verboseInfo: out = output.Verbosity(out, verbosity.Info) - case len(v) == verboseDebug: + case len(verb) == verboseDebug: out = output.Verbosity(out, verbosity.Debug) - case len(v) >= verboseTrace: + case len(verb) >= verboseTrace: out = output.Verbosity(out, verbosity.Trace) default: out = output.Verbosity(out, verbosity.Norm) @@ -104,9 +104,9 @@ func verbose(ctx context.Context, in input.Input, out output.Output) output.Outp } func showHelp(ctx context.Context, cmd *Command, in input.Input, out output.Output) error { - a := &input.Array{} - a.SetArgument(HelpArgumentCommandName, value.New(cmd.Name)) - a.SetOption("help", value.New(false)) + arr := &input.Array{} + arr.SetArgument(HelpArgumentCommandName, value.New(cmd.Name)) + arr.SetOption("help", value.New(false)) if _, err := Find(cmd.Name); errors.Is(err, ErrNotFound) { register(cmd) @@ -117,7 +117,7 @@ func showHelp(ctx context.Context, cmd *Command, in input.Input, out output.Outp return err } - w := input.Chain(a, in) + w := input.Chain(arr, in) return Run(ctx, help, w, out) } @@ -127,11 +127,11 @@ func Default(d *input.Definition) *input.Definition { return d.SetOptions( option.Bool("no-ansi", "Disable ANSI output"), option.Bool("ansi", "Do not ask any interactive question"), - option.Bool("version", "Display this application version", option.Short("V")), - option.Bool("help", "Display this help message", option.Short("h")), + option.Bool("version", "Display this application version", option.Short('V')), + option.Bool("help", "Display this help message", option.Short('h')), option.Bool("verbose", "Increase the verbosity of messages: -v for info output, -vv for debug and -vvv for trace", - option.Short("v"), option.Array), - option.Bool("quiet", "Do not output any message", option.Short("q")), + option.Short('v'), option.Array), + option.Bool("quiet", "Do not output any message", option.Short('q')), ) } diff --git a/doc.go b/doc.go index cd64aac..5cb325e 100644 --- a/doc.go +++ b/doc.go @@ -2,29 +2,32 @@ // The Console package allows you to create command-line commands. // Your console commands can be used for any recurring task, such as cronjobs, imports, or other batch jobs. // console application can be written as follows: -// //cmd/console/main.go -// func main() { -// console.New().Execute(context.Background()) -// } +// +// //cmd/console/main.go +// func main() { +// console.New().Execute(context.Background()) +// } +// // Then, you can register the commands using Add(): -// package main // -// import ( -// "context" +// package main +// +// import ( +// "context" // -// "gitoa.ru/go-4devs/console" -// "gitoa.ru/go-4devs/console/example/pkg/command" -// ) +// "gitoa.ru/go-4devs/console" +// "gitoa.ru/go-4devs/console/example/pkg/command" +// ) // -// func main() { -// console. -// New(). -// Add( -// command.Hello(), -// command.Args(), -// command.Hidden(), -// command.Namespace(), -// ). -// Execute(context.Background()) -// } +// func main() { +// console. +// New(). +// Add( +// command.Hello(), +// command.Args(), +// command.Hidden(), +// command.Namespace(), +// ). +// Execute(context.Background()) +// } package console diff --git a/example/pkg/command/args.go b/example/pkg/command/args.go index 3cf145a..f1f40af 100644 --- a/example/pkg/command/args.go +++ b/example/pkg/command/args.go @@ -2,6 +2,7 @@ package command import ( "context" + "time" "gitoa.ru/go-4devs/console" "gitoa.ru/go-4devs/console/input" @@ -15,9 +16,10 @@ func Args() *console.Command { Description: "Understanding how Console Arguments and Options Are Handled", Configure: func(ctx context.Context, def *input.Definition) error { def.SetOptions( - option.Bool("foo", "foo option", option.Short("f")), - option.New("bar", "required bar option", option.Required, option.Short("b")), - option.New("cat", "cat option", option.Short("c")), + option.Bool("foo", "foo option", option.Short('f')), + option.String("bar", "required bar option", option.Required, option.Short('b')), + option.String("cat", "cat option", option.Short('c')), + option.Time("time", "time example"), ) return nil @@ -26,6 +28,7 @@ func Args() *console.Command { out.Println(ctx, "foo: ", in.Option(ctx, "foo").Bool(), "") out.Println(ctx, "bar: ", in.Option(ctx, "bar").String(), "") out.Println(ctx, "cat: ", in.Option(ctx, "cat").String(), "") + out.Println(ctx, "time: ", in.Option(ctx, "time").Time().Format(time.RFC3339), "") return nil }, diff --git a/example/pkg/command/create_user.go b/example/pkg/command/create_user.go index 620e896..2a52882 100644 --- a/example/pkg/command/create_user.go +++ b/example/pkg/command/create_user.go @@ -6,6 +6,7 @@ import ( "gitoa.ru/go-4devs/console" "gitoa.ru/go-4devs/console/input" "gitoa.ru/go-4devs/console/input/argument" + "gitoa.ru/go-4devs/console/input/variable" "gitoa.ru/go-4devs/console/output" ) @@ -15,7 +16,7 @@ func CreateUser(required bool) *console.Command { Description: "Creates a new user.", Help: "This command allows you to create a user...", Configure: func(ctx context.Context, cfg *input.Definition) error { - var opts []func(*argument.Argument) + var opts []variable.Option if required { opts = append(opts, argument.Required) } diff --git a/example/pkg/command/create_user_test.go b/example/pkg/command/create_user_test.go index 84b74f6..c6b051f 100644 --- a/example/pkg/command/create_user_test.go +++ b/example/pkg/command/create_user_test.go @@ -12,6 +12,8 @@ import ( ) func TestCreateUser(t *testing.T) { + t.Parallel() + ctx := context.Background() buf := bytes.Buffer{} out := output.Buffer(&buf) diff --git a/example/pkg/command/hello.go b/example/pkg/command/hello.go index 85948f5..26ea75c 100644 --- a/example/pkg/command/hello.go +++ b/example/pkg/command/hello.go @@ -25,7 +25,7 @@ func Hello() *console.Command { }, Configure: func(_ context.Context, def *input.Definition) error { def.SetArguments( - argument.New("name", "Same name", argument.Default("World")), + argument.String("name", "Same name", argument.Default("World")), ) return nil diff --git a/example/pkg/command/long.go b/example/pkg/command/long.go index a0dcd05..0b44289 100644 --- a/example/pkg/command/long.go +++ b/example/pkg/command/long.go @@ -6,9 +6,9 @@ import ( "gitoa.ru/go-4devs/console" "gitoa.ru/go-4devs/console/input" + "gitoa.ru/go-4devs/console/input/flag" "gitoa.ru/go-4devs/console/input/option" "gitoa.ru/go-4devs/console/input/validator" - "gitoa.ru/go-4devs/console/input/value/flag" "gitoa.ru/go-4devs/console/output" ) @@ -41,7 +41,7 @@ func Long() *console.Command { Configure: func(ctx context.Context, def *input.Definition) error { def.SetOptions(option.Duration("timeout", "set duration run command", option.Default(defaultTimeout), - option.Short("t"), + option.Short('t'), option.Valid(validator.NotBlank(flag.Duration)), )) diff --git a/go.mod b/go.mod index 357bc26..7db15fe 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,9 @@ module gitoa.ru/go-4devs/console go 1.15 + +require ( + github.com/kr/pretty v0.1.0 // indirect + github.com/stretchr/testify v1.8.0 + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect +) diff --git a/go.sum b/go.sum index e69de29..0edc720 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,21 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/help.go b/help.go index 070c3c8..ceb9775 100644 --- a/help.go +++ b/help.go @@ -8,14 +8,15 @@ import ( "gitoa.ru/go-4devs/console/input" "gitoa.ru/go-4devs/console/input/argument" + "gitoa.ru/go-4devs/console/input/flag" "gitoa.ru/go-4devs/console/input/option" "gitoa.ru/go-4devs/console/input/validator" - "gitoa.ru/go-4devs/console/input/value/flag" + "gitoa.ru/go-4devs/console/input/value" "gitoa.ru/go-4devs/console/output" "gitoa.ru/go-4devs/console/output/descriptor" ) -//nolint: gochecknoinits +//nolint:gochecknoinits func init() { MustRegister(help()) } @@ -43,18 +44,18 @@ To display the list of available commands, please use the list comm des, err := descriptor.Find(format) if err != nil { - return err + return fmt.Errorf("find descriptor: %w", err) } cmd, err := Find(name) if err != nil { - return err + return fmt.Errorf("find cmd: %w", err) } def := input.NewDefinition() if err := cmd.Init(ctx, Default(def)); err != nil { - return err + return fmt.Errorf("init cmd: %w", err) } var bin string @@ -62,22 +63,28 @@ To display the list of available commands, please use the list comm bin = os.Args[0] } - return des.Command(ctx, out, descriptor.Command{ + derr := des.Command(ctx, out, descriptor.Command{ Bin: bin, Name: cmd.Name, Description: cmd.Description, Help: cmd.Help, Definition: def, }) + + if derr != nil { + return fmt.Errorf("descriptor help:%w", derr) + } + + return nil }, Configure: func(ctx context.Context, config *input.Definition) error { formats := descriptor.Descriptors() config. SetArguments( - argument.New(HelpArgumentCommandName, "The command name", argument.Default("help")), + argument.String(HelpArgumentCommandName, "The command name", argument.Default(value.New("help"))), ). SetOptions( - option.New(helpOptFormat, fmt.Sprintf("The output format (%s)", strings.Join(formats, ", ")), + option.String(helpOptFormat, fmt.Sprintf("The output format (%s)", strings.Join(formats, ", ")), option.Required, option.Default(formats[0]), option.Valid( diff --git a/input/argument/argument.go b/input/argument/argument.go index b5f42bb..81c8f63 100644 --- a/input/argument/argument.go +++ b/input/argument/argument.go @@ -1,54 +1,58 @@ package argument import ( - "gitoa.ru/go-4devs/console/input/errs" "gitoa.ru/go-4devs/console/input/value" - "gitoa.ru/go-4devs/console/input/value/flag" + "gitoa.ru/go-4devs/console/input/variable" ) -func New(name, description string, opts ...func(*Argument)) Argument { - a := Argument{ - Name: name, - Description: description, - } +func Default(in interface{}) variable.Option { + return variable.Default(value.New(in)) +} - for _, opt := range opts { - opt(&a) - } +func Required(v *variable.Variable) { + variable.Required(v) +} - return a +func Array(v *variable.Variable) { + variable.Array(v) } -type Argument struct { - Name string - Description string - Default value.Value - Flag flag.Flag - Valid []func(value.Value) error +func String(name, description string, opts ...variable.Option) variable.Variable { + return variable.String(name, description, append(opts, variable.ArgArgument)...) } -func (a Argument) HasDefault() bool { - return a.Default != nil +func Bool(name, description string, opts ...variable.Option) variable.Variable { + return variable.Bool(name, description, append(opts, variable.ArgArgument)...) } -func (a Argument) IsBool() bool { - return a.Flag.IsBool() +func Duration(name, description string, opts ...variable.Option) variable.Variable { + return variable.Duration(name, description, append(opts, variable.ArgArgument)...) } -func (a Argument) IsRequired() bool { - return a.Flag.IsRequired() +func Float64(name, description string, opts ...variable.Option) variable.Variable { + return variable.Float64(name, description, append(opts, variable.ArgArgument)...) } -func (a Argument) IsArray() bool { - return a.Flag.IsArray() +func Int(name, description string, opts ...variable.Option) variable.Variable { + return variable.Int(name, description, append(opts, variable.ArgArgument)...) } -func (a Argument) Validate(v value.Value) error { - for _, valid := range a.Valid { - if err := valid(v); err != nil { - return errs.Argument(a.Name, err) - } - } +func Int64(name, description string, opts ...variable.Option) variable.Variable { + return variable.Int64(name, description, append(opts, variable.ArgArgument)...) +} + +func Time(name, description string, opts ...variable.Option) variable.Variable { + return variable.Time(name, description, append(opts, variable.ArgArgument)...) +} + +func Uint(name, description string, opts ...variable.Option) variable.Variable { + return variable.Uint(name, description, append(opts, variable.ArgArgument)...) +} + +func Uint64(name, descriontion string, opts ...variable.Option) variable.Variable { + return variable.Uint64(name, descriontion, append(opts, variable.ArgArgument)...) +} - return nil +func Err(name string, err error) variable.Error { + return variable.Err(name, variable.TypeArgument, err) } diff --git a/input/argument/option.go b/input/argument/option.go deleted file mode 100644 index 4422a57..0000000 --- a/input/argument/option.go +++ /dev/null @@ -1,26 +0,0 @@ -package argument - -import ( - "gitoa.ru/go-4devs/console/input/value" - "gitoa.ru/go-4devs/console/input/value/flag" -) - -func Required(a *Argument) { - a.Flag |= flag.Required -} - -func Default(v interface{}) func(*Argument) { - return func(a *Argument) { - a.Default = value.New(v) - } -} - -func Flag(flag flag.Flag) func(*Argument) { - return func(a *Argument) { - a.Flag = flag - } -} - -func Array(a *Argument) { - a.Flag |= flag.Array -} diff --git a/input/argv.go b/input/argv.go index d239fca..00a3438 100644 --- a/input/argv.go +++ b/input/argv.go @@ -3,20 +3,41 @@ package input import ( "context" "fmt" + "os" "strings" + "gitoa.ru/go-4devs/console/input/argument" "gitoa.ru/go-4devs/console/input/errs" "gitoa.ru/go-4devs/console/input/option" + "gitoa.ru/go-4devs/console/input/variable" ) const doubleDash = `--` +func NewArgs(skip int) *Argv { + res := 2 + + switch { + case skip > 0 && len(os.Args) > skip: + res = skip + case skip > 0: + res = len(os.Args) + case len(os.Args) == 1: + res = 1 + case len(os.Args) > 1 && os.Args[1][0] == '-': + res = 1 + } + + return &Argv{Args: os.Args[res:]} +} + type Argv struct { Array Args []string ErrHandle func(error) error } +//nolint:cyclop func (i *Argv) Bind(ctx context.Context, def *Definition) error { options := true @@ -64,13 +85,13 @@ func (i *Argv) parseLongOption(arg string, def *Definition) error { opt, err := def.Option(name) if err != nil { - return errs.Option(name, err) + return option.Err(name, err) } return i.appendOption(name, value, opt) } -func (i *Argv) appendOption(name string, data *string, opt option.Option) error { +func (i *Argv) appendOption(name string, data *string, opt variable.Variable) error { if i.HasOption(name) && !opt.IsArray() { return fmt.Errorf("%w: got: array, expect: %s", errs.ErrUnexpectedType, opt.Flag.Type()) } @@ -86,11 +107,11 @@ func (i *Argv) appendOption(name string, data *string, opt option.Option) error val = i.Args[0] i.Args = i.Args[1:] default: - return errs.Option(name, errs.ErrRequired) + return option.Err(name, errs.ErrRequired) } - if err := i.AppendOption(opt.Flag, name, val); err != nil { - return errs.Option(name, err) + if err := i.AppendOption(opt, val); err != nil { + return option.Err(name, err) } return nil @@ -131,8 +152,8 @@ func (i *Argv) parseArgument(arg string, def *Definition) error { return err } - if err := i.AppendArgument(opt.Flag, opt.Name, arg); err != nil { - return errs.Argument(opt.Name, err) + if err := i.AppendArgument(opt, arg); err != nil { + return argument.Err(opt.Name, err) } return nil diff --git a/input/array.go b/input/array.go index 4f8649e..16fc467 100644 --- a/input/array.go +++ b/input/array.go @@ -3,7 +3,9 @@ package input import ( "context" + "gitoa.ru/go-4devs/console/input/argument" "gitoa.ru/go-4devs/console/input/errs" + "gitoa.ru/go-4devs/console/input/option" "gitoa.ru/go-4devs/console/input/value" ) @@ -58,7 +60,7 @@ func (a *Array) bindOption(ctx context.Context, def *Definition) error { continue case opt.IsRequired(): - return errs.Option(name, errs.ErrRequired) + return option.Err(name, errs.ErrRequired) default: continue } @@ -70,7 +72,7 @@ func (a *Array) bindOption(ctx context.Context, def *Definition) error { } if err := opt.Validate(v); err != nil { - return errs.Option(name, err) + return option.Err(name, err) } } @@ -91,7 +93,7 @@ func (a *Array) bindArguments(ctx context.Context, def *Definition) error { continue case arg.IsRequired(): - return errs.Argument(name, errs.ErrRequired) + return argument.Err(name, errs.ErrRequired) default: continue } @@ -99,7 +101,7 @@ func (a *Array) bindArguments(ctx context.Context, def *Definition) error { if v := a.Map.Argument(ctx, name); !value.IsEmpty(v) { if err := arg.Validate(v); err != nil { - return errs.Argument(name, err) + return argument.Err(name, err) } } } diff --git a/input/chain.go b/input/chain.go index 6b33098..7d95a50 100644 --- a/input/chain.go +++ b/input/chain.go @@ -2,6 +2,7 @@ package input import ( "context" + "fmt" "gitoa.ru/go-4devs/console/input/value" ) @@ -35,7 +36,7 @@ func (c chain) Argument(ctx context.Context, name string) value.Value { func (c chain) Bind(ctx context.Context, def *Definition) error { for _, input := range c { if err := input.Bind(ctx, def); err != nil { - return err + return fmt.Errorf("%T:%w", input, err) } } diff --git a/input/definition.go b/input/definition.go index 7960223..2a79d5f 100644 --- a/input/definition.go +++ b/input/definition.go @@ -4,20 +4,21 @@ import ( "gitoa.ru/go-4devs/console/input/argument" "gitoa.ru/go-4devs/console/input/errs" "gitoa.ru/go-4devs/console/input/option" + "gitoa.ru/go-4devs/console/input/variable" ) func NewDefinition() *Definition { return &Definition{ - options: make(map[string]option.Option), - args: make(map[string]argument.Argument), + options: make(map[string]variable.Variable), + args: make(map[string]variable.Variable), short: make(map[string]string), } } type Definition struct { - options map[string]option.Option + options map[string]variable.Variable posOpt []string - args map[string]argument.Argument + args map[string]variable.Variable posArgs []string short map[string]string } @@ -30,11 +31,11 @@ func (d *Definition) Arguments() []string { return d.posArgs } -func (d *Definition) SetOption(name, description string, opts ...func(*option.Option)) *Definition { - return d.SetOptions(option.New(name, description, opts...)) +func (d *Definition) SetOption(name, description string, opts ...variable.Option) *Definition { + return d.SetOptions(option.String(name, description, opts...)) } -func (d *Definition) SetOptions(opts ...option.Option) *Definition { +func (d *Definition) SetOptions(opts ...variable.Variable) *Definition { for _, opt := range opts { if _, has := d.options[opt.Name]; !has { d.posOpt = append([]string{opt.Name}, d.posOpt...) @@ -42,18 +43,18 @@ func (d *Definition) SetOptions(opts ...option.Option) *Definition { d.options[opt.Name] = opt if opt.HasShort() { - d.short[opt.Short] = opt.Name + d.short[opt.Alias] = opt.Name } } return d } -func (d *Definition) SetArgument(name, description string, opts ...func(*argument.Argument)) *Definition { - return d.SetArguments(argument.New(name, description, opts...)) +func (d *Definition) SetArgument(name, description string, opts ...variable.Option) *Definition { + return d.SetArguments(argument.String(name, description, opts...)) } -func (d *Definition) SetArguments(args ...argument.Argument) *Definition { +func (d *Definition) SetArguments(args ...variable.Variable) *Definition { for _, arg := range args { if _, ok := d.args[arg.Name]; !ok { d.posArgs = append(d.posArgs, arg.Name) @@ -65,9 +66,9 @@ func (d *Definition) SetArguments(args ...argument.Argument) *Definition { return d } -func (d *Definition) Argument(pos int) (argument.Argument, error) { +func (d *Definition) Argument(pos int) (variable.Variable, error) { if len(d.posArgs) == 0 { - return argument.Argument{}, errs.ErrNoArgs + return variable.Variable{}, errs.ErrNoArgs } lastPos := len(d.posArgs) - 1 @@ -77,25 +78,25 @@ func (d *Definition) Argument(pos int) (argument.Argument, error) { return arg, nil } - return argument.Argument{}, errs.ErrToManyArgs + return variable.Variable{}, errs.ErrToManyArgs } return d.args[d.posArgs[pos]], nil } -func (d *Definition) ShortOption(short string) (option.Option, error) { +func (d *Definition) ShortOption(short string) (variable.Variable, error) { name, ok := d.short[short] if !ok { - return option.Option{}, errs.ErrNotFound + return variable.Variable{}, errs.ErrNotFound } return d.Option(name) } -func (d *Definition) Option(name string) (option.Option, error) { +func (d *Definition) Option(name string) (variable.Variable, error) { if opt, ok := d.options[name]; ok { return opt, nil } - return option.Option{}, errs.ErrNotFound + return variable.Variable{}, errs.ErrNotFound } diff --git a/input/errs/error.go b/input/errs/error.go index 9d777b7..cb11876 100644 --- a/input/errs/error.go +++ b/input/errs/error.go @@ -2,7 +2,6 @@ package errs import ( "errors" - "fmt" ) var ( @@ -13,46 +12,5 @@ var ( ErrRequired = errors.New("is required") ErrAppend = errors.New("failed append") ErrInvalidName = errors.New("invalid name") + ErrWrongType = errors.New("wrong type") ) - -func New(name, t string, err error) Error { - return Error{ - name: name, - t: t, - err: err, - } -} - -type Error struct { - name string - err error - t string -} - -func (o Error) Error() string { - return fmt.Sprintf("%s: '%s' %s", o.t, o.name, o.err) -} - -func (o Error) Is(err error) bool { - return errors.Is(err, o.err) -} - -func (o Error) Unwrap() error { - return o.err -} - -func Option(name string, err error) Error { - return Error{ - name: name, - err: err, - t: "option", - } -} - -func Argument(name string, err error) Error { - return Error{ - name: name, - err: err, - t: "argument", - } -} diff --git a/input/value/flag/flag.go b/input/flag/flag.go similarity index 99% rename from input/value/flag/flag.go rename to input/flag/flag.go index c1a2e2b..3a0812d 100644 --- a/input/value/flag/flag.go +++ b/input/flag/flag.go @@ -71,6 +71,7 @@ func (i Flag) IsAny() bool { return i&Any > 0 } +//nolint:cyclop func (i Flag) Type() Flag { switch { case i.IsInt(): diff --git a/input/value/flag/flag_string.go b/input/flag/flag_string.go similarity index 100% rename from input/value/flag/flag_string.go rename to input/flag/flag_string.go diff --git a/input/map.go b/input/map.go index 4fdcfda..9b32d4b 100644 --- a/input/map.go +++ b/input/map.go @@ -2,15 +2,16 @@ package input import ( "context" + "fmt" "sync" "gitoa.ru/go-4devs/console/input/value" - "gitoa.ru/go-4devs/console/input/value/flag" + "gitoa.ru/go-4devs/console/input/variable" ) type Map struct { - opts map[string]value.Append - args map[string]value.Append + opts map[string]value.Value + args map[string]value.Value sync.Mutex } @@ -42,15 +43,15 @@ func (m *Map) HasOption(name string) bool { return ok } -func (m *Map) SetOption(name string, v interface{}) { +func (m *Map) SetOption(name string, val interface{}) { m.Lock() defer m.Unlock() if m.opts == nil { - m.opts = make(map[string]value.Append) + m.opts = make(map[string]value.Value) } - m.opts[name] = value.New(v) + m.opts[name] = value.New(val) } func (m *Map) HasArgument(name string) bool { @@ -59,29 +60,59 @@ func (m *Map) HasArgument(name string) bool { return ok } -func (m *Map) SetArgument(name string, v interface{}) { +func (m *Map) SetArgument(name string, val interface{}) { m.Lock() defer m.Unlock() if m.args == nil { - m.args = make(map[string]value.Append) + m.args = make(map[string]value.Value) } - m.args[name] = value.New(v) + m.args[name] = value.New(val) } -func (m *Map) AppendOption(f flag.Flag, name, val string) error { - if _, ok := m.opts[name]; !ok { - m.SetOption(name, value.ByFlag(f)) +func (m *Map) AppendOption(opt variable.Variable, val string) error { + old, ok := m.opts[opt.Name] + if !ok { + value, err := opt.Create(val) + if err != nil { + return fmt.Errorf("append option:%w", err) + } + + m.SetOption(opt.Name, value) + + return nil + } + + value, err := opt.Append(old, val) + if err != nil { + return fmt.Errorf("append option:%w", err) } - return m.opts[name].Append(val) + m.SetOption(opt.Name, value) + + return nil } -func (m *Map) AppendArgument(f flag.Flag, name, val string) error { - if _, ok := m.args[name]; !ok { - m.SetArgument(name, value.ByFlag(f)) +func (m *Map) AppendArgument(arg variable.Variable, val string) error { + old, ok := m.args[arg.Name] + if !ok { + value, err := arg.Create(val) + if err != nil { + return fmt.Errorf("append option:%w", err) + } + + m.SetArgument(arg.Name, value) + + return nil + } + + value, err := arg.Append(old, val) + if err != nil { + return fmt.Errorf("append option:%w", err) } - return m.args[name].Append(val) + m.SetArgument(arg.Name, value) + + return nil } diff --git a/input/option/helpers.go b/input/option/helpers.go deleted file mode 100644 index 458e785..0000000 --- a/input/option/helpers.go +++ /dev/null @@ -1,37 +0,0 @@ -package option - -import ( - "gitoa.ru/go-4devs/console/input/value/flag" -) - -func Bool(name, description string, opts ...func(*Option)) Option { - return New(name, description, append(opts, Value(flag.Bool))...) -} - -func Duration(name, description string, opts ...func(*Option)) Option { - return New(name, description, append(opts, Value(flag.Duration))...) -} - -func Float64(name, description string, opts ...func(*Option)) Option { - return New(name, description, append(opts, Value(flag.Float64))...) -} - -func Int(name, description string, opts ...func(*Option)) Option { - return New(name, description, append(opts, Value(flag.Int))...) -} - -func Int64(name, description string, opts ...func(*Option)) Option { - return New(name, description, append(opts, Value(flag.Int64))...) -} - -func Time(name, description string, opts ...func(*Option)) Option { - return New(name, description, append(opts, Value(flag.Time))...) -} - -func Uint(name, description string, opts ...func(*Option)) Option { - return New(name, description, append(opts, Value(flag.Uint))...) -} - -func Uint64(name, descriontion string, opts ...func(*Option)) Option { - return New(name, descriontion, append(opts, Value(flag.Uint64))...) -} diff --git a/input/option/option.go b/input/option/option.go index a4b659e..5d749e7 100644 --- a/input/option/option.go +++ b/input/option/option.go @@ -1,97 +1,68 @@ package option import ( - "gitoa.ru/go-4devs/console/input/errs" "gitoa.ru/go-4devs/console/input/value" - "gitoa.ru/go-4devs/console/input/value/flag" + "gitoa.ru/go-4devs/console/input/variable" ) -func Required(o *Option) { - o.Flag |= flag.Required -} - -func Default(in interface{}) func(*Option) { - return func(o *Option) { - o.Default = value.New(in) +func Short(in rune) variable.Option { + return func(v *variable.Variable) { + v.Alias = string(in) } } -func Short(s string) func(*Option) { - return func(o *Option) { - o.Short = s - } +func Default(in interface{}) variable.Option { + return variable.Default(value.New(in)) } -func Array(o *Option) { - o.Flag |= flag.Array +func Required(v *variable.Variable) { + variable.Required(v) } -func Value(flag flag.Flag) func(*Option) { - return func(o *Option) { - o.Flag |= flag - } +func Valid(f ...func(value.Value) error) variable.Option { + return variable.Valid(f...) } -func Flag(in flag.Flag) func(*Option) { - return func(o *Option) { - o.Flag = in - } +func Array(v *variable.Variable) { + variable.Array(v) } -func Valid(f ...func(value.Value) error) func(*Option) { - return func(o *Option) { - o.Valid = f - } +func String(name, description string, opts ...variable.Option) variable.Variable { + return variable.String(name, description, append(opts, variable.ArgOption)...) } -func New(name, description string, opts ...func(*Option)) Option { - o := Option{ - Name: name, - Description: description, - } - - for _, opt := range opts { - opt(&o) - } - - return o +func Bool(name, description string, opts ...variable.Option) variable.Variable { + return variable.Bool(name, description, append(opts, variable.ArgOption)...) } -type Option struct { - Name string - Description string - Short string - Flag flag.Flag - Default value.Value - Valid []func(value.Value) error +func Duration(name, description string, opts ...variable.Option) variable.Variable { + return variable.Duration(name, description, append(opts, variable.ArgOption)...) } -func (o Option) HasShort() bool { - return len(o.Short) == 1 +func Float64(name, description string, opts ...variable.Option) variable.Variable { + return variable.Float64(name, description, append(opts, variable.ArgOption)...) } -func (o Option) HasDefault() bool { - return o.Default != nil +func Int(name, description string, opts ...variable.Option) variable.Variable { + return variable.Int(name, description, append(opts, variable.ArgOption)...) } -func (o Option) IsBool() bool { - return o.Flag.IsBool() +func Int64(name, description string, opts ...variable.Option) variable.Variable { + return variable.Int64(name, description, append(opts, variable.ArgOption)...) } -func (o Option) IsArray() bool { - return o.Flag.IsArray() +func Time(name, description string, opts ...variable.Option) variable.Variable { + return variable.Time(name, description, append(opts, variable.ArgOption)...) } -func (o Option) IsRequired() bool { - return o.Flag.IsRequired() +func Uint(name, description string, opts ...variable.Option) variable.Variable { + return variable.Uint(name, description, append(opts, variable.ArgOption)...) } -func (o Option) Validate(v value.Value) error { - for _, valid := range o.Valid { - if err := valid(v); err != nil { - return errs.Option(o.Name, err) - } - } +func Uint64(name, descriontion string, opts ...variable.Option) variable.Variable { + return variable.Uint64(name, descriontion, append(opts, variable.ArgOption)...) +} - return nil +func Err(name string, err error) variable.Error { + return variable.Err(name, variable.TypeOption, err) } diff --git a/input/validator/enum.go b/input/validator/enum.go index c6197ce..4a8b639 100644 --- a/input/validator/enum.go +++ b/input/validator/enum.go @@ -4,13 +4,13 @@ import "gitoa.ru/go-4devs/console/input/value" func Enum(enum ...string) func(value.Value) error { return func(in value.Value) error { - v := in.String() + val := in.String() for _, e := range enum { - if e == v { + if e == val { return nil } } - return NewError(ErrInvalid, v, enum) + return NewError(ErrInvalid, val, enum) } } diff --git a/input/validator/enum_test.go b/input/validator/enum_test.go index b1844e0..c497e56 100644 --- a/input/validator/enum_test.go +++ b/input/validator/enum_test.go @@ -9,6 +9,8 @@ import ( ) func TestEnum(t *testing.T) { + t.Parallel() + validValue := value.New("valid") invalidValue := value.New("invalid") diff --git a/input/validator/not_blank.go b/input/validator/not_blank.go index 8d9ee9d..d8d74f9 100644 --- a/input/validator/not_blank.go +++ b/input/validator/not_blank.go @@ -1,33 +1,33 @@ package validator import ( + "gitoa.ru/go-4devs/console/input/flag" "gitoa.ru/go-4devs/console/input/value" - "gitoa.ru/go-4devs/console/input/value/flag" ) -//nolint: gocyclo -func NotBlank(f flag.Flag) func(value.Value) error { +//nolint:gocyclo,cyclop +func NotBlank(fl flag.Flag) func(value.Value) error { return func(in value.Value) error { switch { - case f.IsAny() && in.Any() != nil: + case fl.IsAny() && in.Any() != nil: return nil - case f.IsArray(): - return arrayNotBlank(f, in) - case f.IsInt() && in.Int() != 0: + case fl.IsArray(): + return arrayNotBlank(fl, in) + case fl.IsInt() && in.Int() != 0: return nil - case f.IsInt64() && in.Int64() != 0: + case fl.IsInt64() && in.Int64() != 0: return nil - case f.IsUint() && in.Uint() != 0: + case fl.IsUint() && in.Uint() != 0: return nil - case f.IsUint64() && in.Uint64() != 0: + case fl.IsUint64() && in.Uint64() != 0: return nil - case f.IsFloat64() && in.Float64() != 0: + case fl.IsFloat64() && in.Float64() != 0: return nil - case f.IsDuration() && in.Duration() != 0: + case fl.IsDuration() && in.Duration() != 0: return nil - case f.IsTime() && !in.Time().IsZero(): + case fl.IsTime() && !in.Time().IsZero(): return nil - case f.IsString() && len(in.String()) > 0: + case fl.IsString() && len(in.String()) > 0: return nil } @@ -35,10 +35,10 @@ func NotBlank(f flag.Flag) func(value.Value) error { } } -//nolint: gocyclo,gocognit -func arrayNotBlank(f flag.Flag, in value.Value) error { +//nolint:gocyclo,gocognit,cyclop +func arrayNotBlank(fl flag.Flag, in value.Value) error { switch { - case f.IsInt() && len(in.Ints()) > 0: + case fl.IsInt() && len(in.Ints()) > 0: for _, i := range in.Ints() { if i == 0 { return ErrNotBlank @@ -46,7 +46,7 @@ func arrayNotBlank(f flag.Flag, in value.Value) error { } return nil - case f.IsInt64() && len(in.Int64s()) > 0: + case fl.IsInt64() && len(in.Int64s()) > 0: for _, i := range in.Int64s() { if i == 0 { return ErrNotBlank @@ -54,7 +54,7 @@ func arrayNotBlank(f flag.Flag, in value.Value) error { } return nil - case f.IsUint() && len(in.Uints()) > 0: + case fl.IsUint() && len(in.Uints()) > 0: for _, u := range in.Uints() { if u == 0 { return ErrNotBlank @@ -62,7 +62,7 @@ func arrayNotBlank(f flag.Flag, in value.Value) error { } return nil - case f.IsUint64() && len(in.Uint64s()) > 0: + case fl.IsUint64() && len(in.Uint64s()) > 0: for _, u := range in.Uint64s() { if u == 0 { return ErrNotBlank @@ -70,7 +70,7 @@ func arrayNotBlank(f flag.Flag, in value.Value) error { } return nil - case f.IsFloat64() && len(in.Float64s()) > 0: + case fl.IsFloat64() && len(in.Float64s()) > 0: for _, f := range in.Float64s() { if f == 0 { return ErrNotBlank @@ -78,9 +78,9 @@ func arrayNotBlank(f flag.Flag, in value.Value) error { } return nil - case f.IsBool() && len(in.Bools()) > 0: + case fl.IsBool() && len(in.Bools()) > 0: return nil - case f.IsDuration() && len(in.Durations()) > 0: + case fl.IsDuration() && len(in.Durations()) > 0: for _, d := range in.Durations() { if d == 0 { return ErrNotBlank @@ -88,7 +88,7 @@ func arrayNotBlank(f flag.Flag, in value.Value) error { } return nil - case f.IsTime() && len(in.Times()) > 0: + case fl.IsTime() && len(in.Times()) > 0: for _, t := range in.Times() { if t.IsZero() { return ErrNotBlank @@ -96,7 +96,7 @@ func arrayNotBlank(f flag.Flag, in value.Value) error { } return nil - case f.IsString() && len(in.Strings()) > 0: + case fl.IsString() && len(in.Strings()) > 0: for _, st := range in.Strings() { if len(st) == 0 { return ErrNotBlank diff --git a/input/validator/not_blank_test.go b/input/validator/not_blank_test.go index 1aca789..e74449f 100644 --- a/input/validator/not_blank_test.go +++ b/input/validator/not_blank_test.go @@ -5,12 +5,14 @@ import ( "testing" "time" + "gitoa.ru/go-4devs/console/input/flag" "gitoa.ru/go-4devs/console/input/validator" "gitoa.ru/go-4devs/console/input/value" - "gitoa.ru/go-4devs/console/input/value/flag" ) func TestNotBlank(t *testing.T) { + t.Parallel() + cases := map[string]struct { flag flag.Flag value value.Value @@ -103,7 +105,7 @@ func TestNotBlank(t *testing.T) { } if err := valid(ca.empty); err == nil || !errors.Is(err, validator.ErrNotBlank) { - t.Errorf("case: %s, expect: %s, got:%s", name, validator.ErrNotBlank, err) + t.Errorf("case empty: %s, expect: %s, got:%s", name, validator.ErrNotBlank, err) } } } diff --git a/input/validator/valid_test.go b/input/validator/valid_test.go index b1f8b15..e84ae1d 100644 --- a/input/validator/valid_test.go +++ b/input/validator/valid_test.go @@ -4,12 +4,14 @@ import ( "errors" "testing" + "gitoa.ru/go-4devs/console/input/flag" "gitoa.ru/go-4devs/console/input/validator" "gitoa.ru/go-4devs/console/input/value" - "gitoa.ru/go-4devs/console/input/value/flag" ) func TestValid(t *testing.T) { + t.Parallel() + validValue := value.New("one") invalidValue := value.New([]string{"one"}) diff --git a/input/value/any.go b/input/value/any.go index 208bfdc..03085ce 100644 --- a/input/value/any.go +++ b/input/value/any.go @@ -1,21 +1,137 @@ package value -import "gitoa.ru/go-4devs/console/input/value/flag" +import ( + "encoding/json" + "fmt" + "time" +) + +var _ Value = NewAny(nil) + +//nolint:gochecknoglobals +var ( + emptyValue = NewAny(nil) +) + +func Empty() Value { + return emptyValue +} + +func IsEmpty(v Value) bool { + return v == nil || v == emptyValue +} + +func NewAny(in interface{}) Value { + return Read{Any{v: in}} +} type Any struct { - empty - Val []interface{} - Flag flag.Flag + v interface{} +} + +func (a Any) Any() interface{} { + return a.v } -func (a *Any) Any() interface{} { - if a.Flag.IsArray() { - return a.Val +func (a Any) Unmarshal(val interface{}) error { + out, err := a.ParseString() + if err != nil { + return fmt.Errorf("any parse string:%w", err) } - if len(a.Val) > 0 { - return a.Val[0] + uerr := json.Unmarshal([]byte(out), val) + if uerr != nil { + return fmt.Errorf("any unmarshal: %w", uerr) } return nil } + +func (a Any) ParseString() (string, error) { + if a.v == nil { + return "", nil + } + + bout, err := json.Marshal(a.v) + if err != nil { + return "", fmt.Errorf("any string:%w", err) + } + + return string(bout), err +} + +func (a Any) ParseInt() (int, error) { + out, ok := a.v.(int) + if !ok { + return 0, a.wrongType("int") + } + + return out, nil +} + +func (a Any) ParseInt64() (int64, error) { + out, ok := a.v.(int64) + if !ok { + return 0, a.wrongType("int64") + } + + return out, nil +} + +func (a Any) ParseUint() (uint, error) { + out, ok := a.v.(uint) + if !ok { + return 0, a.wrongType("uint") + } + + return out, nil +} + +func (a Any) ParseUint64() (uint64, error) { + out, ok := a.v.(uint64) + if !ok { + return 0, a.wrongType("uint64") + } + + return out, nil +} + +func (a Any) ParseFloat64() (float64, error) { + out, ok := a.v.(float64) + if !ok { + return 0, a.wrongType("float64") + } + + return out, nil +} + +func (a Any) ParseBool() (bool, error) { + out, ok := a.v.(bool) + if !ok { + return false, a.wrongType("bool") + } + + return out, nil +} + +func (a Any) ParseDuration() (time.Duration, error) { + out, ok := a.v.(time.Duration) + if !ok { + return 0, a.wrongType("time.Duration") + } + + return out, nil +} + +func (a Any) ParseTime() (time.Time, error) { + out, ok := a.v.(time.Time) + if !ok { + return time.Time{}, a.wrongType("time.Time") + } + + return out, nil +} + +func (a Any) wrongType(ex String) error { + return fmt.Errorf("%w any: got: %T expect: %s", ErrWrongType, a.v, ex) +} diff --git a/input/value/bool.go b/input/value/bool.go index 108d89b..0757944 100644 --- a/input/value/bool.go +++ b/input/value/bool.go @@ -1,44 +1,148 @@ package value import ( - "strconv" + "fmt" + "time" +) - "gitoa.ru/go-4devs/console/input/value/flag" +var ( + _ ParseValue = Bool(false) + _ SliceValue = Bools{} ) -type Bool struct { - empty - Val []bool - Flag flag.Flag +func NewBools(in []bool) Slice { + return Slice{SliceValue: Bools(in)} +} + +type Bools []bool + +func (b Bools) Any() interface{} { + return b.Bools() +} + +func (b Bools) Unmarshal(val interface{}) error { + v, ok := val.(*[]bool) + if !ok { + return fmt.Errorf("%w: expect: *[]bool got: %T", ErrWrongType, val) + } + + *v = b + + return nil +} + +func (b Bools) Strings() []string { + return nil +} + +func (b Bools) Ints() []int { + return nil +} + +func (b Bools) Int64s() []int64 { + return nil +} + +func (b Bools) Uints() []uint { + return nil +} + +func (b Bools) Uint64s() []uint64 { + return nil +} + +func (b Bools) Float64s() []float64 { + return nil +} + +func (b Bools) Bools() []bool { + out := make([]bool, len(b)) + copy(out, b) + + return out +} + +func (b Bools) Durations() []time.Duration { + return nil } -func (b *Bool) Append(in string) error { - v, err := strconv.ParseBool(in) - if err != nil { - return err +func (b Bools) Times() []time.Time { + return nil +} + +func NewBool(in bool) Read { + return Read{ParseValue: Bool(in)} +} + +type Bool bool + +func (b Bool) Unmarshal(val interface{}) error { + v, ok := val.(*bool) + if !ok { + return fmt.Errorf("%w: expect: *bool got: %T", ErrWrongType, val) } - b.Val = append(b.Val, v) + *v = bool(b) return nil } -func (b *Bool) Bool() bool { - if !b.Flag.IsArray() && len(b.Val) == 1 { - return b.Val[0] +func (b Bool) ParseString() (string, error) { + return fmt.Sprintf("%v", b), nil +} + +func (b Bool) ParseInt() (int, error) { + if b { + return 1, nil + } + + return 0, nil +} + +func (b Bool) ParseInt64() (int64, error) { + if b { + return 1, nil + } + + return 0, nil +} + +func (b Bool) ParseUint() (uint, error) { + if b { + return 1, nil } - return false + return 0, nil } -func (b *Bool) Bools() []bool { - return b.Val +func (b Bool) ParseUint64() (uint64, error) { + if b { + return 1, nil + } + + return 0, nil } -func (b *Bool) Any() interface{} { - if b.Flag.IsArray() { - return b.Bools() +func (b Bool) ParseFloat64() (float64, error) { + if b { + return 1, nil } - return b.Bool() + return 0, nil +} + +func (b Bool) ParseBool() (bool, error) { + return bool(b), nil +} + +func (b Bool) ParseDuration() (time.Duration, error) { + return 0, fmt.Errorf("bool to duration:%w", ErrWrongType) +} + +func (b Bool) ParseTime() (time.Time, error) { + return time.Time{}, fmt.Errorf("bool to time:%w", ErrWrongType) +} + +func (b Bool) Any() interface{} { + return bool(b) } diff --git a/input/value/duration.go b/input/value/duration.go index c712818..0d5e97f 100644 --- a/input/value/duration.go +++ b/input/value/duration.go @@ -1,44 +1,128 @@ package value import ( + "fmt" "time" +) - "gitoa.ru/go-4devs/console/input/value/flag" +var ( + _ ParseValue = Duration(0) + _ SliceValue = Durations{} ) -type Duration struct { - empty - Val []time.Duration - Flag flag.Flag +func NewDurations(in []time.Duration) Slice { + return Slice{SliceValue: Durations(in)} } -func (d *Duration) Append(in string) error { - v, err := time.ParseDuration(in) - if err != nil { - return err +type Durations []time.Duration + +func (d Durations) Unmarshal(val interface{}) error { + v, ok := val.(*[]time.Duration) + if !ok { + return fmt.Errorf("%w: expect: *[]time.Duration got: %T", ErrWrongType, val) } - d.Val = append(d.Val, v) + *v = d return nil } -func (d *Duration) Duration() time.Duration { - if !d.Flag.IsArray() && len(d.Val) == 1 { - return d.Val[0] - } +func (d Durations) Any() interface{} { + return d.Durations() +} + +func (d Durations) Strings() []string { + return nil +} + +func (d Durations) Ints() []int { + return nil +} + +func (d Durations) Int64s() []int64 { + return nil +} + +func (d Durations) Uints() []uint { + return nil +} + +func (d Durations) Uint64s() []uint64 { + return nil +} + +func (d Durations) Float64s() []float64 { + return nil +} + +func (d Durations) Bools() []bool { + return nil +} + +func (d Durations) Durations() []time.Duration { + out := make([]time.Duration, len(d)) + copy(out, d) - return 0 + return out } -func (d *Duration) Durations() []time.Duration { - return d.Val +func (d Durations) Times() []time.Time { + return nil +} + +func NewDuration(in time.Duration) Read { + return Read{ParseValue: Duration(in)} +} + +type Duration time.Duration + +func (d Duration) ParseDuration() (time.Duration, error) { + return time.Duration(d), nil +} + +func (d Duration) ParseString() (string, error) { + return time.Duration(d).String(), nil +} + +func (d Duration) ParseInt() (int, error) { + return int(d), nil +} + +func (d Duration) ParseInt64() (int64, error) { + return int64(d), nil +} + +func (d Duration) ParseUint() (uint, error) { + return uint(d), nil +} + +func (d Duration) ParseUint64() (uint64, error) { + return uint64(d), nil +} + +func (d Duration) ParseFloat64() (float64, error) { + return float64(d), nil +} + +func (d Duration) ParseBool() (bool, error) { + return false, fmt.Errorf("duration:%w", ErrWrongType) } -func (d *Duration) Any() interface{} { - if d.Flag.IsArray() { - return d.Durations() +func (d Duration) ParseTime() (time.Time, error) { + return time.Time{}, fmt.Errorf("duration:%w", ErrWrongType) +} + +func (d Duration) Unmarshal(val interface{}) error { + v, ok := val.(*time.Duration) + if !ok { + return fmt.Errorf("%w: expect: *[]time.Duration got: %T", ErrWrongType, val) } - return d.Duration() + *v = time.Duration(d) + + return nil +} + +func (d Duration) Any() interface{} { + return time.Duration(d) } diff --git a/input/value/empty.go b/input/value/empty.go deleted file mode 100644 index f1fa4ee..0000000 --- a/input/value/empty.go +++ /dev/null @@ -1,100 +0,0 @@ -package value - -import ( - "time" -) - -// nolint: gochecknoglobals -var ( - emptyValue = &empty{} -) - -func Empty() Value { - return emptyValue -} - -func IsEmpty(v Value) bool { - return v == nil || v == emptyValue -} - -type empty struct{} - -func (e *empty) Append(string) error { - return ErrAppendEmpty -} - -func (e *empty) String() string { - return "" -} - -func (e *empty) Int() int { - return 0 -} - -func (e *empty) Int64() int64 { - return 0 -} - -func (e *empty) Uint() uint { - return 0 -} - -func (e *empty) Uint64() uint64 { - return 0 -} - -func (e *empty) Float64() float64 { - return 0 -} - -func (e *empty) Bool() bool { - return false -} - -func (e *empty) Duration() time.Duration { - return 0 -} - -func (e *empty) Time() time.Time { - return time.Time{} -} - -func (e *empty) Strings() []string { - return nil -} - -func (e *empty) Ints() []int { - return nil -} - -func (e *empty) Int64s() []int64 { - return nil -} - -func (e *empty) Uints() []uint { - return nil -} - -func (e *empty) Uint64s() []uint64 { - return nil -} - -func (e *empty) Float64s() []float64 { - return nil -} - -func (e *empty) Bools() []bool { - return nil -} - -func (e *empty) Durations() []time.Duration { - return nil -} - -func (e *empty) Times() []time.Time { - return nil -} - -func (e *empty) Any() interface{} { - return nil -} diff --git a/input/value/float64.go b/input/value/float64.go index e9fa6d3..af4faa5 100644 --- a/input/value/float64.go +++ b/input/value/float64.go @@ -1,44 +1,128 @@ package value import ( - "strconv" + "fmt" + "time" +) - "gitoa.ru/go-4devs/console/input/value/flag" +var ( + _ ParseValue = Float64(0) + _ SliceValue = Float64s{} ) -type Float64 struct { - empty - Val []float64 - Flag flag.Flag +func NewFloat64s(in []float64) Slice { + return Slice{SliceValue: Float64s(in)} +} + +type Float64s []float64 + +func (f Float64s) Any() interface{} { + return f.Float64s() } -func (f *Float64) Append(in string) error { - v, err := strconv.ParseFloat(in, 64) - if err != nil { - return err +func (f Float64s) Unmarshal(val interface{}) error { + v, ok := val.(*[]float64) + if !ok { + return fmt.Errorf("%w: expect *[]float64", ErrWrongType) } - f.Val = append(f.Val, v) + *v = f return nil } -func (f *Float64) Float64() float64 { - if !f.Flag.IsArray() && len(f.Val) == 1 { - return f.Val[0] - } +func (f Float64s) Strings() []string { + return nil +} + +func (f Float64s) Ints() []int { + return nil +} + +func (f Float64s) Int64s() []int64 { + return nil +} + +func (f Float64s) Uints() []uint { + return nil +} + +func (f Float64s) Uint64s() []uint64 { + return nil +} + +func (f Float64s) Float64s() []float64 { + out := make([]float64, len(f)) + copy(out, f) + + return out +} + +func (f Float64s) Bools() []bool { + return nil +} + +func (f Float64s) Durations() []time.Duration { + return nil +} + +func (f Float64s) Times() []time.Time { + return nil +} + +func NewFloat64(in float64) Read { + return Read{ParseValue: Float64(in)} +} + +type Float64 float64 + +func (f Float64) Any() interface{} { + return float64(f) +} + +func (f Float64) ParseString() (string, error) { + return fmt.Sprint(float64(f)), nil +} + +func (f Float64) ParseInt() (int, error) { + return int(f), nil +} + +func (f Float64) ParseInt64() (int64, error) { + return int64(f), nil +} + +func (f Float64) ParseUint() (uint, error) { + return uint(f), nil +} + +func (f Float64) ParseUint64() (uint64, error) { + return uint64(f), nil +} - return 0 +func (f Float64) ParseFloat64() (float64, error) { + return float64(f), nil } -func (f *Float64) Float64s() []float64 { - return f.Val +func (f Float64) ParseBool() (bool, error) { + return false, fmt.Errorf("float64:%w", ErrWrongType) } -func (f *Float64) Any() interface{} { - if f.Flag.IsArray() { - return f.Float64s() +func (f Float64) ParseDuration() (time.Duration, error) { + return time.Duration(f), nil +} + +func (f Float64) ParseTime() (time.Time, error) { + return time.Unix(0, int64(f*Float64(time.Second))), nil +} + +func (f Float64) Unmarshal(in interface{}) error { + v, ok := in.(*float64) + if !ok { + return fmt.Errorf("%w: expect *float64", ErrWrongType) } - return f.Float64() + *v = float64(f) + + return nil } diff --git a/input/value/float64_test.go b/input/value/float64_test.go new file mode 100644 index 0000000..f119f4c --- /dev/null +++ b/input/value/float64_test.go @@ -0,0 +1,47 @@ +package value_test + +import ( + "math" + "testing" + + "github.com/stretchr/testify/require" + "gitoa.ru/go-4devs/console/input/value" +) + +func TestFloat64_Unmarshal(t *testing.T) { + t.Parallel() + + f := value.Float64(math.Pi) + + var out float64 + + require.NoError(t, f.Unmarshal(&out)) + require.Equal(t, math.Pi, out) +} + +func TestFloat64_Any(t *testing.T) { + t.Parallel() + + f := value.Float64(math.Pi) + + require.Equal(t, math.Pi, f.Any()) +} + +func TestFloat64s_Unmarshal(t *testing.T) { + t.Parallel() + + f := value.Float64s{math.Pi, math.Sqrt2} + + var out []float64 + + require.NoError(t, f.Unmarshal(&out)) + require.Equal(t, []float64{math.Pi, math.Sqrt2}, out) +} + +func TestFloat64s_Any(t *testing.T) { + t.Parallel() + + f := value.Float64s{math.Pi, math.Sqrt2} + + require.Equal(t, []float64{math.Pi, math.Sqrt2}, f.Any()) +} diff --git a/input/value/int.go b/input/value/int.go index 42eb9e2..537e604 100644 --- a/input/value/int.go +++ b/input/value/int.go @@ -1,44 +1,129 @@ package value import ( + "fmt" "strconv" + "time" +) - "gitoa.ru/go-4devs/console/input/value/flag" +var ( + _ ParseValue = Int(0) + _ SliceValue = Ints{} ) -type Int struct { - empty - Val []int - Flag flag.Flag +func NewInts(in []int) Slice { + return Slice{SliceValue: Ints(in)} } -func (i *Int) Append(in string) error { - v, err := strconv.Atoi(in) - if err != nil { - return err +type Ints []int + +func (i Ints) Unmarshal(in interface{}) error { + val, ok := in.(*[]int) + if !ok { + return fmt.Errorf("%w: expect *[]int", ErrWrongType) } - i.Val = append(i.Val, v) + *val = i return nil } -func (i *Int) Int() int { - if !i.Flag.IsArray() && len(i.Val) == 1 { - return i.Val[0] - } +func (i Ints) Any() interface{} { + return i.Ints() +} + +func (i Ints) Strings() []string { + return nil +} + +func (i Ints) Ints() []int { + out := make([]int, len(i)) + copy(out, i) + + return out +} + +func (i Ints) Int64s() []int64 { + return nil +} + +func (i Ints) Uints() []uint { + return nil +} + +func (i Ints) Uint64s() []uint64 { + return nil +} + +func (i Ints) Float64s() []float64 { + return nil +} + +func (i Ints) Bools() []bool { + return nil +} + +func (i Ints) Durations() []time.Duration { + return nil +} - return 0 +func (i Ints) Times() []time.Time { + return nil } -func (i *Int) Ints() []int { - return i.Val +func NewInt(in int) Read { + return Read{ParseValue: Int(in)} } -func (i *Int) Any() interface{} { - if i.Flag.IsArray() { - return i.Ints() +type Int int + +func (i Int) Unmarshal(in interface{}) error { + v, ok := in.(*int) + if !ok { + return fmt.Errorf("%w: expect *int", ErrWrongType) } - return i.Int() + *v = int(i) + + return nil +} + +func (i Int) ParseString() (string, error) { + return strconv.Itoa(int(i)), nil +} + +func (i Int) ParseInt() (int, error) { + return int(i), nil +} + +func (i Int) ParseInt64() (int64, error) { + return int64(i), nil +} + +func (i Int) ParseUint() (uint, error) { + return uint(i), nil +} + +func (i Int) ParseUint64() (uint64, error) { + return uint64(i), nil +} + +func (i Int) ParseFloat64() (float64, error) { + return float64(i), nil +} + +func (i Int) ParseBool() (bool, error) { + return false, fmt.Errorf("int:%w", ErrWrongType) +} + +func (i Int) ParseDuration() (time.Duration, error) { + return time.Duration(i), nil +} + +func (i Int) ParseTime() (time.Time, error) { + return time.Unix(0, int64(i)), nil +} + +func (i Int) Any() interface{} { + return int(i) } diff --git a/input/value/int64.go b/input/value/int64.go index 947cb77..f7c529d 100644 --- a/input/value/int64.go +++ b/input/value/int64.go @@ -1,44 +1,129 @@ package value import ( + "fmt" "strconv" + "time" +) - "gitoa.ru/go-4devs/console/input/value/flag" +var ( + _ ParseValue = Int64(0) + _ SliceValue = Int64s{} ) -type Int64 struct { - empty - Val []int64 - Flag flag.Flag +func NewInt64s(in []int64) Slice { + return Slice{SliceValue: Int64s(in)} +} + +type Int64s []int64 + +func (i Int64s) Any() interface{} { + return i.Int64s() } -func (i *Int64) Int64() int64 { - if !i.Flag.IsArray() && len(i.Val) == 1 { - return i.Val[0] +func (i Int64s) Unmarshal(val interface{}) error { + v, ok := val.(*[]int64) + if !ok { + return fmt.Errorf("%w: expect *[]int64", ErrWrongType) } - return 0 + *v = i + + return nil } -func (i *Int64) Int64s() []int64 { - return i.Val +func (i Int64s) Strings() []string { + return nil } -func (i *Int64) Any() interface{} { - if i.Flag.IsArray() { - return i.Int64s() - } +func (i Int64s) Ints() []int { + return nil +} + +func (i Int64s) Int64s() []int64 { + out := make([]int64, len(i)) + copy(out, i) + + return out +} + +func (i Int64s) Uints() []uint { + return nil +} + +func (i Int64s) Uint64s() []uint64 { + return nil +} + +func (i Int64s) Float64s() []float64 { + return nil +} + +func (i Int64s) Bools() []bool { + return nil +} + +func (i Int64s) Durations() []time.Duration { + return nil +} + +func (i Int64s) Times() []time.Time { + return nil +} + +func NewInt64(in int64) Read { + return Read{ParseValue: Int64(in)} +} + +type Int64 int64 + +func (i Int64) Any() interface{} { + return int64(i) +} + +func (i Int64) ParseString() (string, error) { + return strconv.FormatInt(int64(i), 10), nil +} + +func (i Int64) ParseInt() (int, error) { + return int(i), nil +} + +func (i Int64) ParseInt64() (int64, error) { + return int64(i), nil +} + +func (i Int64) ParseUint() (uint, error) { + return uint(i), nil +} + +func (i Int64) ParseUint64() (uint64, error) { + return uint64(i), nil +} + +func (i Int64) ParseFloat64() (float64, error) { + return float64(i), nil +} + +func (i Int64) ParseBool() (bool, error) { + return false, fmt.Errorf("int64:%w", ErrWrongType) +} + +func (i Int64) ParseDuration() (time.Duration, error) { + return time.Duration(i), nil +} - return i.Int64() +func (i Int64) ParseTime() (time.Time, error) { + return time.Unix(0, int64(i)), nil } -func (i *Int64) Append(in string) error { - v, err := strconv.ParseInt(in, 10, 64) - if err != nil { - return err +func (i Int64) Unmarshal(val interface{}) error { + v, ok := val.(*int64) + if !ok { + return fmt.Errorf("%w: expect *int64", ErrWrongType) } - i.Val = append(i.Val, v) + *v = int64(i) return nil } diff --git a/input/value/read.go b/input/value/read.go index b984472..0ec4869 100644 --- a/input/value/read.go +++ b/input/value/read.go @@ -1,20 +1,189 @@ package value import ( - "errors" -) + "fmt" + "time" -var _ Append = (*Read)(nil) + "gitoa.ru/go-4devs/console/input/errs" +) var ( - ErrAppendRead = errors.New("invalid append data to read value") - ErrAppendEmpty = errors.New("invalid apped data to empty value") + _ Value = Read{} + _ Value = Slice{} ) +var ErrWrongType = errs.ErrWrongType + type Read struct { - Value + ParseValue +} + +func (r Read) String() string { + sout, _ := r.ParseValue.ParseString() + + return sout +} + +func (r Read) Int() int { + iout, _ := r.ParseValue.ParseInt() + + return iout +} + +func (r Read) Int64() int64 { + iout, _ := r.ParseValue.ParseInt64() + + return iout +} + +func (r Read) Uint() uint { + uout, _ := r.ParseValue.ParseUint() + + return uout +} + +func (r Read) Uint64() uint64 { + uout, _ := r.ParseValue.ParseUint64() + + return uout +} + +func (r Read) Float64() float64 { + fout, _ := r.ParseValue.ParseFloat64() + + return fout +} + +func (r Read) Bool() bool { + bout, _ := r.ParseValue.ParseBool() + + return bout +} + +func (r Read) Duration() time.Duration { + dout, _ := r.ParseValue.ParseDuration() + + return dout +} + +func (r Read) Time() time.Time { + tout, _ := r.ParseValue.ParseTime() + + return tout +} + +func (r Read) Strings() []string { + return []string{r.String()} +} + +func (r Read) Ints() []int { + return []int{r.Int()} +} + +func (r Read) Int64s() []int64 { + return []int64{r.Int64()} +} + +func (r Read) Uints() []uint { + return []uint{r.Uint()} +} + +func (r Read) Uint64s() []uint64 { + return []uint64{r.Uint64()} +} + +func (r Read) Float64s() []float64 { + return []float64{r.Float64()} +} + +func (r Read) Bools() []bool { + return []bool{r.Bool()} +} + +func (r Read) Durations() []time.Duration { + return []time.Duration{r.Duration()} +} + +func (r Read) Times() []time.Time { + return []time.Time{r.Time()} +} + +type Slice struct { + SliceValue +} + +func (s Slice) String() string { + return "" +} + +func (s Slice) Int() int { + return 0 +} + +func (s Slice) Int64() int64 { + return 0 +} + +func (s Slice) Uint() uint { + return 0 +} + +func (s Slice) Uint64() uint64 { + return 0 +} + +func (s Slice) Float64() float64 { + return 0 +} + +func (s Slice) Bool() bool { + return false +} + +func (s Slice) Duration() time.Duration { + return 0 +} + +func (s Slice) Time() time.Time { + return time.Time{} +} + +func (s Slice) wrongType() error { + return fmt.Errorf("%w: for %T", ErrWrongType, s.SliceValue) +} + +func (s Slice) ParseString() (string, error) { + return "", s.wrongType() +} + +func (s Slice) ParseInt() (int, error) { + return 0, s.wrongType() +} + +func (s Slice) ParseInt64() (int64, error) { + return 0, s.wrongType() +} + +func (s Slice) ParseUint() (uint, error) { + return 0, s.wrongType() +} + +func (s Slice) ParseUint64() (uint64, error) { + return 0, s.wrongType() +} + +func (s Slice) ParseFloat64() (float64, error) { + return 0, s.wrongType() +} + +func (s Slice) ParseBool() (bool, error) { + return false, s.wrongType() +} + +func (s Slice) ParseDuration() (time.Duration, error) { + return 0, s.wrongType() } -func (r *Read) Append(string) error { - return ErrAppendRead +func (s Slice) ParseTime() (time.Time, error) { + return time.Time{}, s.wrongType() } diff --git a/input/value/string.go b/input/value/string.go index d54bcc3..2a9f246 100644 --- a/input/value/string.go +++ b/input/value/string.go @@ -1,39 +1,172 @@ package value -import "gitoa.ru/go-4devs/console/input/value/flag" +import ( + "fmt" + "strconv" + "time" +) -type String struct { - empty - Val []string - Flag flag.Flag +var ( + _ ParseValue = (String)("") + _ SliceValue = (Strings)(nil) +) + +func NewStrings(in []string) Slice { + return Slice{SliceValue: Strings(in)} +} + +type Strings []string + +func (s Strings) Unmarshal(in interface{}) error { + val, ok := in.(*[]string) + if !ok { + return fmt.Errorf("%w: expect *[]string", ErrWrongType) + } + + *val = s + + return nil +} + +func (s Strings) Any() interface{} { + return s.Strings() +} + +func (s Strings) Strings() []string { + out := make([]string, len(s)) + copy(out, s) + + return out +} + +func (s Strings) Ints() []int { + return nil +} + +func (s Strings) Int64s() []int64 { + return nil +} + +func (s Strings) Uints() []uint { + return nil +} + +func (s Strings) Uint64s() []uint64 { + return nil +} + +func (s Strings) Float64s() []float64 { + return nil +} + +func (s Strings) Bools() []bool { + return nil +} + +func (s Strings) Durations() []time.Duration { + return nil } -func (s *String) Append(in string) error { - s.Val = append(s.Val, in) +func (s Strings) Times() []time.Time { + return nil +} + +func NewString(in string) Value { + return Read{ParseValue: String(in)} +} + +type String string + +func (s String) ParseString() (string, error) { + return string(s), nil +} + +func (s String) Unmarshal(in interface{}) error { + v, ok := in.(*string) + if !ok { + return fmt.Errorf("%w: expect *string", ErrWrongType) + } + + *v = string(s) return nil } -func (s *String) String() string { - if s.Flag.IsArray() { - return "" +func (s String) Any() interface{} { + return string(s) +} + +func (s String) ParseInt() (int, error) { + v, err := strconv.Atoi(string(s)) + if err != nil { + return 0, fmt.Errorf("string int:%w", err) + } + + return v, nil +} + +func (s String) Int64() int64 { + out, _ := s.ParseInt64() + + return out +} + +func (s String) ParseInt64() (int64, error) { + v, err := strconv.ParseInt(string(s), 10, 64) + if err != nil { + return 0, fmt.Errorf("string int64:%w", err) } - if len(s.Val) == 1 { - return s.Val[0] + return v, nil +} + +func (s String) ParseUint() (uint, error) { + uout, err := s.ParseUint64() + + return uint(uout), err +} + +func (s String) ParseUint64() (uint64, error) { + uout, err := strconv.ParseUint(string(s), 10, 64) + if err != nil { + return 0, fmt.Errorf("string uint:%w", err) } - return "" + return uout, nil } -func (s *String) Strings() []string { - return s.Val +func (s String) ParseFloat64() (float64, error) { + fout, err := strconv.ParseFloat(string(s), 64) + if err != nil { + return 0, fmt.Errorf("string float64:%w", err) + } + + return fout, nil +} + +func (s String) ParseBool() (bool, error) { + v, err := strconv.ParseBool(string(s)) + if err != nil { + return false, fmt.Errorf("string bool:%w", err) + } + + return v, nil +} + +func (s String) ParseDuration() (time.Duration, error) { + v, err := time.ParseDuration(string(s)) + if err != nil { + return 0, fmt.Errorf("string duration:%w", err) + } + + return v, nil } -func (s *String) Any() interface{} { - if s.Flag.IsArray() { - return s.Strings() +func (s String) ParseTime() (time.Time, error) { + v, err := time.Parse(time.RFC3339, string(s)) + if err != nil { + return time.Time{}, fmt.Errorf("string time:%w", err) } - return s.String() + return v, nil } diff --git a/input/value/string_test.go b/input/value/string_test.go new file mode 100644 index 0000000..0305770 --- /dev/null +++ b/input/value/string_test.go @@ -0,0 +1,28 @@ +package value_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "gitoa.ru/go-4devs/console/input/value" +) + +func TestStringUnmarshal(t *testing.T) { + t.Parallel() + + st := value.New("test") + sta := value.New([]string{"test1", "test2"}) + + ac := "" + require.NoError(t, st.Unmarshal(&ac)) + require.Equal(t, "test", ac) + + aca := []string{} + require.NoError(t, sta.Unmarshal(&aca)) + require.Equal(t, []string{"test1", "test2"}, aca) + + require.ErrorIs(t, sta.Unmarshal(ac), value.ErrWrongType) + require.ErrorIs(t, sta.Unmarshal(&ac), value.ErrWrongType) + require.ErrorIs(t, st.Unmarshal(aca), value.ErrWrongType) + require.ErrorIs(t, st.Unmarshal(&aca), value.ErrWrongType) +} diff --git a/input/value/time.go b/input/value/time.go index f751a33..e9b5482 100644 --- a/input/value/time.go +++ b/input/value/time.go @@ -1,44 +1,130 @@ package value import ( + "fmt" "time" +) - "gitoa.ru/go-4devs/console/input/value/flag" +var ( + _ ParseValue = Time{time.Now()} + _ SliceValue = (Times)(nil) ) -type Time struct { - empty - Val []time.Time - Flag flag.Flag +func NewTimes(in []time.Time) Slice { + return Slice{SliceValue: Times(in)} } -func (t *Time) Append(in string) error { - v, err := time.Parse(time.RFC3339, in) - if err != nil { - return err +type Times []time.Time + +func (t Times) Any() interface{} { + return t.Times() +} + +func (t Times) Unmarshal(val interface{}) error { + res, ok := val.(*[]time.Time) + if !ok { + return fmt.Errorf("%w: expect *[]time.Time", ErrWrongType) } - t.Val = append(t.Val, v) + *res = t return nil } -func (t *Time) Time() time.Time { - if !t.Flag.IsArray() && len(t.Val) == 1 { - return t.Val[0] - } +func (t Times) Strings() []string { + return nil +} + +func (t Times) Ints() []int { + return nil +} + +func (t Times) Int64s() []int64 { + return nil +} - return time.Time{} +func (t Times) Uints() []uint { + return nil +} + +func (t Times) Uint64s() []uint64 { + return nil +} + +func (t Times) Float64s() []float64 { + return nil } -func (t *Time) Times() []time.Time { - return t.Val +func (t Times) Bools() []bool { + return nil +} + +func (t Times) Durations() []time.Duration { + return nil +} + +func (t Times) Times() []time.Time { + out := make([]time.Time, len(t)) + copy(out, t) + + return out } -func (t *Time) Amy() interface{} { - if t.Flag.IsArray() { - return t.Times() +func NewTime(in time.Time) Read { + return Read{ParseValue: Time{Time: in}} +} + +type Time struct { + time.Time +} + +func (t Time) ParseString() (string, error) { + return t.Format(time.RFC3339), nil +} + +func (t Time) ParseInt() (int, error) { + return int(t.Unix()), nil +} + +func (t Time) ParseInt64() (int64, error) { + return t.Unix(), nil +} + +func (t Time) ParseUint() (uint, error) { + return uint(t.Unix()), nil +} + +func (t Time) ParseUint64() (uint64, error) { + return uint64(t.Unix()), nil +} + +func (t Time) ParseFloat64() (float64, error) { + return float64(t.UnixNano()), nil +} + +func (t Time) ParseBool() (bool, error) { + return false, fmt.Errorf("time bool:%w", ErrWrongType) +} + +func (t Time) ParseDuration() (time.Duration, error) { + return 0, fmt.Errorf("time duration:%w", ErrWrongType) +} + +func (t Time) ParseTime() (time.Time, error) { + return t.Time, nil +} + +func (t Time) Unmarshal(val interface{}) error { + res, ok := val.(*time.Time) + if !ok { + return fmt.Errorf("%w: expect *time.Time", ErrWrongType) } - return t.Time() + *res = t.Time + + return nil +} + +func (t Time) Any() interface{} { + return t.Time } diff --git a/input/value/uint.go b/input/value/uint.go index 1983b0d..25b4d76 100644 --- a/input/value/uint.go +++ b/input/value/uint.go @@ -1,44 +1,129 @@ package value import ( + "fmt" "strconv" + "time" +) - "gitoa.ru/go-4devs/console/input/value/flag" +var ( + _ ParseValue = Uint(0) + _ SliceValue = (Uints)(nil) ) -type Uint struct { - empty - Val []uint - Flag flag.Flag +func NewUints(in []uint) Slice { + return Slice{SliceValue: Uints(in)} +} + +type Uints []uint + +func (u Uints) Any() interface{} { + return u.Uints() } -func (u *Uint) Append(in string) error { - v, err := strconv.ParseUint(in, 10, 64) - if err != nil { - return err +func (u Uints) Unmarshal(val interface{}) error { + res, ok := val.(*[]uint) + if !ok { + return fmt.Errorf("%w: expect *[]uint", ErrWrongType) } - u.Val = append(u.Val, uint(v)) + *res = u return nil } -func (u *Uint) Uint() uint { - if !u.Flag.IsArray() && len(u.Val) == 1 { - return u.Val[0] - } +func (u Uints) Strings() []string { + return nil +} + +func (u Uints) Ints() []int { + return nil +} + +func (u Uints) Int64s() []int64 { + return nil +} + +func (u Uints) Uints() []uint { + out := make([]uint, len(u)) + copy(out, u) + + return out +} + +func (u Uints) Uint64s() []uint64 { + return nil +} + +func (u Uints) Float64s() []float64 { + return nil +} + +func (u Uints) Bools() []bool { + return nil +} + +func (u Uints) Durations() []time.Duration { + return nil +} + +func (u Uints) Times() []time.Time { + return nil +} + +func NewUint(in uint) Read { + return Read{ParseValue: Uint(in)} +} + +type Uint uint + +func (u Uint) ParseString() (string, error) { + return strconv.FormatUint(uint64(u), 10), nil +} + +func (u Uint) ParseInt() (int, error) { + return int(u), nil +} + +func (u Uint) ParseInt64() (int64, error) { + return int64(u), nil +} + +func (u Uint) ParseUint() (uint, error) { + return uint(u), nil +} + +func (u Uint) ParseUint64() (uint64, error) { + return uint64(u), nil +} + +func (u Uint) ParseFloat64() (float64, error) { + return float64(u), nil +} - return 0 +func (u Uint) ParseBool() (bool, error) { + return false, fmt.Errorf("uint:%w", ErrWrongType) } -func (u *Uint) Uints() []uint { - return u.Val +func (u Uint) ParseDuration() (time.Duration, error) { + return time.Duration(u), nil } -func (u *Uint) Any() interface{} { - if u.Flag.IsArray() { - return u.Uints() +func (u Uint) ParseTime() (time.Time, error) { + return time.Unix(0, int64(u)), nil +} + +func (u Uint) Unmarshal(val interface{}) error { + res, ok := val.(*uint) + if !ok { + return fmt.Errorf("%w: expect *uint", ErrWrongType) } - return u.Uint() + *res = uint(u) + + return nil +} + +func (u Uint) Any() interface{} { + return uint(u) } diff --git a/input/value/uint64.go b/input/value/uint64.go index a78d8cf..264216a 100644 --- a/input/value/uint64.go +++ b/input/value/uint64.go @@ -1,44 +1,129 @@ package value import ( + "fmt" "strconv" + "time" +) - "gitoa.ru/go-4devs/console/input/value/flag" +var ( + _ ParseValue = Uint64(0) + _ SliceValue = (Uint64s)(nil) ) -type Uint64 struct { - empty - Val []uint64 - Flag flag.Flag +func NewUint64s(in []uint64) Slice { + return Slice{SliceValue: Uint64s(in)} +} + +type Uint64s []uint64 + +func (u Uint64s) Any() interface{} { + return u.Uint64s() } -func (u *Uint64) Append(in string) error { - v, err := strconv.ParseUint(in, 10, 64) - if err != nil { - return err +func (u Uint64s) Unmarshal(val interface{}) error { + res, ok := val.(*[]uint64) + if !ok { + return fmt.Errorf("%w: expect *[]uint64", ErrWrongType) } - u.Val = append(u.Val, v) + *res = u return nil } -func (u *Uint64) Uint64() uint64 { - if !u.Flag.IsArray() && len(u.Val) == 1 { - return u.Val[0] - } +func (u Uint64s) Strings() []string { + return nil +} + +func (u Uint64s) Ints() []int { + return nil +} + +func (u Uint64s) Int64s() []int64 { + return nil +} + +func (u Uint64s) Uints() []uint { + return nil +} + +func (u Uint64s) Uint64s() []uint64 { + out := make([]uint64, len(u)) + copy(out, u) + + return out +} + +func (u Uint64s) Float64s() []float64 { + return nil +} + +func (u Uint64s) Bools() []bool { + return nil +} + +func (u Uint64s) Durations() []time.Duration { + return nil +} + +func (u Uint64s) Times() []time.Time { + return nil +} + +func NewUint64(in uint64) Read { + return Read{ParseValue: Uint64(in)} +} + +type Uint64 uint64 + +func (u Uint64) ParseString() (string, error) { + return strconv.FormatUint(uint64(u), 10), nil +} + +func (u Uint64) ParseInt() (int, error) { + return int(u), nil +} + +func (u Uint64) ParseInt64() (int64, error) { + return int64(u), nil +} + +func (u Uint64) ParseUint() (uint, error) { + return uint(u), nil +} + +func (u Uint64) ParseUint64() (uint64, error) { + return uint64(u), nil +} + +func (u Uint64) ParseFloat64() (float64, error) { + return float64(u), nil +} - return 0 +func (u Uint64) ParseBool() (bool, error) { + return false, fmt.Errorf("uint64 bool:%w", ErrWrongType) } -func (u *Uint64) Uint64s() []uint64 { - return u.Val +func (u Uint64) ParseDuration() (time.Duration, error) { + return time.Duration(u), nil } -func (u *Uint64) Any() interface{} { - if u.Flag.IsArray() { - return u.Uint64s() +func (u Uint64) ParseTime() (time.Time, error) { + return time.Unix(0, int64(0)), nil +} + +func (u Uint64) Unmarshal(val interface{}) error { + res, ok := val.(*uint64) + if !ok { + return fmt.Errorf("%w: expect *uint64", ErrWrongType) } - return u.Uint64() + *res = uint64(u) + + return nil +} + +func (u Uint64) Any() interface{} { + return uint64(u) } diff --git a/input/value/value.go b/input/value/value.go index 87d7d44..6359ef3 100644 --- a/input/value/value.go +++ b/input/value/value.go @@ -2,11 +2,19 @@ package value import ( "time" - - "gitoa.ru/go-4devs/console/input/value/flag" ) type Value interface { + ReadValue + ParseValue + ArrValue +} + +type UnmarshalValue interface { + Unmarshal(val interface{}) error +} + +type ReadValue interface { String() string Int() int Int64() int64 @@ -16,8 +24,19 @@ type Value interface { Bool() bool Duration() time.Duration Time() time.Time +} + +type AnyValue interface { Any() interface{} +} +type SliceValue interface { + AnyValue + UnmarshalValue + ArrValue +} + +type ArrValue interface { Strings() []string Ints() []int Int64s() []int64 @@ -29,86 +48,74 @@ type Value interface { Times() []time.Time } +//nolint:interfacebloat +type ParseValue interface { + ParseString() (string, error) + ParseInt() (int, error) + ParseInt64() (int64, error) + ParseUint() (uint, error) + ParseUint64() (uint64, error) + ParseFloat64() (float64, error) + ParseBool() (bool, error) + ParseDuration() (time.Duration, error) + ParseTime() (time.Time, error) + UnmarshalValue + AnyValue +} + type Append interface { Value - Append(string) error + Append(string) (Value, error) } -//nolint: gocyclo -func New(v interface{}) Append { - switch val := v.(type) { +//nolint:gocyclo,cyclop +func New(in interface{}) Value { + switch val := in.(type) { + case bool: + return Read{Bool(val)} + case []bool: + return NewBools(val) case string: - return &String{Val: []string{val}, Flag: flag.String} + return Read{String(val)} case int: - return &Int{Val: []int{val}, Flag: flag.Int} + return Read{Int(val)} case int64: - return &Int64{Val: []int64{val}, Flag: flag.Int64} + return Read{Int64(val)} case uint: - return &Uint{Val: []uint{val}, Flag: flag.Uint} + return Read{Uint(val)} case uint64: - return &Uint64{Val: []uint64{val}, Flag: flag.Uint64} + return Read{Uint64(val)} case float64: - return &Float64{Val: []float64{val}, Flag: flag.Float64} - case bool: - return &Bool{Val: []bool{val}, Flag: flag.Bool} + return Read{Float64(val)} case time.Duration: - return &Duration{Val: []time.Duration{val}, Flag: flag.Duration} + return Read{Duration(val)} case time.Time: - return &Time{Val: []time.Time{val}, Flag: flag.Time} + return Read{Time{val}} case []int64: - return &Int64{Val: val, Flag: flag.Int64 | flag.Array} + return Slice{Int64s(val)} case []uint: - return &Uint{Val: val, Flag: flag.Uint | flag.Array} + return Slice{Uints(val)} case []uint64: - return &Uint64{Val: val, Flag: flag.Uint64 | flag.Array} + return Slice{Uint64s(val)} case []float64: - return &Float64{Val: val, Flag: flag.Float64 | flag.Array} - case []bool: - return &Bool{Val: val, Flag: flag.Bool | flag.Array} + return Slice{Float64s(val)} case []time.Duration: - return &Duration{Val: val, Flag: flag.Duration | flag.Array} + return Slice{Durations(val)} case []time.Time: - return &Time{Val: val, Flag: flag.Time | flag.Array} + return Slice{Times(val)} case []string: - return &String{Val: val, Flag: flag.String | flag.Array} + return Slice{Strings(val)} case []int: - return &Int{Val: val, Flag: flag.Int | flag.Array} + return Slice{Ints(val)} case []interface{}: - return &Any{Val: val, Flag: flag.Any | flag.Array} - case Append: - return val + return Read{Any{v: val}} case Value: - return &Read{Value: val} + return val default: - if v != nil { - return &Any{Val: []interface{}{v}, Flag: flag.Any} + if in != nil { + return Read{Any{v: in}} } - return &empty{} - } -} - -func ByFlag(f flag.Flag) Append { - switch { - case f.IsInt(): - return &Int{Flag: f | flag.Int} - case f.IsInt64(): - return &Int64{Flag: f | flag.Int64} - case f.IsUint(): - return &Uint{Flag: f | flag.Uint} - case f.IsUint64(): - return &Uint64{Flag: f | flag.Uint64} - case f.IsFloat64(): - return &Float64{Flag: f | flag.Float64} - case f.IsBool(): - return &Bool{Flag: f | flag.Bool} - case f.IsDuration(): - return &Duration{Flag: f | flag.Duration} - case f.IsTime(): - return &Time{Flag: f | flag.Time} - case f.IsAny(): - return &Any{Flag: f | flag.Any} - default: - return &String{} + return Empty() } } diff --git a/input/variable/argtype.go b/input/variable/argtype.go new file mode 100644 index 0000000..6b9f71a --- /dev/null +++ b/input/variable/argtype.go @@ -0,0 +1,10 @@ +package variable + +//go:generate stringer -type=ArgType -linecomment + +type ArgType int + +const ( + TypeOption ArgType = iota + 1 // option + TypeArgument // argument +) diff --git a/input/variable/argtype_string.go b/input/variable/argtype_string.go new file mode 100644 index 0000000..cd1c446 --- /dev/null +++ b/input/variable/argtype_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -type=ArgType -linecomment"; DO NOT EDIT. + +package variable + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[TypeOption-1] + _ = x[TypeArgument-2] +} + +const _ArgType_name = "optionargument" + +var _ArgType_index = [...]uint8{0, 6, 14} + +func (i ArgType) String() string { + i -= 1 + if i < 0 || i >= ArgType(len(_ArgType_index)-1) { + return "ArgType(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _ArgType_name[_ArgType_index[i]:_ArgType_index[i+1]] +} diff --git a/input/variable/bool.go b/input/variable/bool.go new file mode 100644 index 0000000..43686be --- /dev/null +++ b/input/variable/bool.go @@ -0,0 +1,31 @@ +package variable + +import ( + "fmt" + "strconv" + + "gitoa.ru/go-4devs/console/input/flag" + "gitoa.ru/go-4devs/console/input/value" +) + +func Bool(name, description string, opts ...Option) Variable { + return String(name, description, append(opts, WithParse(CreateBool, AppendBool), Value(flag.Bool))...) +} + +func CreateBool(in string) (value.Value, error) { + out, err := strconv.ParseBool(in) + if err != nil { + return nil, fmt.Errorf("create bool:%w", err) + } + + return value.NewBool(out), nil +} + +func AppendBool(old value.Value, in string) (value.Value, error) { + out, err := strconv.ParseBool(in) + if err != nil { + return nil, fmt.Errorf("create bool:%w", err) + } + + return value.NewBools(append(old.Bools(), out)), nil +} diff --git a/input/variable/duration.go b/input/variable/duration.go new file mode 100644 index 0000000..e51ea83 --- /dev/null +++ b/input/variable/duration.go @@ -0,0 +1,31 @@ +package variable + +import ( + "fmt" + "time" + + "gitoa.ru/go-4devs/console/input/flag" + "gitoa.ru/go-4devs/console/input/value" +) + +func Duration(name, description string, opts ...Option) Variable { + return String(name, description, append(opts, WithParse(CreateDuration, AppendDuration), Value(flag.Duration))...) +} + +func CreateDuration(in string) (value.Value, error) { + out, err := time.ParseDuration(in) + if err != nil { + return nil, fmt.Errorf("create duration:%w", err) + } + + return value.NewDuration(out), nil +} + +func AppendDuration(old value.Value, in string) (value.Value, error) { + out, err := time.ParseDuration(in) + if err != nil { + return nil, fmt.Errorf("append duration:%w", err) + } + + return value.NewDurations(append(old.Durations(), out)), nil +} diff --git a/input/variable/err.go b/input/variable/err.go new file mode 100644 index 0000000..0272df1 --- /dev/null +++ b/input/variable/err.go @@ -0,0 +1,32 @@ +package variable + +import ( + "errors" + "fmt" +) + +type Error struct { + Name string + Err error + Type ArgType +} + +func (o Error) Error() string { + return fmt.Sprintf("%s: '%s' %s", o.Type, o.Name, o.Err) +} + +func (o Error) Is(err error) bool { + return errors.Is(err, o.Err) +} + +func (o Error) Unwrap() error { + return o.Err +} + +func Err(name string, t ArgType, err error) Error { + return Error{ + Name: name, + Type: t, + Err: err, + } +} diff --git a/input/variable/float64.go b/input/variable/float64.go new file mode 100644 index 0000000..d19777b --- /dev/null +++ b/input/variable/float64.go @@ -0,0 +1,31 @@ +package variable + +import ( + "fmt" + "strconv" + + "gitoa.ru/go-4devs/console/input/flag" + "gitoa.ru/go-4devs/console/input/value" +) + +func Float64(name, description string, opts ...Option) Variable { + return String(name, description, append(opts, WithParse(CreateFloat64, AppendFloat64), Value(flag.Float64))...) +} + +func CreateFloat64(in string) (value.Value, error) { + out, err := strconv.ParseFloat(in, 10) + if err != nil { + return nil, fmt.Errorf("create float64:%w", err) + } + + return value.NewFloat64(out), nil +} + +func AppendFloat64(old value.Value, in string) (value.Value, error) { + out, err := strconv.ParseFloat(in, 10) + if err != nil { + return nil, fmt.Errorf("append float64:%w", err) + } + + return value.NewFloat64s(append(old.Float64s(), out)), nil +} diff --git a/input/variable/int.go b/input/variable/int.go new file mode 100644 index 0000000..2dccb10 --- /dev/null +++ b/input/variable/int.go @@ -0,0 +1,31 @@ +package variable + +import ( + "fmt" + "strconv" + + "gitoa.ru/go-4devs/console/input/flag" + "gitoa.ru/go-4devs/console/input/value" +) + +func Int(name, description string, opts ...Option) Variable { + return New(name, description, append(opts, WithParse(CreateInt, AppendInt), Value(flag.Int))...) +} + +func AppendInt(old value.Value, in string) (value.Value, error) { + out, err := strconv.Atoi(in) + if err != nil { + return nil, fmt.Errorf("append int:%w", err) + } + + return value.NewInts(append(old.Ints(), out)), nil +} + +func CreateInt(in string) (value.Value, error) { + out, err := strconv.Atoi(in) + if err != nil { + return nil, fmt.Errorf("create int:%w", err) + } + + return value.NewInt(out), nil +} diff --git a/input/variable/int64.go b/input/variable/int64.go new file mode 100644 index 0000000..26d2d1a --- /dev/null +++ b/input/variable/int64.go @@ -0,0 +1,31 @@ +package variable + +import ( + "fmt" + "strconv" + + "gitoa.ru/go-4devs/console/input/flag" + "gitoa.ru/go-4devs/console/input/value" +) + +func Int64(name, description string, opts ...Option) Variable { + return String(name, description, append(opts, WithParse(CreateInt64, AppendInt64), Value(flag.Int64))...) +} + +func CreateInt64(in string) (value.Value, error) { + out, err := strconv.ParseInt(in, 10, 64) + if err != nil { + return nil, fmt.Errorf("create int64:%w", err) + } + + return value.NewInt64(out), nil +} + +func AppendInt64(old value.Value, in string) (value.Value, error) { + out, err := strconv.ParseInt(in, 10, 64) + if err != nil { + return nil, fmt.Errorf("append int64:%w", err) + } + + return value.NewInt64s(append(old.Int64s(), out)), nil +} diff --git a/input/variable/string.go b/input/variable/string.go new file mode 100644 index 0000000..ccfb5d1 --- /dev/null +++ b/input/variable/string.go @@ -0,0 +1,17 @@ +package variable + +import ( + "gitoa.ru/go-4devs/console/input/value" +) + +func String(name, description string, opts ...Option) Variable { + return New(name, description, opts...) +} + +func CreateString(in string) (value.Value, error) { + return value.NewString(in), nil +} + +func AppendString(old value.Value, in string) (value.Value, error) { + return value.NewStrings(append(old.Strings(), in)), nil +} diff --git a/input/variable/time.go b/input/variable/time.go new file mode 100644 index 0000000..4b04071 --- /dev/null +++ b/input/variable/time.go @@ -0,0 +1,77 @@ +package variable + +import ( + "fmt" + "time" + + "gitoa.ru/go-4devs/console/input/errs" + "gitoa.ru/go-4devs/console/input/flag" + "gitoa.ru/go-4devs/console/input/value" +) + +const ( + ParamFormat = "format" +) + +func Time(name, description string, opts ...Option) Variable { + return String(name, description, append(opts, + WithParamParse(CreateTime, AppendTime), + WithParam(ParamFormat, RFC3339), + Value(flag.Time), + )...) +} + +func RFC3339(in interface{}) error { + v, ok := in.(*string) + if !ok { + return fmt.Errorf("%w: expect *string got %T", errs.ErrWrongType, in) + } + + *v = time.RFC3339 + + return nil +} + +func CreateTime(param Param) Create { + var ( + formatErr error + format string + ) + + formatErr = param.Value(ParamFormat, &format) + + return func(in string) (value.Value, error) { + if formatErr != nil { + return nil, fmt.Errorf("create format:%w", formatErr) + } + + out, err := time.Parse(format, in) + if err != nil { + return nil, fmt.Errorf("create time:%w", err) + } + + return value.NewTime(out), nil + } +} + +func AppendTime(param Param) Append { + var ( + formatErr error + format string + ) + + formatErr = param.Value(ParamFormat, &format) + + return func(old value.Value, in string) (value.Value, error) { + if formatErr != nil { + return nil, fmt.Errorf("append format:%w", formatErr) + } + + out, err := time.Parse(format, in) + if err != nil { + return nil, fmt.Errorf("append time:%w", err) + } + + return value.NewTimes(append(old.Times(), out)), nil + } +} diff --git a/input/variable/uint.go b/input/variable/uint.go new file mode 100644 index 0000000..f6cc768 --- /dev/null +++ b/input/variable/uint.go @@ -0,0 +1,31 @@ +package variable + +import ( + "fmt" + "strconv" + + "gitoa.ru/go-4devs/console/input/flag" + "gitoa.ru/go-4devs/console/input/value" +) + +func Uint(name, description string, opts ...Option) Variable { + return String(name, description, append(opts, WithParse(CreateUint, AppendUint), Value(flag.Uint))...) +} + +func CreateUint(in string) (value.Value, error) { + out, err := strconv.ParseUint(in, 10, 64) + if err != nil { + return nil, fmt.Errorf("create uint:%w", err) + } + + return value.NewUint(uint(out)), nil +} + +func AppendUint(old value.Value, in string) (value.Value, error) { + out, err := strconv.ParseUint(in, 10, 64) + if err != nil { + return nil, fmt.Errorf("append uint:%w", err) + } + + return value.NewUints(append(old.Uints(), uint(out))), nil +} diff --git a/input/variable/uint64.go b/input/variable/uint64.go new file mode 100644 index 0000000..70dc7b2 --- /dev/null +++ b/input/variable/uint64.go @@ -0,0 +1,31 @@ +package variable + +import ( + "fmt" + "strconv" + + "gitoa.ru/go-4devs/console/input/flag" + "gitoa.ru/go-4devs/console/input/value" +) + +func Uint64(name, descriontion string, opts ...Option) Variable { + return String(name, descriontion, append(opts, WithParse(CreateUint64, AppendUint64), Value(flag.Uint64))...) +} + +func CreateUint64(in string) (value.Value, error) { + out, err := strconv.ParseUint(in, 10, 64) + if err != nil { + return nil, fmt.Errorf("create uint64:%w", err) + } + + return value.NewUint64(out), nil +} + +func AppendUint64(old value.Value, in string) (value.Value, error) { + out, err := strconv.ParseUint(in, 10, 64) + if err != nil { + return nil, fmt.Errorf("append uint64:%w", err) + } + + return value.NewUint64s(append(old.Uint64s(), out)), nil +} diff --git a/input/variable/variable.go b/input/variable/variable.go new file mode 100644 index 0000000..c3f5bf2 --- /dev/null +++ b/input/variable/variable.go @@ -0,0 +1,158 @@ +package variable + +import ( + "fmt" + + "gitoa.ru/go-4devs/console/input/errs" + "gitoa.ru/go-4devs/console/input/flag" + "gitoa.ru/go-4devs/console/input/value" +) + +type Option func(*Variable) + +func WithType(t ArgType) Option { + return func(v *Variable) { + v.Type = t + } +} + +func ArgOption(v *Variable) { + v.Type = TypeOption +} + +func ArgArgument(v *Variable) { + v.Type = TypeArgument +} + +func Value(in flag.Flag) Option { + return func(v *Variable) { + v.Flag |= in + } +} + +func Default(in value.Value) Option { + return func(v *Variable) { + v.Default = in + } +} + +func Required(v *Variable) { + v.Flag |= flag.Required +} + +func WithParse(create Create, update Append) Option { + return func(v *Variable) { + v.append = func(Param) Append { return update } + v.create = func(Param) Create { return create } + } +} + +func WithParamParse(create func(Param) Create, update func(Param) Append) Option { + return func(v *Variable) { + v.append = update + v.create = create + } +} + +func Valid(f ...func(value.Value) error) Option { + return func(v *Variable) { + v.Valid = f + } +} + +func Array(o *Variable) { + o.Flag |= flag.Array +} + +func WithParam(name string, fn func(interface{}) error) Option { + return func(v *Variable) { + v.params[name] = fn + } +} + +type ( + Create func(s string) (value.Value, error) + Append func(old value.Value, s string) (value.Value, error) +) + +func New(name, description string, opts ...Option) Variable { + res := Variable{ + Name: name, + Description: description, + Type: TypeOption, + create: func(Param) Create { return CreateString }, + append: func(Param) Append { return AppendString }, + params: make(Params), + } + + for _, opt := range opts { + opt(&res) + } + + return res +} + +type Variable struct { + Name string + Description string + Alias string + Flag flag.Flag + Type ArgType + Default value.Value + Valid []func(value.Value) error + params Params + create func(Param) Create + append func(Param) Append +} + +func (v Variable) Validate(in value.Value) error { + for _, valid := range v.Valid { + if err := valid(in); err != nil { + return Err(v.Name, v.Type, err) + } + } + + return nil +} + +func (v Variable) IsArray() bool { + return v.Flag.IsArray() +} + +func (v Variable) IsRequired() bool { + return v.Flag.IsRequired() +} + +func (v Variable) HasDefault() bool { + return v.Default != nil +} + +func (v Variable) IsBool() bool { + return v.Flag.IsBool() +} + +func (v Variable) HasShort() bool { + return v.Type == TypeOption && len(v.Alias) == 1 +} + +func (v Variable) Create(s string) (value.Value, error) { + return v.create(v.params)(s) +} + +func (v Variable) Append(old value.Value, s string) (value.Value, error) { + return v.append(v.params)(old, s) +} + +type Param interface { + Value(name string, v interface{}) error +} + +type Params map[string]func(interface{}) error + +func (p Params) Value(name string, v interface{}) error { + if p, ok := p[name]; ok { + return p(v) + } + + return fmt.Errorf("%w: param %v", errs.ErrNotFound, name) +} diff --git a/list.go b/list.go index 008dac9..9a73248 100644 --- a/list.go +++ b/list.go @@ -15,7 +15,7 @@ import ( const defaultLenNamespace = 2 -//nolint: gochecknoinits +//nolint:gochecknoinits func init() { MustRegister(list()) } @@ -32,71 +32,15 @@ You can also display the commands for a specific namespace: You can also output the information in other formats by using the --format option: {{ .Bin }} {{ .Name }} --format=xml `, - Execute: func(ctx context.Context, in input.Input, out output.Output) error { - ns := in.Argument(ctx, "namespace").String() - format := in.Option(ctx, helpOptFormat).String() - - des, err := descriptor.Find(format) - if err != nil { - return err - } - cmds := Commands() - commands := descriptor.Commands{ - Namespace: ns, - Definition: Default(input.NewDefinition()), - } - groups := make(map[string]*descriptor.NSCommand) - namespaces := make([]string, 0, len(cmds)) - empty := descriptor.NSCommand{} - - for _, name := range cmds { - if ns != "" && !strings.HasPrefix(name, ns+":") { - continue - } - - cmd, _ := Find(name) - if cmd.Hidden { - continue - } - - gn := strings.SplitN(name, ":", 2) - if len(gn) != defaultLenNamespace { - empty.Append(cmd.Name, cmd.Description) - - continue - } - - if _, ok := groups[gn[0]]; !ok { - groups[gn[0]] = &descriptor.NSCommand{ - Name: gn[0], - } - namespaces = append(namespaces, gn[0]) - } - - groups[gn[0]].Append(name, cmd.Description) - } - if len(empty.Commands) > 0 { - commands.Commands = append(commands.Commands, empty) - } - - for _, name := range namespaces { - commands.Commands = append(commands.Commands, *groups[name]) - } - - if ns != "" && len(commands.Commands) == 0 { - return fmt.Errorf("%w: namespace %s", ErrNotFound, ns) - } - - return des.Commands(ctx, out, commands) - }, + Execute: executeList, Configure: func(ctx context.Context, config *input.Definition) error { formats := descriptor.Descriptors() config. SetArguments( - argument.New("namespace", "The namespace name"), + argument.String("namespace", "The namespace name"), ). SetOptions( - option.New(helpOptFormat, fmt.Sprintf("The output format (%s)", strings.Join(formats, ", ")), + option.String(helpOptFormat, fmt.Sprintf("The output format (%s)", strings.Join(formats, ", ")), option.Required, option.Default(formats[0]), option.Valid( @@ -110,3 +54,69 @@ You can also output the information in other formats by using the --for }, } } + +//nolint:cyclop +func executeList(ctx context.Context, in input.Input, out output.Output) error { + ns := in.Argument(ctx, "namespace").String() + format := in.Option(ctx, helpOptFormat).String() + + des, err := descriptor.Find(format) + if err != nil { + return fmt.Errorf("find descriptor: %w", err) + } + + cmds := Commands() + commands := descriptor.Commands{ + Namespace: ns, + Definition: Default(input.NewDefinition()), + } + groups := make(map[string]*descriptor.NSCommand) + namespaces := make([]string, 0, len(cmds)) + empty := descriptor.NSCommand{} + + for _, name := range cmds { + if ns != "" && !strings.HasPrefix(name, ns+":") { + continue + } + + cmd, _ := Find(name) + if cmd.Hidden { + continue + } + + gn := strings.SplitN(name, ":", 2) + if len(gn) != defaultLenNamespace { + empty.Append(cmd.Name, cmd.Description) + + continue + } + + if _, ok := groups[gn[0]]; !ok { + groups[gn[0]] = &descriptor.NSCommand{ + Name: gn[0], + } + + namespaces = append(namespaces, gn[0]) + } + + groups[gn[0]].Append(name, cmd.Description) + } + + if len(empty.Commands) > 0 { + commands.Commands = append(commands.Commands, empty) + } + + for _, name := range namespaces { + commands.Commands = append(commands.Commands, *groups[name]) + } + + if ns != "" && len(commands.Commands) == 0 { + return fmt.Errorf("%w: namespace %s", ErrNotFound, ns) + } + + if err := des.Commands(ctx, out, commands); err != nil { + return fmt.Errorf("descriptor:%w", err) + } + + return nil +} diff --git a/output/descriptor/descriptor.go b/output/descriptor/descriptor.go index e5f2e38..fabef29 100644 --- a/output/descriptor/descriptor.go +++ b/output/descriptor/descriptor.go @@ -11,7 +11,7 @@ import ( var ErrDescriptorNotFound = errors.New("descriptor not found") -//nolint: gochecknoglobals +//nolint:gochecknoglobals var ( descriptors = map[string]Descriptor{ "txt": &txt{}, diff --git a/output/descriptor/txt.go b/output/descriptor/txt.go index f99812e..7189ee4 100644 --- a/output/descriptor/txt.go +++ b/output/descriptor/txt.go @@ -10,8 +10,8 @@ import ( "time" "gitoa.ru/go-4devs/console/input" + "gitoa.ru/go-4devs/console/input/flag" "gitoa.ru/go-4devs/console/input/value" - "gitoa.ru/go-4devs/console/input/value/flag" "gitoa.ru/go-4devs/console/output" ) @@ -43,7 +43,7 @@ var ( {{- help . }} `)) - txtListTempkate = template.Must(template.New("txt_list"). + txtListTemplate = template.Must(template.New("txt_list"). Funcs(txtFunc). Parse(`Usage: command [options] [arguments] @@ -58,7 +58,7 @@ func (t *txt) Command(ctx context.Context, out output.Output, cmd Command) error var tpl bytes.Buffer if err := txtHelpTemplate.Execute(&tpl, cmd); err != nil { - return err + return fmt.Errorf("execute txt help tpl:%w", err) } out.Println(ctx, tpl.String()) @@ -69,8 +69,8 @@ func (t *txt) Command(ctx context.Context, out output.Output, cmd Command) error func (t *txt) Commands(ctx context.Context, out output.Output, cmds Commands) error { var buf bytes.Buffer - if err := txtListTempkate.Execute(&buf, cmds); err != nil { - return err + if err := txtListTemplate.Execute(&buf, cmds); err != nil { + return fmt.Errorf("execute txt list tpl:%w", err) } out.Println(ctx, buf.String()) @@ -78,36 +78,37 @@ func (t *txt) Commands(ctx context.Context, out output.Output, cmds Commands) er return nil } -func txtDefaultArray(v value.Value, f flag.Flag) string { - st := v.Strings() +//nolint:cyclop +func txtDefaultArray(val value.Value, fl flag.Flag) string { + st := val.Strings() switch { - case f.IsInt(): - for _, i := range v.Ints() { + case fl.IsInt(): + for _, i := range val.Ints() { st = append(st, strconv.Itoa(i)) } - case f.IsInt64(): - for _, i := range v.Int64s() { + case fl.IsInt64(): + for _, i := range val.Int64s() { st = append(st, strconv.FormatInt(i, 10)) } - case f.IsUint(): - for _, u := range v.Uints() { + case fl.IsUint(): + for _, u := range val.Uints() { st = append(st, strconv.FormatUint(uint64(u), 10)) } - case f.IsUint64(): - for _, u := range v.Uint64s() { + case fl.IsUint64(): + for _, u := range val.Uint64s() { st = append(st, strconv.FormatUint(u, 10)) } - case f.IsFloat64(): - for _, f := range v.Float64s() { + case fl.IsFloat64(): + for _, f := range val.Float64s() { st = append(st, strconv.FormatFloat(f, 'g', -1, 64)) } - case f.IsDuration(): - for _, d := range v.Durations() { + case fl.IsDuration(): + for _, d := range val.Durations() { st = append(st, d.String()) } - case f.IsTime(): - for _, d := range v.Times() { + case fl.IsTime(): + for _, d := range val.Times() { st = append(st, d.Format(time.RFC3339)) } } @@ -115,32 +116,33 @@ func txtDefaultArray(v value.Value, f flag.Flag) string { return strings.Join(st, ",") } -func txtDefault(v value.Value, f flag.Flag) []byte { +//nolint:cyclop +func txtDefault(val value.Value, fl flag.Flag) []byte { var buf bytes.Buffer buf.WriteString(" [default: ") switch { - case f.IsArray(): - buf.WriteString(txtDefaultArray(v, f)) - case f.IsInt(): - buf.WriteString(strconv.Itoa(v.Int())) - case f.IsInt64(): - buf.WriteString(strconv.FormatInt(v.Int64(), 10)) - case f.IsUint(): - buf.WriteString(strconv.FormatUint(uint64(v.Uint()), 10)) - case f.IsUint64(): - buf.WriteString(strconv.FormatUint(v.Uint64(), 10)) - case f.IsFloat64(): - buf.WriteString(strconv.FormatFloat(v.Float64(), 'g', -1, 64)) - case f.IsDuration(): - buf.WriteString(v.Duration().String()) - case f.IsTime(): - buf.WriteString(v.Time().Format(time.RFC3339)) - case f.IsAny(): - buf.WriteString(fmt.Sprint(v.Any())) + case fl.IsArray(): + buf.WriteString(txtDefaultArray(val, fl)) + case fl.IsInt(): + buf.WriteString(strconv.Itoa(val.Int())) + case fl.IsInt64(): + buf.WriteString(strconv.FormatInt(val.Int64(), 10)) + case fl.IsUint(): + buf.WriteString(strconv.FormatUint(uint64(val.Uint()), 10)) + case fl.IsUint64(): + buf.WriteString(strconv.FormatUint(val.Uint64(), 10)) + case fl.IsFloat64(): + buf.WriteString(strconv.FormatFloat(val.Float64(), 'g', -1, 64)) + case fl.IsDuration(): + buf.WriteString(val.Duration().String()) + case fl.IsTime(): + buf.WriteString(val.Time().Format(time.RFC3339)) + case fl.IsAny(): + buf.WriteString(fmt.Sprint(val.Any())) default: - buf.WriteString(v.String()) + buf.WriteString(val.String()) } buf.WriteString("]") @@ -214,7 +216,7 @@ func txtDefinitionOption(maxLen int, def *input.Definition) string { if opt.HasShort() { op.WriteString("-") - op.WriteString(opt.Short) + op.WriteString(opt.Alias) op.WriteString(", ") } else { op.WriteString(" ") @@ -354,18 +356,18 @@ func totalWidth(def *input.Definition) int { for _, name := range def.Options() { opt, _ := def.Option(name) - l := len(opt.Name) + 6 + current := len(opt.Name) + 6 if !opt.IsBool() { - l = l*2 + 1 + current = current*2 + 1 } if opt.HasDefault() { - l += 2 + current += 2 } - if l > max { - max = l + if current > max { + max = current } } diff --git a/output/formatter/formatter.go b/output/formatter/formatter.go index 6993ed3..a2d6d98 100644 --- a/output/formatter/formatter.go +++ b/output/formatter/formatter.go @@ -8,7 +8,6 @@ import ( "gitoa.ru/go-4devs/console/output/style" ) -//nolint: gochecknoglobals var re = regexp.MustCompile(`<(([a-z][^<>]+)|/([a-z][^<>]+)?)>`) func WithStyle(styles func(string) (style.Style, error)) func(*Formatter) { @@ -18,15 +17,15 @@ func WithStyle(styles func(string) (style.Style, error)) func(*Formatter) { } func New(opts ...func(*Formatter)) *Formatter { - f := &Formatter{ + formatter := &Formatter{ styles: style.Find, } for _, opt := range opts { - opt(f) + opt(formatter) } - return f + return formatter } type Formatter struct { diff --git a/output/formatter/formatter_test.go b/output/formatter/formatter_test.go index 6c87150..3c5720a 100644 --- a/output/formatter/formatter_test.go +++ b/output/formatter/formatter_test.go @@ -8,6 +8,8 @@ import ( ) func TestFormatter(t *testing.T) { + t.Parallel() + ctx := context.Background() formatter := formatter.New() diff --git a/output/formatter/none_test.go b/output/formatter/none_test.go index b9614d7..5a0c4d6 100644 --- a/output/formatter/none_test.go +++ b/output/formatter/none_test.go @@ -8,6 +8,8 @@ import ( ) func TestNone(t *testing.T) { + t.Parallel() + ctx := context.Background() none := formatter.None() diff --git a/output/output.go b/output/output.go index 56f61ac..3ba6284 100644 --- a/output/output.go +++ b/output/output.go @@ -63,15 +63,13 @@ func (o Output) Write(b []byte) (int, error) { } func (o Output) Writer(ctx context.Context, verb verbosity.Verbosity) io.Writer { - return verbosityWriter{ctx, o, verb} + return verbosityWriter(func(b []byte) (int, error) { + return o(ctx, verb, string(b)) + }) } -type verbosityWriter struct { - ctx context.Context - out Output - verb verbosity.Verbosity -} +type verbosityWriter func(b []byte) (int, error) func (w verbosityWriter) Write(b []byte) (int, error) { - return w.out(w.ctx, w.verb, string(b)) + return w(b) } diff --git a/output/style/color.go b/output/style/color.go index bb1c0b3..a5e98a2 100644 --- a/output/style/color.go +++ b/output/style/color.go @@ -28,13 +28,13 @@ const ( type Option string func (o Option) Apply(action int) string { - v := string(o) + out := string(o) switch action { case ActionSet: - return v[0:1] + return out[0:1] case ActionUnset: - return v[1:] + return out[1:] } return "" diff --git a/output/style/style.go b/output/style/style.go index 91a653e..b6f6c99 100644 --- a/output/style/style.go +++ b/output/style/style.go @@ -7,7 +7,7 @@ import ( "sync" ) -//nolint: gochecknoglobals +//nolint:gochecknoglobals var ( styles = map[string]Style{ "error": {Foreground: White, Background: Red}, diff --git a/output/writer.go b/output/writer.go index f7b3589..b620138 100644 --- a/output/writer.go +++ b/output/writer.go @@ -41,6 +41,11 @@ func FormatString(_ verbosity.Verbosity, msg string, kv ...label.KeyValue) strin func New(w io.Writer, format func(verb verbosity.Verbosity, msg string, kv ...label.KeyValue) string) Output { return func(ctx context.Context, verb verbosity.Verbosity, msg string, kv ...label.KeyValue) (int, error) { - return fmt.Fprint(w, format(verb, msg, kv...)) + out, err := fmt.Fprint(w, format(verb, msg, kv...)) + if err != nil { + return 0, fmt.Errorf("writer fprint:%w", err) + } + + return out, nil } } diff --git a/output/writer_test.go b/output/writer_test.go index 931c394..6ead6e1 100644 --- a/output/writer_test.go +++ b/output/writer_test.go @@ -10,6 +10,8 @@ import ( ) func TestNew(t *testing.T) { + t.Parallel() + ctx := context.Background() buf := bytes.Buffer{} wr := output.New(&buf, output.FormatString) diff --git a/register.go b/register.go index bd27c59..8cddd5d 100644 --- a/register.go +++ b/register.go @@ -20,31 +20,31 @@ var ( ErrCommandDuplicate = errors.New("console: duplicate command") ) -//nolint: gochecknoglobals +//nolint:gochecknoglobals var ( commandsMu sync.RWMutex commands = make(map[string]*Command) findCommand = regexp.MustCompile("([^:]+|)") ) -type ErrorAlternatives struct { +type AlternativesError struct { alt []string err error } -func (e ErrorAlternatives) Error() string { +func (e AlternativesError) Error() string { return fmt.Sprintf("%s, alternatives: [%s]", e.err, strings.Join(e.alt, ",")) } -func (e ErrorAlternatives) Is(err error) bool { +func (e AlternativesError) Is(err error) bool { return errors.Is(e.err, err) } -func (e ErrorAlternatives) Unwrap() error { +func (e AlternativesError) Unwrap() error { return e.err } -func (e ErrorAlternatives) Alternatives() []string { +func (e AlternativesError) Alternatives() []string { return e.alt } @@ -115,7 +115,7 @@ func Find(name string) (*Command, error) { cmdRegexp, err := regexp.Compile("^" + nameRegexp + "$") if err != nil { - return nil, err + return nil, fmt.Errorf("find by regexp:%w", err) } for name := range commands { @@ -134,7 +134,7 @@ func Find(name string) (*Command, error) { names[i] = findCommands[i].Name } - return nil, ErrorAlternatives{alt: names, err: ErrNotFound} + return nil, AlternativesError{alt: names, err: ErrNotFound} } return nil, ErrNotFound diff --git a/register_test.go b/register_test.go index ead6fde..6176b8e 100644 --- a/register_test.go +++ b/register_test.go @@ -7,6 +7,8 @@ import ( ) func TestFind(t *testing.T) { + t.Parallel() + cases := map[string]string{ "fdevs:console:test": "fdevs:console:test", "fd:c:t": "fdevs:console:test", @@ -18,13 +20,13 @@ func TestFind(t *testing.T) { for name, ex := range cases { res, err := console.Find(name) if err != nil { - t.Errorf("expect err, got:%s", err) + t.Errorf("%v expect err, got:%s", name, err) continue } if res.Name != ex { - t.Errorf("expect: %s, got: %s", ex, res) + t.Errorf("%v expect: %s, got: %s", name, ex, res) } } }