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

Combine bool short names #684

Merged
merged 2 commits into from
Nov 16, 2017
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ applications in an expressive way.
* [Version Flag](#version-flag)
+ [Customization](#customization-2)
+ [Full API Example](#full-api-example)
* [Combining short Bool options](#combining-short-bool-options)
- [Contribution Guidelines](#contribution-guidelines)

<!-- tocstop -->
Expand Down Expand Up @@ -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

Feel free to put up a pull request to fix a bug or maybe add a feature. I will
Expand Down
22 changes: 20 additions & 2 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ type Command struct {
HideHelp bool
// Boolean to hide this command from help or completion
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.
HelpName string
Expand Down Expand Up @@ -141,8 +145,22 @@ func (c Command) Run(ctx *Context) (err error) {
} else {
flagArgs = args[firstFlagIndex:]
}

err = set.Parse(append(flagArgs, regularArgs...))
// separate combined flags
if c.UseShortOptionHandling {
var flagArgsSeparated []string
for _, flagArg := range flagArgs {
if strings.HasPrefix(flagArg, "-") && strings.HasPrefix(flagArg, "--") == false && len(flagArg) > 2 {
for _, flagChar := range flagArg[1:] {
flagArgsSeparated = append(flagArgsSeparated, "-"+string(flagChar))
}
} else {
flagArgsSeparated = append(flagArgsSeparated, flagArg)
}
}
err = set.Parse(append(flagArgsSeparated, regularArgs...))
} else {
err = set.Parse(append(flagArgs, regularArgs...))
}
} else {
err = set.Parse(ctx.Args().Tail())
}
Expand Down
36 changes: 20 additions & 16 deletions command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,23 @@ import (

func TestCommandFlagParsing(t *testing.T) {
cases := []struct {
testArgs []string
skipFlagParsing bool
skipArgReorder bool
expectedErr error
testArgs []string
skipFlagParsing bool
skipArgReorder bool
expectedErr error
UseShortOptionHandling bool
}{
// 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
{[]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 {
Expand All @@ -36,13 +39,14 @@ func TestCommandFlagParsing(t *testing.T) {
context := NewContext(app, set, nil)

command := Command{
Name: "test-cmd",
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(_ *Context) error { return nil },
SkipFlagParsing: c.skipFlagParsing,
SkipArgReorder: c.skipArgReorder,
Name: "test-cmd",
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(_ *Context) error { return nil },
SkipFlagParsing: c.skipFlagParsing,
SkipArgReorder: c.skipArgReorder,
UseShortOptionHandling: c.UseShortOptionHandling,
}

err := command.Run(context)
Expand Down
25 changes: 25 additions & 0 deletions flag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,31 @@ func TestParseMultiBool(t *testing.T) {
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) {
var dest bool
a := App{
Expand Down