Go with interfaces + private opaque types
rather than public types that wrap slices
This commit is contained in:
parent
cd1e8c3452
commit
3d75e9e711
27
app.go
27
app.go
@ -36,8 +36,8 @@ type App struct {
|
|||||||
HideHelp bool
|
HideHelp bool
|
||||||
// Boolean to hide built-in version flag and the VERSION section of help
|
// Boolean to hide built-in version flag and the VERSION section of help
|
||||||
HideVersion bool
|
HideVersion bool
|
||||||
// Populate on app startup, only gettable through method Categories()
|
// Categories contains the categorized commands and is populated on app startup
|
||||||
categories *CommandCategories
|
Categories CommandCategories
|
||||||
// An action to execute when the bash-completion flag is set
|
// An action to execute when the bash-completion flag is set
|
||||||
BashComplete BashCompleteFunc
|
BashComplete BashCompleteFunc
|
||||||
// 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
|
||||||
@ -113,11 +113,11 @@ func (a *App) Setup() {
|
|||||||
}
|
}
|
||||||
a.Commands = newCmds
|
a.Commands = newCmds
|
||||||
|
|
||||||
a.categories = NewCommandCategories()
|
a.Categories = newCommandCategories()
|
||||||
for _, command := range a.Commands {
|
for _, command := range a.Commands {
|
||||||
a.categories.AddCommand(command.Category, command)
|
a.Categories.AddCommand(command.Category, command)
|
||||||
}
|
}
|
||||||
sort.Sort(a.categories)
|
sort.Sort(a.Categories.(*commandCategories))
|
||||||
|
|
||||||
// append help to commands
|
// append help to commands
|
||||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
||||||
@ -184,7 +184,7 @@ func (a *App) Run(arguments []string) (err error) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
if afterErr := a.After(context); afterErr != nil {
|
if afterErr := a.After(context); afterErr != nil {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = NewMultiError(err, afterErr)
|
err = newMultiError(err, afterErr)
|
||||||
} else {
|
} else {
|
||||||
err = afterErr
|
err = afterErr
|
||||||
}
|
}
|
||||||
@ -296,7 +296,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
|||||||
if afterErr != nil {
|
if afterErr != nil {
|
||||||
HandleExitCoder(err)
|
HandleExitCoder(err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = NewMultiError(err, afterErr)
|
err = newMultiError(err, afterErr)
|
||||||
} else {
|
} else {
|
||||||
err = afterErr
|
err = afterErr
|
||||||
}
|
}
|
||||||
@ -340,17 +340,12 @@ func (a *App) Command(name string) *Command {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Categories returns a slice containing all the categories with the commands they contain
|
|
||||||
func (a *App) Categories() *CommandCategories {
|
|
||||||
return a.categories
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisibleCategories returns a slice of categories and commands that are
|
// VisibleCategories returns a slice of categories and commands that are
|
||||||
// Hidden=false
|
// Hidden=false
|
||||||
func (a *App) VisibleCategories() []*CommandCategory {
|
func (a *App) VisibleCategories() []CommandCategory {
|
||||||
ret := []*CommandCategory{}
|
ret := []CommandCategory{}
|
||||||
for _, category := range a.categories.Categories() {
|
for _, category := range a.Categories.Categories() {
|
||||||
if visible := func() *CommandCategory {
|
if visible := func() CommandCategory {
|
||||||
if len(category.VisibleCommands()) > 0 {
|
if len(category.VisibleCommands()) > 0 {
|
||||||
return category
|
return category
|
||||||
}
|
}
|
||||||
|
59
app_test.go
59
app_test.go
@ -216,7 +216,7 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
|
|||||||
|
|
||||||
func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
|
func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
|
||||||
var parsedOption string
|
var parsedOption string
|
||||||
var args *Args
|
var args Args
|
||||||
|
|
||||||
app := NewApp()
|
app := NewApp()
|
||||||
command := &Command{
|
command := &Command{
|
||||||
@ -241,7 +241,7 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestApp_CommandWithDash(t *testing.T) {
|
func TestApp_CommandWithDash(t *testing.T) {
|
||||||
var args *Args
|
var args Args
|
||||||
|
|
||||||
app := NewApp()
|
app := NewApp()
|
||||||
command := &Command{
|
command := &Command{
|
||||||
@ -260,7 +260,7 @@ func TestApp_CommandWithDash(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) {
|
func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) {
|
||||||
var args *Args
|
var args Args
|
||||||
|
|
||||||
app := NewApp()
|
app := NewApp()
|
||||||
command := &Command{
|
command := &Command{
|
||||||
@ -1142,25 +1142,24 @@ func TestApp_Run_Categories(t *testing.T) {
|
|||||||
|
|
||||||
app.Run([]string{"categories"})
|
app.Run([]string{"categories"})
|
||||||
|
|
||||||
expect := &CommandCategories{
|
expect := commandCategories([]*commandCategory{
|
||||||
slice: []*CommandCategory{
|
{
|
||||||
{
|
name: "1",
|
||||||
Name: "1",
|
commands: []*Command{
|
||||||
commands: []*Command{
|
app.Commands[0],
|
||||||
app.Commands[0],
|
app.Commands[1],
|
||||||
app.Commands[1],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "2",
|
|
||||||
commands: []*Command{
|
|
||||||
app.Commands[2],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
{
|
||||||
if !reflect.DeepEqual(app.Categories(), expect) {
|
name: "2",
|
||||||
t.Fatalf("expected categories %#v, to equal %#v", app.Categories(), expect)
|
commands: []*Command{
|
||||||
|
app.Commands[2],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(app.Categories, &expect) {
|
||||||
|
t.Fatalf("expected categories %#v, to equal %#v", app.Categories, &expect)
|
||||||
}
|
}
|
||||||
|
|
||||||
output := buf.String()
|
output := buf.String()
|
||||||
@ -1193,15 +1192,15 @@ func TestApp_VisibleCategories(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := []*CommandCategory{
|
expected := []CommandCategory{
|
||||||
{
|
&commandCategory{
|
||||||
Name: "2",
|
name: "2",
|
||||||
commands: []*Command{
|
commands: []*Command{
|
||||||
app.Commands[1],
|
app.Commands[1],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
&commandCategory{
|
||||||
Name: "3",
|
name: "3",
|
||||||
commands: []*Command{
|
commands: []*Command{
|
||||||
app.Commands[2],
|
app.Commands[2],
|
||||||
},
|
},
|
||||||
@ -1233,9 +1232,9 @@ func TestApp_VisibleCategories(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
expected = []*CommandCategory{
|
expected = []CommandCategory{
|
||||||
{
|
&commandCategory{
|
||||||
Name: "3",
|
name: "3",
|
||||||
commands: []*Command{
|
commands: []*Command{
|
||||||
app.Commands[2],
|
app.Commands[2],
|
||||||
},
|
},
|
||||||
@ -1268,10 +1267,8 @@ func TestApp_VisibleCategories(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
expected = []*CommandCategory{}
|
|
||||||
|
|
||||||
app.Setup()
|
app.Setup()
|
||||||
expect(t, expected, app.VisibleCategories())
|
expect(t, []CommandCategory{}, app.VisibleCategories())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
|
func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
|
||||||
|
62
args.go
62
args.go
@ -6,55 +6,55 @@ var (
|
|||||||
argsRangeErr = errors.New("index out of range")
|
argsRangeErr = errors.New("index out of range")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Args wraps a string slice with some convenience methods
|
type Args interface {
|
||||||
type Args struct {
|
// Get returns the nth argument, or else a blank string
|
||||||
slice []string
|
Get(n int) string
|
||||||
|
// First returns the first argument, or else a blank string
|
||||||
|
First() string
|
||||||
|
// Tail returns the rest of the arguments (not the first one)
|
||||||
|
// or else an empty string slice
|
||||||
|
Tail() []string
|
||||||
|
// Len returns the length of the wrapped slice
|
||||||
|
Len() int
|
||||||
|
// Present checks if there are any arguments present
|
||||||
|
Present() bool
|
||||||
|
// Slice returns a copy of the internal slice
|
||||||
|
Slice() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the nth argument, or else a blank string
|
type args []string
|
||||||
func (a *Args) Get(n int) string {
|
|
||||||
if len(a.slice) > n {
|
func (a *args) Get(n int) string {
|
||||||
return a.slice[n]
|
if len(*a) > n {
|
||||||
|
return (*a)[n]
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// First returns the first argument, or else a blank string
|
func (a *args) First() string {
|
||||||
func (a *Args) First() string {
|
|
||||||
return a.Get(0)
|
return a.Get(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tail returns the rest of the arguments (not the first one)
|
func (a *args) Tail() []string {
|
||||||
// or else an empty string slice
|
|
||||||
func (a *Args) Tail() []string {
|
|
||||||
if a.Len() >= 2 {
|
if a.Len() >= 2 {
|
||||||
return a.slice[1:]
|
tail := []string((*a)[1:])
|
||||||
|
ret := make([]string, len(tail))
|
||||||
|
copy(ret, tail)
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Len returns the length of the wrapped slice
|
func (a *args) Len() int {
|
||||||
func (a *Args) Len() int {
|
return len(*a)
|
||||||
return len(a.slice)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Present checks if there are any arguments present
|
func (a *args) Present() bool {
|
||||||
func (a *Args) Present() bool {
|
|
||||||
return a.Len() != 0
|
return a.Len() != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Swap swaps arguments at the given indexes
|
func (a *args) Slice() []string {
|
||||||
func (a *Args) Swap(from, to int) error {
|
ret := make([]string, len(*a))
|
||||||
if from >= a.Len() || to >= a.Len() {
|
copy(ret, []string(*a))
|
||||||
return argsRangeErr
|
|
||||||
}
|
|
||||||
a.slice[from], a.slice[to] = a.slice[to], a.slice[from]
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slice returns a copy of the internal slice
|
|
||||||
func (a *Args) Slice() []string {
|
|
||||||
ret := make([]string, len(a.slice))
|
|
||||||
copy(ret, a.slice)
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
76
category.go
76
category.go
@ -1,54 +1,80 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
// CommandCategories wraps a slice of *CommandCategory.
|
type CommandCategories interface {
|
||||||
type CommandCategories struct {
|
// AddCommand adds a command to a category, creating a new category if necessary.
|
||||||
slice []*CommandCategory
|
AddCommand(category string, command *Command)
|
||||||
|
// Categories returns a copy of the category slice
|
||||||
|
Categories() []CommandCategory
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCommandCategories() *CommandCategories {
|
type commandCategories []*commandCategory
|
||||||
return &CommandCategories{slice: []*CommandCategory{}}
|
|
||||||
|
func newCommandCategories() CommandCategories {
|
||||||
|
ret := commandCategories([]*commandCategory{})
|
||||||
|
return &ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CommandCategories) Less(i, j int) bool {
|
func (c *commandCategories) Less(i, j int) bool {
|
||||||
return c.slice[i].Name < c.slice[j].Name
|
return (*c)[i].Name() < (*c)[j].Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CommandCategories) Len() int {
|
func (c *commandCategories) Len() int {
|
||||||
return len(c.slice)
|
return len(*c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CommandCategories) Swap(i, j int) {
|
func (c *commandCategories) Swap(i, j int) {
|
||||||
c.slice[i], c.slice[j] = c.slice[j], c.slice[i]
|
(*c)[i], (*c)[j] = (*c)[j], (*c)[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddCommand adds a command to a category, creating a new category if necessary.
|
func (c *commandCategories) AddCommand(category string, command *Command) {
|
||||||
func (c *CommandCategories) AddCommand(category string, command *Command) {
|
for _, commandCategory := range []*commandCategory(*c) {
|
||||||
for _, commandCategory := range c.slice {
|
if commandCategory.name == category {
|
||||||
if commandCategory.Name == category {
|
|
||||||
commandCategory.commands = append(commandCategory.commands, command)
|
commandCategory.commands = append(commandCategory.commands, command)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.slice = append(c.slice,
|
newVal := commandCategories(append(*c,
|
||||||
&CommandCategory{Name: category, commands: []*Command{command}})
|
&commandCategory{name: category, commands: []*Command{command}}))
|
||||||
|
(*c) = newVal
|
||||||
}
|
}
|
||||||
|
|
||||||
// Categories returns a copy of the category slice
|
func (c *commandCategories) Categories() []CommandCategory {
|
||||||
func (c *CommandCategories) Categories() []*CommandCategory {
|
ret := []CommandCategory{}
|
||||||
ret := make([]*CommandCategory, len(c.slice))
|
for _, cat := range *c {
|
||||||
copy(ret, c.slice)
|
ret = append(ret, cat)
|
||||||
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommandCategory is a category containing commands.
|
// CommandCategory is a category containing commands.
|
||||||
type CommandCategory struct {
|
type CommandCategory interface {
|
||||||
Name string
|
// Name returns the category name string
|
||||||
|
Name() string
|
||||||
|
// VisibleCommands returns a slice of the Commands with Hidden=false
|
||||||
|
VisibleCommands() []*Command
|
||||||
|
}
|
||||||
|
|
||||||
|
type commandCategory struct {
|
||||||
|
name string
|
||||||
commands []*Command
|
commands []*Command
|
||||||
}
|
}
|
||||||
|
|
||||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
func newCommandCategory(name string) *commandCategory {
|
||||||
func (c *CommandCategory) VisibleCommands() []*Command {
|
return &commandCategory{
|
||||||
|
name: name,
|
||||||
|
commands: []*Command{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commandCategory) Name() string {
|
||||||
|
return c.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commandCategory) VisibleCommands() []*Command {
|
||||||
|
if c.commands == nil {
|
||||||
|
c.commands = []*Command{}
|
||||||
|
}
|
||||||
|
|
||||||
ret := []*Command{}
|
ret := []*Command{}
|
||||||
for _, command := range c.commands {
|
for _, command := range c.commands {
|
||||||
if !command.Hidden {
|
if !command.Hidden {
|
||||||
|
@ -120,7 +120,7 @@ func (c *Command) Run(ctx *Context) (err error) {
|
|||||||
if afterErr != nil {
|
if afterErr != nil {
|
||||||
HandleExitCoder(err)
|
HandleExitCoder(err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = NewMultiError(err, afterErr)
|
err = newMultiError(err, afterErr)
|
||||||
} else {
|
} else {
|
||||||
err = afterErr
|
err = afterErr
|
||||||
}
|
}
|
||||||
@ -193,12 +193,12 @@ func (c *Command) startApp(ctx *Context) error {
|
|||||||
app.Compiled = ctx.App.Compiled
|
app.Compiled = ctx.App.Compiled
|
||||||
app.Writer = ctx.App.Writer
|
app.Writer = ctx.App.Writer
|
||||||
|
|
||||||
app.categories = NewCommandCategories()
|
app.Categories = newCommandCategories()
|
||||||
for _, command := range c.Subcommands {
|
for _, command := range c.Subcommands {
|
||||||
app.categories.AddCommand(command.Category, command)
|
app.Categories.AddCommand(command.Category, command)
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(app.categories)
|
sort.Sort(app.Categories.(*commandCategories))
|
||||||
|
|
||||||
// bash completion
|
// bash completion
|
||||||
app.EnableBashCompletion = ctx.App.EnableBashCompletion
|
app.EnableBashCompletion = ctx.App.EnableBashCompletion
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
// Context is a type that is passed through to
|
// Context is a type that is passed through to
|
||||||
// each Handler action in a cli application. Context
|
// each Handler action in a cli application. Context
|
||||||
// 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
|
||||||
@ -148,8 +148,9 @@ func (c *Context) Lineage() []*Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Args returns the command line arguments associated with the context.
|
// Args returns the command line arguments associated with the context.
|
||||||
func (c *Context) Args() *Args {
|
func (c *Context) Args() Args {
|
||||||
return &Args{slice: c.flagSet.Args()}
|
ret := args(c.flagSet.Args())
|
||||||
|
return &ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// NArg returns the number of the command line arguments.
|
// NArg returns the number of the command line arguments.
|
||||||
|
50
errors.go
50
errors.go
@ -15,25 +15,39 @@ var OsExiter = os.Exit
|
|||||||
var ErrWriter io.Writer = os.Stderr
|
var ErrWriter io.Writer = os.Stderr
|
||||||
|
|
||||||
// MultiError is an error that wraps multiple errors.
|
// MultiError is an error that wraps multiple errors.
|
||||||
type MultiError struct {
|
type MultiError interface {
|
||||||
errors []error
|
error
|
||||||
|
// Errors returns a copy of the errors slice
|
||||||
|
Errors() []error
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMultiError creates a new MultiError. Pass in one or more errors.
|
// NewMultiError creates a new MultiError. Pass in one or more errors.
|
||||||
func NewMultiError(err ...error) MultiError {
|
func newMultiError(err ...error) MultiError {
|
||||||
return MultiError{errors: err}
|
ret := multiError(err)
|
||||||
|
return &ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error implents the error interface.
|
type multiError []error
|
||||||
func (m MultiError) Error() string {
|
|
||||||
errs := make([]string, len(m.errors))
|
// Error implements the error interface.
|
||||||
for i, err := range m.errors {
|
func (m *multiError) Error() string {
|
||||||
|
errs := make([]string, len(*m))
|
||||||
|
for i, err := range *m {
|
||||||
errs[i] = err.Error()
|
errs[i] = err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.Join(errs, "\n")
|
return strings.Join(errs, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Errors returns a copy of the errors slice
|
||||||
|
func (m *multiError) Errors() []error {
|
||||||
|
errs := make([]error, len(*m))
|
||||||
|
for _, err := range *m {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
// ExitCoder is the interface checked by `App` and `Command` for a custom exit
|
// ExitCoder is the interface checked by `App` and `Command` for a custom exit
|
||||||
// code
|
// code
|
||||||
type ExitCoder interface {
|
type ExitCoder interface {
|
||||||
@ -41,29 +55,25 @@ type ExitCoder interface {
|
|||||||
ExitCode() int
|
ExitCode() int
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExitError fulfills both the builtin `error` interface and `ExitCoder`
|
type exitError struct {
|
||||||
type ExitError struct {
|
|
||||||
exitCode int
|
exitCode int
|
||||||
message string
|
message string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewExitError makes a new *ExitError
|
// Exit wraps a message and exit code into an ExitCoder suitable for handling by
|
||||||
func NewExitError(message string, exitCode int) *ExitError {
|
// HandleExitCoder
|
||||||
return &ExitError{
|
func Exit(message string, exitCode int) ExitCoder {
|
||||||
|
return &exitError{
|
||||||
exitCode: exitCode,
|
exitCode: exitCode,
|
||||||
message: message,
|
message: message,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error returns the string message, fulfilling the interface required by
|
func (ee *exitError) Error() string {
|
||||||
// `error`
|
|
||||||
func (ee *ExitError) Error() string {
|
|
||||||
return ee.message
|
return ee.message
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExitCode returns the exit code, fulfilling the interface required by
|
func (ee *exitError) ExitCode() int {
|
||||||
// `ExitCoder`
|
|
||||||
func (ee *ExitError) ExitCode() int {
|
|
||||||
return ee.exitCode
|
return ee.exitCode
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +95,7 @@ func HandleExitCoder(err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if multiErr, ok := err.(MultiError); ok {
|
if multiErr, ok := err.(MultiError); ok {
|
||||||
for _, merr := range multiErr.errors {
|
for _, merr := range multiErr.Errors() {
|
||||||
HandleExitCoder(merr)
|
HandleExitCoder(merr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ func TestHandleExitCoder_ExitCoder(t *testing.T) {
|
|||||||
|
|
||||||
defer func() { OsExiter = os.Exit }()
|
defer func() { OsExiter = os.Exit }()
|
||||||
|
|
||||||
HandleExitCoder(NewExitError("galactic perimeter breach", 9))
|
HandleExitCoder(Exit("galactic perimeter breach", 9))
|
||||||
|
|
||||||
expect(t, exitCode, 9)
|
expect(t, exitCode, 9)
|
||||||
expect(t, called, true)
|
expect(t, called, true)
|
||||||
@ -51,8 +51,8 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) {
|
|||||||
|
|
||||||
defer func() { OsExiter = os.Exit }()
|
defer func() { OsExiter = os.Exit }()
|
||||||
|
|
||||||
exitErr := NewExitError("galactic perimeter breach", 9)
|
exitErr := Exit("galactic perimeter breach", 9)
|
||||||
err := NewMultiError(errors.New("wowsa"), errors.New("egad"), exitErr)
|
err := newMultiError(errors.New("wowsa"), errors.New("egad"), exitErr)
|
||||||
HandleExitCoder(err)
|
HandleExitCoder(err)
|
||||||
|
|
||||||
expect(t, exitCode, 9)
|
expect(t, exitCode, 9)
|
||||||
|
21
help.go
21
help.go
@ -149,7 +149,7 @@ func ShowCommandHelp(ctx *Context, command string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ctx.App.CommandNotFound == nil {
|
if ctx.App.CommandNotFound == nil {
|
||||||
return NewExitError(fmt.Sprintf("No help topic for '%v'", command), 3)
|
return Exit(fmt.Sprintf("No help topic for '%v'", command), 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.App.CommandNotFound(ctx, command)
|
ctx.App.CommandNotFound(ctx, command)
|
||||||
@ -201,15 +201,28 @@ func printHelp(out io.Writer, templ string, data interface{}) {
|
|||||||
|
|
||||||
w := tabwriter.NewWriter(out, 0, 8, 1, '\t', 0)
|
w := tabwriter.NewWriter(out, 0, 8, 1, '\t', 0)
|
||||||
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
|
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
|
||||||
|
|
||||||
|
errDebug := os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != ""
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
if errDebug {
|
||||||
|
fmt.Fprintf(ErrWriter, "CLI TEMPLATE PANIC: %#v\n", r)
|
||||||
|
}
|
||||||
|
if os.Getenv("CLI_TEMPLATE_REPANIC") != "" {
|
||||||
|
panic(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
err := t.Execute(w, data)
|
err := t.Execute(w, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If the writer is closed, t.Execute will fail, and there's nothing
|
if errDebug {
|
||||||
// we can do to recover.
|
|
||||||
if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" {
|
|
||||||
fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err)
|
fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Flush()
|
w.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,9 +129,9 @@ func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) {
|
|||||||
t.Fatalf("expected error from helpCommand.Action(), but got nil")
|
t.Fatalf("expected error from helpCommand.Action(), but got nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
exitErr, ok := err.(*ExitError)
|
exitErr, ok := err.(*exitError)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("expected ExitError from helpCommand.Action(), but instead got: %v", err.Error())
|
t.Fatalf("expected *exitError from helpCommand.Action(), but instead got: %v", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(exitErr.Error(), "No help topic for") {
|
if !strings.HasPrefix(exitErr.Error(), "No help topic for") {
|
||||||
@ -157,9 +157,9 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) {
|
|||||||
t.Fatalf("expected error from helpCommand.Action(), but got nil")
|
t.Fatalf("expected error from helpCommand.Action(), but got nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
exitErr, ok := err.(*ExitError)
|
exitErr, ok := err.(*exitError)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("expected ExitError from helpCommand.Action(), but instead got: %v", err.Error())
|
t.Fatalf("expected *exitError from helpCommand.Action(), but instead got: %v", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(exitErr.Error(), "No help topic for") {
|
if !strings.HasPrefix(exitErr.Error(), "No help topic for") {
|
||||||
|
@ -12,6 +12,10 @@ var (
|
|||||||
wd, _ = os.Getwd()
|
wd, _ = os.Getwd()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
os.Setenv("CLI_TEMPLATE_REPANIC", "1")
|
||||||
|
}
|
||||||
|
|
||||||
func expect(t *testing.T, a interface{}, b interface{}) {
|
func expect(t *testing.T, a interface{}, b interface{}) {
|
||||||
_, fn, line, _ := runtime.Caller(1)
|
_, fn, line, _ := runtime.Caller(1)
|
||||||
fn = strings.Replace(fn, wd+"/", "", -1)
|
fn = strings.Replace(fn, wd+"/", "", -1)
|
||||||
|
Loading…
Reference in New Issue
Block a user