Conflicts:
	app.go
	app_test.go
This commit is contained in:
Dario Castañé 2015-01-10 00:35:11 +01:00
commit 4e3a83b43e
9 changed files with 512 additions and 125 deletions

View File

@ -172,6 +172,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.
@ -187,7 +189,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
@ -283,6 +296,3 @@ Feel free to put up a pull request to fix a bug or maybe add a feature. I will g
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 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. 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)

50
app.go
View File

@ -2,8 +2,11 @@ package cli
import ( import (
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"text/tabwriter"
"text/template"
"time" "time"
) )
@ -44,6 +47,8 @@ type App struct {
Author string Author string
// Author e-mail // Author e-mail
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.
@ -65,15 +70,36 @@ func NewApp() *App {
BashComplete: DefaultAppComplete, BashComplete: DefaultAppComplete,
Action: helpCommand.Action, Action: helpCommand.Action,
Compiled: compileTime(), Compiled: compileTime(),
Author: "Author",
Email: "unknown@email",
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) (err error) { func (a *App) Run(arguments []string) error {
if HelpPrinter == nil {
defer func() {
HelpPrinter = nil
}()
HelpPrinter = func(templ string, data interface{}) {
w := tabwriter.NewWriter(a.Writer, 0, 8, 1, '\t', 0)
t := template.Must(template.New("help").Parse(templ))
err := t.Execute(w, data)
if err != nil {
panic(err)
}
w.Flush()
}
}
// 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)
a.appendFlag(HelpFlag) if (HelpFlag != BoolFlag{}) {
a.appendFlag(HelpFlag)
}
} }
//append version/help flags //append version/help flags
@ -91,18 +117,18 @@ func (a *App) Run(arguments []string) (err error) {
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, set)
ShowAppHelp(context) ShowAppHelp(context)
fmt.Println("") fmt.Fprintln(a.Writer)
return nerr return nerr
} }
context := NewContext(a, set, set) context := NewContext(a, set, set)
if err != nil { if err != nil {
fmt.Printf("Incorrect Usage.\n\n") fmt.Fprintf(a.Writer, "Incorrect Usage.\n\n")
ShowAppHelp(context) ShowAppHelp(context)
fmt.Println("") fmt.Fprintln(a.Writer)
return err return err
} }
@ -152,7 +178,7 @@ func (a *App) Run(arguments []string) (err 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)
} }
} }
@ -163,7 +189,9 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
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)
a.appendFlag(HelpFlag) if (HelpFlag != BoolFlag{}) {
a.appendFlag(HelpFlag)
}
} }
} }
@ -180,18 +208,18 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
context := NewContext(a, set, ctx.globalSet) context := NewContext(a, set, ctx.globalSet)
if nerr != nil { if nerr != nil {
fmt.Println(nerr) fmt.Fprintln(a.Writer, nerr)
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("") fmt.Fprintln(a.Writer)
return nerr return nerr
} }
if err != nil { if err != nil {
fmt.Printf("Incorrect Usage.\n\n") fmt.Fprintf(a.Writer, "Incorrect Usage.\n\n")
ShowSubcommandHelp(context) ShowSubcommandHelp(context)
return err return err
} }

View File

