Skip to content
Open
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
1 change: 1 addition & 0 deletions command_parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ func (cmd *Command) parseFlags(args Args) (Args, error) {

firstArg := strings.TrimSpace(rargs[0])
if len(firstArg) == 0 {
posArgs = append(posArgs, rargs[0:]...)
break
}

Expand Down
2 changes: 1 addition & 1 deletion command_setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func (cmd *Command) setupDefaults(osArgs []string) {
flag.VisitAll(func(f *flag.Flag) {
// skip test flags
if !strings.HasPrefix(f.Name, ignoreFlagPrefix) {
cmd.Flags = append(cmd.Flags, &extFlag{f})
cmd.Flags = append(cmd.Flags, &extFlag{f, ""})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this change from this PR.

}
})
}
Expand Down
14 changes: 8 additions & 6 deletions command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -867,12 +867,12 @@ var defaultCommandSubCommandTests = []struct {
{"", "jimbob", "foobar", true},
{"", "j", "foobar", true},
{"", "carly", "foobar", true},
{"", "jimmers", "foobar", true},
{"", "jimmers", "foobar", false},
{"", "jimmers", "", true},
{" ", "jimmers", "foobar", true},
/*{"", "", "", true},
{" ", "jimmers", "foobar", false},
{"", "", "", true},
{" ", "", "", false},
{" ", "j", "", false},*/
{" ", "j", "", false},
{"bat", "", "batbaz", true},
{"nothing", "", "batbaz", true},
{"nothing", "", "", false},
Expand Down Expand Up @@ -917,6 +917,7 @@ var defaultCommandFlagTests = []struct {
}{
{"foobar", "", "foobar", true},
{"foobar", "-c derp", "foobar", true},
{"foobar", "-c=", "foobar", true},
{"batbaz", "", "foobar", true},
{"b", "", "", true},
{"f", "", "", true},
Expand All @@ -930,13 +931,14 @@ var defaultCommandFlagTests = []struct {
{"", "-j", "", true},
{" ", "-j", "foobar", true},
{"", "", "", true},
{" ", "", "", true},
{" ", "-j", "", true},
{" ", "", "", false},
{" ", "-j", "", false},
{"bat", "", "batbaz", true},
{"nothing", "", "batbaz", true},
{"nothing", "", "", false},
{"nothing", "--jimbob", "batbaz", true},
{"nothing", "--carly", "", false},
{"nothing", "--carly=", "", false},
}

func TestCommand_RunDefaultCommandWithFlags(t *testing.T) {
Expand Down
159 changes: 156 additions & 3 deletions flag_ext.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,72 @@
package cli

import "flag"
import (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this from PR

"context"
"flag"
"fmt"
"reflect"
"strings"
)

var _ Value = (*externalValue)(nil)

// -- Value Value
type externalValue struct {
e *extFlag
}

// Below functions are to satisfy the flag.Value interface

func (ev *externalValue) Set(s string) error {
if ev != nil && ev.e.f != nil {
return ev.e.f.Value.Set(s)
}
return nil
}

func (ev *externalValue) Get() any {
if ev != nil && ev.e.f != nil {
return ev.e.f.Value.(flag.Getter).Get()
}
return nil
}

func (ev *externalValue) String() string {
if ev != nil && ev.e.f != nil {
return ev.e.String()
}
return ""
}

func (ev *externalValue) IsBoolFlag() bool {
if ev == nil || ev.e.f == nil {
return false
}
bf, ok := ev.e.f.Value.(boolFlag)
return ok && bf.IsBoolFlag()
}

var _ Flag = (*extFlag)(nil)

Check failure on line 49 in flag_ext.go

View workflow job for this annotation

GitHub Actions / golangci-lint

File is not properly formatted (gofumpt)
var _ ActionableFlag = (*extFlag)(nil)
var _ CategorizableFlag = (*extFlag)(nil)
var _ DocGenerationFlag = (*extFlag)(nil)
var _ DocGenerationMultiValueFlag = (*extFlag)(nil)
var _ LocalFlag = (*extFlag)(nil)
var _ RequiredFlag = (*extFlag)(nil)
var _ VisibleFlag = (*extFlag)(nil)

type extFlag struct {
f *flag.Flag
f *flag.Flag
category string
}

func (e *extFlag) PreParse() error {
if e.f.DefValue != "" {
return e.Set("", e.f.DefValue)
// suppress errors for write-only external flags that always return nil
if err := e.Set("", e.f.DefValue); err != nil && e.f.Value.(flag.Getter).Get() != nil {
// wrap error with some context for the user
return fmt.Errorf("external flag --%s default %q: %w", e.f.Name, e.f.DefValue, err)
}
}

return nil
Expand All @@ -30,6 +88,43 @@
return []string{e.f.Name}
}

// IsBoolFlag returns whether the flag doesn't need to accept args
func (e *extFlag) IsBoolFlag() bool {
if e == nil || e.f == nil {
return false
}
return (&externalValue{e}).IsBoolFlag()
}

// IsDefaultVisible returns true if the flag is not hidden, otherwise false
func (e *extFlag) IsDefaultVisible() bool {
return true
}

// IsLocal returns false if flag needs to be persistent across subcommands
func (e *extFlag) IsLocal() bool {
return false
}

// IsMultiValueFlag returns true if the value type T can take multiple
// values from cmd line. This is true for slice and map type flags
func (e *extFlag) IsMultiValueFlag() bool {
if e == nil || e.f == nil {
return false
}
// TBD how to specify
if reflect.TypeOf(e.f.Value) == nil {
return false
}
kind := reflect.TypeOf(e.f.Value).Kind()
return kind == reflect.Slice || kind == reflect.Map
}

// IsRequired returns whether or not the flag is required
func (e *extFlag) IsRequired() bool {
return false
}

func (e *extFlag) IsSet() bool {
return false
}
Expand Down Expand Up @@ -61,3 +156,61 @@
func (e *extFlag) GetEnvVars() []string {
return nil
}

// RunAction executes flag action if set
func (e *extFlag) RunAction(ctx context.Context, cmd *Command) error {
return nil
}

// TypeName returns the type of the flag.
func (e *extFlag) TypeName() string {
if e == nil || e.f == nil {
return ""
}
ty := reflect.TypeOf(e.f.Value)
if ty == nil {
return ""
}
// convert the typename to generic type
convertToGenericType := func(name string) string {
prefixMap := map[string]string{
"float": "float",
"int": "int",
"uint": "uint",
}
for prefix, genericType := range prefixMap {
if strings.HasPrefix(name, prefix) {
return genericType
}
}
return strings.ToLower(name)
}

switch ty.Kind() {
// if it is a Slice, then return the slice's inner type. Will nested slices be used in the future?
case reflect.Slice:
elemType := ty.Elem()
return convertToGenericType(elemType.Name())
// if it is a Map, then return the map's key and value types.
case reflect.Map:
keyType := ty.Key()
valueType := ty.Elem()
return fmt.Sprintf("%s=%s", convertToGenericType(keyType.Name()), convertToGenericType(valueType.Name()))
default:
return convertToGenericType(ty.Name())
}
}

// GetCategory returns the category of the flag
func (e *extFlag) GetCategory() string {
if e == nil {
return ""
}
return e.category
}

func (e *extFlag) SetCategory(c string) {
if e != nil {
e.category = c
}
}
Loading