Skip to content
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions args.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,11 @@ func (a *ArgumentBase[T, C, VC]) Usage() string {
func (a *ArgumentBase[T, C, VC]) Parse(s []string) ([]string, error) {
tracef("calling arg%[1] parse with args %[2]", &a.Name, s)
if a.Max == 0 {
fmt.Printf("WARNING args %s has max 0, not parsing argument", a.Name)
fmt.Printf("WARNING args %s has max 0, not parsing argument\n", a.Name)
return s, nil
}
if a.Max != -1 && a.Min > a.Max {
fmt.Printf("WARNING args %s has min[%d] > max[%d], not parsing argument", a.Name, a.Min, a.Max)
fmt.Printf("WARNING args %s has min[%d] > max[%d], not parsing argument\n", a.Name, a.Min, a.Max)
return s, nil
}

Expand Down
23 changes: 22 additions & 1 deletion args_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ func TestArgumentsRootCommand(t *testing.T) {
require.Error(t, errors.New("No help topic for '12.1"), cmd.Run(context.Background(), []string{"foo", "13", "10.1", "11.09", "12.1"}))
require.Equal(t, int64(13), ival)
require.Equal(t, []float64{10.1, 11.09}, fvals)

cmd.Arguments = append(cmd.Arguments,
&StringArg{
Name: "sa",
},
&UintArg{
Name: "ua",
Min: 2,
Max: 1, // max is less than min
},
)

require.NoError(t, cmd.Run(context.Background(), []string{"foo", "10"}))
}

func TestArgumentsSubcommand(t *testing.T) {
Expand Down Expand Up @@ -103,6 +116,7 @@ func TestArgsUsage(t *testing.T) {
name string
min int
max int
usage string
expected string
}{
{
Expand All @@ -111,6 +125,13 @@ func TestArgsUsage(t *testing.T) {
max: 1,
expected: "[ia]",
},
{
name: "optional",
min: 0,
max: 1,
usage: "[my optional usage]",
expected: "[my optional usage]",
},
{
name: "zero or more",
min: 0,
Expand Down Expand Up @@ -144,7 +165,7 @@ func TestArgsUsage(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
arg.Min, arg.Max = test.min, test.max
arg.Min, arg.Max, arg.UsageText = test.min, test.max, test.usage
require.Equal(t, test.expected, arg.Usage())
})
}
Expand Down
31 changes: 31 additions & 0 deletions cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Expand All @@ -26,3 +27,33 @@ func buildTestContext(t *testing.T) context.Context {

return ctx
}

func TestTracing(t *testing.T) {
olderr := os.Stderr
oldtracing := isTracingOn
defer func() {
os.Stderr = olderr
isTracingOn = oldtracing
}()

file, err := os.CreateTemp(os.TempDir(), "cli*")
assert.NoError(t, err)
os.Stderr = file

// Note we cant really set the env since the isTracingOn
// is read at module startup so any changes mid code
// wont take effect
isTracingOn = false
tracef("something")

isTracingOn = true
tracef("foothing")

assert.NoError(t, file.Close())

b, err := os.ReadFile(file.Name())
assert.NoError(t, err)

assert.Contains(t, string(b), "foothing")
assert.NotContains(t, string(b), "something")
}
15 changes: 15 additions & 0 deletions command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3975,6 +3975,21 @@ func TestZeroValueCommand(t *testing.T) {
assert.NoError(t, cmd.Run(context.Background(), []string{"foo"}))
}

func TestCommandInvalidName(t *testing.T) {
var cmd Command
assert.Equal(t, int64(0), cmd.Int("foo"))
assert.Equal(t, uint64(0), cmd.Uint("foo"))
assert.Equal(t, float64(0), cmd.Float("foo"))
assert.Equal(t, "", cmd.String("foo"))
assert.Equal(t, time.Time{}, cmd.Timestamp("foo"))
assert.Equal(t, time.Duration(0), cmd.Duration("foo"))

assert.Equal(t, []int64(nil), cmd.IntSlice("foo"))
assert.Equal(t, []uint64(nil), cmd.UintSlice("foo"))
assert.Equal(t, []float64(nil), cmd.FloatSlice("foo"))
assert.Equal(t, []string(nil), cmd.StringSlice("foo"))
}

func TestJSONExportCommand(t *testing.T) {
cmd := buildExtendedTestCommand()
cmd.Arguments = []Argument{
Expand Down
24 changes: 0 additions & 24 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ func (m *multiError) Errors() []error {

type requiredFlagsErr interface {
error
getMissingFlags() []string
}

type errRequiredFlags struct {
Expand All @@ -62,10 +61,6 @@ func (e *errRequiredFlags) Error() string {
return fmt.Sprintf("Required flags %q not set", joinedMissingFlags)
}

func (e *errRequiredFlags) getMissingFlags() []string {
return e.missingFlags
}

type mutuallyExclusiveGroup struct {
flag1Name string
flag2Name string
Expand All @@ -86,12 +81,6 @@ func (e *mutuallyExclusiveGroupRequiredFlag) Error() string {
for _, f := range grpf {
grpString = append(grpString, f.Names()...)
}
if len(e.flags.Flags) == 1 {
err := errRequiredFlags{
missingFlags: grpString,
}
return err.Error()
}
missingFlags = append(missingFlags, strings.Join(grpString, " "))
}

Expand Down Expand Up @@ -148,10 +137,6 @@ func (ee *exitError) ExitCode() int {
return ee.exitCode
}

func (ee *exitError) Unwrap() error {
return ee.err
}

// HandleExitCoder handles errors implementing ExitCoder by printing their
// message and calling OsExiter with the given exit code.
//
Expand Down Expand Up @@ -198,12 +183,3 @@ func handleMultiError(multiErr MultiError) int {
}
return code
}

type typeError[T any] struct {
other any
}

func (te *typeError[T]) Error() string {
var t T
return fmt.Sprintf("Expected type %T got instead %T", t, te.other)
}
49 changes: 49 additions & 0 deletions errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,62 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) {

exitErr := Exit("galactic perimeter breach", 9)
exitErr2 := Exit("last ExitCoder", 11)

err := newMultiError(errors.New("wowsa"), errors.New("egad"), exitErr, exitErr2)
HandleExitCoder(err)

assert.Equal(t, 11, exitCode)
assert.True(t, called)
}

type exitFormatter struct {
code int
}

func (f *exitFormatter) Format(s fmt.State, verb rune) {
_, _ = s.Write([]byte("some other special"))
}

func (f *exitFormatter) ExitCode() int {
return f.code
}

func (f *exitFormatter) Error() string {
return fmt.Sprintf("my special error code %d", f.code)
}

func TestHandleExitCoder_ErrorFormatter(t *testing.T) {
exitCode := 0
called := false

OsExiter = func(rc int) {
if !called {
exitCode = rc
called = true
}
}

oldWriter := ErrWriter
var buf bytes.Buffer
ErrWriter = &buf
defer func() {
OsExiter = fakeOsExiter
ErrWriter = oldWriter
}()

exitErr := Exit("galactic perimeter breach", 9)
exitErr2 := Exit("last ExitCoder", 11)
exitErr3 := &exitFormatter{code: 12}

// add some recursion for multi error to fix test coverage
err := newMultiError(errors.New("wowsa"), errors.New("egad"), exitErr3, newMultiError(exitErr, exitErr2))
HandleExitCoder(err)

assert.Equal(t, 11, exitCode)
assert.True(t, called)
assert.Contains(t, buf.String(), "some other special")
}

func TestHandleExitCoder_MultiErrorWithoutExitCoder(t *testing.T) {
exitCode := 0
called := false
Expand Down
24 changes: 0 additions & 24 deletions flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,26 +73,6 @@ var FlagEnvHinter FlagEnvHintFunc = withEnvHint
// details. This is used by the default FlagStringer.
var FlagFileHinter FlagFileHintFunc = withFileHint

// FlagsByName is a slice of Flag.
type FlagsByName []Flag

func (f FlagsByName) Len() int {
return len(f)
}

func (f FlagsByName) Less(i, j int) bool {
if len(f[j].Names()) == 0 {
return false
} else if len(f[i].Names()) == 0 {
return true
}
return lexicographicLess(f[i].Names()[0], f[j].Names()[0])
}

func (f FlagsByName) Swap(i, j int) {
f[i], f[j] = f[j], f[i]
}

// ActionableFlag is an interface that wraps Flag interface and RunAction operation.
type ActionableFlag interface {
RunAction(context.Context, *Command) error
Expand Down Expand Up @@ -129,10 +109,6 @@ type DocGenerationFlag interface {
// GetUsage returns the usage string for the flag
GetUsage() string

// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
GetValue() string

// GetDefaultText returns the default text for this flag
GetDefaultText() string

Expand Down
23 changes: 4 additions & 19 deletions flag_float_slice.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package cli

import (
"flag"
)

type (
FloatSlice = SliceBase[float64, NoConfig, floatValue]
FloatSliceFlag = FlagBase[[]float64, NoConfig, FloatSlice]
Expand All @@ -14,22 +10,11 @@ var NewFloatSlice = NewSliceBase[float64, NoConfig, floatValue]
// FloatSlice looks up the value of a local FloatSliceFlag, returns
// nil if not found
func (cmd *Command) FloatSlice(name string) []float64 {
if flSet := cmd.lookupFlagSet(name); flSet != nil {
return lookupFloatSlice(name, flSet, cmd.Name)
}

return nil
}

func lookupFloatSlice(name string, set *flag.FlagSet, cmdName string) []float64 {
fl := set.Lookup(name)
if fl != nil {
if v, ok := fl.Value.(flag.Getter).Get().([]float64); ok {
tracef("float slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmdName)
return v
}
if v, ok := cmd.Value(name).([]float64); ok {
tracef("float slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
return v
}

tracef("float slice NOT available for flag name %[1]q (cmd=%[2]q)", name, cmdName)
tracef("float slice NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
return nil
}
Loading
Loading