From 43c8c02cf5a10196e5a4c458fdbfee90a561e97c Mon Sep 17 00:00:00 2001 From: zhuchensong Date: Mon, 17 Apr 2017 00:47:04 +0800 Subject: [PATCH 1/2] Support POSIX-style short flag combining --- command.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/command.go b/command.go index be8f8f0..189209d 100644 --- a/command.go +++ b/command.go @@ -142,7 +142,19 @@ func (c Command) Run(ctx *Context) (err error) { flagArgs = args[firstFlagIndex:] } - err = set.Parse(append(flagArgs, regularArgs...)) + // separate combined flags + 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(ctx.Args().Tail()) } From fd5382e7a539858cc19d7eed7755f7102bae5da9 Mon Sep 17 00:00:00 2001 From: baude Date: Mon, 13 Nov 2017 15:28:23 -0600 Subject: [PATCH 2/2] 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 --- README.md | 21 +++++++++++++++++++++ command.go | 26 ++++++++++++++++---------- command_test.go | 36 ++++++++++++++++++++---------------- flag_test.go | 25 +++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index a2fd41d..6096701 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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 diff --git a/command.go b/command.go index 189209d..b559811 100644 --- a/command.go +++ b/command.go @@ -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 @@ -141,20 +145,22 @@ func (c Command) Run(ctx *Context) (err error) { } else { flagArgs = args[firstFlagIndex:] } - // separate combined flags - 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)) + 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) } - } 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 { err = set.Parse(ctx.Args().Tail()) } diff --git a/command_test.go b/command_test.go index 4ad994c..d9d7094 100644 --- a/command_test.go +++ b/command_test.go @@ -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 { @@ -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) diff --git a/flag_test.go b/flag_test.go index 98c2fb9..da9fd73 100644 --- a/flag_test.go +++ b/flag_test.go @@ -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{