Browse Source

update input/outpu

pull/2/head
andrey1s 4 years ago
parent
commit
8886872c77
  1. 16
      app.go
  2. 6
      command_test.go
  3. 31
      console.go
  4. 10
      console_test.go
  5. 9
      example/pkg/command/create_user_test.go
  6. 153
      input/argv.go
  7. 212
      input/argv/input.go
  8. 168
      input/array.go
  9. 87
      input/array/input.go
  10. 9
      input/definition.go
  11. 14
      input/errs/error.go
  12. 12
      input/input.go
  13. 18
      input/type.go
  14. 24
      input/type_string.go
  15. 2
      input/validator/not_blank_test.go
  16. 2
      input/value/any.go
  17. 2
      input/value/bool.go
  18. 2
      input/value/duration.go
  19. 48
      input/value/empty.go
  20. 2
      input/value/float64.go
  21. 2
      input/value/int.go
  22. 2
      input/value/int64.go
  23. 2
      input/value/read.go
  24. 2
      input/value/string.go
  25. 2
      input/value/time.go
  26. 2
      input/value/uint.go
  27. 2
      input/value/uint64.go
  28. 12
      input/value/value.go
  29. 36
      input/wrap.go
  30. 105
      input/wrap/input.go
  31. 22
      output/formatter.go
  32. 42
      output/output.go
  33. 13
      output/quiet.go
  34. 17
      output/verbosity.go
  35. 23
      output/verbosity/norm.go
  36. 13
      output/verbosity/verbosity.go
  37. 28
      output/verbosity/verbosity_string.go
  38. 22
      output/wrap/formatter.go
  39. 45
      output/writer.go
  40. 45
      output/writer/output.go
  41. 5
      output/writer_test.go

16
app.go

