mirror of
https://github.com/foomo/posh.git
synced 2025-10-16 12:45:38 +00:00
feat: extend command tree
This commit is contained in:
parent
81ae13c6db
commit
c87c709849
@ -8,6 +8,7 @@ import (
|
||||
|
||||
intenv "github.com/foomo/posh/internal/env"
|
||||
intlog "github.com/foomo/posh/internal/log"
|
||||
"github.com/foomo/posh/pkg/log"
|
||||
"github.com/foomo/posh/pkg/plugin"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
@ -41,6 +42,7 @@ func Init(provider plugin.Provider) {
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
code := 0
|
||||
l = log.NewFmt()
|
||||
|
||||
// handle interrupt
|
||||
osInterrupt := make(chan os.Signal, 1)
|
||||
|
||||
@ -16,11 +16,6 @@ env:
|
||||
- name: GOPROXY
|
||||
value: "https://proxy.golang.org,direct"
|
||||
|
||||
## Plugin settings
|
||||
plugin:
|
||||
source: .posh/plugin.go
|
||||
provider: New
|
||||
|
||||
## Ownbrew settings
|
||||
ownbrew:
|
||||
binDir: "bin"
|
||||
@ -60,7 +55,15 @@ require:
|
||||
|
||||
## Required scripts that need to succeed
|
||||
scripts: []
|
||||
## Example: require
|
||||
## Example: require 1Password account
|
||||
#- name: op
|
||||
# command: |
|
||||
# [[ $(op account --account <ACCOUNT> get 2>&1) =~ "found no account" ]] && exit 1 || exit 0
|
||||
# help: |
|
||||
# You're 1Password account is not registered yet! Please do so by running:
|
||||
#
|
||||
# $ op account add --address <ACCOUNT>.1password.eu --email <EMAIL>
|
||||
## Example: npm
|
||||
#- name: npm
|
||||
# command: npm whoami --registry=https://npm.pkg.github.com > /dev/null 2>&1
|
||||
# help: |
|
||||
@ -138,13 +141,13 @@ require:
|
||||
# $ brew install teleport
|
||||
|
||||
## Example: goimports
|
||||
# - name: goimports
|
||||
# version: '>=2022'
|
||||
# command: date -r $(which goimports) +%Y.%-m.%-d
|
||||
# help: |
|
||||
# Please ensure you have 'goimports' installed in a recent version: %s!
|
||||
#- name: goimports
|
||||
# version: '>=2022'
|
||||
# command: date -r $(which goimports) +%Y.%-m.%-d
|
||||
# help: |
|
||||
# Please ensure you have 'goimports' installed in a recent version: %s!
|
||||
#
|
||||
# $ go install golang.org/x/tools/cmd/goimports@latest
|
||||
# $ go install golang.org/x/tools/cmd/goimports@latest
|
||||
|
||||
|
||||
## Integrations
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
*.so
|
||||
*.lock
|
||||
/config/
|
||||
/tmp/
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
module {{ .module }}/posh
|
||||
|
||||
go 1.19
|
||||
|
||||
replace (
|
||||
github.com/c-bata/go-prompt v0.2.6 => github.com/franklinkim/go-prompt v0.2.7-0.20210427061716-a8f4995d7aa5
|
||||
)
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
module {{.module}}
|
||||
|
||||
go 1.19
|
||||
|
||||
replace (
|
||||
github.com/c-bata/go-prompt v0.2.6 => github.com/franklinkim/go-prompt v0.2.7-0.20210427061716-a8f4995d7aa5
|
||||
)
|
||||
3
go.mod
3
go.mod
@ -16,6 +16,7 @@ require (
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.14.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/whilp/git-urls v1.0.0
|
||||
)
|
||||
|
||||
@ -28,6 +29,7 @@ require (
|
||||
github.com/acomagu/bufpipe v1.0.3 // indirect
|
||||
github.com/cloudflare/circl v1.1.0 // indirect
|
||||
github.com/containerd/console v1.0.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
@ -58,6 +60,7 @@ require (
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||
github.com/pjbgf/sha1cd v0.2.3 // indirect
|
||||
github.com/pkg/term v1.1.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/sergi/go-diff v1.2.0 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@ -314,6 +314,7 @@ github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU=
|
||||
github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
@ -322,6 +323,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
|
||||
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU=
|
||||
|
||||
@ -28,9 +28,6 @@ type (
|
||||
ArgumentCompleter interface {
|
||||
CompleteArguments(ctx context.Context, r *readline.Readline, d prompt.Document) []prompt.Suggest
|
||||
}
|
||||
PassThroughArgsCompleter interface {
|
||||
CompletePassTroughArgs(ctx context.Context, r *readline.Readline, d prompt.Document) []prompt.Suggest
|
||||
}
|
||||
PassThroughFlagsCompleter interface {
|
||||
CompletePassTroughFlags(ctx context.Context, r *readline.Readline, d prompt.Document) []prompt.Suggest
|
||||
}
|
||||
|
||||
12
pkg/command/tree/errors.go
Normal file
12
pkg/command/tree/errors.go
Normal file
@ -0,0 +1,12 @@
|
||||
package tree
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoop = errors.New("noop")
|
||||
ErrInvalidCommand = errors.New("invalid command")
|
||||
ErrMissingCommand = errors.New("missing command")
|
||||
ErrMissingArgument = errors.New("missing argument")
|
||||
)
|
||||
@ -11,7 +11,7 @@ import (
|
||||
|
||||
type Node struct {
|
||||
Name string
|
||||
Names func() []string
|
||||
Values func(ctx context.Context, r *readline.Readline) []string
|
||||
Args Args
|
||||
Flags func(fs *readline.FlagSet)
|
||||
PassThroughArgs Args
|
||||
@ -51,7 +51,13 @@ func (c *Node) completeArguments(ctx context.Context, p *Root, r *readline.Readl
|
||||
switch {
|
||||
case len(c.Nodes) > 0 && len(localArgs) <= 1:
|
||||
for _, command := range c.Nodes {
|
||||
suggest = append(suggest, prompt.Suggest{Text: command.Name, Description: command.Description})
|
||||
if command.Values != nil {
|
||||
for _, name := range command.Values(ctx, r) {
|
||||
suggest = append(suggest, prompt.Suggest{Text: name, Description: command.Description})
|
||||
}
|
||||
} else {
|
||||
suggest = append(suggest, prompt.Suggest{Text: command.Name, Description: command.Description})
|
||||
}
|
||||
}
|
||||
case len(c.Args) > 0 && len(c.Args) >= len(localArgs):
|
||||
j := len(localArgs)
|
||||
@ -89,11 +95,11 @@ func (c *Node) execute(ctx context.Context, r *readline.Readline, i int) error {
|
||||
localArgs := r.Args()[i:]
|
||||
switch {
|
||||
case len(c.Nodes) > 0 && len(localArgs) == 0:
|
||||
return errors.New("missing [command] argument")
|
||||
return ErrMissingCommand
|
||||
case len(c.Args) > 0:
|
||||
for j, arg := range c.Args {
|
||||
if !arg.Optional && len(localArgs)-2 < j {
|
||||
return errors.New("missing [" + arg.Name + "] argument")
|
||||
if !arg.Optional && len(localArgs)-1 < j {
|
||||
return errors.Wrap(ErrMissingArgument, arg.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/c-bata/go-prompt"
|
||||
"github.com/foomo/posh/pkg/readline"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Root struct {
|
||||
@ -25,17 +24,23 @@ func (t *Root) RunExecution(ctx context.Context, r *readline.Readline) error {
|
||||
cmd *Node
|
||||
index int
|
||||
)
|
||||
if r.Args().LenIs(0) {
|
||||
cmd = t.Node
|
||||
} else if found, i := t.find(t.Nodes, r, 0); found != nil {
|
||||
cmd = found
|
||||
index = i
|
||||
} else {
|
||||
cmd = t.Node
|
||||
|
||||
switch {
|
||||
case t.Node == nil && len(t.Nodes) == 0:
|
||||
return ErrNoop
|
||||
case r.Args().LenIs(0) && t.Node == nil:
|
||||
return ErrMissingCommand
|
||||
}
|
||||
|
||||
if cmd == nil {
|
||||
return errors.New("invalid command")
|
||||
if r.Args().LenIs(0) {
|
||||
cmd = t.Node
|
||||
} else if found, i := t.find(ctx, t.Nodes, r, 0); found != nil {
|
||||
cmd = found
|
||||
index = i
|
||||
} else if t.Node == nil {
|
||||
return ErrInvalidCommand
|
||||
} else {
|
||||
cmd = t.Node
|
||||
}
|
||||
|
||||
if err := cmd.setFlags(r, true); err != nil {
|
||||
@ -52,9 +57,15 @@ func (t *Root) RunCompletion(ctx context.Context, r *readline.Readline) []prompt
|
||||
case readline.ModeArgs:
|
||||
if r.Args().LenLte(1) && len(t.Nodes) > 0 {
|
||||
for _, command := range t.Nodes {
|
||||
suggests = append(suggests, prompt.Suggest{Text: command.Name, Description: command.Description})
|
||||
if command.Values != nil {
|
||||
for _, name := range command.Values(ctx, r) {
|
||||
suggests = append(suggests, prompt.Suggest{Text: name, Description: command.Description})
|
||||
}
|
||||
} else {
|
||||
suggests = append(suggests, prompt.Suggest{Text: command.Name, Description: command.Description})
|
||||
}
|
||||
}
|
||||
} else if cmd, i := t.find(t.Nodes, r, 0); cmd == nil && t.Node != nil {
|
||||
} else if cmd, i := t.find(ctx, t.Nodes, r, 0); cmd == nil && t.Node != nil {
|
||||
if err := t.Node.setFlags(r, false); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
@ -68,7 +79,7 @@ func (t *Root) RunCompletion(ctx context.Context, r *readline.Readline) []prompt
|
||||
suggests = cmd.completeArguments(ctx, t, r, i+1)
|
||||
}
|
||||
case readline.ModeFlags:
|
||||
if cmd, _ := t.find(t.Nodes, r, 0); cmd == nil && t.Node != nil {
|
||||
if cmd, _ := t.find(ctx, t.Nodes, r, 0); cmd == nil && t.Node != nil {
|
||||
if err := t.Node.setFlags(r, false); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
@ -81,10 +92,8 @@ func (t *Root) RunCompletion(ctx context.Context, r *readline.Readline) []prompt
|
||||
} else {
|
||||
suggests = cmd.completeFlags(r)
|
||||
}
|
||||
case readline.ModePassThroughArgs:
|
||||
// TODO
|
||||
case readline.ModePassThroughFlags:
|
||||
if cmd, _ := t.find(t.Nodes, r, 0); cmd == nil && t.Node != nil {
|
||||
if cmd, _ := t.find(ctx, t.Nodes, r, 0); cmd == nil && t.Node != nil {
|
||||
if err := t.Node.setFlags(r, false); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
@ -110,26 +119,26 @@ func (t *Root) RunCompletion(ctx context.Context, r *readline.Readline) []prompt
|
||||
// ~ Private methods
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func (t *Root) find(cmds []*Node, r *readline.Readline, i int) (*Node, int) {
|
||||
func (t *Root) find(ctx context.Context, cmds []*Node, r *readline.Readline, i int) (*Node, int) {
|
||||
if r.Args().LenLt(i + 1) {
|
||||
return nil, i
|
||||
}
|
||||
arg := r.Args().At(i)
|
||||
for _, cmd := range cmds {
|
||||
if cmd.Name == arg {
|
||||
if subCmd, j := t.find(cmd.Nodes, r, i+1); subCmd != nil {
|
||||
if subCmd, j := t.find(ctx, cmd.Nodes, r, i+1); subCmd != nil {
|
||||
return subCmd, j
|
||||
}
|
||||
return cmd, i
|
||||
}
|
||||
if cmd.Names != nil {
|
||||
for _, name := range cmd.Names() {
|
||||
if cmd.Values != nil {
|
||||
for _, name := range cmd.Values(ctx, r) {
|
||||
if name == arg {
|
||||
if subCmd, j := t.find(cmd.Nodes, r, i+1); subCmd != nil {
|
||||
if subCmd, j := t.find(ctx, cmd.Nodes, r, i+1); subCmd != nil {
|
||||
return subCmd, j
|
||||
}
|
||||
return cmd, i
|
||||
}
|
||||
return cmd, i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
427
pkg/command/tree/root_test.go
Normal file
427
pkg/command/tree/root_test.go
Normal file
@ -0,0 +1,427 @@
|
||||
package tree_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/foomo/posh/pkg/command/tree"
|
||||
"github.com/foomo/posh/pkg/log"
|
||||
"github.com/foomo/posh/pkg/readline"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var ErrOK = errors.New("ok")
|
||||
|
||||
func TestRoot(t *testing.T) {
|
||||
l := log.NewTest(t, log.TestWithLevel(log.LevelInfo))
|
||||
ctx := context.TODO()
|
||||
|
||||
var (
|
||||
ErrRoot = errors.New("root")
|
||||
ErrFirst = errors.New("first")
|
||||
ErrSecond = errors.New("second")
|
||||
ErrSecond1 = errors.New("second-1")
|
||||
ErrSecond2 = errors.New("second-2")
|
||||
ErrThird = errors.New("third")
|
||||
ErrThird1 = errors.New("third1")
|
||||
)
|
||||
|
||||
r := &tree.Root{
|
||||
Name: "root",
|
||||
Description: "root tree",
|
||||
Node: &tree.Node{
|
||||
Execute: func(ctx context.Context, r *readline.Readline) error {
|
||||
return ErrRoot
|
||||
},
|
||||
},
|
||||
Nodes: tree.Nodes{
|
||||
{
|
||||
Name: "first",
|
||||
Execute: func(ctx context.Context, r *readline.Readline) error {
|
||||
return ErrFirst
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "second",
|
||||
Nodes: tree.Nodes{
|
||||
{
|
||||
Name: "second-1",
|
||||
Execute: func(ctx context.Context, r *readline.Readline) error {
|
||||
return ErrSecond1
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "second-2",
|
||||
Execute: func(ctx context.Context, r *readline.Readline) error {
|
||||
return ErrSecond2
|
||||
},
|
||||
},
|
||||
},
|
||||
Execute: func(ctx context.Context, r *readline.Readline) error {
|
||||
return ErrSecond
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "third",
|
||||
Values: func(ctx context.Context, r *readline.Readline) []string {
|
||||
return []string{"third-a", "third-b"}
|
||||
},
|
||||
Nodes: tree.Nodes{
|
||||
{
|
||||
Name: "third-1",
|
||||
Execute: func(ctx context.Context, r *readline.Readline) error {
|
||||
return ErrThird1
|
||||
},
|
||||
},
|
||||
},
|
||||
Execute: func(ctx context.Context, r *readline.Readline) error {
|
||||
return ErrThird
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "tree",
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.ErrorIs(t, err, ErrRoot)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tree first",
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.ErrorIs(t, err, ErrFirst)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tree first foo",
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.ErrorIs(t, err, ErrFirst)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tree second",
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.ErrorIs(t, err, ErrSecond)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tree second second-1",
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.ErrorIs(t, err, ErrSecond1)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tree second second-2",
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.ErrorIs(t, err, ErrSecond2)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tree second second-3",
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.ErrorIs(t, err, ErrSecond)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tree third-a",
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.ErrorIs(t, err, ErrThird)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tree third-b",
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.ErrorIs(t, err, ErrThird)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tree third-c",
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.ErrorIs(t, err, ErrRoot)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tree third-a third-1",
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.ErrorIs(t, err, ErrThird1)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rl, err := readline.New(l)
|
||||
require.NoError(t, err)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t1 *testing.T) {
|
||||
require.NoError(t1, rl.Parse(tt.name))
|
||||
if !tt.wantErr(t1, r.RunExecution(context.WithValue(ctx, "t", t1), rl)) {
|
||||
l.Warn(rl.String())
|
||||
} else {
|
||||
l.Debug(rl.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoot_Node(t *testing.T) {
|
||||
l := log.NewTest(t, log.TestWithLevel(log.LevelInfo))
|
||||
ctx := context.TODO()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
root *tree.Root
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "tree",
|
||||
root: &tree.Root{},
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.ErrorIs(t, err, tree.ErrNoop)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tree",
|
||||
root: &tree.Root{
|
||||
Node: &tree.Node{
|
||||
Execute: func(ctx context.Context, r *readline.Readline) error {
|
||||
return ErrOK
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.ErrorIs(t, err, ErrOK)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tree one",
|
||||
root: &tree.Root{
|
||||
Node: &tree.Node{
|
||||
Execute: func(ctx context.Context, r *readline.Readline) error {
|
||||
assert.Equal(T(ctx), "one", r.Args().At(0))
|
||||
return ErrOK
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.ErrorIs(t, err, ErrOK)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rl, err := readline.New(l)
|
||||
require.NoError(t, err)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t1 *testing.T) {
|
||||
require.NoError(t1, rl.Parse(tt.name))
|
||||
if !tt.wantErr(t1, tt.root.RunExecution(context.WithValue(ctx, "t", t1), rl)) {
|
||||
l.Warn(rl.String())
|
||||
} else {
|
||||
l.Debug(rl.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoot_NodeArgs(t *testing.T) {
|
||||
l := log.NewTest(t, log.TestWithLevel(log.LevelInfo))
|
||||
ctx := context.TODO()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
root *tree.Root
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "tree",
|
||||
root: &tree.Root{
|
||||
Node: &tree.Node{
|
||||
Args: tree.Args{
|
||||
{
|
||||
Name: "first",
|
||||
},
|
||||
},
|
||||
Execute: func(ctx context.Context, r *readline.Readline) error {
|
||||
return ErrOK
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.ErrorIs(t, err, tree.ErrMissingArgument)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tree one",
|
||||
root: &tree.Root{
|
||||
Node: &tree.Node{
|
||||
Args: tree.Args{
|
||||
{
|
||||
Name: "first",
|
||||
},
|
||||
},
|
||||
Execute: func(ctx context.Context, r *readline.Readline) error {
|
||||
assert.Equal(T(ctx), "one", r.Args().At(0))
|
||||
return ErrOK
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.ErrorIs(t, err, ErrOK)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tree",
|
||||
root: &tree.Root{
|
||||
Node: &tree.Node{
|
||||
Args: tree.Args{
|
||||
{
|
||||
Name: "first",
|
||||
},
|
||||
{
|
||||
Name: "second",
|
||||
},
|
||||
},
|
||||
Execute: func(ctx context.Context, r *readline.Readline) error {
|
||||
return ErrOK
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.ErrorIs(t, err, tree.ErrMissingArgument)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tree one",
|
||||
root: &tree.Root{
|
||||
Node: &tree.Node{
|
||||
Args: tree.Args{
|
||||
{
|
||||
Name: "first",
|
||||
},
|
||||
{
|
||||
Name: "second",
|
||||
},
|
||||
},
|
||||
Execute: func(ctx context.Context, r *readline.Readline) error {
|
||||
return ErrOK
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.ErrorIs(t, err, tree.ErrMissingArgument)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tree one two",
|
||||
root: &tree.Root{
|
||||
Node: &tree.Node{
|
||||
Args: tree.Args{
|
||||
{
|
||||
Name: "first",
|
||||
},
|
||||
{
|
||||
Name: "second",
|
||||
},
|
||||
},
|
||||
Execute: func(ctx context.Context, r *readline.Readline) error {
|
||||
assert.Equal(T(ctx), "one", r.Args().At(0))
|
||||
assert.Equal(T(ctx), "two", r.Args().At(1))
|
||||
return ErrOK
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.ErrorIs(t, err, ErrOK)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rl, err := readline.New(l)
|
||||
require.NoError(t, err)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t1 *testing.T) {
|
||||
require.NoError(t1, rl.Parse(tt.name))
|
||||
if !tt.wantErr(t1, tt.root.RunExecution(context.WithValue(ctx, "t", t1), rl)) {
|
||||
l.Warn(rl.String())
|
||||
} else {
|
||||
l.Debug(rl.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoot_NodeFlags(t *testing.T) {
|
||||
l := log.NewTest(t, log.TestWithLevel(log.LevelDebug))
|
||||
ctx := context.TODO()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
root *tree.Root
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "tree",
|
||||
root: &tree.Root{
|
||||
Node: &tree.Node{
|
||||
Flags: func(fs *readline.FlagSet) {
|
||||
fs.String("first", "first", "first")
|
||||
fs.Bool("second", false, "second")
|
||||
fs.Int64("third", 0, "third")
|
||||
},
|
||||
Execute: func(ctx context.Context, r *readline.Readline) error {
|
||||
assert.Equal(T(ctx), "first", r.FlagSet().GetString("first"))
|
||||
assert.False(T(ctx), r.FlagSet().GetBool("second"))
|
||||
assert.Equal(T(ctx), int64(0), r.FlagSet().GetInt64("third"))
|
||||
return ErrOK
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.ErrorIs(t, err, ErrOK)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tree --first one --second --third 13",
|
||||
root: &tree.Root{
|
||||
Node: &tree.Node{
|
||||
Flags: func(fs *readline.FlagSet) {
|
||||
fs.String("first", "first", "first")
|
||||
fs.Bool("second", false, "second")
|
||||
fs.Int64("third", 0, "third")
|
||||
},
|
||||
Execute: func(ctx context.Context, r *readline.Readline) error {
|
||||
assert.Equal(T(ctx), "one", r.FlagSet().GetString("first"))
|
||||
assert.True(T(ctx), r.FlagSet().GetBool("second"))
|
||||
assert.Equal(T(ctx), int64(13), r.FlagSet().GetInt64("third"))
|
||||
return ErrOK
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.ErrorIs(t, err, ErrOK)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rl, err := readline.New(l)
|
||||
require.NoError(t, err)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t1 *testing.T) {
|
||||
require.NoError(t1, rl.Parse(tt.name))
|
||||
if !tt.wantErr(t1, tt.root.RunExecution(context.WithValue(ctx, "t", t1), rl)) {
|
||||
l.Warn(rl.String())
|
||||
} else {
|
||||
l.Debug(rl.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func T(ctx context.Context) *testing.T {
|
||||
return ctx.Value("t").(*testing.T)
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Plugin struct {
|
||||
Source string `json:"source" yaml:"source"`
|
||||
Target string `json:"target" yaml:"target"`
|
||||
Provider string `json:"provider" yaml:"provider"`
|
||||
}
|
||||
|
||||
func (c Plugin) String() string {
|
||||
return fmt.Sprintf("Filename: %s, Provider: %s", c.Source, c.Provider)
|
||||
}
|
||||
155
pkg/log/test.go
Normal file
155
pkg/log/test.go
Normal file
@ -0,0 +1,155 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type (
|
||||
Test struct {
|
||||
t *testing.T
|
||||
name string
|
||||
level Level
|
||||
}
|
||||
TestOption func(*Test)
|
||||
)
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Options
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func TestWithLevel(v Level) TestOption {
|
||||
return func(o *Test) {
|
||||
o.level = v
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Constructor
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func NewTest(t *testing.T, opts ...TestOption) *Test {
|
||||
inst := &Test{
|
||||
t: t,
|
||||
level: LevelError,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
opt(inst)
|
||||
}
|
||||
}
|
||||
return inst
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Public methods
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func (l *Test) Level() Level {
|
||||
return l.level
|
||||
}
|
||||
|
||||
func (l *Test) IsLevel(v Level) bool {
|
||||
return l.level <= v
|
||||
}
|
||||
|
||||
func (l *Test) Named(name string) Logger {
|
||||
clone := *l
|
||||
clone.name = name
|
||||
return &clone
|
||||
}
|
||||
|
||||
func (l *Test) Print(a ...interface{}) {
|
||||
l.t.Log(l.prefix("", a)...)
|
||||
}
|
||||
|
||||
func (l *Test) Printf(format string, a ...interface{}) {
|
||||
l.Print(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func (l *Test) Success(a ...interface{}) {
|
||||
l.t.Log(l.prefix("success", a)...)
|
||||
}
|
||||
|
||||
func (l *Test) Successf(format string, a ...interface{}) {
|
||||
l.Success(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func (l *Test) Trace(a ...interface{}) {
|
||||
if l.IsLevel(LevelTrace) {
|
||||
l.t.Log(l.prefix("trace", a)...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Test) Tracef(format string, a ...interface{}) {
|
||||
l.Trace(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func (l *Test) Debug(a ...interface{}) {
|
||||
if l.IsLevel(LevelDebug) {
|
||||
l.t.Log(l.prefix("debug", a)...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Test) Debugf(format string, a ...interface{}) {
|
||||
l.Debug(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func (l *Test) Info(a ...interface{}) {
|
||||
if l.IsLevel(LevelInfo) {
|
||||
l.t.Log(l.prefix("info", a)...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Test) Infof(format string, a ...interface{}) {
|
||||
l.Info(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func (l *Test) Warn(a ...interface{}) {
|
||||
if l.IsLevel(LevelWarn) {
|
||||
l.t.Log(l.prefix("warn", a)...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Test) Warnf(format string, a ...interface{}) {
|
||||
l.Warn(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func (l *Test) Error(a ...interface{}) {
|
||||
if l.IsLevel(LevelError) {
|
||||
l.t.Error(l.prefix("error", a)...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Test) Errorf(format string, a ...interface{}) {
|
||||
l.Error(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func (l *Test) Fatal(a ...interface{}) {
|
||||
l.t.Fatal(l.prefix("fatal", a)...)
|
||||
}
|
||||
|
||||
func (l *Test) Fatalf(format string, a ...interface{}) {
|
||||
l.Fatal(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func (l *Test) Must(err error) {
|
||||
if err != nil {
|
||||
l.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// ~ Private methods
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func (l *Test) prefix(level string, a []any) []any {
|
||||
var ret []interface{}
|
||||
if level != "" {
|
||||
ret = append(ret, level+":")
|
||||
}
|
||||
if l.name != "" && l.IsLevel(LevelDebug) {
|
||||
ret = append(ret, fmt.Sprintf("[%s]", l.name))
|
||||
}
|
||||
return append(ret, a...)
|
||||
}
|
||||
@ -318,12 +318,6 @@ func (s *Prompt) complete(d prompt.Document) []prompt.Suggest {
|
||||
} else if value, ok := cmd.(command.Completer); ok {
|
||||
return s.filter(value.Complete(ctx, s.readline, d), word, true)
|
||||
}
|
||||
case readline.ModePassThroughArgs:
|
||||
if value, ok := cmd.(command.PassThroughArgsCompleter); ok {
|
||||
return s.filter(value.CompletePassTroughArgs(ctx, s.readline, d), word, true)
|
||||
} else if value, ok := cmd.(command.Completer); ok {
|
||||
return s.filter(value.Complete(ctx, s.readline, d), word, true)
|
||||
}
|
||||
case readline.ModePassThroughFlags:
|
||||
if value, ok := cmd.(command.PassThroughFlagsCompleter); ok {
|
||||
return s.filter(value.CompletePassTroughFlags(ctx, s.readline, d), word, true)
|
||||
|
||||
@ -70,3 +70,20 @@ func (a Args) Last() string {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (a Args) IndexOf(v string) int {
|
||||
for i, s := range a {
|
||||
if s == v {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (a Args) Slice(start, end int) Args {
|
||||
return append(a[:start], a[end:]...)
|
||||
}
|
||||
|
||||
func (a Args) Splice(start, num int) Args {
|
||||
return append(a[:start], a[start+num:]...)
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@ type Mode string
|
||||
const (
|
||||
ModeArgs Mode = ""
|
||||
ModeFlags Mode = "flags"
|
||||
ModePassThroughArgs Mode = "passThrough"
|
||||
ModePassThroughFlags Mode = "passThroughFlags"
|
||||
ModeAdditionalArgs Mode = "additional"
|
||||
)
|
||||
|
||||
@ -18,7 +18,6 @@ type (
|
||||
args Args
|
||||
flags Args
|
||||
flagSet *FlagSet
|
||||
passThroughArgs Args
|
||||
passThroughFlags Args
|
||||
passThroughFlagSet *FlagSet
|
||||
additionalArgs Args
|
||||
@ -92,12 +91,6 @@ func (a *Readline) FlagSet() *FlagSet {
|
||||
return a.flagSet
|
||||
}
|
||||
|
||||
func (a *Readline) PassThroughArgs() Args {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.passThroughArgs
|
||||
}
|
||||
|
||||
func (a *Readline) PassThroughFlags() Args {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -138,9 +131,6 @@ func (a *Readline) Parse(input string) error {
|
||||
a.mode = ModeFlags
|
||||
}
|
||||
if i != last && (a.mode == ModeArgs || a.mode == ModeFlags) && Arg(part).IsPass() {
|
||||
a.mode = ModePassThroughArgs
|
||||
}
|
||||
if a.mode == ModePassThroughArgs && Arg(part).IsFlag() {
|
||||
a.mode = ModePassThroughFlags
|
||||
}
|
||||
if Arg(part).IsAdditional() && i < len(parts)-1 {
|
||||
@ -152,8 +142,6 @@ func (a *Readline) Parse(input string) error {
|
||||
a.args = append(a.args, part)
|
||||
case ModeFlags:
|
||||
a.flags = append(a.flags, part)
|
||||
case ModePassThroughArgs:
|
||||
a.passThroughArgs = append(a.passThroughArgs, part)
|
||||
case ModePassThroughFlags:
|
||||
a.passThroughFlags = append(a.passThroughFlags, part)
|
||||
case ModeAdditionalArgs:
|
||||
@ -200,10 +188,9 @@ Cmd: %s
|
||||
Mode %s
|
||||
Args: %s
|
||||
Flags: %s
|
||||
PassThroughArgs: %s
|
||||
PassThroughFlags: %s
|
||||
AdditionalArgs %s
|
||||
`, a.Cmd(), a.Mode(), a.Args(), a.Flags(), a.PassThroughArgs(), a.PassThroughFlags(), a.AdditionalArgs())
|
||||
`, a.Cmd(), a.Mode(), a.Args(), a.Flags(), a.PassThroughFlags(), a.AdditionalArgs())
|
||||
}
|
||||
|
||||
func (a *Readline) IsModeDefault() bool {
|
||||
@ -211,7 +198,7 @@ func (a *Readline) IsModeDefault() bool {
|
||||
}
|
||||
|
||||
func (a *Readline) IsModePassThrough() bool {
|
||||
return a.Mode() == ModePassThroughArgs
|
||||
return a.Mode() == ModePassThroughFlags
|
||||
}
|
||||
|
||||
func (a *Readline) IsModeAdditional() bool {
|
||||
@ -258,7 +245,6 @@ func (a *Readline) reset() {
|
||||
a.args = nil
|
||||
a.flags = nil
|
||||
a.flagSet = nil
|
||||
a.passThroughArgs = nil
|
||||
a.passThroughFlags = nil
|
||||
a.passThroughFlagSet = nil
|
||||
a.additionalArgs = nil
|
||||
|
||||
22
pkg/util/files/mkdirall.go
Normal file
22
pkg/util/files/mkdirall.go
Normal file
@ -0,0 +1,22 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func MkdirAll(paths ...string) error {
|
||||
for _, path := range paths {
|
||||
if stat, err := os.Stat(path); err != nil && os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(path, os.ModeDir|0722); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
} else if !stat.IsDir() {
|
||||
return errors.Errorf("%s not a directory", path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user