@ -1,6 +1,7 @@
package cli_test package cli_test
import ( import (
"flag"
"fmt" "fmt"
"os" "os"
"testing" "testing"
@ -192,6 +193,50 @@ func TestApp_CommandWithArgBeforeFlags(t *testing.T) {
expect(t, firstArg, "my-arg") expect(t, firstArg, "my-arg")
} }
func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
var parsedOption string
var args []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")
args = c.Args()
},
}
app.Commands = []cli.Command{command}
app.Run([]string{"", "cmd", "my-arg", "--option", "my-option", "--", "--notARealFlag"})
expect(t, parsedOption, "my-option")
expect(t, args[0], "my-arg")
expect(t, args[1], "--")
expect(t, args[2], "--notARealFlag")
}
func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) {
var args []string
app := cli.NewApp()
command := cli.Command{
Name: "cmd",
Action: func(c *cli.Context) {
args = c.Args()
},
}
app.Commands = []cli.Command{command}
app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"})
expect(t, args[0], "my-arg")
expect(t, args[1], "--")
expect(t, args[2], "notAFlagAtAll")
}
func TestApp_Float64Flag(t *testing.T) { func TestApp_Float64Flag(t *testing.T) {
var meters float64 var meters float64
@ -265,6 +310,50 @@ func TestApp_ParseSliceFlags(t *testing.T) {
} }
} }
func TestApp_DefaultStdout(t *testing.T) {
app := cli.NewApp()
if app.Writer != os.Stdout {
t.Error("Default output writer not set.")
}
}
type mockWriter struct {
written []byte
}
func (fw *mockWriter) Write(p []byte) (n int, err error) {
if fw.written == nil {
fw.written = p
} else {
fw.written = append(fw.written, p...)
}
return len(p), nil
}
func (fw *mockWriter) GetWritten() (b []byte) {
return fw.written
}
func TestApp_SetStdout(t *testing.T) {
w := &mockWriter{}
app := cli.NewApp()
app.Name = "test"
app.Writer = w
err := app.Run([]string{"help"})
if err != nil {
t.Fatalf("Run error: %s", err)
}
if len(w.written) == 0 {
t.Error("App did not write output to desired writer.")
}
}
func TestApp_BeforeFunc(t *testing.T) { func TestApp_BeforeFunc(t *testing.T) {
beforeRun, subcommandRun := false, false beforeRun, subcommandRun := false, false
beforeError := fmt.Errorf("fail") beforeError := fmt.Errorf("fail")
@ -396,6 +485,22 @@ func TestApp_AfterFunc(t *testing.T) {
} }
} }
func TestAppNoHelpFlag(t *testing.T) {
oldFlag := cli.HelpFlag
defer func() {
cli.HelpFlag = oldFlag
}()
cli.HelpFlag = cli.BoolFlag{}
app := cli.NewApp()
err := app.Run([]string{"test", "-h"})
if err != flag.ErrHelp {
t.Errorf("expected error about missing help flag, but got: %s (%T)", err, err)
}
}
func TestAppHelpPrinter(t *testing.T) { func TestAppHelpPrinter(t *testing.T) {
oldPrinter := cli.HelpPrinter oldPrinter := cli.HelpPrinter
defer func() { defer func() {

View File

@ -43,7 +43,7 @@ func (c Command) Run(ctx *Context) error {
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,
@ -59,36 +59,48 @@ func (c Command) Run(ctx *Context) error {
set.SetOutput(ioutil.Discard) set.SetOutput(ioutil.Discard)
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 var err error
if firstFlagIndex > -1 && !c.SkipFlagParsing { 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())
} }
if err != nil { if err != nil {
fmt.Printf("Incorrect Usage.\n\n") fmt.Fprint(ctx.App.Writer, "Incorrect Usage.\n\n")
ShowCommandHelp(ctx, c.Name) ShowCommandHelp(ctx, c.Name)
fmt.Println("") fmt.Fprintln(ctx.App.Writer)
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("") fmt.Fprintln(ctx.App.Writer)
return nerr return nerr
} }
context := NewContext(ctx.App, set, ctx.globalSet) context := NewContext(ctx.App, set, ctx.globalSet)

View File

@ -13,11 +13,12 @@ import (
// can be used to retrieve context-specific Args and // can be used to retrieve context-specific Args and
// parsed command-line options. // parsed command-line options.
type Context struct { type Context struct {
App *App App *App
Command Command Command Command
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.
@ -105,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)
@ -116,6 +117,17 @@ 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. // 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 +140,18 @@ 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
}
type Args []string type Args []string
// Returns the command line arguments associated with the context. // Returns the command line arguments associated with the context.

View File

@ -69,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)
} }

132
flag.go
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, ",") {
val.Set(envVal) envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
val.Set(envVal)
break
}
} }
} }
@ -113,17 +124,22 @@ type StringSliceFlag struct {
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))
} }
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, ",") {
newVal := &StringSlice{} envVar = strings.TrimSpace(envVar)
for _, s := range strings.Split(envVal, ",") { if envVal := os.Getenv(envVar); envVal != "" {
newVal.Set(s) newVal := &StringSlice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
newVal.Set(s)
}
f.Value = newVal
break
} }
f.Value = newVal
} }
} }
@ -167,20 +183,25 @@ type IntSliceFlag struct {
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))
} }
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, ",") {
newVal := &IntSlice{} envVar = strings.TrimSpace(envVar)
for _, s := range strings.Split(envVal, ",") { if envVal := os.Getenv(envVar); envVal != "" {
err := newVal.Set(s) newVal := &IntSlice{}
if err != nil { for _, s := range strings.Split(envVal, ",") {
fmt.Fprintf(os.Stderr, err.Error()) s = strings.TrimSpace(s)
err := newVal.Set(s)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
}
} }
f.Value = newVal
break
} }
f.Value = newVal
} }
} }
@ -206,10 +227,14 @@ func (f BoolFlag) String() string {
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, ",") {
envValBool, err := strconv.ParseBool(envVal) envVar = strings.TrimSpace(envVar)
if err == nil { if envVal := os.Getenv(envVar); envVal != "" {
val = envValBool envValBool, err := strconv.ParseBool(envVal)
if err == nil {
val = envValBool
}
break
} }
} }
} }
@ -236,10 +261,14 @@ func (f BoolTFlag) String() string {
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, ",") {
envValBool, err := strconv.ParseBool(envVal) envVar = strings.TrimSpace(envVar)
if err == nil { if envVal := os.Getenv(envVar); envVal != "" {
val = envValBool envValBool, err := strconv.ParseBool(envVal)
if err == nil {
val = envValBool
break
}
} }
} }
} }
@ -265,7 +294,7 @@ func (f StringFlag) String() 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"
} }
@ -275,8 +304,12 @@ func (f StringFlag) String() string {
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, ",") {
f.Value = envVal envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
f.Value = envVal
break
}
} }
} }
@ -297,15 +330,19 @@ type IntFlag struct {
} }
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))
} }
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 err == nil { if envVal := os.Getenv(envVar); envVal != "" {
f.Value = int(envValInt) envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err == nil {
f.Value = int(envValInt)
break
}
} }
} }
} }
@ -327,15 +364,19 @@ type DurationFlag struct {
} }
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))
} }
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, ",") {
envValDuration, err := time.ParseDuration(envVal) envVar = strings.TrimSpace(envVar)
if err == nil { if envVal := os.Getenv(envVar); envVal != "" {
f.Value = envValDuration envValDuration, err := time.ParseDuration(envVal)
if err == nil {
f.Value = envValDuration
break
}
} }
} }
} }
@ -357,15 +398,18 @@ type Float64Flag struct {
} }
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))
} }
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, ",") {
envValFloat, err := strconv.ParseFloat(envVal, 10) envVar = strings.TrimSpace(envVar)
if err == nil { if envVal := os.Getenv(envVar); envVal != "" {
f.Value = float64(envValFloat) envValFloat, err := strconv.ParseFloat(envVal, 10)
if err == nil {
f.Value = float64(envValFloat)
}
} }
} }
} }
@ -404,7 +448,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

