Merge remote-tracking branch 'origin/master' into allow-writer-to-be-set

Conflicts:
	app.go
	help.go
This commit is contained in:
jszwedko 2014-12-01 23:51:35 -05:00
commit b8c8282de5
14 changed files with 866 additions and 107 deletions

View File

@ -1,2 +1,6 @@
language: go language: go
go: 1.1 go: 1.1
script:
- go vet ./...
- go test -v ./...

View File

@ -9,17 +9,17 @@ http://godoc.org/github.com/codegangsta/cli
## Overview ## Overview
Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app. Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app.
This is where cli.go comes into play. cli.go makes command line programming fun, organized, and expressive! **This is where cli.go comes into play.** cli.go makes command line programming fun, organized, and expressive!
## Installation ## Installation
Make sure you have a working Go environment (go 1.1 is *required*). [See the install instructions](http://golang.org/doc/install.html). Make sure you have a working Go environment (go 1.1 is *required*). [See the install instructions](http://golang.org/doc/install.html).
To install cli.go, simply run: To install `cli.go`, simply run:
``` ```
$ go get github.com/codegangsta/cli $ go get github.com/codegangsta/cli
``` ```
Make sure your PATH includes to the `$GOPATH/bin` directory so your commands can be easily used: Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used:
``` ```
export PATH=$PATH:$GOPATH/bin export PATH=$PATH:$GOPATH/bin
``` ```
@ -68,8 +68,9 @@ Running this already gives you a ton of functionality, plus support for things l
Being a programmer can be a lonely job. Thankfully by the power of automation that is not the case! Let's create a greeter app to fend off our demons of loneliness! Being a programmer can be a lonely job. Thankfully by the power of automation that is not the case! Let's create a greeter app to fend off our demons of loneliness!
Start by creating a directory named `greet`, and within it, add a file, `greet.go` with the following code in it:
``` go ``` go
/* greet.go */
package main package main
import ( import (
@ -122,7 +123,7 @@ GLOBAL OPTIONS
``` ```
### Arguments ### Arguments
You can lookup arguments by calling the `Args` function on cli.Context. You can lookup arguments by calling the `Args` function on `cli.Context`.
``` go ``` go
... ...
@ -137,7 +138,11 @@ Setting and querying flags is simple.
``` go ``` go
... ...
app.Flags = []cli.Flag { app.Flags = []cli.Flag {
cli.StringFlag{"lang", "english", "language for the greeting"}, cli.StringFlag{
Name: "lang",
Value: "english",
Usage: "language for the greeting",
},
} }
app.Action = func(c *cli.Context) { app.Action = func(c *cli.Context) {
name := "someone" name := "someone"
@ -155,11 +160,30 @@ app.Action = func(c *cli.Context) {
#### Alternate Names #### Alternate Names
You can set alternate (or short) names for flags by providing a comma-delimited list for the Name. e.g. You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g.
``` go ``` go
app.Flags = []cli.Flag { app.Flags = []cli.Flag {
cli.StringFlag{"lang, l", "english", "language for the greeting"}, cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
},
}
```
#### Values from the Environment
You can also have the default value set from the environment via `EnvVar`. e.g.
``` go
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
EnvVar: "APP_LANG",
},
} }
``` ```
@ -214,8 +238,8 @@ app.Commands = []cli.Command{
### Bash Completion ### Bash Completion
You can enable completion commands by setting the EnableBashCompletion You can enable completion commands by setting the `EnableBashCompletion`
flag on the App object. By default, this setting will only auto-complete to flag on the `App` object. By default, this setting will only auto-complete to
show an app's subcommands, but you can write your own completion methods for show an app's subcommands, but you can write your own completion methods for
the App or its subcommands. the App or its subcommands.
```go ```go
@ -237,7 +261,7 @@ app.Commands = []cli.Command{
return return
} }
for _, t := range tasks { for _, t := range tasks {
println(t) fmt.Println(t)
} }
}, },
} }
@ -247,11 +271,15 @@ app.Commands = []cli.Command{
#### To Enable #### To Enable
Source the autocomplete/bash_autocomplete file in your .bashrc file while Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while
setting the PROG variable to the name of your program: setting the `PROG` variable to the name of your program:
`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` `PROG=myprogram source /.../cli/autocomplete/bash_autocomplete`
## About ## Contribution Guidelines
cli.go is written by none other than the [Code Gangsta](http://codegangsta.io) Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch.
If you are have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together.
If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out.

25
app.go
View File

@ -25,6 +25,10 @@ type App struct {
Flags []Flag Flags []Flag
// Boolean to enable bash completion commands // Boolean to enable bash completion commands
EnableBashCompletion bool EnableBashCompletion bool
// Boolean to hide built-in help command
HideHelp bool
// Boolean to hide built-in version flag
HideVersion bool
// An action to execute when the bash-completion flag is set // An action to execute when the bash-completion flag is set
BashComplete func(context *Context) BashComplete func(context *Context)
// An action to execute before any subcommands are run, but after the context is ready // An action to execute before any subcommands are run, but after the context is ready
@ -88,16 +92,19 @@ func (a *App) Run(arguments []string) error {
} }
// append help to commands // append help to commands
if a.Command(helpCommand.Name) == nil { if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand) a.Commands = append(a.Commands, helpCommand)
a.appendFlag(HelpFlag)
} }
//append version/help flags //append version/help flags
if a.EnableBashCompletion { if a.EnableBashCompletion {
a.appendFlag(BashCompletionFlag) a.appendFlag(BashCompletionFlag)
} }
if !a.HideVersion {
a.appendFlag(VersionFlag) a.appendFlag(VersionFlag)
a.appendFlag(HelpFlag) }
// parse flags // parse flags
set := flagSet(a.Name, a.Flags) set := flagSet(a.Name, a.Flags)
@ -153,12 +160,21 @@ func (a *App) Run(arguments []string) error {
return nil return nil
} }
// Another entry point to the cli app, takes care of passing arguments and error handling
func (a *App) RunAndExitOnError() {
if err := a.Run(os.Args); err != nil {
os.Stderr.WriteString(fmt.Sprintln(err))
os.Exit(1)
}
}
// Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags // Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags
func (a *App) RunAsSubcommand(ctx *Context) error { func (a *App) RunAsSubcommand(ctx *Context) error {
// append help to commands // append help to commands
if len(a.Commands) > 0 { if len(a.Commands) > 0 {
if a.Command(helpCommand.Name) == nil { if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand) a.Commands = append(a.Commands, helpCommand)
a.appendFlag(HelpFlag)
} }
} }
@ -166,14 +182,13 @@ func (a *App) RunAsSubcommand(ctx *Context) error {
if a.EnableBashCompletion { if a.EnableBashCompletion {
a.appendFlag(BashCompletionFlag) a.appendFlag(BashCompletionFlag)
} }
a.appendFlag(HelpFlag)
// parse flags // parse flags
set := flagSet(a.Name, a.Flags) set := flagSet(a.Name, a.Flags)
set.SetOutput(ioutil.Discard) set.SetOutput(ioutil.Discard)
err := set.Parse(ctx.Args().Tail()) err := set.Parse(ctx.Args().Tail())
nerr := normalizeFlags(a.Flags, set) nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, set) context := NewContext(a, set, ctx.globalSet)
if nerr != nil { if nerr != nil {
io.WriteString(a.Writer, fmt.Sprintln(nerr)) io.WriteString(a.Writer, fmt.Sprintln(nerr))

View File

@ -2,9 +2,10 @@ package cli_test
import ( import (
"fmt" "fmt"
"github.com/codegangsta/cli"
"os" "os"
"testing" "testing"
"github.com/codegangsta/cli"
) )
func ExampleApp() { func ExampleApp() {
@ -42,7 +43,11 @@ func ExampleAppSubcommand() {
Usage: "sends a greeting in english", Usage: "sends a greeting in english",
Description: "greets someone in english", Description: "greets someone in english",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{"name", "Bob", "Name of the person to greet"}, cli.StringFlag{
Name: "name",
Value: "Bob",
Usage: "Name of the person to greet",
},
}, },
Action: func(c *cli.Context) { Action: func(c *cli.Context) {
fmt.Println("Hello,", c.String("name")) fmt.Println("Hello,", c.String("name"))
@ -83,12 +88,10 @@ func ExampleAppHelp() {
// describeit - use it to see a description // describeit - use it to see a description
// //
// USAGE: // USAGE:
// command describeit [command options] [arguments...] // command describeit [arguments...]
// //
// DESCRIPTION: // DESCRIPTION:
// This is how we describe describeit the function // This is how we describe describeit the function
//
// OPTIONS:
} }
func ExampleAppBashComplete() { func ExampleAppBashComplete() {
@ -254,11 +257,11 @@ func TestApp_ParseSliceFlags(t *testing.T) {
var expectedStringSlice = []string{"8.8.8.8", "8.8.4.4"} var expectedStringSlice = []string{"8.8.8.8", "8.8.4.4"}
if !IntsEquals(parsedIntSlice, expectedIntSlice) { if !IntsEquals(parsedIntSlice, expectedIntSlice) {
t.Errorf("%s does not match %s", parsedIntSlice, expectedIntSlice) t.Errorf("%v does not match %v", parsedIntSlice, expectedIntSlice)
} }
if !StrsEquals(parsedStringSlice, expectedStringSlice) { if !StrsEquals(parsedStringSlice, expectedStringSlice) {
t.Errorf("%s does not match %s", parsedStringSlice, expectedStringSlice) t.Errorf("%v does not match %v", parsedStringSlice, expectedStringSlice)
} }
} }
@ -391,6 +394,26 @@ func TestAppHelpPrinter(t *testing.T) {
} }
} }
func TestAppVersionPrinter(t *testing.T) {
oldPrinter := cli.VersionPrinter
defer func() {
cli.VersionPrinter = oldPrinter
}()
var wasCalled = false
cli.VersionPrinter = func(c *cli.Context) {
wasCalled = true
}
app := cli.NewApp()
ctx := cli.NewContext(app, nil, nil)
cli.ShowVersion(ctx)
if wasCalled == false {
t.Errorf("Version printer expected to be called, but was not")
}
}
func TestAppCommandNotFound(t *testing.T) { func TestAppCommandNotFound(t *testing.T) {
beforeRun, subcommandRun := false, false beforeRun, subcommandRun := false, false
app := cli.NewApp() app := cli.NewApp()
@ -413,3 +436,32 @@ func TestAppCommandNotFound(t *testing.T) {
expect(t, beforeRun, true) expect(t, beforeRun, true)
expect(t, subcommandRun, false) expect(t, subcommandRun, false)
} }
func TestGlobalFlagsInSubcommands(t *testing.T) {
subcommandRun := false
app := cli.NewApp()
app.Flags = []cli.Flag{
cli.BoolFlag{Name: "debug, d", Usage: "Enable debugging"},
}
app.Commands = []cli.Command{
cli.Command{
Name: "foo",
Subcommands: []cli.Command{
{
Name: "bar",
Action: func(c *cli.Context) {
if c.GlobalBool("debug") {
subcommandRun = true
}
},
},
},
},
}
app.Run([]string{"command", "-d", "foo", "bar"})
expect(t, subcommandRun, true)
}

View File

@ -5,7 +5,7 @@ _cli_bash_autocomplete() {
COMPREPLY=() COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}" prev="${COMP_WORDS[COMP_CWORD-1]}"
opts=$( ${COMP_WORDS[@]:0:COMP_CWORD} --generate-bash-completion ) opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0 return 0
} }

View File

@ -0,0 +1,5 @@
autoload -U compinit && compinit
autoload -U bashcompinit && bashcompinit
script_dir=$(dirname $0)
source ${script_dir}/bash_autocomplete

View File

@ -1,8 +1,9 @@
package cli_test package cli_test
import ( import (
"github.com/codegangsta/cli"
"os" "os"
"github.com/codegangsta/cli"
) )
func Example() { func Example() {
@ -47,7 +48,11 @@ func ExampleSubcommand() {
Usage: "sends a greeting in english", Usage: "sends a greeting in english",
Description: "greets someone in english", Description: "greets someone in english",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{"name", "Bob", "Name of the person to greet"}, cli.StringFlag{
Name: "name",
Value: "Bob",
Usage: "Name of the person to greet",
},
}, },
Action: func(c *cli.Context) { Action: func(c *cli.Context) {
println("Hello, ", c.String("name")) println("Hello, ", c.String("name"))
@ -57,7 +62,11 @@ func ExampleSubcommand() {
ShortName: "sp", ShortName: "sp",
Usage: "sends a greeting in spanish", Usage: "sends a greeting in spanish",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{"surname", "Jones", "Surname of the person to greet"}, cli.StringFlag{
Name: "surname",
Value: "Jones",
Usage: "Surname of the person to greet",
},
}, },
Action: func(c *cli.Context) { Action: func(c *cli.Context) {
println("Hola, ", c.String("surname")) println("Hola, ", c.String("surname"))
@ -67,7 +76,11 @@ func ExampleSubcommand() {
ShortName: "fr", ShortName: "fr",
Usage: "sends a greeting in french", Usage: "sends a greeting in french",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{"nickname", "Stevie", "Nickname of the person to greet"}, cli.StringFlag{
Name: "nickname",
Value: "Stevie",
Usage: "Nickname of the person to greet",
},
}, },
Action: func(c *cli.Context) { Action: func(c *cli.Context) {
println("Bonjour, ", c.String("nickname")) println("Bonjour, ", c.String("nickname"))

View File

@ -30,6 +30,8 @@ type Command struct {
Flags []Flag Flags []Flag
// Treat all flags as normal arguments if true // Treat all flags as normal arguments if true
SkipFlagParsing bool SkipFlagParsing bool
// Boolean to hide built-in help command
HideHelp bool
} }
// Invokes the command given the context, parses ctx.Args() to generate command-specific flags // Invokes the command given the context, parses ctx.Args() to generate command-specific flags
@ -39,11 +41,13 @@ func (c Command) Run(ctx *Context) error {
return c.startApp(ctx) return c.startApp(ctx)
} }
if !c.HideHelp {
// append help to flags // append help to flags
c.Flags = append( c.Flags = append(
c.Flags, c.Flags,
HelpFlag, HelpFlag,
) )
}
if ctx.App.EnableBashCompletion { if ctx.App.EnableBashCompletion {
c.Flags = append(c.Flags, BashCompletionFlag) c.Flags = append(c.Flags, BashCompletionFlag)
@ -115,9 +119,13 @@ func (c Command) startApp(ctx *Context) error {
app.Usage = c.Usage app.Usage = c.Usage
} }
// set CommandNotFound
app.CommandNotFound = ctx.App.CommandNotFound
// set the flags and commands // set the flags and commands
app.Commands = c.Subcommands app.Commands = c.Subcommands
app.Flags = c.Flags app.Flags = c.Flags
app.HideHelp = c.HideHelp
// bash completion // bash completion
app.EnableBashCompletion = ctx.App.EnableBashCompletion app.EnableBashCompletion = ctx.App.EnableBashCompletion

View File

@ -2,8 +2,9 @@ package cli_test
import ( import (
"flag" "flag"
"github.com/codegangsta/cli"
"testing" "testing"
"github.com/codegangsta/cli"
) )
func TestCommandDoNotIgnoreFlags(t *testing.T) { func TestCommandDoNotIgnoreFlags(t *testing.T) {
@ -14,12 +15,12 @@ func TestCommandDoNotIgnoreFlags(t *testing.T) {
c := cli.NewContext(app, set, set) c := cli.NewContext(app, set, set)
command := cli.Command { command := cli.Command{
Name: "test-cmd", Name: "test-cmd",
ShortName: "tc", ShortName: "tc",
Usage: "this is for testing", Usage: "this is for testing",
Description: "testing", Description: "testing",
Action: func(_ *cli.Context) { }, Action: func(_ *cli.Context) {},
} }
err := command.Run(c) err := command.Run(c)
@ -34,12 +35,12 @@ func TestCommandIgnoreFlags(t *testing.T) {
c := cli.NewContext(app, set, set) c := cli.NewContext(app, set, set)
command := cli.Command { command := cli.Command{
Name: "test-cmd", Name: "test-cmd",
ShortName: "tc", ShortName: "tc",
Usage: "this is for testing", Usage: "this is for testing",
Description: "testing", Description: "testing",
Action: func(_ *cli.Context) { }, Action: func(_ *cli.Context) {},
SkipFlagParsing: true, SkipFlagParsing: true,
} }
err := command.Run(c) err := command.Run(c)

View File

@ -5,6 +5,7 @@ import (
"flag" "flag"
"strconv" "strconv"
"strings" "strings"
"time"
) )
// Context is a type that is passed through to // Context is a type that is passed through to
@ -17,6 +18,7 @@ type Context struct {
flagSet *flag.FlagSet flagSet *flag.FlagSet
globalSet *flag.FlagSet globalSet *flag.FlagSet
setFlags map[string]bool setFlags map[string]bool
globalSetFlags map[string]bool
} }
// Creates a new context. For use in when invoking an App or Command action. // Creates a new context. For use in when invoking an App or Command action.
@ -29,6 +31,11 @@ func (c *Context) Int(name string) int {
return lookupInt(name, c.flagSet) return lookupInt(name, c.flagSet)
} }
// Looks up the value of a local time.Duration flag, returns 0 if no time.Duration flag exists
func (c *Context) Duration(name string) time.Duration {
return lookupDuration(name, c.flagSet)
}
// Looks up the value of a local float64 flag, returns 0 if no float64 flag exists // Looks up the value of a local float64 flag, returns 0 if no float64 flag exists
func (c *Context) Float64(name string) float64 { func (c *Context) Float64(name string) float64 {
return lookupFloat64(name, c.flagSet) return lookupFloat64(name, c.flagSet)
@ -69,6 +76,11 @@ func (c *Context) GlobalInt(name string) int {
return lookupInt(name, c.globalSet) return lookupInt(name, c.globalSet)
} }
// Looks up the value of a global time.Duration flag, returns 0 if no time.Duration flag exists
func (c *Context) GlobalDuration(name string) time.Duration {
return lookupDuration(name, c.globalSet)
}
// Looks up the value of a global bool flag, returns false if no bool flag exists // Looks up the value of a global bool flag, returns false if no bool flag exists
func (c *Context) GlobalBool(name string) bool { func (c *Context) GlobalBool(name string) bool {
return lookupBool(name, c.globalSet) return lookupBool(name, c.globalSet)
@ -94,7 +106,7 @@ func (c *Context) GlobalGeneric(name string) interface{} {
return lookupGeneric(name, c.globalSet) return lookupGeneric(name, c.globalSet)
} }
// Determines if the flag was actually set exists // Determines if the flag was actually set
func (c *Context) IsSet(name string) bool { func (c *Context) IsSet(name string) bool {
if c.setFlags == nil { if c.setFlags == nil {
c.setFlags = make(map[string]bool) c.setFlags = make(map[string]bool)
@ -105,6 +117,41 @@ func (c *Context) IsSet(name string) bool {
return c.setFlags[name] == true return c.setFlags[name] == true
} }
// Determines if the global flag was actually set
func (c *Context) GlobalIsSet(name string) bool {
if c.globalSetFlags == nil {
c.globalSetFlags = make(map[string]bool)
c.globalSet.Visit(func(f *flag.Flag) {
c.globalSetFlags[f.Name] = true
})
}
return c.globalSetFlags[name] == true
}
// Returns a slice of flag names used in this context.
func (c *Context) FlagNames() (names []string) {
for _, flag := range c.Command.Flags {
name := strings.Split(flag.getName(), ",")[0]
if name == "help" {
continue
}
names = append(names, name)
}
return
}
// Returns a slice of global flag names used by the app.
func (c *Context) GlobalFlagNames() (names []string) {
for _, flag := range c.App.Flags {
name := strings.Split(flag.getName(), ",")[0]
if name == "help" || name == "version" {
continue
}
names = append(names, name)
}
return
}
type Args []string type Args []string
// Returns the command line arguments associated with the context. // Returns the command line arguments associated with the context.
@ -140,6 +187,15 @@ func (a Args) Present() bool {
return len(a) != 0 return len(a) != 0
} }
// Swaps arguments at the given indexes
func (a Args) Swap(from, to int) error {
if from >= len(a) || to >= len(a) {
return errors.New("index out of range")
}
a[from], a[to] = a[to], a[from]
return nil
}
func lookupInt(name string, set *flag.FlagSet) int { func lookupInt(name string, set *flag.FlagSet) int {
f := set.Lookup(name) f := set.Lookup(name)
if f != nil { if f != nil {
@ -153,6 +209,18 @@ func lookupInt(name string, set *flag.FlagSet) int {
return 0 return 0
} }
func lookupDuration(name string, set *flag.FlagSet) time.Duration {
f := set.Lookup(name)
if f != nil {
val, err := time.ParseDuration(f.Value.String())
if err == nil {
return val
}
}
return 0
}
func lookupFloat64(name string, set *flag.FlagSet) float64 { func lookupFloat64(name string, set *flag.FlagSet) float64 {
f := set.Lookup(name) f := set.Lookup(name)
if f != nil { if f != nil {

View File

@ -2,8 +2,10 @@ package cli_test
import ( import (
"flag" "flag"
"github.com/codegangsta/cli"
"testing" "testing"
"time"
"github.com/codegangsta/cli"
) )
func TestNewContext(t *testing.T) { func TestNewContext(t *testing.T) {
@ -26,6 +28,13 @@ func TestContext_Int(t *testing.T) {
expect(t, c.Int("myflag"), 12) expect(t, c.Int("myflag"), 12)
} }
func TestContext_Duration(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Duration("myflag", time.Duration(12*time.Second), "doc")
c := cli.NewContext(nil, set, set)
expect(t, c.Duration("myflag"), time.Duration(12*time.Second))
}
func TestContext_String(t *testing.T) { func TestContext_String(t *testing.T) {
set := flag.NewFlagSet("test", 0) set := flag.NewFlagSet("test", 0)
set.String("myflag", "hello world", "doc") set.String("myflag", "hello world", "doc")
@ -60,9 +69,31 @@ func TestContext_IsSet(t *testing.T) {
set := flag.NewFlagSet("test", 0) set := flag.NewFlagSet("test", 0)
set.Bool("myflag", false, "doc") set.Bool("myflag", false, "doc")
set.String("otherflag", "hello world", "doc") set.String("otherflag", "hello world", "doc")
c := cli.NewContext(nil, set, set) globalSet := flag.NewFlagSet("test", 0)
globalSet.Bool("myflagGlobal", true, "doc")
c := cli.NewContext(nil, set, globalSet)
set.Parse([]string{"--myflag", "bat", "baz"}) set.Parse([]string{"--myflag", "bat", "baz"})
globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"})
expect(t, c.IsSet("myflag"), true) expect(t, c.IsSet("myflag"), true)
expect(t, c.IsSet("otherflag"), false) expect(t, c.IsSet("otherflag"), false)
expect(t, c.IsSet("bogusflag"), false) expect(t, c.IsSet("bogusflag"), false)
expect(t, c.IsSet("myflagGlobal"), false)
}
func TestContext_GlobalIsSet(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("myflag", false, "doc")
set.String("otherflag", "hello world", "doc")
globalSet := flag.NewFlagSet("test", 0)
globalSet.Bool("myflagGlobal", true, "doc")
globalSet.Bool("myflagGlobalUnset", true, "doc")
c := cli.NewContext(nil, set, globalSet)
set.Parse([]string{"--myflag", "bat", "baz"})
globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"})
expect(t, c.GlobalIsSet("myflag"), false)
expect(t, c.GlobalIsSet("otherflag"), false)
expect(t, c.GlobalIsSet("bogusflag"), false)
expect(t, c.GlobalIsSet("myflagGlobal"), true)
expect(t, c.GlobalIsSet("myflagGlobalUnset"), false)
expect(t, c.GlobalIsSet("bogusGlobal"), false)
} }

156
flag.go
View File

@ -3,18 +3,28 @@ package cli
import ( import (
"flag" "flag"
"fmt" "fmt"
"os"
"strconv" "strconv"
"strings" "strings"
"time"
) )
// This flag enables bash-completion for all commands and subcommands // This flag enables bash-completion for all commands and subcommands
var BashCompletionFlag = BoolFlag{"generate-bash-completion", ""} var BashCompletionFlag = BoolFlag{
Name: "generate-bash-completion",
}
// This flag prints the version for the application // This flag prints the version for the application
var VersionFlag = BoolFlag{"version, v", "print the version"} var VersionFlag = BoolFlag{
Name: "version, v",
Usage: "print the version",
}
// This flag prints the help for all commands and subcommands // This flag prints the help for all commands and subcommands
var HelpFlag = BoolFlag{"help, h", "show help"} var HelpFlag = BoolFlag{
Name: "help, h",
Usage: "show help",
}
// Flag is a common interface related to parsing flags in cli. // Flag is a common interface related to parsing flags in cli.
// For more advanced flag parsing techniques, it is recomended that // For more advanced flag parsing techniques, it is recomended that
@ -54,13 +64,21 @@ type GenericFlag struct {
Name string Name string
Value Generic Value Generic
Usage string Usage string
EnvVar string
} }
func (f GenericFlag) String() string { func (f GenericFlag) String() string {
return fmt.Sprintf("%s%s %v\t`%v` %s", prefixFor(f.Name), f.Name, f.Value, "-"+f.Name+" option -"+f.Name+" option", f.Usage) return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s %v\t`%v` %s", prefixFor(f.Name), f.Name, f.Value, "-"+f.Name+" option -"+f.Name+" option", f.Usage))
} }
func (f GenericFlag) Apply(set *flag.FlagSet) { func (f GenericFlag) Apply(set *flag.FlagSet) {
val := f.Value
if f.EnvVar != "" {
if envVal := os.Getenv(f.EnvVar); envVal != "" {
val.Set(envVal)
}
}
eachName(f.Name, func(name string) { eachName(f.Name, func(name string) {
set.Var(f.Value, name, f.Usage) set.Var(f.Value, name, f.Usage)
}) })
@ -89,15 +107,26 @@ type StringSliceFlag struct {
Name string Name string
Value *StringSlice Value *StringSlice
Usage string Usage string
EnvVar string
} }
func (f StringSliceFlag) String() string { func (f StringSliceFlag) String() string {
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
pref := prefixFor(firstName) pref := prefixFor(firstName)
return fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage) return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
} }
func (f StringSliceFlag) Apply(set *flag.FlagSet) { func (f StringSliceFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
if envVal := os.Getenv(f.EnvVar); envVal != "" {
newVal := &StringSlice{}
for _, s := range strings.Split(envVal, ",") {
newVal.Set(s)
}
f.Value = newVal
}
}
eachName(f.Name, func(name string) { eachName(f.Name, func(name string) {
set.Var(f.Value, name, f.Usage) set.Var(f.Value, name, f.Usage)
}) })
@ -132,15 +161,29 @@ type IntSliceFlag struct {
Name string Name string
Value *IntSlice Value *IntSlice
Usage string Usage string
EnvVar string
} }
func (f IntSliceFlag) String() string { func (f IntSliceFlag) String() string {
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
pref := prefixFor(firstName) pref := prefixFor(firstName)
return fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage) return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
} }
func (f IntSliceFlag) Apply(set *flag.FlagSet) { func (f IntSliceFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
if envVal := os.Getenv(f.EnvVar); envVal != "" {
newVal := &IntSlice{}
for _, s := range strings.Split(envVal, ",") {
err := newVal.Set(s)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
}
}
f.Value = newVal
}
}
eachName(f.Name, func(name string) { eachName(f.Name, func(name string) {
set.Var(f.Value, name, f.Usage) set.Var(f.Value, name, f.Usage)
}) })
@ -153,15 +196,26 @@ func (f IntSliceFlag) getName() string {
type BoolFlag struct { type BoolFlag struct {
Name string Name string
Usage string Usage string
EnvVar string
} }
func (f BoolFlag) String() string { func (f BoolFlag) String() string {
return fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage) return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
} }
func (f BoolFlag) Apply(set *flag.FlagSet) { func (f BoolFlag) Apply(set *flag.FlagSet) {
val := false
if f.EnvVar != "" {
if envVal := os.Getenv(f.EnvVar); envVal != "" {
envValBool, err := strconv.ParseBool(envVal)
if err == nil {
val = envValBool
}
}
}
eachName(f.Name, func(name string) { eachName(f.Name, func(name string) {
set.Bool(name, false, f.Usage) set.Bool(name, val, f.Usage)
}) })
} }
@ -172,15 +226,26 @@ func (f BoolFlag) getName() string {
type BoolTFlag struct { type BoolTFlag struct {
Name string Name string
Usage string Usage string
EnvVar string
} }
func (f BoolTFlag) String() string { func (f BoolTFlag) String() string {
return fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage) return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
} }
func (f BoolTFlag) Apply(set *flag.FlagSet) { func (f BoolTFlag) Apply(set *flag.FlagSet) {
val := true
if f.EnvVar != "" {
if envVal := os.Getenv(f.EnvVar); envVal != "" {
envValBool, err := strconv.ParseBool(envVal)
if err == nil {
val = envValBool
}
}
}
eachName(f.Name, func(name string) { eachName(f.Name, func(name string) {
set.Bool(name, true, f.Usage) set.Bool(name, val, f.Usage)
}) })
} }
@ -192,6 +257,7 @@ type StringFlag struct {
Name string Name string
Value string Value string
Usage string Usage string
EnvVar string
} }
func (f StringFlag) String() string { func (f StringFlag) String() string {
@ -204,10 +270,16 @@ func (f StringFlag) String() string {
fmtString = "%s %v\t%v" fmtString = "%s %v\t%v"
} }
return fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage) return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage))
} }
func (f StringFlag) Apply(set *flag.FlagSet) { func (f StringFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
if envVal := os.Getenv(f.EnvVar); envVal != "" {
f.Value = envVal
}
}
eachName(f.Name, func(name string) { eachName(f.Name, func(name string) {
set.String(name, f.Value, f.Usage) set.String(name, f.Value, f.Usage)
}) })
@ -221,13 +293,23 @@ type IntFlag struct {
Name string Name string
Value int Value int
Usage string Usage string
EnvVar string
} }
func (f IntFlag) String() string { func (f IntFlag) String() string {
return fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage) return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage))
} }
func (f IntFlag) Apply(set *flag.FlagSet) { func (f IntFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
if envVal := os.Getenv(f.EnvVar); envVal != "" {
envValInt, err := strconv.ParseUint(envVal, 10, 64)
if err == nil {
f.Value = int(envValInt)
}
}
}
eachName(f.Name, func(name string) { eachName(f.Name, func(name string) {
set.Int(name, f.Value, f.Usage) set.Int(name, f.Value, f.Usage)
}) })
@ -237,17 +319,57 @@ func (f IntFlag) getName() string {
return f.Name return f.Name
} }
type DurationFlag struct {
Name string
Value time.Duration
Usage string
EnvVar string
}
func (f DurationFlag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage))
}
func (f DurationFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
if envVal := os.Getenv(f.EnvVar); envVal != "" {
envValDuration, err := time.ParseDuration(envVal)
if err == nil {
f.Value = envValDuration
}
}
}
eachName(f.Name, func(name string) {
set.Duration(name, f.Value, f.Usage)
})
}
func (f DurationFlag) getName() string {
return f.Name
}
type Float64Flag struct { type Float64Flag struct {
Name string Name string
Value float64 Value float64
Usage string Usage string
EnvVar string
} }
func (f Float64Flag) String() string { func (f Float64Flag) String() string {
return fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage) return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage))
} }
func (f Float64Flag) Apply(set *flag.FlagSet) { func (f Float64Flag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
if envVal := os.Getenv(f.EnvVar); envVal != "" {
envValFloat, err := strconv.ParseFloat(envVal, 10)
if err == nil {
f.Value = float64(envValFloat)
}
}
}
eachName(f.Name, func(name string) { eachName(f.Name, func(name string) {
set.Float64(name, f.Value, f.Usage) set.Float64(name, f.Value, f.Usage)
}) })
@ -278,3 +400,11 @@ func prefixedNames(fullName string) (prefixed string) {
} }
return return
} }
func withEnvHint(envVar, str string) string {
envText := ""
if envVar != "" {
envText = fmt.Sprintf(" [$%s]", envVar)
}
return str + envText
}

View File

@ -1,12 +1,13 @@
package cli_test package cli_test
import ( import (
"github.com/codegangsta/cli"
"fmt" "fmt"
"os"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
"github.com/codegangsta/cli"
) )
var boolFlagTests = []struct { var boolFlagTests = []struct {
@ -52,6 +53,71 @@ func TestStringFlagHelpOutput(t *testing.T) {
} }
} }
func TestStringFlagWithEnvVarHelpOutput(t *testing.T) {
os.Setenv("APP_FOO", "derp")
for _, test := range stringFlagTests {
flag := cli.StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"}
output := flag.String()
if !strings.HasSuffix(output, " [$APP_FOO]") {
t.Errorf("%s does not end with [$APP_FOO]", output)
}
}
}
var stringSliceFlagTests = []struct {
name string
value *cli.StringSlice
expected string
}{
{"help", func() *cli.StringSlice {
s := &cli.StringSlice{}
s.Set("")
return s
}(), "--help '--help option --help option'\t"},
{"h", func() *cli.StringSlice {
s := &cli.StringSlice{}
s.Set("")
return s
}(), "-h '-h option -h option'\t"},
{"h", func() *cli.StringSlice {
s := &cli.StringSlice{}
s.Set("")
return s
}(), "-h '-h option -h option'\t"},
{"test", func() *cli.StringSlice {
s := &cli.StringSlice{}
s.Set("Something")
return s
}(), "--test '--test option --test option'\t"},
}
func TestStringSliceFlagHelpOutput(t *testing.T) {
for _, test := range stringSliceFlagTests {
flag := cli.StringSliceFlag{Name: test.name, Value: test.value}
output := flag.String()
if output != test.expected {
t.Errorf("%q does not match %q", output, test.expected)
}
}
}
func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) {
os.Setenv("APP_QWWX", "11,4")
for _, test := range stringSliceFlagTests {
flag := cli.StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"}
output := flag.String()
if !strings.HasSuffix(output, " [$APP_QWWX]") {
t.Errorf("%q does not end with [$APP_QWWX]", output)
}
}
}
var intFlagTests = []struct { var intFlagTests = []struct {
name string name string
expected string expected string
@ -72,6 +138,92 @@ func TestIntFlagHelpOutput(t *testing.T) {
} }
} }
func TestIntFlagWithEnvVarHelpOutput(t *testing.T) {
os.Setenv("APP_BAR", "2")
for _, test := range intFlagTests {
flag := cli.IntFlag{Name: test.name, EnvVar: "APP_BAR"}
output := flag.String()
if !strings.HasSuffix(output, " [$APP_BAR]") {
t.Errorf("%s does not end with [$APP_BAR]", output)
}
}
}
var durationFlagTests = []struct {
name string
expected string
}{
{"help", "--help '0'\t"},
{"h", "-h '0'\t"},
}
func TestDurationFlagHelpOutput(t *testing.T) {
for _, test := range durationFlagTests {
flag := cli.DurationFlag{Name: test.name}
output := flag.String()
if output != test.expected {
t.Errorf("%s does not match %s", output, test.expected)
}
}
}
func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) {
os.Setenv("APP_BAR", "2h3m6s")
for _, test := range durationFlagTests {
flag := cli.DurationFlag{Name: test.name, EnvVar: "APP_BAR"}
output := flag.String()
if !strings.HasSuffix(output, " [$APP_BAR]") {
t.Errorf("%s does not end with [$APP_BAR]", output)
}
}
}
var intSliceFlagTests = []struct {
name string
value *cli.IntSlice
expected string
}{
{"help", &cli.IntSlice{}, "--help '--help option --help option'\t"},
{"h", &cli.IntSlice{}, "-h '-h option -h option'\t"},
{"h", &cli.IntSlice{}, "-h '-h option -h option'\t"},
{"test", func() *cli.IntSlice {
i := &cli.IntSlice{}
i.Set("9")
return i
}(), "--test '--test option --test option'\t"},
}
func TestIntSliceFlagHelpOutput(t *testing.T) {
for _, test := range intSliceFlagTests {
flag := cli.IntSliceFlag{Name: test.name, Value: test.value}
output := flag.String()
if output != test.expected {
t.Errorf("%q does not match %q", output, test.expected)
}
}
}
func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) {
os.Setenv("APP_SMURF", "42,3")
for _, test := range intSliceFlagTests {
flag := cli.IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"}
output := flag.String()
if !strings.HasSuffix(output, " [$APP_SMURF]") {
t.Errorf("%q does not end with [$APP_SMURF]", output)
}
}
}
var float64FlagTests = []struct { var float64FlagTests = []struct {
name string name string
expected string expected string
@ -92,6 +244,54 @@ func TestFloat64FlagHelpOutput(t *testing.T) {
} }
} }
func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) {
os.Setenv("APP_BAZ", "99.4")
for _, test := range float64FlagTests {
flag := cli.Float64Flag{Name: test.name, EnvVar: "APP_BAZ"}
output := flag.String()
if !strings.HasSuffix(output, " [$APP_BAZ]") {
t.Errorf("%s does not end with [$APP_BAZ]", output)
}
}
}
var genericFlagTests = []struct {
name string
value cli.Generic
expected string
}{
{"help", &Parser{}, "--help <nil>\t`-help option -help option` "},
{"h", &Parser{}, "-h <nil>\t`-h option -h option` "},
{"test", &Parser{}, "--test <nil>\t`-test option -test option` "},
}
func TestGenericFlagHelpOutput(t *testing.T) {
for _, test := range genericFlagTests {
flag := cli.GenericFlag{Name: test.name}
output := flag.String()
if output != test.expected {
t.Errorf("%q does not match %q", output, test.expected)
}
}
}
func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) {
os.Setenv("APP_ZAP", "3")
for _, test := range genericFlagTests {
flag := cli.GenericFlag{Name: test.name, EnvVar: "APP_ZAP"}
output := flag.String()
if !strings.HasSuffix(output, " [$APP_ZAP]") {
t.Errorf("%s does not end with [$APP_ZAP]", output)
}
}
}
func TestParseMultiString(t *testing.T) { func TestParseMultiString(t *testing.T) {
(&cli.App{ (&cli.App{
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -108,6 +308,23 @@ func TestParseMultiString(t *testing.T) {
}).Run([]string{"run", "-s", "10"}) }).Run([]string{"run", "-s", "10"})
} }
func TestParseMultiStringFromEnv(t *testing.T) {
os.Setenv("APP_COUNT", "20")
(&cli.App{
Flags: []cli.Flag{
cli.StringFlag{Name: "count, c", EnvVar: "APP_COUNT"},
},
Action: func(ctx *cli.Context) {
if ctx.String("count") != "20" {
t.Errorf("main name not set")
}
if ctx.String("c") != "20" {
t.Errorf("short name not set")
}
},
}).Run([]string{"run"})
}
func TestParseMultiStringSlice(t *testing.T) { func TestParseMultiStringSlice(t *testing.T) {
(&cli.App{ (&cli.App{
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -124,6 +341,24 @@ func TestParseMultiStringSlice(t *testing.T) {
}).Run([]string{"run", "-s", "10", "-s", "20"}) }).Run([]string{"run", "-s", "10", "-s", "20"})
} }
func TestParseMultiStringSliceFromEnv(t *testing.T) {
os.Setenv("APP_INTERVALS", "20,30,40")
(&cli.App{
Flags: []cli.Flag{
cli.StringSliceFlag{Name: "intervals, i", Value: &cli.StringSlice{}, EnvVar: "APP_INTERVALS"},
},
Action: func(ctx *cli.Context) {
if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
t.Errorf("main name not set from env")
}
if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) {
t.Errorf("short name not set from env")
}
},
}).Run([]string{"run"})
}
func TestParseMultiInt(t *testing.T) { func TestParseMultiInt(t *testing.T) {
a := cli.App{ a := cli.App{
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -141,6 +376,93 @@ func TestParseMultiInt(t *testing.T) {
a.Run([]string{"run", "-s", "10"}) a.Run([]string{"run", "-s", "10"})
} }
func TestParseMultiIntFromEnv(t *testing.T) {
os.Setenv("APP_TIMEOUT_SECONDS", "10")
a := cli.App{
Flags: []cli.Flag{
cli.IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
},
Action: func(ctx *cli.Context) {
if ctx.Int("timeout") != 10 {
t.Errorf("main name not set")
}
if ctx.Int("t") != 10 {
t.Errorf("short name not set")
}
},
}
a.Run([]string{"run"})
}
func TestParseMultiIntSlice(t *testing.T) {
(&cli.App{
Flags: []cli.Flag{
cli.IntSliceFlag{Name: "serve, s", Value: &cli.IntSlice{}},
},
Action: func(ctx *cli.Context) {
if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) {
t.Errorf("main name not set")
}
if !reflect.DeepEqual(ctx.IntSlice("s"), []int{10, 20}) {
t.Errorf("short name not set")
}
},
}).Run([]string{"run", "-s", "10", "-s", "20"})
}
func TestParseMultiIntSliceFromEnv(t *testing.T) {
os.Setenv("APP_INTERVALS", "20,30,40")
(&cli.App{
Flags: []cli.Flag{
cli.IntSliceFlag{Name: "intervals, i", Value: &cli.IntSlice{}, EnvVar: "APP_INTERVALS"},
},
Action: func(ctx *cli.Context) {
if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) {
t.Errorf("main name not set from env")
}
if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) {
t.Errorf("short name not set from env")
}
},
}).Run([]string{"run"})
}
func TestParseMultiFloat64(t *testing.T) {
a := cli.App{
Flags: []cli.Flag{
cli.Float64Flag{Name: "serve, s"},
},
Action: func(ctx *cli.Context) {
if ctx.Float64("serve") != 10.2 {
t.Errorf("main name not set")
}
if ctx.Float64("s") != 10.2 {
t.Errorf("short name not set")
}
},
}
a.Run([]string{"run", "-s", "10.2"})
}
func TestParseMultiFloat64FromEnv(t *testing.T) {
os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
a := cli.App{
Flags: []cli.Flag{
cli.Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
},
Action: func(ctx *cli.Context) {
if ctx.Float64("timeout") != 15.5 {
t.Errorf("main name not set")
}
if ctx.Float64("t") != 15.5 {
t.Errorf("short name not set")
}
},
}
a.Run([]string{"run"})
}
func TestParseMultiBool(t *testing.T) { func TestParseMultiBool(t *testing.T) {
a := cli.App{ a := cli.App{
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -158,6 +480,59 @@ func TestParseMultiBool(t *testing.T) {
a.Run([]string{"run", "--serve"}) a.Run([]string{"run", "--serve"})
} }
func TestParseMultiBoolFromEnv(t *testing.T) {
os.Setenv("APP_DEBUG", "1")
a := cli.App{
Flags: []cli.Flag{
cli.BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"},
},
Action: func(ctx *cli.Context) {
if ctx.Bool("debug") != true {
t.Errorf("main name not set from env")
}
if ctx.Bool("d") != true {
t.Errorf("short name not set from env")
}
},
}
a.Run([]string{"run"})
}
func TestParseMultiBoolT(t *testing.T) {
a := cli.App{
Flags: []cli.Flag{
cli.BoolTFlag{Name: "serve, s"},
},
Action: func(ctx *cli.Context) {
if ctx.BoolT("serve") != true {
t.Errorf("main name not set")
}
if ctx.BoolT("s") != true {
t.Errorf("short name not set")
}
},
}
a.Run([]string{"run", "--serve"})
}
func TestParseMultiBoolTFromEnv(t *testing.T) {
os.Setenv("APP_DEBUG", "0")
a := cli.App{
Flags: []cli.Flag{
cli.BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"},
},
Action: func(ctx *cli.Context) {
if ctx.BoolT("debug") != false {
t.Errorf("main name not set from env")
}
if ctx.BoolT("d") != false {
t.Errorf("short name not set from env")
}
},
}
a.Run([]string{"run"})
}
type Parser [2]string type Parser [2]string
func (p *Parser) Set(value string) error { func (p *Parser) Set(value string) error {
@ -192,3 +567,21 @@ func TestParseGeneric(t *testing.T) {
} }
a.Run([]string{"run", "-s", "10,20"}) a.Run([]string{"run", "-s", "10,20"})
} }
func TestParseGenericFromEnv(t *testing.T) {
os.Setenv("APP_SERVE", "20,30")
a := cli.App{
Flags: []cli.Flag{
cli.GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"},
},
Action: func(ctx *cli.Context) {
if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"20", "30"}) {
t.Errorf("main name not set from env")
}
if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"20", "30"}) {
t.Errorf("short name not set from env")
}
},
}
a.Run([]string{"run"})
}

