Skip to content

Commit

Permalink
Allow combining short flags globally
Browse files Browse the repository at this point in the history
  • Loading branch information
rliebz committed Aug 5, 2019
1 parent 26945c5 commit 03153b9
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 104 deletions.
93 changes: 72 additions & 21 deletions README.md
Expand Up @@ -38,6 +38,7 @@ applications in an expressive way.
* [Subcommands](#subcommands)
* [Subcommands categories](#subcommands-categories)
* [Exit code](#exit-code)
* [Combining short options](#combining-short-options)
* [Bash Completion](#bash-completion)
+ [Enabling](#enabling)
+ [Distribution](#distribution)
Expand All @@ -47,7 +48,6 @@ 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 @@ -921,6 +921,76 @@ func main() {
}
```

### Combining short options

Traditional use of options using their shortnames look like this:

```
$ cmd -s -o -m "Some message"
```

Suppose you want users to be able to combine options with their shortnames. This
can be done using the `UseShortOptionHandling` bool in your app configuration,
or for individual commands by attaching it to the command configuration. For
example:

<!-- {
"args": ["short", "&#45;som", "Some message"],
"output": "serve: true\noption: true\nmessage: Some message\n"
} -->
``` go
package main

import (
"fmt"
"log"
"os"

"github.com/urfave/cli"
)

func main() {
app := cli.NewApp()
app.UseShortOptionHandling = true
app.Commands = []cli.Command{
{
Name: "short",
Usage: "complete a task on the list",
Flags: []cli.Flag{
cli.BoolFlag{Name: "serve, s"},
cli.BoolFlag{Name: "option, o"},
cli.StringFlag{Name: "message, m"},
},
Action: func(c *cli.Context) error {
fmt.Println("serve:", c.Bool("serve"))
fmt.Println("option:", c.Bool("option"))
fmt.Println("message:", c.String("message"))
return nil
},
},
}

err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```

If your program has any number of bool flags such as `serve` and `option`, and
optionally one non-bool flag `message`, with the short options of `-s`, `-o`,
and `-m` respectively, setting `UseShortOptionHandling` will also support the
following syntax:

```
$ cmd -som "Some message"
```

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.

### Bash Completion

You can enable completion commands by setting the `EnableBashCompletion`
Expand Down Expand Up @@ -1372,6 +1442,7 @@ func main() {
cli.Uint64Flag{Name: "bigage"},
}
app.EnableBashCompletion = true
app.UseShortOptionHandling = true
app.HideHelp = false
app.HideVersion = false
app.BashComplete = func(c *cli.Context) {
Expand Down Expand Up @@ -1502,26 +1573,6 @@ 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

See [./CONTRIBUTING.md](./CONTRIBUTING.md)
26 changes: 17 additions & 9 deletions app.go
@@ -1,9 +1,9 @@
package cli

import (
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
Expand Down Expand Up @@ -94,6 +94,10 @@ type App struct {
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
CustomAppHelpTemplate string
// 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

didSetup bool
}
Expand Down Expand Up @@ -173,6 +177,14 @@ func (a *App) Setup() {
}
}

func (a *App) newFlagSet() (*flag.FlagSet, error) {
return flagSet(a.Name, a.Flags)
}

func (a *App) useShortOptionHandling() bool {
return a.UseShortOptionHandling
}

// Run is the entry point to the cli app. Parses the arguments slice and routes
// to the proper flag/args combination
func (a *App) Run(arguments []string) (err error) {
Expand All @@ -186,14 +198,12 @@ func (a *App) Run(arguments []string) (err error) {
// always appends the completion flag at the end of the command
shellComplete, arguments := checkShellCompleteFlag(a, arguments)

// parse flags
set, err := flagSet(a.Name, a.Flags)
_, err = a.newFlagSet()
if err != nil {
return err
}

set.SetOutput(ioutil.Discard)
err = set.Parse(arguments[1:])
set, err := parseIter(a, arguments[1:])
nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, nil)
if nerr != nil {
Expand Down Expand Up @@ -311,14 +321,12 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
}
a.Commands = newCmds

// parse flags
set, err := flagSet(a.Name, a.Flags)
_, err = a.newFlagSet()
if err != nil {
return err
}

set.SetOutput(ioutil.Discard)
err = set.Parse(ctx.Args().Tail())
set, err := parseIter(a, ctx.Args().Tail())
nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, ctx)

Expand Down
88 changes: 88 additions & 0 deletions app_test.go
Expand Up @@ -551,6 +551,94 @@ func TestApp_VisibleCommands(t *testing.T) {
}
}

func TestApp_UseShortOptionHandling(t *testing.T) {
var one, two bool
var name string
expected := "expectedName"

app := NewApp()
app.UseShortOptionHandling = true
app.Flags = []Flag{
BoolFlag{Name: "one, o"},
BoolFlag{Name: "two, t"},
StringFlag{Name: "name, n"},
}
app.Action = func(c *Context) error {
one = c.Bool("one")
two = c.Bool("two")
name = c.String("name")
return nil
}

app.Run([]string{"", "-on", expected})
expect(t, one, true)
expect(t, two, false)
expect(t, name, expected)
}

func TestApp_UseShortOptionHandlingCommand(t *testing.T) {
var one, two bool
var name string
expected := "expectedName"

app := NewApp()
app.UseShortOptionHandling = true
command := Command{
Name: "cmd",
Flags: []Flag{
BoolFlag{Name: "one, o"},
BoolFlag{Name: "two, t"},
StringFlag{Name: "name, n"},
},
Action: func(c *Context) error {
one = c.Bool("one")
two = c.Bool("two")
name = c.String("name")
return nil
},
}
app.Commands = []Command{command}

app.Run([]string{"", "cmd", "-on", expected})
expect(t, one, true)
expect(t, two, false)
expect(t, name, expected)
}

func TestApp_UseShortOptionHandlingSubCommand(t *testing.T) {
var one, two bool
var name string
expected := "expectedName"

app := NewApp()
app.UseShortOptionHandling = true
command := Command{
Name: "cmd",
}
subCommand := Command{
Name: "sub",
Flags: []Flag{
BoolFlag{Name: "one, o"},
BoolFlag{Name: "two, t"},
StringFlag{Name: "name, n"},
},
Action: func(c *Context) error {
one = c.Bool("one")
two = c.Bool("two")
name = c.String("name")
return nil
},
}
command.Subcommands = []Command{subCommand}
app.Commands = []Command{command}

err := app.Run([]string{"", "cmd", "sub", "-on", expected})
expect(t, err, nil)
expect(t, one, true)
expect(t, two, false)
expect(t, name, expected)
}

func TestApp_Float64Flag(t *testing.T) {
var meters float64

Expand Down

0 comments on commit 03153b9

Please sign in to comment.