Godep: update codegangsta/cli dependency

Update codegangsta/cli dependency to the newer version for ArgsUsage
This commit is contained in:
Anthony Romano 2015-12-18 17:46:40 -08:00
parent bff857dc28
commit 9ee9e552e6
16 changed files with 680 additions and 1475 deletions

4
Godeps/Godeps.json generated
View File

@ -37,8 +37,8 @@
}, },
{ {
"ImportPath": "github.com/codegangsta/cli", "ImportPath": "github.com/codegangsta/cli",
"Comment": "1.2.0-26-gf7ebb76", "Comment": "1.2.0-183-gb5232bb",
"Rev": "f7ebb761e83e21225d1d8954fde853bf8edd46c4" "Rev": "b5232bb2934f606f9f27a1305f1eea224e8e8b88"
}, },
{ {
"ImportPath": "github.com/coreos/go-semver/semver", "ImportPath": "github.com/coreos/go-semver/semver",

View File

@ -1,5 +1,18 @@
language: go language: go
go: 1.1 sudo: false
go:
- 1.0.3
- 1.1.2
- 1.2.2
- 1.3.3
- 1.4.2
- 1.5.1
- tip
matrix:
allow_failures:
- go: tip
script: script:
- go vet ./... - go vet ./...

View File

@ -1,18 +1,17 @@
[![Coverage](http://gocover.io/_badge/github.com/codegangsta/cli?0)](http://gocover.io/github.com/codegangsta/cli)
[![Build Status](https://travis-ci.org/codegangsta/cli.png?branch=master)](https://travis-ci.org/codegangsta/cli) [![Build Status](https://travis-ci.org/codegangsta/cli.png?branch=master)](https://travis-ci.org/codegangsta/cli)
[![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli)
# cli.go # cli.go
cli.go is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. `cli.go` is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way.
You can view the API docs here:
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:
``` ```
@ -25,7 +24,7 @@ export PATH=$PATH:$GOPATH/bin
``` ```
## Getting Started ## Getting Started
One of the philosophies behind cli.go is that an API should be playful and full of discovery. So a cli.go app can be as little as one line of code in `main()`. One of the philosophies behind `cli.go` is that an API should be playful and full of discovery. So a `cli.go` app can be as little as one line of code in `main()`.
``` go ``` go
package main package main
@ -68,8 +67,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 (
@ -102,7 +102,8 @@ $ greet
Hello friend! Hello friend!
``` ```
cli.go also generates some bitchass help text: `cli.go` also generates neat help text:
``` ```
$ greet help $ greet help
NAME: NAME:
@ -157,6 +158,34 @@ app.Action = func(c *cli.Context) {
... ...
``` ```
You can also set a destination variable for a flag, to which the content will be scanned.
``` go
...
var language string
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang",
Value: "english",
Usage: "language for the greeting",
Destination: &language,
},
}
app.Action = func(c *cli.Context) {
name := "someone"
if len(c.Args()) > 0 {
name = c.Args()[0]
}
if language == "spanish" {
println("Hola", name)
} else {
println("Hello", name)
}
}
...
```
See full list of flags at http://godoc.org/github.com/codegangsta/cli
#### 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.
@ -171,6 +200,8 @@ app.Flags = []cli.Flag {
} }
``` ```
That flag can then be set with `--lang spanish` or `-l spanish`. Note that giving two different forms of the same flag in the same command invocation is an error.
#### Values from the Environment #### Values from the Environment
You can also have the default value set from the environment via `EnvVar`. e.g. You can also have the default value set from the environment via `EnvVar`. e.g.
@ -186,7 +217,18 @@ app.Flags = []cli.Flag {
} }
``` ```
That flag can then be set with `--lang spanish` or `-l spanish`. Note that giving two different forms of the same flag in the same command invocation is an error. The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default.
``` go
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG",
},
}
```
### Subcommands ### Subcommands
@ -196,7 +238,7 @@ Subcommands can be defined for a more git-like command line app.
app.Commands = []cli.Command{ app.Commands = []cli.Command{
{ {
Name: "add", Name: "add",
ShortName: "a", Aliases: []string{"a"},
Usage: "add a task to the list", Usage: "add a task to the list",
Action: func(c *cli.Context) { Action: func(c *cli.Context) {
println("added task: ", c.Args().First()) println("added task: ", c.Args().First())
@ -204,7 +246,7 @@ app.Commands = []cli.Command{
}, },
{ {
Name: "complete", Name: "complete",
ShortName: "c", Aliases: []string{"c"},
Usage: "complete a task on the list", Usage: "complete a task on the list",
Action: func(c *cli.Context) { Action: func(c *cli.Context) {
println("completed task: ", c.Args().First()) println("completed task: ", c.Args().First())
@ -212,7 +254,7 @@ app.Commands = []cli.Command{
}, },
{ {
Name: "template", Name: "template",
ShortName: "r", Aliases: []string{"r"},
Usage: "options for task templates", Usage: "options for task templates",
Subcommands: []cli.Command{ Subcommands: []cli.Command{
{ {
@ -249,7 +291,7 @@ app.EnableBashCompletion = true
app.Commands = []cli.Command{ app.Commands = []cli.Command{
{ {
Name: "complete", Name: "complete",
ShortName: "c", Aliases: []string{"c"},
Usage: "complete a task on the list", Usage: "complete a task on the list",
Action: func(c *cli.Context) { Action: func(c *cli.Context) {
println("completed task: ", c.Args().First()) println("completed task: ", c.Args().First())
@ -275,13 +317,25 @@ setting the `PROG` variable to the name of your program:
`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` `PROG=myprogram source /.../cli/autocomplete/bash_autocomplete`
#### To Distribute
Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename
it to the name of the program you wish to add autocomplete support for (or
automatically install it there if you are distributing a package). Don't forget
to source the file to make it active in the current shell.
```
sudo cp src/bash_autocomplete /etc/bash_completion.d/<myprogram>
source /etc/bash_completion.d/<myprogram>
```
Alternatively, you can just document that users should source the generic
`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set
to the name of their program (as above).
## Contribution Guidelines ## Contribution Guidelines
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. 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 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. 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.
## About
cli.go is written by none other than the [Code Gangsta](http://codegangsta.io)

View File

@ -2,18 +2,23 @@ package cli
import ( import (
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"time" "time"
) )
// App is the main structure of a cli application. It is recomended that // App is the main structure of a cli application. It is recomended that
// and app be created with the cli.NewApp() function // an app be created with the cli.NewApp() function
type App struct { type App struct {
// The name of the program. Defaults to os.Args[0] // The name of the program. Defaults to os.Args[0]
Name string Name string
// Full name of command for help, defaults to Name
HelpName string
// Description of the program. // Description of the program.
Usage string Usage string
// Description of the program argument format.
ArgsUsage string
// Version of the program // Version of the program
Version string Version string
// List of commands to execute // List of commands to execute
@ -24,21 +29,32 @@ type App struct {
EnableBashCompletion bool EnableBashCompletion bool
// Boolean to hide built-in help command // Boolean to hide built-in help command
HideHelp bool 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
// If a non-nil error is returned, no subcommands are run // If a non-nil error is returned, no subcommands are run
Before func(context *Context) error Before func(context *Context) error
// An action to execute after any subcommands are run, but after the subcommand has finished
// It is run even if Action() panics
After func(context *Context) error
// The action to execute when no subcommands are specified // The action to execute when no subcommands are specified
Action func(context *Context) Action func(context *Context)
// Execute this function if the proper command cannot be found // Execute this function if the proper command cannot be found
CommandNotFound func(context *Context, command string) CommandNotFound func(context *Context, command string)
// Compilation date // Compilation date
Compiled time.Time Compiled time.Time
// Author // List of all authors who contributed
Authors []Author
// Copyright of the binary if any
Copyright string
// Name of Author (Note: Use App.Authors, this is deprecated)
Author string Author string
// Author e-mail // Email of Author (Note: Use App.Authors, this is deprecated)
Email string Email string
// Writer writer to write output to
Writer io.Writer
} }
// Tries to find out when this binary was compiled. // Tries to find out when this binary was compiled.
@ -55,61 +71,95 @@ func compileTime() time.Time {
func NewApp() *App { func NewApp() *App {
return &App{ return &App{
Name: os.Args[0], Name: os.Args[0],
HelpName: os.Args[0],
Usage: "A new cli application", Usage: "A new cli application",
Version: "0.0.0", Version: "0.0.0",
BashComplete: DefaultAppComplete, BashComplete: DefaultAppComplete,
Action: helpCommand.Action, Action: helpCommand.Action,
Compiled: compileTime(), Compiled: compileTime(),
Writer: os.Stdout,
} }
} }
// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination // Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
func (a *App) Run(arguments []string) error { func (a *App) Run(arguments []string) (err error) {
if a.Author != "" || a.Email != "" {
a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email})
}
newCmds := []Command{}
for _, c := range a.Commands {
if c.HelpName == "" {
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
}
newCmds = append(newCmds, c)
}
a.Commands = newCmds
// append help to commands // append help to commands
if a.Command(helpCommand.Name) == nil && !a.HideHelp { if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand) a.Commands = append(a.Commands, helpCommand)
if (HelpFlag != BoolFlag{}) {
a.appendFlag(HelpFlag) 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)
}
// 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(arguments[1:]) err = set.Parse(arguments[1:])
nerr := normalizeFlags(a.Flags, set) nerr := normalizeFlags(a.Flags, set)
if nerr != nil { if nerr != nil {
fmt.Println(nerr) fmt.Fprintln(a.Writer, nerr)
context := NewContext(a, set, set) context := NewContext(a, set, nil)
ShowAppHelp(context) ShowAppHelp(context)
fmt.Println("")
return nerr return nerr
} }
context := NewContext(a, set, set) context := NewContext(a, set, nil)
if err != nil {
fmt.Printf("Incorrect Usage.\n\n")
ShowAppHelp(context)
fmt.Println("")
return err
}
if checkCompletions(context) { if checkCompletions(context) {
return nil return nil
} }
if checkHelp(context) { if err != nil {
fmt.Fprintln(a.Writer, "Incorrect Usage.")
fmt.Fprintln(a.Writer)
ShowAppHelp(context)
return err
}
if !a.HideHelp && checkHelp(context) {
ShowAppHelp(context)
return nil return nil
} }
if checkVersion(context) { if !a.HideVersion && checkVersion(context) {
ShowVersion(context)
return nil return nil
} }
if a.After != nil {
defer func() {
afterErr := a.After(context)
if afterErr != nil {
if err != nil {
err = NewMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
}
if a.Before != nil { if a.Before != nil {
err := a.Before(context) err := a.Before(context)
if err != nil { if err != nil {
@ -134,20 +184,31 @@ func (a *App) Run(arguments []string) error {
// Another entry point to the cli app, takes care of passing arguments and error handling // Another entry point to the cli app, takes care of passing arguments and error handling
func (a *App) RunAndExitOnError() { func (a *App) RunAndExitOnError() {
if err := a.Run(os.Args); err != nil { if err := a.Run(os.Args); err != nil {
os.Stderr.WriteString(fmt.Sprintln(err)) fmt.Fprintln(os.Stderr, err)
os.Exit(1) 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) (err 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 && !a.HideHelp { if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand) a.Commands = append(a.Commands, helpCommand)
if (HelpFlag != BoolFlag{}) {
a.appendFlag(HelpFlag) a.appendFlag(HelpFlag)
} }
} }
}
newCmds := []Command{}
for _, c := range a.Commands {
if c.HelpName == "" {
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
}
newCmds = append(newCmds, c)
}
a.Commands = newCmds
// append flags // append flags
if a.EnableBashCompletion { if a.EnableBashCompletion {
@ -157,31 +218,32 @@ func (a *App) RunAsSubcommand(ctx *Context) error {
// 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, ctx.globalSet) context := NewContext(a, set, ctx)
if nerr != nil { if nerr != nil {
fmt.Println(nerr) fmt.Fprintln(a.Writer, nerr)
fmt.Fprintln(a.Writer)
if len(a.Commands) > 0 { if len(a.Commands) > 0 {
ShowSubcommandHelp(context) ShowSubcommandHelp(context)
} else { } else {
ShowCommandHelp(ctx, context.Args().First()) ShowCommandHelp(ctx, context.Args().First())
} }
fmt.Println("")
return nerr return nerr
} }
if err != nil {
fmt.Printf("Incorrect Usage.\n\n")
ShowSubcommandHelp(context)
return err
}
if checkCompletions(context) { if checkCompletions(context) {
return nil return nil
} }
if err != nil {
fmt.Fprintln(a.Writer, "Incorrect Usage.")
fmt.Fprintln(a.Writer)
ShowSubcommandHelp(context)
return err
}
if len(a.Commands) > 0 { if len(a.Commands) > 0 {
if checkSubcommandHelp(context) { if checkSubcommandHelp(context) {
return nil return nil
@ -192,6 +254,19 @@ func (a *App) RunAsSubcommand(ctx *Context) error {
} }
} }
if a.After != nil {
defer func() {
afterErr := a.After(context)
if afterErr != nil {
if err != nil {
err = NewMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
}
if a.Before != nil { if a.Before != nil {
err := a.Before(context) err := a.Before(context)
if err != nil { if err != nil {
@ -209,11 +284,7 @@ func (a *App) RunAsSubcommand(ctx *Context) error {
} }
// Run default Action // Run default Action
if len(a.Commands) > 0 {
a.Action(context) a.Action(context)
} else {
a.Action(ctx)
}
return nil return nil
} }
@ -244,3 +315,19 @@ func (a *App) appendFlag(flag Flag) {
a.Flags = append(a.Flags, flag) a.Flags = append(a.Flags, flag)
} }
} }
// Author represents someone who has contributed to a cli project.
type Author struct {
Name string // The Authors name
Email string // The Authors email
}
// String makes Author comply to the Stringer interface, to allow an easy print in the templating process
func (a Author) String() string {
e := ""
if a.Email != "" {
e = "<" + a.Email + "> "
}
return fmt.Sprintf("%v %v", a.Name, e)
}

View File

@ -1,423 +0,0 @@
package cli_test
import (
"fmt"
"os"
"testing"
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli"
)
func ExampleApp() {
// set args for examples sake
os.Args = []string{"greet", "--name", "Jeremy"}
app := cli.NewApp()
app.Name = "greet"
app.Flags = []cli.Flag{
cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
}
app.Action = func(c *cli.Context) {
fmt.Printf("Hello %v\n", c.String("name"))
}
app.Run(os.Args)
// Output:
// Hello Jeremy
}
func ExampleAppSubcommand() {
// set args for examples sake
os.Args = []string{"say", "hi", "english", "--name", "Jeremy"}
app := cli.NewApp()
app.Name = "say"
app.Commands = []cli.Command{
{
Name: "hello",
ShortName: "hi",
Usage: "use it to see a description",
Description: "This is how we describe hello the function",
Subcommands: []cli.Command{
{
Name: "english",
ShortName: "en",
Usage: "sends a greeting in english",
Description: "greets someone in english",
Flags: []cli.Flag{
cli.StringFlag{
Name: "name",
Value: "Bob",
Usage: "Name of the person to greet",
},
},
Action: func(c *cli.Context) {
fmt.Println("Hello,", c.String("name"))
},
},
},
},
}
app.Run(os.Args)
// Output:
// Hello, Jeremy
}
func ExampleAppHelp() {
// set args for examples sake
os.Args = []string{"greet", "h", "describeit"}
app := cli.NewApp()
app.Name = "greet"
app.Flags = []cli.Flag{
cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
}
app.Commands = []cli.Command{
{
Name: "describeit",
ShortName: "d",
Usage: "use it to see a description",
Description: "This is how we describe describeit the function",
Action: func(c *cli.Context) {
fmt.Printf("i like to describe things")
},
},
}
app.Run(os.Args)
// Output:
// NAME:
// describeit - use it to see a description
//
// USAGE:
// command describeit [arguments...]
//
// DESCRIPTION:
// This is how we describe describeit the function
}
func ExampleAppBashComplete() {
// set args for examples sake
os.Args = []string{"greet", "--generate-bash-completion"}
app := cli.NewApp()
app.Name = "greet"
app.EnableBashCompletion = true
app.Commands = []cli.Command{
{
Name: "describeit",
ShortName: "d",
Usage: "use it to see a description",
Description: "This is how we describe describeit the function",
Action: func(c *cli.Context) {
fmt.Printf("i like to describe things")
},
}, {
Name: "next",
Usage: "next example",
Description: "more stuff to see when generating bash completion",
Action: func(c *cli.Context) {
fmt.Printf("the next example")
},
},
}
app.Run(os.Args)
// Output:
// describeit
// d
// next
// help
// h
}
func TestApp_Run(t *testing.T) {
s := ""
app := cli.NewApp()
app.Action = func(c *cli.Context) {
s = s + c.Args().First()
}
err := app.Run([]string{"command", "foo"})
expect(t, err, nil)
err = app.Run([]string{"command", "bar"})
expect(t, err, nil)
expect(t, s, "foobar")
}
var commandAppTests = []struct {
name string
expected bool
}{
{"foobar", true},
{"batbaz", true},
{"b", true},
{"f", true},
{"bat", false},
{"nothing", false},
}
func TestApp_Command(t *testing.T) {
app := cli.NewApp()
fooCommand := cli.Command{Name: "foobar", ShortName: "f"}
batCommand := cli.Command{Name: "batbaz", ShortName: "b"}
app.Commands = []cli.Command{
fooCommand,
batCommand,
}
for _, test := range commandAppTests {
expect(t, app.Command(test.name) != nil, test.expected)
}
}
func TestApp_CommandWithArgBeforeFlags(t *testing.T) {
var parsedOption, firstArg string
app := cli.NewApp()
command := cli.Command{
Name: "cmd",
Flags: []cli.Flag{
cli.StringFlag{Name: "option", Value: "", Usage: "some option"},
},
Action: func(c *cli.Context) {
parsedOption = c.String("option")
firstArg = c.Args().First()
},
}
app.Commands = []cli.Command{command}
app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"})
expect(t, parsedOption, "my-option")
expect(t, firstArg, "my-arg")
}
func TestApp_Float64Flag(t *testing.T) {
var meters float64
app := cli.NewApp()
app.Flags = []cli.Flag{
cli.Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"},
}
app.Action = func(c *cli.Context) {
meters = c.Float64("height")
}
app.Run([]string{"", "--height", "1.93"})
expect(t, meters, 1.93)
}
func TestApp_ParseSliceFlags(t *testing.T) {
var parsedOption, firstArg string
var parsedIntSlice []int
var parsedStringSlice []string
app := cli.NewApp()
command := cli.Command{
Name: "cmd",
Flags: []cli.Flag{
cli.IntSliceFlag{Name: "p", Value: &cli.IntSlice{}, Usage: "set one or more ip addr"},
cli.StringSliceFlag{Name: "ip", Value: &cli.StringSlice{}, Usage: "set one or more ports to open"},
},
Action: func(c *cli.Context) {
parsedIntSlice = c.IntSlice("p")
parsedStringSlice = c.StringSlice("ip")
parsedOption = c.String("option")
firstArg = c.Args().First()
},
}
app.Commands = []cli.Command{command}
app.Run([]string{"", "cmd", "my-arg", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4"})
IntsEquals := func(a, b []int) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
StrsEquals := func(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
var expectedIntSlice = []int{22, 80}
var expectedStringSlice = []string{"8.8.8.8", "8.8.4.4"}
if !IntsEquals(parsedIntSlice, expectedIntSlice) {
t.Errorf("%v does not match %v", parsedIntSlice, expectedIntSlice)
}
if !StrsEquals(parsedStringSlice, expectedStringSlice) {
t.Errorf("%v does not match %v", parsedStringSlice, expectedStringSlice)
}
}
func TestApp_BeforeFunc(t *testing.T) {
beforeRun, subcommandRun := false, false
beforeError := fmt.Errorf("fail")
var err error
app := cli.NewApp()
app.Before = func(c *cli.Context) error {
beforeRun = true
s := c.String("opt")
if s == "fail" {
return beforeError
}
return nil
}
app.Commands = []cli.Command{
cli.Command{
Name: "sub",
Action: func(c *cli.Context) {
subcommandRun = true
},
},
}
app.Flags = []cli.Flag{
cli.StringFlag{Name: "opt"},
}
// run with the Before() func succeeding
err = app.Run([]string{"command", "--opt", "succeed", "sub"})
if err != nil {
t.Fatalf("Run error: %s", err)
}
if beforeRun == false {
t.Errorf("Before() not executed when expected")
}
if subcommandRun == false {
t.Errorf("Subcommand not executed when expected")
}
// reset
beforeRun, subcommandRun = false, false
// run with the Before() func failing
err = app.Run([]string{"command", "--opt", "fail", "sub"})
// should be the same error produced by the Before func
if err != beforeError {
t.Errorf("Run error expected, but not received")
}
if beforeRun == false {
t.Errorf("Before() not executed when expected")
}
if subcommandRun == true {
t.Errorf("Subcommand executed when NOT expected")
}
}
func TestAppHelpPrinter(t *testing.T) {
oldPrinter := cli.HelpPrinter
defer func() {
cli.HelpPrinter = oldPrinter
}()
var wasCalled = false
cli.HelpPrinter = func(template string, data interface{}) {
wasCalled = true
}
app := cli.NewApp()
app.Run([]string{"-h"})
if wasCalled == false {
t.Errorf("Help printer expected to be called, but was not")
}
}
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) {
beforeRun, subcommandRun := false, false
app := cli.NewApp()
app.CommandNotFound = func(c *cli.Context, command string) {
beforeRun = true
}
app.Commands = []cli.Command{
cli.Command{
Name: "bar",
Action: func(c *cli.Context) {
subcommandRun = true
},
},
}
app.Run([]string{"command", "foo"})
expect(t, beforeRun, true)
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

@ -1,10 +1,11 @@
#! /bin/bash #! /bin/bash
: ${PROG:=$(basename ${BASH_SOURCE})}
_cli_bash_autocomplete() { _cli_bash_autocomplete() {
local cur prev opts base local cur opts base
COMPREPLY=() COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
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

@ -17,3 +17,24 @@
// app.Run(os.Args) // app.Run(os.Args)
// } // }
package cli package cli
import (
"strings"
)
type MultiError struct {
Errors []error
}
func NewMultiError(err ...error) MultiError {
return MultiError{Errors: err}
}
func (m MultiError) Error() string {
errs := make([]string, len(m.Errors))
for i, err := range m.Errors {
errs[i] = err.Error()
}
return strings.Join(errs, "\n")
}

View File

@ -1,100 +0,0 @@
package cli_test
import (
"os"
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli"
)
func Example() {
app := cli.NewApp()
app.Name = "todo"
app.Usage = "task list on the command line"
app.Commands = []cli.Command{
{
Name: "add",
ShortName: "a",
Usage: "add a task to the list",
Action: func(c *cli.Context) {
println("added task: ", c.Args().First())
},
},
{
Name: "complete",
ShortName: "c",
Usage: "complete a task on the list",
Action: func(c *cli.Context) {
println("completed task: ", c.Args().First())
},
},
}
app.Run(os.Args)
}
func ExampleSubcommand() {
app := cli.NewApp()
app.Name = "say"
app.Commands = []cli.Command{
{
Name: "hello",
ShortName: "hi",
Usage: "use it to see a description",
Description: "This is how we describe hello the function",
Subcommands: []cli.Command{
{
Name: "english",
ShortName: "en",
Usage: "sends a greeting in english",
Description: "greets someone in english",
Flags: []cli.Flag{
cli.StringFlag{
Name: "name",
Value: "Bob",
Usage: "Name of the person to greet",
},
},
Action: func(c *cli.Context) {
println("Hello, ", c.String("name"))
},
}, {
Name: "spanish",
ShortName: "sp",
Usage: "sends a greeting in spanish",
Flags: []cli.Flag{
cli.StringFlag{
Name: "surname",
Value: "Jones",
Usage: "Surname of the person to greet",
},
},
Action: func(c *cli.Context) {
println("Hola, ", c.String("surname"))
},
}, {
Name: "french",
ShortName: "fr",
Usage: "sends a greeting in french",
Flags: []cli.Flag{
cli.StringFlag{
Name: "nickname",
Value: "Stevie",
Usage: "Nickname of the person to greet",
},
},
Action: func(c *cli.Context) {
println("Bonjour, ", c.String("nickname"))
},
},
},
}, {
Name: "bye",
Usage: "says goodbye",
Action: func(c *cli.Context) {
println("bye")
},
},
}
app.Run(os.Args)
}

View File

@ -10,17 +10,24 @@ import (
type Command struct { type Command struct {
// The name of the command // The name of the command
Name string Name string
// short name of the command. Typically one character // short name of the command. Typically one character (deprecated, use `Aliases`)
ShortName string ShortName string
// A list of aliases for the command
Aliases []string
// A short description of the usage of this command // A short description of the usage of this command
Usage string Usage string
// A longer explanation of how the command works // A longer explanation of how the command works
Description string Description string
// A short description of the arguments of this command
ArgsUsage string
// The function to call when checking for bash command completions // The function to call when checking for bash command completions
BashComplete func(context *Context) BashComplete func(context *Context)
// An action to execute before any sub-subcommands are run, but after the context is ready // An action to execute before any sub-subcommands are run, but after the context is ready
// If a non-nil error is returned, no sub-subcommands are run // If a non-nil error is returned, no sub-subcommands are run
Before func(context *Context) error Before func(context *Context) error
// An action to execute after any subcommands are run, but after the subcommand has finished
// It is run even if Action() panics
After func(context *Context) error
// The function to call when this command is invoked // The function to call when this command is invoked
Action func(context *Context) Action func(context *Context)
// List of child commands // List of child commands
@ -31,16 +38,28 @@ type Command struct {
SkipFlagParsing bool SkipFlagParsing bool
// Boolean to hide built-in help command // Boolean to hide built-in help command
HideHelp bool HideHelp bool
// Full name of command for help, defaults to full command name, including parent commands.
HelpName string
commandNamePath []string
}
// Returns the full name of the command.
// For subcommands this ensures that parent commands are part of the command path
func (c Command) FullName() string {
if c.commandNamePath == nil {
return c.Name
}
return strings.Join(c.commandNamePath, " ")
} }
// 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
func (c Command) Run(ctx *Context) error { func (c Command) Run(ctx *Context) error {
if len(c.Subcommands) > 0 || c.Before != nil || c.After != nil {
if len(c.Subcommands) > 0 || c.Before != nil {
return c.startApp(ctx) return c.startApp(ctx)
} }
if !c.HideHelp { if !c.HideHelp && (HelpFlag != BoolFlag{}) {
// append help to flags // append help to flags
c.Flags = append( c.Flags = append(
c.Flags, c.Flags,
@ -55,40 +74,57 @@ func (c Command) Run(ctx *Context) error {
set := flagSet(c.Name, c.Flags) set := flagSet(c.Name, c.Flags)
set.SetOutput(ioutil.Discard) set.SetOutput(ioutil.Discard)
var err error
if !c.SkipFlagParsing {
firstFlagIndex := -1 firstFlagIndex := -1
terminatorIndex := -1
for index, arg := range ctx.Args() { for index, arg := range ctx.Args() {
if strings.HasPrefix(arg, "-") { if arg == "--" {
firstFlagIndex = index terminatorIndex = index
break break
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
firstFlagIndex = index
} }
} }
var err error if firstFlagIndex > -1 {
if firstFlagIndex > -1 && !c.SkipFlagParsing {
args := ctx.Args() args := ctx.Args()
regularArgs := args[1:firstFlagIndex] regularArgs := make([]string, len(args[1:firstFlagIndex]))
flagArgs := args[firstFlagIndex:] copy(regularArgs, args[1:firstFlagIndex])
var flagArgs []string
if terminatorIndex > -1 {
flagArgs = args[firstFlagIndex:terminatorIndex]
regularArgs = append(regularArgs, args[terminatorIndex:]...)
} else {
flagArgs = args[firstFlagIndex:]
}
err = set.Parse(append(flagArgs, regularArgs...)) err = set.Parse(append(flagArgs, regularArgs...))
} else { } else {
err = set.Parse(ctx.Args().Tail()) err = set.Parse(ctx.Args().Tail())
} }
} else {
if c.SkipFlagParsing {
err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
}
}
if err != nil { if err != nil {
fmt.Printf("Incorrect Usage.\n\n") fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.")
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name) ShowCommandHelp(ctx, c.Name)
fmt.Println("")
return err return err
} }
nerr := normalizeFlags(c.Flags, set) nerr := normalizeFlags(c.Flags, set)
if nerr != nil { if nerr != nil {
fmt.Println(nerr) fmt.Fprintln(ctx.App.Writer, nerr)
fmt.Println("") fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name) ShowCommandHelp(ctx, c.Name)
fmt.Println("")
return nerr return nerr
} }
context := NewContext(ctx.App, set, ctx.globalSet) context := NewContext(ctx.App, set, ctx)
if checkCommandCompletions(context, c.Name) { if checkCommandCompletions(context, c.Name) {
return nil return nil
@ -102,9 +138,24 @@ func (c Command) Run(ctx *Context) error {
return nil return nil
} }
func (c Command) Names() []string {
names := []string{c.Name}
if c.ShortName != "" {
names = append(names, c.ShortName)
}
return append(names, c.Aliases...)
}
// Returns true if Command.Name or Command.ShortName matches given name // Returns true if Command.Name or Command.ShortName matches given name
func (c Command) HasName(name string) bool { func (c Command) HasName(name string) bool {
return c.Name == name || c.ShortName == name for _, n := range c.Names() {
if n == name {
return true
}
}
return false
} }
func (c Command) startApp(ctx *Context) error { func (c Command) startApp(ctx *Context) error {
@ -112,6 +163,12 @@ func (c Command) startApp(ctx *Context) error {
// set the name and usage // set the name and usage
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
if c.HelpName == "" {
app.HelpName = c.HelpName
} else {
app.HelpName = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
}
if c.Description != "" { if c.Description != "" {
app.Usage = c.Description app.Usage = c.Description
} else { } else {
@ -126,6 +183,13 @@ func (c Command) startApp(ctx *Context) error {
app.Flags = c.Flags app.Flags = c.Flags
app.HideHelp = c.HideHelp app.HideHelp = c.HideHelp
app.Version = ctx.App.Version
app.HideVersion = ctx.App.HideVersion
app.Compiled = ctx.App.Compiled
app.Author = ctx.App.Author
app.Email = ctx.App.Email
app.Writer = ctx.App.Writer
// bash completion // bash completion
app.EnableBashCompletion = ctx.App.EnableBashCompletion app.EnableBashCompletion = ctx.App.EnableBashCompletion
if c.BashComplete != nil { if c.BashComplete != nil {
@ -134,11 +198,19 @@ func (c Command) startApp(ctx *Context) error {
// set the actions // set the actions
app.Before = c.Before app.Before = c.Before
app.After = c.After
if c.Action != nil { if c.Action != nil {
app.Action = c.Action app.Action = c.Action
} else { } else {
app.Action = helpSubcommand.Action app.Action = helpSubcommand.Action
} }
var newCmds []Command
for _, cc := range app.Commands {
cc.commandNamePath = []string{c.Name, cc.Name}
newCmds = append(newCmds, cc)
}
app.Commands = newCmds
return app.RunAsSubcommand(ctx) return app.RunAsSubcommand(ctx)
} }

View File

@ -1,49 +0,0 @@
package cli_test
import (
"flag"
"testing"
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli"
)
func TestCommandDoNotIgnoreFlags(t *testing.T) {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
test := []string{"blah", "blah", "-break"}
set.Parse(test)
c := cli.NewContext(app, set, set)
command := cli.Command{
Name: "test-cmd",
ShortName: "tc",
Usage: "this is for testing",
Description: "testing",
Action: func(_ *cli.Context) {},
}
err := command.Run(c)
expect(t, err.Error(), "flag provided but not defined: -break")
}
func TestCommandIgnoreFlags(t *testing.T) {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
test := []string{"blah", "blah"}
set.Parse(test)
c := cli.NewContext(app, set, set)
command := cli.Command{
Name: "test-cmd",
ShortName: "tc",
Usage: "this is for testing",
Description: "testing",
Action: func(_ *cli.Context) {},
SkipFlagParsing: true,
}
err := command.Run(c)
expect(t, err, nil)
}

View File

@ -16,13 +16,14 @@ type Context struct {
App *App App *App
Command Command Command Command
flagSet *flag.FlagSet flagSet *flag.FlagSet
globalSet *flag.FlagSet
setFlags map[string]bool setFlags map[string]bool
globalSetFlags map[string]bool
parentContext *Context
} }
// 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.
func NewContext(app *App, set *flag.FlagSet, globalSet *flag.FlagSet) *Context { func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
return &Context{App: app, flagSet: set, globalSet: globalSet} return &Context{App: app, flagSet: set, parentContext: parentCtx}
} }
// Looks up the value of a local int flag, returns 0 if no int flag exists // Looks up the value of a local int flag, returns 0 if no int flag exists
@ -72,40 +73,66 @@ func (c *Context) Generic(name string) interface{} {
// Looks up the value of a global int flag, returns 0 if no int flag exists // Looks up the value of a global int flag, returns 0 if no int flag exists
func (c *Context) GlobalInt(name string) int { func (c *Context) GlobalInt(name string) int {
return lookupInt(name, c.globalSet) if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupInt(name, fs)
}
return 0
} }
// Looks up the value of a global time.Duration flag, returns 0 if no time.Duration flag exists // 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 { func (c *Context) GlobalDuration(name string) time.Duration {
return lookupDuration(name, c.globalSet) if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupDuration(name, fs)
}
return 0
} }
// 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) if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupBool(name, fs)
}
return false
} }
// Looks up the value of a global string flag, returns "" if no string flag exists // Looks up the value of a global string flag, returns "" if no string flag exists
func (c *Context) GlobalString(name string) string { func (c *Context) GlobalString(name string) string {
return lookupString(name, c.globalSet) if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupString(name, fs)
}
return ""
} }
// Looks up the value of a global string slice flag, returns nil if no string slice flag exists // Looks up the value of a global string slice flag, returns nil if no string slice flag exists
func (c *Context) GlobalStringSlice(name string) []string { func (c *Context) GlobalStringSlice(name string) []string {
return lookupStringSlice(name, c.globalSet) if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupStringSlice(name, fs)
}
return nil
} }
// Looks up the value of a global int slice flag, returns nil if no int slice flag exists // Looks up the value of a global int slice flag, returns nil if no int slice flag exists
func (c *Context) GlobalIntSlice(name string) []int { func (c *Context) GlobalIntSlice(name string) []int {
return lookupIntSlice(name, c.globalSet) if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupIntSlice(name, fs)
}
return nil
} }
// Looks up the value of a global generic flag, returns nil if no generic flag exists // Looks up the value of a global generic flag, returns nil if no generic flag exists
func (c *Context) GlobalGeneric(name string) interface{} { func (c *Context) GlobalGeneric(name string) interface{} {
return lookupGeneric(name, c.globalSet) if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupGeneric(name, fs)
}
return nil
} }
// Determines if the flag was actually set exists // Returns the number of flags set
func (c *Context) NumFlags() int {
return c.flagSet.NFlag()
}
// 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)
@ -116,6 +143,23 @@ 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)
ctx := c
if ctx.parentContext != nil {
ctx = ctx.parentContext
}
for ; ctx != nil && c.globalSetFlags[name] == false; ctx = ctx.parentContext {
ctx.flagSet.Visit(func(f *flag.Flag) {
c.globalSetFlags[f.Name] = true
})
}
}
return c.globalSetFlags[name]
}
// Returns a slice of flag names used in this context. // Returns a slice of flag names used in this context.
func (c *Context) FlagNames() (names []string) { func (c *Context) FlagNames() (names []string) {
for _, flag := range c.Command.Flags { for _, flag := range c.Command.Flags {
@ -128,6 +172,23 @@ func (c *Context) FlagNames() (names []string) {
return 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
}
// Returns the parent context, if any
func (c *Context) Parent() *Context {
return c.parentContext
}
type Args []string type Args []string
// Returns the command line arguments associated with the context. // Returns the command line arguments associated with the context.
@ -172,6 +233,18 @@ func (a Args) Swap(from, to int) error {
return nil return nil
} }
func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet {
if ctx.parentContext != nil {
ctx = ctx.parentContext
}
for ; ctx != nil; ctx = ctx.parentContext {
if f := ctx.flagSet.Lookup(name); f != nil {
return ctx.flagSet
}
}
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 {

View File

@ -1,77 +0,0 @@
package cli_test
import (
"flag"
"testing"
"time"
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli"
)
func TestNewContext(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Int("myflag", 12, "doc")
globalSet := flag.NewFlagSet("test", 0)
globalSet.Int("myflag", 42, "doc")
command := cli.Command{Name: "mycommand"}
c := cli.NewContext(nil, set, globalSet)
c.Command = command
expect(t, c.Int("myflag"), 12)
expect(t, c.GlobalInt("myflag"), 42)
expect(t, c.Command.Name, "mycommand")
}
func TestContext_Int(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Int("myflag", 12, "doc")
c := cli.NewContext(nil, set, set)
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) {
set := flag.NewFlagSet("test", 0)
set.String("myflag", "hello world", "doc")
c := cli.NewContext(nil, set, set)
expect(t, c.String("myflag"), "hello world")
}
func TestContext_Bool(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("myflag", false, "doc")
c := cli.NewContext(nil, set, set)
expect(t, c.Bool("myflag"), false)
}
func TestContext_BoolT(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("myflag", true, "doc")
c := cli.NewContext(nil, set, set)
expect(t, c.BoolT("myflag"), true)
}
func TestContext_Args(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("myflag", false, "doc")
c := cli.NewContext(nil, set, set)
set.Parse([]string{"--myflag", "bat", "baz"})
expect(t, len(c.Args()), 2)
expect(t, c.Bool("myflag"), true)
}
func TestContext_IsSet(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("myflag", false, "doc")
set.String("otherflag", "hello world", "doc")
c := cli.NewContext(nil, set, set)
set.Parse([]string{"--myflag", "bat", "baz"})
expect(t, c.IsSet("myflag"), true)
expect(t, c.IsSet("otherflag"), false)
expect(t, c.IsSet("bogusflag"), false)
}

View File

@ -21,6 +21,8 @@ var VersionFlag = BoolFlag{
} }
// This flag prints the help for all commands and subcommands // This flag prints the help for all commands and subcommands
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
// unless HideHelp is set to true)
var HelpFlag = BoolFlag{ var HelpFlag = BoolFlag{
Name: "help, h", Name: "help, h",
Usage: "show help", Usage: "show help",
@ -67,15 +69,24 @@ type GenericFlag struct {
EnvVar string EnvVar string
} }
// String returns the string representation of the generic flag to display the
// help text to the user (uses the String() method of the generic flag to show
// the value)
func (f GenericFlag) String() string { func (f GenericFlag) String() string {
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)) return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s \"%v\"\t%v", prefixFor(f.Name), f.Name, f.Value, f.Usage))
} }
// Apply takes the flagset and calls Set on the generic flag with the value
// provided by the user for parsing by the flag
func (f GenericFlag) Apply(set *flag.FlagSet) { func (f GenericFlag) Apply(set *flag.FlagSet) {
val := f.Value val := f.Value
if f.EnvVar != "" { if f.EnvVar != "" {
if envVal := os.Getenv(f.EnvVar); envVal != "" { for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
val.Set(envVal) val.Set(envVal)
break
}
} }
} }
@ -88,21 +99,27 @@ func (f GenericFlag) getName() string {
return f.Name return f.Name
} }
// StringSlice is an opaque type for []string to satisfy flag.Value
type StringSlice []string type StringSlice []string
// Set appends the string value to the list of values
func (f *StringSlice) Set(value string) error { func (f *StringSlice) Set(value string) error {
*f = append(*f, value) *f = append(*f, value)
return nil return nil
} }
// String returns a readable representation of this value (for usage defaults)
func (f *StringSlice) String() string { func (f *StringSlice) String() string {
return fmt.Sprintf("%s", *f) return fmt.Sprintf("%s", *f)
} }
// Value returns the slice of strings set by this flag
func (f *StringSlice) Value() []string { func (f *StringSlice) Value() []string {
return *f return *f
} }
// StringSlice is a string flag that can be specified multiple times on the
// command-line
type StringSliceFlag struct { type StringSliceFlag struct {
Name string Name string
Value *StringSlice Value *StringSlice
@ -110,24 +127,34 @@ type StringSliceFlag struct {
EnvVar string EnvVar string
} }
// String returns the usage
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 withEnvHint(f.EnvVar, 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))
} }
// Apply populates the flag given the flag set and environment
func (f StringSliceFlag) Apply(set *flag.FlagSet) { func (f StringSliceFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" { if f.EnvVar != "" {
if envVal := os.Getenv(f.EnvVar); envVal != "" { for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
newVal := &StringSlice{} newVal := &StringSlice{}
for _, s := range strings.Split(envVal, ",") { for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
newVal.Set(s) newVal.Set(s)
} }
f.Value = newVal f.Value = newVal
break
}
} }
} }
eachName(f.Name, func(name string) { eachName(f.Name, func(name string) {
if f.Value == nil {
f.Value = &StringSlice{}
}
set.Var(f.Value, name, f.Usage) set.Var(f.Value, name, f.Usage)
}) })
} }
@ -136,10 +163,11 @@ func (f StringSliceFlag) getName() string {
return f.Name return f.Name
} }
// StringSlice is an opaque type for []int to satisfy flag.Value
type IntSlice []int type IntSlice []int
// Set parses the value into an integer and appends it to the list of values
func (f *IntSlice) Set(value string) error { func (f *IntSlice) Set(value string) error {
tmp, err := strconv.Atoi(value) tmp, err := strconv.Atoi(value)
if err != nil { if err != nil {
return err return err
@ -149,14 +177,18 @@ func (f *IntSlice) Set(value string) error {
return nil return nil
} }
// String returns a readable representation of this value (for usage defaults)
func (f *IntSlice) String() string { func (f *IntSlice) String() string {
return fmt.Sprintf("%d", *f) return fmt.Sprintf("%d", *f)
} }
// Value returns the slice of ints set by this flag
func (f *IntSlice) Value() []int { func (f *IntSlice) Value() []int {
return *f return *f
} }
// IntSliceFlag is an int flag that can be specified multiple times on the
// command-line
type IntSliceFlag struct { type IntSliceFlag struct {
Name string Name string
Value *IntSlice Value *IntSlice
@ -164,27 +196,37 @@ type IntSliceFlag struct {
EnvVar string EnvVar string
} }
// String returns the usage
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 withEnvHint(f.EnvVar, 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))
} }
// Apply populates the flag given the flag set and environment
func (f IntSliceFlag) Apply(set *flag.FlagSet) { func (f IntSliceFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" { if f.EnvVar != "" {
if envVal := os.Getenv(f.EnvVar); envVal != "" { for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
newVal := &IntSlice{} newVal := &IntSlice{}
for _, s := range strings.Split(envVal, ",") { for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
err := newVal.Set(s) err := newVal.Set(s)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())
} }
} }
f.Value = newVal f.Value = newVal
break
}
} }
} }
eachName(f.Name, func(name string) { eachName(f.Name, func(name string) {
if f.Value == nil {
f.Value = &IntSlice{}
}
set.Var(f.Value, name, f.Usage) set.Var(f.Value, name, f.Usage)
}) })
} }
@ -193,28 +235,40 @@ func (f IntSliceFlag) getName() string {
return f.Name return f.Name
} }
// BoolFlag is a switch that defaults to false
type BoolFlag struct { type BoolFlag struct {
Name string Name string
Usage string Usage string
EnvVar string EnvVar string
Destination *bool
} }
// String returns a readable representation of this value (for usage defaults)
func (f BoolFlag) String() string { func (f BoolFlag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
} }
// Apply populates the flag given the flag set and environment
func (f BoolFlag) Apply(set *flag.FlagSet) { func (f BoolFlag) Apply(set *flag.FlagSet) {
val := false val := false
if f.EnvVar != "" { if f.EnvVar != "" {
if envVal := os.Getenv(f.EnvVar); envVal != "" { for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
envValBool, err := strconv.ParseBool(envVal) envValBool, err := strconv.ParseBool(envVal)
if err == nil { if err == nil {
val = envValBool val = envValBool
} }
break
}
} }
} }
eachName(f.Name, func(name string) { eachName(f.Name, func(name string) {
if f.Destination != nil {
set.BoolVar(f.Destination, name, val, f.Usage)
return
}
set.Bool(name, val, f.Usage) set.Bool(name, val, f.Usage)
}) })
} }
@ -223,28 +277,41 @@ func (f BoolFlag) getName() string {
return f.Name return f.Name
} }
// BoolTFlag this represents a boolean flag that is true by default, but can
// still be set to false by --some-flag=false
type BoolTFlag struct { type BoolTFlag struct {
Name string Name string
Usage string Usage string
EnvVar string EnvVar string
Destination *bool
} }
// String returns a readable representation of this value (for usage defaults)
func (f BoolTFlag) String() string { func (f BoolTFlag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
} }
// Apply populates the flag given the flag set and environment
func (f BoolTFlag) Apply(set *flag.FlagSet) { func (f BoolTFlag) Apply(set *flag.FlagSet) {
val := true val := true
if f.EnvVar != "" { if f.EnvVar != "" {
if envVal := os.Getenv(f.EnvVar); envVal != "" { for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
envValBool, err := strconv.ParseBool(envVal) envValBool, err := strconv.ParseBool(envVal)
if err == nil { if err == nil {
val = envValBool val = envValBool
break
}
} }
} }
} }
eachName(f.Name, func(name string) { eachName(f.Name, func(name string) {
if f.Destination != nil {
set.BoolVar(f.Destination, name, val, f.Usage)
return
}
set.Bool(name, val, f.Usage) set.Bool(name, val, f.Usage)
}) })
} }
@ -253,19 +320,22 @@ func (f BoolTFlag) getName() string {
return f.Name return f.Name
} }
// StringFlag represents a flag that takes as string value
type StringFlag struct { type StringFlag struct {
Name string Name string
Value string Value string
Usage string Usage string
EnvVar string EnvVar string
Destination *string
} }
// String returns the usage
func (f StringFlag) String() string { func (f StringFlag) String() string {
var fmtString string var fmtString string
fmtString = "%s %v\t%v" fmtString = "%s %v\t%v"
if len(f.Value) > 0 { if len(f.Value) > 0 {
fmtString = "%s '%v'\t%v" fmtString = "%s \"%v\"\t%v"
} else { } else {
fmtString = "%s %v\t%v" fmtString = "%s %v\t%v"
} }
@ -273,14 +343,23 @@ func (f StringFlag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage)) return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage))
} }
// Apply populates the flag given the flag set and environment
func (f StringFlag) Apply(set *flag.FlagSet) { func (f StringFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" { if f.EnvVar != "" {
if envVal := os.Getenv(f.EnvVar); envVal != "" { for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
f.Value = envVal f.Value = envVal
break
}
} }
} }
eachName(f.Name, func(name string) { eachName(f.Name, func(name string) {
if f.Destination != nil {
set.StringVar(f.Destination, name, f.Value, f.Usage)
return
}
set.String(name, f.Value, f.Usage) set.String(name, f.Value, f.Usage)
}) })
} }
@ -289,28 +368,41 @@ func (f StringFlag) getName() string {
return f.Name return f.Name
} }
// IntFlag is a flag that takes an integer
// Errors if the value provided cannot be parsed
type IntFlag struct { type IntFlag struct {
Name string Name string
Value int Value int
Usage string Usage string
EnvVar string EnvVar string
Destination *int
} }
// String returns the usage
func (f IntFlag) String() string { func (f IntFlag) String() string {
return withEnvHint(f.EnvVar, 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))
} }
// Apply populates the flag given the flag set and environment
func (f IntFlag) Apply(set *flag.FlagSet) { func (f IntFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" { if f.EnvVar != "" {
if envVal := os.Getenv(f.EnvVar); envVal != "" { for _, envVar := range strings.Split(f.EnvVar, ",") {
envValInt, err := strconv.ParseUint(envVal, 10, 64) envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err == nil { if err == nil {
f.Value = int(envValInt) f.Value = int(envValInt)
break
}
} }
} }
} }
eachName(f.Name, func(name string) { eachName(f.Name, func(name string) {
if f.Destination != nil {
set.IntVar(f.Destination, name, f.Value, f.Usage)
return
}
set.Int(name, f.Value, f.Usage) set.Int(name, f.Value, f.Usage)
}) })
} }
@ -319,28 +411,41 @@ func (f IntFlag) getName() string {
return f.Name return f.Name
} }
// DurationFlag is a flag that takes a duration specified in Go's duration
// format: https://golang.org/pkg/time/#ParseDuration
type DurationFlag struct { type DurationFlag struct {
Name string Name string
Value time.Duration Value time.Duration
Usage string Usage string
EnvVar string EnvVar string
Destination *time.Duration
} }
// String returns a readable representation of this value (for usage defaults)
func (f DurationFlag) String() string { func (f DurationFlag) String() string {
return withEnvHint(f.EnvVar, 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))
} }
// Apply populates the flag given the flag set and environment
func (f DurationFlag) Apply(set *flag.FlagSet) { func (f DurationFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" { if f.EnvVar != "" {
if envVal := os.Getenv(f.EnvVar); envVal != "" { for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
envValDuration, err := time.ParseDuration(envVal) envValDuration, err := time.ParseDuration(envVal)
if err == nil { if err == nil {
f.Value = envValDuration f.Value = envValDuration
break
}
} }
} }
} }
eachName(f.Name, func(name string) { eachName(f.Name, func(name string) {
if f.Destination != nil {
set.DurationVar(f.Destination, name, f.Value, f.Usage)
return
}
set.Duration(name, f.Value, f.Usage) set.Duration(name, f.Value, f.Usage)
}) })
} }
@ -349,28 +454,40 @@ func (f DurationFlag) getName() string {
return f.Name return f.Name
} }
// Float64Flag is a flag that takes an float value
// Errors if the value provided cannot be parsed
type Float64Flag struct { type Float64Flag struct {
Name string Name string
Value float64 Value float64
Usage string Usage string
EnvVar string EnvVar string
Destination *float64
} }
// String returns the usage
func (f Float64Flag) String() string { func (f Float64Flag) String() string {
return withEnvHint(f.EnvVar, 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))
} }
// Apply populates the flag given the flag set and environment
func (f Float64Flag) Apply(set *flag.FlagSet) { func (f Float64Flag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" { if f.EnvVar != "" {
if envVal := os.Getenv(f.EnvVar); envVal != "" { for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
envValFloat, err := strconv.ParseFloat(envVal, 10) envValFloat, err := strconv.ParseFloat(envVal, 10)
if err == nil { if err == nil {
f.Value = float64(envValFloat) f.Value = float64(envValFloat)
} }
} }
} }
}
eachName(f.Name, func(name string) { eachName(f.Name, func(name string) {
if f.Destination != nil {
set.Float64Var(f.Destination, name, f.Value, f.Usage)
return
}
set.Float64(name, f.Value, f.Usage) set.Float64(name, f.Value, f.Usage)
}) })
} }
@ -404,7 +521,7 @@ func prefixedNames(fullName string) (prefixed string) {
func withEnvHint(envVar, str string) string { func withEnvHint(envVar, str string) string {
envText := "" envText := ""
if envVar != "" { if envVar != "" {
envText = fmt.Sprintf(" [$%s]", envVar) envText = fmt.Sprintf(" [$%s]", strings.Join(strings.Split(envVar, ","), ", $"))
} }
return str + envText return str + envText
} }

View File

@ -1,587 +0,0 @@
package cli_test
import (
"fmt"
"os"
"reflect"
"strings"
"testing"
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli"
)
var boolFlagTests = []struct {
name string
expected string
}{
{"help", "--help\t"},
{"h", "-h\t"},
}
func TestBoolFlagHelpOutput(t *testing.T) {
for _, test := range boolFlagTests {
flag := cli.BoolFlag{Name: test.name}
output := flag.String()
if output != test.expected {
t.Errorf("%s does not match %s", output, test.expected)
}
}
}
var stringFlagTests = []struct {
name string
value string
expected string
}{
{"help", "", "--help \t"},
{"h", "", "-h \t"},
{"h", "", "-h \t"},
{"test", "Something", "--test 'Something'\t"},
}
func TestStringFlagHelpOutput(t *testing.T) {
for _, test := range stringFlagTests {
flag := cli.StringFlag{Name: test.name, Value: test.value}
output := flag.String()
if output != test.expected {
t.Errorf("%s does not match %s", output, test.expected)
}
}
}
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 {
name string
expected string
}{
{"help", "--help '0'\t"},
{"h", "-h '0'\t"},
}
func TestIntFlagHelpOutput(t *testing.T) {
for _, test := range intFlagTests {
flag := cli.IntFlag{Name: test.name}
output := flag.String()
if output != test.expected {
t.Errorf("%s does not match %s", output, test.expected)
}
}
}
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 {
name string
expected string
}{
{"help", "--help '0'\t"},
{"h", "-h '0'\t"},
}
func TestFloat64FlagHelpOutput(t *testing.T) {
for _, test := range float64FlagTests {
flag := cli.Float64Flag{Name: test.name}
output := flag.String()
if output != test.expected {
t.Errorf("%s does not match %s", output, test.expected)
}
}
}
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) {
(&cli.App{
Flags: []cli.Flag{
cli.StringFlag{Name: "serve, s"},
},
Action: func(ctx *cli.Context) {
if ctx.String("serve") != "10" {
t.Errorf("main name not set")
}
if ctx.String("s") != "10" {
t.Errorf("short name not set")
}
},
}).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) {
(&cli.App{
Flags: []cli.Flag{
cli.StringSliceFlag{Name: "serve, s", Value: &cli.StringSlice{}},
},
Action: func(ctx *cli.Context) {
if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"10", "20"}) {
t.Errorf("main name not set")
}
if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"10", "20"}) {
t.Errorf("short name not set")
}
},
}).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) {
a := cli.App{
Flags: []cli.Flag{
cli.IntFlag{Name: "serve, s"},
},
Action: func(ctx *cli.Context) {
if ctx.Int("serve") != 10 {
t.Errorf("main name not set")
}
if ctx.Int("s") != 10 {
t.Errorf("short name not set")
}
},
}
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) {
a := cli.App{
Flags: []cli.Flag{
cli.BoolFlag{Name: "serve, s"},
},
Action: func(ctx *cli.Context) {
if ctx.Bool("serve") != true {
t.Errorf("main name not set")
}
if ctx.Bool("s") != true {
t.Errorf("short name not set")
}
},
}
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
func (p *Parser) Set(value string) error {
parts := strings.Split(value, ",")
if len(parts) != 2 {
return fmt.Errorf("invalid format")
}
(*p)[0] = parts[0]
(*p)[1] = parts[1]
return nil
}
func (p *Parser) String() string {
return fmt.Sprintf("%s,%s", p[0], p[1])
}
func TestParseGeneric(t *testing.T) {
a := cli.App{
Flags: []cli.Flag{
cli.GenericFlag{Name: "serve, s", Value: &Parser{}},
},
Action: func(ctx *cli.Context) {
if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) {
t.Errorf("main name not set")
}
if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"10", "20"}) {
t.Errorf("short name not set")
}
},
}
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"})
}

View File

@ -2,7 +2,8 @@ package cli
import ( import (
"fmt" "fmt"
"os" "io"
"strings"
"text/tabwriter" "text/tabwriter"
"text/template" "text/template"
) )
@ -14,31 +15,33 @@ var AppHelpTemplate = `NAME:
{{.Name}} - {{.Usage}} {{.Name}} - {{.Usage}}
USAGE: USAGE:
{{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...] {{.HelpName}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
{{if .Version}}
VERSION: VERSION:
{{.Version}}{{if or .Author .Email}} {{.Version}}
{{end}}{{if len .Authors}}
AUTHOR:{{if .Author}} AUTHOR(S):
{{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}} {{range .Authors}}{{ . }}{{end}}
{{.Email}}{{end}}{{end}} {{end}}{{if .Commands}}
COMMANDS: COMMANDS:
{{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
{{end}}{{if .Flags}} {{end}}{{end}}{{if .Flags}}
GLOBAL OPTIONS: GLOBAL OPTIONS:
{{range .Flags}}{{.}} {{range .Flags}}{{.}}
{{end}}{{end}} {{end}}{{end}}{{if .Copyright }}
COPYRIGHT:
{{.Copyright}}
{{end}}
` `
// The text template for the command help topic. // The text template for the command help topic.
// cli.go uses text/template to render templates. You can // cli.go uses text/template to render templates. You can
// render custom help text by setting this variable. // render custom help text by setting this variable.
var CommandHelpTemplate = `NAME: var CommandHelpTemplate = `NAME:
{{.Name}} - {{.Usage}} {{.HelpName}} - {{.Usage}}
USAGE: USAGE:
command {{.Name}}{{if .Flags}} [command options]{{end}} [arguments...]{{if .Description}} {{.HelpName}}{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Description}}
DESCRIPTION: DESCRIPTION:
{{.Description}}{{end}}{{if .Flags}} {{.Description}}{{end}}{{if .Flags}}
@ -52,13 +55,13 @@ OPTIONS:
// cli.go uses text/template to render templates. You can // cli.go uses text/template to render templates. You can
// render custom help text by setting this variable. // render custom help text by setting this variable.
var SubcommandHelpTemplate = `NAME: var SubcommandHelpTemplate = `NAME:
{{.Name}} - {{.Usage}} {{.HelpName}} - {{.Usage}}
USAGE: USAGE:
{{.Name}} command{{if .Flags}} [command options]{{end}} [arguments...] {{.HelpName}} command{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
COMMANDS: COMMANDS:
{{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
{{end}}{{if .Flags}} {{end}}{{if .Flags}}
OPTIONS: OPTIONS:
{{range .Flags}}{{.}} {{range .Flags}}{{.}}
@ -67,8 +70,9 @@ OPTIONS:
var helpCommand = Command{ var helpCommand = Command{
Name: "help", Name: "help",
ShortName: "h", Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command", Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]",
Action: func(c *Context) { Action: func(c *Context) {
args := c.Args() args := c.Args()
if args.Present() { if args.Present() {
@ -81,8 +85,9 @@ var helpCommand = Command{
var helpSubcommand = Command{ var helpSubcommand = Command{
Name: "help", Name: "help",
ShortName: "h", Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command", Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]",
Action: func(c *Context) { Action: func(c *Context) {
args := c.Args() args := c.Args()
if args.Present() { if args.Present() {
@ -93,45 +98,52 @@ var helpSubcommand = Command{
}, },
} }
// Prints help for the App // Prints help for the App or Command
var HelpPrinter = printHelp type helpPrinter func(w io.Writer, templ string, data interface{})
var HelpPrinter helpPrinter = printHelp
// Prints version for the App // Prints version for the App
var VersionPrinter = printVersion var VersionPrinter = printVersion
func ShowAppHelp(c *Context) { func ShowAppHelp(c *Context) {
HelpPrinter(AppHelpTemplate, c.App) HelpPrinter(c.App.Writer, AppHelpTemplate, c.App)
} }
// Prints the list of subcommands as the default app completion method // Prints the list of subcommands as the default app completion method
func DefaultAppComplete(c *Context) { func DefaultAppComplete(c *Context) {
for _, command := range c.App.Commands { for _, command := range c.App.Commands {
fmt.Println(command.Name) for _, name := range command.Names() {
if command.ShortName != "" { fmt.Fprintln(c.App.Writer, name)
fmt.Println(command.ShortName)
} }
} }
} }
// Prints help for the given command // Prints help for the given command
func ShowCommandHelp(c *Context, command string) { func ShowCommandHelp(ctx *Context, command string) {
for _, c := range c.App.Commands { // show the subcommand help for a command with subcommands
if command == "" {
HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App)
return
}
for _, c := range ctx.App.Commands {
if c.HasName(command) { if c.HasName(command) {
HelpPrinter(CommandHelpTemplate, c) HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c)
return return
} }
} }
if c.App.CommandNotFound != nil { if ctx.App.CommandNotFound != nil {
c.App.CommandNotFound(c, command) ctx.App.CommandNotFound(ctx, command)
} else { } else {
fmt.Printf("No help topic for '%v'\n", command) fmt.Fprintf(ctx.App.Writer, "No help topic for '%v'\n", command)
} }
} }
// Prints help for the given subcommand // Prints help for the given subcommand
func ShowSubcommandHelp(c *Context) { func ShowSubcommandHelp(c *Context) {
HelpPrinter(SubcommandHelpTemplate, c.App) ShowCommandHelp(c, c.Command.Name)
} }
// Prints the version number of the App // Prints the version number of the App
@ -140,7 +152,7 @@ func ShowVersion(c *Context) {
} }
func printVersion(c *Context) { func printVersion(c *Context) {
fmt.Printf("%v version %v\n", c.App.Name, c.App.Version) fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
} }
// Prints the lists of commands within a given context // Prints the lists of commands within a given context
@ -159,9 +171,13 @@ func ShowCommandCompletions(ctx *Context, command string) {
} }
} }
func printHelp(templ string, data interface{}) { func printHelp(out io.Writer, templ string, data interface{}) {
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0) funcMap := template.FuncMap{
t := template.Must(template.New("help").Parse(templ)) "join": strings.Join,
}
w := tabwriter.NewWriter(out, 0, 8, 1, '\t', 0)
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
err := t.Execute(w, data) err := t.Execute(w, data)
if err != nil { if err != nil {
panic(err) panic(err)
@ -170,21 +186,27 @@ func printHelp(templ string, data interface{}) {
} }
func checkVersion(c *Context) bool { func checkVersion(c *Context) bool {
if c.GlobalBool("version") { found := false
ShowVersion(c) if VersionFlag.Name != "" {
return true eachName(VersionFlag.Name, func(name string) {
if c.GlobalBool(name) || c.Bool(name) {
found = true
} }
})
return false }
return found
} }
func checkHelp(c *Context) bool { func checkHelp(c *Context) bool {
if c.GlobalBool("h") || c.GlobalBool("help") { found := false
ShowAppHelp(c) if HelpFlag.Name != "" {
return true eachName(HelpFlag.Name, func(name string) {
if c.GlobalBool(name) || c.Bool(name) {
found = true
} }
})
return false }
return found
} }
func checkCommandHelp(c *Context, name string) bool { func checkCommandHelp(c *Context, name string) bool {
@ -206,7 +228,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
} }

View File

@ -1,19 +0,0 @@
package cli_test
import (
"reflect"
"testing"
)
/* Test Helpers */
func expect(t *testing.T, a interface{}, b interface{}) {
if a != b {
t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
}
}
func refute(t *testing.T, a interface{}, b interface{}) {
if a == b {
t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
}
}