Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 0 additions & 4 deletions flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,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
}
47 changes: 4 additions & 43 deletions flag_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,6 @@ func (f *fnValue) String() string {
return f.v.String()
}

func (f *fnValue) Serialize() string {
if s, ok := f.v.(Serializer); ok {
return s.Serialize()
}
return f.v.String()
}

func (f *fnValue) IsBoolFlag() bool { return f.isBool }
func (f *fnValue) Count() int {
if s, ok := f.v.(Countable); ok {
Expand Down Expand Up @@ -96,15 +89,6 @@ type FlagBase[T any, C any, VC ValueCreator[T, C]] struct {
value Value // value representing this flag's value
}

// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *FlagBase[T, C, V]) GetValue() string {
if reflect.TypeOf(f.Value).Kind() == reflect.Bool {
return ""
}
return fmt.Sprintf("%v", f.Value)
}

// Apply populates the flag given the flag set and environment
func (f *FlagBase[T, C, V]) Apply(set *flag.FlagSet) error {
tracef("apply (flag=%[1]q)", f.Name)
Expand All @@ -128,13 +112,7 @@ func (f *FlagBase[T, C, V]) Apply(set *flag.FlagSet) error {
)
}
} else if val == "" && reflect.TypeOf(f.Value).Kind() == reflect.Bool {
val = "false"
if err := tmpVal.Set(val); err != nil {
return fmt.Errorf(
"could not parse %[1]q as %[2]T value from %[3]s for flag %[4]s: %[5]s",
val, f.Value, source, f.Name, err,
)
}
_ = tmpVal.Set("false")
}

newVal = tmpVal.Get().(T)
Expand All @@ -149,11 +127,7 @@ func (f *FlagBase[T, C, V]) Apply(set *flag.FlagSet) error {

// Validate the given default or values set from external sources as well
if f.Validator != nil && f.ValidateDefaults {
if v, ok := f.value.Get().(T); !ok {
return &typeError[T]{
other: f.value.Get(),
}
} else if err := f.Validator(v); err != nil {
if err := f.Validator(f.value.Get().(T)); err != nil {
return err
}
}
Expand All @@ -176,11 +150,7 @@ func (f *FlagBase[T, C, V]) Apply(set *flag.FlagSet) error {
}
f.hasBeenSet = true
if f.Validator != nil {
if v, ok := f.value.Get().(T); !ok {
return &typeError[T]{
other: f.value.Get(),
}
} else if err := f.Validator(v); err != nil {
if err := f.Validator(f.value.Get().(T)); err != nil {
return err
}
}
Expand Down Expand Up @@ -254,19 +224,10 @@ func (f *FlagBase[T, C, V]) GetDefaultText() string {
return v.ToString(f.Value)
}

// Get returns the flag’s value in the given Command.
func (f *FlagBase[T, C, V]) Get(cmd *Command) T {
if v, ok := cmd.Value(f.Name).(T); ok {
return v
}
var t T
return t
}

// RunAction executes flag action if set
func (f *FlagBase[T, C, V]) RunAction(ctx context.Context, cmd *Command) error {
if f.Action != nil {
return f.Action(ctx, cmd, f.Get(cmd))
return f.Action(ctx, cmd, cmd.Value(f.Name).(T))
}

return nil
Expand Down
Loading