Combine bool short names
Adds the ability to allow the combination of bool short-name options. For example, cmd foobar -ov This is done through a bool "UseShortOptionHandler" set in the command struct. Built upon PR #621 Signed-off-by: baude <bbaude@redhat.com>
This commit is contained in:
parent
43c8c02cf5
commit
fd5382e7a5
21
README.md
21
README.md
@ -47,6 +47,7 @@ applications in an expressive way.
|
|||||||
* [Version Flag](#version-flag)
|
* [Version Flag](#version-flag)
|
||||||
+ [Customization](#customization-2)
|
+ [Customization](#customization-2)
|
||||||
+ [Full API Example](#full-api-example)
|
+ [Full API Example](#full-api-example)
|
||||||
|
* [Combining short Bool options](#combining-short-bool-options)
|
||||||
- [Contribution Guidelines](#contribution-guidelines)
|
- [Contribution Guidelines](#contribution-guidelines)
|
||||||
|
|
||||||
<!-- tocstop -->
|
<!-- tocstop -->
|
||||||
@ -1410,6 +1411,26 @@ func wopAction(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Combining short Bool options
|
||||||
|
|
||||||
|
Traditional use of boolean options using their shortnames look like this:
|
||||||
|
```
|
||||||
|
# cmd foobar -s -o
|
||||||
|
```
|
||||||
|
|
||||||
|
Suppose you want users to be able to combine your bool options with their shortname. This
|
||||||
|
can be done using the **UseShortOptionHandling** bool in your commands. Suppose your program
|
||||||
|
has a two bool flags such as *serve* and *option* with the short options of *-o* and
|
||||||
|
*-s* respectively. With **UseShortOptionHandling** set to *true*, a user can use a syntax
|
||||||
|
like:
|
||||||
|
```
|
||||||
|
# cmd foobar -so
|
||||||
|
```
|
||||||
|
|
||||||
|
If you enable the **UseShortOptionHandling*, then you must not use any flags that have a single
|
||||||
|
leading *-* or this will result in failures. For example, **-option** can no longer be used. Flags
|
||||||
|
with two leading dashes (such as **--options**) are still valid.
|
||||||
|
|
||||||
## Contribution Guidelines
|
## Contribution Guidelines
|
||||||
|
|
||||||
Feel free to put up a pull request to fix a bug or maybe add a feature. I will
|
Feel free to put up a pull request to fix a bug or maybe add a feature. I will
|
||||||
|
26
command.go
26
command.go
@ -55,6 +55,10 @@ type Command struct {
|
|||||||
HideHelp bool
|
HideHelp bool
|
||||||
// Boolean to hide this command from help or completion
|
// Boolean to hide this command from help or completion
|
||||||
Hidden bool
|
Hidden bool
|
||||||
|
// Boolean to enable short-option handling so user can combine several
|
||||||
|
// single-character bool arguements into one
|
||||||
|
// i.e. foobar -o -v -> foobar -ov
|
||||||
|
UseShortOptionHandling bool
|
||||||
|
|
||||||
// Full name of command for help, defaults to full command name, including parent commands.
|
// Full name of command for help, defaults to full command name, including parent commands.
|
||||||
HelpName string
|
HelpName string
|
||||||
@ -141,20 +145,22 @@ func (c Command) Run(ctx *Context) (err error) {
|
|||||||
} else {
|
} else {
|
||||||
flagArgs = args[firstFlagIndex:]
|
flagArgs = args[firstFlagIndex:]
|
||||||
}
|
}
|
||||||
|
|
||||||
// separate combined flags
|
// separate combined flags
|
||||||
var flagArgsSeparated []string
|
if c.UseShortOptionHandling {
|
||||||
for _, flagArg := range flagArgs {
|
var flagArgsSeparated []string
|
||||||
if strings.HasPrefix(flagArg, "-") && strings.HasPrefix(flagArg, "--") == false && len(flagArg) >2 {
|
for _, flagArg := range flagArgs {
|
||||||
for _, flagChar := range flagArg[1:] {
|
if strings.HasPrefix(flagArg, "-") && strings.HasPrefix(flagArg, "--") == false && len(flagArg) > 2 {
|
||||||
flagArgsSeparated = append(flagArgsSeparated, "-" + string(flagChar))
|
for _, flagChar := range flagArg[1:] {
|
||||||
|
flagArgsSeparated = append(flagArgsSeparated, "-"+string(flagChar))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
flagArgsSeparated = append(flagArgsSeparated, flagArg)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
flagArgsSeparated = append(flagArgsSeparated, flagArg)
|
|
||||||
}
|
}
|
||||||
|
err = set.Parse(append(flagArgsSeparated, regularArgs...))
|
||||||
|
} else {
|
||||||
|
err = set.Parse(append(flagArgs, regularArgs...))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = set.Parse(append(flagArgsSeparated, regularArgs...))
|
|
||||||
} else {
|
} else {
|
||||||
err = set.Parse(ctx.Args().Tail())
|
err = set.Parse(ctx.Args().Tail())
|
||||||
}
|
}
|
||||||
|
@ -11,20 +11,23 @@ import (
|
|||||||
|
|
||||||
func TestCommandFlagParsing(t *testing.T) {
|
func TestCommandFlagParsing(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
testArgs []string
|
testArgs []string
|
||||||
skipFlagParsing bool
|
skipFlagParsing bool
|
||||||
skipArgReorder bool
|
skipArgReorder bool
|
||||||
expectedErr error
|
expectedErr error
|
||||||
|
UseShortOptionHandling bool
|
||||||
}{
|
}{
|
||||||
// Test normal "not ignoring flags" flow
|
// Test normal "not ignoring flags" flow
|
||||||
{[]string{"test-cmd", "blah", "blah", "-break"}, false, false, errors.New("flag provided but not defined: -break")},
|
{[]string{"test-cmd", "blah", "blah", "-break"}, false, false, errors.New("flag provided but not defined: -break"), false},
|
||||||
|
|
||||||
// Test no arg reorder
|
// Test no arg reorder
|
||||||
{[]string{"test-cmd", "blah", "blah", "-break"}, false, true, nil},
|
{[]string{"test-cmd", "blah", "blah", "-break"}, false, true, nil, false},
|
||||||
|
|
||||||
|
{[]string{"test-cmd", "blah", "blah"}, true, false, nil, false}, // Test SkipFlagParsing without any args that look like flags
|
||||||
|
{[]string{"test-cmd", "blah", "-break"}, true, false, nil, false}, // Test SkipFlagParsing with random flag arg
|
||||||
|
{[]string{"test-cmd", "blah", "-help"}, true, false, nil, false}, // Test SkipFlagParsing with "special" help flag arg
|
||||||
|
{[]string{"test-cmd", "blah"}, false, false, nil, true}, // Test UseShortOptionHandling
|
||||||
|
|
||||||
{[]string{"test-cmd", "blah", "blah"}, true, false, nil}, // Test SkipFlagParsing without any args that look like flags
|
|
||||||
{[]string{"test-cmd", "blah", "-break"}, true, false, nil}, // Test SkipFlagParsing with random flag arg
|
|
||||||
{[]string{"test-cmd", "blah", "-help"}, true, false, nil}, // Test SkipFlagParsing with "special" help flag arg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
@ -36,13 +39,14 @@ func TestCommandFlagParsing(t *testing.T) {
|
|||||||
context := NewContext(app, set, nil)
|
context := NewContext(app, set, nil)
|
||||||
|
|
||||||
command := Command{
|
command := Command{
|
||||||
Name: "test-cmd",
|
Name: "test-cmd",
|
||||||
Aliases: []string{"tc"},
|
Aliases: []string{"tc"},
|
||||||
Usage: "this is for testing",
|
Usage: "this is for testing",
|
||||||
Description: "testing",
|
Description: "testing",
|
||||||
Action: func(_ *Context) error { return nil },
|
Action: func(_ *Context) error { return nil },
|
||||||
SkipFlagParsing: c.skipFlagParsing,
|
SkipFlagParsing: c.skipFlagParsing,
|
||||||
SkipArgReorder: c.skipArgReorder,
|
SkipArgReorder: c.skipArgReorder,
|
||||||
|
UseShortOptionHandling: c.UseShortOptionHandling,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := command.Run(context)
|
err := command.Run(context)
|
||||||
|
25
flag_test.go
25
flag_test.go
@ -1048,6 +1048,31 @@ func TestParseMultiBool(t *testing.T) {
|
|||||||
a.Run([]string{"run", "--serve"})
|
a.Run([]string{"run", "--serve"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseBoolShortOptionHandle(t *testing.T) {
|
||||||
|
a := App{
|
||||||
|
Commands: []Command{
|
||||||
|
{
|
||||||
|
Name: "foobar",
|
||||||
|
UseShortOptionHandling: true,
|
||||||
|
Action: func(ctx *Context) error {
|
||||||
|
if ctx.Bool("serve") != true {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.Bool("option") != true {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
Flags: []Flag{
|
||||||
|
BoolFlag{Name: "serve, s"},
|
||||||
|
BoolFlag{Name: "option, o"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run", "foobar", "-so"})
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseDestinationBool(t *testing.T) {
|
func TestParseDestinationBool(t *testing.T) {
|
||||||
var dest bool
|
var dest bool
|
||||||
a := App{
|
a := App{
|
||||||
|
Loading…
Reference in New Issue
Block a user