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:
- name: golangci-lint
image: golangci/golangci-lint:v1.26
image: golangci/golangci-lint:v1.49
volumes:
- name: deps
path: /go/src/mod

25
.golangci.yml

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

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.
func WithSkipArgs(l int) func(*App) {
return func(a *App) {
a.skipArgv = l
}
return WithInput(input.NewArgs(l))
}
// WithExit sets exit callback by default os.Exit.
@ -39,44 +37,25 @@ func WithExit(f func(int)) func(*App) {
// New creates and configure new console app.
func New(opts ...func(*App)) *App {
a := &App{
app := &App{
out: output.Stdout(),
exit: os.Exit,
in: input.NewArgs(0),
}
for _, opt := range opts {
opt(a)
}
if a.in == nil {
skip := 2
switch {
case a.skipArgv > 0 && len(os.Args) > a.skipArgv:
skip = a.skipArgv
case a.skipArgv > 0:
skip = len(os.Args)
case len(os.Args) == 1:
skip = 1
case len(os.Args) > 1 && os.Args[1][0] == '-':
skip = 1
}
a.in = &input.Argv{
Args: os.Args[skip:],
}
opt(app)
}
return a
return app
}
// App is collection of command and configure env.
type App struct {
cmds []*Command
out output.Output
in input.Input
skipArgv int
exit func(int)
cmds []*Command
out output.Output
in input.Input
exit func(int)
}
// Add add or replace command.

18
app_test.go

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

36
command.go

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

17
command_test.go

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

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

45
doc.go

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

9
example/pkg/command/args.go

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

3
example/pkg/command/create_user.go

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

2
example/pkg/command/create_user_test.go

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

2
example/pkg/command/hello.go

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

4
example/pkg/command/long.go

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

6
go.mod

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

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

68
input/argument/argument.go

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

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

10
input/array.go

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

3
input/chain.go

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

37
input/definition.go

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

44
input/errs/error.go

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

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

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

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

65
input/map.go

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

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

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 {
return func(in value.Value) error {
v := in.String()
val := in.String()
for _, e := range enum {
if e == v {
if e == val {
return nil
}
}
return NewError(ErrInvalid, v, enum)
return NewError(ErrInvalid, val, enum)
}
}

2
input/validator/enum_test.go

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

50
input/validator/not_blank.go

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

6
input/validator/not_blank_test.go

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

4
input/validator/valid_test.go

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

134
input/value/any.go

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

146
input/value/bool.go

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

126
input/value/duration.go

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

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

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

127
input/value/int64.go

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

185
input/value/read.go

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

171
input/value/string.go

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

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

127
input/value/uint.go

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

127
input/value/uint64.go

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

123
input/value/value.go

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

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

96
output/descriptor/txt.go

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

7
output/formatter/formatter.go

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

2
output/formatter/formatter_test.go

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

2
output/formatter/none_test.go

@ -8,6 +8,8 @@ import (
)
func TestNone(t *testing.T) {
t.Parallel()
ctx := context.Background()
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 {
return verbosityWriter{ctx, o, verb}
return verbosityWriter(func(b []byte) (int, error) {
return o(ctx, verb, string(b))
})
}
type verbosityWriter struct {
ctx context.Context
out Output
verb verbosity.Verbosity
}
type verbosityWriter func(b []byte) (int, error)
func (w verbosityWriter) Write(b []byte) (int, error) {
return w.out(w.ctx, w.verb, string(b))
return w(b)
}

6
output/style/color.go

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

2
output/style/style.go

@ -7,7 +7,7 @@ import (
"sync"
)
//nolint: gochecknoglobals
//nolint:gochecknoglobals
var (
styles = map[string]Style{
"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 {
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) {
t.Parallel()
ctx := context.Background()
buf := bytes.Buffer{}
wr := output.New(&buf, output.FormatString)

16
register.go

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

6
register_test.go

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

Loading…
Cancel
Save