2 Commits

Author SHA1 Message Date
4fdeb73e8a add value vithh error (#1)
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Co-authored-by: andrey1s <andrey@4devs.pro>
Reviewed-on: #1
Co-authored-by: andrey <andrey@4devs.io>
Co-committed-by: andrey <andrey@4devs.io>
2022-09-18 21:37:25 +03:00
andrey1s
2657672288 update verbose info level (#2)
All checks were successful
continuous-integration/drone/push Build is passing
update verbose info level

Co-authored-by: andrey1s <andrey.simfi@list.ru>
2021-03-03 20:59:34 +03:00
74 changed files with 2591 additions and 942 deletions

View File

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

View File

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

43
app.go
View File

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

View File

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

View File

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

View File

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

View File

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

45
doc.go
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

6
go.mod
View File

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

21
go.sum
View File

@@ -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
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,44 +1,148 @@
package value package value
import ( import (
"strconv" "fmt"
"time"
"gitoa.ru/go-4devs/console/input/value/flag"
) )
type Bool struct { var (
empty _ ParseValue = Bool(false)
Val []bool _ SliceValue = Bools{}
Flag flag.Flag )
func NewBools(in []bool) Slice {
return Slice{SliceValue: Bools(in)}
} }
func (b *Bool) Append(in string) error { type Bools []bool
v, err := strconv.ParseBool(in)
if err != nil { func (b Bools) Any() interface{} {
return err 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)
} }
b.Val = append(b.Val, v) *v = b
return nil return nil
} }
func (b *Bool) Bool() bool { func (b Bools) Strings() []string {
if !b.Flag.IsArray() && len(b.Val) == 1 { return nil
return b.Val[0] }
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 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)
} }
return false *v = bool(b)
return nil
} }
func (b *Bool) Bools() []bool { func (b Bool) ParseString() (string, error) {
return b.Val return fmt.Sprintf("%v", b), nil
} }
func (b *Bool) Any() interface{} { func (b Bool) ParseInt() (int, error) {
if b.Flag.IsArray() { if b {
return b.Bools() return 1, nil
} }
return b.Bool() 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 0, nil
}
func (b Bool) ParseUint64() (uint64, error) {
if b {
return 1, nil
}
return 0, nil
}
func (b Bool) ParseFloat64() (float64, error) {
if b {
return 1, nil
}
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)
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,44 +1,129 @@
package value package value
import ( import (
"fmt"
"strconv" "strconv"
"time"
"gitoa.ru/go-4devs/console/input/value/flag"
) )
type Int64 struct { var (
empty _ ParseValue = Int64(0)
Val []int64 _ SliceValue = Int64s{}
Flag flag.Flag )
func NewInt64s(in []int64) Slice {
return Slice{SliceValue: Int64s(in)}
} }
func (i *Int64) Int64() int64 { type Int64s []int64
if !i.Flag.IsArray() && len(i.Val) == 1 {
return i.Val[0] func (i Int64s) Any() interface{} {
return i.Int64s()
}
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
}
return i.Int64()
} func (i Int64s) Int64s() []int64 {
out := make([]int64, len(i))
func (i *Int64) Append(in string) error { copy(out, i)
v, err := strconv.ParseInt(in, 10, 64)
if err != nil { return out
return err }
}
func (i Int64s) Uints() []uint {
i.Val = append(i.Val, v) 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
}
func (i Int64) ParseTime() (time.Time, error) {
return time.Unix(0, int64(i)), nil
}
func (i Int64) Unmarshal(val interface{}) error {
v, ok := val.(*int64)
if !ok {
return fmt.Errorf("%w: expect *int64", ErrWrongType)
}
*v = int64(i)
return nil return nil
} }

View File

@@ -1,20 +1,189 @@
package value package value
import ( import (
"errors" "fmt"
) "time"
var _ Append = (*Read)(nil) "gitoa.ru/go-4devs/console/input/errs"
)
var ( var (
ErrAppendRead = errors.New("invalid append data to read value") _ Value = Read{}
ErrAppendEmpty = errors.New("invalid apped data to empty value") _ Value = Slice{}
) )
var ErrWrongType = errs.ErrWrongType
type Read struct { type Read struct {
Value ParseValue
} }
func (r *Read) Append(string) error { func (r Read) String() string {
return ErrAppendRead 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 (s Slice) ParseTime() (time.Time, error) {
return time.Time{}, s.wrongType()
} }

View File

@@ -1,39 +1,172 @@
package value package value
import "gitoa.ru/go-4devs/console/input/value/flag" import (
"fmt"
"strconv"
"time"
)
type String struct { var (
empty _ ParseValue = (String)("")
Val []string _ SliceValue = (Strings)(nil)
Flag flag.Flag )
func NewStrings(in []string) Slice {
return Slice{SliceValue: Strings(in)}
} }
func (s *String) Append(in string) error { type Strings []string
s.Val = append(s.Val, in)
func (s Strings) Unmarshal(in interface{}) error {
val, ok := in.(*[]string)
if !ok {
return fmt.Errorf("%w: expect *[]string", ErrWrongType)
}
*val = s
return nil return nil
} }
func (s *String) String() string { func (s Strings) Any() interface{} {
if s.Flag.IsArray() { return s.Strings()
return ""
}
if len(s.Val) == 1 {
return s.Val[0]
}
return ""
} }
func (s *String) Strings() []string { func (s Strings) Strings() []string {
return s.Val out := make([]string, len(s))
copy(out, s)
return out
} }
func (s *String) Any() interface{} { func (s Strings) Ints() []int {
if s.Flag.IsArray() { return nil
return s.Strings() }
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 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)
} }
return s.String() *v = string(s)
return nil
}
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)
}
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 uout, nil
}
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) 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 v, nil
} }

