Skip to content

Commit

Permalink
Merge pull request #361 from codegangsta/txgruppi-develop
Browse files Browse the repository at this point in the history
Optional exit code support
  • Loading branch information
meatballhat committed Apr 30, 2016
2 parents 222ec6f + 06c01a4 commit 663fc0b
Show file tree
Hide file tree
Showing 16 changed files with 561 additions and 170 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ matrix:
allow_failures:
- go: tip

before_script:
- go get github.com/meatballhat/gfmxr/...

script:
- go vet ./...
- go test -v ./...
- gfmxr -c $(grep -c 'package main' README.md) -s README.md
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,28 @@
### Added
- Support for placeholders in flag usage strings

### Changed
- The `App.Action` and `Command.Action` now prefer a return signature of
`func(*cli.Context) error`, as defined by `cli.ActionFunc`. If a non-nil
`error` is returned, there may be two outcomes:
- If the error fulfills `cli.ExitCoder`, then `os.Exit` will be called
automatically
- Else the error is bubbled up and returned from `App.Run`
- Specifying an `Action` with the legacy return signature of
`func(*cli.Context)` will produce a deprecation message to stderr
- Specifying an `Action` that is not a `func` type will produce a non-zero exit
from `App.Run`
- Specifying an `Action` func that has an invalid (input) signature will
produce a non-zero exit from `App.Run`

### Deprecated
- <a name="deprecated-cli-app-runandexitonerror"></a>
`cli.App.RunAndExitOnError`, which should now be done by returning an error
that fulfills `cli.ExitCoder` to `cli.App.Run`.
- <a name="deprecated-cli-app-action-signature"></a> the legacy signature for
`cli.App.Action` of `func(*cli.Context)`, which should now have a return
signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`.

### Fixed
- Added missing `*cli.Context.GlobalFloat64` method

Expand Down
138 changes: 91 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,19 @@ This app will run and show help text, but is not very useful. Let's give an acti
package main

import (
"fmt"
"os"

"github.com/codegangsta/cli"
)

func main() {
app := cli.NewApp()
app.Name = "boom"
app.Usage = "make an explosive entrance"
app.Action = func(c *cli.Context) {
println("boom! I say!")
app.Action = func(c *cli.Context) error {
fmt.Println("boom! I say!")
return nil
}

app.Run(os.Args)
Expand All @@ -78,16 +81,19 @@ Start by creating a directory named `greet`, and within it, add a file, `greet.g
package main

import (
"fmt"
"os"

"github.com/codegangsta/cli"
)