@ -38,7 +38,7 @@ var stringFlagTests = []struct {
{"help", "", "--help \t"}, {"help", "", "--help \t"},
{"h", "", "-h \t"}, {"h", "", "-h \t"},
{"h", "", "-h \t"}, {"h", "", "-h \t"},
{"test", "Something", "--test 'Something'\t"}, {"test", "Something", "--test \"Something\"\t"},
} }
func TestStringFlagHelpOutput(t *testing.T) { func TestStringFlagHelpOutput(t *testing.T) {
@ -54,7 +54,7 @@ func TestStringFlagHelpOutput(t *testing.T) {
} }
func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { func TestStringFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv()
os.Setenv("APP_FOO", "derp") os.Setenv("APP_FOO", "derp")
for _, test := range stringFlagTests { for _, test := range stringFlagTests {
flag := cli.StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"} flag := cli.StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"}
@ -75,22 +75,22 @@ var stringSliceFlagTests = []struct {
s := &cli.StringSlice{} s := &cli.StringSlice{}
s.Set("") s.Set("")
return s return s
}(), "--help '--help option --help option'\t"}, }(), "--help [--help option --help option]\t"},
{"h", func() *cli.StringSlice { {"h", func() *cli.StringSlice {
s := &cli.StringSlice{} s := &cli.StringSlice{}
s.Set("") s.Set("")
return s return s
}(), "-h '-h option -h option'\t"}, }(), "-h [-h option -h option]\t"},
{"h", func() *cli.StringSlice { {"h", func() *cli.StringSlice {
s := &cli.StringSlice{} s := &cli.StringSlice{}
s.Set("") s.Set("")
return s return s
}(), "-h '-h option -h option'\t"}, }(), "-h [-h option -h option]\t"},
{"test", func() *cli.StringSlice { {"test", func() *cli.StringSlice {
s := &cli.StringSlice{} s := &cli.StringSlice{}
s.Set("Something") s.Set("Something")
return s return s
}(), "--test '--test option --test option'\t"}, }(), "--test [--test option --test option]\t"},
} }
func TestStringSliceFlagHelpOutput(t *testing.T) { func TestStringSliceFlagHelpOutput(t *testing.T) {
@ -106,7 +106,7 @@ func TestStringSliceFlagHelpOutput(t *testing.T) {
} }
func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv()
os.Setenv("APP_QWWX", "11,4") os.Setenv("APP_QWWX", "11,4")
for _, test := range stringSliceFlagTests { for _, test := range stringSliceFlagTests {
flag := cli.StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"} flag := cli.StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"}
@ -122,8 +122,8 @@ var intFlagTests = []struct {
name string name string
expected string expected string
}{ }{
{"help", "--help '0'\t"}, {"help", "--help \"0\"\t"},
{"h", "-h '0'\t"}, {"h", "-h \"0\"\t"},
} }
func TestIntFlagHelpOutput(t *testing.T) { func TestIntFlagHelpOutput(t *testing.T) {
@ -139,7 +139,7 @@ func TestIntFlagHelpOutput(t *testing.T) {
} }
func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { func TestIntFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv()
os.Setenv("APP_BAR", "2") os.Setenv("APP_BAR", "2")
for _, test := range intFlagTests { for _, test := range intFlagTests {
flag := cli.IntFlag{Name: test.name, EnvVar: "APP_BAR"} flag := cli.IntFlag{Name: test.name, EnvVar: "APP_BAR"}
@ -155,8 +155,8 @@ var durationFlagTests = []struct {
name string name string
expected string expected string
}{ }{
{"help", "--help '0'\t"}, {"help", "--help \"0\"\t"},
{"h", "-h '0'\t"}, {"h", "-h \"0\"\t"},
} }
func TestDurationFlagHelpOutput(t *testing.T) { func TestDurationFlagHelpOutput(t *testing.T) {
@ -172,7 +172,7 @@ func TestDurationFlagHelpOutput(t *testing.T) {
} }
func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv()
os.Setenv("APP_BAR", "2h3m6s") os.Setenv("APP_BAR", "2h3m6s")
for _, test := range durationFlagTests { for _, test := range durationFlagTests {
flag := cli.DurationFlag{Name: test.name, EnvVar: "APP_BAR"} flag := cli.DurationFlag{Name: test.name, EnvVar: "APP_BAR"}
@ -189,14 +189,14 @@ var intSliceFlagTests = []struct {
value *cli.IntSlice value *cli.IntSlice
expected string expected string
}{ }{
{"help", &cli.IntSlice{}, "--help '--help option --help option'\t"}, {"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"},
{"h", &cli.IntSlice{}, "-h '-h option -h option'\t"}, {"h", &cli.IntSlice{}, "-h [-h option -h option]\t"},
{"test", func() *cli.IntSlice { {"test", func() *cli.IntSlice {
i := &cli.IntSlice{} i := &cli.IntSlice{}
i.Set("9") i.Set("9")
return i return i
}(), "--test '--test option --test option'\t"}, }(), "--test [--test option --test option]\t"},
} }
func TestIntSliceFlagHelpOutput(t *testing.T) { func TestIntSliceFlagHelpOutput(t *testing.T) {
@ -212,7 +212,7 @@ func TestIntSliceFlagHelpOutput(t *testing.T) {
} }
func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv()
os.Setenv("APP_SMURF", "42,3") os.Setenv("APP_SMURF", "42,3")
for _, test := range intSliceFlagTests { for _, test := range intSliceFlagTests {
flag := cli.IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"} flag := cli.IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"}
@ -228,8 +228,8 @@ var float64FlagTests = []struct {
name string name string
expected string expected string
}{ }{
{"help", "--help '0'\t"}, {"help", "--help \"0\"\t"},
{"h", "-h '0'\t"}, {"h", "-h \"0\"\t"},
} }
func TestFloat64FlagHelpOutput(t *testing.T) { func TestFloat64FlagHelpOutput(t *testing.T) {
@ -245,7 +245,7 @@ func TestFloat64FlagHelpOutput(t *testing.T) {
} }
func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv()
os.Setenv("APP_BAZ", "99.4") os.Setenv("APP_BAZ", "99.4")
for _, test := range float64FlagTests { for _, test := range float64FlagTests {
flag := cli.Float64Flag{Name: test.name, EnvVar: "APP_BAZ"} flag := cli.Float64Flag{Name: test.name, EnvVar: "APP_BAZ"}
@ -262,15 +262,14 @@ var genericFlagTests = []struct {
value cli.Generic value cli.Generic
expected string expected string
}{ }{
{"help", &Parser{}, "--help <nil>\t`-help option -help option` "}, {"test", &Parser{"abc", "def"}, "--test \"abc,def\"\ttest flag"},
{"h", &Parser{}, "-h <nil>\t`-h option -h option` "}, {"t", &Parser{"abc", "def"}, "-t \"abc,def\"\ttest flag"},
{"test", &Parser{}, "--test <nil>\t`-test option -test option` "},
} }
func TestGenericFlagHelpOutput(t *testing.T) { func TestGenericFlagHelpOutput(t *testing.T) {
for _, test := range genericFlagTests { for _, test := range genericFlagTests {
flag := cli.GenericFlag{Name: test.name} flag := cli.GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"}
output := flag.String() output := flag.String()
if output != test.expected { if output != test.expected {
@ -280,7 +279,7 @@ func TestGenericFlagHelpOutput(t *testing.T) {
} }
func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv()
os.Setenv("APP_ZAP", "3") os.Setenv("APP_ZAP", "3")
for _, test := range genericFlagTests { for _, test := range genericFlagTests {
flag := cli.GenericFlag{Name: test.name, EnvVar: "APP_ZAP"} flag := cli.GenericFlag{Name: test.name, EnvVar: "APP_ZAP"}
@ -309,6 +308,7 @@ func TestParseMultiString(t *testing.T) {
} }
func TestParseMultiStringFromEnv(t *testing.T) { func TestParseMultiStringFromEnv(t *testing.T) {
os.Clearenv()
os.Setenv("APP_COUNT", "20") os.Setenv("APP_COUNT", "20")
(&cli.App{ (&cli.App{
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -325,6 +325,24 @@ func TestParseMultiStringFromEnv(t *testing.T) {
}).Run([]string{"run"}) }).Run([]string{"run"})
} }
func TestParseMultiStringFromEnvCascade(t *testing.T) {
os.Clearenv()
os.Setenv("APP_COUNT", "20")
(&cli.App{
Flags: []cli.Flag{
cli.StringFlag{Name: "count, c", EnvVar: "COMPAT_COUNT,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{
@ -342,6 +360,7 @@ func TestParseMultiStringSlice(t *testing.T) {
} }
func TestParseMultiStringSliceFromEnv(t *testing.T) { func TestParseMultiStringSliceFromEnv(t *testing.T) {
os.Clearenv()
os.Setenv("APP_INTERVALS", "20,30,40") os.Setenv("APP_INTERVALS", "20,30,40")
(&cli.App{ (&cli.App{
@ -359,6 +378,25 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) {
}).Run([]string{"run"}) }).Run([]string{"run"})
} }
func TestParseMultiStringSliceFromEnvCascade(t *testing.T) {
os.Clearenv()
os.Setenv("APP_INTERVALS", "20,30,40")
(&cli.App{
Flags: []cli.Flag{
cli.StringSliceFlag{Name: "intervals, i", Value: &cli.StringSlice{}, EnvVar: "COMPAT_INTERVALS,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{
@ -377,6 +415,7 @@ func TestParseMultiInt(t *testing.T) {
} }
func TestParseMultiIntFromEnv(t *testing.T) { func TestParseMultiIntFromEnv(t *testing.T) {
os.Clearenv()
os.Setenv("APP_TIMEOUT_SECONDS", "10") os.Setenv("APP_TIMEOUT_SECONDS", "10")
a := cli.App{ a := cli.App{
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -394,6 +433,25 @@ func TestParseMultiIntFromEnv(t *testing.T) {
a.Run([]string{"run"}) a.Run([]string{"run"})
} }
func TestParseMultiIntFromEnvCascade(t *testing.T) {
os.Clearenv()
os.Setenv("APP_TIMEOUT_SECONDS", "10")
a := cli.App{
Flags: []cli.Flag{
cli.IntFlag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,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) { func TestParseMultiIntSlice(t *testing.T) {
(&cli.App{ (&cli.App{
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -411,6 +469,7 @@ func TestParseMultiIntSlice(t *testing.T) {
} }
func TestParseMultiIntSliceFromEnv(t *testing.T) { func TestParseMultiIntSliceFromEnv(t *testing.T) {
os.Clearenv()
os.Setenv("APP_INTERVALS", "20,30,40") os.Setenv("APP_INTERVALS", "20,30,40")
(&cli.App{ (&cli.App{
@ -428,6 +487,25 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) {
}).Run([]string{"run"}) }).Run([]string{"run"})
} }
func TestParseMultiIntSliceFromEnvCascade(t *testing.T) {
os.Clearenv()
os.Setenv("APP_INTERVALS", "20,30,40")
(&cli.App{
Flags: []cli.Flag{
cli.IntSliceFlag{Name: "intervals, i", Value: &cli.IntSlice{}, EnvVar: "COMPAT_INTERVALS,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) { func TestParseMultiFloat64(t *testing.T) {
a := cli.App{ a := cli.App{
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -446,6 +524,7 @@ func TestParseMultiFloat64(t *testing.T) {
} }
func TestParseMultiFloat64FromEnv(t *testing.T) { func TestParseMultiFloat64FromEnv(t *testing.T) {
os.Clearenv()
os.Setenv("APP_TIMEOUT_SECONDS", "15.5") os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
a := cli.App{ a := cli.App{
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -463,6 +542,25 @@ func TestParseMultiFloat64FromEnv(t *testing.T) {
a.Run([]string{"run"}) a.Run([]string{"run"})
} }
func TestParseMultiFloat64FromEnvCascade(t *testing.T) {
os.Clearenv()
os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
a := cli.App{
Flags: []cli.Flag{
cli.Float64Flag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,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{
@ -481,6 +579,7 @@ func TestParseMultiBool(t *testing.T) {
} }
func TestParseMultiBoolFromEnv(t *testing.T) { func TestParseMultiBoolFromEnv(t *testing.T) {
os.Clearenv()
os.Setenv("APP_DEBUG", "1") os.Setenv("APP_DEBUG", "1")
a := cli.App{ a := cli.App{
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -498,6 +597,25 @@ func TestParseMultiBoolFromEnv(t *testing.T) {
a.Run([]string{"run"}) a.Run([]string{"run"})
} }
func TestParseMultiBoolFromEnvCascade(t *testing.T) {
os.Clearenv()
os.Setenv("APP_DEBUG", "1")
a := cli.App{
Flags: []cli.Flag{
cli.BoolFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,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) { func TestParseMultiBoolT(t *testing.T) {
a := cli.App{ a := cli.App{
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -516,6 +634,7 @@ func TestParseMultiBoolT(t *testing.T) {
} }
func TestParseMultiBoolTFromEnv(t *testing.T) { func TestParseMultiBoolTFromEnv(t *testing.T) {
os.Clearenv()
os.Setenv("APP_DEBUG", "0") os.Setenv("APP_DEBUG", "0")
a := cli.App{ a := cli.App{
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -533,6 +652,25 @@ func TestParseMultiBoolTFromEnv(t *testing.T) {
a.Run([]string{"run"}) a.Run([]string{"run"})
} }
func TestParseMultiBoolTFromEnvCascade(t *testing.T) {
os.Clearenv()
os.Setenv("APP_DEBUG", "0")
a := cli.App{
Flags: []cli.Flag{
cli.BoolTFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,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 {
@ -569,6 +707,7 @@ func TestParseGeneric(t *testing.T) {
} }
func TestParseGenericFromEnv(t *testing.T) { func TestParseGenericFromEnv(t *testing.T) {
os.Clearenv()
os.Setenv("APP_SERVE", "20,30") os.Setenv("APP_SERVE", "20,30")
a := cli.App{ a := cli.App{
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -585,3 +724,19 @@ func TestParseGenericFromEnv(t *testing.T) {
} }
a.Run([]string{"run"}) a.Run([]string{"run"})
} }
func TestParseGenericFromEnvCascade(t *testing.T) {
os.Clearenv()
os.Setenv("APP_FOO", "99,2000")
a := cli.App{
Flags: []cli.Flag{
cli.GenericFlag{Name: "foos", Value: &Parser{}, EnvVar: "COMPAT_FOO,APP_FOO"},
},
Action: func(ctx *cli.Context) {
if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) {
t.Errorf("value not set from env")
}
},
}
a.Run([]string{"run"})
}

33
help.go
View File

@ -1,11 +1,6 @@
package cli package cli
import ( import "fmt"
"fmt"
"os"
"text/tabwriter"
"text/template"
)
// The text template for the Default help topic. // The text template for the Default help topic.
// cli.go uses text/template to render templates. You can // cli.go uses text/template to render templates. You can
@ -94,7 +89,9 @@ var helpSubcommand = Command{
} }
// Prints help for the App // Prints help for the App
var HelpPrinter = printHelp type helpPrinter func(templ string, data interface{})
var HelpPrinter helpPrinter = nil
// Prints version for the App // Prints version for the App
var VersionPrinter = printVersion var VersionPrinter = printVersion
@ -106,9 +103,9 @@ func ShowAppHelp(c *Context) {
// 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) fmt.Fprintln(c.App.Writer, command.Name)
if command.ShortName != "" { if command.ShortName != "" {
fmt.Println(command.ShortName) fmt.Fprintln(c.App.Writer, command.ShortName)
} }
} }
} }
@ -125,13 +122,13 @@ func ShowCommandHelp(c *Context, command string) {
if c.App.CommandNotFound != nil { if c.App.CommandNotFound != nil {
c.App.CommandNotFound(c, command) c.App.CommandNotFound(c, command)
} else { } else {
fmt.Printf("No help topic for '%v'\n", command) fmt.Fprintf(c.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 +137,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,16 +156,6 @@ func ShowCommandCompletions(ctx *Context, command string) {
} }
} }
func printHelp(templ string, data interface{}) {
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
t := template.Must(template.New("help").Parse(templ))
err := t.Execute(w, data)
if err != nil {
panic(err)
}
w.Flush()
}
func checkVersion(c *Context) bool { func checkVersion(c *Context) bool {
if c.GlobalBool("version") { if c.GlobalBool("version") {
ShowVersion(c) ShowVersion(c)
@ -206,7 +193,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
} }