Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: urfave/cli
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v2.13.0
Choose a base ref
...
head repository: urfave/cli
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v2.14.0
Choose a head ref
  • 3 commits
  • 8 files changed
  • 3 contributors

Commits on Aug 30, 2022

  1. Verified

    This commit was signed with the committer’s verified signature.
    kzaher Krunoslav Zaher
    Copy the full SHA
    514a114 View commit details
  2. Fix:(issue_1293) Wrap usage text for commands (#1460)

    * Fix:(issue_1293) Wrap usage text for commands
    
    * Change way to calculate offset
    
    * Make test case more robust
    
    * Remove add function
    
    * Add comment
    dearchap authored Aug 30, 2022
    Copy the full SHA
    254c38e View commit details
  3. Add App.InvalidFlagAccessHandler (#1446)

    * Add App.UnknownFlagHandler
    
    * Rename App.UnknownFlagHandler to App.InvalidFlagAccessHandler
    
    * Traverse parent contexts
    icholy authored Aug 30, 2022
    Copy the full SHA
    ca9df40 View commit details
Showing with 135 additions and 12 deletions.
  1. +2 −0 app.go
  2. +14 −1 context.go
  3. +38 −0 context_test.go
  4. +1 −1 docs/index.md
  5. +3 −0 funcs.go
  6. +37 −6 help.go
  7. +36 −0 help_test.go
  8. +4 −4 template.go
2 changes: 2 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
@@ -78,6 +78,8 @@ type App struct {
CommandNotFound CommandNotFoundFunc
// Execute this function if a usage error occurs
OnUsageError OnUsageErrorFunc
// Execute this function when an invalid flag is accessed from the context
InvalidFlagAccessHandler InvalidFlagAccessFunc
// Compilation date
Compiled time.Time
// List of all authors who contributed
15 changes: 14 additions & 1 deletion context.go
Original file line number Diff line number Diff line change
@@ -46,6 +46,9 @@ func (cCtx *Context) NumFlags() int {

// Set sets a context flag to a value.
func (cCtx *Context) Set(name, value string) error {
if cCtx.flagSet.Lookup(name) == nil {
cCtx.onInvalidFlag(name)
}
return cCtx.flagSet.Set(name, value)
}

@@ -158,7 +161,7 @@ func (cCtx *Context) lookupFlagSet(name string) *flag.FlagSet {
return c.flagSet
}
}

cCtx.onInvalidFlag(name)
return nil
}

@@ -190,6 +193,16 @@ func (cCtx *Context) checkRequiredFlags(flags []Flag) requiredFlagsErr {
return nil
}

func (cCtx *Context) onInvalidFlag(name string) {
for cCtx != nil {
if cCtx.App != nil && cCtx.App.InvalidFlagAccessHandler != nil {
cCtx.App.InvalidFlagAccessHandler(cCtx, name)
break
}
cCtx = cCtx.parentContext
}
}

func makeFlagNameVisitor(names *[]string) func(*flag.Flag) {
return func(f *flag.Flag) {
nameParts := strings.Split(f.Name, ",")
38 changes: 38 additions & 0 deletions context_test.go
Original file line number Diff line number Diff line change
@@ -150,6 +150,31 @@ func TestContext_Value(t *testing.T) {
expect(t, c.Value("unknown-flag"), nil)
}

func TestContext_Value_InvalidFlagAccessHandler(t *testing.T) {
var flagName string
app := &App{
InvalidFlagAccessHandler: func(_ *Context, name string) {
flagName = name
},
Commands: []*Command{
{
Name: "command",
Subcommands: []*Command{
{
Name: "subcommand",
Action: func(ctx *Context) error {
ctx.Value("missing")
return nil
},
},
},
},
},
}
expect(t, app.Run([]string{"run", "command", "subcommand"}), nil)
expect(t, flagName, "missing")
}

func TestContext_Args(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("myflag", false, "doc")
@@ -258,6 +283,19 @@ func TestContext_Set(t *testing.T) {
expect(t, c.IsSet("int"), true)
}

func TestContext_Set_InvalidFlagAccessHandler(t *testing.T) {
set := flag.NewFlagSet("test", 0)
var flagName string
app := &App{
InvalidFlagAccessHandler: func(_ *Context, name string) {
flagName = name
},
}
c := NewContext(app, set, nil)
c.Set("missing", "")
expect(t, flagName, "missing")
}

func TestContext_LocalFlagNames(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("one-flag", false, "doc")
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ an expressive way.

These are the guides for each major supported version:

- [`v2`](./v2/)
- [`v2`](./v2/getting-started)
- [`v1`](./v1/getting-started)

In addition to the version-specific guides, these other documents are available:
3 changes: 3 additions & 0 deletions funcs.go
Original file line number Diff line number Diff line change
@@ -23,6 +23,9 @@ type CommandNotFoundFunc func(*Context, string)
// is displayed and the execution is interrupted.
type OnUsageErrorFunc func(cCtx *Context, err error, isSubcommand bool) error

// InvalidFlagAccessFunc is executed when an invalid flag is accessed from the context.
type InvalidFlagAccessFunc func(*Context, string)

// ExitErrHandlerFunc is executed if provided in order to handle exitError values
// returned by Actions and Before/After functions.
type ExitErrHandlerFunc func(cCtx *Context, err error)
43 changes: 37 additions & 6 deletions help.go
Original file line number Diff line number Diff line change
@@ -295,12 +295,14 @@ func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs
const maxLineLength = 10000

funcMap := template.FuncMap{
"join": strings.Join,
"indent": indent,
"nindent": nindent,
"trim": strings.TrimSpace,
"wrap": func(input string, offset int) string { return wrap(input, offset, maxLineLength) },
"offset": offset,
"join": strings.Join,
"subtract": subtract,
"indent": indent,
"nindent": nindent,
"trim": strings.TrimSpace,
"wrap": func(input string, offset int) string { return wrap(input, offset, maxLineLength) },
"offset": offset,
"offsetCommands": offsetCommands,
}

if customFuncs["wrapAt"] != nil {
@@ -416,6 +418,10 @@ func checkCommandCompletions(c *Context, name string) bool {
return true
}

func subtract(a, b int) int {
return a - b
}

func indent(spaces int, v string) string {
pad := strings.Repeat(" ", spaces)
return pad + strings.Replace(v, "\n", "\n"+pad, -1)
@@ -476,3 +482,28 @@ func wrapLine(input string, offset int, wrapAt int, padding string) string {
func offset(input string, fixed int) int {
return len(input) + fixed
}

// this function tries to find the max width of the names column
// so say we have the following rows for help
//
// foo1, foo2, foo3 some string here
// bar1, b2 some other string here
//
// We want to offset the 2nd row usage by some amount so that everything
// is aligned
//
// foo1, foo2, foo3 some string here
// bar1, b2 some other string here
//
// to find that offset we find the length of all the rows and use the max
// to calculate the offset
func offsetCommands(cmds []*Command, fixed int) int {
var max int = 0
for _, cmd := range cmds {
s := strings.Join(cmd.Names(), ", ")
if len(s) > max {
max = len(s)
}
}
return max + fixed
}
36 changes: 36 additions & 0 deletions help_test.go
Original file line number Diff line number Diff line change
@@ -895,6 +895,42 @@ App UsageText`,
}
}

func TestShowAppHelp_CommandMultiLine_UsageText(t *testing.T) {
app := &App{
UsageText: `This is a
multi
line
App UsageText`,
Commands: []*Command{
{
Name: "frobbly",
Aliases: []string{"frb1", "frbb2", "frl2"},
Usage: "this is a long help output for the run command, long usage \noutput, long usage output, long usage output, long usage output\noutput, long usage output, long usage output",
},
{
Name: "grobbly",
Aliases: []string{"grb1", "grbb2"},
Usage: "this is another long help output for the run command, long usage \noutput, long usage output",
},
},
}

output := &bytes.Buffer{}
app.Writer = output

_ = app.Run([]string{"foo"})

expected := "COMMANDS:\n" +
" frobbly, frb1, frbb2, frl2 this is a long help output for the run command, long usage \n" +
" output, long usage output, long usage output, long usage output\n" +
" output, long usage output, long usage output\n" +
" grobbly, grb1, grbb2 this is another long help output for the run command, long usage \n" +
" output, long usage output"
if !strings.Contains(output.String(), expected) {
t.Errorf("expected output to include usage text; got: %q", output.String())
}
}

func TestHideHelpCommand(t *testing.T) {
app := &App{
HideHelpCommand: true,
8 changes: 4 additions & 4 deletions template.go
Original file line number Diff line number Diff line change
@@ -21,8 +21,8 @@ AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}}
{{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}}
GLOBAL OPTIONS:{{range .VisibleFlagCategories}}
{{if .Name}}{{.Name}}
@@ -76,8 +76,8 @@ DESCRIPTION:
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}}
{{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
OPTIONS:
{{range .VisibleFlags}}{{.}}