View File

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

View File

@@ -1,44 +1,130 @@
package value package value
import ( import (
"fmt"
"time" "time"
"gitoa.ru/go-4devs/console/input/value/flag"
) )
type Time struct { var (
empty _ ParseValue = Time{time.Now()}
Val []time.Time _ SliceValue = (Times)(nil)
Flag flag.Flag )
func NewTimes(in []time.Time) Slice {
return Slice{SliceValue: Times(in)}
} }
func (t *Time) Append(in string) error { type Times []time.Time
v, err := time.Parse(time.RFC3339, in)
if err != nil { func (t Times) Any() interface{} {
return err return t.Times()
}
func (t Times) Unmarshal(val interface{}) error {
res, ok := val.(*[]time.Time)
if !ok {
return fmt.Errorf("%w: expect *[]time.Time", ErrWrongType)
} }
t.Val = append(t.Val, v) *res = t
return nil return nil
} }
func (t *Time) Time() time.Time { func (t Times) Strings() []string {
if !t.Flag.IsArray() && len(t.Val) == 1 { return nil
return t.Val[0] }
func (t Times) Ints() []int {
return nil
}
func (t Times) Int64s() []int64 {
return nil
}
func (t Times) Uints() []uint {
return nil
}
func (t Times) Uint64s() []uint64 {
return nil
}
func (t Times) Float64s() []float64 {
return nil
}
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 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 time.Time{} *res = t.Time
return nil
} }
func (t *Time) Times() []time.Time { func (t Time) Any() interface{} {
return t.Val return t.Time
}
func (t *Time) Amy() interface{} {
if t.Flag.IsArray() {
return t.Times()
}
return t.Time()
} }

View File

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

View File

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

View File

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

10
input/variable/argtype.go Normal file
View File

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

View File

@@ -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 Normal file
View File

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

View File

@@ -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 Normal file
View File

@@ -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 Normal file
View File

@@ -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 Normal file
View File

@@ -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 Normal file
View File

@@ -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 Normal file
View File

@@ -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 Normal file
View File

@@ -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 Normal file
View File

@@ -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 Normal file
View File

@@ -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 Normal file
View File

@@ -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
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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