33
help.go
View File

@ -12,17 +12,21 @@ var AppHelpTemplate = `NAME:
{{.Name}} - {{.Usage}} {{.Name}} - {{.Usage}}
USAGE: USAGE:
{{.Name}} [global options] command [command options] [arguments...] {{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...]
VERSION: VERSION:
{{.Version}} {{.Version}}{{if or .Author .Email}}
AUTHOR:{{if .Author}}
{{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}}
{{.Email}}{{end}}{{end}}
COMMANDS: COMMANDS:
{{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
{{end}} {{end}}{{if .Flags}}
GLOBAL OPTIONS: GLOBAL OPTIONS:
{{range .Flags}}{{.}} {{range .Flags}}{{.}}
{{end}} {{end}}{{end}}
` `
// The text template for the command help topic. // The text template for the command help topic.
@ -32,14 +36,14 @@ var CommandHelpTemplate = `NAME:
{{.Name}} - {{.Usage}} {{.Name}} - {{.Usage}}
USAGE: USAGE:
command {{.Name}} [command options] [arguments...] command {{.Name}}{{if .Flags}} [command options]{{end}} [arguments...]{{if .Description}}
DESCRIPTION: DESCRIPTION:
{{.Description}} {{.Description}}{{end}}{{if .Flags}}
OPTIONS: OPTIONS:
{{range .Flags}}{{.}} {{range .Flags}}{{.}}
{{end}} {{end}}{{ end }}
` `
// The text template for the subcommand help topic. // The text template for the subcommand help topic.
@ -49,14 +53,14 @@ var SubcommandHelpTemplate = `NAME:
{{.Name}} - {{.Usage}} {{.Name}} - {{.Usage}}
USAGE: USAGE:
{{.Name}} [global options] command [command options] [arguments...] {{.Name}} command{{if .Flags}} [command options]{{end}} [arguments...]
COMMANDS: COMMANDS:
{{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
{{end}} {{end}}{{if .Flags}}
OPTIONS: OPTIONS:
{{range .Flags}}{{.}} {{range .Flags}}{{.}}
{{end}} {{end}}{{end}}
` `
var helpCommand = Command{ var helpCommand = Command{
@ -92,6 +96,9 @@ type helpPrinter func(templ string, data interface{})
var HelpPrinter helpPrinter = nil var HelpPrinter helpPrinter = nil
// Prints version for the App
var VersionPrinter = printVersion
func ShowAppHelp(c *Context) { func ShowAppHelp(c *Context) {
HelpPrinter(AppHelpTemplate, c.App) HelpPrinter(AppHelpTemplate, c.App)
} }
@ -129,6 +136,10 @@ func ShowSubcommandHelp(c *Context) {
// Prints the version number of the App // Prints the version number of the App
func ShowVersion(c *Context) { func ShowVersion(c *Context) {
VersionPrinter(c)
}
func printVersion(c *Context) {
io.WriteString(c.App.Writer, fmt.Sprintf("%v version %v\n", c.App.Name, c.App.Version)) io.WriteString(c.App.Writer, fmt.Sprintf("%v version %v\n", c.App.Name, c.App.Version))
} }
@ -185,7 +196,7 @@ func checkSubcommandHelp(c *Context) bool {
} }
func checkCompletions(c *Context) bool { func checkCompletions(c *Context) bool {
if c.GlobalBool(BashCompletionFlag.Name) && c.App.EnableBashCompletion { if (c.GlobalBool(BashCompletionFlag.Name) || c.Bool(BashCompletionFlag.Name)) && c.App.EnableBashCompletion {
ShowCompletions(c) ShowCompletions(c)
return true return true
} }