feat: extend command tree

This commit is contained in:
Kevin Franklin Kim 2023-01-18 11:40:37 +01:00
parent 81ae13c6db
commit c87c709849
19 changed files with 703 additions and 88 deletions

View File

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

View File

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

View File

@ -1,4 +1,3 @@
*.so
*.lock
/config/
/tmp/

View File

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

View File

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

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

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

View File

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

View 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")
)

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,6 @@ type Mode string
const (
ModeArgs Mode = ""
ModeFlags Mode = "flags"
ModePassThroughArgs Mode = "passThrough"
ModePassThroughFlags Mode = "passThroughFlags"
ModeAdditionalArgs Mode = "additional"
)

View File

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

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