Compare commits
17 Commits
v0.0.2
...
517895e319
| Author | SHA1 | Date | |
|---|---|---|---|
| 517895e319 | |||
| e48a9b29d8 | |||
| 71f774aa5a | |||
| c6a6300edf | |||
| c9b4f4cfd0 | |||
| d2ef3f7d0a | |||
| 3b32bb2759 | |||
| ad5cf18535 | |||
|
|
1151e7c3ad | ||
| 44d8837dbc | |||
|
|
0b6a6ee99b | ||
| 7771ff495d | |||
|
|
662cbdb510 | ||
| 65a754363f | |||
| ef4d5d126a | |||
|
|
4ddc526abf | ||
| 4fdeb73e8a |
24
.drone.yml
24
.drone.yml
@@ -1,24 +0,0 @@
|
||||
kind: pipeline
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: golangci-lint
|
||||
image: golangci/golangci-lint:v1.26
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /go/src/mod
|
||||
commands:
|
||||
- golangci-lint run --timeout 5m
|
||||
|
||||
- name: test
|
||||
image: golang
|
||||
volumes:
|
||||
- name: deps
|
||||
path: /go/src/mod
|
||||
commands:
|
||||
- go test ./...
|
||||
|
||||
volumes:
|
||||
- name: deps
|
||||
temp: {}
|
||||
|
||||
34
.gitea/workflows/example.yml
Normal file
34
.gitea/workflows/example.yml
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Go Action
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
paths:
|
||||
- 'example/**'
|
||||
|
||||
jobs:
|
||||
goaction:
|
||||
runs-on: ubuntu-latest # Use a Gitea Actions runner label
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4 # Action to clone the repo
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5 # Action to install a specific Go version
|
||||
with:
|
||||
go-version: '1.25.5' # Specify your required Go version
|
||||
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v8 # Use the golangci-lint action
|
||||
with:
|
||||
version: v2.7.2 # Specify the linter version
|
||||
# Optional: additional arguments
|
||||
args: --verbose
|
||||
working-directory: example
|
||||
|
||||
- name: Run go test
|
||||
run: go test ./...
|
||||
working-directory: example
|
||||
|
||||
32
.gitea/workflows/goaction.yml
Normal file
32
.gitea/workflows/goaction.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Go Action
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'example/**'
|
||||
|
||||
jobs:
|
||||
goaction:
|
||||
runs-on: ubuntu-latest # Use a Gitea Actions runner label
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4 # Action to clone the repo
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5 # Action to install a specific Go version
|
||||
with:
|
||||
go-version: '1.25.5' # Specify your required Go version
|
||||
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v8 # Use the golangci-lint action
|
||||
with:
|
||||
version: v2.7.2 # Specify the linter version
|
||||
# Optional: additional arguments
|
||||
args: --verbose
|
||||
|
||||
- name: Run go test
|
||||
run: go test ./...
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
linters-settings:
|
||||
version: "2"
|
||||
linters:
|
||||
default: all
|
||||
disable:
|
||||
- exhaustruct
|
||||
- gomoddirectives
|
||||
- ireturn
|
||||
- wsl
|
||||
- noinlineerr
|
||||
- depguard
|
||||
settings:
|
||||
funcorder:
|
||||
constructor: false
|
||||
dupl:
|
||||
threshold: 100
|
||||
funlen:
|
||||
@@ -9,28 +21,44 @@ linters-settings:
|
||||
min-occurrences: 2
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
golint:
|
||||
min-confidence: 0
|
||||
gomnd:
|
||||
settings:
|
||||
mnd:
|
||||
# don't include the "operation" and "assign"
|
||||
checks: argument,case,condition,return
|
||||
govet:
|
||||
check-shadowing: true
|
||||
lll:
|
||||
line-length: 140
|
||||
maligned:
|
||||
suggest-new: true
|
||||
misspell:
|
||||
locale: US
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
|
||||
issues:
|
||||
# Excluding configuration per-path, per-linter, per-text and per-source
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- gomnd
|
||||
locale: US
|
||||
staticcheck:
|
||||
checks:
|
||||
- all
|
||||
- -SA1030
|
||||
varnamelen:
|
||||
min-name-length: 2
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
rules:
|
||||
- linters:
|
||||
- mnd
|
||||
- varnamelen
|
||||
path: _test\.go
|
||||
- linters:
|
||||
- dupl
|
||||
path: input/variable
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
formatters:
|
||||
enable:
|
||||
- gci
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
||||
39
README.md
39
README.md
@@ -1,7 +1,6 @@
|
||||
# Console
|
||||
|
||||
|
||||
[](https://drone.gitoa.ru/go-4devs/console)
|
||||

|
||||
[](https://goreportcard.com/report/gitoa.ru/go-4devs/console)
|
||||
[](http://godoc.org/gitoa.ru/go-4devs/console)
|
||||
|
||||
@@ -17,14 +16,14 @@ import (
|
||||
"context"
|
||||
|
||||
"gitoa.ru/go-4devs/console"
|
||||
"gitoa.ru/go-4devs/console/input"
|
||||
"gitoa.ru/go-4devs/console/output"
|
||||
"gitoa.ru/go-4devs/config"
|
||||
)
|
||||
|
||||
func CreateUser() *console.Command {
|
||||
return &console.Command{
|
||||
Name: "app:create-user",
|
||||
Execute: func(ctx context.Context, in input.Input, out output.Output) error {
|
||||
Execute: func(ctx context.Context, in config.Provider, out output.Output) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -49,12 +48,14 @@ func CreateUser() *console.Command {
|
||||
func CreateUser(required bool) *console.Command {
|
||||
return &console.Command{
|
||||
//....
|
||||
Configure: func(ctx context.Context, cfg *input.Definition) error {
|
||||
var opts []func(*input.Argument)
|
||||
Configure: func(ctx context.Context, cfg config.Definition) error {
|
||||
var opts []func(*arg.Option)
|
||||
if required {
|
||||
opts = append(opts, argument.Required)
|
||||
opts = append(opts, arg.Required)
|
||||
}
|
||||
cfg.SetArgument("password", "User password", opts...)
|
||||
cfg.Add(
|
||||
arg.String("password", "User password", opts...)
|
||||
)
|
||||
|
||||
return nil
|
||||
},
|
||||
@@ -98,7 +99,7 @@ The Execute field has access to the output stream to write messages to the conso
|
||||
func CreateUser(required bool) *console.Command {
|
||||
return &console.Command{
|
||||
// ....
|
||||
Execute: func(ctx context.Context, in input.Input, out output.Output) error {
|
||||
Execute: func(ctx context.Context, in config.Provider, out output.Output) error {
|
||||
// outputs a message followed by a "\n"
|
||||
out.Println(ctx, "User Creator")
|
||||
out.Println(ctx, "Whoa!")
|
||||
@@ -128,21 +129,23 @@ Use input options or arguments to pass information to the command:
|
||||
```go
|
||||
func CreateUser(required bool) *console.Command {
|
||||
return &console.Command{
|
||||
Configure: func(ctx context.Context, cfg *input.Definition) error {
|
||||
Configure: func(ctx context.Context, cfg config.Definition) error {
|
||||
var opts []func(*input.Argument)
|
||||
if required {
|
||||
opts = append(opts, argument.Required)
|
||||
}
|
||||
cfg.
|
||||
SetArgument("username", "The username of the user.", argument.Required).
|
||||
SetArgument("password", "User password", opts...)
|
||||
cfg.Add(
|
||||
arg.String("username", "The username of the user.", arg.Required),
|
||||
arg.String("password", "User password", opts...),
|
||||
)
|
||||
|
||||
return nil
|
||||
},
|
||||
Execute: func(ctx context.Context, in input.Input, out output.Output) error {
|
||||
Execute: func(ctx context.Context, in config.Provider, out output.Output) error {
|
||||
// outputs a message followed by a "\n"
|
||||
username, _ := in.Value(ctx, "username")
|
||||
out.Println(ctx, "User Creator")
|
||||
out.Println(ctx, "Username: ", in.Argument(ctx, "username").String())
|
||||
out.Println(ctx, "Username: ", username.String())
|
||||
|
||||
return nil
|
||||
},
|
||||
@@ -170,14 +173,14 @@ import (
|
||||
|
||||
"gitoa.ru/go-4devs/console"
|
||||
"gitoa.ru/go-4devs/console/example/pkg/command"
|
||||
"gitoa.ru/go-4devs/console/input/array"
|
||||
"gitoa.ru/go-4devs/config/provider/memory"
|
||||
"gitoa.ru/go-4devs/console/output"
|
||||
)
|
||||
|
||||
func TestCreateUser(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
in := input.Array{}
|
||||
in.SetArgument("username","andrey")
|
||||
in := memory.Map{}
|
||||
in.Set("andrey","username")
|
||||
buf := bytes.Buffer{}
|
||||
out := output.Buffer(&buf)
|
||||
|
||||
|
||||
77
app.go
77
app.go
@@ -4,8 +4,11 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input"
|
||||
"gitoa.ru/go-4devs/console/input/value"
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/provider/arg"
|
||||
"gitoa.ru/go-4devs/config/provider/chain"
|
||||
"gitoa.ru/go-4devs/config/provider/memory"
|
||||
"gitoa.ru/go-4devs/config/value"
|
||||
"gitoa.ru/go-4devs/console/output"
|
||||
)
|
||||
|
||||
@@ -17,7 +20,7 @@ func WithOutput(out output.Output) func(*App) {
|
||||
}
|
||||
|
||||
// WithInput sets input, by default creates inpur by os.Args.
|
||||
func WithInput(in input.Input) func(*App) {
|
||||
func WithInput(in config.BindProvider) func(*App) {
|
||||
return func(a *App) {
|
||||
a.in = in
|
||||
}
|
||||
@@ -25,9 +28,7 @@ func WithInput(in input.Input) func(*App) {
|
||||
|
||||
// WithSkipArgs sets how many arguments are passed. For example, you don't need to pass the name of a single command.
|
||||
func WithSkipArgs(l int) func(*App) {
|
||||
return func(a *App) {
|
||||
a.skipArgv = l
|
||||
}
|
||||
return WithInput(chain.New(arg.New(arg.WithArgs(os.Args[resolveSkip(l):])), &memory.Default{}))
|
||||
}
|
||||
|
||||
// WithExit sets exit callback by default os.Exit.
|
||||
@@ -39,44 +40,25 @@ func WithExit(f func(int)) func(*App) {
|
||||
|
||||
// New creates and configure new console app.
|
||||
func New(opts ...func(*App)) *App {
|
||||
a := &App{
|
||||
app := &App{
|
||||
out: output.Stdout(),
|
||||
exit: os.Exit,
|
||||
in: chain.New(arg.New(arg.WithArgs(os.Args[resolveSkip(0):])), &memory.Default{}),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(a)
|
||||
opt(app)
|
||||
}
|
||||
|
||||
if a.in == nil {
|
||||
skip := 2
|
||||
|
||||
switch {
|
||||
case a.skipArgv > 0 && len(os.Args) > a.skipArgv:
|
||||
skip = a.skipArgv
|
||||
case a.skipArgv > 0:
|
||||
skip = len(os.Args)
|
||||
case len(os.Args) == 1:
|
||||
skip = 1
|
||||
case len(os.Args) > 1 && os.Args[1][0] == '-':
|
||||
skip = 1
|
||||
}
|
||||
|
||||
a.in = &input.Argv{
|
||||
Args: os.Args[skip:],
|
||||
}
|
||||
}
|
||||
|
||||
return a
|
||||
return app
|
||||
}
|
||||
|
||||
// App is collection of command and configure env.
|
||||
type App struct {
|
||||
cmds []*Command
|
||||
out output.Output
|
||||
in input.Input
|
||||
skipArgv int
|
||||
exit func(int)
|
||||
cmds []*Command
|
||||
out output.Output
|
||||
in config.BindProvider
|
||||
exit func(int)
|
||||
}
|
||||
|
||||
// Add add or replace command.
|
||||
@@ -96,7 +78,8 @@ func (a *App) Execute(ctx context.Context) {
|
||||
if err != nil {
|
||||
a.printError(ctx, err)
|
||||
|
||||
if err := a.list(ctx); err != nil {
|
||||
err := a.list(ctx)
|
||||
if err != nil {
|
||||
a.printError(ctx, err)
|
||||
}
|
||||
|
||||
@@ -107,7 +90,8 @@ func (a *App) Execute(ctx context.Context) {
|
||||
}
|
||||
|
||||
func (a *App) exec(ctx context.Context, cmd *Command) {
|
||||
if err := Run(ctx, cmd, a.in, a.out); err != nil {
|
||||
err := Run(ctx, cmd, a.in, a.out)
|
||||
if err != nil {
|
||||
a.printError(ctx, err)
|
||||
a.exit(1)
|
||||
}
|
||||
@@ -131,9 +115,9 @@ func (a *App) list(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
arr := &input.Array{}
|
||||
arr.SetArgument("command_name", value.New(CommandList))
|
||||
in := input.Chain(arr, a.in)
|
||||
arr := &memory.Map{}
|
||||
arr.SetOption(value.New(CommandList), ArgumentCommandName)
|
||||
in := chain.New(arr, a.in)
|
||||
|
||||
return Run(ctx, cmd, in, a.out)
|
||||
}
|
||||
@@ -141,3 +125,20 @@ func (a *App) list(ctx context.Context) error {
|
||||
func (a *App) printError(ctx context.Context, err error) {
|
||||
ansi(ctx, a.in, a.out).Println(ctx, "<error>\n\n ", err, "\n</error>")
|
||||
}
|
||||
|
||||
func resolveSkip(in int) int {
|
||||
res := 2
|
||||
|
||||
switch {
|
||||
case in > 0 && len(os.Args) > in:
|
||||
res = in
|
||||
case in > 0:
|
||||
res = len(os.Args)
|
||||
case len(os.Args) == 1:
|
||||
res = 1
|
||||
case len(os.Args) > 1 && os.Args[1][0] == '-':
|
||||
res = 1
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
51
app_test.go
51
app_test.go
@@ -5,10 +5,9 @@ import (
|
||||
"os"
|
||||
|
||||
"gitoa.ru/go-4devs/console"
|
||||
"gitoa.ru/go-4devs/console/example/pkg/command"
|
||||
)
|
||||
|
||||
//nolint: lll
|
||||
//nolint:lll
|
||||
func ExampleNew_help() {
|
||||
ctx := context.Background()
|
||||
os.Args = []string{
|
||||
@@ -31,18 +30,21 @@ func ExampleNew_help() {
|
||||
// test:command [options] [--] [<test_argument>]
|
||||
//
|
||||
// Arguments:
|
||||
// test_argument test argument
|
||||
// test_argument test argument
|
||||
//
|
||||
// Options:
|
||||
// --duration[=DURATION] test duration with default [default: 1s]
|
||||
// --bool test bool option
|
||||
// --string[=STRING] array string (multiple values allowed)
|
||||
// -q, --quiet Do not output any message
|
||||
// -v, --verbose Increase the verbosity of messages: -v for info output, -vv for debug and -vvv for trace (multiple values allowed)
|
||||
// -h, --help Display this help message
|
||||
// -V, --version Display this application version
|
||||
// --ansi Do not ask any interactive question
|
||||
// --no-ansi Disable ANSI output
|
||||
// --string[=STRING] array string (multiple values allowed)
|
||||
// --group-bool bool
|
||||
// --group-test-string[=GROUP-TEST-STRING] test group string [default:group string default value]
|
||||
// --log-{service}-level[=LOG-{SERVICE}-LEVEL] service level [default:debug]
|
||||
// --bool test bool option
|
||||
// --duration[=DURATION] test duration with default
|
||||
// --ansi Do not ask any interactive question
|
||||
// -V, --version Display this application version
|
||||
// -h, --help Display this help message
|
||||
// -v, --verbose Increase the verbosity of messages: -v for info output, -vv for debug and -vvv for trace (multiple values allowed)
|
||||
// -q, --quiet Do not output any message
|
||||
// --no-ansi Disable ANSI output
|
||||
}
|
||||
|
||||
func ExampleNew_list() {
|
||||
@@ -56,9 +58,18 @@ func ExampleNew_list() {
|
||||
console.New(console.WithExit(func(int) {})).
|
||||
Add(
|
||||
Command(),
|
||||
command.Hello(),
|
||||
command.Args(),
|
||||
command.Namespace(),
|
||||
&console.Command{
|
||||
Name: "fdevs:console:arg",
|
||||
Description: "Understanding how Console Arguments and Options Are Handled",
|
||||
},
|
||||
&console.Command{
|
||||
Name: "fdevs:console:hello",
|
||||
Description: "example hello command",
|
||||
},
|
||||
&console.Command{
|
||||
Name: "app:start",
|
||||
Description: "example command in other namespace",
|
||||
},
|
||||
).
|
||||
Execute(ctx)
|
||||
// Output:
|
||||
@@ -66,12 +77,12 @@ func ExampleNew_list() {
|
||||
// command [options] [arguments]
|
||||
//
|
||||
// Options:
|
||||
// -q, --quiet Do not output any message
|
||||
// -v, --verbose Increase the verbosity of messages: -v for info output, -vv for debug and -vvv for trace (multiple values allowed)
|
||||
// -h, --help Display this help message
|
||||
// -V, --version Display this application version
|
||||
// --ansi Do not ask any interactive question
|
||||
// --no-ansi Disable ANSI output
|
||||
// --ansi Do not ask any interactive question
|
||||
// -V, --version Display this application version
|
||||
// -h, --help Display this help message
|
||||
// -v, --verbose Increase the verbosity of messages: -v for info output, -vv for debug and -vvv for trace (multiple values allowed)
|
||||
// -q, --quiet Do not output any message
|
||||
//
|
||||
// Available commands:
|
||||
// help Displays help for a command
|
||||
|
||||
74
command.go
74
command.go
@@ -4,51 +4,51 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input"
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/console/output"
|
||||
)
|
||||
|
||||
type (
|
||||
Action func(ctx context.Context, input input.Input, output output.Output) error
|
||||
Handle func(ctx context.Context, in input.Input, out output.Output, n Action) error
|
||||
Configure func(ctx context.Context, cfg *input.Definition) error
|
||||
Prepare func(ctx context.Context, cfg *input.Definition, n Configure) error
|
||||
Action func(ctx context.Context, input config.Provider, output output.Output) error
|
||||
Handle func(ctx context.Context, in config.Provider, out output.Output, n Action) error
|
||||
Configure func(ctx context.Context, cfg config.Definition) error
|
||||
Prepare func(ctx context.Context, cfg config.Definition, n Configure) error
|
||||
Option func(*Command)
|
||||
)
|
||||
|
||||
// WithPrepare append middleware for configuration command.
|
||||
func WithPrepare(p ...Prepare) Option {
|
||||
func WithPrepare(prepares ...Prepare) Option {
|
||||
return func(c *Command) {
|
||||
if c.Prepare != nil {
|
||||
p = append([]Prepare{c.Prepare}, p...)
|
||||
prepares = append([]Prepare{c.Prepare}, prepares...)
|
||||
}
|
||||
|
||||
c.Prepare = ChainPrepare(p...)
|
||||
c.Prepare = ChainPrepare(prepares...)
|
||||
}
|
||||
}
|
||||
|
||||
// WithHandle append middleware for executed command.
|
||||
func WithHandle(h ...Handle) Option {
|
||||
func WithHandle(handles ...Handle) Option {
|
||||
return func(c *Command) {
|
||||
if c.Handle != nil {
|
||||
h = append([]Handle{c.Handle}, h...)
|
||||
handles = append([]Handle{c.Handle}, handles...)
|
||||
}
|
||||
|
||||
c.Handle = ChainHandle(h...)
|
||||
c.Handle = ChainHandle(handles...)
|
||||
}
|
||||
}
|
||||
|
||||
// WithHidden sets hidden command.
|
||||
func WithHidden(v bool) Option {
|
||||
func WithHidden(hidden bool) Option {
|
||||
return func(c *Command) {
|
||||
c.Hidden = v
|
||||
c.Hidden = hidden
|
||||
}
|
||||
}
|
||||
|
||||
// WithName sets name command.
|
||||
func WithName(n string) Option {
|
||||
func WithName(name string) Option {
|
||||
return func(c *Command) {
|
||||
c.Name = n
|
||||
c.Name = name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,11 @@ func (c *Command) With(opts ...Option) *Command {
|
||||
}
|
||||
|
||||
// Run run command with input and output.
|
||||
func (c *Command) Run(ctx context.Context, in input.Input, out output.Output) error {
|
||||
func (c *Command) Run(ctx context.Context, in config.Provider, out output.Output) error {
|
||||
if c.Execute == nil {
|
||||
return fmt.Errorf("%w", ErrExecuteNil)
|
||||
}
|
||||
|
||||
if c.Handle != nil {
|
||||
return c.Handle(ctx, in, out, c.Execute)
|
||||
}
|
||||
@@ -108,12 +112,12 @@ func (c *Command) Run(ctx context.Context, in input.Input, out output.Output) er
|
||||
}
|
||||
|
||||
// Init configures command.
|
||||
func (c *Command) Init(ctx context.Context, cfg *input.Definition) error {
|
||||
func (c *Command) Init(ctx context.Context, cfg config.Definition) error {
|
||||
switch {
|
||||
case c.Prepare != nil && c.Configure != nil:
|
||||
return c.Prepare(ctx, cfg, c.Configure)
|
||||
case c.Prepare != nil:
|
||||
return c.Prepare(ctx, cfg, func(_ context.Context, _ *input.Definition) error {
|
||||
return c.Prepare(ctx, cfg, func(_ context.Context, _ config.Definition) error {
|
||||
return nil
|
||||
})
|
||||
case c.Configure != nil:
|
||||
@@ -125,24 +129,25 @@ func (c *Command) Init(ctx context.Context, cfg *input.Definition) error {
|
||||
|
||||
// ChainPrepare creates middleware for configures command.
|
||||
func ChainPrepare(prepare ...Prepare) Prepare {
|
||||
n := len(prepare)
|
||||
if n == 1 {
|
||||
num := len(prepare)
|
||||
if num == 1 {
|
||||
return prepare[0]
|
||||
}
|
||||
|
||||
if n > 1 {
|
||||
lastI := n - 1
|
||||
if num > 1 {
|
||||
lastI := num - 1
|
||||
|
||||
return func(ctx context.Context, def *input.Definition, next Configure) error {
|
||||
return func(ctx context.Context, def config.Definition, next Configure) error {
|
||||
var (
|
||||
chainHandler func(context.Context, *input.Definition) error
|
||||
chainHandler func(context.Context, config.Definition) error
|
||||
curI int
|
||||
)
|
||||
|
||||
chainHandler = func(currentCtx context.Context, currentDef *input.Definition) error {
|
||||
chainHandler = func(currentCtx context.Context, currentDef config.Definition) error {
|
||||
if curI == lastI {
|
||||
return next(currentCtx, currentDef)
|
||||
}
|
||||
|
||||
curI++
|
||||
err := prepare[curI](currentCtx, currentDef, chainHandler)
|
||||
curI--
|
||||
@@ -154,31 +159,32 @@ func ChainPrepare(prepare ...Prepare) Prepare {
|
||||
}
|
||||
}
|
||||
|
||||
return func(ctx context.Context, cfg *input.Definition, next Configure) error {
|
||||
return func(ctx context.Context, cfg config.Definition, next Configure) error {
|
||||
return next(ctx, cfg)
|
||||
}
|
||||
}
|
||||
|
||||
// ChainHandle creates middleware for executes command.
|
||||
func ChainHandle(handlers ...Handle) Handle {
|
||||
n := len(handlers)
|
||||
if n == 1 {
|
||||
num := len(handlers)
|
||||
if num == 1 {
|
||||
return handlers[0]
|
||||
}
|
||||
|
||||
if n > 1 {
|
||||
lastI := n - 1
|
||||
if num > 1 {
|
||||
lastI := num - 1
|
||||
|
||||
return func(ctx context.Context, in input.Input, out output.Output, next Action) error {
|
||||
return func(ctx context.Context, in config.Provider, out output.Output, next Action) error {
|
||||
var (
|
||||
chainHandler func(context.Context, input.Input, output.Output) error
|
||||
chainHandler func(context.Context, config.Provider, output.Output) error
|
||||
curI int
|
||||
)
|
||||
|
||||
chainHandler = func(currentCtx context.Context, currentIn input.Input, currentOut output.Output) error {
|
||||
chainHandler = func(currentCtx context.Context, currentIn config.Provider, currentOut output.Output) error {
|
||||
if curI == lastI {
|
||||
return next(currentCtx, currentIn, currentOut)
|
||||
}
|
||||
|
||||
curI++
|
||||
err := handlers[curI](currentCtx, currentIn, currentOut, chainHandler)
|
||||
curI--
|
||||
@@ -190,7 +196,7 @@ func ChainHandle(handlers ...Handle) Handle {
|
||||
}
|
||||
}
|
||||
|
||||
return func(ctx context.Context, in input.Input, out output.Output, next Action) error {
|
||||
return func(ctx context.Context, in config.Provider, out output.Output, next Action) error {
|
||||
return next(ctx, in, out)
|
||||
}
|
||||
}
|
||||
|
||||
111
command_test.go
111
command_test.go
@@ -2,48 +2,69 @@ package console_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/definition"
|
||||
"gitoa.ru/go-4devs/config/definition/group"
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
"gitoa.ru/go-4devs/config/definition/proto"
|
||||
"gitoa.ru/go-4devs/config/provider/arg"
|
||||
"gitoa.ru/go-4devs/config/provider/memory"
|
||||
"gitoa.ru/go-4devs/config/value"
|
||||
"gitoa.ru/go-4devs/console"
|
||||
"gitoa.ru/go-4devs/console/example/pkg/command"
|
||||
"gitoa.ru/go-4devs/console/input"
|
||||
"gitoa.ru/go-4devs/console/input/argument"
|
||||
"gitoa.ru/go-4devs/console/input/option"
|
||||
"gitoa.ru/go-4devs/console/output"
|
||||
)
|
||||
|
||||
//nolint: gochecknoinits
|
||||
//nolint:gochecknoinits
|
||||
func init() {
|
||||
console.MustRegister(Command().With(console.WithName("fdevs:console:test")))
|
||||
console.MustRegister(command.Args())
|
||||
console.MustRegister(Command().With(console.WithName("fdevs:console:arg")))
|
||||
}
|
||||
|
||||
func Command() *console.Command {
|
||||
return &console.Command{
|
||||
Name: "test:command",
|
||||
Description: "test command",
|
||||
Execute: func(ctx context.Context, in input.Input, out output.Output) error {
|
||||
Execute: func(ctx context.Context, in config.Provider, out output.Output) error {
|
||||
var astr []string
|
||||
if aerr := console.ReadValue(ctx, in, "string").Unmarshal(&astr); aerr != nil && !errors.Is(aerr, config.ErrNotFound) {
|
||||
return fmt.Errorf("unmarshal string:%w", aerr)
|
||||
}
|
||||
|
||||
out.Print(ctx,
|
||||
"test argument:", in.Argument(ctx, "test_argument").String(), "\n",
|
||||
"bool option:", in.Option(ctx, "bool").Bool(), "\n",
|
||||
"duration option with default:", in.Option(ctx, "duration").Duration(), "\n",
|
||||
"array string:[", strings.Join(in.Option(ctx, "string").Strings(), ","), "]\n",
|
||||
"test argument:", console.ReadValue(ctx, in, "test_argument").String(), "\n",
|
||||
"bool option:", console.ReadValue(ctx, in, "bool").Bool(), "\n",
|
||||
"duration option with default:", console.ReadValue(ctx, in, "duration").Duration(), "\n",
|
||||
"array string:[", strings.Join(astr, ","), "]\n",
|
||||
"group string:", console.ReadValue(ctx, in, "group", "test", "string").String(), "\n",
|
||||
"log http service:", console.ReadValue(ctx, in, "log", "http", "level").String(), "\n",
|
||||
)
|
||||
|
||||
return nil
|
||||
},
|
||||
Configure: func(ctx context.Context, def *input.Definition) error {
|
||||
Configure: func(_ context.Context, def config.Definition) error {
|
||||
def.
|
||||
SetArguments(
|
||||
argument.New("test_argument", "test argument"),
|
||||
).
|
||||
SetOptions(
|
||||
option.New("string", "array string", option.Array),
|
||||
Add(
|
||||
group.New("group", "group example",
|
||||
option.Bool("bool", "bool"),
|
||||
group.New("test", "test", option.String("string", "test group string", option.Default("group string default value"))),
|
||||
),
|
||||
group.New("log", "log",
|
||||
proto.New("service", "service level",
|
||||
option.String("level", "service level", option.Default("debug")),
|
||||
),
|
||||
),
|
||||
arg.String("test_argument", "test argument"),
|
||||
option.String("string", "array string", option.Slice),
|
||||
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(value.New(time.Second))),
|
||||
option.Time("hidden", "hidden time", option.Default(value.New(time.Second)), option.Hidden),
|
||||
)
|
||||
|
||||
return nil
|
||||
@@ -52,70 +73,92 @@ func Command() *console.Command {
|
||||
}
|
||||
|
||||
func TestChainPrepare(t *testing.T) {
|
||||
var cnt int32
|
||||
t.Parallel()
|
||||
|
||||
var cnt int64
|
||||
|
||||
ctx := context.Background()
|
||||
def := input.NewDefinition()
|
||||
def := definition.New()
|
||||
|
||||
prepare := func(ctx context.Context, def *input.Definition, n console.Configure) error {
|
||||
atomic.AddInt32(&cnt, 1)
|
||||
prepare := func(ctx context.Context, def config.Definition, n console.Configure) error {
|
||||
atomic.AddInt64(&cnt, 1)
|
||||
|
||||
return n(ctx, def)
|
||||
}
|
||||
configure := func(context.Context, *input.Definition) error {
|
||||
configure := func(context.Context, config.Definition) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := range []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10} {
|
||||
prepares := make([]console.Prepare, i)
|
||||
for p := 0; p < i; p++ {
|
||||
for p := range i {
|
||||
prepares[p] = prepare
|
||||
}
|
||||
|
||||
cnt = 0
|
||||
chain := console.ChainPrepare(prepares...)
|
||||
|
||||
if err := chain(ctx, def, configure); err != nil {
|
||||
err := chain(ctx, def, configure)
|
||||
if err != nil {
|
||||
t.Errorf("expected nil err, got: %s", err)
|
||||
}
|
||||
|
||||
if cnt != int32(i) {
|
||||
if cnt != int64(i) {
|
||||
t.Fatalf("expected: call prepare 1, got: %d ", cnt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestChainHandle(t *testing.T) {
|
||||
var cnt int32
|
||||
t.Parallel()
|
||||
|
||||
var cnt int64
|
||||
|
||||
ctx := context.Background()
|
||||
in := &input.Array{}
|
||||
in := &memory.Map{}
|
||||
out := output.Stdout()
|
||||
|
||||
handle := func(ctx context.Context, in input.Input, out output.Output, next console.Action) error {
|
||||
atomic.AddInt32(&cnt, 1)
|
||||
handle := func(ctx context.Context, in config.Provider, out output.Output, next console.Action) error {
|
||||
atomic.AddInt64(&cnt, 1)
|
||||
|
||||
return next(ctx, in, out)
|
||||
}
|
||||
action := func(context.Context, input.Input, output.Output) error {
|
||||
action := func(context.Context, config.Provider, output.Output) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := range []int{0, 1, 2, 30, 40, 50} {
|
||||
handles := make([]console.Handle, i)
|
||||
for p := 0; p < i; p++ {
|
||||
for p := range i {
|
||||
handles[p] = handle
|
||||
}
|
||||
|
||||
cnt = 0
|
||||
chain := console.ChainHandle(handles...)
|
||||
|
||||
if err := chain(ctx, in, out, action); err != nil {
|
||||
err := chain(ctx, in, out, action)
|
||||
if err != nil {
|
||||
t.Errorf("expected nil err, got: %s", err)
|
||||
}
|
||||
|
||||
if cnt != int32(i) {
|
||||
if cnt != int64(i) {
|
||||
t.Fatalf("expected: call prepare 1, got: %d ", cnt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunEmptyExecute(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
empty := console.Command{
|
||||
Name: "empty",
|
||||
}
|
||||
in := &memory.Map{}
|
||||
out := output.Stdout()
|
||||
|
||||
err := empty.Run(ctx, in, out)
|
||||
if !errors.Is(err, console.ErrExecuteNil) {
|
||||
t.Fatalf("expected: %v, got: %v ", console.ErrExecuteNil, err)
|
||||
}
|
||||
}
|
||||
|
||||
101
console.go
101
console.go
@@ -3,11 +3,16 @@ package console
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input"
|
||||
"gitoa.ru/go-4devs/console/input/option"
|
||||
"gitoa.ru/go-4devs/console/input/value"
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/definition"
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
"gitoa.ru/go-4devs/config/provider/chain"
|
||||
"gitoa.ru/go-4devs/config/provider/memory"
|
||||
"gitoa.ru/go-4devs/config/value"
|
||||
"gitoa.ru/go-4devs/console/output"
|
||||
"gitoa.ru/go-4devs/console/output/verbosity"
|
||||
)
|
||||
@@ -18,6 +23,19 @@ const (
|
||||
verboseInfo = 1
|
||||
)
|
||||
|
||||
const (
|
||||
OptionHelp = "help"
|
||||
OptionVersion = "version"
|
||||
OptionAnsi = "ansi"
|
||||
OptionNoAnsi = "no-ansi"
|
||||
OptionQuiet = "quiet"
|
||||
OptionVerbose = "verbose"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultOptionsPosition = math.MaxUint64 / 2
|
||||
)
|
||||
|
||||
// Execute the current command with option.
|
||||
func Execute(ctx context.Context, cmd *Command, opts ...func(*App)) {
|
||||
opts = append([]func(*App){WithSkipArgs(1)}, opts...)
|
||||
@@ -25,15 +43,19 @@ func Execute(ctx context.Context, cmd *Command, opts ...func(*App)) {
|
||||
}
|
||||
|
||||
// Run current command by input and output.
|
||||
func Run(ctx context.Context, cmd *Command, in input.Input, out output.Output) error {
|
||||
def := input.NewDefinition()
|
||||
func Run(ctx context.Context, cmd *Command, in config.BindProvider, out output.Output) error {
|
||||
def := definition.New()
|
||||
|
||||
if err := cmd.Init(ctx, def); err != nil {
|
||||
err := cmd.Init(ctx, def)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := in.Bind(ctx, Default(def)); err != nil {
|
||||
ansi(ctx, in, out).Print(ctx, "<error>\n\n ", err, "\n</error>\n")
|
||||
def.Add(Default()...)
|
||||
|
||||
berr := in.Bind(ctx, config.NewVars(def.Options()...))
|
||||
if berr != nil {
|
||||
log.Print(berr)
|
||||
|
||||
return showHelp(ctx, cmd, in, output.Ansi(out))
|
||||
}
|
||||
@@ -42,7 +64,7 @@ func Run(ctx context.Context, cmd *Command, in input.Input, out output.Output) e
|
||||
|
||||
out = verbose(ctx, in, out)
|
||||
|
||||
if in.Option(ctx, "version").Bool() {
|
||||
if ReadValue(ctx, in, OptionVersion).Bool() {
|
||||
version := cmd.Version
|
||||
if version == "" {
|
||||
version = "unknown"
|
||||
@@ -53,18 +75,18 @@ func Run(ctx context.Context, cmd *Command, in input.Input, out output.Output) e
|
||||
return nil
|
||||
}
|
||||
|
||||
if in.Option(ctx, "help").Bool() {
|
||||
if ReadValue(ctx, in, OptionHelp).Bool() {
|
||||
return showHelp(ctx, cmd, in, out)
|
||||
}
|
||||
|
||||
return cmd.Run(ctx, in, out)
|
||||
}
|
||||
|
||||
func ansi(ctx context.Context, in input.Input, out output.Output) output.Output {
|
||||
func ansi(ctx context.Context, in config.Provider, out output.Output) output.Output {
|
||||
switch {
|
||||
case in.Option(ctx, "ansi").Bool():
|
||||
case ReadValue(ctx, in, OptionAnsi).Bool():
|
||||
out = output.Ansi(out)
|
||||
case in.Option(ctx, "no-ansi").Bool():
|
||||
case ReadValue(ctx, in, OptionNoAnsi).Bool():
|
||||
out = output.None(out)
|
||||
case lookupEnv("NO_COLOR"):
|
||||
out = output.None(out)
|
||||
@@ -81,19 +103,21 @@ func lookupEnv(name string) bool {
|
||||
return has && v == "true"
|
||||
}
|
||||
|
||||
func verbose(ctx context.Context, in input.Input, out output.Output) output.Output {
|
||||
func verbose(ctx context.Context, in config.Provider, out output.Output) output.Output {
|
||||
switch {
|
||||
case in.Option(ctx, "quiet").Bool():
|
||||
case ReadValue(ctx, in, OptionQuiet).Bool():
|
||||
out = output.Quiet()
|
||||
default:
|
||||
v := in.Option(ctx, "verbose").Bools()
|
||||
var verb []bool
|
||||
|
||||
_ = ReadValue(ctx, in, OptionVerbose).Unmarshal(&verb)
|
||||
|
||||
switch {
|
||||
case len(v) == verboseInfo:
|
||||
case len(verb) == verboseInfo:
|
||||
out = output.Verbosity(out, verbosity.Info)
|
||||
case len(v) == verboseDebug:
|
||||
case len(verb) == verboseDebug:
|
||||
out = output.Verbosity(out, verbosity.Debug)
|
||||
case len(v) >= verboseTrace:
|
||||
case len(verb) >= verboseTrace:
|
||||
out = output.Verbosity(out, verbosity.Trace)
|
||||
default:
|
||||
out = output.Verbosity(out, verbosity.Norm)
|
||||
@@ -103,10 +127,10 @@ func verbose(ctx context.Context, in input.Input, out output.Output) output.Outp
|
||||
return out
|
||||
}
|
||||
|
||||
func showHelp(ctx context.Context, cmd *Command, in input.Input, out output.Output) error {
|
||||
a := &input.Array{}
|
||||
a.SetArgument(HelpArgumentCommandName, value.New(cmd.Name))
|
||||
a.SetOption("help", value.New(false))
|
||||
func showHelp(ctx context.Context, cmd *Command, in config.Provider, out output.Output) error {
|
||||
arr := &memory.Map{}
|
||||
arr.SetOption(value.New(cmd.Name), ArgumentCommandName)
|
||||
arr.SetOption(value.New(false), OptionHelp)
|
||||
|
||||
if _, err := Find(cmd.Name); errors.Is(err, ErrNotFound) {
|
||||
register(cmd)
|
||||
@@ -117,21 +141,30 @@ func showHelp(ctx context.Context, cmd *Command, in input.Input, out output.Outp
|
||||
return err
|
||||
}
|
||||
|
||||
w := input.Chain(a, in)
|
||||
w := chain.New(arr, in)
|
||||
|
||||
return Run(ctx, help, w, out)
|
||||
}
|
||||
|
||||
// Default options and argument command.
|
||||
func Default(d *input.Definition) *input.Definition {
|
||||
return d.SetOptions(
|
||||
option.Bool("no-ansi", "Disable ANSI output"),
|
||||
option.Bool("ansi", "Do not ask any interactive question"),
|
||||
option.Bool("version", "Display this application version", option.Short("V")),
|
||||
option.Bool("help", "Display this help message", option.Short("h")),
|
||||
option.Bool("verbose",
|
||||
func Default() []config.Option {
|
||||
return []config.Option{
|
||||
option.Bool(OptionNoAnsi, "Disable ANSI output", option.Position(defaultOptionsPosition)),
|
||||
option.Bool(OptionAnsi, "Do not ask any interactive question", option.Position(defaultOptionsPosition)),
|
||||
option.Bool(OptionVersion, "Display this application version", option.Short('V'), option.Position(defaultOptionsPosition)),
|
||||
option.Bool(OptionHelp, "Display this help message", option.Short('h'), option.Position(defaultOptionsPosition)),
|
||||
option.Bool(OptionVerbose,
|
||||
"Increase the verbosity of messages: -v for info output, -vv for debug and -vvv for trace",
|
||||
option.Short("v"), option.Array),
|
||||
option.Bool("quiet", "Do not output any message", option.Short("q")),
|
||||
)
|
||||
option.Short('v'), option.Slice, option.Position(defaultOptionsPosition)),
|
||||
option.Bool(OptionQuiet, "Do not output any message", option.Short('q'), option.Position(defaultOptionsPosition)),
|
||||
}
|
||||
}
|
||||
|
||||
func ReadValue(ctx context.Context, in config.Provider, path ...string) config.Value {
|
||||
val, err := in.Value(ctx, path...)
|
||||
if err != nil {
|
||||
return value.EmptyValue()
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
@@ -2,11 +2,14 @@ package console_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"gitoa.ru/go-4devs/config/provider/chain"
|
||||
"gitoa.ru/go-4devs/config/provider/memory"
|
||||
"gitoa.ru/go-4devs/config/value"
|
||||
"gitoa.ru/go-4devs/console"
|
||||
"gitoa.ru/go-4devs/console/input"
|
||||
"gitoa.ru/go-4devs/console/input/value"
|
||||
"gitoa.ru/go-4devs/console/output"
|
||||
)
|
||||
|
||||
@@ -14,7 +17,7 @@ func ExampleRun() {
|
||||
cmd := Command()
|
||||
ctx := context.Background()
|
||||
out := output.Stdout()
|
||||
in := &input.Array{}
|
||||
in := chain.New(&memory.Map{}, &memory.Default{})
|
||||
|
||||
err := console.Run(ctx, cmd, in, out)
|
||||
fmt.Println("err:", err)
|
||||
@@ -23,23 +26,33 @@ func ExampleRun() {
|
||||
// bool option:false
|
||||
// duration option with default:1s
|
||||
// array string:[]
|
||||
// group string:group string default value
|
||||
// log http service:debug
|
||||
// err: <nil>
|
||||
}
|
||||
|
||||
func ExampleExecute() {
|
||||
cmd := Command()
|
||||
ctx := context.Background()
|
||||
in := &input.Array{}
|
||||
in := &memory.Map{}
|
||||
|
||||
jb, err := json.Marshal([]string{"same value", "other value"})
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
|
||||
// Run command: ./bin "argument value" -b --string="same value" --string="other value"
|
||||
in.SetOption("bool", value.New(true))
|
||||
in.SetOption("string", value.New([]string{"same value", "other value"}))
|
||||
in.SetArgument("test_argument", value.New("argument value"))
|
||||
in.SetOption(value.New(true), "bool")
|
||||
in.SetOption(value.JBytes(jb), "string")
|
||||
in.SetOption(value.New("argument value"), "test_argument")
|
||||
in.SetOption(value.New("error"), "log", "http", "level")
|
||||
|
||||
console.Execute(ctx, cmd, console.WithInput(in), console.WithExit(func(int) {}))
|
||||
console.Execute(ctx, cmd, console.WithInput(chain.New(in, &memory.Default{})), console.WithExit(func(int) {}))
|
||||
// Output:
|
||||
// test argument:argument value
|
||||
// bool option:true
|
||||
// duration option with default:1s
|
||||
// array string:[same value,other value]
|
||||
// group string:group string default value
|
||||
// log http service:error
|
||||
}
|
||||
|
||||
45
doc.go
45
doc.go
@@ -2,29 +2,32 @@
|
||||
// The Console package allows you to create command-line commands.
|
||||
// Your console commands can be used for any recurring task, such as cronjobs, imports, or other batch jobs.
|
||||
// console application can be written as follows:
|
||||
// //cmd/console/main.go
|
||||
// func main() {
|
||||
// console.New().Execute(context.Background())
|
||||
// }
|
||||
//
|
||||
// //cmd/console/main.go
|
||||
// func main() {
|
||||
// console.New().Execute(context.Background())
|
||||
// }
|
||||
//
|
||||
// Then, you can register the commands using Add():
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "context"
|
||||
// package main
|
||||
//
|
||||
// "gitoa.ru/go-4devs/console"
|
||||
// "gitoa.ru/go-4devs/console/example/pkg/command"
|
||||
// )
|
||||
// import (
|
||||
// "context"
|
||||
//
|
||||
// func main() {
|
||||
// console.
|
||||
// New().
|
||||
// Add(
|
||||
// command.Hello(),
|
||||
// command.Args(),
|
||||
// command.Hidden(),
|
||||
// command.Namespace(),
|
||||
// ).
|
||||
// Execute(context.Background())
|
||||
// }
|
||||
// "gitoa.ru/go-4devs/console"
|
||||
// "gitoa.ru/go-4devs/console/example/pkg/command"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// console.
|
||||
// New().
|
||||
// Add(
|
||||
// command.Hello(),
|
||||
// command.Args(),
|
||||
// command.Hidden(),
|
||||
// command.Namespace(),
|
||||
// ).
|
||||
// Execute(context.Background())
|
||||
// }
|
||||
package console
|
||||
|
||||
31
error.go
Normal file
31
error.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("command not found")
|
||||
ErrCommandNil = errors.New("console: Register command is nil")
|
||||
ErrExecuteNil = errors.New("console: execute is nil")
|
||||
ErrCommandDuplicate = errors.New("console: duplicate command")
|
||||
)
|
||||
|
||||
type AlternativesError struct {
|
||||
Alt []string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e AlternativesError) Error() string {
|
||||
return fmt.Sprintf("%s, alternatives: [%s]", e.Err, strings.Join(e.Alt, ","))
|
||||
}
|
||||
|
||||
func (e AlternativesError) Is(err error) bool {
|
||||
return errors.Is(e.Err, err)
|
||||
}
|
||||
|
||||
func (e AlternativesError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
Binary file not shown.
@@ -21,5 +21,6 @@ func main() {
|
||||
<-ch
|
||||
cancel()
|
||||
}()
|
||||
|
||||
console.Execute(ctx, command.Long())
|
||||
}
|
||||
|
||||
35
example/cmd/config/main.go
Normal file
35
example/cmd/config/main.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitoa.ru/go-4devs/config/provider/arg"
|
||||
"gitoa.ru/go-4devs/config/provider/chain"
|
||||
"gitoa.ru/go-4devs/config/provider/env"
|
||||
"gitoa.ru/go-4devs/config/provider/memory"
|
||||
"gitoa.ru/go-4devs/console"
|
||||
"gitoa.ru/go-4devs/console/example/pkg/command"
|
||||
)
|
||||
|
||||
const (
|
||||
Namespace = "fdevs"
|
||||
AppName = "console"
|
||||
)
|
||||
|
||||
// FDEVS_CONSOLE_CAT=env FDEVS_CONSOLE_HIDDEN=2022-09-18T23:07:49+03:00 go run cmd/config/main.go fdevs:console:arg -b tmp.
|
||||
// FDEVS_CONSOLE_CAT=env go run cmd/config/main.go fdevs:console:arg --hidden=2022-09-18T23:07:49+03:00 -b tmp.
|
||||
func main() {
|
||||
console.
|
||||
New(console.WithInput(
|
||||
chain.New(
|
||||
arg.New(arg.WithSkip(0)),
|
||||
env.New(Namespace, AppName),
|
||||
&memory.Default{},
|
||||
),
|
||||
)).
|
||||
Add(
|
||||
command.Long(),
|
||||
command.Args(),
|
||||
).
|
||||
Execute(context.Background())
|
||||
}
|
||||
10
example/go.mod
Normal file
10
example/go.mod
Normal file
@@ -0,0 +1,10 @@
|
||||
module gitoa.ru/go-4devs/console/example
|
||||
|
||||
go 1.23
|
||||
|
||||
toolchain go1.24.1
|
||||
|
||||
require (
|
||||
gitoa.ru/go-4devs/config v0.0.7
|
||||
gitoa.ru/go-4devs/console v0.2.0
|
||||
)
|
||||
4
example/go.sum
Normal file
4
example/go.sum
Normal file
@@ -0,0 +1,4 @@
|
||||
gitoa.ru/go-4devs/config v0.0.7 h1:8q6axRNLgXE5dYQd8Jbh9j+STqevbibVyvwrtsuHpZk=
|
||||
gitoa.ru/go-4devs/config v0.0.7/go.mod h1:UINWnObZA0nLiJro+TtavUBBvN0cSt17aRHOk20pP74=
|
||||
gitoa.ru/go-4devs/console v0.2.0 h1:6lsbArs99GA8vGdnwNDThZNKjFNctNtTlSCUjhgwIpU=
|
||||
gitoa.ru/go-4devs/console v0.2.0/go.mod h1:xi4Svw7T+lylckAQiJQS/2qwDwF4YbIanlhcbQrBAiI=
|
||||
@@ -2,10 +2,11 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
"gitoa.ru/go-4devs/console"
|
||||
"gitoa.ru/go-4devs/console/input"
|
||||
"gitoa.ru/go-4devs/console/input/option"
|
||||
"gitoa.ru/go-4devs/console/output"
|
||||
)
|
||||
|
||||
@@ -13,19 +14,23 @@ func Args() *console.Command {
|
||||
return &console.Command{
|
||||
Name: "fdevs:console:arg",
|
||||
Description: "Understanding how Console Arguments and Options Are Handled",
|
||||
Configure: func(ctx context.Context, def *input.Definition) error {
|
||||
def.SetOptions(
|
||||
option.Bool("foo", "foo option", option.Short("f")),
|
||||
option.New("bar", "required bar option", option.Required, option.Short("b")),
|
||||
option.New("cat", "cat option", option.Short("c")),
|
||||
Configure: func(_ context.Context, def config.Definition) error {
|
||||
def.Add(
|
||||
option.Bool("foo", "foo option", option.Short('f')),
|
||||
option.String("bar", "required bar option", option.Required, option.Short('b')),
|
||||
option.String("cat", "cat option", option.Short('c')),
|
||||
option.Time("time", "time example"),
|
||||
option.Time("hidden", "hidden time example", option.Hidden),
|
||||
)
|
||||
|
||||
return nil
|
||||
},
|
||||
Execute: func(ctx context.Context, in input.Input, out output.Output) error {
|
||||
out.Println(ctx, "foo: <info>", in.Option(ctx, "foo").Bool(), "</info>")
|
||||
out.Println(ctx, "bar: <info>", in.Option(ctx, "bar").String(), "</info>")
|
||||
out.Println(ctx, "cat: <info>", in.Option(ctx, "cat").String(), "</info>")
|
||||
Execute: func(ctx context.Context, in config.Provider, out output.Output) error {
|
||||
out.Println(ctx, "foo: <info>", console.ReadValue(ctx, in, "foo").Bool(), "</info>")
|
||||
out.Println(ctx, "bar: <info>", console.ReadValue(ctx, in, "bar").String(), "</info>")
|
||||
out.Println(ctx, "cat: <info>", console.ReadValue(ctx, in, "cat").String(), "</info>")
|
||||
out.Println(ctx, "time: <info>", console.ReadValue(ctx, in, "time").Time().Format(time.RFC3339), "</info>")
|
||||
out.Println(ctx, "hidden: <info>", console.ReadValue(ctx, in, "hidden").Time().Format(time.RFC3339), "</info>")
|
||||
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -3,9 +3,11 @@ package command
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
"gitoa.ru/go-4devs/config/param"
|
||||
argument "gitoa.ru/go-4devs/config/provider/arg"
|
||||
"gitoa.ru/go-4devs/console"
|
||||
"gitoa.ru/go-4devs/console/input"
|
||||
"gitoa.ru/go-4devs/console/input/argument"
|
||||
"gitoa.ru/go-4devs/console/output"
|
||||
)
|
||||
|
||||
@@ -14,21 +16,24 @@ func CreateUser(required bool) *console.Command {
|
||||
Name: "app:create-user",
|
||||
Description: "Creates a new user.",
|
||||
Help: "This command allows you to create a user...",
|
||||
Configure: func(ctx context.Context, cfg *input.Definition) error {
|
||||
var opts []func(*argument.Argument)
|
||||
Configure: func(_ context.Context, cfg config.Definition) error {
|
||||
var opts []param.Option
|
||||
if required {
|
||||
opts = append(opts, argument.Required)
|
||||
opts = append(opts, option.Required)
|
||||
}
|
||||
|
||||
cfg.
|
||||
SetArgument("username", "The username of the user.", argument.Required).
|
||||
SetArgument("password", "User password", opts...)
|
||||
Add(
|
||||
argument.String("username", "The username of the user.", option.Required),
|
||||
argument.String("password", "User password", opts...),
|
||||
)
|
||||
|
||||
return nil
|
||||
},
|
||||
Execute: func(ctx context.Context, in input.Input, out output.Output) error {
|
||||
Execute: func(ctx context.Context, in config.Provider, out output.Output) error {
|
||||
// outputs a message followed by a "\n"
|
||||
out.Println(ctx, "User Creator")
|
||||
out.Println(ctx, "Username: ", in.Argument(ctx, "username").String())
|
||||
out.Println(ctx, "Username: ", console.ReadValue(ctx, in, "username").String())
|
||||
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -5,18 +5,20 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"gitoa.ru/go-4devs/config/provider/memory"
|
||||
"gitoa.ru/go-4devs/console"
|
||||
"gitoa.ru/go-4devs/console/example/pkg/command"
|
||||
"gitoa.ru/go-4devs/console/input"
|
||||
"gitoa.ru/go-4devs/console/output"
|
||||
)
|
||||
|
||||
func TestCreateUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
buf := bytes.Buffer{}
|
||||
out := output.Buffer(&buf)
|
||||
in := &input.Array{}
|
||||
in.SetArgument("username", "andrey")
|
||||
in := &memory.Map{}
|
||||
in.SetOption("andrey", "username")
|
||||
|
||||
err := console.Run(ctx, command.CreateUser(false), in, out)
|
||||
if err != nil {
|
||||
|
||||
@@ -3,9 +3,10 @@ package command
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
"gitoa.ru/go-4devs/config/provider/arg"
|
||||
"gitoa.ru/go-4devs/console"
|
||||
"gitoa.ru/go-4devs/console/input"
|
||||
"gitoa.ru/go-4devs/console/input/argument"
|
||||
"gitoa.ru/go-4devs/console/output"
|
||||
)
|
||||
|
||||
@@ -13,19 +14,23 @@ func Hello() *console.Command {
|
||||
return &console.Command{
|
||||
Name: "fdevs:console:hello",
|
||||
Description: "example hello command",
|
||||
Execute: func(ctx context.Context, in input.Input, out output.Output) error {
|
||||
name := in.Argument(ctx, "name").String()
|
||||
Execute: func(ctx context.Context, in config.Provider, out output.Output) error {
|
||||
name := console.ReadValue(ctx, in, "name").String()
|
||||
out.Println(ctx, "<error>Hello</error> <info>", name, "</info>")
|
||||
|
||||
out.Info(ctx, "same trace info\n")
|
||||
out.Debug(ctx, "have some question?\n")
|
||||
out.Trace(ctx, "this message shows with -vvv\n")
|
||||
|
||||
pass := console.ReadValue(ctx, in, "pass").String()
|
||||
out.Println(ctx, "hidden option pass <info>", pass, "</info>")
|
||||
|
||||
return nil
|
||||
},
|
||||
Configure: func(_ context.Context, def *input.Definition) error {
|
||||
def.SetArguments(
|
||||
argument.New("name", "Same name", argument.Default("World")),
|
||||
Configure: func(_ context.Context, def config.Definition) error {
|
||||
def.Add(
|
||||
arg.String("name", "Same name", arg.Default("World")),
|
||||
option.String("pass", "password", option.Hidden),
|
||||
)
|
||||
|
||||
return nil
|
||||
|
||||
@@ -3,8 +3,8 @@ package command
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/console"
|
||||
"gitoa.ru/go-4devs/console/input"
|
||||
"gitoa.ru/go-4devs/console/output"
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ func Hidden() *console.Command {
|
||||
Name: "fdevs:console:hidden",
|
||||
Description: "hidden command exmale",
|
||||
Hidden: true,
|
||||
Execute: func(ctx context.Context, _ input.Input, out output.Output) error {
|
||||
Execute: func(ctx context.Context, _ config.Provider, out output.Output) error {
|
||||
out.Println(ctx, "<info> call hidden command</info>")
|
||||
|
||||
return nil
|
||||
|
||||
@@ -4,11 +4,11 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
"gitoa.ru/go-4devs/config/validator"
|
||||
"gitoa.ru/go-4devs/config/value"
|
||||
"gitoa.ru/go-4devs/console"
|
||||
"gitoa.ru/go-4devs/console/input"
|
||||
"gitoa.ru/go-4devs/console/input/option"
|
||||
"gitoa.ru/go-4devs/console/input/validator"
|
||||
"gitoa.ru/go-4devs/console/input/value/flag"
|
||||
"gitoa.ru/go-4devs/console/output"
|
||||
)
|
||||
|
||||
@@ -18,11 +18,13 @@ const defaultTimeout = time.Second * 30
|
||||
func Long() *console.Command {
|
||||
return &console.Command{
|
||||
Name: "fdevs:command:long",
|
||||
Execute: func(ctx context.Context, in input.Input, out output.Output) error {
|
||||
timeout := in.Option(ctx, "timeout").Duration()
|
||||
Execute: func(ctx context.Context, in config.Provider, out output.Output) error {
|
||||
timeout := console.ReadValue(ctx, in, "timeout").Duration()
|
||||
timer := time.NewTimer(timeout)
|
||||
|
||||
ticker := time.NewTicker(time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case t := <-ticker.C:
|
||||
@@ -38,11 +40,11 @@ func Long() *console.Command {
|
||||
}
|
||||
}
|
||||
},
|
||||
Configure: func(ctx context.Context, def *input.Definition) error {
|
||||
def.SetOptions(option.Duration("timeout", "set duration run command",
|
||||
option.Default(defaultTimeout),
|
||||
option.Short("t"),
|
||||
option.Valid(validator.NotBlank(flag.Duration)),
|
||||
Configure: func(_ context.Context, def config.Definition) error {
|
||||
def.Add(option.Duration("timeout", "set duration run command",
|
||||
option.Default(value.New(defaultTimeout)),
|
||||
option.Short('t'),
|
||||
validator.Valid(validator.NotBlank),
|
||||
))
|
||||
|
||||
return nil
|
||||
|
||||
@@ -3,8 +3,8 @@ package command
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/console"
|
||||
"gitoa.ru/go-4devs/console/input"
|
||||
"gitoa.ru/go-4devs/console/output"
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ func Namespace() *console.Command {
|
||||
return &console.Command{
|
||||
Name: "app:start",
|
||||
Description: "example command in other namespace",
|
||||
Execute: func(ctx context.Context, _ input.Input, out output.Output) error {
|
||||
Execute: func(ctx context.Context, _ config.Provider, out output.Output) error {
|
||||
out.Println(ctx, "example command in other namespace")
|
||||
|
||||
return nil
|
||||
|
||||
12
go.mod
12
go.mod
@@ -1,3 +1,13 @@
|
||||
module gitoa.ru/go-4devs/console
|
||||
|
||||
go 1.15
|
||||
go 1.24.0
|
||||
|
||||
require gitoa.ru/go-4devs/config v0.0.8
|
||||
|
||||
require (
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
)
|
||||
|
||||
tool golang.org/x/tools/cmd/stringer
|
||||
|
||||
8
go.sum
8
go.sum
@@ -0,0 +1,8 @@
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
|
||||
61
help.go
61
help.go
@@ -6,23 +6,25 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input"
|
||||
"gitoa.ru/go-4devs/console/input/argument"
|
||||
"gitoa.ru/go-4devs/console/input/option"
|
||||
"gitoa.ru/go-4devs/console/input/validator"
|
||||
"gitoa.ru/go-4devs/console/input/value/flag"
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/definition"
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
"gitoa.ru/go-4devs/config/param"
|
||||
"gitoa.ru/go-4devs/config/provider/arg"
|
||||
"gitoa.ru/go-4devs/config/validator"
|
||||
"gitoa.ru/go-4devs/config/value"
|
||||
"gitoa.ru/go-4devs/console/output"
|
||||
"gitoa.ru/go-4devs/console/output/descriptor"
|
||||
)
|
||||
|
||||
//nolint: gochecknoinits
|
||||
//nolint:gochecknoinits
|
||||
func init() {
|
||||
MustRegister(help())
|
||||
}
|
||||
|
||||
const (
|
||||
HelpArgumentCommandName = "command_name"
|
||||
helpOptFormat = "format"
|
||||
ArgumentCommandName = "command_name"
|
||||
OptionFormat = "format"
|
||||
)
|
||||
|
||||
func help() *Command {
|
||||
@@ -36,25 +38,27 @@ You can also output the help in other formats by using the <comment>--format</co
|
||||
<info>{{ .Bin }} {{ .Name }} --format=xml list</info>
|
||||
To display the list of available commands, please use the <info>list</info> command.
|
||||
`,
|
||||
Execute: func(ctx context.Context, in input.Input, out output.Output) error {
|
||||
Execute: func(ctx context.Context, in config.Provider, out output.Output) error {
|
||||
var err error
|
||||
name := in.Argument(ctx, HelpArgumentCommandName).String()
|
||||
format := in.Option(ctx, helpOptFormat).String()
|
||||
|
||||
name := ReadValue(ctx, in, ArgumentCommandName).String()
|
||||
format := ReadValue(ctx, in, OptionFormat).String()
|
||||
|
||||
des, err := descriptor.Find(format)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("find descriptor[%v]: %w", format, err)
|
||||
}
|
||||
|
||||
cmd, err := Find(name)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("find cmd: %w", err)
|
||||
}
|
||||
|
||||
def := input.NewDefinition()
|
||||
def := definition.New()
|
||||
def.Add(Default()...)
|
||||
|
||||
if err := cmd.Init(ctx, Default(def)); err != nil {
|
||||
return err
|
||||
if err := cmd.Init(ctx, def); err != nil {
|
||||
return fmt.Errorf("init cmd: %w", err)
|
||||
}
|
||||
|
||||
var bin string
|
||||
@@ -62,26 +66,29 @@ To display the list of available commands, please use the <info>list</info> comm
|
||||
bin = os.Args[0]
|
||||
}
|
||||
|
||||
return des.Command(ctx, out, descriptor.Command{
|
||||
derr := des.Command(ctx, out, descriptor.Command{
|
||||
Bin: bin,
|
||||
Name: cmd.Name,
|
||||
Description: cmd.Description,
|
||||
Help: cmd.Help,
|
||||
Definition: def,
|
||||
Options: def.With(param.New(descriptor.TxtStyle())),
|
||||
})
|
||||
if derr != nil {
|
||||
return fmt.Errorf("descriptor help:%w", derr)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Configure: func(ctx context.Context, config *input.Definition) error {
|
||||
Configure: func(_ context.Context, config config.Definition) error {
|
||||
formats := descriptor.Descriptors()
|
||||
config.
|
||||
SetArguments(
|
||||
argument.New(HelpArgumentCommandName, "The command name", argument.Default("help")),
|
||||
).
|
||||
SetOptions(
|
||||
option.New(helpOptFormat, fmt.Sprintf("The output format (%s)", strings.Join(formats, ", ")),
|
||||
Add(
|
||||
arg.String(ArgumentCommandName, "The command name", arg.Default(value.New("help"))),
|
||||
option.String(OptionFormat, fmt.Sprintf("The output format (%s)", strings.Join(formats, ", ")),
|
||||
option.Required,
|
||||
option.Default(formats[0]),
|
||||
option.Valid(
|
||||
validator.NotBlank(flag.String),
|
||||
option.Default(value.New(formats[0])),
|
||||
validator.Valid(
|
||||
validator.NotBlank,
|
||||
validator.Enum(formats...),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
package argument
|
||||
|
||||
import (
|
||||
"gitoa.ru/go-4devs/console/input/errs"
|
||||
"gitoa.ru/go-4devs/console/input/value"
|
||||
"gitoa.ru/go-4devs/console/input/value/flag"
|
||||
)
|
||||
|
||||
func New(name, description string, opts ...func(*Argument)) Argument {
|
||||
a := Argument{
|
||||
Name: name,
|
||||
Description: description,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(&a)
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
type Argument struct {
|
||||
Name string
|
||||
Description string
|
||||
Default value.Value
|
||||
Flag flag.Flag
|
||||
Valid []func(value.Value) error
|
||||
}
|
||||
|
||||
func (a Argument) HasDefault() bool {
|
||||
return a.Default != nil
|
||||
}
|
||||
|
||||
func (a Argument) IsBool() bool {
|
||||
return a.Flag.IsBool()
|
||||
}
|
||||
|
||||
func (a Argument) IsRequired() bool {
|
||||
return a.Flag.IsRequired()
|
||||
}
|
||||
|
||||
func (a Argument) IsArray() bool {
|
||||
return a.Flag.IsArray()
|
||||
}
|
||||
|
||||
func (a Argument) Validate(v value.Value) error {
|
||||
for _, valid := range a.Valid {
|
||||
if err := valid(v); err != nil {
|
||||
return errs.Argument(a.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
139
input/argv.go
139
input/argv.go
@@ -1,139 +0,0 @@
|
||||
package input
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input/errs"
|
||||
"gitoa.ru/go-4devs/console/input/option"
|
||||
)
|
||||
|
||||
const doubleDash = `--`
|
||||
|
||||
type Argv struct {
|
||||
Array
|
||||
Args []string
|
||||
ErrHandle func(error) error
|
||||
}
|
||||
|
||||
func (i *Argv) Bind(ctx context.Context, def *Definition) error {
|
||||
options := true
|
||||
|
||||
for len(i.Args) > 0 {
|
||||
var err error
|
||||
|
||||
arg := i.Args[0]
|
||||
i.Args = i.Args[1:]
|
||||
|
||||
switch {
|
||||
case options && arg == doubleDash:
|
||||
options = false
|
||||
case options && len(arg) > 2 && arg[0:2] == doubleDash:
|
||||
err = i.parseLongOption(arg[2:], def)
|
||||
case options && arg[0:1] == "-":
|
||||
if len(arg) == 1 {
|
||||
return fmt.Errorf("%w: option name required given '-'", errs.ErrInvalidName)
|
||||
}
|
||||
|
||||
err = i.parseShortOption(arg[1:], def)
|
||||
default:
|
||||
err = i.parseArgument(arg, def)
|
||||
}
|
||||
|
||||
if err != nil && i.ErrHandle != nil {
|
||||
if herr := i.ErrHandle(err); herr != nil {
|
||||
return herr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return i.Array.Bind(ctx, def)
|
||||
}
|
||||
|
||||
func (i *Argv) parseLongOption(arg string, def *Definition) error {
|
||||
var value *string
|
||||
|
||||
name := arg
|
||||
|
||||
if strings.Contains(arg, "=") {
|
||||
vals := strings.SplitN(arg, "=", 2)
|
||||
name = vals[0]
|
||||
value = &vals[1]
|
||||
}
|
||||
|
||||
opt, err := def.Option(name)
|
||||
if err != nil {
|
||||
return errs.Option(name, err)
|
||||
}
|
||||
|
||||
return i.appendOption(name, value, opt)
|
||||
}
|
||||
|
||||
func (i *Argv) appendOption(name string, data *string, opt option.Option) error {
|
||||
if i.HasOption(name) && !opt.IsArray() {
|
||||
return fmt.Errorf("%w: got: array, expect: %s", errs.ErrUnexpectedType, opt.Flag.Type())
|
||||
}
|
||||
|
||||
var val string
|
||||
|
||||
switch {
|
||||
case data != nil:
|
||||
val = *data
|
||||
case opt.IsBool():
|
||||
val = "true"
|
||||
case len(i.Args) > 0 && len(i.Args[0]) > 0 && i.Args[0][0:1] != "-":
|
||||
val = i.Args[0]
|
||||
i.Args = i.Args[1:]
|
||||
default:
|
||||
return errs.Option(name, errs.ErrRequired)
|
||||
}
|
||||
|
||||
if err := i.AppendOption(opt.Flag, name, val); err != nil {
|
||||
return errs.Option(name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Argv) parseShortOption(arg string, def *Definition) error {
|
||||
name := arg
|
||||
|
||||
var value string
|
||||
|
||||
if len(name) > 1 {
|
||||
name, value = arg[0:1], arg[1:]
|
||||
}
|
||||
|
||||
opt, err := def.ShortOption(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if opt.IsBool() && value != "" {
|
||||
if err := i.parseShortOption(value, def); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value = ""
|
||||
}
|
||||
|
||||
if value == "" {
|
||||
return i.appendOption(opt.Name, nil, opt)
|
||||
}
|
||||
|
||||
return i.appendOption(opt.Name, &value, opt)
|
||||
}
|
||||
|
||||
func (i *Argv) parseArgument(arg string, def *Definition) error {
|
||||
opt, err := def.Argument(i.LenArguments())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := i.AppendArgument(opt.Flag, opt.Name, arg); err != nil {
|
||||
return errs.Argument(opt.Name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
108
input/array.go
108
input/array.go
@@ -1,108 +0,0 @@
|
||||
package input
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input/errs"
|
||||
"gitoa.ru/go-4devs/console/input/value"
|
||||
)
|
||||
|
||||
type Array struct {
|
||||
Map
|
||||
defaults Map
|
||||
}
|
||||
|
||||
func (a *Array) Option(ctx context.Context, name string) value.Value {
|
||||
if v := a.Map.Option(ctx, name); !value.IsEmpty(v) {
|
||||
return v
|
||||
}
|
||||
|
||||
if v := a.defaults.Option(ctx, name); !value.IsEmpty(v) {
|
||||
return v
|
||||
}
|
||||
|
||||
return value.Empty()
|
||||
}
|
||||
|
||||
func (a *Array) Argument(ctx context.Context, name string) value.Value {
|
||||
if v := a.Map.Argument(ctx, name); !value.IsEmpty(v) {
|
||||
return v
|
||||
}
|
||||
|
||||
if v := a.defaults.Argument(ctx, name); !value.IsEmpty(v) {
|
||||
return v
|
||||
}
|
||||
|
||||
return value.Empty()
|
||||
}
|
||||
|
||||
func (a *Array) Bind(ctx context.Context, d *Definition) error {
|
||||
if err := a.bindArguments(ctx, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return a.bindOption(ctx, d)
|
||||
}
|
||||
|
||||
func (a *Array) bindOption(ctx context.Context, def *Definition) error {
|
||||
for _, name := range def.Options() {
|
||||
opt, err := def.Option(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !a.HasOption(name) {
|
||||
switch {
|
||||
case opt.HasDefault():
|
||||
a.defaults.SetOption(name, opt.Default)
|
||||
|
||||
continue
|
||||
case opt.IsRequired():
|
||||
return errs.Option(name, errs.ErrRequired)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
v := a.Map.Option(ctx, name)
|
||||
if value.IsEmpty(v) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := opt.Validate(v); err != nil {
|
||||
return errs.Option(name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Array) bindArguments(ctx context.Context, def *Definition) error {
|
||||
for pos, name := range def.Arguments() {
|
||||
arg, err := def.Argument(pos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !a.HasArgument(name) {
|
||||
switch {
|
||||
case arg.HasDefault():
|
||||
a.defaults.SetArgument(name, arg.Default)
|
||||
|
||||
continue
|
||||
case arg.IsRequired():
|
||||
return errs.Argument(name, errs.ErrRequired)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if v := a.Map.Argument(ctx, name); !value.IsEmpty(v) {
|
||||
if err := arg.Validate(v); err != nil {
|
||||
return errs.Argument(name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package input
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input/value"
|
||||
)
|
||||
|
||||
func Chain(c ...Input) Input {
|
||||
return chain(c)
|
||||
}
|
||||
|
||||
type chain []Input
|
||||
|
||||
func (c chain) Option(ctx context.Context, name string) value.Value {
|
||||
for _, in := range c {
|
||||
if val := in.Option(ctx, name); !value.IsEmpty(val) {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
return value.Empty()
|
||||
}
|
||||
|
||||
func (c chain) Argument(ctx context.Context, name string) value.Value {
|
||||
for _, in := range c {
|
||||
if val := in.Argument(ctx, name); !value.IsEmpty(val) {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
return value.Empty()
|
||||
}
|
||||
|
||||
func (c chain) Bind(ctx context.Context, def *Definition) error {
|
||||
for _, input := range c {
|
||||
if err := input.Bind(ctx, def); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
package input
|
||||
|
||||
import (
|
||||
"gitoa.ru/go-4devs/console/input/argument"
|
||||
"gitoa.ru/go-4devs/console/input/errs"
|
||||
"gitoa.ru/go-4devs/console/input/option"
|
||||
)
|
||||
|
||||
func NewDefinition() *Definition {
|
||||
return &Definition{
|
||||
options: make(map[string]option.Option),
|
||||
args: make(map[string]argument.Argument),
|
||||
short: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
type Definition struct {
|
||||
options map[string]option.Option
|
||||
posOpt []string
|
||||
args map[string]argument.Argument
|
||||
posArgs []string
|
||||
short map[string]string
|
||||
}
|
||||
|
||||
func (d *Definition) Options() []string {
|
||||
return d.posOpt
|
||||
}
|
||||
|
||||
func (d *Definition) Arguments() []string {
|
||||
return d.posArgs
|
||||
}
|
||||
|
||||
func (d *Definition) SetOption(name, description string, opts ...func(*option.Option)) *Definition {
|
||||
return d.SetOptions(option.New(name, description, opts...))
|
||||
}
|
||||
|
||||
func (d *Definition) SetOptions(opts ...option.Option) *Definition {
|
||||
for _, opt := range opts {
|
||||
if _, has := d.options[opt.Name]; !has {
|
||||
d.posOpt = append([]string{opt.Name}, d.posOpt...)
|
||||
}
|
||||
|
||||
d.options[opt.Name] = opt
|
||||
if opt.HasShort() {
|
||||
d.short[opt.Short] = opt.Name
|
||||
}
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *Definition) SetArgument(name, description string, opts ...func(*argument.Argument)) *Definition {
|
||||
return d.SetArguments(argument.New(name, description, opts...))
|
||||
}
|
||||
|
||||
func (d *Definition) SetArguments(args ...argument.Argument) *Definition {
|
||||
for _, arg := range args {
|
||||
if _, ok := d.args[arg.Name]; !ok {
|
||||
d.posArgs = append(d.posArgs, arg.Name)
|
||||
}
|
||||
|
||||
d.args[arg.Name] = arg
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *Definition) Argument(pos int) (argument.Argument, error) {
|
||||
if len(d.posArgs) == 0 {
|
||||
return argument.Argument{}, errs.ErrNoArgs
|
||||
}
|
||||
|
||||
lastPos := len(d.posArgs) - 1
|
||||
if lastPos < pos {
|
||||
arg := d.args[d.posArgs[lastPos]]
|
||||
if arg.IsArray() {
|
||||
return arg, nil
|
||||
}
|
||||
|
||||
return argument.Argument{}, errs.ErrToManyArgs
|
||||
}
|
||||
|
||||
return d.args[d.posArgs[pos]], nil
|
||||
}
|
||||
|
||||
func (d *Definition) ShortOption(short string) (option.Option, error) {
|
||||
name, ok := d.short[short]
|
||||
if !ok {
|
||||
return option.Option{}, errs.ErrNotFound
|
||||
}
|
||||
|
||||
return d.Option(name)
|
||||
}
|
||||
|
||||
func (d *Definition) Option(name string) (option.Option, error) {
|
||||
if opt, ok := d.options[name]; ok {
|
||||
return opt, nil
|
||||
}
|
||||
|
||||
return option.Option{}, errs.ErrNotFound
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package errs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("not found")
|
||||
ErrNoArgs = errors.New("no arguments expected")
|
||||
ErrToManyArgs = errors.New("too many arguments")
|
||||
ErrUnexpectedType = errors.New("unexpected type")
|
||||
ErrRequired = errors.New("is required")
|
||||
ErrAppend = errors.New("failed append")
|
||||
ErrInvalidName = errors.New("invalid name")
|
||||
)
|
||||
|
||||
func New(name, t string, err error) Error {
|
||||
return Error{
|
||||
name: name,
|
||||
t: t,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
name string
|
||||
err error
|
||||
t string
|
||||
}
|
||||
|
||||
func (o Error) Error() string {
|
||||
return fmt.Sprintf("%s: '%s' %s", o.t, o.name, o.err)
|
||||
}
|
||||
|
||||
func (o Error) Is(err error) bool {
|
||||
return errors.Is(err, o.err)
|
||||
}
|
||||
|
||||
func (o Error) Unwrap() error {
|
||||
return o.err
|
||||
}
|
||||
|
||||
func Option(name string, err error) Error {
|
||||
return Error{
|
||||
name: name,
|
||||
err: err,
|
||||
t: "option",
|
||||
}
|
||||
}
|
||||
|
||||
func Argument(name string, err error) Error {
|
||||
return Error{
|
||||
name: name,
|
||||
err: err,
|
||||
t: "argument",
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package input
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input/value"
|
||||
)
|
||||
|
||||
type Input interface {
|
||||
Option(ctx context.Context, name string) value.Value
|
||||
Argument(ctx context.Context, name string) value.Value
|
||||
Bind(ctx context.Context, def *Definition) error
|
||||
}
|
||||
87
input/map.go
87
input/map.go
@@ -1,87 +0,0 @@
|
||||
package input
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input/value"
|
||||
"gitoa.ru/go-4devs/console/input/value/flag"
|
||||
)
|
||||
|
||||
type Map struct {
|
||||
opts map[string]value.Append
|
||||
args map[string]value.Append
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (m *Map) Option(_ context.Context, name string) value.Value {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
return m.opts[name]
|
||||
}
|
||||
|
||||
func (m *Map) Argument(_ context.Context, name string) value.Value {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
return m.args[name]
|
||||
}
|
||||
|
||||
func (m *Map) Bind(_ context.Context, _ *Definition) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Map) LenArguments() int {
|
||||
return len(m.args)
|
||||
}
|
||||
|
||||
func (m *Map) HasOption(name string) bool {
|
||||
_, ok := m.opts[name]
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
func (m *Map) SetOption(name string, v interface{}) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
if m.opts == nil {
|
||||
m.opts = make(map[string]value.Append)
|
||||
}
|
||||
|
||||
m.opts[name] = value.New(v)
|
||||
}
|
||||
|
||||
func (m *Map) HasArgument(name string) bool {
|
||||
_, ok := m.args[name]
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
func (m *Map) SetArgument(name string, v interface{}) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
if m.args == nil {
|
||||
m.args = make(map[string]value.Append)
|
||||
}
|
||||
|
||||
m.args[name] = value.New(v)
|
||||
}
|
||||
|
||||
func (m *Map) AppendOption(f flag.Flag, name, val string) error {
|
||||
if _, ok := m.opts[name]; !ok {
|
||||
m.SetOption(name, value.ByFlag(f))
|
||||
}
|
||||
|
||||
return m.opts[name].Append(val)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -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))...)
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package option
|
||||
|
||||
import (
|
||||
"gitoa.ru/go-4devs/console/input/errs"
|
||||
"gitoa.ru/go-4devs/console/input/value"
|
||||
"gitoa.ru/go-4devs/console/input/value/flag"
|
||||
)
|
||||
|
||||
func Required(o *Option) {
|
||||
o.Flag |= flag.Required
|
||||
}
|
||||
|
||||
func Default(in interface{}) func(*Option) {
|
||||
return func(o *Option) {
|
||||
o.Default = value.New(in)
|
||||
}
|
||||
}
|
||||
|
||||
func Short(s string) func(*Option) {
|
||||
return func(o *Option) {
|
||||
o.Short = s
|
||||
}
|
||||
}
|
||||
|
||||
func Array(o *Option) {
|
||||
o.Flag |= flag.Array
|
||||
}
|
||||
|
||||
func Value(flag flag.Flag) func(*Option) {
|
||||
return func(o *Option) {
|
||||
o.Flag |= flag
|
||||
}
|
||||
}
|
||||
|
||||
func Flag(in flag.Flag) func(*Option) {
|
||||
return func(o *Option) {
|
||||
o.Flag = in
|
||||
}
|
||||
}
|
||||
|
||||
func Valid(f ...func(value.Value) error) func(*Option) {
|
||||
return func(o *Option) {
|
||||
o.Valid = f
|
||||
}
|
||||
}
|
||||
|
||||
func New(name, description string, opts ...func(*Option)) Option {
|
||||
o := Option{
|
||||
Name: name,
|
||||
Description: description,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(&o)
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
type Option struct {
|
||||
Name string
|
||||
Description string
|
||||
Short string
|
||||
Flag flag.Flag
|
||||
Default value.Value
|
||||
Valid []func(value.Value) error
|
||||
}
|
||||
|
||||
func (o Option) HasShort() bool {
|
||||
return len(o.Short) == 1
|
||||
}
|
||||
|
||||
func (o Option) HasDefault() bool {
|
||||
return o.Default != nil
|
||||
}
|
||||
|
||||
func (o Option) IsBool() bool {
|
||||
return o.Flag.IsBool()
|
||||
}
|
||||
|
||||
func (o Option) IsArray() bool {
|
||||
return o.Flag.IsArray()
|
||||
}
|
||||
|
||||
func (o Option) IsRequired() bool {
|
||||
return o.Flag.IsRequired()
|
||||
}
|
||||
|
||||
func (o Option) Validate(v value.Value) error {
|
||||
for _, valid := range o.Valid {
|
||||
if err := valid(v); err != nil {
|
||||
return errs.Option(o.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package validator
|
||||
|
||||
import "gitoa.ru/go-4devs/console/input/value"
|
||||
|
||||
func Enum(enum ...string) func(value.Value) error {
|
||||
return func(in value.Value) error {
|
||||
v := in.String()
|
||||
for _, e := range enum {
|
||||
if e == v {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return NewError(ErrInvalid, v, enum)
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package validator_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input/validator"
|
||||
"gitoa.ru/go-4devs/console/input/value"
|
||||
)
|
||||
|
||||
func TestEnum(t *testing.T) {
|
||||
validValue := value.New("valid")
|
||||
invalidValue := value.New("invalid")
|
||||
|
||||
enum := validator.Enum("valid", "other", "three")
|
||||
|
||||
if err := enum(validValue); err != nil {
|
||||
t.Errorf("expected valid value got err:%s", err)
|
||||
}
|
||||
|
||||
if err := enum(invalidValue); !errors.Is(err, validator.ErrInvalid) {
|
||||
t.Errorf("expected err:%s, got: %s", validator.ErrInvalid, err)
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalid = errors.New("invalid value")
|
||||
ErrNotBlank = errors.New("not blank")
|
||||
)
|
||||
|
||||
func NewError(err error, value, expect interface{}) Error {
|
||||
return Error{
|
||||
err: err,
|
||||
value: value,
|
||||
expect: expect,
|
||||
}
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
err error
|
||||
value interface{}
|
||||
expect interface{}
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprintf("%s: expext: %s, given: %s", e.err, e.expect, e.value)
|
||||
}
|
||||
|
||||
func (e Error) Is(err error) bool {
|
||||
return errors.Is(e.err, err)
|
||||
}
|
||||
|
||||
func (e Error) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"gitoa.ru/go-4devs/console/input/value"
|
||||
"gitoa.ru/go-4devs/console/input/value/flag"
|
||||
)
|
||||
|
||||
//nolint: gocyclo
|
||||
func NotBlank(f flag.Flag) func(value.Value) error {
|
||||
return func(in value.Value) error {
|
||||
switch {
|
||||
case f.IsAny() && in.Any() != nil:
|
||||
return nil
|
||||
case f.IsArray():
|
||||
return arrayNotBlank(f, in)
|
||||
case f.IsInt() && in.Int() != 0:
|
||||
return nil
|
||||
case f.IsInt64() && in.Int64() != 0:
|
||||
return nil
|
||||
case f.IsUint() && in.Uint() != 0:
|
||||
return nil
|
||||
case f.IsUint64() && in.Uint64() != 0:
|
||||
return nil
|
||||
case f.IsFloat64() && in.Float64() != 0:
|
||||
return nil
|
||||
case f.IsDuration() && in.Duration() != 0:
|
||||
return nil
|
||||
case f.IsTime() && !in.Time().IsZero():
|
||||
return nil
|
||||
case f.IsString() && len(in.String()) > 0:
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrNotBlank
|
||||
}
|
||||
}
|
||||
|
||||
//nolint: gocyclo,gocognit
|
||||
func arrayNotBlank(f flag.Flag, in value.Value) error {
|
||||
switch {
|
||||
case f.IsInt() && len(in.Ints()) > 0:
|
||||
for _, i := range in.Ints() {
|
||||
if i == 0 {
|
||||
return ErrNotBlank
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
case f.IsInt64() && len(in.Int64s()) > 0:
|
||||
for _, i := range in.Int64s() {
|
||||
if i == 0 {
|
||||
return ErrNotBlank
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
case f.IsUint() && len(in.Uints()) > 0:
|
||||
for _, u := range in.Uints() {
|
||||
if u == 0 {
|
||||
return ErrNotBlank
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
case f.IsUint64() && len(in.Uint64s()) > 0:
|
||||
for _, u := range in.Uint64s() {
|
||||
if u == 0 {
|
||||
return ErrNotBlank
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
case f.IsFloat64() && len(in.Float64s()) > 0:
|
||||
for _, f := range in.Float64s() {
|
||||
if f == 0 {
|
||||
return ErrNotBlank
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
case f.IsBool() && len(in.Bools()) > 0:
|
||||
return nil
|
||||
case f.IsDuration() && len(in.Durations()) > 0:
|
||||
for _, d := range in.Durations() {
|
||||
if d == 0 {
|
||||
return ErrNotBlank
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
case f.IsTime() && len(in.Times()) > 0:
|
||||
for _, t := range in.Times() {
|
||||
if t.IsZero() {
|
||||
return ErrNotBlank
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
case f.IsString() && len(in.Strings()) > 0:
|
||||
for _, st := range in.Strings() {
|
||||
if len(st) == 0 {
|
||||
return ErrNotBlank
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrNotBlank
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package validator_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input/validator"
|
||||
"gitoa.ru/go-4devs/console/input/value"
|
||||
"gitoa.ru/go-4devs/console/input/value/flag"
|
||||
)
|
||||
|
||||
func TestNotBlank(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
flag flag.Flag
|
||||
value value.Value
|
||||
empty value.Value
|
||||
}{
|
||||
"any": {flag: flag.Any, value: value.New(float32(1))},
|
||||
"array int": {
|
||||
flag: flag.Int | flag.Array,
|
||||
value: value.New([]int{1}),
|
||||
empty: value.New([]int{10, 20, 0}),
|
||||
},
|
||||
"array int64": {
|
||||
flag: flag.Int64 | flag.Array,
|
||||
value: value.New([]int64{1}),
|
||||
empty: value.New([]int64{0}),
|
||||
},
|
||||
"array uint": {
|
||||
flag: flag.Uint | flag.Array,
|
||||
value: value.New([]uint{1}),
|
||||
empty: value.New([]uint{1, 0}),
|
||||
},
|
||||
"array uint64": {
|
||||
flag: flag.Uint64 | flag.Array,
|
||||
value: value.New([]uint64{1}),
|
||||
empty: value.New([]uint64{0}),
|
||||
},
|
||||
"array float64": {
|
||||
flag: flag.Float64 | flag.Array,
|
||||
value: value.New([]float64{0.2}),
|
||||
empty: value.New([]float64{0}),
|
||||
},
|
||||
"array bool": {
|
||||
flag: flag.Bool | flag.Array,
|
||||
value: value.New([]bool{true, false}),
|
||||
empty: value.New([]bool{}),
|
||||
},
|
||||
"array duration": {
|
||||
flag: flag.Duration | flag.Array,
|
||||
value: value.New([]time.Duration{time.Second}),
|
||||
empty: value.New([]time.Duration{time.Second, 0}),
|
||||
},
|
||||
"array time": {
|
||||
flag: flag.Time | flag.Array,
|
||||
value: value.New([]time.Time{time.Now()}),
|
||||
empty: value.New([]time.Time{{}, time.Now()}),
|
||||
},
|
||||
"array string": {
|
||||
flag: flag.Array,
|
||||
value: value.New([]string{"value"}),
|
||||
empty: value.New([]string{""}),
|
||||
},
|
||||
"int": {
|
||||
flag: flag.Int,
|
||||
value: value.New(int(1)),
|
||||
},
|
||||
"int64": {
|
||||
flag: flag.Int64,
|
||||
value: value.New(int64(2)),
|
||||
},
|
||||
"uint": {
|
||||
flag: flag.Uint,
|
||||
value: value.New(uint(1)),
|
||||
empty: value.New([]uint{1}),
|
||||
},
|
||||
"uint64": {
|
||||
flag: flag.Uint64,
|
||||
value: value.New(uint64(10)),
|
||||
},
|
||||
"float64": {
|
||||
flag: flag.Float64,
|
||||
value: value.New(float64(.00001)),
|
||||
},
|
||||
"duration": {
|
||||
flag: flag.Duration,
|
||||
value: value.New(time.Minute),
|
||||
empty: value.New("same string"),
|
||||
},
|
||||
"time": {flag: flag.Time, value: value.New(time.Now())},
|
||||
"string": {value: value.New("string"), empty: value.New("")},
|
||||
}
|
||||
|
||||
for name, ca := range cases {
|
||||
valid := validator.NotBlank(ca.flag)
|
||||
if err := valid(ca.value); err != nil {
|
||||
t.Errorf("case: %s, expected error <nil>, got: %s", name, err)
|
||||
}
|
||||
|
||||
if ca.empty == nil {
|
||||
ca.empty = value.Empty()
|
||||
}
|
||||
|
||||
if err := valid(ca.empty); err == nil || !errors.Is(err, validator.ErrNotBlank) {
|
||||
t.Errorf("case: %s, expect: %s, got:%s", name, validator.ErrNotBlank, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package validator
|
||||
|
||||
import "gitoa.ru/go-4devs/console/input/value"
|
||||
|
||||
func Valid(v ...func(value.Value) error) func(value.Value) error {
|
||||
return func(in value.Value) error {
|
||||
for _, valid := range v {
|
||||
if err := valid(in); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package validator_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input/validator"
|
||||
"gitoa.ru/go-4devs/console/input/value"
|
||||
"gitoa.ru/go-4devs/console/input/value/flag"
|
||||
)
|
||||
|
||||
func TestValid(t *testing.T) {
|
||||
validValue := value.New("one")
|
||||
invalidValue := value.New([]string{"one"})
|
||||
|
||||
valid := validator.Valid(
|
||||
validator.NotBlank(flag.String),
|
||||
validator.Enum("one", "two"),
|
||||
)
|
||||
|
||||
if err := valid(validValue); err != nil {
|
||||
t.Errorf("expected valid value, got: %s", err)
|
||||
}
|
||||
|
||||
if err := valid(invalidValue); !errors.Is(err, validator.ErrNotBlank) {
|
||||
t.Errorf("expected not blank, got:%s", err)
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package value
|
||||
|
||||
import "gitoa.ru/go-4devs/console/input/value/flag"
|
||||
|
||||
type Any struct {
|
||||
empty
|
||||
Val []interface{}
|
||||
Flag flag.Flag
|
||||
}
|
||||
|
||||
func (a *Any) Any() interface{} {
|
||||
if a.Flag.IsArray() {
|
||||
return a.Val
|
||||
}
|
||||
|
||||
if len(a.Val) > 0 {
|
||||
return a.Val[0]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package value
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input/value/flag"
|
||||
)
|
||||
|
||||
type Bool struct {
|
||||
empty
|
||||
Val []bool
|
||||
Flag flag.Flag
|
||||
}
|
||||
|
||||
func (b *Bool) Append(in string) error {
|
||||
v, err := strconv.ParseBool(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Val = append(b.Val, v)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bool) Bool() bool {
|
||||
if !b.Flag.IsArray() && len(b.Val) == 1 {
|
||||
return b.Val[0]
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *Bool) Bools() []bool {
|
||||
return b.Val
|
||||
}
|
||||
|
||||
func (b *Bool) Any() interface{} {
|
||||
if b.Flag.IsArray() {
|
||||
return b.Bools()
|
||||
}
|
||||
|
||||
return b.Bool()
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package value
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input/value/flag"
|
||||
)
|
||||
|
||||
type Duration struct {
|
||||
empty
|
||||
Val []time.Duration
|
||||
Flag flag.Flag
|
||||
}
|
||||
|
||||
func (d *Duration) Append(in string) error {
|
||||
v, err := time.ParseDuration(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.Val = append(d.Val, v)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Duration) Duration() time.Duration {
|
||||
if !d.Flag.IsArray() && len(d.Val) == 1 {
|
||||
return d.Val[0]
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (d *Duration) Durations() []time.Duration {
|
||||
return d.Val
|
||||
}
|
||||
|
||||
func (d *Duration) Any() interface{} {
|
||||
if d.Flag.IsArray() {
|
||||
return d.Durations()
|
||||
}
|
||||
|
||||
return d.Duration()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package flag
|
||||
|
||||
//go:generate stringer -type=Flag -linecomment
|
||||
|
||||
type Flag int
|
||||
|
||||
const (
|
||||
String Flag = 0 // string
|
||||
Required Flag = 1 << iota // required
|
||||
Array // array
|
||||
Int // int
|
||||
Int64 // int64
|
||||
Uint // uint
|
||||
Uint64 // uint64
|
||||
Float64 // float64
|
||||
Bool // bool
|
||||
Duration // duration
|
||||
Time // time
|
||||
Any // any
|
||||
)
|
||||
|
||||
func (i Flag) With(v Flag) Flag {
|
||||
return i | v
|
||||
}
|
||||
|
||||
func (i Flag) IsString() bool {
|
||||
return i|Required|Array^Required^Array == 0
|
||||
}
|
||||
|
||||
func (i Flag) IsRequired() bool {
|
||||
return i&Required > 0
|
||||
}
|
||||
|
||||
func (i Flag) IsArray() bool {
|
||||
return i&Array > 0
|
||||
}
|
||||
|
||||
func (i Flag) IsInt() bool {
|
||||
return i&Int > 0
|
||||
}
|
||||
|
||||
func (i Flag) IsInt64() bool {
|
||||
return i&Int64 > 0
|
||||
}
|
||||
|
||||
func (i Flag) IsUint() bool {
|
||||
return i&Uint > 0
|
||||
}
|
||||
|
||||
func (i Flag) IsUint64() bool {
|
||||
return i&Uint64 > 0
|
||||
}
|
||||
|
||||
func (i Flag) IsFloat64() bool {
|
||||
return i&Float64 > 0
|
||||
}
|
||||
|
||||
func (i Flag) IsBool() bool {
|
||||
return i&Bool > 0
|
||||
}
|
||||
|
||||
func (i Flag) IsDuration() bool {
|
||||
return i&Duration > 0
|
||||
}
|
||||
|
||||
func (i Flag) IsTime() bool {
|
||||
return i&Time > 0
|
||||
}
|
||||
|
||||
func (i Flag) IsAny() bool {
|
||||
return i&Any > 0
|
||||
}
|
||||
|
||||
func (i Flag) Type() Flag {
|
||||
switch {
|
||||
case i.IsInt():
|
||||
return Int
|
||||
case i.IsInt64():
|
||||
return Int64
|
||||
case i.IsUint():
|
||||
return Uint
|
||||
case i.IsUint64():
|
||||
return Uint64
|
||||
case i.IsFloat64():
|
||||
return Float64
|
||||
case i.IsBool():
|
||||
return Bool
|
||||
case i.IsDuration():
|
||||
return Duration
|
||||
case i.IsTime():
|
||||
return Time
|
||||
case i.IsAny():
|
||||
return Any
|
||||
default:
|
||||
return String
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// Code generated by "stringer -type=Flag -linecomment"; DO NOT EDIT.
|
||||
|
||||
package flag
|
||||
|
||||
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[String-0]
|
||||
_ = x[Required-2]
|
||||
_ = x[Array-4]
|
||||
_ = x[Int-8]
|
||||
_ = x[Int64-16]
|
||||
_ = x[Uint-32]
|
||||
_ = x[Uint64-64]
|
||||
_ = x[Float64-128]
|
||||
_ = x[Bool-256]
|
||||
_ = x[Duration-512]
|
||||
_ = x[Time-1024]
|
||||
_ = x[Any-2048]
|
||||
}
|
||||
|
||||
const _Flag_name = "stringrequiredarrayintint64uintuint64float64booldurationtimeany"
|
||||
|
||||
var _Flag_map = map[Flag]string{
|
||||
0: _Flag_name[0:6],
|
||||
2: _Flag_name[6:14],
|
||||
4: _Flag_name[14:19],
|
||||
8: _Flag_name[19:22],
|
||||
16: _Flag_name[22:27],
|
||||
32: _Flag_name[27:31],
|
||||
64: _Flag_name[31:37],
|
||||
128: _Flag_name[37:44],
|
||||
256: _Flag_name[44:48],
|
||||
512: _Flag_name[48:56],
|
||||
1024: _Flag_name[56:60],
|
||||
2048: _Flag_name[60:63],
|
||||
}
|
||||
|
||||
func (i Flag) String() string {
|
||||
if str, ok := _Flag_map[i]; ok {
|
||||
return str
|
||||
}
|
||||
return "Flag(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package value
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input/value/flag"
|
||||
)
|
||||
|
||||
type Float64 struct {
|
||||
empty
|
||||
Val []float64
|
||||
Flag flag.Flag
|
||||
}
|
||||
|
||||
func (f *Float64) Append(in string) error {
|
||||
v, err := strconv.ParseFloat(in, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.Val = append(f.Val, v)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Float64) Float64() float64 {
|
||||
if !f.Flag.IsArray() && len(f.Val) == 1 {
|
||||
return f.Val[0]
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f *Float64) Float64s() []float64 {
|
||||
return f.Val
|
||||
}
|
||||
|
||||
func (f *Float64) Any() interface{} {
|
||||
if f.Flag.IsArray() {
|
||||
return f.Float64s()
|
||||
}
|
||||
|
||||
return f.Float64()
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package value
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input/value/flag"
|
||||
)
|
||||
|
||||
type Int struct {
|
||||
empty
|
||||
Val []int
|
||||
Flag flag.Flag
|
||||
}
|
||||
|
||||
func (i *Int) Append(in string) error {
|
||||
v, err := strconv.Atoi(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.Val = append(i.Val, v)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Int) Int() int {
|
||||
if !i.Flag.IsArray() && len(i.Val) == 1 {
|
||||
return i.Val[0]
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (i *Int) Ints() []int {
|
||||
return i.Val
|
||||
}
|
||||
|
||||
func (i *Int) Any() interface{} {
|
||||
if i.Flag.IsArray() {
|
||||
return i.Ints()
|
||||
}
|
||||
|
||||
return i.Int()
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package value
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input/value/flag"
|
||||
)
|
||||
|
||||
type Int64 struct {
|
||||
empty
|
||||
Val []int64
|
||||
Flag flag.Flag
|
||||
}
|
||||
|
||||
func (i *Int64) Int64() int64 {
|
||||
if !i.Flag.IsArray() && len(i.Val) == 1 {
|
||||
return i.Val[0]
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (i *Int64) Int64s() []int64 {
|
||||
return i.Val
|
||||
}
|
||||
|
||||
func (i *Int64) Any() interface{} {
|
||||
if i.Flag.IsArray() {
|
||||
return i.Int64s()
|
||||
}
|
||||
|
||||
return i.Int64()
|
||||
}
|
||||
|
||||
func (i *Int64) Append(in string) error {
|
||||
v, err := strconv.ParseInt(in, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.Val = append(i.Val, v)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package value
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var _ Append = (*Read)(nil)
|
||||
|
||||
var (
|
||||
ErrAppendRead = errors.New("invalid append data to read value")
|
||||
ErrAppendEmpty = errors.New("invalid apped data to empty value")
|
||||
)
|
||||
|
||||
type Read struct {
|
||||
Value
|
||||
}
|
||||
|
||||
func (r *Read) Append(string) error {
|
||||
return ErrAppendRead
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package value
|
||||
|
||||
import "gitoa.ru/go-4devs/console/input/value/flag"
|
||||
|
||||
type String struct {
|
||||
empty
|
||||
Val []string
|
||||
Flag flag.Flag
|
||||
}
|
||||
|
||||
func (s *String) Append(in string) error {
|
||||
s.Val = append(s.Val, in)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *String) String() string {
|
||||
if s.Flag.IsArray() {
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(s.Val) == 1 {
|
||||
return s.Val[0]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *String) Strings() []string {
|
||||
return s.Val
|
||||
}
|
||||
|
||||
func (s *String) Any() interface{} {
|
||||
if s.Flag.IsArray() {
|
||||
return s.Strings()
|
||||
}
|
||||
|
||||
return s.String()
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package value
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input/value/flag"
|
||||
)
|
||||
|
||||
type Time struct {
|
||||
empty
|
||||
Val []time.Time
|
||||
Flag flag.Flag
|
||||
}
|
||||
|
||||
func (t *Time) Append(in string) error {
|
||||
v, err := time.Parse(time.RFC3339, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.Val = append(t.Val, v)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Time) Time() time.Time {
|
||||
if !t.Flag.IsArray() && len(t.Val) == 1 {
|
||||
return t.Val[0]
|
||||
}
|
||||
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (t *Time) Times() []time.Time {
|
||||
return t.Val
|
||||
}
|
||||
|
||||
func (t *Time) Amy() interface{} {
|
||||
if t.Flag.IsArray() {
|
||||
return t.Times()
|
||||
}
|
||||
|
||||
return t.Time()
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package value
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input/value/flag"
|
||||
)
|
||||
|
||||
type Uint struct {
|
||||
empty
|
||||
Val []uint
|
||||
Flag flag.Flag
|
||||
}
|
||||
|
||||
func (u *Uint) Append(in string) error {
|
||||
v, err := strconv.ParseUint(in, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.Val = append(u.Val, uint(v))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Uint) Uint() uint {
|
||||
if !u.Flag.IsArray() && len(u.Val) == 1 {
|
||||
return u.Val[0]
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (u *Uint) Uints() []uint {
|
||||
return u.Val
|
||||
}
|
||||
|
||||
func (u *Uint) Any() interface{} {
|
||||
if u.Flag.IsArray() {
|
||||
return u.Uints()
|
||||
}
|
||||
|
||||
return u.Uint()
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package value
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input/value/flag"
|
||||
)
|
||||
|
||||
type Uint64 struct {
|
||||
empty
|
||||
Val []uint64
|
||||
Flag flag.Flag
|
||||
}
|
||||
|
||||
func (u *Uint64) Append(in string) error {
|
||||
v, err := strconv.ParseUint(in, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.Val = append(u.Val, v)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Uint64) Uint64() uint64 {
|
||||
if !u.Flag.IsArray() && len(u.Val) == 1 {
|
||||
return u.Val[0]
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (u *Uint64) Uint64s() []uint64 {
|
||||
return u.Val
|
||||
}
|
||||
|
||||
func (u *Uint64) Any() interface{} {
|
||||
if u.Flag.IsArray() {
|
||||
return u.Uint64s()
|
||||
}
|
||||
|
||||
return u.Uint64()
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
package value
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input/value/flag"
|
||||
)
|
||||
|
||||
type Value interface {
|
||||
String() string
|
||||
Int() int
|
||||
Int64() int64
|
||||
Uint() uint
|
||||
Uint64() uint64
|
||||
Float64() float64
|
||||
Bool() bool
|
||||
Duration() time.Duration
|
||||
Time() time.Time
|
||||
Any() interface{}
|
||||
|
||||
Strings() []string
|
||||
Ints() []int
|
||||
Int64s() []int64
|
||||
Uints() []uint
|
||||
Uint64s() []uint64
|
||||
Float64s() []float64
|
||||
Bools() []bool
|
||||
Durations() []time.Duration
|
||||
Times() []time.Time
|
||||
}
|
||||
|
||||
type Append interface {
|
||||
Value
|
||||
Append(string) error
|
||||
}
|
||||
|
||||
//nolint: gocyclo
|
||||
func New(v interface{}) Append {
|
||||
switch val := v.(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:
|
||||
return &Bool{Val: []bool{val}, Flag: flag.Bool}
|
||||
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:
|
||||
return &Bool{Val: val, Flag: flag.Bool | flag.Array}
|
||||
case []time.Duration:
|
||||
return &Duration{Val: val, Flag: flag.Duration | flag.Array}
|
||||
case []time.Time:
|
||||
return &Time{Val: val, Flag: flag.Time | flag.Array}
|
||||
case []string:
|
||||
return &String{Val: val, Flag: flag.String | flag.Array}
|
||||
case []int:
|
||||
return &Int{Val: val, Flag: flag.Int | flag.Array}
|
||||
case []interface{}:
|
||||
return &Any{Val: val, Flag: flag.Any | flag.Array}
|
||||
case Append:
|
||||
return val
|
||||
case Value:
|
||||
return &Read{Value: val}
|
||||
default:
|
||||
if v != nil {
|
||||
return &Any{Val: []interface{}{v}, Flag: flag.Any}
|
||||
}
|
||||
|
||||
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{}
|
||||
}
|
||||
}
|
||||
159
list.go
159
list.go
@@ -5,21 +5,28 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input"
|
||||
"gitoa.ru/go-4devs/console/input/argument"
|
||||
"gitoa.ru/go-4devs/console/input/option"
|
||||
"gitoa.ru/go-4devs/console/input/validator"
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/definition"
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
"gitoa.ru/go-4devs/config/param"
|
||||
"gitoa.ru/go-4devs/config/provider/arg"
|
||||
"gitoa.ru/go-4devs/config/validator"
|
||||
"gitoa.ru/go-4devs/config/value"
|
||||
"gitoa.ru/go-4devs/console/output"
|
||||
"gitoa.ru/go-4devs/console/output/descriptor"
|
||||
)
|
||||
|
||||
const defaultLenNamespace = 2
|
||||
|
||||
//nolint: gochecknoinits
|
||||
//nolint:gochecknoinits
|
||||
func init() {
|
||||
MustRegister(list())
|
||||
}
|
||||
|
||||
const (
|
||||
ArgumentNamespace = "namespace"
|
||||
)
|
||||
|
||||
func list() *Command {
|
||||
return &Command{
|
||||
Name: CommandList,
|
||||
@@ -32,75 +39,17 @@ You can also display the commands for a specific namespace:
|
||||
You can also output the information in other formats by using the <comment>--format</comment> option:
|
||||
<info>{{ .Bin }} {{ .Name }} --format=xml</info>
|
||||
`,
|
||||
Execute: func(ctx context.Context, in input.Input, out output.Output) error {
|
||||
ns := in.Argument(ctx, "namespace").String()
|
||||
format := in.Option(ctx, helpOptFormat).String()
|
||||
|
||||
des, err := descriptor.Find(format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmds := Commands()
|
||||
commands := descriptor.Commands{
|
||||
Namespace: ns,
|
||||
Definition: Default(input.NewDefinition()),
|
||||
}
|
||||
groups := make(map[string]*descriptor.NSCommand)
|
||||
namespaces := make([]string, 0, len(cmds))
|
||||
empty := descriptor.NSCommand{}
|
||||
|
||||
for _, name := range cmds {
|
||||
if ns != "" && !strings.HasPrefix(name, ns+":") {
|
||||
continue
|
||||
}
|
||||
|
||||
cmd, _ := Find(name)
|
||||
if cmd.Hidden {
|
||||
continue
|
||||
}
|
||||
|
||||
gn := strings.SplitN(name, ":", 2)
|
||||
if len(gn) != defaultLenNamespace {
|
||||
empty.Append(cmd.Name, cmd.Description)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := groups[gn[0]]; !ok {
|
||||
groups[gn[0]] = &descriptor.NSCommand{
|
||||
Name: gn[0],
|
||||
}
|
||||
namespaces = append(namespaces, gn[0])
|
||||
}
|
||||
|
||||
groups[gn[0]].Append(name, cmd.Description)
|
||||
}
|
||||
if len(empty.Commands) > 0 {
|
||||
commands.Commands = append(commands.Commands, empty)
|
||||
}
|
||||
|
||||
for _, name := range namespaces {
|
||||
commands.Commands = append(commands.Commands, *groups[name])
|
||||
}
|
||||
|
||||
if ns != "" && len(commands.Commands) == 0 {
|
||||
return fmt.Errorf("%w: namespace %s", ErrNotFound, ns)
|
||||
}
|
||||
|
||||
return des.Commands(ctx, out, commands)
|
||||
},
|
||||
Configure: func(ctx context.Context, config *input.Definition) error {
|
||||
Execute: executeList,
|
||||
Configure: func(_ context.Context, cfg config.Definition) error {
|
||||
formats := descriptor.Descriptors()
|
||||
config.
|
||||
SetArguments(
|
||||
argument.New("namespace", "The namespace name"),
|
||||
).
|
||||
SetOptions(
|
||||
option.New(helpOptFormat, fmt.Sprintf("The output format (%s)", strings.Join(formats, ", ")),
|
||||
cfg.
|
||||
Add(
|
||||
arg.String(ArgumentNamespace, "The namespace name"),
|
||||
option.String(OptionFormat, fmt.Sprintf("The output format (%s)", strings.Join(formats, ", ")),
|
||||
option.Required,
|
||||
option.Default(formats[0]),
|
||||
option.Valid(
|
||||
validator.NotBlank(0),
|
||||
option.Default(value.New(formats[0])),
|
||||
validator.Valid(
|
||||
validator.NotBlank,
|
||||
validator.Enum(formats...),
|
||||
),
|
||||
),
|
||||
@@ -110,3 +59,69 @@ You can also output the information in other formats by using the <comment>--for
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:cyclop
|
||||
func executeList(ctx context.Context, in config.Provider, out output.Output) error {
|
||||
ns := ReadValue(ctx, in, ArgumentNamespace).String()
|
||||
format := ReadValue(ctx, in, OptionFormat).String()
|
||||
|
||||
des, err := descriptor.Find(format)
|
||||
if err != nil {
|
||||
return fmt.Errorf("find descriptor[%v]: %w", format, err)
|
||||
}
|
||||
|
||||
cmds := Commands()
|
||||
commands := descriptor.Commands{
|
||||
Namespace: ns,
|
||||
Options: definition.New(Default()...).With(param.New(descriptor.TxtStyle())),
|
||||
}
|
||||
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, ":", defaultLenNamespace)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -5,13 +5,13 @@ import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input"
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/console/output"
|
||||
)
|
||||
|
||||
var ErrDescriptorNotFound = errors.New("descriptor not found")
|
||||
|
||||
//nolint: gochecknoglobals
|
||||
//nolint:gochecknoglobals
|
||||
var (
|
||||
descriptors = map[string]Descriptor{
|
||||
"txt": &txt{},
|
||||
@@ -20,17 +20,19 @@ var (
|
||||
)
|
||||
|
||||
type Command struct {
|
||||
config.Options
|
||||
|
||||
Bin string
|
||||
Name string
|
||||
Description string
|
||||
Help string
|
||||
Definition *input.Definition
|
||||
}
|
||||
|
||||
type Commands struct {
|
||||
Namespace string
|
||||
Definition *input.Definition
|
||||
Commands []NSCommand
|
||||
config.Options
|
||||
|
||||
Namespace string
|
||||
Commands []NSCommand
|
||||
}
|
||||
|
||||
type NSCommand struct {
|
||||
|
||||
@@ -4,20 +4,19 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"gitoa.ru/go-4devs/console/input"
|
||||
"gitoa.ru/go-4devs/console/input/value"
|
||||
"gitoa.ru/go-4devs/console/input/value/flag"
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
"gitoa.ru/go-4devs/config/param"
|
||||
"gitoa.ru/go-4devs/config/provider/arg"
|
||||
"gitoa.ru/go-4devs/console/output"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultSpace = 2
|
||||
infoLen = 13
|
||||
defaultSpace = 2
|
||||
dashDelimiter = "-"
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
@@ -38,27 +37,41 @@ var (
|
||||
|
||||
{{ end -}}
|
||||
<comment>Usage:</comment>
|
||||
{{ .Name }} {{ synopsis .Definition }}
|
||||
{{- definition .Definition }}
|
||||
{{ .Name }} {{ synopsis .Options }}
|
||||
{{ definition .Options }}
|
||||
{{- help . }}
|
||||
`))
|
||||
|
||||
txtListTempkate = template.Must(template.New("txt_list").
|
||||
txtListTemplate = template.Must(template.New("txt_list").
|
||||
Funcs(txtFunc).
|
||||
Parse(`<comment>Usage:</comment>
|
||||
command [options] [arguments]
|
||||
{{- definition .Definition }}
|
||||
{{ definition .Options }}
|
||||
{{- commands .Commands -}}
|
||||
`))
|
||||
)
|
||||
|
||||
func TxtStyle() param.Option {
|
||||
return arg.WithStyle(
|
||||
arg.Style{
|
||||
Start: "<comment>",
|
||||
End: "</comment>",
|
||||
},
|
||||
arg.Style{
|
||||
Start: "<info>",
|
||||
End: "</info>",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
type txt struct{}
|
||||
|
||||
func (t *txt) Command(ctx context.Context, out output.Output, cmd Command) error {
|
||||
var tpl bytes.Buffer
|
||||
|
||||
if err := txtHelpTemplate.Execute(&tpl, cmd); err != nil {
|
||||
return err
|
||||
err := txtHelpTemplate.Execute(&tpl, cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute txt help tpl:%w", err)
|
||||
}
|
||||
|
||||
out.Println(ctx, tpl.String())
|
||||
@@ -69,8 +82,9 @@ func (t *txt) Command(ctx context.Context, out output.Output, cmd Command) error
|
||||
func (t *txt) Commands(ctx context.Context, out output.Output, cmds Commands) error {
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := txtListTempkate.Execute(&buf, cmds); err != nil {
|
||||
return err
|
||||
err := txtListTemplate.Execute(&buf, cmds)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute txt list tpl:%w", err)
|
||||
}
|
||||
|
||||
out.Println(ctx, buf.String())
|
||||
@@ -78,78 +92,8 @@ func (t *txt) Commands(ctx context.Context, out output.Output, cmds Commands) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func txtDefaultArray(v value.Value, f flag.Flag) string {
|
||||
st := v.Strings()
|
||||
|
||||
switch {
|
||||
case f.IsInt():
|
||||
for _, i := range v.Ints() {
|
||||
st = append(st, strconv.Itoa(i))
|
||||
}
|
||||
case f.IsInt64():
|
||||
for _, i := range v.Int64s() {
|
||||
st = append(st, strconv.FormatInt(i, 10))
|
||||
}
|
||||
case f.IsUint():
|
||||
for _, u := range v.Uints() {
|
||||
st = append(st, strconv.FormatUint(uint64(u), 10))
|
||||
}
|
||||
case f.IsUint64():
|
||||
for _, u := range v.Uint64s() {
|
||||
st = append(st, strconv.FormatUint(u, 10))
|
||||
}
|
||||
case f.IsFloat64():
|
||||
for _, f := range v.Float64s() {
|
||||
st = append(st, strconv.FormatFloat(f, 'g', -1, 64))
|
||||
}
|
||||
case f.IsDuration():
|
||||
for _, d := range v.Durations() {
|
||||
st = append(st, d.String())
|
||||
}
|
||||
case f.IsTime():
|
||||
for _, d := range v.Times() {
|
||||
st = append(st, d.Format(time.RFC3339))
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(st, ",")
|
||||
}
|
||||
|
||||
func txtDefault(v value.Value, f flag.Flag) []byte {
|
||||
var buf bytes.Buffer
|
||||
|
||||
buf.WriteString("<comment> [default: ")
|
||||
|
||||
switch {
|
||||
case f.IsArray():
|
||||
buf.WriteString(txtDefaultArray(v, f))
|
||||
case f.IsInt():
|
||||
buf.WriteString(strconv.Itoa(v.Int()))
|
||||
case f.IsInt64():
|
||||
buf.WriteString(strconv.FormatInt(v.Int64(), 10))
|
||||
case f.IsUint():
|
||||
buf.WriteString(strconv.FormatUint(uint64(v.Uint()), 10))
|
||||
case f.IsUint64():
|
||||
buf.WriteString(strconv.FormatUint(v.Uint64(), 10))
|
||||
case f.IsFloat64():
|
||||
buf.WriteString(strconv.FormatFloat(v.Float64(), 'g', -1, 64))
|
||||
case f.IsDuration():
|
||||
buf.WriteString(v.Duration().String())
|
||||
case f.IsTime():
|
||||
buf.WriteString(v.Time().Format(time.RFC3339))
|
||||
case f.IsAny():
|
||||
buf.WriteString(fmt.Sprint(v.Any()))
|
||||
default:
|
||||
buf.WriteString(v.String())
|
||||
}
|
||||
|
||||
buf.WriteString("]</comment>")
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func txtCommands(cmds []NSCommand) string {
|
||||
max := commandsTotalWidth(cmds)
|
||||
width := commandsTotalWidth(cmds)
|
||||
showNS := len(cmds) > 1
|
||||
|
||||
var buf bytes.Buffer
|
||||
@@ -175,7 +119,7 @@ func txtCommands(cmds []NSCommand) string {
|
||||
buf.WriteString(" <info>")
|
||||
buf.WriteString(cmd.Name)
|
||||
buf.WriteString("</info>")
|
||||
buf.WriteString(strings.Repeat(" ", max-len(cmd.Name)+defaultSpace))
|
||||
buf.WriteString(strings.Repeat(" ", width-len(cmd.Name)+defaultSpace))
|
||||
buf.WriteString(cmd.Description)
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
@@ -199,124 +143,48 @@ func txtHelp(cmd Command) string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func txtDefinitionOption(maxLen int, def *input.Definition) string {
|
||||
buf := bytes.Buffer{}
|
||||
opts := def.Options()
|
||||
|
||||
buf.WriteString("\n\n<comment>Options:</comment>\n")
|
||||
|
||||
for _, name := range opts {
|
||||
opt, _ := def.Option(name)
|
||||
|
||||
var op bytes.Buffer
|
||||
|
||||
op.WriteString(" <info>")
|
||||
|
||||
if opt.HasShort() {
|
||||
op.WriteString("-")
|
||||
op.WriteString(opt.Short)
|
||||
op.WriteString(", ")
|
||||
} else {
|
||||
op.WriteString(" ")
|
||||
}
|
||||
|
||||
op.WriteString("--")
|
||||
op.WriteString(opt.Name)
|
||||
|
||||
if !opt.IsBool() {
|
||||
if !opt.IsRequired() {
|
||||
op.WriteString("[")
|
||||
}
|
||||
|
||||
op.WriteString("=")
|
||||
op.WriteString(strings.ToUpper(opt.Name))
|
||||
|
||||
if !opt.IsRequired() {
|
||||
op.WriteString("]")
|
||||
}
|
||||
}
|
||||
|
||||
op.WriteString("</info>")
|
||||
buf.Write(op.Bytes())
|
||||
buf.WriteString(strings.Repeat(" ", maxLen+17-op.Len()))
|
||||
buf.WriteString(opt.Description)
|
||||
|
||||
if opt.HasDefault() {
|
||||
buf.Write(txtDefault(opt.Default, opt.Flag))
|
||||
}
|
||||
|
||||
if opt.IsArray() {
|
||||
buf.WriteString("<comment> (multiple values allowed)</comment>")
|
||||
}
|
||||
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func txtDefinition(def *input.Definition) string {
|
||||
max := totalWidth(def)
|
||||
|
||||
func txtDefinition(options config.Options) string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
if args := def.Arguments(); len(args) > 0 {
|
||||
buf.WriteString("\n\n<comment>Arguments:</comment>\n")
|
||||
|
||||
for pos := range args {
|
||||
var ab bytes.Buffer
|
||||
|
||||
arg, _ := def.Argument(pos)
|
||||
|
||||
ab.WriteString(" <info>")
|
||||
ab.WriteString(arg.Name)
|
||||
ab.WriteString("</info>")
|
||||
ab.WriteString(strings.Repeat(" ", max+infoLen+defaultSpace-ab.Len()))
|
||||
|
||||
buf.Write(ab.Bytes())
|
||||
buf.WriteString(arg.Description)
|
||||
|
||||
if arg.HasDefault() {
|
||||
buf.Write(txtDefault(arg.Default, arg.Flag))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts := def.Options(); len(opts) > 0 {
|
||||
buf.WriteString(txtDefinitionOption(max, def))
|
||||
err := arg.NewDump().Reference(&buf, options)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func txtSynopsis(def *input.Definition) string {
|
||||
func txtSynopsis(options config.Options) string {
|
||||
def := arg.NewViews(options, nil)
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
if len(def.Options()) > 0 {
|
||||
buf.WriteString("[options] ")
|
||||
}
|
||||
|
||||
if buf.Len() > 0 && len(def.Arguments()) > 0 {
|
||||
args := def.Arguments()
|
||||
|
||||
if buf.Len() > 0 && len(args) > 0 {
|
||||
buf.WriteString("[--]")
|
||||
}
|
||||
|
||||
var opt int
|
||||
|
||||
for pos := range def.Arguments() {
|
||||
for _, arg := range args {
|
||||
buf.WriteString(" ")
|
||||
|
||||
arg, _ := def.Argument(pos)
|
||||
|
||||
if !arg.IsRequired() {
|
||||
if !option.IsRequired(arg) {
|
||||
buf.WriteString("[")
|
||||
|
||||
opt++
|
||||
}
|
||||
|
||||
buf.WriteString("<")
|
||||
buf.WriteString(arg.Name)
|
||||
buf.WriteString(arg.Name(dashDelimiter))
|
||||
buf.WriteString(">")
|
||||
|
||||
if arg.IsArray() {
|
||||
if option.IsSlice(arg) {
|
||||
buf.WriteString("...")
|
||||
}
|
||||
}
|
||||
@@ -327,47 +195,15 @@ func txtSynopsis(def *input.Definition) string {
|
||||
}
|
||||
|
||||
func commandsTotalWidth(cmds []NSCommand) int {
|
||||
var max int
|
||||
var width int
|
||||
|
||||
for _, ns := range cmds {
|
||||
for _, cmd := range ns.Commands {
|
||||
if len(cmd.Name) > max {
|
||||
max = len(cmd.Name)
|
||||
if len(cmd.Name) > width {
|
||||
width = len(cmd.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return max
|
||||
}
|
||||
|
||||
func totalWidth(def *input.Definition) int {
|
||||
var max int
|
||||
|
||||
for pos := range def.Arguments() {
|
||||
arg, _ := def.Argument(pos)
|
||||
l := len(arg.Name)
|
||||
|
||||
if l > max {
|
||||
max = l
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range def.Options() {
|
||||
opt, _ := def.Option(name)
|
||||
l := len(opt.Name) + 6
|
||||
|
||||
if !opt.IsBool() {
|
||||
l = l*2 + 1
|
||||
}
|
||||
|
||||
if opt.HasDefault() {
|
||||
l += 2
|
||||
}
|
||||
|
||||
if l > max {
|
||||
max = l
|
||||
}
|
||||
}
|
||||
|
||||
return max
|
||||
return width
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"gitoa.ru/go-4devs/console/output/style"
|
||||
)
|
||||
|
||||
//nolint: gochecknoglobals
|
||||
var re = regexp.MustCompile(`<(([a-z][^<>]+)|/([a-z][^<>]+)?)>`)
|
||||
|
||||
func WithStyle(styles func(string) (style.Style, error)) func(*Formatter) {
|
||||
@@ -18,22 +17,22 @@ func WithStyle(styles func(string) (style.Style, error)) func(*Formatter) {
|
||||
}
|
||||
|
||||
func New(opts ...func(*Formatter)) *Formatter {
|
||||
f := &Formatter{
|
||||
formatter := &Formatter{
|
||||
styles: style.Find,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(f)
|
||||
opt(formatter)
|
||||
}
|
||||
|
||||
return f
|
||||
return formatter
|
||||
}
|
||||
|
||||
type Formatter struct {
|
||||
styles func(string) (style.Style, error)
|
||||
}
|
||||
|
||||
func (a *Formatter) Format(ctx context.Context, msg string) string {
|
||||
func (a *Formatter) Format(_ context.Context, msg string) string {
|
||||
var (
|
||||
out bytes.Buffer
|
||||
cur int
|
||||
@@ -51,8 +50,8 @@ func (a *Formatter) Format(ctx context.Context, msg string) string {
|
||||
err error
|
||||
)
|
||||
|
||||
switch {
|
||||
case tag[0:1] == "/":
|
||||
switch tag[0:1] {
|
||||
case "/":
|
||||
st, err = a.styles(tag[1:])
|
||||
if err == nil {
|
||||
out.WriteString(st.Set(style.ActionUnset))
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
)
|
||||
|
||||
func TestFormatter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
formatter := formatter.New()
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
)
|
||||
|
||||
func TestNone(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
none := formatter.None()
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package label
|
||||
|
||||
type Key string
|
||||
|
||||
func (k Key) Any(v interface{}) KeyValue {
|
||||
func (k Key) Any(v any) KeyValue {
|
||||
return KeyValue{
|
||||
Key: k,
|
||||
Value: AnyValue(v),
|
||||
|
||||
@@ -30,7 +30,7 @@ func (k KeyValue) String() string {
|
||||
return string(k.Key) + "=\"" + k.Value.String() + "\""
|
||||
}
|
||||
|
||||
func Any(k string, v interface{}) KeyValue {
|
||||
func Any(k string, v any) KeyValue {
|
||||
return Key(k).Any(v)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,14 +17,14 @@ const (
|
||||
|
||||
type Value struct {
|
||||
vtype Type
|
||||
value interface{}
|
||||
value any
|
||||
}
|
||||
|
||||
func (v Value) String() string {
|
||||
return fmt.Sprint(v.value)
|
||||
}
|
||||
|
||||
func AnyValue(v interface{}) Value {
|
||||
func AnyValue(v any) Value {
|
||||
return Value{vtype: TypeAny, value: v}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ func writeError(_ int, err error) {
|
||||
|
||||
type Output func(ctx context.Context, verb verbosity.Verbosity, msg string, args ...label.KeyValue) (int, error)
|
||||
|
||||
func (o Output) Print(ctx context.Context, args ...interface{}) {
|
||||
func (o Output) Print(ctx context.Context, args ...any) {
|
||||
writeError(o(ctx, verbosity.Norm, fmt.Sprint(args...)))
|
||||
}
|
||||
|
||||
@@ -26,15 +26,15 @@ func (o Output) PrintKV(ctx context.Context, msg string, kv ...label.KeyValue) {
|
||||
writeError(o(ctx, verbosity.Norm, msg, kv...))
|
||||
}
|
||||
|
||||
func (o Output) Printf(ctx context.Context, format string, args ...interface{}) {
|
||||
func (o Output) Printf(ctx context.Context, format string, args ...any) {
|
||||
writeError(o(ctx, verbosity.Norm, fmt.Sprintf(format, args...)))
|
||||
}
|
||||
|
||||
func (o Output) Println(ctx context.Context, args ...interface{}) {
|
||||
func (o Output) Println(ctx context.Context, args ...any) {
|
||||
writeError(o(ctx, verbosity.Norm, fmt.Sprintln(args...)))
|
||||
}
|
||||
|
||||
func (o Output) Info(ctx context.Context, args ...interface{}) {
|
||||
func (o Output) Info(ctx context.Context, args ...any) {
|
||||
writeError(o(ctx, verbosity.Info, fmt.Sprint(args...)))
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func (o Output) InfoKV(ctx context.Context, msg string, kv ...label.KeyValue) {
|
||||
writeError(o(ctx, verbosity.Info, msg, kv...))
|
||||
}
|
||||
|
||||
func (o Output) Debug(ctx context.Context, args ...interface{}) {
|
||||
func (o Output) Debug(ctx context.Context, args ...any) {
|
||||
writeError(o(ctx, verbosity.Debug, fmt.Sprint(args...)))
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func (o Output) DebugKV(ctx context.Context, msg string, kv ...label.KeyValue) {
|
||||
writeError(o(ctx, verbosity.Debug, msg, kv...))
|
||||
}
|
||||
|
||||
func (o Output) Trace(ctx context.Context, args ...interface{}) {
|
||||
func (o Output) Trace(ctx context.Context, args ...any) {
|
||||
writeError(o(ctx, verbosity.Trace, fmt.Sprint(args...)))
|
||||
}
|
||||
|
||||
@@ -63,15 +63,13 @@ func (o Output) Write(b []byte) (int, error) {
|
||||
}
|
||||
|
||||
func (o Output) Writer(ctx context.Context, verb verbosity.Verbosity) io.Writer {
|
||||
return verbosityWriter{ctx, o, verb}
|
||||
return verbosityWriter(func(b []byte) (int, error) {
|
||||
return o(ctx, verb, string(b))
|
||||
})
|
||||
}
|
||||
|
||||
type verbosityWriter struct {
|
||||
ctx context.Context
|
||||
out Output
|
||||
verb verbosity.Verbosity
|
||||
}
|
||||
type verbosityWriter func(b []byte) (int, error)
|
||||
|
||||
func (w verbosityWriter) Write(b []byte) (int, error) {
|
||||
return w.out(w.ctx, w.verb, string(b))
|
||||
return w(b)
|
||||
}
|
||||
|
||||
@@ -28,13 +28,13 @@ const (
|
||||
type Option string
|
||||
|
||||
func (o Option) Apply(action int) string {
|
||||
v := string(o)
|
||||
out := string(o)
|
||||
|
||||
switch action {
|
||||
case ActionSet:
|
||||
return v[0:1]
|
||||
return out[0:1]
|
||||
case ActionUnset:
|
||||
return v[1:]
|
||||
return out[1:]
|
||||
}
|
||||
|
||||
return ""
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
//nolint: gochecknoglobals
|
||||
//nolint:gochecknoglobals
|
||||
var (
|
||||
styles = map[string]Style{
|
||||
"error": {Foreground: White, Background: Red},
|
||||
@@ -50,7 +50,8 @@ func Register(name string, style Style) error {
|
||||
}
|
||||
|
||||
func MustRegister(name string, style Style) {
|
||||
if err := Register(name, style); err != nil {
|
||||
err := Register(name, style)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package verbosity
|
||||
|
||||
//go:generate stringer -type=Verbosity -linecomment
|
||||
//go:generate go tool stringer -type=Verbosity -linecomment
|
||||
|
||||
type Verbosity int
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ const _Verbosity_name = "quietnorminfodebugtrace"
|
||||
var _Verbosity_index = [...]uint8{0, 5, 9, 13, 18, 23}
|
||||
|
||||
func (i Verbosity) String() string {
|
||||
i -= -1
|
||||
if i < 0 || i >= Verbosity(len(_Verbosity_index)-1) {
|
||||
return "Verbosity(" + strconv.FormatInt(int64(i+-1), 10) + ")"
|
||||
idx := int(i) - -1
|
||||
if i < -1 || idx >= len(_Verbosity_index)-1 {
|
||||
return "Verbosity(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Verbosity_name[_Verbosity_index[i]:_Verbosity_index[i+1]]
|
||||
return _Verbosity_name[_Verbosity_index[idx]:_Verbosity_index[idx+1]]
|
||||
}
|
||||
|
||||
@@ -40,7 +40,12 @@ func FormatString(_ verbosity.Verbosity, msg string, kv ...label.KeyValue) strin
|
||||
}
|
||||
|
||||
func New(w io.Writer, format func(verb verbosity.Verbosity, msg string, kv ...label.KeyValue) string) Output {
|
||||
return func(ctx context.Context, verb verbosity.Verbosity, msg string, kv ...label.KeyValue) (int, error) {
|
||||
return fmt.Fprint(w, format(verb, msg, kv...))
|
||||
return func(_ context.Context, verb verbosity.Verbosity, msg string, kv ...label.KeyValue) (int, error) {
|
||||
out, err := fmt.Fprint(w, format(verb, msg, kv...))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("writer fprint:%w", err)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
buf := bytes.Buffer{}
|
||||
wr := output.New(&buf, output.FormatString)
|
||||
|
||||
37
register.go
37
register.go
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -14,43 +13,17 @@ const (
|
||||
CommandList = "list"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("command not found")
|
||||
ErrCommandNil = errors.New("console: Register command is nil")
|
||||
ErrCommandDuplicate = errors.New("console: duplicate command")
|
||||
)
|
||||
|
||||
//nolint: gochecknoglobals
|
||||
//nolint:gochecknoglobals
|
||||
var (
|
||||
commandsMu sync.RWMutex
|
||||
commands = make(map[string]*Command)
|
||||
findCommand = regexp.MustCompile("([^:]+|)")
|
||||
)
|
||||
|
||||
type ErrorAlternatives struct {
|
||||
alt []string
|
||||
err error
|
||||
}
|
||||
|
||||
func (e ErrorAlternatives) Error() string {
|
||||
return fmt.Sprintf("%s, alternatives: [%s]", e.err, strings.Join(e.alt, ","))
|
||||
}
|
||||
|
||||
func (e ErrorAlternatives) Is(err error) bool {
|
||||
return errors.Is(e.err, err)
|
||||
}
|
||||
|
||||
func (e ErrorAlternatives) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
func (e ErrorAlternatives) Alternatives() []string {
|
||||
return e.alt
|
||||
}
|
||||
|
||||
// MustRegister register command or panic if err.
|
||||
func MustRegister(cmd *Command) {
|
||||
if err := Register(cmd); err != nil {
|
||||
err := Register(cmd)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -115,7 +88,7 @@ func Find(name string) (*Command, error) {
|
||||
|
||||
cmdRegexp, err := regexp.Compile("^" + nameRegexp + "$")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("find by regexp:%w", err)
|
||||
}
|
||||
|
||||
for name := range commands {
|
||||
@@ -134,7 +107,7 @@ func Find(name string) (*Command, error) {
|
||||
names[i] = findCommands[i].Name
|
||||
}
|
||||
|
||||
return nil, ErrorAlternatives{alt: names, err: ErrNotFound}
|
||||
return nil, AlternativesError{Alt: names, Err: ErrNotFound}
|
||||
}
|
||||
|
||||
return nil, ErrNotFound
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
)
|
||||
|
||||
func TestFind(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := map[string]string{
|
||||
"fdevs:console:test": "fdevs:console:test",
|
||||
"fd:c:t": "fdevs:console:test",
|
||||
@@ -18,13 +20,13 @@ func TestFind(t *testing.T) {
|
||||
for name, ex := range cases {
|
||||
res, err := console.Find(name)
|
||||
if err != nil {
|
||||
t.Errorf("expect <nil> err, got:%s", err)
|
||||
t.Errorf("%v expect <nil> err, got:%s", name, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if res.Name != ex {
|
||||
t.Errorf("expect: %s, got: %s", ex, res)
|
||||
t.Errorf("%v expect: %s, got: %s", name, ex, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user