@ -5,10 +5,8 @@ import (
"os" "os"
"gitoa.ru/go-4devs/console/input" "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/input/value"
"gitoa.ru/go-4devs/console/output" "gitoa.ru/go-4devs/console/output"
"gitoa.ru/go-4devs/console/output/writer"
) )
// WithOutput sets outpu,^ by default output os.Stdout. // 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. // New creates and configure new console app.
func New(opts ...func(*App)) *App { func New(opts ...func(*App)) *App {
a := &App{ a := &App{
out: writer.Stdout(), out: output.Stdout(),
exit: os.Exit, exit: os.Exit,
} }
@ -64,7 +62,9 @@ func New(opts ...func(*App)) *App {
skip = 1 skip = 1
} }
a.in = argv.New(os.Args[skip:]) a.in = &input.Argv{
Args: os.Args[skip:],
}
} }
return a return a
@ -130,10 +130,12 @@ func (a *App) list(ctx context.Context) error {
if err != nil { if err != nil {
return err 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, in, a.out)
return Run(ctx, cmd, a.in, a.out)
} }
func (a *App) printError(ctx context.Context, err error) { func (a *App) printError(ctx context.Context, err error) {

6
command_test.go

@ -11,10 +11,8 @@ import (
"gitoa.ru/go-4devs/console/example/pkg/command" "gitoa.ru/go-4devs/console/example/pkg/command"
"gitoa.ru/go-4devs/console/input" "gitoa.ru/go-4devs/console/input"
"gitoa.ru/go-4devs/console/input/argument" "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/input/option"
"gitoa.ru/go-4devs/console/output" "gitoa.ru/go-4devs/console/output"
"gitoa.ru/go-4devs/console/output/writer"
) )
//nolint: gochecknoinits //nolint: gochecknoinits
@ -91,8 +89,8 @@ func TestChainHandle(t *testing.T) {
var cnt int32 var cnt int32
ctx := context.Background() ctx := context.Background()
in := array.New() in := &input.Array{}
out := writer.Stdout() out := output.Stdout()
handle := func(ctx context.Context, in input.Input, out output.Output, next console.Action) error { handle := func(ctx context.Context, in input.Input, out output.Output, next console.Action) error {
atomic.AddInt32(&cnt, 1) atomic.AddInt32(&cnt, 1)

31
console.go

@ -10,7 +10,6 @@ import (
"gitoa.ru/go-4devs/console/input/value" "gitoa.ru/go-4devs/console/input/value"
"gitoa.ru/go-4devs/console/output" "gitoa.ru/go-4devs/console/output"
"gitoa.ru/go-4devs/console/output/verbosity" "gitoa.ru/go-4devs/console/output/verbosity"
"gitoa.ru/go-4devs/console/output/wrap"
) )
const ( 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 { if err := in.Bind(ctx, Default(def)); err != nil {
ansi(ctx, in, out).Print(ctx, "<error>\n\n ", err, "\n</error>\n") ansi(ctx, in, out).Print(ctx, "<error>\n\n ", err, "\n</error>\n")
return showHelp(ctx, cmd, in, wrap.Ansi(out)) return showHelp(ctx, cmd, in, output.Ansi(out))
} }
out = ansi(ctx, in, 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 { func ansi(ctx context.Context, in input.Input, out output.Output) output.Output {
switch { switch {
case in.Option(ctx, "ansi").Bool(): case in.Option(ctx, "ansi").Bool():
out = wrap.Ansi(out) out = output.Ansi(out)
case in.Option(ctx, "no-ansi").Bool(): case in.Option(ctx, "no-ansi").Bool():
out = wrap.None(out) out = output.None(out)
case lookupEnv("NO_COLOR"): case lookupEnv("NO_COLOR"):
out = wrap.None(out) out = output.None(out)
default: default:
out = wrap.Ansi(out) out = output.Ansi(out)
} }
return 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 { func verbose(ctx context.Context, in input.Input, out output.Output) output.Output {
switch { switch {
case in.Option(ctx, "quiet").Bool(): case in.Option(ctx, "quiet").Bool():
out = verbosity.Quiet() out = output.Quiet()
default: default:
v := in.Option(ctx, "verbose").Bools() v := in.Option(ctx, "verbose").Bools()
switch { switch {
case len(v) == verboseInfo: case len(v) == verboseInfo:
out = verbosity.Verb(out, output.VerbosityInfo) out = output.Verbosity(out, verbosity.Info)
case len(v) == verboseDebug: case len(v) == verboseDebug:
out = verbosity.Verb(out, output.VerbosityDebug) out = output.Verbosity(out, verbosity.Debug)
case len(v) >= verboseTrace: case len(v) >= verboseTrace:
out = verbosity.Verb(out, output.VerbosityTrace) out = output.Verbosity(out, verbosity.Trace)
default: 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 { func showHelp(ctx context.Context, cmd *Command, in input.Input, out output.Output) error {
in.SetArgument(HelpArgumentCommandName, value.New(cmd.Name)) w := &input.Wrap{
in.SetOption("help", value.New(false)) Input: in,
}
w.SetArgument(HelpArgumentCommandName, value.New(cmd.Name))
w.SetOption("help", value.New(false))
if _, err := Find(cmd.Name); errors.Is(err, ErrNotFound) { if _, err := Find(cmd.Name); errors.Is(err, ErrNotFound) {
register(cmd) register(cmd)
@ -117,7 +120,7 @@ func showHelp(ctx context.Context, cmd *Command, in input.Input, out output.Outp
return err return err
} }
return Run(ctx, help, in, out) return Run(ctx, help, w, out)
} }
// Default options and argument command. // Default options and argument command.

10
console_test.go

@ -5,16 +5,16 @@ import (
"fmt" "fmt"
"gitoa.ru/go-4devs/console" "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/input/value"
"gitoa.ru/go-4devs/console/output/writer" "gitoa.ru/go-4devs/console/output"
) )
func ExampleRun() { func ExampleRun() {
cmd := Command() cmd := Command()
ctx := context.Background() ctx := context.Background()
out := writer.Stdout() out := output.Stdout()
in := array.New() in := &input.Array{}
err := console.Run(ctx, cmd, in, out) err := console.Run(ctx, cmd, in, out)
fmt.Println("err:", err) fmt.Println("err:", err)
@ -29,7 +29,7 @@ func ExampleRun() {
func ExampleExecute() { func ExampleExecute() {
cmd := Command() cmd := Command()
ctx := context.Background() ctx := context.Background()
in := array.New() in := &input.Array{}
// Run command: ./bin "argument value" -b --string="same value" --string="other value" // Run command: ./bin "argument value" -b --string="same value" --string="other value"
in.SetOption("bool", value.New(true)) in.SetOption("bool", value.New(true))

9
example/pkg/command/create_user_test.go

@ -7,15 +7,16 @@ import (
"gitoa.ru/go-4devs/console" "gitoa.ru/go-4devs/console"
"gitoa.ru/go-4devs/console/example/pkg/command" "gitoa.ru/go-4devs/console/example/pkg/command"
"gitoa.ru/go-4devs/console/input/array" "gitoa.ru/go-4devs/console/input"
"gitoa.ru/go-4devs/console/output/writer" "gitoa.ru/go-4devs/console/output"
) )
func TestCreateUser(t *testing.T) { func TestCreateUser(t *testing.T) {
ctx := context.Background() ctx := context.Background()
in := array.New(array.Argument("username", "andrey")) in := &input.Array{}
in.SetArgument("username", "andrey")
buf := bytes.Buffer{} buf := bytes.Buffer{}
out := writer.Buffer(&buf) out := output.Buffer(&buf)
err := console.Run(ctx, command.CreateUser(false), in, out) err := console.Run(ctx, command.CreateUser(false), in, out)
if err != nil { if err != nil {

153
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
}

212
input/argv/input.go

@ -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
}

168
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()
}

87
input/array/input.go

@ -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
}

9
input/definition.go

@ -2,6 +2,7 @@ package input
import ( import (
"gitoa.ru/go-4devs/console/input/argument" "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/option"
) )
@ -66,7 +67,7 @@ func (d *Definition) SetArguments(args ...argument.Argument) *Definition {
func (d *Definition) Argument(pos int) (argument.Argument, error) { func (d *Definition) Argument(pos int) (argument.Argument, error) {
if len(d.posArgs) == 0 { if len(d.posArgs) == 0 {
return argument.Argument{}, ErrNoArgs return argument.Argument{}, errs.ErrNoArgs
} }
lastPos := len(d.posArgs) - 1 lastPos := len(d.posArgs) - 1
@ -76,7 +77,7 @@ func (d *Definition) Argument(pos int) (argument.Argument, error) {
return arg, nil return arg, nil
} }
return argument.Argument{}, ErrToManyArgs return argument.Argument{}, errs.ErrToManyArgs
} }
return d.args[d.posArgs[pos]], nil 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) { func (d *Definition) ShortOption(short string) (option.Option, error) {
name, ok := d.short[short] name, ok := d.short[short]
if !ok { if !ok {
return option.Option{}, ErrNotFound return option.Option{}, errs.ErrNotFound
} }
return d.Option(name) return d.Option(name)
@ -96,5 +97,5 @@ func (d *Definition) Option(name string) (option.Option, error) {
return opt, nil return opt, nil
} }
return option.Option{}, ErrNotFound return option.Option{}, errs.ErrNotFound
} }

14
input/error.go → input/errs/error.go

@ -1,4 +1,4 @@
package input package errs
import ( import (
"errors" "errors"
@ -15,6 +15,14 @@ var (
ErrInvalidName = errors.New("invalid name") ErrInvalidName = errors.New("invalid name")
) )
func New(name, t string, err error) Error {
return Error{
name: name,
t: t,
err: err,
}
}
type Error struct { type Error struct {
name string name string
err error err error
@ -33,7 +41,7 @@ func (o Error) Unwrap() error {
return o.err return o.err
} }
func ErrorOption(name string, err error) Error { func Option(name string, err error) Error {
return Error{ return Error{
name: name, name: name,
err: err, 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{ return Error{
name: name, name: name,
err: err, err: err,

12
input/input.go

@ -6,18 +6,8 @@ import (
"gitoa.ru/go-4devs/console/input/value" "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 { type Input interface {
Option(ctx context.Context, name string) value.Value Option(ctx context.Context, name string) value.Value
Argument(ctx context.Context, name string) value.Value Argument(ctx context.Context, name string) value.Value
ReadInput Bind(ctx context.Context, def *Definition) error
} }

18
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
}

24
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]]
}

2
input/validator/not_blank_test.go

@ -99,7 +99,7 @@ func TestNotBlank(t *testing.T) {
} }
if ca.empty == nil { if ca.empty == nil {
ca.empty = &value.Empty{} ca.empty = value.Empty
} }
if err := valid(ca.empty); err == nil || !errors.Is(err, validator.ErrNotBlank) { if err := valid(ca.empty); err == nil || !errors.Is(err, validator.ErrNotBlank) {

2
input/value/any.go

@ -3,7 +3,7 @@ package value
import "gitoa.ru/go-4devs/console/input/flag" import "gitoa.ru/go-4devs/console/input/flag"
type Any struct { type Any struct {
Empty empty
Val []interface{} Val []interface{}
Flag flag.Flag Flag flag.Flag
} }

2
input/value/bool.go

@ -7,7 +7,7 @@ import (
) )
type Bool struct { type Bool struct {
Empty empty
Val []bool Val []bool
Flag flag.Flag Flag flag.Flag
} }

2
input/value/duration.go

@ -7,7 +7,7 @@ import (
) )
type Duration struct { type Duration struct {
Empty empty
Val []time.Duration Val []time.Duration
Flag flag.Flag Flag flag.Flag
} }

48
input/value/empty.go

@ -4,84 +4,90 @@ import (
"time" "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 return ErrAppendEmpty
} }
func (e *Empty) String() string { func (e *empty) String() string {
return "" return ""
} }
func (e *Empty) Int() int { func (e *empty) Int() int {
return 0 return 0
} }
func (e *Empty) Int64() int64 { func (e *empty) Int64() int64 {
return 0 return 0
} }
func (e *Empty) Uint() uint { func (e *empty) Uint() uint {
return 0 return 0
} }
func (e *Empty) Uint64() uint64 { func (e *empty) Uint64() uint64 {
return 0 return 0
} }
func (e *Empty) Float64() float64 { func (e *empty) Float64() float64 {
return 0 return 0
} }
func (e *Empty) Bool() bool { func (e *empty) Bool() bool {
return false return false
} }
func (e *Empty) Duration() time.Duration { func (e *empty) Duration() time.Duration {
return 0 return 0
} }
func (e *Empty) Time() time.Time { func (e *empty) Time() time.Time {
return time.Time{} return time.Time{}
} }
func (e *Empty) Strings() []string { func (e *empty) Strings() []string {
return nil return nil
} }
func (e *Empty) Ints() []int { func (e *empty) Ints() []int {
return nil return nil
} }
func (e *Empty) Int64s() []int64 { func (e *empty) Int64s() []int64 {
return nil return nil
} }
func (e *Empty) Uints() []uint { func (e *empty) Uints() []uint {
return nil return nil
} }
func (e *Empty) Uint64s() []uint64 { func (e *empty) Uint64s() []uint64 {
return nil return nil
} }
func (e *Empty) Float64s() []float64 { func (e *empty) Float64s() []float64 {
return nil return nil
} }
func (e *Empty) Bools() []bool { func (e *empty) Bools() []bool {
return nil return nil
} }
func (e *Empty) Durations() []time.Duration { func (e *empty) Durations() []time.Duration {
return nil return nil
} }
func (e *Empty) Times() []time.Time { func (e *empty) Times() []time.Time {
return nil return nil
} }
func (e *Empty) Any() interface{} { func (e *empty) Any() interface{} {
return nil return nil
} }

2
input/value/float64.go

@ -7,7 +7,7 @@ import (
) )
type Float64 struct { type Float64 struct {
Empty empty
Val []float64 Val []float64
Flag flag.Flag Flag flag.Flag
} }

2
input/value/int.go

@ -7,7 +7,7 @@ import (
) )
type Int struct { type Int struct {
Empty empty
Val []int Val []int
Flag flag.Flag Flag flag.Flag
} }

2
input/value/int64.go

@ -7,7 +7,7 @@ import (
) )
type Int64 struct { type Int64 struct {
Empty empty
Val []int64 Val []int64
Flag flag.Flag Flag flag.Flag
} }

2
input/value/read.go

@ -4,7 +4,7 @@ import (
"errors" "errors"
) )
var _ AppendValue = (*Read)(nil) var _ Append = (*Read)(nil)
var ( var (
ErrAppendRead = errors.New("invalid append data to read value") ErrAppendRead = errors.New("invalid append data to read value")

2
input/value/string.go

@ -3,7 +3,7 @@ package value
import "gitoa.ru/go-4devs/console/input/flag" import "gitoa.ru/go-4devs/console/input/flag"
type String struct { type String struct {
Empty empty
Val []string Val []string
Flag flag.Flag Flag flag.Flag
} }

2
input/value/time.go

@ -7,7 +7,7 @@ import (
) )
type Time struct { type Time struct {
Empty empty
Val []time.Time Val []time.Time
Flag flag.Flag Flag flag.Flag
} }

2
input/value/uint.go

@ -7,7 +7,7 @@ import (
) )
type Uint struct { type Uint struct {
Empty empty
Val []uint Val []uint
Flag flag.Flag Flag flag.Flag
} }

2
input/value/uint64.go

@ -7,7 +7,7 @@ import (
) )
type Uint64 struct { type Uint64 struct {
Empty empty
Val []uint64 Val []uint64
Flag flag.Flag Flag flag.Flag
} }

12
input/value/value.go

@ -29,13 +29,13 @@ type Value interface {
Times() []time.Time Times() []time.Time
} }
type AppendValue interface { type Append interface {
Value Value
Append(string) error Append(string) error
} }
//nolint: gocyclo //nolint: gocyclo
func New(v interface{}) Value { func New(v interface{}) Append {
switch val := v.(type) { switch val := v.(type) {
case string: case string:
return &String{Val: []string{val}, Flag: flag.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} return &Int{Val: val, Flag: flag.Int | flag.Array}
case []interface{}: case []interface{}:
return &Any{Val: val, Flag: flag.Any | flag.Array} return &Any{Val: val, Flag: flag.Any | flag.Array}
case Value: case Append:
return val return val
case Value:
return &Read{Value: val}
default: default:
if v != nil { if v != nil {
return &Any{Val: []interface{}{v}, Flag: flag.Any} 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 { switch {
case f.IsInt(): case f.IsInt():
return &Int{Flag: f | flag.Int} return &Int{Flag: f | flag.Int}

36
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)
}

105
input/wrap/input.go

@ -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
}

22
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())
}

42
output/output.go

@ -5,76 +5,70 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
)
type Verbosity int
const ( "gitoa.ru/go-4devs/console/output/verbosity"
VerbosityQuiet Verbosity = iota - 1
VerbosityNorm
VerbosityInfo
VerbosityDebug
VerbosityTrace
) )
func writeError(n int, err error) { func writeError(_ int, err error) {
if err != nil {
fmt.Fprint(os.Stderr, err) 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{}) { 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) { 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{}) { 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{}) { 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{}) { 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) { 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{}) { 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) { 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{}) { 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) { 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) { 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} return verbosityWriter{ctx, o, verb}
} }
type verbosityWriter struct { type verbosityWriter struct {
ctx context.Context ctx context.Context
out Output out Output
verb Verbosity verb verbosity.Verbosity
} }
func (w verbosityWriter) Write(b []byte) (int, error) { func (w verbosityWriter) Write(b []byte) (int, error) {

13
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
}
}

17
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
}
}

23
output/verbosity/norm.go

@ -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
}
}

13
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
)

28
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]]
}

22
output/wrap/formatter.go

@ -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())
}

45
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...))
}
}

45
output/writer/output.go

@ -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...))
}
}

5
output/writer/output_test.go → output/writer_test.go

@ -1,4 +1,4 @@
package writer_test package output_test
import ( import (
"bytes" "bytes"
@ -6,13 +6,12 @@ import (
"testing" "testing"
"gitoa.ru/go-4devs/console/output" "gitoa.ru/go-4devs/console/output"
"gitoa.ru/go-4devs/console/output/writer"
) )
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
ctx := context.Background() ctx := context.Background()
buf := bytes.Buffer{} buf := bytes.Buffer{}
wr := writer.New(&buf, writer.String) wr := output.New(&buf, output.FormatString)
cases := map[string]struct { cases := map[string]struct {
ex string ex string
Loading…
Cancel
Save