Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple chars shorthand eg: -ff file     --filefrom file #396

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
117 changes: 84 additions & 33 deletions flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ type FlagSet struct {
// help/usage messages.
SortFlags bool

// Allow multiple characters shorthand
AllowMultCharsShorthand bool

// ParseErrorsWhitelist is used to configure a whitelist of errors
ParseErrorsWhitelist ParseErrorsWhitelist

Expand All @@ -156,7 +159,7 @@ type FlagSet struct {
formal map[NormalizedName]*Flag
orderedFormal []*Flag
sortedFormal []*Flag
shorthands map[byte]*Flag
shorthands map[string]*Flag
args []string // arguments after flags
argsLenAtDash int // len(args) when a '--' was located when parsing, or -1 if no --
errorHandling ErrorHandling
Expand Down Expand Up @@ -363,12 +366,17 @@ func (f *FlagSet) ShorthandLookup(name string) *Flag {
if name == "" {
return nil
}
if len(name) > 1 {
msg := fmt.Sprintf("can not look up shorthand which is more than one ASCII character: %q", name)
fmt.Fprintf(f.Output(), msg)
panic(msg)
c := ""
if f.AllowMultCharsShorthand {
c = name
} else {
if len(name) > 1 {
msg := fmt.Sprintf("can not look up shorthand which is more than one ASCII character: %q", name)
fmt.Fprintf(f.Output(), msg)
panic(msg)
}
c = string(name[0])
}
c := name[0]
return f.shorthands[c]
}

Expand Down Expand Up @@ -865,15 +873,20 @@ func (f *FlagSet) AddFlag(flag *Flag) {
if flag.Shorthand == "" {
return
}
if len(flag.Shorthand) > 1 {
if !f.AllowMultCharsShorthand && len(flag.Shorthand) > 1 {
msg := fmt.Sprintf("%q shorthand is more than one ASCII character", flag.Shorthand)
fmt.Fprintf(f.Output(), msg)
panic(msg)
}
if f.shorthands == nil {
f.shorthands = make(map[byte]*Flag)
f.shorthands = make(map[string]*Flag)
}
c := ""
if f.AllowMultCharsShorthand {
c = flag.Shorthand
} else {
c = string(flag.Shorthand[0])
}
c := flag.Shorthand[0]
used, alreadyThere := f.shorthands[c]
if alreadyThere {
msg := fmt.Sprintf("unable to redefine %q shorthand in %q flagset: it's already used for %q flag", c, f.name, used.Name)
Expand Down Expand Up @@ -1018,22 +1031,42 @@ func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parse
return
}

outShorts = shorthands[1:]
c := shorthands[0]
c := ""
eqVal := ""
if f.AllowMultCharsShorthand {
// When in multiple-chars-shorhand-enabled mode, only "-ff=arg" or "-ff arg" is allowed.
eqPos := strings.Index(shorthands, "=")
if eqPos > 0 {
c = shorthands[0:eqPos]
eqVal = shorthands[eqPos+1:]
} else {
c = shorthands
}
outShorts = ""
} else {
outShorts = shorthands[1:]
c = string(shorthands[0])
}

flag, exists := f.shorthands[c]
if !exists {
switch {
case c == 'h':
case c == "h":
f.usage()
err = ErrHelp
return
case f.ParseErrorsWhitelist.UnknownFlags:
// '-f=arg arg ...'
// we do not want to lose arg in this case
if len(shorthands) > 2 && shorthands[1] == '=' {
outShorts = ""
return
if f.AllowMultCharsShorthand {
if len(eqVal) > 0 {
return
}
} else {
if len(shorthands) > 2 && shorthands[1] == '=' {
outShorts = ""
return
}
}

outArgs = stripUnknownFlagValue(outArgs)
Expand All @@ -1045,25 +1078,43 @@ func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parse
}

var value string
if len(shorthands) > 2 && shorthands[1] == '=' {
// '-f=arg'
value = shorthands[2:]
outShorts = ""
} else if flag.NoOptDefVal != "" {
// '-f' (arg was optional)
value = flag.NoOptDefVal
} else if len(shorthands) > 1 {
// '-farg'
value = shorthands[1:]
outShorts = ""
} else if len(args) > 0 {
// '-f arg'
value = args[0]
outArgs = args[1:]
if f.AllowMultCharsShorthand {
if len(eqVal) > 0 {
value = eqVal
outShorts = ""
} else if flag.NoOptDefVal != "" {
// '-f' (arg was optional)
value = flag.NoOptDefVal
} else if len(args) > 0 {
// '-f arg'
value = args[0]
outArgs = args[1:]
} else {
// '-f' (arg was required)
err = f.failf("flag needs an argument: %q in -%s", c, shorthands)
return
}
} else {
// '-f' (arg was required)
err = f.failf("flag needs an argument: %q in -%s", c, shorthands)
return
if len(shorthands) > 2 && shorthands[1] == '=' {
// '-f=arg'
value = shorthands[2:]
outShorts = ""
} else if flag.NoOptDefVal != "" {
// '-f' (arg was optional)
value = flag.NoOptDefVal
} else if len(shorthands) > 1 {
// '-farg'
value = shorthands[1:]
outShorts = ""
} else if len(args) > 0 {
// '-f arg'
value = args[0]
outArgs = args[1:]
} else {
// '-f' (arg was required)
err = f.failf("flag needs an argument: %q in -%s", c, shorthands)
return
}
}

if flag.ShorthandDeprecated != "" {
Expand Down
120 changes: 118 additions & 2 deletions flag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,67 @@ func TestShorthand(t *testing.T) {
}
}

func TestShorthandMultiChars(t *testing.T) {
f := NewFlagSet("shorthand", ContinueOnError)
f.AllowMultCharsShorthand = true
if f.Parsed() {
t.Error("f.Parse() = true before Parse")
}
boolaFlag := f.BoolP("boola", "a", false, "bool value")
boolbFlag := f.BoolP("boolb", "b", false, "bool2 value")
stringaFlag := f.StringP("stringa", "s", "0", "string value")
stringzFlag := f.StringP("stringz", "z", "0", "string value")
stringaaFlag := f.StringP("stringaa", "aa", "0", "string value")
stringzzFlag := f.StringP("stringzz", "zz", "0", "string value")
extra := "interspersed-argument"
notaflag := "--i-look-like-a-flag"
args := []string{
"-a=true",
extra,
"-z=something",
"-aa",
"multi-chars-shorthand-aa",
"-zz=multi-chars-shorthand-zz",
"--",
notaflag,
}
f.SetOutput(ioutil.Discard)
if err := f.Parse(args); err != nil {
t.Error("expected no error, got ", err)
}
if !f.Parsed() {
t.Error("f.Parse() = false after Parse")
}
if *boolaFlag != true {
t.Error("boola flag should be true, is ", *boolaFlag)
}
if *boolbFlag != false {
t.Error("boolb flag should be false, is ", *boolbFlag)
}
if *stringaFlag != "0" {
t.Error("stringa flag should be `0`, is ", *stringaFlag)
}
if *stringzFlag != "something" {
t.Error("stringz flag should be `something`, is ", *stringzFlag)
}
if *stringaaFlag != "multi-chars-shorthand-aa" {
t.Error("stringaa flag should be `multi-chars-shorthand-aa`, is ", *stringaaFlag)
}
if *stringzzFlag != "multi-chars-shorthand-zz" {
t.Error("stringzz flag should be `multi-chars-shorthand-zz`, is ", *stringzzFlag)
}
if len(f.Args()) != 2 {
t.Error("expected one argument, got", len(f.Args()))
} else if f.Args()[0] != extra {
t.Errorf("expected argument %q got %q", extra, f.Args()[0])
} else if f.Args()[1] != notaflag {
t.Errorf("expected argument %q got %q", notaflag, f.Args()[1])
}
if f.ArgsLenAtDash() != 1 {
t.Errorf("expected argsLenAtDash %d got %d", f.ArgsLenAtDash(), 1)
}
}

func TestShorthandLookup(t *testing.T) {
f := NewFlagSet("shorthand", ContinueOnError)
if f.Parsed() {
Expand Down Expand Up @@ -576,6 +637,63 @@ func TestShorthandLookup(t *testing.T) {
t.Errorf("f.ShorthandLookup(\"ab\") did not panic")
}

func TestShorthandLookupMultiChars(t *testing.T) {
f := NewFlagSet("shorthand", ContinueOnError)
f.AllowMultCharsShorthand = true
if f.Parsed() {
t.Error("f.Parse() = true before Parse")
}
f.BoolP("boola", "a", false, "bool value")
f.BoolP("boolb", "b", false, "bool2 value")
f.StringP("stringa", "s", "0", "string value")
f.StringP("stringz", "z", "0", "string value")
f.StringP("stringaa", "aa", "0", "string value")
f.StringP("stringzz", "zz", "0", "string value")
extra := "interspersed-argument"
notaflag := "--i-look-like-a-flag"
args := []string{
"-a=true",
extra,
"-z=something",
"-aa",
"multi-chars-shorthand-aa",
"-zz=multi-chars-shorthand-zz",
"--",
notaflag,
}
f.SetOutput(ioutil.Discard)
if err := f.Parse(args); err != nil {
t.Error("expected no error, got ", err)
}
if !f.Parsed() {
t.Error("f.Parse() = false after Parse")
}
stringaaFlag := f.ShorthandLookup("aa")
if stringaaFlag == nil {
t.Error("stringaa flag should not be nil")
}
if stringaaFlag.Value.String() != "multi-chars-shorthand-aa" {
t.Error("stringaa flag should be `multi-chars-shorthand-aa`, is ", stringaaFlag.Value.String())
}
stringzzFlag := f.ShorthandLookup("zz")
if stringzzFlag == nil {
t.Error("stringzz flag should not be nil")
}
if stringzzFlag.Value.String() != "multi-chars-shorthand-zz" {
t.Error("stringzz flag should be `multi-chars-shorthand-zz`, is ", stringzzFlag.Value.String())
}
if len(f.Args()) != 2 {
t.Error("expected one argument, got", len(f.Args()))
} else if f.Args()[0] != extra {
t.Errorf("expected argument %q got %q", extra, f.Args()[0])
} else if f.Args()[1] != notaflag {
t.Errorf("expected argument %q got %q", notaflag, f.Args()[1])
}
if f.ArgsLenAtDash() != 1 {
t.Errorf("expected argsLenAtDash %d got %d", f.ArgsLenAtDash(), 1)
}
}

func TestParse(t *testing.T) {
ResetForTesting(func() { t.Error("bad parse") })
testParse(GetCommandLine(), t)
Expand Down Expand Up @@ -1134,7 +1252,6 @@ func TestMultipleNormalizeFlagNameInvocations(t *testing.T) {
}
}

//
func TestHiddenFlagInUsage(t *testing.T) {
f := NewFlagSet("bob", ContinueOnError)
f.Bool("secretFlag", true, "shhh")
Expand All @@ -1149,7 +1266,6 @@ func TestHiddenFlagInUsage(t *testing.T) {
}
}

//
func TestHiddenFlagUsage(t *testing.T) {
f := NewFlagSet("bob", ContinueOnError)
f.Bool("secretFlag", true, "shhh")
Expand Down