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. 44
      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"
"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) {

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

31
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, "<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)
@ -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.

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

9
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 {

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 (
"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
}

14
input/error.go → 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,

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

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 {
ca.empty = &value.Empty{}
ca.empty = value.Empty
}
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"
type Any struct {
Empty
empty
Val []interface{}
Flag flag.Flag
}

2
input/value/bool.go

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

2
input/value/duration.go

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

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

2
input/value/float64.go

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

2
input/value/int.go

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

2
input/value/int64.go

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

2
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")

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

2
input/value/time.go

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

2
input/value/uint.go

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

2
input/value/uint64.go

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

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

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

44
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) {

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 (
"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
Loading…
Cancel
Save