From 8886872c77ee62b59820ee78997237c832fc38ee Mon Sep 17 00:00:00 2001 From: andrey1s Date: Sun, 25 Oct 2020 19:20:19 +0300 Subject: [PATCH] update input/outpu --- app.go | 16 +- command_test.go | 6 +- console.go | 31 +-- console_test.go | 10 +- example/pkg/command/create_user_test.go | 9 +- input/argv.go | 153 +++++++++++++ input/argv/input.go | 212 ------------------ input/array.go | 168 ++++++++++++++ input/array/input.go | 87 ------- input/definition.go | 9 +- input/{ => errs}/error.go | 14 +- input/input.go | 12 +- input/type.go | 18 ++ input/type_string.go | 24 ++ input/validator/not_blank_test.go | 2 +- input/value/any.go | 2 +- input/value/bool.go | 2 +- input/value/duration.go | 2 +- input/value/empty.go | 48 ++-- input/value/float64.go | 2 +- input/value/int.go | 2 +- input/value/int64.go | 2 +- input/value/read.go | 2 +- input/value/string.go | 2 +- input/value/time.go | 2 +- input/value/uint.go | 2 +- input/value/uint64.go | 2 +- input/value/value.go | 12 +- input/wrap.go | 36 +++ input/wrap/input.go | 105 --------- output/formatter.go | 22 ++ output/output.go | 44 ++-- output/quiet.go | 13 ++ output/verbosity.go | 17 ++ output/verbosity/norm.go | 23 -- output/verbosity/verbosity.go | 13 ++ output/verbosity/verbosity_string.go | 28 +++ output/wrap/formatter.go | 22 -- output/writer.go | 45 ++++ output/writer/output.go | 45 ---- .../{writer/output_test.go => writer_test.go} | 5 +- 41 files changed, 659 insertions(+), 612 deletions(-) create mode 100644 input/argv.go delete mode 100644 input/argv/input.go create mode 100644 input/array.go delete mode 100644 input/array/input.go rename input/{ => errs}/error.go (79%) create mode 100644 input/type.go create mode 100644 input/type_string.go create mode 100644 input/wrap.go delete mode 100644 input/wrap/input.go create mode 100644 output/formatter.go create mode 100644 output/quiet.go create mode 100644 output/verbosity.go delete mode 100644 output/verbosity/norm.go create mode 100644 output/verbosity/verbosity.go create mode 100644 output/verbosity/verbosity_string.go delete mode 100644 output/wrap/formatter.go create mode 100644 output/writer.go delete mode 100644 output/writer/output.go rename output/{writer/output_test.go => writer_test.go} (89%) diff --git a/app.go b/app.go index 47ceda1..0ad42f2 100644 --- a/app.go +++ b/app.go @@ -5,10 +5,8 @@ import ( "os" "gitoa.ru/go-4devs/console/input" - "gitoa.ru/go-4devs/console/input/argv" "gitoa.ru/go-4devs/console/input/value" "gitoa.ru/go-4devs/console/output" - "gitoa.ru/go-4devs/console/output/writer" ) // WithOutput sets outpu,^ by default output os.Stdout. @@ -42,7 +40,7 @@ func WithExit(f func(int)) func(*App) { // New creates and configure new console app. func New(opts ...func(*App)) *App { a := &App{ - out: writer.Stdout(), + out: output.Stdout(), exit: os.Exit, } @@ -64,7 +62,9 @@ func New(opts ...func(*App)) *App { skip = 1 } - a.in = argv.New(os.Args[skip:]) + a.in = &input.Argv{ + Args: os.Args[skip:], + } } return a @@ -130,10 +130,12 @@ func (a *App) list(ctx context.Context) error { if err != nil { return err } + in := &input.Wrap{ + Input: a.in, + } + in.SetArgument("command_name", value.New(CommandList)) - a.in.SetArgument("command_name", value.New(CommandList)) - - return Run(ctx, cmd, a.in, a.out) + return Run(ctx, cmd, in, a.out) } func (a *App) printError(ctx context.Context, err error) { diff --git a/command_test.go b/command_test.go index 90f45e0..500a2c2 100644 --- a/command_test.go +++ b/command_test.go @@ -11,10 +11,8 @@ import ( "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/array" "gitoa.ru/go-4devs/console/input/option" "gitoa.ru/go-4devs/console/output" - "gitoa.ru/go-4devs/console/output/writer" ) //nolint: gochecknoinits @@ -91,8 +89,8 @@ func TestChainHandle(t *testing.T) { var cnt int32 ctx := context.Background() - in := array.New() - out := writer.Stdout() + in := &input.Array{} + out := output.Stdout() handle := func(ctx context.Context, in input.Input, out output.Output, next console.Action) error { atomic.AddInt32(&cnt, 1) diff --git a/console.go b/console.go index f37217f..aef1831 100644 --- a/console.go +++ b/console.go @@ -10,7 +10,6 @@ import ( "gitoa.ru/go-4devs/console/input/value" "gitoa.ru/go-4devs/console/output" "gitoa.ru/go-4devs/console/output/verbosity" - "gitoa.ru/go-4devs/console/output/wrap" ) const ( @@ -36,7 +35,7 @@ func Run(ctx context.Context, cmd *Command, in input.Input, out output.Output) e if err := in.Bind(ctx, Default(def)); err != nil { ansi(ctx, in, out).Print(ctx, "\n\n ", err, "\n\n") - return showHelp(ctx, cmd, in, wrap.Ansi(out)) + return showHelp(ctx, cmd, in, output.Ansi(out)) } out = ansi(ctx, in, out) @@ -64,13 +63,13 @@ func Run(ctx context.Context, cmd *Command, in input.Input, out output.Output) e func ansi(ctx context.Context, in input.Input, out output.Output) output.Output { switch { case in.Option(ctx, "ansi").Bool(): - out = wrap.Ansi(out) + out = output.Ansi(out) case in.Option(ctx, "no-ansi").Bool(): - out = wrap.None(out) + out = output.None(out) case lookupEnv("NO_COLOR"): - out = wrap.None(out) + out = output.None(out) default: - out = wrap.Ansi(out) + out = output.Ansi(out) } return out @@ -85,19 +84,19 @@ func lookupEnv(name string) bool { func verbose(ctx context.Context, in input.Input, out output.Output) output.Output { switch { case in.Option(ctx, "quiet").Bool(): - out = verbosity.Quiet() + out = output.Quiet() default: v := in.Option(ctx, "verbose").Bools() switch { case len(v) == verboseInfo: - out = verbosity.Verb(out, output.VerbosityInfo) + out = output.Verbosity(out, verbosity.Info) case len(v) == verboseDebug: - out = verbosity.Verb(out, output.VerbosityDebug) + out = output.Verbosity(out, verbosity.Debug) case len(v) >= verboseTrace: - out = verbosity.Verb(out, output.VerbosityTrace) + out = output.Verbosity(out, verbosity.Trace) default: - out = verbosity.Verb(out, output.VerbosityNorm) + out = output.Verbosity(out, verbosity.Norm) } } @@ -105,8 +104,12 @@ 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 { - in.SetArgument(HelpArgumentCommandName, value.New(cmd.Name)) - in.SetOption("help", value.New(false)) + w := &input.Wrap{ + Input: in, + } + + w.SetArgument(HelpArgumentCommandName, value.New(cmd.Name)) + w.SetOption("help", value.New(false)) if _, err := Find(cmd.Name); errors.Is(err, ErrNotFound) { register(cmd) @@ -117,7 +120,7 @@ func showHelp(ctx context.Context, cmd *Command, in input.Input, out output.Outp return err } - return Run(ctx, help, in, out) + return Run(ctx, help, w, out) } // Default options and argument command. diff --git a/console_test.go b/console_test.go index e2911cd..f2f4911 100644 --- a/console_test.go +++ b/console_test.go @@ -5,16 +5,16 @@ import ( "fmt" "gitoa.ru/go-4devs/console" - "gitoa.ru/go-4devs/console/input/array" + "gitoa.ru/go-4devs/console/input" "gitoa.ru/go-4devs/console/input/value" - "gitoa.ru/go-4devs/console/output/writer" + "gitoa.ru/go-4devs/console/output" ) func ExampleRun() { cmd := Command() ctx := context.Background() - out := writer.Stdout() - in := array.New() + out := output.Stdout() + in := &input.Array{} err := console.Run(ctx, cmd, in, out) fmt.Println("err:", err) @@ -29,7 +29,7 @@ func ExampleRun() { func ExampleExecute() { cmd := Command() ctx := context.Background() - in := array.New() + in := &input.Array{} // Run command: ./bin "argument value" -b --string="same value" --string="other value" in.SetOption("bool", value.New(true)) diff --git a/example/pkg/command/create_user_test.go b/example/pkg/command/create_user_test.go index 8a05278..7c49ea1 100644 --- a/example/pkg/command/create_user_test.go +++ b/example/pkg/command/create_user_test.go @@ -7,15 +7,16 @@ import ( "gitoa.ru/go-4devs/console" "gitoa.ru/go-4devs/console/example/pkg/command" - "gitoa.ru/go-4devs/console/input/array" - "gitoa.ru/go-4devs/console/output/writer" + "gitoa.ru/go-4devs/console/input" + "gitoa.ru/go-4devs/console/output" ) func TestCreateUser(t *testing.T) { ctx := context.Background() - in := array.New(array.Argument("username", "andrey")) + in := &input.Array{} + in.SetArgument("username", "andrey") buf := bytes.Buffer{} - out := writer.Buffer(&buf) + out := output.Buffer(&buf) err := console.Run(ctx, command.CreateUser(false), in, out) if err != nil { diff --git a/input/argv.go b/input/argv.go new file mode 100644 index 0000000..bf1238b --- /dev/null +++ b/input/argv.go @@ -0,0 +1,153 @@ +package input + +import ( + "context" + "fmt" + "strings" + + "gitoa.ru/go-4devs/console/input/errs" + "gitoa.ru/go-4devs/console/input/option" + "gitoa.ru/go-4devs/console/input/value" +) + +const doubleDash = `--` + +type Argv struct { + Array + Args []string + ErrHandle func(error) error +} + +func (i *Argv) Bind(ctx context.Context, def *Definition) error { + options := true + + for len(i.Args) > 0 { + var err error + + arg := i.Args[0] + i.Args = i.Args[1:] + + switch { + case options && arg == doubleDash: + options = false + case options && len(arg) > 2 && arg[0:2] == doubleDash: + err = i.parseLongOption(arg[2:], def) + case options && arg[0:1] == "-": + if len(arg) == 1 { + return fmt.Errorf("%w: option name required given '-'", errs.ErrInvalidName) + } + + err = i.parseShortOption(arg[1:], def) + default: + err = i.parseArgument(arg, def) + } + + if err != nil && i.ErrHandle != nil { + if herr := i.ErrHandle(err); herr != nil { + return herr + } + } + } + + return i.Array.Bind(ctx, def) +} + +func (i *Argv) parseLongOption(arg string, def *Definition) error { + var value *string + + name := arg + + if strings.Contains(arg, "=") { + vals := strings.SplitN(arg, "=", 2) + name = vals[0] + value = &vals[1] + } + + opt, err := def.Option(name) + if err != nil { + return errs.Option(name, err) + } + + return i.appendOption(name, value, opt) +} + +func (i *Argv) appendOption(name string, data *string, opt option.Option) error { + v, ok := i.GetOption(name) + + if ok && !opt.IsArray() { + return fmt.Errorf("%w: got: array, expect: %s", errs.ErrUnexpectedType, opt.Flag.Type()) + } + + var val string + + switch { + case data != nil: + val = *data + case opt.IsBool(): + val = "true" + case len(i.Args) > 0 && len(i.Args[0]) > 0 && i.Args[0][0:1] != "-": + val = i.Args[0] + i.Args = i.Args[1:] + default: + return errs.Option(name, errs.ErrRequired) + } + + if !ok { + v = value.ByFlag(opt.Flag) + i.SetOption(name, v) + } + + if err := v.Append(val); err != nil { + return errs.Option(name, err) + } + + return nil +} + +func (i *Argv) parseShortOption(arg string, def *Definition) error { + name := arg + + var value string + + if len(name) > 1 { + name, value = arg[0:1], arg[1:] + } + + opt, err := def.ShortOption(name) + if err != nil { + return err + } + + if opt.IsBool() && value != "" { + if err := i.parseShortOption(value, def); err != nil { + return err + } + + value = "" + } + + if value == "" { + return i.appendOption(opt.Name, nil, opt) + } + + return i.appendOption(opt.Name, &value, opt) +} + +func (i *Argv) parseArgument(arg string, def *Definition) error { + opt, err := def.Argument(i.LenArguments()) + if err != nil { + return err + } + + v, ok := i.GetArgument(opt.Name) + if !ok { + v = value.ByFlag(opt.Flag) + i.SetArgument(opt.Name, v) + } + + if err := v.Append(arg); err != nil { + return errs.Argument(opt.Name, err) + } + + return nil +} diff --git a/input/argv/input.go b/input/argv/input.go deleted file mode 100644 index e7f524c..0000000 --- a/input/argv/input.go +++ /dev/null @@ -1,212 +0,0 @@ -package argv - -import ( - "context" - "fmt" - "strings" - "sync" - - "gitoa.ru/go-4devs/console/input" - "gitoa.ru/go-4devs/console/input/option" - "gitoa.ru/go-4devs/console/input/value" - "gitoa.ru/go-4devs/console/input/wrap" -) - -const doubleDash = `--` - -var _ input.ReadInput = (*Input)(nil) - -func WithErrorHandle(h func(error) error) func(*Input) { - return func(i *Input) { - i.errorHandle = h - } -} - -func New(args []string, opts ...func(*Input)) *wrap.Input { - i := &Input{ - args: args, - arguments: make(map[string]value.AppendValue), - options: make(map[string]value.AppendValue), - errorHandle: func(err error) error { - return err - }, - } - - for _, opt := range opts { - opt(i) - } - - return &wrap.Input{ReadInput: i} -} - -type Input struct { - args []string - arguments map[string]value.AppendValue - options map[string]value.AppendValue - mu sync.RWMutex - errorHandle func(error) error -} - -func (i *Input) ReadOption(ctx context.Context, name string) (value.Value, error) { - if v, ok := i.options[name]; ok { - return v, nil - } - - return nil, input.ErrNotFound -} - -func (i *Input) SetOption(name string, val value.Value) { - i.mu.Lock() - defer i.mu.Unlock() - - i.options[name] = &value.Read{Value: val} -} - -func (i *Input) ReadArgument(ctx context.Context, name string) (value.Value, error) { - if v, ok := i.arguments[name]; ok { - return v, nil - } - - return nil, input.ErrNotFound -} - -func (i *Input) SetArgument(name string, val value.Value) { - i.mu.Lock() - defer i.mu.Unlock() - - i.arguments[name] = &value.Read{Value: val} -} - -func (i *Input) Bind(ctx context.Context, def *input.Definition) error { - options := true - - for len(i.args) > 0 { - var err error - - arg := i.args[0] - i.args = i.args[1:] - - switch { - case options && arg == doubleDash: - options = false - case options && len(arg) > 2 && arg[0:2] == doubleDash: - err = i.parseLongOption(arg[2:], def) - case options && arg[0:1] == "-": - if len(arg) == 1 { - return fmt.Errorf("%w: option name required given '-'", input.ErrInvalidName) - } - - err = i.parseShortOption(arg[1:], def) - default: - err = i.parseArgument(arg, def) - } - - if err != nil { - if herr := i.errorHandle(err); herr != nil { - return herr - } - } - } - - return nil -} - -func (i *Input) parseLongOption(arg string, def *input.Definition) error { - var value *string - - name := arg - - if strings.Contains(arg, "=") { - vals := strings.SplitN(arg, "=", 2) - name = vals[0] - value = &vals[1] - } - - opt, err := def.Option(name) - if err != nil { - return input.ErrorOption(name, err) - } - - return i.appendOption(name, value, opt) -} - -func (i *Input) appendOption(name string, data *string, opt option.Option) error { - v, ok := i.options[name] - - if ok && !opt.IsArray() { - return fmt.Errorf("%w: got: array, expect: %s", input.ErrUnexpectedType, opt.Flag.Type()) - } - - var val string - - switch { - case data != nil: - val = *data - case opt.IsBool(): - val = "true" - case len(i.args) > 0 && len(i.args[0]) > 0 && i.args[0][0:1] != "-": - val = i.args[0] - i.args = i.args[1:] - default: - return input.ErrorOption(name, input.ErrRequired) - } - - if !ok { - v = value.ByFlag(opt.Flag) - i.options[name] = v - } - - if err := v.Append(val); err != nil { - return input.ErrorOption(name, err) - } - - return nil -} - -func (i *Input) parseShortOption(arg string, def *input.Definition) error { - name := arg - - var value string - - if len(name) > 1 { - name, value = arg[0:1], arg[1:] - } - - opt, err := def.ShortOption(name) - if err != nil { - return err - } - - if opt.IsBool() && value != "" { - if err := i.parseShortOption(value, def); err != nil { - return err - } - - value = "" - } - - if value == "" { - return i.appendOption(opt.Name, nil, opt) - } - - return i.appendOption(opt.Name, &value, opt) -} - -func (i *Input) parseArgument(arg string, def *input.Definition) error { - opt, err := def.Argument(len(i.arguments)) - if err != nil { - return err - } - - v, ok := i.arguments[opt.Name] - if !ok { - v = value.ByFlag(opt.Flag) - i.arguments[opt.Name] = v - } - - if err := v.Append(arg); err != nil { - return input.ErrorArgument(opt.Name, err) - } - - return nil -} diff --git a/input/array.go b/input/array.go new file mode 100644 index 0000000..6b34234 --- /dev/null +++ b/input/array.go @@ -0,0 +1,168 @@ +package input + +import ( + "context" + "sync" + + "gitoa.ru/go-4devs/console/input/errs" + "gitoa.ru/go-4devs/console/input/value" +) + +type Array struct { + defaults array + value array +} + +func (a *Array) GetOption(name string) (value.Append, bool) { + return a.value.GetOption(name) +} + +func (a *Array) SetOption(name string, v interface{}) { + a.value.SetOption(name, v) +} + +func (a *Array) LenArguments() int { + return a.value.LenArguments() +} + +func (a *Array) GetArgument(name string) (value.Append, bool) { + return a.value.GetArgument(name) +} + +func (a *Array) SetArgument(name string, v interface{}) { + a.value.SetArgument(name, v) +} + +func (a *Array) Option(_ context.Context, name string) value.Value { + if v, ok := a.value.GetOption(name); ok { + return v + } + + if v, ok := a.defaults.GetOption(name); ok { + return v + } + + return value.Empty +} + +func (a *Array) Argument(_ context.Context, name string) value.Value { + if v, ok := a.value.GetArgument(name); ok { + return v + } + + if v, ok := a.defaults.GetArgument(name); ok { + return v + } + + return value.Empty +} + +func (a *Array) Bind(ctx context.Context, d *Definition) error { + if err := a.bindArguments(ctx, d); err != nil { + return err + } + + return a.bindOption(ctx, d) +} + +func (a *Array) bindOption(ctx context.Context, def *Definition) error { + for _, name := range def.Options() { + opt, err := def.Option(name) + if err != nil { + return err + } + + v, ok := a.value.GetOption(name) + if !ok { + switch { + case opt.HasDefault(): + a.defaults.SetOption(name, opt.Default) + continue + case opt.IsRequired(): + return errs.Option(name, errs.ErrRequired) + default: + continue + } + } + + if err := opt.Validate(v); err != nil { + return errs.Option(name, err) + } + + a.SetOption(name, v) + } + + return nil +} + +func (a *Array) bindArguments(ctx context.Context, def *Definition) error { + for pos, name := range def.Arguments() { + arg, err := def.Argument(pos) + if err != nil { + return err + } + + v, ok := a.value.GetArgument(name) + if !ok { + switch { + case arg.HasDefault(): + a.defaults.SetArgument(name, arg.Default) + continue + case arg.IsRequired(): + return errs.Argument(name, errs.ErrRequired) + default: + continue + } + } + + if err := arg.Validate(v); err != nil { + return errs.Argument(name, err) + } + + a.SetArgument(name, v) + } + + return nil +} + +type array struct { + opts map[string]value.Append + args map[string]value.Append + mu sync.Mutex +} + +func (a *array) GetOption(name string) (value.Append, bool) { + v, ok := a.opts[name] + + return v, ok +} + +func (a *array) SetOption(name string, v interface{}) { + if a.opts == nil { + a.opts = make(map[string]value.Append) + } + + a.mu.Lock() + a.opts[name] = value.New(v) + a.mu.Unlock() +} + +func (a *array) LenArguments() int { + return len(a.args) +} + +func (a *array) GetArgument(name string) (value.Append, bool) { + v, ok := a.args[name] + + return v, ok +} + +func (a *array) SetArgument(name string, v interface{}) { + if a.args == nil { + a.args = make(map[string]value.Append) + } + + a.mu.Lock() + a.args[name] = value.New(v) + a.mu.Unlock() +} diff --git a/input/array/input.go b/input/array/input.go deleted file mode 100644 index 0b9bd62..0000000 --- a/input/array/input.go +++ /dev/null @@ -1,87 +0,0 @@ -package array - -import ( - "context" - "sync" - - "gitoa.ru/go-4devs/console/input" - "gitoa.ru/go-4devs/console/input/value" - "gitoa.ru/go-4devs/console/input/wrap" -) - -var _ input.ReadInput = (*Input)(nil) - -func Argument(name string, v interface{}) func(*Input) { - return func(i *Input) { - i.args[name] = value.New(v) - } -} - -func Option(name string, v interface{}) func(*Input) { - return func(i *Input) { - i.opt[name] = value.New(v) - } -} - -func New(opts ...func(*Input)) *wrap.Input { - i := &Input{ - args: make(map[string]value.Value), - opt: make(map[string]value.Value), - } - - for _, opt := range opts { - opt(i) - } - - return &wrap.Input{ReadInput: i} -} - -type Input struct { - args map[string]value.Value - opt map[string]value.Value - mu sync.Mutex -} - -func (i *Input) ReadOption(_ context.Context, name string) (value.Value, error) { - if o, has := i.opt[name]; has { - return o, nil - } - - return nil, input.ErrorOption(name, input.ErrNotFound) -} - -func (i *Input) HasOption(name string) bool { - _, has := i.opt[name] - - return has -} - -func (i *Input) SetOption(name string, val value.Value) { - i.mu.Lock() - i.opt[name] = val - i.mu.Unlock() -} - -func (i *Input) ReadArgument(_ context.Context, name string) (value.Value, error) { - if a, has := i.args[name]; has { - return a, nil - } - - return nil, input.ErrorArgument(name, input.ErrNotFound) -} - -func (i *Input) HasArgument(name string) bool { - _, has := i.args[name] - - return has -} - -func (i *Input) SetArgument(name string, val value.Value) { - i.mu.Lock() - i.args[name] = val - i.mu.Unlock() -} - -func (i *Input) Bind(_ context.Context, def *input.Definition) error { - return nil -} diff --git a/input/definition.go b/input/definition.go index c3f526e..7960223 100644 --- a/input/definition.go +++ b/input/definition.go @@ -2,6 +2,7 @@ package input import ( "gitoa.ru/go-4devs/console/input/argument" + "gitoa.ru/go-4devs/console/input/errs" "gitoa.ru/go-4devs/console/input/option" ) @@ -66,7 +67,7 @@ func (d *Definition) SetArguments(args ...argument.Argument) *Definition { func (d *Definition) Argument(pos int) (argument.Argument, error) { if len(d.posArgs) == 0 { - return argument.Argument{}, ErrNoArgs + return argument.Argument{}, errs.ErrNoArgs } lastPos := len(d.posArgs) - 1 @@ -76,7 +77,7 @@ func (d *Definition) Argument(pos int) (argument.Argument, error) { return arg, nil } - return argument.Argument{}, ErrToManyArgs + return argument.Argument{}, errs.ErrToManyArgs } return d.args[d.posArgs[pos]], nil @@ -85,7 +86,7 @@ func (d *Definition) Argument(pos int) (argument.Argument, error) { func (d *Definition) ShortOption(short string) (option.Option, error) { name, ok := d.short[short] if !ok { - return option.Option{}, ErrNotFound + return option.Option{}, errs.ErrNotFound } return d.Option(name) @@ -96,5 +97,5 @@ func (d *Definition) Option(name string) (option.Option, error) { return opt, nil } - return option.Option{}, ErrNotFound + return option.Option{}, errs.ErrNotFound } diff --git a/input/error.go b/input/errs/error.go similarity index 79% rename from input/error.go rename to input/errs/error.go index 49f8661..9d777b7 100644 --- a/input/error.go +++ b/input/errs/error.go @@ -1,4 +1,4 @@ -package input +package errs import ( "errors" @@ -15,6 +15,14 @@ var ( ErrInvalidName = errors.New("invalid name") ) +func New(name, t string, err error) Error { + return Error{ + name: name, + t: t, + err: err, + } +} + type Error struct { name string err error @@ -33,7 +41,7 @@ func (o Error) Unwrap() error { return o.err } -func ErrorOption(name string, err error) Error { +func Option(name string, err error) Error { return Error{ name: name, err: err, @@ -41,7 +49,7 @@ func ErrorOption(name string, err error) Error { } } -func ErrorArgument(name string, err error) Error { +func Argument(name string, err error) Error { return Error{ name: name, err: err, diff --git a/input/input.go b/input/input.go index 7b687ac..600928d 100644 --- a/input/input.go +++ b/input/input.go @@ -6,18 +6,8 @@ import ( "gitoa.ru/go-4devs/console/input/value" ) -type ReadInput interface { - Bind(ctx context.Context, def *Definition) error - - ReadOption(ctx context.Context, name string) (value.Value, error) - SetOption(name string, v value.Value) - - ReadArgument(ctx context.Context, name string) (value.Value, error) - SetArgument(name string, v value.Value) -} - type Input interface { Option(ctx context.Context, name string) value.Value Argument(ctx context.Context, name string) value.Value - ReadInput + Bind(ctx context.Context, def *Definition) error } diff --git a/input/type.go b/input/type.go new file mode 100644 index 0000000..b8f4dbd --- /dev/null +++ b/input/type.go @@ -0,0 +1,18 @@ +package input + +//go:generate stringer -type=Type -linecomment + +type Type int + +const ( + Argument Type = iota // argument + Option // option +) + +func (t Type) IsArgument() bool { + return t == Argument +} + +func (t Type) IsOption() bool { + return t == Option +} diff --git a/input/type_string.go b/input/type_string.go new file mode 100644 index 0000000..8dd3d69 --- /dev/null +++ b/input/type_string.go @@ -0,0 +1,24 @@ +// Code generated by "stringer -type=Type -linecomment"; DO NOT EDIT. + +package input + +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[Argument-0] + _ = x[Option-1] +} + +const _Type_name = "argumentoption" + +var _Type_index = [...]uint8{0, 8, 14} + +func (i Type) String() string { + if i < 0 || i >= Type(len(_Type_index)-1) { + return "Type(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Type_name[_Type_index[i]:_Type_index[i+1]] +} diff --git a/input/validator/not_blank_test.go b/input/validator/not_blank_test.go index 6bcd610..e22f0f3 100644 --- a/input/validator/not_blank_test.go +++ b/input/validator/not_blank_test.go @@ -99,7 +99,7 @@ func TestNotBlank(t *testing.T) { } if ca.empty == nil { - ca.empty = &value.Empty{} + ca.empty = value.Empty } if err := valid(ca.empty); err == nil || !errors.Is(err, validator.ErrNotBlank) { diff --git a/input/value/any.go b/input/value/any.go index 0f4be91..55f0e22 100644 --- a/input/value/any.go +++ b/input/value/any.go @@ -3,7 +3,7 @@ package value import "gitoa.ru/go-4devs/console/input/flag" type Any struct { - Empty + empty Val []interface{} Flag flag.Flag } diff --git a/input/value/bool.go b/input/value/bool.go index 1291817..e00bc70 100644 --- a/input/value/bool.go +++ b/input/value/bool.go @@ -7,7 +7,7 @@ import ( ) type Bool struct { - Empty + empty Val []bool Flag flag.Flag } diff --git a/input/value/duration.go b/input/value/duration.go index 13e6482..bb94345 100644 --- a/input/value/duration.go +++ b/input/value/duration.go @@ -7,7 +7,7 @@ import ( ) type Duration struct { - Empty + empty Val []time.Duration Flag flag.Flag } diff --git a/input/value/empty.go b/input/value/empty.go index 9e239ea..1fd329f 100644 --- a/input/value/empty.go +++ b/input/value/empty.go @@ -4,84 +4,90 @@ import ( "time" ) -type Empty struct{} +var Empty = &empty{} -func (e *Empty) Append(string) error { +func IsEmpty(v Value) bool { + return v == Empty +} + +type empty struct{} + +func (e *empty) Append(string) error { return ErrAppendEmpty } -func (e *Empty) String() string { +func (e *empty) String() string { return "" } -func (e *Empty) Int() int { +func (e *empty) Int() int { return 0 } -func (e *Empty) Int64() int64 { +func (e *empty) Int64() int64 { return 0 } -func (e *Empty) Uint() uint { +func (e *empty) Uint() uint { return 0 } -func (e *Empty) Uint64() uint64 { +func (e *empty) Uint64() uint64 { return 0 } -func (e *Empty) Float64() float64 { +func (e *empty) Float64() float64 { return 0 } -func (e *Empty) Bool() bool { +func (e *empty) Bool() bool { return false } -func (e *Empty) Duration() time.Duration { +func (e *empty) Duration() time.Duration { return 0 } -func (e *Empty) Time() time.Time { +func (e *empty) Time() time.Time { return time.Time{} } -func (e *Empty) Strings() []string { +func (e *empty) Strings() []string { return nil } -func (e *Empty) Ints() []int { +func (e *empty) Ints() []int { return nil } -func (e *Empty) Int64s() []int64 { +func (e *empty) Int64s() []int64 { return nil } -func (e *Empty) Uints() []uint { +func (e *empty) Uints() []uint { return nil } -func (e *Empty) Uint64s() []uint64 { +func (e *empty) Uint64s() []uint64 { return nil } -func (e *Empty) Float64s() []float64 { +func (e *empty) Float64s() []float64 { return nil } -func (e *Empty) Bools() []bool { +func (e *empty) Bools() []bool { return nil } -func (e *Empty) Durations() []time.Duration { +func (e *empty) Durations() []time.Duration { return nil } -func (e *Empty) Times() []time.Time { +func (e *empty) Times() []time.Time { return nil } -func (e *Empty) Any() interface{} { +func (e *empty) Any() interface{} { return nil } diff --git a/input/value/float64.go b/input/value/float64.go index aa36cd9..6c8d2b0 100644 --- a/input/value/float64.go +++ b/input/value/float64.go @@ -7,7 +7,7 @@ import ( ) type Float64 struct { - Empty + empty Val []float64 Flag flag.Flag } diff --git a/input/value/int.go b/input/value/int.go index 6821924..2121061 100644 --- a/input/value/int.go +++ b/input/value/int.go @@ -7,7 +7,7 @@ import ( ) type Int struct { - Empty + empty Val []int Flag flag.Flag } diff --git a/input/value/int64.go b/input/value/int64.go index 5ed8258..7f072c4 100644 --- a/input/value/int64.go +++ b/input/value/int64.go @@ -7,7 +7,7 @@ import ( ) type Int64 struct { - Empty + empty Val []int64 Flag flag.Flag } diff --git a/input/value/read.go b/input/value/read.go index 144ddde..b984472 100644 --- a/input/value/read.go +++ b/input/value/read.go @@ -4,7 +4,7 @@ import ( "errors" ) -var _ AppendValue = (*Read)(nil) +var _ Append = (*Read)(nil) var ( ErrAppendRead = errors.New("invalid append data to read value") diff --git a/input/value/string.go b/input/value/string.go index 9409f51..c6ff557 100644 --- a/input/value/string.go +++ b/input/value/string.go @@ -3,7 +3,7 @@ package value import "gitoa.ru/go-4devs/console/input/flag" type String struct { - Empty + empty Val []string Flag flag.Flag } diff --git a/input/value/time.go b/input/value/time.go index c5c12a9..038f5df 100644 --- a/input/value/time.go +++ b/input/value/time.go @@ -7,7 +7,7 @@ import ( ) type Time struct { - Empty + empty Val []time.Time Flag flag.Flag } diff --git a/input/value/uint.go b/input/value/uint.go index 9187e6b..1299ced 100644 --- a/input/value/uint.go +++ b/input/value/uint.go @@ -7,7 +7,7 @@ import ( ) type Uint struct { - Empty + empty Val []uint Flag flag.Flag } diff --git a/input/value/uint64.go b/input/value/uint64.go index 52ddd73..2f0e298 100644 --- a/input/value/uint64.go +++ b/input/value/uint64.go @@ -7,7 +7,7 @@ import ( ) type Uint64 struct { - Empty + empty Val []uint64 Flag flag.Flag } diff --git a/input/value/value.go b/input/value/value.go index dd1766b..788ee75 100644 --- a/input/value/value.go +++ b/input/value/value.go @@ -29,13 +29,13 @@ type Value interface { Times() []time.Time } -type AppendValue interface { +type Append interface { Value Append(string) error } //nolint: gocyclo -func New(v interface{}) Value { +func New(v interface{}) Append { switch val := v.(type) { case string: return &String{Val: []string{val}, Flag: flag.String} @@ -75,18 +75,20 @@ func New(v interface{}) Value { return &Int{Val: val, Flag: flag.Int | flag.Array} case []interface{}: return &Any{Val: val, Flag: flag.Any | flag.Array} - case Value: + case Append: return val + case Value: + return &Read{Value: val} default: if v != nil { return &Any{Val: []interface{}{v}, Flag: flag.Any} } - return &Empty{} + return &empty{} } } -func ByFlag(f flag.Flag) AppendValue { +func ByFlag(f flag.Flag) Append { switch { case f.IsInt(): return &Int{Flag: f | flag.Int} diff --git a/input/wrap.go b/input/wrap.go new file mode 100644 index 0000000..1b2302d --- /dev/null +++ b/input/wrap.go @@ -0,0 +1,36 @@ +package input + +import ( + "context" + + "gitoa.ru/go-4devs/console/input/value" +) + +type Wrap struct { + Input + Array +} + +func (w *Wrap) Option(ctx context.Context, name string) value.Value { + if v, ok := w.Array.GetOption(name); ok { + return v + } + + return w.Input.Option(ctx, name) +} + +func (w *Wrap) Argument(ctx context.Context, name string) value.Value { + if v, ok := w.Array.GetArgument(name); ok { + return v + } + + return w.Input.Argument(ctx, name) +} + +func (w *Wrap) Bind(ctx context.Context, def *Definition) error { + if err := w.Input.Bind(ctx, def); err != nil { + return err + } + + return w.Array.Bind(ctx, def) +} diff --git a/input/wrap/input.go b/input/wrap/input.go deleted file mode 100644 index 1e678b9..0000000 --- a/input/wrap/input.go +++ /dev/null @@ -1,105 +0,0 @@ -package wrap - -import ( - "context" - "errors" - - "gitoa.ru/go-4devs/console/input" - "gitoa.ru/go-4devs/console/input/value" -) - -type Input struct { - input.ReadInput -} - -func (i *Input) Option(ctx context.Context, name string) value.Value { - if v, err := i.ReadOption(ctx, name); err == nil { - return v - } - - return &value.Empty{} -} - -func (i *Input) Argument(ctx context.Context, name string) value.Value { - if v, err := i.ReadArgument(ctx, name); err == nil { - return v - } - - return &value.Empty{} -} - -func (i *Input) Bind(ctx context.Context, def *input.Definition) error { - if err := i.ReadInput.Bind(ctx, def); err != nil { - return err - } - - if err := i.bindArguments(ctx, def); err != nil { - return err - } - - return i.bindOptions(ctx, def) -} - -func (i *Input) bindOptions(ctx context.Context, def *input.Definition) error { - for _, name := range def.Options() { - opt, err := def.Option(name) - if err != nil { - return err - } - - v, err := i.ReadOption(ctx, name) - if err != nil && !errors.Is(err, input.ErrNotFound) { - return input.ErrorOption(name, err) - } - - if err == nil { - if err := opt.Validate(v); err != nil { - return input.ErrorOption(name, err) - } - - continue - } - - if opt.IsRequired() && !opt.HasDefault() { - return input.ErrorOption(name, input.ErrRequired) - } - - if opt.HasDefault() { - i.SetOption(name, opt.Default) - } - } - - return nil -} - -func (i *Input) bindArguments(ctx context.Context, def *input.Definition) error { - for pos, name := range def.Arguments() { - arg, err := def.Argument(pos) - if err != nil { - return err - } - - v, err := i.ReadArgument(ctx, name) - if err != nil && !errors.Is(err, input.ErrNotFound) { - return input.ErrorArgument(name, err) - } - - if err == nil { - if err := arg.Validate(v); err != nil { - return input.ErrorArgument(name, err) - } - - continue - } - - if arg.IsRequired() && !arg.HasDefault() { - return input.ErrorArgument(name, input.ErrRequired) - } - - if arg.HasDefault() { - i.SetArgument(name, arg.Default) - } - } - - return nil -} diff --git a/output/formatter.go b/output/formatter.go new file mode 100644 index 0000000..2790753 --- /dev/null +++ b/output/formatter.go @@ -0,0 +1,22 @@ +package output + +import ( + "context" + + "gitoa.ru/go-4devs/console/output/formatter" + "gitoa.ru/go-4devs/console/output/verbosity" +) + +func Format(out Output, format *formatter.Formatter) Output { + return func(ctx context.Context, v verbosity.Verbosity, msg string, kv ...KeyValue) (int, error) { + return out(ctx, v, format.Format(ctx, msg), kv...) + } +} + +func Ansi(out Output) Output { + return Format(out, formatter.Ansi()) +} + +func None(out Output) Output { + return Format(out, formatter.None()) +} diff --git a/output/output.go b/output/output.go index 68927b7..b42fb93 100644 --- a/output/output.go +++ b/output/output.go @@ -5,76 +5,70 @@ import ( "fmt" "io" "os" -) - -type Verbosity int -const ( - VerbosityQuiet Verbosity = iota - 1 - VerbosityNorm - VerbosityInfo - VerbosityDebug - VerbosityTrace + "gitoa.ru/go-4devs/console/output/verbosity" ) -func writeError(n int, err error) { - fmt.Fprint(os.Stderr, err) +func writeError(_ int, err error) { + if err != nil { + fmt.Fprint(os.Stderr, err) + } } -type Output func(ctx context.Context, verb Verbosity, msg string, args ...KeyValue) (int, error) +type Output func(ctx context.Context, verb verbosity.Verbosity, msg string, args ...KeyValue) (int, error) func (o Output) Print(ctx context.Context, args ...interface{}) { - writeError(o(ctx, VerbosityNorm, fmt.Sprint(args...))) + writeError(o(ctx, verbosity.Norm, fmt.Sprint(args...))) } func (o Output) PrintKV(ctx context.Context, msg string, kv ...KeyValue) { - writeError(o(ctx, VerbosityNorm, msg, kv...)) + writeError(o(ctx, verbosity.Norm, msg, kv...)) } func (o Output) Printf(ctx context.Context, format string, args ...interface{}) { - writeError(o(ctx, VerbosityNorm, fmt.Sprintf(format, args...))) + writeError(o(ctx, verbosity.Norm, fmt.Sprintf(format, args...))) } func (o Output) Println(ctx context.Context, args ...interface{}) { - writeError(o(ctx, VerbosityNorm, fmt.Sprintln(args...))) + writeError(o(ctx, verbosity.Norm, fmt.Sprintln(args...))) } func (o Output) Info(ctx context.Context, args ...interface{}) { - writeError(o(ctx, VerbosityInfo, fmt.Sprint(args...))) + writeError(o(ctx, verbosity.Info, fmt.Sprint(args...))) } func (o Output) InfoKV(ctx context.Context, msg string, kv ...KeyValue) { - writeError(o(ctx, VerbosityInfo, msg, kv...)) + writeError(o(ctx, verbosity.Info, msg, kv...)) } func (o Output) Debug(ctx context.Context, args ...interface{}) { - writeError(o(ctx, VerbosityDebug, fmt.Sprint(args...))) + writeError(o(ctx, verbosity.Debug, fmt.Sprint(args...))) } func (o Output) DebugKV(ctx context.Context, msg string, kv ...KeyValue) { - writeError(o(ctx, VerbosityDebug, msg, kv...)) + writeError(o(ctx, verbosity.Debug, msg, kv...)) } func (o Output) Trace(ctx context.Context, args ...interface{}) { - writeError(o(ctx, VerbosityTrace, fmt.Sprint(args...))) + writeError(o(ctx, verbosity.Trace, fmt.Sprint(args...))) } func (o Output) TraceKV(ctx context.Context, msg string, kv ...KeyValue) { - writeError(o(ctx, VerbosityTrace, msg, kv...)) + writeError(o(ctx, verbosity.Trace, msg, kv...)) } func (o Output) Write(b []byte) (int, error) { - return o(context.Background(), VerbosityNorm, string(b)) + return o(context.Background(), verbosity.Norm, string(b)) } -func (o Output) Writer(ctx context.Context, verb Verbosity) io.Writer { +func (o Output) Writer(ctx context.Context, verb verbosity.Verbosity) io.Writer { return verbosityWriter{ctx, o, verb} } type verbosityWriter struct { ctx context.Context out Output - verb Verbosity + verb verbosity.Verbosity } func (w verbosityWriter) Write(b []byte) (int, error) { diff --git a/output/quiet.go b/output/quiet.go new file mode 100644 index 0000000..8b86908 --- /dev/null +++ b/output/quiet.go @@ -0,0 +1,13 @@ +package output + +import ( + "context" + + "gitoa.ru/go-4devs/console/output/verbosity" +) + +func Quiet() Output { + return func(context.Context, verbosity.Verbosity, string, ...KeyValue) (int, error) { + return 0, nil + } +} diff --git a/output/verbosity.go b/output/verbosity.go new file mode 100644 index 0000000..4bf38d5 --- /dev/null +++ b/output/verbosity.go @@ -0,0 +1,17 @@ +package output + +import ( + "context" + + "gitoa.ru/go-4devs/console/output/verbosity" +) + +func Verbosity(out Output, verb verbosity.Verbosity) Output { + return func(ctx context.Context, v verbosity.Verbosity, msg string, kv ...KeyValue) (int, error) { + if verb >= v { + return out(ctx, v, msg, kv...) + } + + return 0, nil + } +} diff --git a/output/verbosity/norm.go b/output/verbosity/norm.go deleted file mode 100644 index 608769b..0000000 --- a/output/verbosity/norm.go +++ /dev/null @@ -1,23 +0,0 @@ -package verbosity - -import ( - "context" - - "gitoa.ru/go-4devs/console/output" -) - -func Verb(out output.Output, verb output.Verbosity) output.Output { - return func(ctx context.Context, v output.Verbosity, msg string, kv ...output.KeyValue) (int, error) { - if verb >= v { - return out(ctx, v, msg, kv...) - } - - return 0, nil - } -} - -func Quiet() output.Output { - return func(context.Context, output.Verbosity, string, ...output.KeyValue) (int, error) { - return 0, nil - } -} diff --git a/output/verbosity/verbosity.go b/output/verbosity/verbosity.go new file mode 100644 index 0000000..5d9cf01 --- /dev/null +++ b/output/verbosity/verbosity.go @@ -0,0 +1,13 @@ +package verbosity + +//go:generate stringer -type=Verbosity -linecomment + +type Verbosity int + +const ( + Quiet Verbosity = iota - 1 // quiet + Norm // norm + Info // info + Debug // debug + Trace // trace +) diff --git a/output/verbosity/verbosity_string.go b/output/verbosity/verbosity_string.go new file mode 100644 index 0000000..dabebe1 --- /dev/null +++ b/output/verbosity/verbosity_string.go @@ -0,0 +1,28 @@ +// Code generated by "stringer -type=Verbosity -linecomment"; DO NOT EDIT. + +package verbosity + +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[Quiet - -1] + _ = x[Norm-0] + _ = x[Info-1] + _ = x[Debug-2] + _ = x[Trace-3] +} + +const _Verbosity_name = "quietnorminfodebugtrace" + +var _Verbosity_index = [...]uint8{0, 5, 9, 13, 18, 23} + +func (i Verbosity) String() string { + i -= -1 + if i < 0 || i >= Verbosity(len(_Verbosity_index)-1) { + return "Verbosity(" + strconv.FormatInt(int64(i+-1), 10) + ")" + } + return _Verbosity_name[_Verbosity_index[i]:_Verbosity_index[i+1]] +} diff --git a/output/wrap/formatter.go b/output/wrap/formatter.go deleted file mode 100644 index c2e8443..0000000 --- a/output/wrap/formatter.go +++ /dev/null @@ -1,22 +0,0 @@ -package wrap - -import ( - "context" - - "gitoa.ru/go-4devs/console/output" - "gitoa.ru/go-4devs/console/output/formatter" -) - -func Format(out output.Output, format *formatter.Formatter) output.Output { - return func(ctx context.Context, v output.Verbosity, msg string, kv ...output.KeyValue) (int, error) { - return out(ctx, v, format.Format(ctx, msg), kv...) - } -} - -func Ansi(out output.Output) output.Output { - return Format(out, formatter.Ansi()) -} - -func None(out output.Output) output.Output { - return Format(out, formatter.None()) -} diff --git a/output/writer.go b/output/writer.go new file mode 100644 index 0000000..307eb08 --- /dev/null +++ b/output/writer.go @@ -0,0 +1,45 @@ +package output + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + "strings" + + "gitoa.ru/go-4devs/console/output/verbosity" +) + +const newline = "\n" + +func Stderr() Output { + return New(os.Stderr, FormatString) +} + +func Stdout() Output { + return New(os.Stdout, FormatString) +} + +func Buffer(buf *bytes.Buffer) Output { + return New(buf, FormatString) +} + +func FormatString(_ verbosity.Verbosity, msg string, kv ...KeyValue) string { + if len(kv) > 0 { + nline := "" + if msg[len(msg)-1:] == newline { + nline = newline + } + + return "msg=\"" + strings.TrimSpace(msg) + "\", " + KeyValues(kv).String() + nline + } + + return msg +} + +func New(w io.Writer, format func(verb verbosity.Verbosity, msg string, kv ...KeyValue) string) Output { + return func(ctx context.Context, verb verbosity.Verbosity, msg string, kv ...KeyValue) (int, error) { + return fmt.Fprint(w, format(verb, msg, kv...)) + } +} diff --git a/output/writer/output.go b/output/writer/output.go deleted file mode 100644 index 4678499..0000000 --- a/output/writer/output.go +++ /dev/null @@ -1,45 +0,0 @@ -package writer - -import ( - "bytes" - "context" - "fmt" - "io" - "os" - "strings" - - "gitoa.ru/go-4devs/console/output" -) - -const newline = "\n" - -func Stderr() output.Output { - return New(os.Stderr, String) -} - -func Stdout() output.Output { - return New(os.Stdout, String) -} - -func Buffer(buf *bytes.Buffer) output.Output { - return New(buf, String) -} - -func String(_ output.Verbosity, msg string, kv ...output.KeyValue) string { - if len(kv) > 0 { - nline := "" - if msg[len(msg)-1:] == newline { - nline = newline - } - - return "msg=\"" + strings.TrimSpace(msg) + "\", " + output.KeyValues(kv).String() + nline - } - - return msg -} - -func New(w io.Writer, format func(verb output.Verbosity, msg string, kv ...output.KeyValue) string) output.Output { - return func(ctx context.Context, verb output.Verbosity, msg string, kv ...output.KeyValue) (int, error) { - return fmt.Fprint(w, format(verb, msg, kv...)) - } -} diff --git a/output/writer/output_test.go b/output/writer_test.go similarity index 89% rename from output/writer/output_test.go rename to output/writer_test.go index 1a4352d..d8598f7 100644 --- a/output/writer/output_test.go +++ b/output/writer_test.go @@ -1,4 +1,4 @@ -package writer_test +package output_test import ( "bytes" @@ -6,13 +6,12 @@ import ( "testing" "gitoa.ru/go-4devs/console/output" - "gitoa.ru/go-4devs/console/output/writer" ) func TestNew(t *testing.T) { ctx := context.Background() buf := bytes.Buffer{} - wr := writer.New(&buf, writer.String) + wr := output.New(&buf, output.FormatString) cases := map[string]struct { ex string