Browse Source

add value vithh error (#1)

Co-authored-by: andrey1s <andrey@4devs.pro>
Reviewed-on: https://gitoa.ru/go-4devs/console/pulls/1
Co-authored-by: andrey <andrey@4devs.io>
Co-committed-by: andrey <andrey@4devs.io>
pull/2/head v0.1.0
andrey 2 years ago
parent
commit
4fdeb73e8a
  1. 2
      .drone.yml
  2. 25
      .golangci.yml
  3. 39
      app.go
  4. 18
      app_test.go
  5. 36
      command.go
  6. 17
      command_test.go
  7. 24
      console.go
  8. 45
      doc.go
  9. 9
      example/pkg/command/args.go
  10. 3
      example/pkg/command/create_user.go
  11. 2
      example/pkg/command/create_user_test.go
  12. 2
      example/pkg/command/hello.go
  13. 4
      example/pkg/command/long.go
  14. 6
      go.mod
  15. 21
      go.sum
  16. 23
      help.go
  17. 68
      input/argument/argument.go
  18. 26
      input/argument/option.go
  19. 35
      input/argv.go
  20. 10
      input/array.go
  21. 3
      input/chain.go
  22. 37
      input/definition.go
  23. 44
      input/errs/error.go
  24. 1
      input/flag/flag.go
  25. 0
      input/flag/flag_string.go
  26. 65
      input/map.go
  27. 37
      input/option/helpers.go
  28. 95
      input/option/option.go
  29. 6
      input/validator/enum.go
  30. 2
      input/validator/enum_test.go
  31. 50
      input/validator/not_blank.go
  32. 6
      input/validator/not_blank_test.go
  33. 4
      input/validator/valid_test.go
  34. 134
      input/value/any.go
  35. 146
      input/value/bool.go
  36. 126
      input/value/duration.go
  37. 100
      input/value/empty.go
  38. 128
      input/value/float64.go
  39. 47
      input/value/float64_test.go
  40. 127
      input/value/int.go
  41. 127
      input/value/int64.go
  42. 185
      input/value/read.go
  43. 171
      input/value/string.go
  44. 28
      input/value/string_test.go
  45. 128
      input/value/time.go
  46. 127
      input/value/uint.go
  47. 127
      input/value/uint64.go
  48. 123
      input/value/value.go
  49. 10
      input/variable/argtype.go
  50. 25
      input/variable/argtype_string.go
  51. 31
      input/variable/bool.go
  52. 31
      input/variable/duration.go
  53. 32
      input/variable/err.go
  54. 31
      input/variable/float64.go
  55. 31
      input/variable/int.go
  56. 31
      input/variable/int64.go
  57. 17
      input/variable/string.go
  58. 77
      input/variable/time.go
  59. 31
      input/variable/uint.go
  60. 31
      input/variable/uint64.go
  61. 158
      input/variable/variable.go
  62. 130
      list.go
  63. 2
      output/descriptor/descriptor.go
  64. 96
      output/descriptor/txt.go
  65. 7
      output/formatter/formatter.go
  66. 2
      output/formatter/formatter_test.go
  67. 2
      output/formatter/none_test.go
  68. 12
      output/output.go
  69. 6
      output/style/color.go
  70. 2
      output/style/style.go
  71. 7
      output/writer.go
  72. 2
      output/writer_test.go
  73. 16
      register.go
  74. 6
      register_test.go

2
.drone.yml

@ -3,7 +3,7 @@ name: default
steps: steps:
- name: golangci-lint - name: golangci-lint
image: golangci/golangci-lint:v1.26 image: golangci/golangci-lint:v1.49
volumes: volumes:
- name: deps - name: deps
path: /go/src/mod path: /go/src/mod

25
.golangci.yml

@ -16,6 +16,9 @@ linters-settings:
mnd: mnd:
# don't include the "operation" and "assign" # don't include the "operation" and "assign"
checks: argument,case,condition,return checks: argument,case,condition,return
ignored-functions:
- "strconv.*"
- "strings.*"
govet: govet:
check-shadowing: true check-shadowing: true
lll: lll:
@ -24,9 +27,27 @@ linters-settings:
suggest-new: true suggest-new: true
misspell: misspell:
locale: US locale: US
varnamelen:
min-name-length: 2
staticcheck:
checks: ["all","-SA1030"]
linters: linters:
enable-all: true enable-all: true
disable:
- varcheck
- maligned
- scopelint
- nosnakecase
- ifshort
- golint
- interfacer
- structcheck
- deadcode
- exhaustivestruct
- ireturn
- exhaustruct
issues: issues:
# Excluding configuration per-path, per-linter, per-text and per-source # Excluding configuration per-path, per-linter, per-text and per-source
@ -34,3 +55,7 @@ issues:
- path: _test\.go - path: _test\.go
linters: linters:
- gomnd - gomnd
- varnamelen
- path: input/variable
linters:
- dupl

39
app.go

@ -25,9 +25,7 @@ func WithInput(in input.Input) func(*App) {
// WithSkipArgs sets how many arguments are passed. For example, you don't need to pass the name of a single command. // WithSkipArgs sets how many arguments are passed. For example, you don't need to pass the name of a single command.
func WithSkipArgs(l int) func(*App) { func WithSkipArgs(l int) func(*App) {
return func(a *App) { return WithInput(input.NewArgs(l))
a.skipArgv = l
}
} }
// WithExit sets exit callback by default os.Exit. // WithExit sets exit callback by default os.Exit.
@ -39,44 +37,25 @@ func WithExit(f func(int)) func(*App) {
// New creates and configure new console app. // New creates and configure new console app.
func New(opts ...func(*App)) *App { func New(opts ...func(*App)) *App {
a := &App{ app := &App{
out: output.Stdout(), out: output.Stdout(),
exit: os.Exit, exit: os.Exit,
in: input.NewArgs(0),
} }
for _, opt := range opts { for _, opt := range opts {
opt(a) opt(app)
}
if a.in == nil {
skip := 2
switch {
case a.skipArgv > 0 && len(os.Args) > a.skipArgv:
skip = a.skipArgv
case a.skipArgv > 0:
skip = len(os.Args)
case len(os.Args) == 1:
skip = 1
case len(os.Args) > 1 && os.Args[1][0] == '-':
skip = 1
}
a.in = &input.Argv{
Args: os.Args[skip:],
}
} }
return a return app
} }
// App is collection of command and configure env. // App is collection of command and configure env.
type App struct { type App struct {
cmds []*Command cmds []*Command
out output.Output out output.Output
in input.Input in input.Input
skipArgv int exit func(int)
exit func(int)
} }
// Add add or replace command. // Add add or replace command.

18
app_test.go

@ -5,10 +5,9 @@ import (
"os" "os"
"gitoa.ru/go-4devs/console" "gitoa.ru/go-4devs/console"
"gitoa.ru/go-4devs/console/example/pkg/command"
) )
//nolint: lll //nolint:lll
func ExampleNew_help() { func ExampleNew_help() {
ctx := context.Background() ctx := context.Background()
os.Args = []string{ os.Args = []string{
@ -56,9 +55,18 @@ func ExampleNew_list() {
console.New(console.WithExit(func(int) {})). console.New(console.WithExit(func(int) {})).
Add( Add(
Command(), Command(),
command.Hello(), &console.Command{
command.Args(), Name: "fdevs:console:arg",
command.Namespace(), Description: "Understanding how Console Arguments and Options Are Handled",
},
&console.Command{
Name: "fdevs:console:hello",
Description: "example hello command",
},
&console.Command{
Name: "app:start",
Description: "example command in other namespace",
},
). ).
Execute(ctx) Execute(ctx)
// Output: // Output:

36
command.go

@ -17,38 +17,38 @@ type (
) )
// WithPrepare append middleware for configuration command. // WithPrepare append middleware for configuration command.
func WithPrepare(p ...Prepare) Option { func WithPrepare(prepares ...Prepare) Option {
return func(c *Command) { return func(c *Command) {
if c.Prepare != nil { if c.Prepare != nil {
p = append([]Prepare{c.Prepare}, p...) prepares = append([]Prepare{c.Prepare}, prepares...)
} }
c.Prepare = ChainPrepare(p...) c.Prepare = ChainPrepare(prepares...)
} }
} }
// WithHandle append middleware for executed command. // WithHandle append middleware for executed command.
func WithHandle(h ...Handle) Option { func WithHandle(handles ...Handle) Option {
return func(c *Command) { return func(c *Command) {
if c.Handle != nil { if c.Handle != nil {
h = append([]Handle{c.Handle}, h...) handles = append([]Handle{c.Handle}, handles...)
} }
c.Handle = ChainHandle(h...) c.Handle = ChainHandle(handles...)
} }
} }
// WithHidden sets hidden command. // WithHidden sets hidden command.
func WithHidden(v bool) Option { func WithHidden(hidden bool) Option {
return func(c *Command) { return func(c *Command) {
c.Hidden = v c.Hidden = hidden
} }
} }
// WithName sets name command. // WithName sets name command.
func WithName(n string) Option { func WithName(name string) Option {
return func(c *Command) { return func(c *Command) {
c.Name = n c.Name = name
} }
} }
@ -125,13 +125,13 @@ func (c *Command) Init(ctx context.Context, cfg *input.Definition) error {
// ChainPrepare creates middleware for configures command. // ChainPrepare creates middleware for configures command.
func ChainPrepare(prepare ...Prepare) Prepare { func ChainPrepare(prepare ...Prepare) Prepare {
n := len(prepare) num := len(prepare)
if n == 1 { if num == 1 {
return prepare[0] return prepare[0]
} }
if n > 1 { if num > 1 {
lastI := n - 1 lastI := num - 1
return func(ctx context.Context, def *input.Definition, next Configure) error { return func(ctx context.Context, def *input.Definition, next Configure) error {
var ( var (
@ -161,13 +161,13 @@ func ChainPrepare(prepare ...Prepare) Prepare {
// ChainHandle creates middleware for executes command. // ChainHandle creates middleware for executes command.
func ChainHandle(handlers ...Handle) Handle { func ChainHandle(handlers ...Handle) Handle {
n := len(handlers) num := len(handlers)
if n == 1 { if num == 1 {
return handlers[0] return handlers[0]
} }
if n > 1 { if num > 1 {
lastI := n - 1 lastI := num - 1
return func(ctx context.Context, in input.Input, out output.Output, next Action) error { return func(ctx context.Context, in input.Input, out output.Output, next Action) error {
var ( var (

17
command_test.go

@ -8,17 +8,16 @@ import (
"time" "time"
"gitoa.ru/go-4devs/console" "gitoa.ru/go-4devs/console"
"gitoa.ru/go-4devs/console/example/pkg/command"
"gitoa.ru/go-4devs/console/input" "gitoa.ru/go-4devs/console/input"
"gitoa.ru/go-4devs/console/input/argument" "gitoa.ru/go-4devs/console/input/argument"
"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"
) )
//nolint: gochecknoinits //nolint:gochecknoinits
func init() { func init() {
console.MustRegister(Command().With(console.WithName("fdevs:console:test"))) console.MustRegister(Command().With(console.WithName("fdevs:console:test")))
console.MustRegister(command.Args()) console.MustRegister(Command().With(console.WithName("fdevs:console:arg")))
} }
func Command() *console.Command { func Command() *console.Command {
@ -38,10 +37,10 @@ func Command() *console.Command {
Configure: func(ctx context.Context, def *input.Definition) error { Configure: func(ctx context.Context, def *input.Definition) error {
def. def.
SetArguments( SetArguments(
argument.New("test_argument", "test argument"), argument.String("test_argument", "test argument"),
). ).
SetOptions( SetOptions(
option.New("string", "array string", option.Array), option.String("string", "array string", option.Array),
option.Bool("bool", "test bool option"), option.Bool("bool", "test bool option"),
option.Duration("duration", "test duration with default", option.Default(time.Second)), option.Duration("duration", "test duration with default", option.Default(time.Second)),
) )
@ -52,6 +51,8 @@ func Command() *console.Command {
} }
func TestChainPrepare(t *testing.T) { func TestChainPrepare(t *testing.T) {
t.Parallel()
var cnt int32 var cnt int32
ctx := context.Background() ctx := context.Background()
@ -86,10 +87,14 @@ func TestChainPrepare(t *testing.T) {
} }
func TestChainHandle(t *testing.T) { func TestChainHandle(t *testing.T) {
t.Parallel()
var cnt int32 var cnt int32
ctx := context.Background() ctx := context.Background()
in := &input.Array{} in := &input.Array{
Map: input.Map{},
}
out := output.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 {

24
console.go

@ -86,14 +86,14 @@ func verbose(ctx context.Context, in input.Input, out output.Output) output.Outp
case in.Option(ctx, "quiet").Bool(): case in.Option(ctx, "quiet").Bool():
out = output.Quiet() out = output.Quiet()
default: default:
v := in.Option(ctx, "verbose").Bools() verb := in.Option(ctx, "verbose").Bools()
switch { switch {
case len(v) == verboseInfo: case len(verb) == verboseInfo:
out = output.Verbosity(out, verbosity.Info) out = output.Verbosity(out, verbosity.Info)
case len(v) == verboseDebug: case len(verb) == verboseDebug:
out = output.Verbosity(out, verbosity.Debug) out = output.Verbosity(out, verbosity.Debug)
case len(v) >= verboseTrace: case len(verb) >= verboseTrace:
out = output.Verbosity(out, verbosity.Trace) out = output.Verbosity(out, verbosity.Trace)
default: default:
out = output.Verbosity(out, verbosity.Norm) out = output.Verbosity(out, verbosity.Norm)
@ -104,9 +104,9 @@ func verbose(ctx context.Context, in input.Input, out output.Output) output.Outp
} }
func showHelp(ctx context.Context, cmd *Command, in input.Input, out output.Output) error { func showHelp(ctx context.Context, cmd *Command, in input.Input, out output.Output) error {
a := &input.Array{} arr := &input.Array{}
a.SetArgument(HelpArgumentCommandName, value.New(cmd.Name)) arr.SetArgument(HelpArgumentCommandName, value.New(cmd.Name))
a.SetOption("help", value.New(false)) arr.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 +117,7 @@ func showHelp(ctx context.Context, cmd *Command, in input.Input, out output.Outp
return err return err
} }
w := input.Chain(a, in) w := input.Chain(arr, in)
return Run(ctx, help, w, out) return Run(ctx, help, w, out)
} }
@ -127,11 +127,11 @@ func Default(d *input.Definition) *input.Definition {
return d.SetOptions( return d.SetOptions(
option.Bool("no-ansi", "Disable ANSI output"), option.Bool("no-ansi", "Disable ANSI output"),
option.Bool("ansi", "Do not ask any interactive question"), option.Bool("ansi", "Do not ask any interactive question"),
option.Bool("version", "Display this application version", option.Short("V")), option.Bool("version", "Display this application version", option.Short('V')),
option.Bool("help", "Display this help message", option.Short("h")), option.Bool("help", "Display this help message", option.Short('h')),
option.Bool("verbose", option.Bool("verbose",
"Increase the verbosity of messages: -v for info output, -vv for debug and -vvv for trace", "Increase the verbosity of messages: -v for info output, -vv for debug and -vvv for trace",
option.Short("v"), option.Array), option.Short('v'), option.Array),
option.Bool("quiet", "Do not output any message", option.Short("q")), option.Bool("quiet", "Do not output any message", option.Short('q')),
) )
} }

45
doc.go

@ -2,29 +2,32 @@
// The Console package allows you to create command-line commands. // The Console package allows you to create command-line commands.
// Your console commands can be used for any recurring task, such as cronjobs, imports, or other batch jobs. // Your console commands can be used for any recurring task, such as cronjobs, imports, or other batch jobs.
// console application can be written as follows: // console application can be written as follows:
// //cmd/console/main.go //
// func main() { // //cmd/console/main.go
// console.New().Execute(context.Background()) // func main() {
// } // console.New().Execute(context.Background())
// }
//
// Then, you can register the commands using Add(): // Then, you can register the commands using Add():
// package main
// //
// import ( // package main
// "context" //
// import (
// "context"
// //
// "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"
// ) // )
// //
// func main() { // func main() {
// console. // console.
// New(). // New().
// Add( // Add(
// command.Hello(), // command.Hello(),
// command.Args(), // command.Args(),
// command.Hidden(), // command.Hidden(),
// command.Namespace(), // command.Namespace(),
// ). // ).
// Execute(context.Background()) // Execute(context.Background())
// } // }
package console package console

9
example/pkg/command/args.go

@ -2,6 +2,7 @@ package command
import ( import (
"context" "context"
"time"
"gitoa.ru/go-4devs/console" "gitoa.ru/go-4devs/console"
"gitoa.ru/go-4devs/console/input" "gitoa.ru/go-4devs/console/input"
@ -15,9 +16,10 @@ func Args() *console.Command {
Description: "Understanding how Console Arguments and Options Are Handled", Description: "Understanding how Console Arguments and Options Are Handled",
Configure: func(ctx context.Context, def *input.Definition) error { Configure: func(ctx context.Context, def *input.Definition) error {
def.SetOptions( def.SetOptions(
option.Bool("foo", "foo option", option.Short("f")), option.Bool("foo", "foo option", option.Short('f')),
option.New("bar", "required bar option", option.Required, option.Short("b")), option.String("bar", "required bar option", option.Required, option.Short('b')),
option.New("cat", "cat option", option.Short("c")), option.String("cat", "cat option", option.Short('c')),
option.Time("time", "time example"),
) )
return nil return nil
@ -26,6 +28,7 @@ func Args() *console.Command {
out.Println(ctx, "foo: <info>", in.Option(ctx, "foo").Bool(), "</info>") out.Println(ctx, "foo: <info>", in.Option(ctx, "foo").Bool(), "</info>")
out.Println(ctx, "bar: <info>", in.Option(ctx, "bar").String(), "</info>") out.Println(ctx, "bar: <info>", in.Option(ctx, "bar").String(), "</info>")
out.Println(ctx, "cat: <info>", in.Option(ctx, "cat").String(), "</info>") out.Println(ctx, "cat: <info>", in.Option(ctx, "cat").String(), "</info>")
out.Println(ctx, "time: <info>", in.Option(ctx, "time").Time().Format(time.RFC3339), "</info>")
return nil return nil
}, },

3
example/pkg/command/create_user.go

@ -6,6 +6,7 @@ import (
"gitoa.ru/go-4devs/console" "gitoa.ru/go-4devs/console"
"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/variable"
"gitoa.ru/go-4devs/console/output" "gitoa.ru/go-4devs/console/output"
) )
@ -15,7 +16,7 @@ func CreateUser(required bool) *console.Command {
Description: "Creates a new user.", Description: "Creates a new user.",
Help: "This command allows you to create a user...", Help: "This command allows you to create a user...",
Configure: func(ctx context.Context, cfg *input.Definition) error { Configure: func(ctx context.Context, cfg *input.Definition) error {
var opts []func(*argument.Argument) var opts []variable.Option
if required { if required {
opts = append(opts, argument.Required) opts = append(opts, argument.Required)
} }

2
example/pkg/command/create_user_test.go

@ -12,6 +12,8 @@ import (
) )
func TestCreateUser(t *testing.T) { func TestCreateUser(t *testing.T) {
t.Parallel()
ctx := context.Background() ctx := context.Background()
buf := bytes.Buffer{} buf := bytes.Buffer{}
out := output.Buffer(&buf) out := output.Buffer(&buf)

2
example/pkg/command/hello.go

@ -25,7 +25,7 @@ func Hello() *console.Command {
}, },
Configure: func(_ context.Context, def *input.Definition) error { Configure: func(_ context.Context, def *input.Definition) error {
def.SetArguments( def.SetArguments(
argument.New("name", "Same name", argument.Default("World")), argument.String("name", "Same name", argument.Default("World")),
) )
return nil return nil

4
example/pkg/command/long.go

@ -6,9 +6,9 @@ import (
"gitoa.ru/go-4devs/console" "gitoa.ru/go-4devs/console"
"gitoa.ru/go-4devs/console/input" "gitoa.ru/go-4devs/console/input"
"gitoa.ru/go-4devs/console/input/flag"
"gitoa.ru/go-4devs/console/input/option" "gitoa.ru/go-4devs/console/input/option"
"gitoa.ru/go-4devs/console/input/validator" "gitoa.ru/go-4devs/console/input/validator"
"gitoa.ru/go-4devs/console/input/value/flag"
"gitoa.ru/go-4devs/console/output" "gitoa.ru/go-4devs/console/output"
) )
@ -41,7 +41,7 @@ func Long() *console.Command {
Configure: func(ctx context.Context, def *input.Definition) error { Configure: func(ctx context.Context, def *input.Definition) error {
def.SetOptions(option.Duration("timeout", "set duration run command", def.SetOptions(option.Duration("timeout", "set duration run command",
option.Default(defaultTimeout), option.Default(defaultTimeout),
option.Short("t"), option.Short('t'),
option.Valid(validator.NotBlank(flag.Duration)), option.Valid(validator.NotBlank(flag.Duration)),
)) ))

6
go.mod

@ -1,3 +1,9 @@
module gitoa.ru/go-4devs/console module gitoa.ru/go-4devs/console
go 1.15 go 1.15
require (
github.com/kr/pretty v0.1.0 // indirect
github.com/stretchr/testify v1.8.0
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
)

21
go.sum

@ -0,0 +1,21 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

23
help.go

@ -8,14 +8,15 @@ import (
"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/flag"
"gitoa.ru/go-4devs/console/input/option" "gitoa.ru/go-4devs/console/input/option"
"gitoa.ru/go-4devs/console/input/validator" "gitoa.ru/go-4devs/console/input/validator"
"gitoa.ru/go-4devs/console/input/value/flag" "gitoa.ru/go-4devs/console/input/value"
"gitoa.ru/go-4devs/console/output" "gitoa.ru/go-4devs/console/output"
"gitoa.ru/go-4devs/console/output/descriptor" "gitoa.ru/go-4devs/console/output/descriptor"
) )
//nolint: gochecknoinits //nolint:gochecknoinits
func init() { func init() {
MustRegister(help()) MustRegister(help())
} }
@ -43,18 +44,18 @@ To display the list of available commands, please use the <info>list</info> comm
des, err := descriptor.Find(format) des, err := descriptor.Find(format)
if err != nil { if err != nil {
return err return fmt.Errorf("find descriptor: %w", err)
} }
cmd, err := Find(name) cmd, err := Find(name)
if err != nil { if err != nil {
return err return fmt.Errorf("find cmd: %w", err)
} }
def := input.NewDefinition() def := input.NewDefinition()
if err := cmd.Init(ctx, Default(def)); err != nil { if err := cmd.Init(ctx, Default(def)); err != nil {
return err return fmt.Errorf("init cmd: %w", err)
} }
var bin string var bin string
@ -62,22 +63,28 @@ To display the list of available commands, please use the <info>list</info> comm
bin = os.Args[0] bin = os.Args[0]
} }
return des.Command(ctx, out, descriptor.Command{ derr := des.Command(ctx, out, descriptor.Command{
Bin: bin, Bin: bin,
Name: cmd.Name, Name: cmd.Name,
Description: cmd.Description, Description: cmd.Description,
Help: cmd.Help, Help: cmd.Help,
Definition: def, Definition: def,
}) })
if derr != nil {
return fmt.Errorf("descriptor help:%w", derr)
}
return nil
}, },
Configure: func(ctx context.Context, config *input.Definition) error { Configure: func(ctx context.Context, config *input.Definition) error {
formats := descriptor.Descriptors() formats := descriptor.Descriptors()
config. config.
SetArguments( SetArguments(
argument.New(HelpArgumentCommandName, "The command name", argument.Default("help")), argument.String(HelpArgumentCommandName, "The command name", argument.Default(value.New("help"))),
). ).
SetOptions( SetOptions(
option.New(helpOptFormat, fmt.Sprintf("The output format (%s)", strings.Join(formats, ", ")), option.String(helpOptFormat, fmt.Sprintf("The output format (%s)", strings.Join(formats, ", ")),
option.Required, option.Required,
option.Default(formats[0]), option.Default(formats[0]),
option.Valid( option.Valid(

68
input/argument/argument.go

@ -1,54 +1,58 @@
package argument package argument
import ( import (
"gitoa.ru/go-4devs/console/input/errs"
"gitoa.ru/go-4devs/console/input/value" "gitoa.ru/go-4devs/console/input/value"
"gitoa.ru/go-4devs/console/input/value/flag" "gitoa.ru/go-4devs/console/input/variable"
) )
func New(name, description string, opts ...func(*Argument)) Argument { func Default(in interface{}) variable.Option {
a := Argument{ return variable.Default(value.New(in))
Name: name, }
Description: description,
}
for _, opt := range opts { func Required(v *variable.Variable) {
opt(&a) variable.Required(v)
} }
return a func Array(v *variable.Variable) {
variable.Array(v)
} }
type Argument struct { func String(name, description string, opts ...variable.Option) variable.Variable {
Name string return variable.String(name, description, append(opts, variable.ArgArgument)...)
Description string
Default value.Value
Flag flag.Flag
Valid []func(value.Value) error
} }
func (a Argument) HasDefault() bool { func Bool(name, description string, opts ...variable.Option) variable.Variable {
return a.Default != nil return variable.Bool(name, description, append(opts, variable.ArgArgument)...)
} }
func (a Argument) IsBool() bool { func Duration(name, description string, opts ...variable.Option) variable.Variable {
return a.Flag.IsBool() return variable.Duration(name, description, append(opts, variable.ArgArgument)...)
} }
func (a Argument) IsRequired() bool { func Float64(name, description string, opts ...variable.Option) variable.Variable {
return a.Flag.IsRequired() return variable.Float64(name, description, append(opts, variable.ArgArgument)...)
} }
func (a Argument) IsArray() bool { func Int(name, description string, opts ...variable.Option) variable.Variable {
return a.Flag.IsArray() return variable.Int(name, description, append(opts, variable.ArgArgument)...)
} }
func (a Argument) Validate(v value.Value) error { func Int64(name, description string, opts ...variable.Option) variable.Variable {
for _, valid := range a.Valid { return variable.Int64(name, description, append(opts, variable.ArgArgument)...)
if err := valid(v); err != nil { }
return errs.Argument(a.Name, err)
} func Time(name, description string, opts ...variable.Option) variable.Variable {
} return variable.Time(name, description, append(opts, variable.ArgArgument)...)
}
func Uint(name, description string, opts ...variable.Option) variable.Variable {
return variable.Uint(name, description, append(opts, variable.ArgArgument)...)
}
func Uint64(name, descriontion string, opts ...variable.Option) variable.Variable {
return variable.Uint64(name, descriontion, append(opts, variable.ArgArgument)...)
}
return nil func Err(name string, err error) variable.Error {
return variable.Err(name, variable.TypeArgument, err)
} }

26
input/argument/option.go

@ -1,26 +0,0 @@
package argument
import (
"gitoa.ru/go-4devs/console/input/value"
"gitoa.ru/go-4devs/console/input/value/flag"
)
func Required(a *Argument) {
a.Flag |= flag.Required
}
func Default(v interface{}) func(*Argument) {
return func(a *Argument) {
a.Default = value.New(v)
}
}
func Flag(flag flag.Flag) func(*Argument) {
return func(a *Argument) {
a.Flag = flag
}
}
func Array(a *Argument) {
a.Flag |= flag.Array
}

35
input/argv.go

@ -3,20 +3,41 @@ package input
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"strings" "strings"
"gitoa.ru/go-4devs/console/input/argument"
"gitoa.ru/go-4devs/console/input/errs" "gitoa.ru/go-4devs/console/input/errs"
"gitoa.ru/go-4devs/console/input/option" "gitoa.ru/go-4devs/console/input/option"
"gitoa.ru/go-4devs/console/input/variable"
) )
const doubleDash = `--` const doubleDash = `--`
func NewArgs(skip int) *Argv {
res := 2
switch {
case skip > 0 && len(os.Args) > skip:
res = skip
case skip > 0:
res = len(os.Args)
case len(os.Args) == 1:
res = 1
case len(os.Args) > 1 && os.Args[1][0] == '-':
res = 1
}
return &Argv{Args: os.Args[res:]}
}
type Argv struct { type Argv struct {
Array Array
Args []string Args []string
ErrHandle func(error) error ErrHandle func(error) error
} }
//nolint:cyclop
func (i *Argv) Bind(ctx context.Context, def *Definition) error { func (i *Argv) Bind(ctx context.Context, def *Definition) error {
options := true options := true
@ -64,13 +85,13 @@ func (i *Argv) parseLongOption(arg string, def *Definition) error {
opt, err := def.Option(name) opt, err := def.Option(name)
if err != nil { if err != nil {
return errs.Option(name, err) return option.Err(name, err)
} }
return i.appendOption(name, value, opt) return i.appendOption(name, value, opt)
} }
func (i *Argv) appendOption(name string, data *string, opt option.Option) error { func (i *Argv) appendOption(name string, data *string, opt variable.Variable) error {
if i.HasOption(name) && !opt.IsArray() { if i.HasOption(name) && !opt.IsArray() {
return fmt.Errorf("%w: got: array, expect: %s", errs.ErrUnexpectedType, opt.Flag.Type()) return fmt.Errorf("%w: got: array, expect: %s", errs.ErrUnexpectedType, opt.Flag.Type())
} }
@ -86,11 +107,11 @@ func (i *Argv) appendOption(name string, data *string, opt option.Option) error
val = i.Args[0] val = i.Args[0]
i.Args = i.Args[1:] i.Args = i.Args[1:]
default: default:
return errs.Option(name, errs.ErrRequired) return option.Err(name, errs.ErrRequired)
} }
if err := i.AppendOption(opt.Flag, name, val); err != nil { if err := i.AppendOption(opt, val); err != nil {
return errs.Option(name, err) return option.Err(name, err)
} }
return nil return nil
@ -131,8 +152,8 @@ func (i *Argv) parseArgument(arg string, def *Definition) error {
return err return err
} }
if err := i.AppendArgument(opt.Flag, opt.Name, arg); err != nil { if err := i.AppendArgument(opt, arg); err != nil {
return errs.Argument(opt.Name, err) return argument.Err(opt.Name, err)
} }
return nil return nil

10
input/array.go

@ -3,7 +3,9 @@ package input
import ( import (
"context" "context"
"gitoa.ru/go-4devs/console/input/argument"
"gitoa.ru/go-4devs/console/input/errs" "gitoa.ru/go-4devs/console/input/errs"
"gitoa.ru/go-4devs/console/input/option"
"gitoa.ru/go-4devs/console/input/value" "gitoa.ru/go-4devs/console/input/value"
) )
@ -58,7 +60,7 @@ func (a *Array) bindOption(ctx context.Context, def *Definition) error {
continue continue
case opt.IsRequired(): case opt.IsRequired():
return errs.Option(name, errs.ErrRequired) return option.Err(name, errs.ErrRequired)
default: default:
continue continue
} }
@ -70,7 +72,7 @@ func (a *Array) bindOption(ctx context.Context, def *Definition) error {
} }
if err := opt.Validate(v); err != nil { if err := opt.Validate(v); err != nil {
return errs.Option(name, err) return option.Err(name, err)
} }
} }
@ -91,7 +93,7 @@ func (a *Array) bindArguments(ctx context.Context, def *Definition) error {
continue continue
case arg.IsRequired(): case arg.IsRequired():
return errs.Argument(name, errs.ErrRequired) return argument.Err(name, errs.ErrRequired)
default: default:
continue continue
} }
@ -99,7 +101,7 @@ func (a *Array) bindArguments(ctx context.Context, def *Definition) error {
if v := a.Map.Argument(ctx, name); !value.IsEmpty(v) { if v := a.Map.Argument(ctx, name); !value.IsEmpty(v) {
if err := arg.Validate(v); err != nil { if err := arg.Validate(v); err != nil {
return errs.Argument(name, err) return argument.Err(name, err)
} }
} }
} }

3
input/chain.go

@ -2,6 +2,7 @@ package input
import ( import (
"context" "context"
"fmt"
"gitoa.ru/go-4devs/console/input/value" "gitoa.ru/go-4devs/console/input/value"
) )
@ -35,7 +36,7 @@ func (c chain) Argument(ctx context.Context, name string) value.Value {
func (c chain) Bind(ctx context.Context, def *Definition) error { func (c chain) Bind(ctx context.Context, def *Definition) error {
for _, input := range c { for _, input := range c {
if err := input.Bind(ctx, def); err != nil { if err := input.Bind(ctx, def); err != nil {
return err return fmt.Errorf("%T:%w", input, err)
} }
} }

37
input/definition.go

@ -4,20 +4,21 @@ 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/errs"
"gitoa.ru/go-4devs/console/input/option" "gitoa.ru/go-4devs/console/input/option"
"gitoa.ru/go-4devs/console/input/variable"
) )
func NewDefinition() *Definition { func NewDefinition() *Definition {
return &Definition{ return &Definition{
options: make(map[string]option.Option), options: make(map[string]variable.Variable),
args: make(map[string]argument.Argument), args: make(map[string]variable.Variable),
short: make(map[string]string), short: make(map[string]string),
} }
} }
type Definition struct { type Definition struct {
options map[string]option.Option options map[string]variable.Variable
posOpt []string posOpt []string
args map[string]argument.Argument args map[string]variable.Variable
posArgs []string posArgs []string
short map[string]string short map[string]string
} }
@ -30,11 +31,11 @@ func (d *Definition) Arguments() []string {
return d.posArgs return d.posArgs
} }
func (d *Definition) SetOption(name, description string, opts ...func(*option.Option)) *Definition { func (d *Definition) SetOption(name, description string, opts ...variable.Option) *Definition {
return d.SetOptions(option.New(name, description, opts...)) return d.SetOptions(option.String(name, description, opts...))
} }
func (d *Definition) SetOptions(opts ...option.Option) *Definition { func (d *Definition) SetOptions(opts ...variable.Variable) *Definition {
for _, opt := range opts { for _, opt := range opts {
if _, has := d.options[opt.Name]; !has { if _, has := d.options[opt.Name]; !has {
d.posOpt = append([]string{opt.Name}, d.posOpt...) d.posOpt = append([]string{opt.Name}, d.posOpt...)
@ -42,18 +43,18 @@ func (d *Definition) SetOptions(opts ...option.Option) *Definition {
d.options[opt.Name] = opt d.options[opt.Name] = opt
if opt.HasShort() { if opt.HasShort() {
d.short[opt.Short] = opt.Name d.short[opt.Alias] = opt.Name
} }
} }
return d return d
} }
func (d *Definition) SetArgument(name, description string, opts ...func(*argument.Argument)) *Definition { func (d *Definition) SetArgument(name, description string, opts ...variable.Option) *Definition {
return d.SetArguments(argument.New(name, description, opts...)) return d.SetArguments(argument.String(name, description, opts...))
} }
func (d *Definition) SetArguments(args ...argument.Argument) *Definition { func (d *Definition) SetArguments(args ...variable.Variable) *Definition {
for _, arg := range args { for _, arg := range args {
if _, ok := d.args[arg.Name]; !ok { if _, ok := d.args[arg.Name]; !ok {
d.posArgs = append(d.posArgs, arg.Name) d.posArgs = append(d.posArgs, arg.Name)
@ -65,9 +66,9 @@ func (d *Definition) SetArguments(args ...argument.Argument) *Definition {
return d return d
} }
func (d *Definition) Argument(pos int) (argument.Argument, error) { func (d *Definition) Argument(pos int) (variable.Variable, error) {
if len(d.posArgs) == 0 { if len(d.posArgs) == 0 {
return argument.Argument{}, errs.ErrNoArgs return variable.Variable{}, errs.ErrNoArgs
} }
lastPos := len(d.posArgs) - 1 lastPos := len(d.posArgs) - 1
@ -77,25 +78,25 @@ func (d *Definition) Argument(pos int) (argument.Argument, error) {
return arg, nil return arg, nil
} }
return argument.Argument{}, errs.ErrToManyArgs return variable.Variable{}, errs.ErrToManyArgs
} }
return d.args[d.posArgs[pos]], nil return d.args[d.posArgs[pos]], nil
} }
func (d *Definition) ShortOption(short string) (option.Option, error) { func (d *Definition) ShortOption(short string) (variable.Variable, error) {
name, ok := d.short[short] name, ok := d.short[short]
if !ok { if !ok {
return option.Option{}, errs.ErrNotFound return variable.Variable{}, errs.ErrNotFound
} }
return d.Option(name) return d.Option(name)
} }
func (d *Definition) Option(name string) (option.Option, error) { func (d *Definition) Option(name string) (variable.Variable, error) {
if opt, ok := d.options[name]; ok { if opt, ok := d.options[name]; ok {
return opt, nil return opt, nil
} }
return option.Option{}, errs.ErrNotFound return variable.Variable{}, errs.ErrNotFound
} }

44
input/errs/error.go

@ -2,7 +2,6 @@ package errs
import ( import (
"errors" "errors"
"fmt"
) )
var ( var (
@ -13,46 +12,5 @@ var (
ErrRequired = errors.New("is required") ErrRequired = errors.New("is required")
ErrAppend = errors.New("failed append") ErrAppend = errors.New("failed append")
ErrInvalidName = errors.New("invalid name") ErrInvalidName = errors.New("invalid name")
ErrWrongType = errors.New("wrong type")
) )
func New(name, t string, err error) Error {
return Error{
name: name,
t: t,
err: err,
}
}
type Error struct {
name string
err error
t string
}
func (o Error) Error() string {
return fmt.Sprintf("%s: '%s' %s", o.t, o.name, o.err)
}
func (o Error) Is(err error) bool {
return errors.Is(err, o.err)
}
func (o Error) Unwrap() error {
return o.err
}
func Option(name string, err error) Error {
return Error{
name: name,
err: err,
t: "option",
}
}
func Argument(name string, err error) Error {
return Error{
name: name,
err: err,
t: "argument",
}
}

1
input/value/flag/flag.go → input/flag/flag.go

@ -71,6 +71,7 @@ func (i Flag) IsAny() bool {
return i&Any > 0 return i&Any > 0
} }
//nolint:cyclop
func (i Flag) Type() Flag { func (i Flag) Type() Flag {
switch { switch {
case i.IsInt(): case i.IsInt():

0
input/value/flag/flag_string.go → input/flag/flag_string.go

65
input/map.go

@ -2,15 +2,16 @@ package input
import ( import (
"context" "context"
"fmt"
"sync" "sync"
"gitoa.ru/go-4devs/console/input/value" "gitoa.ru/go-4devs/console/input/value"
"gitoa.ru/go-4devs/console/input/value/flag" "gitoa.ru/go-4devs/console/input/variable"
) )
type Map struct { type Map struct {
opts map[string]value.Append opts map[string]value.Value
args map[string]value.Append args map[string]value.Value
sync.Mutex sync.Mutex
} }
@ -42,15 +43,15 @@ func (m *Map) HasOption(name string) bool {
return ok return ok
} }
func (m *Map) SetOption(name string, v interface{}) { func (m *Map) SetOption(name string, val interface{}) {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
if m.opts == nil { if m.opts == nil {
m.opts = make(map[string]value.Append) m.opts = make(map[string]value.Value)
} }
m.opts[name] = value.New(v) m.opts[name] = value.New(val)
} }
func (m *Map) HasArgument(name string) bool { func (m *Map) HasArgument(name string) bool {
@ -59,29 +60,59 @@ func (m *Map) HasArgument(name string) bool {
return ok return ok
} }
func (m *Map) SetArgument(name string, v interface{}) { func (m *Map) SetArgument(name string, val interface{}) {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
if m.args == nil { if m.args == nil {
m.args = make(map[string]value.Append) m.args = make(map[string]value.Value)
} }
m.args[name] = value.New(v) m.args[name] = value.New(val)
} }
func (m *Map) AppendOption(f flag.Flag, name, val string) error { func (m *Map) AppendOption(opt variable.Variable, val string) error {
if _, ok := m.opts[name]; !ok { old, ok := m.opts[opt.Name]
m.SetOption(name, value.ByFlag(f)) if !ok {
value, err := opt.Create(val)
if err != nil {
return fmt.Errorf("append option:%w", err)
}
m.SetOption(opt.Name, value)
return nil
}
value, err := opt.Append(old, val)
if err != nil {
return fmt.Errorf("append option:%w", err)
} }
return m.opts[name].Append(val) m.SetOption(opt.Name, value)
return nil
} }
func (m *Map) AppendArgument(f flag.Flag, name, val string) error { func (m *Map) AppendArgument(arg variable.Variable, val string) error {
if _, ok := m.args[name]; !ok { old, ok := m.args[arg.Name]
m.SetArgument(name, value.ByFlag(f)) if !ok {
value, err := arg.Create(val)
if err != nil {
return fmt.Errorf("append option:%w", err)
}
m.SetArgument(arg.Name, value)
return nil
}
value, err := arg.Append(old, val)
if err != nil {
return fmt.Errorf("append option:%w", err)
} }
return m.args[name].Append(val) m.SetArgument(arg.Name, value)
return nil
} }

37
input/option/helpers.go

@ -1,37 +0,0 @@
package option
import (
"gitoa.ru/go-4devs/console/input/value/flag"
)
func Bool(name, description string, opts ...func(*Option)) Option {
return New(name, description, append(opts, Value(flag.Bool))...)
}
func Duration(name, description string, opts ...func(*Option)) Option {
return New(name, description, append(opts, Value(flag.Duration))...)
}
func Float64(name, description string, opts ...func(*Option)) Option {
return New(name, description, append(opts, Value(flag.Float64))...)
}
func Int(name, description string, opts ...func(*Option)) Option {
return New(name, description, append(opts, Value(flag.Int))...)
}
func Int64(name, description string, opts ...func(*Option)) Option {
return New(name, description, append(opts, Value(flag.Int64))...)
}
func Time(name, description string, opts ...func(*Option)) Option {
return New(name, description, append(opts, Value(flag.Time))...)
}
func Uint(name, description string, opts ...func(*Option)) Option {
return New(name, description, append(opts, Value(flag.Uint))...)
}
func Uint64(name, descriontion string, opts ...func(*Option)) Option {
return New(name, descriontion, append(opts, Value(flag.Uint64))...)
}

95
input/option/option.go

@ -1,97 +1,68 @@
package option package option
import ( import (
"gitoa.ru/go-4devs/console/input/errs"
"gitoa.ru/go-4devs/console/input/value" "gitoa.ru/go-4devs/console/input/value"
"gitoa.ru/go-4devs/console/input/value/flag" "gitoa.ru/go-4devs/console/input/variable"
) )
func Required(o *Option) { func Short(in rune) variable.Option {
o.Flag |= flag.Required return func(v *variable.Variable) {
} v.Alias = string(in)
func Default(in interface{}) func(*Option) {
return func(o *Option) {
o.Default = value.New(in)
} }
} }
func Short(s string) func(*Option) { func Default(in interface{}) variable.Option {
return func(o *Option) { return variable.Default(value.New(in))
o.Short = s
}
} }
func Array(o *Option) { func Required(v *variable.Variable) {
o.Flag |= flag.Array variable.Required(v)
} }
func Value(flag flag.Flag) func(*Option) { func Valid(f ...func(value.Value) error) variable.Option {
return func(o *Option) { return variable.Valid(f...)
o.Flag |= flag
}
} }
func Flag(in flag.Flag) func(*Option) { func Array(v *variable.Variable) {
return func(o *Option) { variable.Array(v)
o.Flag = in
}
} }
func Valid(f ...func(value.Value) error) func(*Option) { func String(name, description string, opts ...variable.Option) variable.Variable {
return func(o *Option) { return variable.String(name, description, append(opts, variable.ArgOption)...)
o.Valid = f
}
} }
func New(name, description string, opts ...func(*Option)) Option { func Bool(name, description string, opts ...variable.Option) variable.Variable {
o := Option{ return variable.Bool(name, description, append(opts, variable.ArgOption)...)
Name: name,
Description: description,
}
for _, opt := range opts {
opt(&o)
}
return o
} }
type Option struct { func Duration(name, description string, opts ...variable.Option) variable.Variable {
Name string return variable.Duration(name, description, append(opts, variable.ArgOption)...)
Description string
Short string
Flag flag.Flag
Default value.Value
Valid []func(value.Value) error
} }
func (o Option) HasShort() bool { func Float64(name, description string, opts ...variable.Option) variable.Variable {
return len(o.Short) == 1 return variable.Float64(name, description, append(opts, variable.ArgOption)...)
} }
func (o Option) HasDefault() bool { func Int(name, description string, opts ...variable.Option) variable.Variable {
return o.Default != nil return variable.Int(name, description, append(opts, variable.ArgOption)...)
} }
func (o Option) IsBool() bool { func Int64(name, description string, opts ...variable.Option) variable.Variable {
return o.Flag.IsBool() return variable.Int64(name, description, append(opts, variable.ArgOption)...)
} }
func (o Option) IsArray() bool { func Time(name, description string, opts ...variable.Option) variable.Variable {
return o.Flag.IsArray() return variable.Time(name, description, append(opts, variable.ArgOption)...)
} }
func (o Option) IsRequired() bool { func Uint(name, description string, opts ...variable.Option) variable.Variable {
return o.Flag.IsRequired() return variable.Uint(name, description, append(opts, variable.ArgOption)...)
} }
func (o Option) Validate(v value.Value) error { func Uint64(name, descriontion string, opts ...variable.Option) variable.Variable {
for _, valid := range o.Valid { return variable.Uint64(name, descriontion, append(opts, variable.ArgOption)...)
if err := valid(v); err != nil { }
return errs.Option(o.Name, err)
}
}
return nil func Err(name string, err error) variable.Error {
return variable.Err(name, variable.TypeOption, err)
} }

6
input/validator/enum.go

@ -4,13 +4,13 @@ import "gitoa.ru/go-4devs/console/input/value"
func Enum(enum ...string) func(value.Value) error { func Enum(enum ...string) func(value.Value) error {
return func(in value.Value) error { return func(in value.Value) error {
v := in.String() val := in.String()
for _, e := range enum { for _, e := range enum {
if e == v { if e == val {
return nil return nil
} }
} }
return NewError(ErrInvalid, v, enum) return NewError(ErrInvalid, val, enum)
} }
} }

2
input/validator/enum_test.go

@ -9,6 +9,8 @@ import (
) )
func TestEnum(t *testing.T) { func TestEnum(t *testing.T) {
t.Parallel()
validValue := value.New("valid") validValue := value.New("valid")
invalidValue := value.New("invalid") invalidValue := value.New("invalid")

50
input/validator/not_blank.go

@ -1,33 +1,33 @@
package validator package validator
import ( import (
"gitoa.ru/go-4devs/console/input/flag"
"gitoa.ru/go-4devs/console/input/value" "gitoa.ru/go-4devs/console/input/value"
"gitoa.ru/go-4devs/console/input/value/flag"
) )
//nolint: gocyclo //nolint:gocyclo,cyclop
func NotBlank(f flag.Flag) func(value.Value) error { func NotBlank(fl flag.Flag) func(value.Value) error {
return func(in value.Value) error { return func(in value.Value) error {
switch { switch {
case f.IsAny() && in.Any() != nil: case fl.IsAny() && in.Any() != nil:
return nil return nil
case f.IsArray(): case fl.IsArray():
return arrayNotBlank(f, in) return arrayNotBlank(fl, in)
case f.IsInt() && in.Int() != 0: case fl.IsInt() && in.Int() != 0:
return nil return nil
case f.IsInt64() && in.Int64() != 0: case fl.IsInt64() && in.Int64() != 0:
return nil return nil
case f.IsUint() && in.Uint() != 0: case fl.IsUint() && in.Uint() != 0:
return nil return nil
case f.IsUint64() && in.Uint64() != 0: case fl.IsUint64() && in.Uint64() != 0:
return nil return nil
case f.IsFloat64() && in.Float64() != 0: case fl.IsFloat64() && in.Float64() != 0:
return nil return nil
case f.IsDuration() && in.Duration() != 0: case fl.IsDuration() && in.Duration() != 0:
return nil return nil
case f.IsTime() && !in.Time().IsZero(): case fl.IsTime() && !in.Time().IsZero():
return nil return nil
case f.IsString() && len(in.String()) > 0: case fl.IsString() && len(in.String()) > 0:
return nil return nil
} }
@ -35,10 +35,10 @@ func NotBlank(f flag.Flag) func(value.Value) error {
} }
} }
//nolint: gocyclo,gocognit //nolint:gocyclo,gocognit,cyclop
func arrayNotBlank(f flag.Flag, in value.Value) error { func arrayNotBlank(fl flag.Flag, in value.Value) error {
switch { switch {
case f.IsInt() && len(in.Ints()) > 0: case fl.IsInt() && len(in.Ints()) > 0:
for _, i := range in.Ints() { for _, i := range in.Ints() {
if i == 0 { if i == 0 {
return ErrNotBlank return ErrNotBlank
@ -46,7 +46,7 @@ func arrayNotBlank(f flag.Flag, in value.Value) error {
} }
return nil return nil
case f.IsInt64() && len(in.Int64s()) > 0: case fl.IsInt64() && len(in.Int64s()) > 0:
for _, i := range in.Int64s() { for _, i := range in.Int64s() {
if i == 0 { if i == 0 {
return ErrNotBlank return ErrNotBlank
@ -54,7 +54,7 @@ func arrayNotBlank(f flag.Flag, in value.Value) error {
} }
return nil return nil
case f.IsUint() && len(in.Uints()) > 0: case fl.IsUint() && len(in.Uints()) > 0:
for _, u := range in.Uints() { for _, u := range in.Uints() {
if u == 0 { if u == 0 {
return ErrNotBlank return ErrNotBlank
@ -62,7 +62,7 @@ func arrayNotBlank(f flag.Flag, in value.Value) error {
} }
return nil return nil
case f.IsUint64() && len(in.Uint64s()) > 0: case fl.IsUint64() && len(in.Uint64s()) > 0:
for _, u := range in.Uint64s() { for _, u := range in.Uint64s() {
if u == 0 { if u == 0 {
return ErrNotBlank return ErrNotBlank
@ -70,7 +70,7 @@ func arrayNotBlank(f flag.Flag, in value.Value) error {
} }
return nil return nil
case f.IsFloat64() && len(in.Float64s()) > 0: case fl.IsFloat64() && len(in.Float64s()) > 0:
for _, f := range in.Float64s() { for _, f := range in.Float64s() {
if f == 0 { if f == 0 {
return ErrNotBlank return ErrNotBlank
@ -78,9 +78,9 @@ func arrayNotBlank(f flag.Flag, in value.Value) error {
} }
return nil return nil
case f.IsBool() && len(in.Bools()) > 0: case fl.IsBool() && len(in.Bools()) > 0:
return nil return nil
case f.IsDuration() && len(in.Durations()) > 0: case fl.IsDuration() && len(in.Durations()) > 0:
for _, d := range in.Durations() { for _, d := range in.Durations() {
if d == 0 { if d == 0 {
return ErrNotBlank return ErrNotBlank
@ -88,7 +88,7 @@ func arrayNotBlank(f flag.Flag, in value.Value) error {
} }
return nil return nil
case f.IsTime() && len(in.Times()) > 0: case fl.IsTime() && len(in.Times()) > 0:
for _, t := range in.Times() { for _, t := range in.Times() {
if t.IsZero() { if t.IsZero() {
return ErrNotBlank return ErrNotBlank
@ -96,7 +96,7 @@ func arrayNotBlank(f flag.Flag, in value.Value) error {
} }
return nil return nil
case f.IsString() && len(in.Strings()) > 0: case fl.IsString() && len(in.Strings()) > 0:
for _, st := range in.Strings() { for _, st := range in.Strings() {
if len(st) == 0 { if len(st) == 0 {
return ErrNotBlank return ErrNotBlank

6
input/validator/not_blank_test.go

@ -5,12 +5,14 @@ import (
"testing" "testing"
"time" "time"
"gitoa.ru/go-4devs/console/input/flag"
"gitoa.ru/go-4devs/console/input/validator" "gitoa.ru/go-4devs/console/input/validator"
"gitoa.ru/go-4devs/console/input/value" "gitoa.ru/go-4devs/console/input/value"
"gitoa.ru/go-4devs/console/input/value/flag"
) )
func TestNotBlank(t *testing.T) { func TestNotBlank(t *testing.T) {
t.Parallel()
cases := map[string]struct { cases := map[string]struct {
flag flag.Flag flag flag.Flag
value value.Value value value.Value
@ -103,7 +105,7 @@ func TestNotBlank(t *testing.T) {
} }
if err := valid(ca.empty); err == nil || !errors.Is(err, validator.ErrNotBlank) { if err := valid(ca.empty); err == nil || !errors.Is(err, validator.ErrNotBlank) {
t.Errorf("case: %s, expect: %s, got:%s", name, validator.ErrNotBlank, err) t.Errorf("case empty: %s, expect: %s, got:%s", name, validator.ErrNotBlank, err)
} }
} }
} }

4
input/validator/valid_test.go

@ -4,12 +4,14 @@ import (
"errors" "errors"
"testing" "testing"
"gitoa.ru/go-4devs/console/input/flag"
"gitoa.ru/go-4devs/console/input/validator" "gitoa.ru/go-4devs/console/input/validator"
"gitoa.ru/go-4devs/console/input/value" "gitoa.ru/go-4devs/console/input/value"
"gitoa.ru/go-4devs/console/input/value/flag"
) )
func TestValid(t *testing.T) { func TestValid(t *testing.T) {
t.Parallel()
validValue := value.New("one") validValue := value.New("one")
invalidValue := value.New([]string{"one"}) invalidValue := value.New([]string{"one"})

134
input/value/any.go

@ -1,21 +1,137 @@
package value package value
import "gitoa.ru/go-4devs/console/input/value/flag" import (
"encoding/json"
"fmt"
"time"
)
var _ Value = NewAny(nil)
//nolint:gochecknoglobals
var (
emptyValue = NewAny(nil)
)
func Empty() Value {
return emptyValue
}
func IsEmpty(v Value) bool {
return v == nil || v == emptyValue
}
func NewAny(in interface{}) Value {
return Read{Any{v: in}}
}
type Any struct { type Any struct {
empty v interface{}
Val []interface{} }
Flag flag.Flag
func (a Any) Any() interface{} {
return a.v
} }
func (a *Any) Any() interface{} { func (a Any) Unmarshal(val interface{}) error {
if a.Flag.IsArray() { out, err := a.ParseString()
return a.Val if err != nil {
return fmt.Errorf("any parse string:%w", err)
} }
if len(a.Val) > 0 { uerr := json.Unmarshal([]byte(out), val)
return a.Val[0] if uerr != nil {
return fmt.Errorf("any unmarshal: %w", uerr)
} }
return nil return nil
} }
func (a Any) ParseString() (string, error) {
if a.v == nil {
return "", nil
}
bout, err := json.Marshal(a.v)
if err != nil {
return "", fmt.Errorf("any string:%w", err)
}
return string(bout), err
}
func (a Any) ParseInt() (int, error) {
out, ok := a.v.(int)
if !ok {
return 0, a.wrongType("int")
}
return out, nil
}
func (a Any) ParseInt64() (int64, error) {
out, ok := a.v.(int64)
if !ok {
return 0, a.wrongType("int64")
}
return out, nil
}
func (a Any) ParseUint() (uint, error) {
out, ok := a.v.(uint)
if !ok {
return 0, a.wrongType("uint")
}
return out, nil
}
func (a Any) ParseUint64() (uint64, error) {
out, ok := a.v.(uint64)
if !ok {
return 0, a.wrongType("uint64")
}
return out, nil
}
func (a Any) ParseFloat64() (float64, error) {
out, ok := a.v.(float64)
if !ok {
return 0, a.wrongType("float64")
}
return out, nil
}
func (a Any) ParseBool() (bool, error) {
out, ok := a.v.(bool)
if !ok {
return false, a.wrongType("bool")
}
return out, nil
}
func (a Any) ParseDuration() (time.Duration, error) {
out, ok := a.v.(time.Duration)
if !ok {
return 0, a.wrongType("time.Duration")
}
return out, nil
}
func (a Any) ParseTime() (time.Time, error) {
out, ok := a.v.(time.Time)
if !ok {
return time.Time{}, a.wrongType("time.Time")
}
return out, nil
}
func (a Any) wrongType(ex String) error {
return fmt.Errorf("%w any: got: %T expect: %s", ErrWrongType, a.v, ex)
}

146
input/value/bool.go

@ -1,44 +1,148 @@
package value package value
import ( import (
"strconv" "fmt"
"time"
)
"gitoa.ru/go-4devs/console/input/value/flag" var (
_ ParseValue = Bool(false)
_ SliceValue = Bools{}
) )
type Bool struct { func NewBools(in []bool) Slice {
empty return Slice{SliceValue: Bools(in)}
Val []bool }
Flag flag.Flag
type Bools []bool
func (b Bools) Any() interface{} {
return b.Bools()
}
func (b Bools) Unmarshal(val interface{}) error {
v, ok := val.(*[]bool)
if !ok {
return fmt.Errorf("%w: expect: *[]bool got: %T", ErrWrongType, val)
}
*v = b
return nil
}
func (b Bools) Strings() []string {
return nil
}
func (b Bools) Ints() []int {
return nil
}
func (b Bools) Int64s() []int64 {
return nil
}
func (b Bools) Uints() []uint {
return nil
}
func (b Bools) Uint64s() []uint64 {
return nil
}
func (b Bools) Float64s() []float64 {
return nil
}
func (b Bools) Bools() []bool {
out := make([]bool, len(b))
copy(out, b)
return out
}
func (b Bools) Durations() []time.Duration {
return nil
} }
func (b *Bool) Append(in string) error { func (b Bools) Times() []time.Time {
v, err := strconv.ParseBool(in) return nil
if err != nil { }
return err
func NewBool(in bool) Read {
return Read{ParseValue: Bool(in)}
}
type Bool bool
func (b Bool) Unmarshal(val interface{}) error {
v, ok := val.(*bool)
if !ok {
return fmt.Errorf("%w: expect: *bool got: %T", ErrWrongType, val)
} }
b.Val = append(b.Val, v) *v = bool(b)
return nil return nil
} }
func (b *Bool) Bool() bool { func (b Bool) ParseString() (string, error) {
if !b.Flag.IsArray() && len(b.Val) == 1 { return fmt.Sprintf("%v", b), nil
return b.Val[0] }
func (b Bool) ParseInt() (int, error) {
if b {
return 1, nil
}
return 0, nil
}
func (b Bool) ParseInt64() (int64, error) {
if b {
return 1, nil
}
return 0, nil
}
func (b Bool) ParseUint() (uint, error) {
if b {
return 1, nil
} }
return false return 0, nil
} }
func (b *Bool) Bools() []bool { func (b Bool) ParseUint64() (uint64, error) {
return b.Val if b {
return 1, nil
}
return 0, nil
} }
func (b *Bool) Any() interface{} { func (b Bool) ParseFloat64() (float64, error) {
if b.Flag.IsArray() { if b {
return b.Bools() return 1, nil
} }
return b.Bool() return 0, nil
}
func (b Bool) ParseBool() (bool, error) {
return bool(b), nil
}
func (b Bool) ParseDuration() (time.Duration, error) {
return 0, fmt.Errorf("bool to duration:%w", ErrWrongType)
}
func (b Bool) ParseTime() (time.Time, error) {
return time.Time{}, fmt.Errorf("bool to time:%w", ErrWrongType)
}
func (b Bool) Any() interface{} {
return bool(b)
} }

126
input/value/duration.go

@ -1,44 +1,128 @@
package value package value
import ( import (
"fmt"
"time" "time"
)
"gitoa.ru/go-4devs/console/input/value/flag" var (
_ ParseValue = Duration(0)
_ SliceValue = Durations{}
) )
type Duration struct { func NewDurations(in []time.Duration) Slice {
empty return Slice{SliceValue: Durations(in)}
Val []time.Duration
Flag flag.Flag
} }
func (d *Duration) Append(in string) error { type Durations []time.Duration
v, err := time.ParseDuration(in)
if err != nil { func (d Durations) Unmarshal(val interface{}) error {
return err v, ok := val.(*[]time.Duration)
if !ok {
return fmt.Errorf("%w: expect: *[]time.Duration got: %T", ErrWrongType, val)
} }
d.Val = append(d.Val, v) *v = d
return nil return nil
} }
func (d *Duration) Duration() time.Duration { func (d Durations) Any() interface{} {
if !d.Flag.IsArray() && len(d.Val) == 1 { return d.Durations()
return d.Val[0] }
}
func (d Durations) Strings() []string {
return nil
}
func (d Durations) Ints() []int {
return nil
}
func (d Durations) Int64s() []int64 {
return nil
}
func (d Durations) Uints() []uint {
return nil
}
func (d Durations) Uint64s() []uint64 {
return nil
}
func (d Durations) Float64s() []float64 {
return nil
}
func (d Durations) Bools() []bool {
return nil
}
func (d Durations) Durations() []time.Duration {
out := make([]time.Duration, len(d))
copy(out, d)
return 0 return out
} }
func (d *Duration) Durations() []time.Duration { func (d Durations) Times() []time.Time {
return d.Val return nil
}
func NewDuration(in time.Duration) Read {
return Read{ParseValue: Duration(in)}
}
type Duration time.Duration
func (d Duration) ParseDuration() (time.Duration, error) {
return time.Duration(d), nil
}
func (d Duration) ParseString() (string, error) {
return time.Duration(d).String(), nil
}
func (d Duration) ParseInt() (int, error) {
return int(d), nil
}
func (d Duration) ParseInt64() (int64, error) {
return int64(d), nil
}
func (d Duration) ParseUint() (uint, error) {
return uint(d), nil
}
func (d Duration) ParseUint64() (uint64, error) {
return uint64(d), nil
}
func (d Duration) ParseFloat64() (float64, error) {
return float64(d), nil
}
func (d Duration) ParseBool() (bool, error) {
return false, fmt.Errorf("duration:%w", ErrWrongType)
} }
func (d *Duration) Any() interface{} { func (d Duration) ParseTime() (time.Time, error) {
if d.Flag.IsArray() { return time.Time{}, fmt.Errorf("duration:%w", ErrWrongType)
return d.Durations() }
func (d Duration) Unmarshal(val interface{}) error {
v, ok := val.(*time.Duration)
if !ok {
return fmt.Errorf("%w: expect: *[]time.Duration got: %T", ErrWrongType, val)
} }
return d.Duration() *v = time.Duration(d)
return nil
}
func (d Duration) Any() interface{} {
return time.Duration(d)
} }

100
input/value/empty.go

@ -1,100 +0,0 @@
package value
import (
"time"
)
// nolint: gochecknoglobals
var (
emptyValue = &empty{}
)
func Empty() Value {
return emptyValue
}
func IsEmpty(v Value) bool {
return v == nil || v == emptyValue
}
type empty struct{}
func (e *empty) Append(string) error {
return ErrAppendEmpty
}
func (e *empty) String() string {
return ""
}
func (e *empty) Int() int {
return 0
}
func (e *empty) Int64() int64 {
return 0
}
func (e *empty) Uint() uint {
return 0
}
func (e *empty) Uint64() uint64 {
return 0
}
func (e *empty) Float64() float64 {
return 0
}
func (e *empty) Bool() bool {
return false
}
func (e *empty) Duration() time.Duration {
return 0
}
func (e *empty) Time() time.Time {
return time.Time{}
}
func (e *empty) Strings() []string {
return nil
}
func (e *empty) Ints() []int {
return nil
}
func (e *empty) Int64s() []int64 {
return nil
}
func (e *empty) Uints() []uint {
return nil
}
func (e *empty) Uint64s() []uint64 {
return nil
}
func (e *empty) Float64s() []float64 {
return nil
}
func (e *empty) Bools() []bool {
return nil
}
func (e *empty) Durations() []time.Duration {
return nil
}
func (e *empty) Times() []time.Time {
return nil
}
func (e *empty) Any() interface{} {
return nil
}

128
input/value/float64.go

@ -1,44 +1,128 @@
package value package value
import ( import (
"strconv" "fmt"
"time"
)
"gitoa.ru/go-4devs/console/input/value/flag" var (
_ ParseValue = Float64(0)
_ SliceValue = Float64s{}
) )
type Float64 struct { func NewFloat64s(in []float64) Slice {
empty return Slice{SliceValue: Float64s(in)}
Val []float64 }
Flag flag.Flag
type Float64s []float64
func (f Float64s) Any() interface{} {
return f.Float64s()
} }
func (f *Float64) Append(in string) error { func (f Float64s) Unmarshal(val interface{}) error {
v, err := strconv.ParseFloat(in, 64) v, ok := val.(*[]float64)
if err != nil { if !ok {
return err return fmt.Errorf("%w: expect *[]float64", ErrWrongType)
} }
f.Val = append(f.Val, v) *v = f
return nil return nil
} }
func (f *Float64) Float64() float64 { func (f Float64s) Strings() []string {
if !f.Flag.IsArray() && len(f.Val) == 1 { return nil
return f.Val[0] }
}
func (f Float64s) Ints() []int {
return nil
}
func (f Float64s) Int64s() []int64 {
return nil
}
func (f Float64s) Uints() []uint {
return nil
}
func (f Float64s) Uint64s() []uint64 {
return nil
}
func (f Float64s) Float64s() []float64 {
out := make([]float64, len(f))
copy(out, f)
return out
}
func (f Float64s) Bools() []bool {
return nil
}
func (f Float64s) Durations() []time.Duration {
return nil
}
func (f Float64s) Times() []time.Time {
return nil
}
func NewFloat64(in float64) Read {
return Read{ParseValue: Float64(in)}
}
type Float64 float64
func (f Float64) Any() interface{} {
return float64(f)
}
func (f Float64) ParseString() (string, error) {
return fmt.Sprint(float64(f)), nil
}
func (f Float64) ParseInt() (int, error) {
return int(f), nil
}
func (f Float64) ParseInt64() (int64, error) {
return int64(f), nil
}
func (f Float64) ParseUint() (uint, error) {
return uint(f), nil
}
func (f Float64) ParseUint64() (uint64, error) {
return uint64(f), nil
}
return 0 func (f Float64) ParseFloat64() (float64, error) {
return float64(f), nil
} }
func (f *Float64) Float64s() []float64 { func (f Float64) ParseBool() (bool, error) {
return f.Val return false, fmt.Errorf("float64:%w", ErrWrongType)
} }
func (f *Float64) Any() interface{} { func (f Float64) ParseDuration() (time.Duration, error) {
if f.Flag.IsArray() { return time.Duration(f), nil
return f.Float64s() }
func (f Float64) ParseTime() (time.Time, error) {
return time.Unix(0, int64(f*Float64(time.Second))), nil
}
func (f Float64) Unmarshal(in interface{}) error {
v, ok := in.(*float64)
if !ok {
return fmt.Errorf("%w: expect *float64", ErrWrongType)
} }
return f.Float64() *v = float64(f)
return nil
} }

47
input/value/float64_test.go

@ -0,0 +1,47 @@
package value_test
import (
"math"
"testing"
"github.com/stretchr/testify/require"
"gitoa.ru/go-4devs/console/input/value"
)
func TestFloat64_Unmarshal(t *testing.T) {
t.Parallel()
f := value.Float64(math.Pi)
var out float64
require.NoError(t, f.Unmarshal(&out))
require.Equal(t, math.Pi, out)
}
func TestFloat64_Any(t *testing.T) {
t.Parallel()
f := value.Float64(math.Pi)
require.Equal(t, math.Pi, f.Any())
}
func TestFloat64s_Unmarshal(t *testing.T) {
t.Parallel()
f := value.Float64s{math.Pi, math.Sqrt2}
var out []float64
require.NoError(t, f.Unmarshal(&out))
require.Equal(t, []float64{math.Pi, math.Sqrt2}, out)
}
func TestFloat64s_Any(t *testing.T) {
t.Parallel()
f := value.Float64s{math.Pi, math.Sqrt2}
require.Equal(t, []float64{math.Pi, math.Sqrt2}, f.Any())
}

127
input/value/int.go

@ -1,44 +1,129 @@
package value package value
import ( import (
"fmt"
"strconv" "strconv"
"time"
)
"gitoa.ru/go-4devs/console/input/value/flag" var (
_ ParseValue = Int(0)
_ SliceValue = Ints{}
) )
type Int struct { func NewInts(in []int) Slice {
empty return Slice{SliceValue: Ints(in)}
Val []int
Flag flag.Flag
} }
func (i *Int) Append(in string) error { type Ints []int
v, err := strconv.Atoi(in)
if err != nil { func (i Ints) Unmarshal(in interface{}) error {
return err val, ok := in.(*[]int)
if !ok {
return fmt.Errorf("%w: expect *[]int", ErrWrongType)
} }
i.Val = append(i.Val, v) *val = i
return nil return nil
} }
func (i *Int) Int() int { func (i Ints) Any() interface{} {
if !i.Flag.IsArray() && len(i.Val) == 1 { return i.Ints()
return i.Val[0] }
}
func (i Ints) Strings() []string {
return nil
}
func (i Ints) Ints() []int {
out := make([]int, len(i))
copy(out, i)
return out
}
func (i Ints) Int64s() []int64 {
return nil
}
func (i Ints) Uints() []uint {
return nil
}
func (i Ints) Uint64s() []uint64 {
return nil
}
func (i Ints) Float64s() []float64 {
return nil
}
func (i Ints) Bools() []bool {
return nil
}
func (i Ints) Durations() []time.Duration {
return nil
}
return 0 func (i Ints) Times() []time.Time {
return nil
} }
func (i *Int) Ints() []int { func NewInt(in int) Read {
return i.Val return Read{ParseValue: Int(in)}
} }
func (i *Int) Any() interface{} { type Int int
if i.Flag.IsArray() {
return i.Ints() func (i Int) Unmarshal(in interface{}) error {
v, ok := in.(*int)
if !ok {
return fmt.Errorf("%w: expect *int", ErrWrongType)
} }
return i.Int() *v = int(i)
return nil
}
func (i Int) ParseString() (string, error) {
return strconv.Itoa(int(i)), nil
}
func (i Int) ParseInt() (int, error) {
return int(i), nil
}
func (i Int) ParseInt64() (int64, error) {
return int64(i), nil
}
func (i Int) ParseUint() (uint, error) {
return uint(i), nil
}
func (i Int) ParseUint64() (uint64, error) {
return uint64(i), nil
}
func (i Int) ParseFloat64() (float64, error) {
return float64(i), nil
}
func (i Int) ParseBool() (bool, error) {
return false, fmt.Errorf("int:%w", ErrWrongType)
}
func (i Int) ParseDuration() (time.Duration, error) {
return time.Duration(i), nil
}
func (i Int) ParseTime() (time.Time, error) {
return time.Unix(0, int64(i)), nil
}
func (i Int) Any() interface{} {
return int(i)
} }

127
input/value/int64.go

@ -1,44 +1,129 @@
package value package value
import ( import (
"fmt"
"strconv" "strconv"
"time"
)
"gitoa.ru/go-4devs/console/input/value/flag" var (
_ ParseValue = Int64(0)
_ SliceValue = Int64s{}
) )
type Int64 struct { func NewInt64s(in []int64) Slice {
empty return Slice{SliceValue: Int64s(in)}
Val []int64 }
Flag flag.Flag
type Int64s []int64
func (i Int64s) Any() interface{} {
return i.Int64s()
} }
func (i *Int64) Int64() int64 { func (i Int64s) Unmarshal(val interface{}) error {
if !i.Flag.IsArray() && len(i.Val) == 1 { v, ok := val.(*[]int64)
return i.Val[0] if !ok {
return fmt.Errorf("%w: expect *[]int64", ErrWrongType)
} }
return 0 *v = i
return nil
} }
func (i *Int64) Int64s() []int64 { func (i Int64s) Strings() []string {
return i.Val return nil
} }
func (i *Int64) Any() interface{} { func (i Int64s) Ints() []int {
if i.Flag.IsArray() { return nil
return i.Int64s() }
}
func (i Int64s) Int64s() []int64 {
out := make([]int64, len(i))
copy(out, i)
return out
}
func (i Int64s) Uints() []uint {
return nil
}
func (i Int64s) Uint64s() []uint64 {
return nil
}
func (i Int64s) Float64s() []float64 {
return nil
}
func (i Int64s) Bools() []bool {
return nil
}
func (i Int64s) Durations() []time.Duration {
return nil
}
func (i Int64s) Times() []time.Time {
return nil
}
func NewInt64(in int64) Read {
return Read{ParseValue: Int64(in)}
}
type Int64 int64
func (i Int64) Any() interface{} {
return int64(i)
}
func (i Int64) ParseString() (string, error) {
return strconv.FormatInt(int64(i), 10), nil
}
func (i Int64) ParseInt() (int, error) {
return int(i), nil
}
func (i Int64) ParseInt64() (int64, error) {
return int64(i), nil
}
func (i Int64) ParseUint() (uint, error) {
return uint(i), nil
}
func (i Int64) ParseUint64() (uint64, error) {
return uint64(i), nil
}
func (i Int64) ParseFloat64() (float64, error) {
return float64(i), nil
}
func (i Int64) ParseBool() (bool, error) {
return false, fmt.Errorf("int64:%w", ErrWrongType)
}
func (i Int64) ParseDuration() (time.Duration, error) {
return time.Duration(i), nil
}
return i.Int64() func (i Int64) ParseTime() (time.Time, error) {
return time.Unix(0, int64(i)), nil
} }
func (i *Int64) Append(in string) error { func (i Int64) Unmarshal(val interface{}) error {
v, err := strconv.ParseInt(in, 10, 64) v, ok := val.(*int64)
if err != nil { if !ok {
return err return fmt.Errorf("%w: expect *int64", ErrWrongType)
} }
i.Val = append(i.Val, v) *v = int64(i)
return nil return nil
} }

185
input/value/read.go

@ -1,20 +1,189 @@
package value package value
import ( import (
"errors" "fmt"
) "time"
var _ Append = (*Read)(nil) "gitoa.ru/go-4devs/console/input/errs"
)
var ( var (
ErrAppendRead = errors.New("invalid append data to read value") _ Value = Read{}
ErrAppendEmpty = errors.New("invalid apped data to empty value") _ Value = Slice{}
) )
var ErrWrongType = errs.ErrWrongType
type Read struct { type Read struct {
Value ParseValue
}
func (r Read) String() string {
sout, _ := r.ParseValue.ParseString()
return sout
}
func (r Read) Int() int {
iout, _ := r.ParseValue.ParseInt()
return iout
}
func (r Read) Int64() int64 {
iout, _ := r.ParseValue.ParseInt64()
return iout
}
func (r Read) Uint() uint {
uout, _ := r.ParseValue.ParseUint()
return uout
}
func (r Read) Uint64() uint64 {
uout, _ := r.ParseValue.ParseUint64()
return uout
}
func (r Read) Float64() float64 {
fout, _ := r.ParseValue.ParseFloat64()
return fout
}
func (r Read) Bool() bool {
bout, _ := r.ParseValue.ParseBool()
return bout
}
func (r Read) Duration() time.Duration {
dout, _ := r.ParseValue.ParseDuration()
return dout
}
func (r Read) Time() time.Time {
tout, _ := r.ParseValue.ParseTime()
return tout
}
func (r Read) Strings() []string {
return []string{r.String()}
}
func (r Read) Ints() []int {
return []int{r.Int()}
}
func (r Read) Int64s() []int64 {
return []int64{r.Int64()}
}
func (r Read) Uints() []uint {
return []uint{r.Uint()}
}
func (r Read) Uint64s() []uint64 {
return []uint64{r.Uint64()}
}
func (r Read) Float64s() []float64 {
return []float64{r.Float64()}
}
func (r Read) Bools() []bool {
return []bool{r.Bool()}
}
func (r Read) Durations() []time.Duration {
return []time.Duration{r.Duration()}
}
func (r Read) Times() []time.Time {
return []time.Time{r.Time()}
}
type Slice struct {
SliceValue
}
func (s Slice) String() string {
return ""
}
func (s Slice) Int() int {
return 0
}
func (s Slice) Int64() int64 {
return 0
}
func (s Slice) Uint() uint {
return 0
}
func (s Slice) Uint64() uint64 {
return 0
}
func (s Slice) Float64() float64 {
return 0
}
func (s Slice) Bool() bool {
return false
}
func (s Slice) Duration() time.Duration {
return 0
}
func (s Slice) Time() time.Time {
return time.Time{}
}
func (s Slice) wrongType() error {
return fmt.Errorf("%w: for %T", ErrWrongType, s.SliceValue)
}
func (s Slice) ParseString() (string, error) {
return "", s.wrongType()
}
func (s Slice) ParseInt() (int, error) {
return 0, s.wrongType()
}
func (s Slice) ParseInt64() (int64, error) {
return 0, s.wrongType()
}
func (s Slice) ParseUint() (uint, error) {
return 0, s.wrongType()
}
func (s Slice) ParseUint64() (uint64, error) {
return 0, s.wrongType()
}
func (s Slice) ParseFloat64() (float64, error) {
return 0, s.wrongType()
}
func (s Slice) ParseBool() (bool, error) {
return false, s.wrongType()
}
func (s Slice) ParseDuration() (time.Duration, error) {
return 0, s.wrongType()
} }
func (r *Read) Append(string) error { func (s Slice) ParseTime() (time.Time, error) {
return ErrAppendRead return time.Time{}, s.wrongType()
} }

171
input/value/string.go

@ -1,39 +1,172 @@
package value package value
import "gitoa.ru/go-4devs/console/input/value/flag" import (
"fmt"
"strconv"
"time"
)
type String struct { var (
empty _ ParseValue = (String)("")
Val []string _ SliceValue = (Strings)(nil)
Flag flag.Flag )
func NewStrings(in []string) Slice {
return Slice{SliceValue: Strings(in)}
}
type Strings []string
func (s Strings) Unmarshal(in interface{}) error {
val, ok := in.(*[]string)
if !ok {
return fmt.Errorf("%w: expect *[]string", ErrWrongType)
}
*val = s
return nil
}
func (s Strings) Any() interface{} {
return s.Strings()
}
func (s Strings) Strings() []string {
out := make([]string, len(s))
copy(out, s)
return out
}
func (s Strings) Ints() []int {
return nil
}
func (s Strings) Int64s() []int64 {
return nil
}
func (s Strings) Uints() []uint {
return nil
}
func (s Strings) Uint64s() []uint64 {
return nil
}
func (s Strings) Float64s() []float64 {
return nil
}
func (s Strings) Bools() []bool {
return nil
}
func (s Strings) Durations() []time.Duration {
return nil
} }
func (s *String) Append(in string) error { func (s Strings) Times() []time.Time {
s.Val = append(s.Val, in) return nil
}
func NewString(in string) Value {
return Read{ParseValue: String(in)}
}
type String string
func (s String) ParseString() (string, error) {
return string(s), nil
}
func (s String) Unmarshal(in interface{}) error {
v, ok := in.(*string)
if !ok {
return fmt.Errorf("%w: expect *string", ErrWrongType)
}
*v = string(s)
return nil return nil
} }
func (s *String) String() string { func (s String) Any() interface{} {
if s.Flag.IsArray() { return string(s)
return "" }
func (s String) ParseInt() (int, error) {
v, err := strconv.Atoi(string(s))
if err != nil {
return 0, fmt.Errorf("string int:%w", err)
}
return v, nil
}
func (s String) Int64() int64 {
out, _ := s.ParseInt64()
return out
}
func (s String) ParseInt64() (int64, error) {
v, err := strconv.ParseInt(string(s), 10, 64)
if err != nil {
return 0, fmt.Errorf("string int64:%w", err)
} }
if len(s.Val) == 1 { return v, nil
return s.Val[0] }
func (s String) ParseUint() (uint, error) {
uout, err := s.ParseUint64()
return uint(uout), err
}
func (s String) ParseUint64() (uint64, error) {
uout, err := strconv.ParseUint(string(s), 10, 64)
if err != nil {
return 0, fmt.Errorf("string uint:%w", err)
} }
return "" return uout, nil
} }
func (s *String) Strings() []string { func (s String) ParseFloat64() (float64, error) {
return s.Val fout, err := strconv.ParseFloat(string(s), 64)
if err != nil {
return 0, fmt.Errorf("string float64:%w", err)
}
return fout, nil
}
func (s String) ParseBool() (bool, error) {
v, err := strconv.ParseBool(string(s))
if err != nil {
return false, fmt.Errorf("string bool:%w", err)
}
return v, nil
}
func (s String) ParseDuration() (time.Duration, error) {
v, err := time.ParseDuration(string(s))
if err != nil {
return 0, fmt.Errorf("string duration:%w", err)
}
return v, nil
} }
func (s *String) Any() interface{} { func (s String) ParseTime() (time.Time, error) {
if s.Flag.IsArray() { v, err := time.Parse(time.RFC3339, string(s))
return s.Strings() if err != nil {
return time.Time{}, fmt.Errorf("string time:%w", err)
} }
return s.String() return v, nil
} }

28
input/value/string_test.go

@ -0,0 +1,28 @@
package value_test
import (
"testing"
"github.com/stretchr/testify/require"
"gitoa.ru/go-4devs/console/input/value"
)
func TestStringUnmarshal(t *testing.T) {
t.Parallel()
st := value.New("test")
sta := value.New([]string{"test1", "test2"})
ac := ""
require.NoError(t, st.Unmarshal(&ac))
require.Equal(t, "test", ac)
aca := []string{}
require.NoError(t, sta.Unmarshal(&aca))
require.Equal(t, []string{"test1", "test2"}, aca)
require.ErrorIs(t, sta.Unmarshal(ac), value.ErrWrongType)
require.ErrorIs(t, sta.Unmarshal(&ac), value.ErrWrongType)
require.ErrorIs(t, st.Unmarshal(aca), value.ErrWrongType)
require.ErrorIs(t, st.Unmarshal(&aca), value.ErrWrongType)
}

128
input/value/time.go

@ -1,44 +1,130 @@
package value package value
import ( import (
"fmt"
"time" "time"
)
"gitoa.ru/go-4devs/console/input/value/flag" var (
_ ParseValue = Time{time.Now()}
_ SliceValue = (Times)(nil)
) )
type Time struct { func NewTimes(in []time.Time) Slice {
empty return Slice{SliceValue: Times(in)}
Val []time.Time
Flag flag.Flag
} }
func (t *Time) Append(in string) error { type Times []time.Time
v, err := time.Parse(time.RFC3339, in)
if err != nil { func (t Times) Any() interface{} {
return err return t.Times()
}
func (t Times) Unmarshal(val interface{}) error {
res, ok := val.(*[]time.Time)
if !ok {
return fmt.Errorf("%w: expect *[]time.Time", ErrWrongType)
} }
t.Val = append(t.Val, v) *res = t
return nil return nil
} }
func (t *Time) Time() time.Time { func (t Times) Strings() []string {
if !t.Flag.IsArray() && len(t.Val) == 1 { return nil
return t.Val[0] }
}
func (t Times) Ints() []int {
return nil
}
func (t Times) Int64s() []int64 {
return nil
}
return time.Time{} func (t Times) Uints() []uint {
return nil
}
func (t Times) Uint64s() []uint64 {
return nil
}
func (t Times) Float64s() []float64 {
return nil
} }
func (t *Time) Times() []time.Time { func (t Times) Bools() []bool {
return t.Val return nil
}
func (t Times) Durations() []time.Duration {
return nil
}
func (t Times) Times() []time.Time {
out := make([]time.Time, len(t))
copy(out, t)
return out
} }
func (t *Time) Amy() interface{} { func NewTime(in time.Time) Read {
if t.Flag.IsArray() { return Read{ParseValue: Time{Time: in}}
return t.Times() }
type Time struct {
time.Time
}
func (t Time) ParseString() (string, error) {
return t.Format(time.RFC3339), nil
}
func (t Time) ParseInt() (int, error) {
return int(t.Unix()), nil
}
func (t Time) ParseInt64() (int64, error) {
return t.Unix(), nil
}
func (t Time) ParseUint() (uint, error) {
return uint(t.Unix()), nil
}
func (t Time) ParseUint64() (uint64, error) {
return uint64(t.Unix()), nil
}
func (t Time) ParseFloat64() (float64, error) {
return float64(t.UnixNano()), nil
}
func (t Time) ParseBool() (bool, error) {
return false, fmt.Errorf("time bool:%w", ErrWrongType)
}
func (t Time) ParseDuration() (time.Duration, error) {
return 0, fmt.Errorf("time duration:%w", ErrWrongType)
}
func (t Time) ParseTime() (time.Time, error) {
return t.Time, nil
}
func (t Time) Unmarshal(val interface{}) error {
res, ok := val.(*time.Time)
if !ok {
return fmt.Errorf("%w: expect *time.Time", ErrWrongType)
} }
return t.Time() *res = t.Time
return nil
}
func (t Time) Any() interface{} {
return t.Time
} }

127
input/value/uint.go

@ -1,44 +1,129 @@
package value package value
import ( import (
"fmt"
"strconv" "strconv"
"time"
)
"gitoa.ru/go-4devs/console/input/value/flag" var (
_ ParseValue = Uint(0)
_ SliceValue = (Uints)(nil)
) )
type Uint struct { func NewUints(in []uint) Slice {
empty return Slice{SliceValue: Uints(in)}
Val []uint }
Flag flag.Flag
type Uints []uint
func (u Uints) Any() interface{} {
return u.Uints()
} }
func (u *Uint) Append(in string) error { func (u Uints) Unmarshal(val interface{}) error {
v, err := strconv.ParseUint(in, 10, 64) res, ok := val.(*[]uint)
if err != nil { if !ok {
return err return fmt.Errorf("%w: expect *[]uint", ErrWrongType)
} }
u.Val = append(u.Val, uint(v)) *res = u
return nil return nil
} }
func (u *Uint) Uint() uint { func (u Uints) Strings() []string {
if !u.Flag.IsArray() && len(u.Val) == 1 { return nil
return u.Val[0] }
}
func (u Uints) Ints() []int {
return nil
}
func (u Uints) Int64s() []int64 {
return nil
}
func (u Uints) Uints() []uint {
out := make([]uint, len(u))
copy(out, u)
return out
}
func (u Uints) Uint64s() []uint64 {
return nil
}
func (u Uints) Float64s() []float64 {
return nil
}
func (u Uints) Bools() []bool {
return nil
}
func (u Uints) Durations() []time.Duration {
return nil
}
func (u Uints) Times() []time.Time {
return nil
}
func NewUint(in uint) Read {
return Read{ParseValue: Uint(in)}
}
type Uint uint
func (u Uint) ParseString() (string, error) {
return strconv.FormatUint(uint64(u), 10), nil
}
func (u Uint) ParseInt() (int, error) {
return int(u), nil
}
func (u Uint) ParseInt64() (int64, error) {
return int64(u), nil
}
func (u Uint) ParseUint() (uint, error) {
return uint(u), nil
}
func (u Uint) ParseUint64() (uint64, error) {
return uint64(u), nil
}
func (u Uint) ParseFloat64() (float64, error) {
return float64(u), nil
}
return 0 func (u Uint) ParseBool() (bool, error) {
return false, fmt.Errorf("uint:%w", ErrWrongType)
} }
func (u *Uint) Uints() []uint { func (u Uint) ParseDuration() (time.Duration, error) {
return u.Val return time.Duration(u), nil
} }
func (u *Uint) Any() interface{} { func (u Uint) ParseTime() (time.Time, error) {
if u.Flag.IsArray() { return time.Unix(0, int64(u)), nil
return u.Uints() }
func (u Uint) Unmarshal(val interface{}) error {
res, ok := val.(*uint)
if !ok {
return fmt.Errorf("%w: expect *uint", ErrWrongType)
} }
return u.Uint() *res = uint(u)
return nil
}
func (u Uint) Any() interface{} {
return uint(u)
} }

127
input/value/uint64.go

@ -1,44 +1,129 @@
package value package value
import ( import (
"fmt"
"strconv" "strconv"
"time"
)
"gitoa.ru/go-4devs/console/input/value/flag" var (
_ ParseValue = Uint64(0)
_ SliceValue = (Uint64s)(nil)
) )
type Uint64 struct { func NewUint64s(in []uint64) Slice {
empty return Slice{SliceValue: Uint64s(in)}
Val []uint64 }
Flag flag.Flag
type Uint64s []uint64
func (u Uint64s) Any() interface{} {
return u.Uint64s()
} }
func (u *Uint64) Append(in string) error { func (u Uint64s) Unmarshal(val interface{}) error {
v, err := strconv.ParseUint(in, 10, 64) res, ok := val.(*[]uint64)
if err != nil { if !ok {
return err return fmt.Errorf("%w: expect *[]uint64", ErrWrongType)
} }
u.Val = append(u.Val, v) *res = u
return nil return nil
} }
func (u *Uint64) Uint64() uint64 { func (u Uint64s) Strings() []string {
if !u.Flag.IsArray() && len(u.Val) == 1 { return nil
return u.Val[0] }
}
func (u Uint64s) Ints() []int {
return nil
}
func (u Uint64s) Int64s() []int64 {
return nil
}
func (u Uint64s) Uints() []uint {
return nil
}
func (u Uint64s) Uint64s() []uint64 {
out := make([]uint64, len(u))
copy(out, u)
return out
}
func (u Uint64s) Float64s() []float64 {
return nil
}
func (u Uint64s) Bools() []bool {
return nil
}
func (u Uint64s) Durations() []time.Duration {
return nil
}
func (u Uint64s) Times() []time.Time {
return nil
}
func NewUint64(in uint64) Read {
return Read{ParseValue: Uint64(in)}
}
type Uint64 uint64
func (u Uint64) ParseString() (string, error) {
return strconv.FormatUint(uint64(u), 10), nil
}
func (u Uint64) ParseInt() (int, error) {
return int(u), nil
}
func (u Uint64) ParseInt64() (int64, error) {
return int64(u), nil
}
func (u Uint64) ParseUint() (uint, error) {
return uint(u), nil
}
func (u Uint64) ParseUint64() (uint64, error) {
return uint64(u), nil
}
func (u Uint64) ParseFloat64() (float64, error) {
return float64(u), nil
}
return 0 func (u Uint64) ParseBool() (bool, error) {
return false, fmt.Errorf("uint64 bool:%w", ErrWrongType)
} }
func (u *Uint64) Uint64s() []uint64 { func (u Uint64) ParseDuration() (time.Duration, error) {
return u.Val return time.Duration(u), nil
} }
func (u *Uint64) Any() interface{} { func (u Uint64) ParseTime() (time.Time, error) {
if u.Flag.IsArray() { return time.Unix(0, int64(0)), nil
return u.Uint64s() }
func (u Uint64) Unmarshal(val interface{}) error {
res, ok := val.(*uint64)
if !ok {
return fmt.Errorf("%w: expect *uint64", ErrWrongType)
} }
return u.Uint64() *res = uint64(u)
return nil
}
func (u Uint64) Any() interface{} {
return uint64(u)
} }

123
input/value/value.go

@ -2,11 +2,19 @@ package value
import ( import (
"time" "time"
"gitoa.ru/go-4devs/console/input/value/flag"
) )
type Value interface { type Value interface {
ReadValue
ParseValue
ArrValue
}
type UnmarshalValue interface {
Unmarshal(val interface{}) error
}
type ReadValue interface {
String() string String() string
Int() int Int() int
Int64() int64 Int64() int64
@ -16,8 +24,19 @@ type Value interface {
Bool() bool Bool() bool
Duration() time.Duration Duration() time.Duration
Time() time.Time Time() time.Time
}
type AnyValue interface {
Any() interface{} Any() interface{}
}
type SliceValue interface {
AnyValue
UnmarshalValue
ArrValue
}
type ArrValue interface {
Strings() []string Strings() []string
Ints() []int Ints() []int
Int64s() []int64 Int64s() []int64
@ -29,86 +48,74 @@ type Value interface {
Times() []time.Time Times() []time.Time
} }
//nolint:interfacebloat
type ParseValue interface {
ParseString() (string, error)
ParseInt() (int, error)
ParseInt64() (int64, error)
ParseUint() (uint, error)
ParseUint64() (uint64, error)
ParseFloat64() (float64, error)
ParseBool() (bool, error)
ParseDuration() (time.Duration, error)
ParseTime() (time.Time, error)
UnmarshalValue
AnyValue
}
type Append interface { type Append interface {
Value Value
Append(string) error Append(string) (Value, error)
} }
//nolint: gocyclo //nolint:gocyclo,cyclop
func New(v interface{}) Append { func New(in interface{}) Value {
switch val := v.(type) { switch val := in.(type) {
case bool:
return Read{Bool(val)}
case []bool:
return NewBools(val)
case string: case string:
return &String{Val: []string{val}, Flag: flag.String} return Read{String(val)}
case int: case int:
return &Int{Val: []int{val}, Flag: flag.Int} return Read{Int(val)}
case int64: case int64:
return &Int64{Val: []int64{val}, Flag: flag.Int64} return Read{Int64(val)}
case uint: case uint:
return &Uint{Val: []uint{val}, Flag: flag.Uint} return Read{Uint(val)}
case uint64: case uint64:
return &Uint64{Val: []uint64{val}, Flag: flag.Uint64} return Read{Uint64(val)}
case float64: case float64:
return &Float64{Val: []float64{val}, Flag: flag.Float64} return Read{Float64(val)}
case bool:
return &Bool{Val: []bool{val}, Flag: flag.Bool}
case time.Duration: case time.Duration:
return &Duration{Val: []time.Duration{val}, Flag: flag.Duration} return Read{Duration(val)}
case time.Time: case time.Time:
return &Time{Val: []time.Time{val}, Flag: flag.Time} return Read{Time{val}}
case []int64: case []int64:
return &Int64{Val: val, Flag: flag.Int64 | flag.Array} return Slice{Int64s(val)}
case []uint: case []uint:
return &Uint{Val: val, Flag: flag.Uint | flag.Array} return Slice{Uints(val)}
case []uint64: case []uint64:
return &Uint64{Val: val, Flag: flag.Uint64 | flag.Array} return Slice{Uint64s(val)}
case []float64: case []float64:
return &Float64{Val: val, Flag: flag.Float64 | flag.Array} return Slice{Float64s(val)}
case []bool:
return &Bool{Val: val, Flag: flag.Bool | flag.Array}
case []time.Duration: case []time.Duration:
return &Duration{Val: val, Flag: flag.Duration | flag.Array} return Slice{Durations(val)}
case []time.Time: case []time.Time:
return &Time{Val: val, Flag: flag.Time | flag.Array} return Slice{Times(val)}
case []string: case []string:
return &String{Val: val, Flag: flag.String | flag.Array} return Slice{Strings(val)}
case []int: case []int:
return &Int{Val: val, Flag: flag.Int | flag.Array} return Slice{Ints(val)}
case []interface{}: case []interface{}:
return &Any{Val: val, Flag: flag.Any | flag.Array} return Read{Any{v: val}}
case Append:
return val
case Value: case Value:
return &Read{Value: val} return val
default: default:
if v != nil { if in != nil {
return &Any{Val: []interface{}{v}, Flag: flag.Any} return Read{Any{v: in}}
} }
return &empty{} return Empty()
}
}
func ByFlag(f flag.Flag) Append {
switch {
case f.IsInt():
return &Int{Flag: f | flag.Int}
case f.IsInt64():
return &Int64{Flag: f | flag.Int64}
case f.IsUint():
return &Uint{Flag: f | flag.Uint}
case f.IsUint64():
return &Uint64{Flag: f | flag.Uint64}
case f.IsFloat64():
return &Float64{Flag: f | flag.Float64}
case f.IsBool():
return &Bool{Flag: f | flag.Bool}
case f.IsDuration():
return &Duration{Flag: f | flag.Duration}
case f.IsTime():
return &Time{Flag: f | flag.Time}
case f.IsAny():
return &Any{Flag: f | flag.Any}
default:
return &String{}
} }
} }

10
input/variable/argtype.go

@ -0,0 +1,10 @@
package variable
//go:generate stringer -type=ArgType -linecomment
type ArgType int
const (
TypeOption ArgType = iota + 1 // option
TypeArgument // argument
)

25
input/variable/argtype_string.go

@ -0,0 +1,25 @@
// Code generated by "stringer -type=ArgType -linecomment"; DO NOT EDIT.
package variable
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[TypeOption-1]
_ = x[TypeArgument-2]
}
const _ArgType_name = "optionargument"
var _ArgType_index = [...]uint8{0, 6, 14}
func (i ArgType) String() string {
i -= 1
if i < 0 || i >= ArgType(len(_ArgType_index)-1) {
return "ArgType(" + strconv.FormatInt(int64(i+1), 10) + ")"
}
return _ArgType_name[_ArgType_index[i]:_ArgType_index[i+1]]
}

31
input/variable/bool.go

@ -0,0 +1,31 @@
package variable
import (
"fmt"
"strconv"
"gitoa.ru/go-4devs/console/input/flag"
"gitoa.ru/go-4devs/console/input/value"
)
func Bool(name, description string, opts ...Option) Variable {
return String(name, description, append(opts, WithParse(CreateBool, AppendBool), Value(flag.Bool))...)
}
func CreateBool(in string) (value.Value, error) {
out, err := strconv.ParseBool(in)
if err != nil {
return nil, fmt.Errorf("create bool:%w", err)
}
return value.NewBool(out), nil
}
func AppendBool(old value.Value, in string) (value.Value, error) {
out, err := strconv.ParseBool(in)
if err != nil {
return nil, fmt.Errorf("create bool:%w", err)
}
return value.NewBools(append(old.Bools(), out)), nil
}

31
input/variable/duration.go

@ -0,0 +1,31 @@
package variable
import (
"fmt"
"time"
"gitoa.ru/go-4devs/console/input/flag"
"gitoa.ru/go-4devs/console/input/value"
)
func Duration(name, description string, opts ...Option) Variable {
return String(name, description, append(opts, WithParse(CreateDuration, AppendDuration), Value(flag.Duration))...)
}
func CreateDuration(in string) (value.Value, error) {
out, err := time.ParseDuration(in)
if err != nil {
return nil, fmt.Errorf("create duration:%w", err)
}
return value.NewDuration(out), nil
}
func AppendDuration(old value.Value, in string) (value.Value, error) {
out, err := time.ParseDuration(in)
if err != nil {
return nil, fmt.Errorf("append duration:%w", err)
}
return value.NewDurations(append(old.Durations(), out)), nil
}

32
input/variable/err.go

@ -0,0 +1,32 @@
package variable
import (
"errors"
"fmt"
)
type Error struct {
Name string
Err error
Type ArgType
}
func (o Error) Error() string {
return fmt.Sprintf("%s: '%s' %s", o.Type, o.Name, o.Err)
}
func (o Error) Is(err error) bool {
return errors.Is(err, o.Err)
}
func (o Error) Unwrap() error {
return o.Err
}
func Err(name string, t ArgType, err error) Error {
return Error{
Name: name,
Type: t,
Err: err,
}
}

31
input/variable/float64.go

@ -0,0 +1,31 @@
package variable
import (
"fmt"
"strconv"
"gitoa.ru/go-4devs/console/input/flag"
"gitoa.ru/go-4devs/console/input/value"
)
func Float64(name, description string, opts ...Option) Variable {
return String(name, description, append(opts, WithParse(CreateFloat64, AppendFloat64), Value(flag.Float64))...)
}
func CreateFloat64(in string) (value.Value, error) {
out, err := strconv.ParseFloat(in, 10)
if err != nil {
return nil, fmt.Errorf("create float64:%w", err)
}
return value.NewFloat64(out), nil
}
func AppendFloat64(old value.Value, in string) (value.Value, error) {
out, err := strconv.ParseFloat(in, 10)
if err != nil {
return nil, fmt.Errorf("append float64:%w", err)
}
return value.NewFloat64s(append(old.Float64s(), out)), nil
}

31
input/variable/int.go

@ -0,0 +1,31 @@
package variable
import (
"fmt"
"strconv"
"gitoa.ru/go-4devs/console/input/flag"
"gitoa.ru/go-4devs/console/input/value"
)
func Int(name, description string, opts ...Option) Variable {
return New(name, description, append(opts, WithParse(CreateInt, AppendInt), Value(flag.Int))...)
}
func AppendInt(old value.Value, in string) (value.Value, error) {
out, err := strconv.Atoi(in)
if err != nil {
return nil, fmt.Errorf("append int:%w", err)
}
return value.NewInts(append(old.Ints(), out)), nil
}
func CreateInt(in string) (value.Value, error) {
out, err := strconv.Atoi(in)
if err != nil {
return nil, fmt.Errorf("create int:%w", err)
}
return value.NewInt(out), nil
}

31
input/variable/int64.go

@ -0,0 +1,31 @@
package variable
import (
"fmt"
"strconv"
"gitoa.ru/go-4devs/console/input/flag"
"gitoa.ru/go-4devs/console/input/value"
)
func Int64(name, description string, opts ...Option) Variable {
return String(name, description, append(opts, WithParse(CreateInt64, AppendInt64), Value(flag.Int64))...)
}
func CreateInt64(in string) (value.Value, error) {
out, err := strconv.ParseInt(in, 10, 64)
if err != nil {
return nil, fmt.Errorf("create int64:%w", err)
}
return value.NewInt64(out), nil
}
func AppendInt64(old value.Value, in string) (value.Value, error) {
out, err := strconv.ParseInt(in, 10, 64)
if err != nil {
return nil, fmt.Errorf("append int64:%w", err)
}
return value.NewInt64s(append(old.Int64s(), out)), nil
}

17
input/variable/string.go

@ -0,0 +1,17 @@
package variable
import (
"gitoa.ru/go-4devs/console/input/value"
)
func String(name, description string, opts ...Option) Variable {
return New(name, description, opts...)
}
func CreateString(in string) (value.Value, error) {
return value.NewString(in), nil
}
func AppendString(old value.Value, in string) (value.Value, error) {
return value.NewStrings(append(old.Strings(), in)), nil
}

77
input/variable/time.go

@ -0,0 +1,77 @@
package variable
import (
"fmt"
"time"
"gitoa.ru/go-4devs/console/input/errs"
"gitoa.ru/go-4devs/console/input/flag"
"gitoa.ru/go-4devs/console/input/value"
)
const (
ParamFormat = "format"
)
func Time(name, description string, opts ...Option) Variable {
return String(name, description, append(opts,
WithParamParse(CreateTime, AppendTime),
WithParam(ParamFormat, RFC3339),
Value(flag.Time),
)...)
}
func RFC3339(in interface{}) error {
v, ok := in.(*string)
if !ok {
return fmt.Errorf("%w: expect *string got %T", errs.ErrWrongType, in)
}
*v = time.RFC3339
return nil
}
func CreateTime(param Param) Create {
var (
formatErr error
format string
)
formatErr = param.Value(ParamFormat, &format)
return func(in string) (value.Value, error) {
if formatErr != nil {
return nil, fmt.Errorf("create format:%w", formatErr)
}
out, err := time.Parse(format, in)
if err != nil {
return nil, fmt.Errorf("create time:%w", err)
}
return value.NewTime(out), nil
}
}
func AppendTime(param Param) Append {
var (
formatErr error
format string
)
formatErr = param.Value(ParamFormat, &format)
return func(old value.Value, in string) (value.Value, error) {
if formatErr != nil {
return nil, fmt.Errorf("append format:%w", formatErr)
}
out, err := time.Parse(format, in)
if err != nil {
return nil, fmt.Errorf("append time:%w", err)
}
return value.NewTimes(append(old.Times(), out)), nil
}
}

31
input/variable/uint.go

@ -0,0 +1,31 @@
package variable
import (
"fmt"
"strconv"
"gitoa.ru/go-4devs/console/input/flag"
"gitoa.ru/go-4devs/console/input/value"
)
func Uint(name, description string, opts ...Option) Variable {
return String(name, description, append(opts, WithParse(CreateUint, AppendUint), Value(flag.Uint))...)
}
func CreateUint(in string) (value.Value, error) {
out, err := strconv.ParseUint(in, 10, 64)
if err != nil {
return nil, fmt.Errorf("create uint:%w", err)
}
return value.NewUint(uint(out)), nil
}
func AppendUint(old value.Value, in string) (value.Value, error) {
out, err := strconv.ParseUint(in, 10, 64)
if err != nil {
return nil, fmt.Errorf("append uint:%w", err)
}
return value.NewUints(append(old.Uints(), uint(out))), nil
}

31
input/variable/uint64.go

@ -0,0 +1,31 @@
package variable
import (
"fmt"
"strconv"
"gitoa.ru/go-4devs/console/input/flag"
"gitoa.ru/go-4devs/console/input/value"
)
func Uint64(name, descriontion string, opts ...Option) Variable {
return String(name, descriontion, append(opts, WithParse(CreateUint64, AppendUint64), Value(flag.Uint64))...)
}
func CreateUint64(in string) (value.Value, error) {
out, err := strconv.ParseUint(in, 10, 64)
if err != nil {
return nil, fmt.Errorf("create uint64:%w", err)
}
return value.NewUint64(out), nil
}
func AppendUint64(old value.Value, in string) (value.Value, error) {
out, err := strconv.ParseUint(in, 10, 64)
if err != nil {
return nil, fmt.Errorf("append uint64:%w", err)
}
return value.NewUint64s(append(old.Uint64s(), out)), nil
}

158
input/variable/variable.go

@ -0,0 +1,158 @@
package variable
import (
"fmt"
"gitoa.ru/go-4devs/console/input/errs"
"gitoa.ru/go-4devs/console/input/flag"
"gitoa.ru/go-4devs/console/input/value"
)
type Option func(*Variable)
func WithType(t ArgType) Option {
return func(v *Variable) {
v.Type = t
}
}
func ArgOption(v *Variable) {
v.Type = TypeOption
}
func ArgArgument(v *Variable) {
v.Type = TypeArgument
}
func Value(in flag.Flag) Option {
return func(v *Variable) {
v.Flag |= in
}
}
func Default(in value.Value) Option {
return func(v *Variable) {
v.Default = in
}
}
func Required(v *Variable) {
v.Flag |= flag.Required
}
func WithParse(create Create, update Append) Option {
return func(v *Variable) {
v.append = func(Param) Append { return update }
v.create = func(Param) Create { return create }
}
}
func WithParamParse(create func(Param) Create, update func(Param) Append) Option {
return func(v *Variable) {
v.append = update
v.create = create
}
}
func Valid(f ...func(value.Value) error) Option {
return func(v *Variable) {
v.Valid = f
}
}
func Array(o *Variable) {
o.Flag |= flag.Array
}
func WithParam(name string, fn func(interface{}) error) Option {
return func(v *Variable) {
v.params[name] = fn
}
}
type (
Create func(s string) (value.Value, error)
Append func(old value.Value, s string) (value.Value, error)
)
func New(name, description string, opts ...Option) Variable {
res := Variable{
Name: name,
Description: description,
Type: TypeOption,
create: func(Param) Create { return CreateString },
append: func(Param) Append { return AppendString },
params: make(Params),
}
for _, opt := range opts {
opt(&res)
}
return res
}
type Variable struct {
Name string
Description string
Alias string
Flag flag.Flag
Type ArgType
Default value.Value
Valid []func(value.Value) error
params Params
create func(Param) Create
append func(Param) Append
}
func (v Variable) Validate(in value.Value) error {
for _, valid := range v.Valid {
if err := valid(in); err != nil {
return Err(v.Name, v.Type, err)
}
}
return nil
}
func (v Variable) IsArray() bool {
return v.Flag.IsArray()
}
func (v Variable) IsRequired() bool {
return v.Flag.IsRequired()
}
func (v Variable) HasDefault() bool {
return v.Default != nil
}
func (v Variable) IsBool() bool {
return v.Flag.IsBool()
}
func (v Variable) HasShort() bool {
return v.Type == TypeOption && len(v.Alias) == 1
}
func (v Variable) Create(s string) (value.Value, error) {
return v.create(v.params)(s)
}
func (v Variable) Append(old value.Value, s string) (value.Value, error) {
return v.append(v.params)(old, s)
}
type Param interface {
Value(name string, v interface{}) error
}
type Params map[string]func(interface{}) error
func (p Params) Value(name string, v interface{}) error {
if p, ok := p[name]; ok {
return p(v)
}
return fmt.Errorf("%w: param %v", errs.ErrNotFound, name)
}

130
list.go

@ -15,7 +15,7 @@ import (
const defaultLenNamespace = 2 const defaultLenNamespace = 2
//nolint: gochecknoinits //nolint:gochecknoinits
func init() { func init() {
MustRegister(list()) MustRegister(list())
} }
@ -32,71 +32,15 @@ You can also display the commands for a specific namespace:
You can also output the information in other formats by using the <comment>--format</comment> option: You can also output the information in other formats by using the <comment>--format</comment> option:
<info>{{ .Bin }} {{ .Name }} --format=xml</info> <info>{{ .Bin }} {{ .Name }} --format=xml</info>
`, `,
Execute: func(ctx context.Context, in input.Input, out output.Output) error { Execute: executeList,
ns := in.Argument(ctx, "namespace").String()
format := in.Option(ctx, helpOptFormat).String()
des, err := descriptor.Find(format)
if err != nil {
return err
}
cmds := Commands()
commands := descriptor.Commands{
Namespace: ns,
Definition: Default(input.NewDefinition()),
}
groups := make(map[string]*descriptor.NSCommand)
namespaces := make([]string, 0, len(cmds))
empty := descriptor.NSCommand{}
for _, name := range cmds {
if ns != "" && !strings.HasPrefix(name, ns+":") {
continue
}
cmd, _ := Find(name)
if cmd.Hidden {
continue
}
gn := strings.SplitN(name, ":", 2)
if len(gn) != defaultLenNamespace {
empty.Append(cmd.Name, cmd.Description)
continue
}
if _, ok := groups[gn[0]]; !ok {
groups[gn[0]] = &descriptor.NSCommand{
Name: gn[0],
}
namespaces = append(namespaces, gn[0])
}
groups[gn[0]].Append(name, cmd.Description)
}
if len(empty.Commands) > 0 {
commands.Commands = append(commands.Commands, empty)
}
for _, name := range namespaces {
commands.Commands = append(commands.Commands, *groups[name])
}
if ns != "" && len(commands.Commands) == 0 {
return fmt.Errorf("%w: namespace %s", ErrNotFound, ns)
}
return des.Commands(ctx, out, commands)
},
Configure: func(ctx context.Context, config *input.Definition) error { Configure: func(ctx context.Context, config *input.Definition) error {
formats := descriptor.Descriptors() formats := descriptor.Descriptors()
config. config.
SetArguments( SetArguments(
argument.New("namespace", "The namespace name"), argument.String("namespace", "The namespace name"),
). ).
SetOptions( SetOptions(
option.New(helpOptFormat, fmt.Sprintf("The output format (%s)", strings.Join(formats, ", ")), option.String(helpOptFormat, fmt.Sprintf("The output format (%s)", strings.Join(formats, ", ")),
option.Required, option.Required,
option.Default(formats[0]), option.Default(formats[0]),
option.Valid( option.Valid(
@ -110,3 +54,69 @@ You can also output the information in other formats by using the <comment>--for
}, },
} }
} }
//nolint:cyclop
func executeList(ctx context.Context, in input.Input, out output.Output) error {
ns := in.Argument(ctx, "namespace").String()
format := in.Option(ctx, helpOptFormat).String()
des, err := descriptor.Find(format)
if err != nil {
return fmt.Errorf("find descriptor: %w", err)
}
cmds := Commands()
commands := descriptor.Commands{
Namespace: ns,
Definition: Default(input.NewDefinition()),
}
groups := make(map[string]*descriptor.NSCommand)
namespaces := make([]string, 0, len(cmds))
empty := descriptor.NSCommand{}
for _, name := range cmds {
if ns != "" && !strings.HasPrefix(name, ns+":") {
continue
}
cmd, _ := Find(name)
if cmd.Hidden {
continue
}
gn := strings.SplitN(name, ":", 2)
if len(gn) != defaultLenNamespace {
empty.Append(cmd.Name, cmd.Description)
continue
}
if _, ok := groups[gn[0]]; !ok {
groups[gn[0]] = &descriptor.NSCommand{
Name: gn[0],
}
namespaces = append(namespaces, gn[0])
}
groups[gn[0]].Append(name, cmd.Description)
}
if len(empty.Commands) > 0 {
commands.Commands = append(commands.Commands, empty)
}
for _, name := range namespaces {
commands.Commands = append(commands.Commands, *groups[name])
}
if ns != "" && len(commands.Commands) == 0 {
return fmt.Errorf("%w: namespace %s", ErrNotFound, ns)
}
if err := des.Commands(ctx, out, commands); err != nil {
return fmt.Errorf("descriptor:%w", err)
}
return nil
}

2
output/descriptor/descriptor.go

@ -11,7 +11,7 @@ import (
var ErrDescriptorNotFound = errors.New("descriptor not found") var ErrDescriptorNotFound = errors.New("descriptor not found")
//nolint: gochecknoglobals //nolint:gochecknoglobals
var ( var (
descriptors = map[string]Descriptor{ descriptors = map[string]Descriptor{
"txt": &txt{}, "txt": &txt{},

96
output/descriptor/txt.go

@ -10,8 +10,8 @@ import (
"time" "time"
"gitoa.ru/go-4devs/console/input" "gitoa.ru/go-4devs/console/input"
"gitoa.ru/go-4devs/console/input/flag"
"gitoa.ru/go-4devs/console/input/value" "gitoa.ru/go-4devs/console/input/value"
"gitoa.ru/go-4devs/console/input/value/flag"
"gitoa.ru/go-4devs/console/output" "gitoa.ru/go-4devs/console/output"
) )
@ -43,7 +43,7 @@ var (
{{- help . }} {{- help . }}
`)) `))
txtListTempkate = template.Must(template.New("txt_list"). txtListTemplate = template.Must(template.New("txt_list").
Funcs(txtFunc). Funcs(txtFunc).
Parse(`<comment>Usage:</comment> Parse(`<comment>Usage:</comment>
command [options] [arguments] command [options] [arguments]
@ -58,7 +58,7 @@ func (t *txt) Command(ctx context.Context, out output.Output, cmd Command) error
var tpl bytes.Buffer var tpl bytes.Buffer
if err := txtHelpTemplate.Execute(&tpl, cmd); err != nil { if err := txtHelpTemplate.Execute(&tpl, cmd); err != nil {
return err return fmt.Errorf("execute txt help tpl:%w", err)
} }
out.Println(ctx, tpl.String()) out.Println(ctx, tpl.String())
@ -69,8 +69,8 @@ func (t *txt) Command(ctx context.Context, out output.Output, cmd Command) error
func (t *txt) Commands(ctx context.Context, out output.Output, cmds Commands) error { func (t *txt) Commands(ctx context.Context, out output.Output, cmds Commands) error {
var buf bytes.Buffer var buf bytes.Buffer
if err := txtListTempkate.Execute(&buf, cmds); err != nil { if err := txtListTemplate.Execute(&buf, cmds); err != nil {
return err return fmt.Errorf("execute txt list tpl:%w", err)
} }
out.Println(ctx, buf.String()) out.Println(ctx, buf.String())
@ -78,36 +78,37 @@ func (t *txt) Commands(ctx context.Context, out output.Output, cmds Commands) er
return nil return nil
} }
func txtDefaultArray(v value.Value, f flag.Flag) string { //nolint:cyclop
st := v.Strings() func txtDefaultArray(val value.Value, fl flag.Flag) string {
st := val.Strings()
switch { switch {
case f.IsInt(): case fl.IsInt():
for _, i := range v.Ints() { for _, i := range val.Ints() {
st = append(st, strconv.Itoa(i)) st = append(st, strconv.Itoa(i))
} }
case f.IsInt64(): case fl.IsInt64():
for _, i := range v.Int64s() { for _, i := range val.Int64s() {
st = append(st, strconv.FormatInt(i, 10)) st = append(st, strconv.FormatInt(i, 10))
} }
case f.IsUint(): case fl.IsUint():
for _, u := range v.Uints() { for _, u := range val.Uints() {
st = append(st, strconv.FormatUint(uint64(u), 10)) st = append(st, strconv.FormatUint(uint64(u), 10))
} }
case f.IsUint64(): case fl.IsUint64():
for _, u := range v.Uint64s() { for _, u := range val.Uint64s() {
st = append(st, strconv.FormatUint(u, 10)) st = append(st, strconv.FormatUint(u, 10))
} }
case f.IsFloat64(): case fl.IsFloat64():
for _, f := range v.Float64s() { for _, f := range val.Float64s() {
st = append(st, strconv.FormatFloat(f, 'g', -1, 64)) st = append(st, strconv.FormatFloat(f, 'g', -1, 64))
} }
case f.IsDuration(): case fl.IsDuration():
for _, d := range v.Durations() { for _, d := range val.Durations() {
st = append(st, d.String()) st = append(st, d.String())
} }
case f.IsTime(): case fl.IsTime():
for _, d := range v.Times() { for _, d := range val.Times() {
st = append(st, d.Format(time.RFC3339)) st = append(st, d.Format(time.RFC3339))
} }
} }
@ -115,32 +116,33 @@ func txtDefaultArray(v value.Value, f flag.Flag) string {
return strings.Join(st, ",") return strings.Join(st, ",")
} }
func txtDefault(v value.Value, f flag.Flag) []byte { //nolint:cyclop
func txtDefault(val value.Value, fl flag.Flag) []byte {
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteString("<comment> [default: ") buf.WriteString("<comment> [default: ")
switch { switch {
case f.IsArray(): case fl.IsArray():
buf.WriteString(txtDefaultArray(v, f)) buf.WriteString(txtDefaultArray(val, fl))
case f.IsInt(): case fl.IsInt():
buf.WriteString(strconv.Itoa(v.Int())) buf.WriteString(strconv.Itoa(val.Int()))
case f.IsInt64(): case fl.IsInt64():
buf.WriteString(strconv.FormatInt(v.Int64(), 10)) buf.WriteString(strconv.FormatInt(val.Int64(), 10))
case f.IsUint(): case fl.IsUint():
buf.WriteString(strconv.FormatUint(uint64(v.Uint()), 10)) buf.WriteString(strconv.FormatUint(uint64(val.Uint()), 10))
case f.IsUint64(): case fl.IsUint64():
buf.WriteString(strconv.FormatUint(v.Uint64(), 10)) buf.WriteString(strconv.FormatUint(val.Uint64(), 10))
case f.IsFloat64(): case fl.IsFloat64():
buf.WriteString(strconv.FormatFloat(v.Float64(), 'g', -1, 64)) buf.WriteString(strconv.FormatFloat(val.Float64(), 'g', -1, 64))
case f.IsDuration(): case fl.IsDuration():
buf.WriteString(v.Duration().String()) buf.WriteString(val.Duration().String())
case f.IsTime(): case fl.IsTime():
buf.WriteString(v.Time().Format(time.RFC3339)) buf.WriteString(val.Time().Format(time.RFC3339))
case f.IsAny(): case fl.IsAny():
buf.WriteString(fmt.Sprint(v.Any())) buf.WriteString(fmt.Sprint(val.Any()))
default: default:
buf.WriteString(v.String()) buf.WriteString(val.String())
} }
buf.WriteString("]</comment>") buf.WriteString("]</comment>")
@ -214,7 +216,7 @@ func txtDefinitionOption(maxLen int, def *input.Definition) string {
if opt.HasShort() { if opt.HasShort() {
op.WriteString("-") op.WriteString("-")
op.WriteString(opt.Short) op.WriteString(opt.Alias)
op.WriteString(", ") op.WriteString(", ")
} else { } else {
op.WriteString(" ") op.WriteString(" ")
@ -354,18 +356,18 @@ func totalWidth(def *input.Definition) int {
for _, name := range def.Options() { for _, name := range def.Options() {
opt, _ := def.Option(name) opt, _ := def.Option(name)
l := len(opt.Name) + 6 current := len(opt.Name) + 6
if !opt.IsBool() { if !opt.IsBool() {
l = l*2 + 1 current = current*2 + 1
} }
if opt.HasDefault() { if opt.HasDefault() {
l += 2 current += 2
} }
if l > max { if current > max {
max = l max = current
} }
} }

7
output/formatter/formatter.go

@ -8,7 +8,6 @@ import (
"gitoa.ru/go-4devs/console/output/style" "gitoa.ru/go-4devs/console/output/style"
) )
//nolint: gochecknoglobals
var re = regexp.MustCompile(`<(([a-z][^<>]+)|/([a-z][^<>]+)?)>`) var re = regexp.MustCompile(`<(([a-z][^<>]+)|/([a-z][^<>]+)?)>`)
func WithStyle(styles func(string) (style.Style, error)) func(*Formatter) { func WithStyle(styles func(string) (style.Style, error)) func(*Formatter) {
@ -18,15 +17,15 @@ func WithStyle(styles func(string) (style.Style, error)) func(*Formatter) {
} }
func New(opts ...func(*Formatter)) *Formatter { func New(opts ...func(*Formatter)) *Formatter {
f := &Formatter{ formatter := &Formatter{
styles: style.Find, styles: style.Find,
} }
for _, opt := range opts { for _, opt := range opts {
opt(f) opt(formatter)
} }
return f return formatter
} }
type Formatter struct { type Formatter struct {

2
output/formatter/formatter_test.go

@ -8,6 +8,8 @@ import (
) )
func TestFormatter(t *testing.T) { func TestFormatter(t *testing.T) {
t.Parallel()
ctx := context.Background() ctx := context.Background()
formatter := formatter.New() formatter := formatter.New()

2
output/formatter/none_test.go

@ -8,6 +8,8 @@ import (
) )
func TestNone(t *testing.T) { func TestNone(t *testing.T) {
t.Parallel()
ctx := context.Background() ctx := context.Background()
none := formatter.None() none := formatter.None()

12
output/output.go

@ -63,15 +63,13 @@ func (o Output) Write(b []byte) (int, error) {
} }
func (o Output) Writer(ctx context.Context, verb verbosity.Verbosity) io.Writer { func (o Output) Writer(ctx context.Context, verb verbosity.Verbosity) io.Writer {
return verbosityWriter{ctx, o, verb} return verbosityWriter(func(b []byte) (int, error) {
return o(ctx, verb, string(b))
})
} }
type verbosityWriter struct { type verbosityWriter func(b []byte) (int, error)
ctx context.Context
out Output
verb verbosity.Verbosity
}
func (w verbosityWriter) Write(b []byte) (int, error) { func (w verbosityWriter) Write(b []byte) (int, error) {
return w.out(w.ctx, w.verb, string(b)) return w(b)
} }

6
output/style/color.go

@ -28,13 +28,13 @@ const (
type Option string type Option string
func (o Option) Apply(action int) string { func (o Option) Apply(action int) string {
v := string(o) out := string(o)
switch action { switch action {
case ActionSet: case ActionSet:
return v[0:1] return out[0:1]
case ActionUnset: case ActionUnset:
return v[1:] return out[1:]
} }
return "" return ""

2
output/style/style.go

@ -7,7 +7,7 @@ import (
"sync" "sync"
) )
//nolint: gochecknoglobals //nolint:gochecknoglobals
var ( var (
styles = map[string]Style{ styles = map[string]Style{
"error": {Foreground: White, Background: Red}, "error": {Foreground: White, Background: Red},

7
output/writer.go

@ -41,6 +41,11 @@ func FormatString(_ verbosity.Verbosity, msg string, kv ...label.KeyValue) strin
func New(w io.Writer, format func(verb verbosity.Verbosity, msg string, kv ...label.KeyValue) string) Output { func New(w io.Writer, format func(verb verbosity.Verbosity, msg string, kv ...label.KeyValue) string) Output {
return func(ctx context.Context, verb verbosity.Verbosity, msg string, kv ...label.KeyValue) (int, error) { return func(ctx context.Context, verb verbosity.Verbosity, msg string, kv ...label.KeyValue) (int, error) {
return fmt.Fprint(w, format(verb, msg, kv...)) out, err := fmt.Fprint(w, format(verb, msg, kv...))
if err != nil {
return 0, fmt.Errorf("writer fprint:%w", err)
}
return out, nil
} }
} }

2
output/writer_test.go

@ -10,6 +10,8 @@ import (
) )
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
t.Parallel()
ctx := context.Background() ctx := context.Background()
buf := bytes.Buffer{} buf := bytes.Buffer{}
wr := output.New(&buf, output.FormatString) wr := output.New(&buf, output.FormatString)

16
register.go

@ -20,31 +20,31 @@ var (
ErrCommandDuplicate = errors.New("console: duplicate command") ErrCommandDuplicate = errors.New("console: duplicate command")
) )
//nolint: gochecknoglobals //nolint:gochecknoglobals
var ( var (
commandsMu sync.RWMutex commandsMu sync.RWMutex
commands = make(map[string]*Command) commands = make(map[string]*Command)
findCommand = regexp.MustCompile("([^:]+|)") findCommand = regexp.MustCompile("([^:]+|)")
) )
type ErrorAlternatives struct { type AlternativesError struct {
alt []string alt []string
err error err error
} }
func (e ErrorAlternatives) Error() string { func (e AlternativesError) Error() string {
return fmt.Sprintf("%s, alternatives: [%s]", e.err, strings.Join(e.alt, ",")) return fmt.Sprintf("%s, alternatives: [%s]", e.err, strings.Join(e.alt, ","))
} }
func (e ErrorAlternatives) Is(err error) bool { func (e AlternativesError) Is(err error) bool {
return errors.Is(e.err, err) return errors.Is(e.err, err)
} }
func (e ErrorAlternatives) Unwrap() error { func (e AlternativesError) Unwrap() error {
return e.err return e.err
} }
func (e ErrorAlternatives) Alternatives() []string { func (e AlternativesError) Alternatives() []string {
return e.alt return e.alt
} }
@ -115,7 +115,7 @@ func Find(name string) (*Command, error) {
cmdRegexp, err := regexp.Compile("^" + nameRegexp + "$") cmdRegexp, err := regexp.Compile("^" + nameRegexp + "$")
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("find by regexp:%w", err)
} }
for name := range commands { for name := range commands {
@ -134,7 +134,7 @@ func Find(name string) (*Command, error) {
names[i] = findCommands[i].Name names[i] = findCommands[i].Name
} }
return nil, ErrorAlternatives{alt: names, err: ErrNotFound} return nil, AlternativesError{alt: names, err: ErrNotFound}
} }
return nil, ErrNotFound return nil, ErrNotFound

6
register_test.go

@ -7,6 +7,8 @@ import (
) )
func TestFind(t *testing.T) { func TestFind(t *testing.T) {
t.Parallel()
cases := map[string]string{ cases := map[string]string{
"fdevs:console:test": "fdevs:console:test", "fdevs:console:test": "fdevs:console:test",
"fd:c:t": "fdevs:console:test", "fd:c:t": "fdevs:console:test",
@ -18,13 +20,13 @@ func TestFind(t *testing.T) {
for name, ex := range cases { for name, ex := range cases {
res, err := console.Find(name) res, err := console.Find(name)
if err != nil { if err != nil {
t.Errorf("expect <nil> err, got:%s", err) t.Errorf("%v expect <nil> err, got:%s", name, err)
continue continue
} }
if res.Name != ex { if res.Name != ex {
t.Errorf("expect: %s, got: %s", ex, res) t.Errorf("%v expect: %s, got: %s", name, ex, res)
} }
} }
} }

Loading…
Cancel
Save