func main() {
app := cli.NewApp()
app.Name = "greet"
app.Usage = "fight the loneliness!"
app.Action = func(c *cli.Context) {
println("Hello friend!")
app.Action = func(c *cli.Context) error {
fmt.Println("Hello friend!")
return nil
}

app.Run(os.Args)
Expand Down Expand Up @@ -133,8 +139,9 @@ You can lookup arguments by calling the `Args` function on `cli.Context`.

``` go
...
app.Action = func(c *cli.Context) {
println("Hello", c.Args()[0])
app.Action = func(c *cli.Context) error {
fmt.Println("Hello", c.Args()[0])
return nil
}
...
```
Expand All @@ -152,16 +159,17 @@ app.Flags = []cli.Flag {
Usage: "language for the greeting",
},
}
app.Action = func(c *cli.Context) {
app.Action = func(c *cli.Context) error {
name := "someone"
if c.NArg() > 0 {
name = c.Args()[0]
}
if c.String("lang") == "spanish" {
println("Hola", name)
fmt.Println("Hola", name)
} else {
println("Hello", name)
fmt.Println("Hello", name)
}
return nil
}
...
```
Expand All @@ -179,16 +187,17 @@ app.Flags = []cli.Flag {
Destination: &language,
},
}
app.Action = func(c *cli.Context) {
app.Action = func(c *cli.Context) error {
name := "someone"
if c.NArg() > 0 {
name = c.Args()[0]
}
if language == "spanish" {
println("Hola", name)
fmt.Println("Hola", name)
} else {
println("Hello", name)
fmt.Println("Hello", name)
}
return nil
}
...
```
Expand All @@ -214,7 +223,7 @@ Will result in help output like:
--config FILE, -c FILE Load configuration from FILE
```

Note that only the first placeholder is used. Subsequent back-quoted words will be left as-is.
Note that only the first placeholder is used. Subsequent back-quoted words will be left as-is.

#### Alternate Names

Expand Down Expand Up @@ -276,8 +285,8 @@ Initialization must also occur for these flags. Below is an example initializing
command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
```

The code above will use the "load" string as a flag name to get the file name of a yaml file from the cli.Context.
It will then use that file name to initialize the yaml input source for any flags that are defined on that command.
The code above will use the "load" string as a flag name to get the file name of a yaml file from the cli.Context.
It will then use that file name to initialize the yaml input source for any flags that are defined on that command.
As a note the "load" flag used would also have to be defined on the command flags in order for this code snipped to work.

Currently only YAML files are supported but developers can add support for other input sources by implementing the
Expand All @@ -286,20 +295,20 @@ altsrc.InputSourceContext for their given sources.
Here is a more complete sample of a command using YAML support:

``` go
command := &cli.Command{
Name: "test-cmd",
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(c *cli.Context) {
// Action to run
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "test"}),
cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
err := command.Run(c)
command := &cli.Command{
Name: "test-cmd",
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(c *cli.Context) error {
// Action to run
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "test"}),
cli.StringFlag{Name: "load"}},
}
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
err := command.Run(c)
```

### Subcommands
Expand All @@ -314,15 +323,15 @@ app.Commands = []cli.Command{
Aliases: []string{"a"},
Usage: "add a task to the list",
Action: func(c *cli.Context) {
println("added task: ", c.Args().First())
fmt.Println("added task: ", c.Args().First())
},
},
{
Name: "complete",
Aliases: []string{"c"},
Usage: "complete a task on the list",
Action: func(c *cli.Context) {
println("completed task: ", c.Args().First())
fmt.Println("completed task: ", c.Args().First())
},
},
{
Expand All @@ -334,14 +343,14 @@ app.Commands = []cli.Command{
Name: "add",
Usage: "add a new template",
Action: func(c *cli.Context) {
println("new task template: ", c.Args().First())
fmt.Println("new task template: ", c.Args().First())
},
},
{
Name: "remove",
Usage: "remove an existing template",
Action: func(c *cli.Context) {
println("removed task template: ", c.Args().First())
fmt.Println("removed task template: ", c.Args().First())
},
},
},
Expand All @@ -360,19 +369,19 @@ E.g.

```go
...
app.Commands = []cli.Command{
{
Name: "noop",
},
{
Name: "add",
Category: "template",
},
{
Name: "remove",
Category: "template",
},
}
app.Commands = []cli.Command{
{
Name: "noop",
},
{
Name: "add",
Category: "template",
},
{
Name: "remove",
Category: "template",
},
}
...
```

Expand All @@ -389,6 +398,41 @@ COMMANDS:
...
```

### Exit code

Calling `App.Run` will not automatically call `os.Exit`, which means that by
default the exit code will "fall through" to being `0`. An explicit exit code
may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a
`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.:

``` go
package main

import (
"os"

"github.com/codegangsta/cli"
)

func main() {
app := cli.NewApp()
app.Flags = []cli.Flag{
cli.BoolTFlag{
Name: "ginger-crouton",
Usage: "is it in the soup?",
},
}
app.Action = func(ctx *cli.Context) error {
if !ctx.Bool("ginger-crouton") {
return cli.NewExitError("it is not in the soup", 86)
}
return nil
}

app.Run(os.Args)
}
```

### Bash Completion

You can enable completion commands by setting the `EnableBashCompletion`
Expand All @@ -407,7 +451,7 @@ app.Commands = []cli.Command{
Aliases: []string{"c"},
Usage: "complete a task on the list",
Action: func(c *cli.Context) {
println("completed task: ", c.Args().First())
fmt.Println("completed task: ", c.Args().First())
},
BashComplete: func(c *cli.Context) {
// This will complete if no args are passed
Expand Down
4 changes: 2 additions & 2 deletions altsrc/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func ApplyInputSourceValues(context *cli.Context, inputSourceContext InputSource
// InitInputSource is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new
// input source based on the func provided. If there is no error it will then apply the new input source to any flags
// that are supported by the input source
func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceContext, error)) func(context *cli.Context) error {
func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceContext, error)) cli.BeforeFunc {
return func(context *cli.Context) error {
inputSource, err := createInputSource()
if err != nil {
Expand All @@ -52,7 +52,7 @@ func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceCont
// InitInputSourceWithContext is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new
// input source based on the func provided with potentially using existing cli.Context values to initialize itself. If there is
// no error it will then apply the new input source to any flags that are supported by the input source
func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context *cli.Context) (InputSourceContext, error)) func(context *cli.Context) error {
func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context *cli.Context) (InputSourceContext, error)) cli.BeforeFunc {
return func(context *cli.Context) error {
inputSource, err := createInputSource(context)
if err != nil {
Expand Down
15 changes: 10 additions & 5 deletions altsrc/yaml_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ func TestCommandYamlFileTest(t *testing.T) {
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(c *cli.Context) {
Action: func(c *cli.Context) error {
val := c.Int("test")
expect(t, val, 15)
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "test"}),
Expand Down Expand Up @@ -61,9 +62,10 @@ func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) {
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(c *cli.Context) {
Action: func(c *cli.Context) error {
val := c.Int("test")
expect(t, val, 10)
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "THE_TEST"}),
Expand Down Expand Up @@ -92,9 +94,10 @@ func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) {
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(c *cli.Context) {
Action: func(c *cli.Context) error {
val := c.Int("test")
expect(t, val, 7)
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "test"}),
Expand Down Expand Up @@ -123,9 +126,10 @@ func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) {
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(c *cli.Context) {
Action: func(c *cli.Context) error {
val := c.Int("test")
expect(t, val, 15)
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "test", Value: 7}),
Expand Down Expand Up @@ -157,9 +161,10 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(c *cli.Context) {
Action: func(c *cli.Context) error {
val := c.Int("test")
expect(t, val, 11)
return nil
},
Flags: []cli.Flag{
NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVar: "THE_TEST"}),
Expand Down
Loading

0 comments on commit 663fc0b

Please sign in to comment.