Merge remote-tracking branch 'origin/main' into applying-pr1218

This commit is contained in:
Dan Buch 2022-05-22 08:53:47 -04:00
commit e66ce91db1
Signed by: meatballhat
GPG Key ID: A12F782281063434
46 changed files with 868 additions and 290 deletions

View File

@ -37,7 +37,7 @@ jobs:
- name: vet - name: vet
run: go run internal/build/build.go vet run: go run internal/build/build.go vet
- name: test with tags - name: test with urfave_cli_no_docs tag
run: go run internal/build/build.go -tags urfave_cli_no_docs test run: go run internal/build/build.go -tags urfave_cli_no_docs test
- name: test - name: test
@ -47,7 +47,7 @@ jobs:
run: go run internal/build/build.go check-binary-size run: go run internal/build/build.go check-binary-size
- name: check-binary-size with tags (informational only) - name: check-binary-size with tags (informational only)
run: go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size || true run: go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
if: success() && matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest' if: success() && matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest'
@ -76,19 +76,37 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Install Dependencies - name: Install Dependencies
run: run: |
mkdir -p "${GITHUB_WORKSPACE}/.local/bin" && mkdir -p "${GITHUB_WORKSPACE}/.local/bin"
curl -fsSL -o "${GITHUB_WORKSPACE}/.local/bin/gfmrun" "https://github.com/urfave/gfmrun/releases/download/v1.3.0/gfmrun-$(go env GOOS)-$(go env GOARCH)-v1.3.0" && curl -fsSL -o "${GITHUB_WORKSPACE}/.local/bin/gfmrun" "https://github.com/urfave/gfmrun/releases/download/v1.3.0/gfmrun-$(go env GOOS)-$(go env GOARCH)-v1.3.0"
chmod +x "${GITHUB_WORKSPACE}/.local/bin/gfmrun" && chmod +x "${GITHUB_WORKSPACE}/.local/bin/gfmrun"
npm install -g markdown-toc@1.2.0
- name: gfmrun - name: gfmrun
run: go run internal/build/build.go gfmrun docs/v2/manual.md run: go run internal/build/build.go gfmrun docs/v2/manual.md
- name: toc
run: go run internal/build/build.go toc docs/v2/manual.md
- name: diff check - name: diff check
run: | run: |
git diff --exit-code git diff --exit-code
git diff --cached --exit-code git diff --cached --exit-code
publish:
if: startswith(github.ref, 'refs/tags/')
name: publish
needs: [test-docs]
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup mkdocs
run: |
pip install -U pip
pip install -r mkdocs-requirements.txt
git remote rm origin
git remote add origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/urfave/cli.git
- name: Publish Docs
run: |
mkdocs gh-deploy --force

2
.gitignore vendored
View File

@ -1,10 +1,10 @@
*.coverprofile *.coverprofile
*.orig *.orig
node_modules/
vendor vendor
.idea .idea
internal/*/built-example internal/*/built-example
coverage.txt coverage.txt
/.local/ /.local/
/site/
*.exe *.exe

View File

@ -5,7 +5,7 @@
# attention on files that are primarily Go. # attention on files that are primarily Go.
.PHONY: all .PHONY: all
all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun toc v2diff all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun v2diff
# NOTE: this is a special catch-all rule to run any of the commands # NOTE: this is a special catch-all rule to run any of the commands
# defined in internal/build/build.go with optional arguments passed # defined in internal/build/build.go with optional arguments passed
@ -27,6 +27,14 @@ tag-check-binary-size:
gfmrun: gfmrun:
go run internal/build/build.go gfmrun docs/v2/manual.md go run internal/build/build.go gfmrun docs/v2/manual.md
.PHONY: toc .PHONY: docs
toc: docs:
go run internal/build/build.go toc docs/v2/manual.md mkdocs build
.PHONY: docs-deps
docs-deps:
pip install -r mkdocs-requirements.txt
.PHONY: serve-docs
serve-docs:
mkdocs serve

View File

@ -63,7 +63,7 @@ You can use the following build tags:
When set, this removes `ToMarkdown` and `ToMan` methods, so your application When set, this removes `ToMarkdown` and `ToMan` methods, so your application
won't be able to call those. This reduces the resulting binary size by about won't be able to call those. This reduces the resulting binary size by about
300-400 KB (measured using Go 1.18.1 on Linux/amd64), due to less dependencies. 300-400 KB (measured using Go 1.18.1 on Linux/amd64), due to fewer dependencies.
### GOPATH ### GOPATH

12
app.go
View File

@ -94,6 +94,8 @@ type App struct {
// single-character bool arguments into one // single-character bool arguments into one
// i.e. foobar -o -v -> foobar -ov // i.e. foobar -o -v -> foobar -ov
UseShortOptionHandling bool UseShortOptionHandling bool
// Enable suggestions for commands and flags
Suggest bool
didSetup bool didSetup bool
} }
@ -264,6 +266,11 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
return err return err
} }
_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
if a.Suggest {
if suggestion, err := a.suggestFlagFromError(err, ""); err == nil {
fmt.Fprintf(a.Writer, suggestion)
}
}
_ = ShowAppHelp(cCtx) _ = ShowAppHelp(cCtx)
return err return err
} }
@ -383,6 +390,11 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
return err return err
} }
_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
if a.Suggest {
if suggestion, err := a.suggestFlagFromError(err, cCtx.Command.Name); err == nil {
fmt.Fprintf(a.Writer, suggestion)
}
}
_ = ShowSubcommandHelp(cCtx) _ = ShowSubcommandHelp(cCtx)
return err return err
} }

View File

@ -119,6 +119,11 @@ func (c *Command) Run(ctx *Context) (err error) {
} }
_, _ = fmt.Fprintln(cCtx.App.Writer, "Incorrect Usage:", err.Error()) _, _ = fmt.Fprintln(cCtx.App.Writer, "Incorrect Usage:", err.Error())
_, _ = fmt.Fprintln(cCtx.App.Writer) _, _ = fmt.Fprintln(cCtx.App.Writer)
if ctx.App.Suggest {
if suggestion, err := ctx.App.suggestFlagFromError(err, c.Name); err == nil {
fmt.Fprintf(cCtx.App.Writer, suggestion)
}
}
_ = ShowCommandHelp(cCtx, c.Name) _ = ShowCommandHelp(cCtx, c.Name)
return err return err
} }
@ -249,6 +254,7 @@ func (c *Command) startApp(ctx *Context) error {
app.ErrWriter = ctx.App.ErrWriter app.ErrWriter = ctx.App.ErrWriter
app.ExitErrHandler = ctx.App.ExitErrHandler app.ExitErrHandler = ctx.App.ExitErrHandler
app.UseShortOptionHandling = ctx.App.UseShortOptionHandling app.UseShortOptionHandling = ctx.App.UseShortOptionHandling
app.Suggest = ctx.App.Suggest
app.categories = newCommandCategories() app.categories = newCommandCategories()
for _, command := range c.Subcommands { for _, command := range c.Subcommands {

1
docs/CNAME Normal file
View File

@ -0,0 +1 @@
cli.urfave.org

1
docs/CODE_OF_CONDUCT.md Symbolic link
View File

@ -0,0 +1 @@
../CODE_OF_CONDUCT.md

View File

@ -98,6 +98,28 @@ line help system which may be consulted for further information, e.g.:
go run internal/genflags/cmd/genflags/main.go --help go run internal/genflags/cmd/genflags/main.go --help
``` ```
#### docs output
The documentation in the `docs` directory is automatically built via `mkdocs` into a
static site and published when releases are pushed (see [RELEASING](./RELEASING/)). There
is no strict requirement to build the documentation when developing locally, but the
following `make` targets may be used if desired:
```sh
# install documentation dependencies with `pip`
make docs-deps
```
```sh
# build the static site in `./site`
make docs
```
```sh
# start an mkdocs development server
make serve-docs
```
### pull requests ### pull requests
Please feel free to open a pull request to fix a bug or add a feature. The @urfave/cli Please feel free to open a pull request to fix a bug or add a feature. The @urfave/cli

21
docs/index.md Normal file
View File

@ -0,0 +1,21 @@
# Welcome to urfave/cli
[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://pkg.go.dev/github.com/urfave/cli/v2)
[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli)
[![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli)
[![codecov](https://codecov.io/gh/urfave/cli/branch/main/graph/badge.svg)](https://codecov.io/gh/urfave/cli)
`urfave/cli` is a 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.
These are the guides for each major supported version:
- [`v2`](./v2/)
- [`v1`](./v1/)
In addition to the version-specific guides, these other documents are available:
- [CONTRIBUTING](./CONTRIBUTING/)
- [CODE OF CONDUCT](./CODE_OF_CONDUCT/)
- [RELEASING](./RELEASING/)

View File

@ -1,6 +1,4 @@
Migration Guide: v1 to v2 # Migration Guide: v1 to v2
===
v2 has a number of breaking changes but converting is relatively v2 has a number of breaking changes but converting is relatively
straightforward: make the changes documented below then resolve any straightforward: make the changes documented below then resolve any
@ -11,25 +9,6 @@ If you find any issues not covered by this document, please post a
comment on [Issue 921](https://github.com/urfave/cli/issues/921) or comment on [Issue 921](https://github.com/urfave/cli/issues/921) or
consider sending a PR to help improve this guide. consider sending a PR to help improve this guide.
<!-- toc -->
* [Flags before args](#flags-before-args)
* [Import string changed](#import-string-changed)
* [Flag aliases are done differently](#flag-aliases-are-done-differently)
* [EnvVar is now a list (EnvVars)](#envvar-is-now-a-list-envvars)
* [Actions returns errors](#actions-returns-errors)
* [cli.Flag changed](#cliflag-changed)
* [Commands are now lists of pointers](#commands-are-now-lists-of-pointers)
* [Lists of commands should be pointers](#lists-of-commands-should-be-pointers)
* [Appending Commands](#appending-commands)
* [GlobalString, GlobalBool and its likes are deprecated](#globalstring-globalbool-and-its-likes-are-deprecated)
* [BoolTFlag and BoolT are deprecated](#booltflag-and-boolt-are-deprecated)
* [&cli.StringSlice{""} replaced with cli.NewStringSlice("")](#clistringslice-replaced-with-clinewstringslice)
* [Replace deprecated functions](#replace-deprecated-functions)
* [Everything else](#everything-else)
<!-- tocstop -->
# Flags before args # Flags before args
In v2 flags must come before args. This is more POSIX-compliant. You In v2 flags must come before args. This is more POSIX-compliant. You

1
docs/v1/index.md Symbolic link
View File

@ -0,0 +1 @@
manual.md

View File

@ -1,35 +1,4 @@
cli v1 manual # v1 guide
===
<!-- toc -->
- [Getting Started](#getting-started)
- [Examples](#examples)
* [Arguments](#arguments)
* [Flags](#flags)
+ [Placeholder Values](#placeholder-values)
+ [Alternate Names](#alternate-names)
+ [Ordering](#ordering)
+ [Values from the Environment](#values-from-the-environment)
+ [Values from files](#values-from-files)
+ [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others)
+ [Precedence](#precedence)
* [Subcommands](#subcommands)
* [Subcommands categories](#subcommands-categories)
* [Exit code](#exit-code)
* [Combining short options](#combining-short-options)
* [Bash Completion](#bash-completion)
+ [Enabling](#enabling)
+ [Distribution](#distribution)
+ [Customization](#customization)
* [Generated Help Text](#generated-help-text)
+ [Customization](#customization-1)
* [Version Flag](#version-flag)
+ [Customization](#customization-2)
+ [Full API Example](#full-api-example)
* [Migrating to V2](#migrating-to-v2)
<!-- tocstop -->
## Getting Started ## Getting Started

1
docs/v2/index.md Symbolic link
View File

@ -0,0 +1 @@
manual.md

View File

@ -1,51 +1,4 @@
cli v2 manual # v2 guide
===
<!-- toc -->
- [Migrating From Older Releases](#migrating-from-older-releases)
- [Getting Started](#getting-started)
- [Examples](#examples)
* [Arguments](#arguments)
* [Flags](#flags)
+ [Placeholder Values](#placeholder-values)
+ [Alternate Names](#alternate-names)
+ [Ordering](#ordering)
+ [Values from the Environment](#values-from-the-environment)
+ [Values from files](#values-from-files)
+ [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others)
+ [Required Flags](#required-flags)
+ [Default Values for help output](#default-values-for-help-output)
+ [Precedence](#precedence)
* [Subcommands](#subcommands)
* [Subcommands categories](#subcommands-categories)
* [Exit code](#exit-code)
* [Combining short options](#combining-short-options)
* [Bash Completion](#bash-completion)
+ [Default auto-completion](#default-auto-completion)
+ [Custom auto-completion](#custom-auto-completion)
+ [Enabling](#enabling)
+ [Distribution and Persistent Autocompletion](#distribution-and-persistent-autocompletion)
+ [Customization](#customization)
+ [ZSH Support](#zsh-support)
+ [ZSH default auto-complete example](#zsh-default-auto-complete-example)
+ [ZSH custom auto-complete example](#zsh-custom-auto-complete-example)
+ [PowerShell Support](#powershell-support)
* [Generated Help Text](#generated-help-text)
+ [Customization](#customization-1)
* [Version Flag](#version-flag)
+ [Customization](#customization-2)
* [Timestamp Flag](#timestamp-flag)
* [Full API Example](#full-api-example)
<!-- tocstop -->
## Migrating From Older Releases
There are a small set of breaking changes between v1 and v2.
Converting is relatively straightforward and typically takes less than
an hour. Specific steps are included in
[Migration Guide: v1 to v2](../migrate-v1-to-v2.md). Also see the [pkg.go.dev docs](https://pkg.go.dev/github.com/urfave/cli/v2) for v2 API documentation.
## Getting Started ## Getting Started
@ -1456,6 +1409,13 @@ In this example the flag could be used like this :
Side note: quotes may be necessary around the date depending on your layout (if you have spaces for instance) Side note: quotes may be necessary around the date depending on your layout (if you have spaces for instance)
### Suggestions
To enable flag and command suggestions, set `app.Suggest = true`. If the suggest
feature is enabled, then the help output of the corresponding command will
provide an appropriate suggestion for the provided flag or subcommand if
available.
### Full API Example ### Full API Example
**Notice**: This is a contrived (functioning) example meant strictly for API **Notice**: This is a contrived (functioning) example meant strictly for API
@ -1713,3 +1673,11 @@ func wopAction(c *cli.Context) error {
return nil return nil
} }
``` ```
## Migrating From Older Releases
There are a small set of breaking changes between v1 and v2.
Converting is relatively straightforward and typically takes less than
an hour. Specific steps are included in
[Migration Guide: v1 to v2](../migrate-v1-to-v2.md). Also see the [pkg.go.dev docs](https://pkg.go.dev/github.com/urfave/cli/v2) for v2 API documentation.

View File

@ -6,11 +6,6 @@ import (
"strconv" "strconv"
) )
// IsRequired returns whether or not the flag is required
func (f *BoolFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false // TakesValue returns true of the flag takes a value, otherwise false
func (f *BoolFlag) TakesValue() bool { func (f *BoolFlag) TakesValue() bool {
return false return false
@ -27,11 +22,6 @@ func (f *BoolFlag) GetValue() string {
return "" return ""
} }
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *BoolFlag) IsVisible() bool {
return !f.Hidden
}
// GetDefaultText returns the default text for this flag // GetDefaultText returns the default text for this flag
func (f *BoolFlag) GetDefaultText() string { func (f *BoolFlag) GetDefaultText() string {
if f.DefaultText != "" { if f.DefaultText != "" {

View File

@ -6,11 +6,6 @@ import (
"time" "time"
) )
// IsRequired returns whether or not the flag is required
func (f *DurationFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false // TakesValue returns true of the flag takes a value, otherwise false
func (f *DurationFlag) TakesValue() bool { func (f *DurationFlag) TakesValue() bool {
return true return true
@ -27,11 +22,6 @@ func (f *DurationFlag) GetValue() string {
return f.Value.String() return f.Value.String()
} }
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *DurationFlag) IsVisible() bool {
return !f.Hidden
}
// GetDefaultText returns the default text for this flag // GetDefaultText returns the default text for this flag
func (f *DurationFlag) GetDefaultText() string { func (f *DurationFlag) GetDefaultText() string {
if f.DefaultText != "" { if f.DefaultText != "" {

View File

@ -6,11 +6,6 @@ import (
"strconv" "strconv"
) )
// IsRequired returns whether or not the flag is required
func (f *Float64Flag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false // TakesValue returns true of the flag takes a value, otherwise false
func (f *Float64Flag) TakesValue() bool { func (f *Float64Flag) TakesValue() bool {
return true return true
@ -40,11 +35,6 @@ func (f *Float64Flag) GetEnvVars() []string {
return f.EnvVars return f.EnvVars
} }
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *Float64Flag) IsVisible() bool {
return !f.Hidden
}
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f *Float64Flag) Apply(set *flag.FlagSet) error { func (f *Float64Flag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {

View File

@ -81,11 +81,6 @@ func (f *Float64SliceFlag) String() string {
return withEnvHint(f.GetEnvVars(), stringifyFloat64SliceFlag(f)) return withEnvHint(f.GetEnvVars(), stringifyFloat64SliceFlag(f))
} }
// IsRequired returns whether or not the flag is required
func (f *Float64SliceFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true if the flag takes a value, otherwise false // TakesValue returns true if the flag takes a value, otherwise false
func (f *Float64SliceFlag) TakesValue() bool { func (f *Float64SliceFlag) TakesValue() bool {
return true return true
@ -105,11 +100,6 @@ func (f *Float64SliceFlag) GetValue() string {
return "" return ""
} }
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *Float64SliceFlag) IsVisible() bool {
return !f.Hidden
}
// GetDefaultText returns the default text for this flag // GetDefaultText returns the default text for this flag
func (f *Float64SliceFlag) GetDefaultText() string { func (f *Float64SliceFlag) GetDefaultText() string {
if f.DefaultText != "" { if f.DefaultText != "" {

View File

@ -11,11 +11,6 @@ type Generic interface {
String() string String() string
} }
// IsRequired returns whether or not the flag is required
func (f *GenericFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false // TakesValue returns true of the flag takes a value, otherwise false
func (f *GenericFlag) TakesValue() bool { func (f *GenericFlag) TakesValue() bool {
return true return true
@ -35,11 +30,6 @@ func (f *GenericFlag) GetValue() string {
return "" return ""
} }
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *GenericFlag) IsVisible() bool {
return !f.Hidden
}
// GetDefaultText returns the default text for this flag // GetDefaultText returns the default text for this flag
func (f *GenericFlag) GetDefaultText() string { func (f *GenericFlag) GetDefaultText() string {
if f.DefaultText != "" { if f.DefaultText != "" {

View File

@ -6,11 +6,6 @@ import (
"strconv" "strconv"
) )
// IsRequired returns whether or not the flag is required
func (f *IntFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false // TakesValue returns true of the flag takes a value, otherwise false
func (f *IntFlag) TakesValue() bool { func (f *IntFlag) TakesValue() bool {
return true return true
@ -27,11 +22,6 @@ func (f *IntFlag) GetValue() string {
return fmt.Sprintf("%d", f.Value) return fmt.Sprintf("%d", f.Value)
} }
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *IntFlag) IsVisible() bool {
return !f.Hidden
}
// GetDefaultText returns the default text for this flag // GetDefaultText returns the default text for this flag
func (f *IntFlag) GetDefaultText() string { func (f *IntFlag) GetDefaultText() string {
if f.DefaultText != "" { if f.DefaultText != "" {

View File

@ -6,11 +6,6 @@ import (
"strconv" "strconv"
) )
// IsRequired returns whether or not the flag is required
func (f *Int64Flag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false // TakesValue returns true of the flag takes a value, otherwise false
func (f *Int64Flag) TakesValue() bool { func (f *Int64Flag) TakesValue() bool {
return true return true
@ -27,11 +22,6 @@ func (f *Int64Flag) GetValue() string {
return fmt.Sprintf("%d", f.Value) return fmt.Sprintf("%d", f.Value)
} }
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *Int64Flag) IsVisible() bool {
return !f.Hidden
}
// GetDefaultText returns the default text for this flag // GetDefaultText returns the default text for this flag
func (f *Int64Flag) GetDefaultText() string { func (f *Int64Flag) GetDefaultText() string {
if f.DefaultText != "" { if f.DefaultText != "" {

View File

@ -82,11 +82,6 @@ func (f *Int64SliceFlag) String() string {
return withEnvHint(f.GetEnvVars(), stringifyInt64SliceFlag(f)) return withEnvHint(f.GetEnvVars(), stringifyInt64SliceFlag(f))
} }
// IsRequired returns whether or not the flag is required
func (f *Int64SliceFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false // TakesValue returns true of the flag takes a value, otherwise false
func (f *Int64SliceFlag) TakesValue() bool { func (f *Int64SliceFlag) TakesValue() bool {
return true return true
@ -106,11 +101,6 @@ func (f *Int64SliceFlag) GetValue() string {
return "" return ""
} }
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *Int64SliceFlag) IsVisible() bool {
return !f.Hidden
}
// GetDefaultText returns the default text for this flag // GetDefaultText returns the default text for this flag
func (f *Int64SliceFlag) GetDefaultText() string { func (f *Int64SliceFlag) GetDefaultText() string {
if f.DefaultText != "" { if f.DefaultText != "" {

View File

@ -93,11 +93,6 @@ func (f *IntSliceFlag) String() string {
return withEnvHint(f.GetEnvVars(), stringifyIntSliceFlag(f)) return withEnvHint(f.GetEnvVars(), stringifyIntSliceFlag(f))
} }
// IsRequired returns whether or not the flag is required
func (f *IntSliceFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false // TakesValue returns true of the flag takes a value, otherwise false
func (f *IntSliceFlag) TakesValue() bool { func (f *IntSliceFlag) TakesValue() bool {
return true return true
@ -117,11 +112,6 @@ func (f *IntSliceFlag) GetValue() string {
return "" return ""
} }
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *IntSliceFlag) IsVisible() bool {
return !f.Hidden
}
// GetDefaultText returns the default text for this flag // GetDefaultText returns the default text for this flag
func (f *IntSliceFlag) GetDefaultText() string { func (f *IntSliceFlag) GetDefaultText() string {
if f.DefaultText != "" { if f.DefaultText != "" {

View File

@ -7,11 +7,6 @@ import (
type Path = string type Path = string
// IsRequired returns whether or not the flag is required
func (f *PathFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false // TakesValue returns true of the flag takes a value, otherwise false
func (f *PathFlag) TakesValue() bool { func (f *PathFlag) TakesValue() bool {
return true return true
@ -28,11 +23,6 @@ func (f *PathFlag) GetValue() string {
return f.Value return f.Value
} }
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *PathFlag) IsVisible() bool {
return !f.Hidden
}
// GetDefaultText returns the default text for this flag // GetDefaultText returns the default text for this flag
func (f *PathFlag) GetDefaultText() string { func (f *PathFlag) GetDefaultText() string {
if f.DefaultText != "" { if f.DefaultText != "" {

View File

@ -5,11 +5,6 @@ import (
"fmt" "fmt"
) )
// IsRequired returns whether or not the flag is required
func (f *StringFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false // TakesValue returns true of the flag takes a value, otherwise false
func (f *StringFlag) TakesValue() bool { func (f *StringFlag) TakesValue() bool {
return true return true
@ -26,11 +21,6 @@ func (f *StringFlag) GetValue() string {
return f.Value return f.Value
} }
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *StringFlag) IsVisible() bool {
return !f.Hidden
}
// GetDefaultText returns the default text for this flag // GetDefaultText returns the default text for this flag
func (f *StringFlag) GetDefaultText() string { func (f *StringFlag) GetDefaultText() string {
if f.DefaultText != "" { if f.DefaultText != "" {

View File

@ -76,11 +76,6 @@ func (f *StringSliceFlag) String() string {
return withEnvHint(f.GetEnvVars(), stringifyStringSliceFlag(f)) return withEnvHint(f.GetEnvVars(), stringifyStringSliceFlag(f))
} }
// IsRequired returns whether or not the flag is required
func (f *StringSliceFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false // TakesValue returns true of the flag takes a value, otherwise false
func (f *StringSliceFlag) TakesValue() bool { func (f *StringSliceFlag) TakesValue() bool {
return true return true
@ -100,11 +95,6 @@ func (f *StringSliceFlag) GetValue() string {
return "" return ""
} }
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *StringSliceFlag) IsVisible() bool {
return !f.Hidden
}
// GetDefaultText returns the default text for this flag // GetDefaultText returns the default text for this flag
func (f *StringSliceFlag) GetDefaultText() string { func (f *StringSliceFlag) GetDefaultText() string {
if f.DefaultText != "" { if f.DefaultText != "" {

View File

@ -58,11 +58,6 @@ func (t *Timestamp) Get() interface{} {
return *t return *t
} }
// IsRequired returns whether or not the flag is required
func (f *TimestampFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false // TakesValue returns true of the flag takes a value, otherwise false
func (f *TimestampFlag) TakesValue() bool { func (f *TimestampFlag) TakesValue() bool {
return true return true
@ -82,11 +77,6 @@ func (f *TimestampFlag) GetValue() string {
return "" return ""
} }
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *TimestampFlag) IsVisible() bool {
return !f.Hidden
}
// GetDefaultText returns the default text for this flag // GetDefaultText returns the default text for this flag
func (f *TimestampFlag) GetDefaultText() string { func (f *TimestampFlag) GetDefaultText() string {
if f.DefaultText != "" { if f.DefaultText != "" {

View File

@ -6,11 +6,6 @@ import (
"strconv" "strconv"
) )
// IsRequired returns whether or not the flag is required
func (f *UintFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false // TakesValue returns true of the flag takes a value, otherwise false
func (f *UintFlag) TakesValue() bool { func (f *UintFlag) TakesValue() bool {
return true return true
@ -21,11 +16,6 @@ func (f *UintFlag) GetUsage() string {
return f.Usage return f.Usage
} }
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *UintFlag) IsVisible() bool {
return !f.Hidden
}
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f *UintFlag) Apply(set *flag.FlagSet) error { func (f *UintFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {

View File

@ -6,11 +6,6 @@ import (
"strconv" "strconv"
) )
// IsRequired returns whether or not the flag is required
func (f *Uint64Flag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false // TakesValue returns true of the flag takes a value, otherwise false
func (f *Uint64Flag) TakesValue() bool { func (f *Uint64Flag) TakesValue() bool {
return true return true
@ -21,11 +16,6 @@ func (f *Uint64Flag) GetUsage() string {
return f.Usage return f.Usage
} }
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *Uint64Flag) IsVisible() bool {
return !f.Hidden
}
// Apply populates the flag given the flag set and environment // Apply populates the flag given the flag set and environment
func (f *Uint64Flag) Apply(set *flag.FlagSet) error { func (f *Uint64Flag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {

1
go.mod
View File

@ -4,6 +4,7 @@ go 1.18
require ( require (
github.com/BurntSushi/toml v1.1.0 github.com/BurntSushi/toml v1.1.0
github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0
github.com/cpuguy83/go-md2man/v2 v2.0.1 github.com/cpuguy83/go-md2man/v2 v2.0.1
golang.org/x/text v0.3.7 golang.org/x/text v0.3.7
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0

8
go.sum
View File

@ -1,9 +1,17 @@
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/antzucaro/matchr v0.0.0-20180616170659-cbc221335f3c h1:CucViv7orgFBMkehuFFdkCVF5ERovbkRRyhvaYaHu/k=
github.com/antzucaro/matchr v0.0.0-20180616170659-cbc221335f3c/go.mod h1:bV/CkX4+ANGDaBwbHkt9kK287al/i9BsB18PRBvyqYo=
github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0 h1:R/qAiUxFT3mNgQaNqJe0IVznjKRNm23ohAIh9lgtlzc=
github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0/go.mod h1:v3ZDlfVAL1OrkKHbGSFFK60k0/7hruHPDq2XMs9Gu6U=
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=

View File

@ -305,6 +305,8 @@ type App struct {
// single-character bool arguments into one // single-character bool arguments into one
// i.e. foobar -o -v -> foobar -ov // i.e. foobar -o -v -> foobar -ov
UseShortOptionHandling bool UseShortOptionHandling bool
// Enable suggestions for commands and flags
Suggest bool
// Has unexported fields. // Has unexported fields.
} }

21
help.go
View File

@ -10,9 +10,14 @@ import (
"unicode/utf8" "unicode/utf8"
) )
const (
helpName = "help"
helpAlias = "h"
)
var helpCommand = &Command{ var helpCommand = &Command{
Name: "help", Name: helpName,
Aliases: []string{"h"}, Aliases: []string{helpAlias},
Usage: "Shows a list of commands or help for one command", Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]", ArgsUsage: "[command]",
Action: func(cCtx *Context) error { Action: func(cCtx *Context) error {
@ -27,8 +32,8 @@ var helpCommand = &Command{
} }
var helpSubcommand = &Command{ var helpSubcommand = &Command{
Name: "help", Name: helpName,
Aliases: []string{"h"}, Aliases: []string{helpAlias},
Usage: "Shows a list of commands or help for one command", Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]", ArgsUsage: "[command]",
Action: func(cCtx *Context) error { Action: func(cCtx *Context) error {
@ -214,7 +219,13 @@ func ShowCommandHelp(ctx *Context, command string) error {
} }
if ctx.App.CommandNotFound == nil { if ctx.App.CommandNotFound == nil {
return Exit(fmt.Sprintf("No help topic for '%v'", command), 3) errMsg := fmt.Sprintf("No help topic for '%v'", command)
if ctx.App.Suggest {
if suggestion := suggestCommand(ctx.App.Commands, command); suggestion != "" {
errMsg += ". " + suggestion
}
}
return Exit(errMsg, 3)
} }
ctx.App.CommandNotFound(ctx, command) ctx.App.CommandNotFound(ctx, command)

View File

@ -63,10 +63,6 @@ func main() {
Name: "gfmrun", Name: "gfmrun",
Action: GfmrunActionFunc, Action: GfmrunActionFunc,
}, },
{
Name: "toc",
Action: TocActionFunc,
},
{ {
Name: "check-binary-size", Name: "check-binary-size",
Action: checkBinarySizeActionFunc, Action: checkBinarySizeActionFunc,
@ -216,15 +212,6 @@ func GfmrunActionFunc(c *cli.Context) error {
return runCmd("gfmrun", "-c", fmt.Sprint(counter), "-s", filename) return runCmd("gfmrun", "-c", fmt.Sprint(counter), "-s", filename)
} }
func TocActionFunc(c *cli.Context) error {
filename := c.Args().Get(0)
if filename == "" {
filename = "README.md"
}
return runCmd("markdown-toc", "-i", filename)
}
// checkBinarySizeActionFunc checks the size of an example binary to ensure that we are keeping size down // checkBinarySizeActionFunc checks the size of an example binary to ensure that we are keeping size down
// this was originally inspired by https://github.com/urfave/cli/issues/1055, and followed up on as a part // this was originally inspired by https://github.com/urfave/cli/issues/1055, and followed up on as a part
// of https://github.com/urfave/cli/issues/1057 // of https://github.com/urfave/cli/issues/1057

View File

@ -31,7 +31,7 @@ type {{.TypeName}} struct {
func (f *{{.TypeName}}) String() string { func (f *{{.TypeName}}) String() string {
return {{$.UrfaveCLINamespace}}FlagStringer(f) return {{$.UrfaveCLINamespace}}FlagStringer(f)
} }
{{end}} {{end}}{{/* /if .GenerateFmtStringerInterface */}}
{{if .GenerateFlagInterface}} {{if .GenerateFlagInterface}}
// IsSet returns whether or not the flag has been set through env or file // IsSet returns whether or not the flag has been set through env or file
@ -45,6 +45,20 @@ func (f *{{.TypeName}}) Names() []string {
} }
{{end}}{{/* /if .GenerateFlagInterface */}} {{end}}{{/* /if .GenerateFlagInterface */}}
{{if .GenerateRequiredFlagInterface}}
// IsRequired returns whether or not the flag is required
func (f *{{.TypeName}}) IsRequired() bool {
return f.Required
}
{{end}}{{/* /if .GenerateRequiredFlagInterface */}}
{{if .GenerateVisibleFlagInterface}}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *{{.TypeName}}) IsVisible() bool {
return !f.Hidden
}
{{end}}{{/* /if .GenerateVisibleFlagInterface */}}
{{end}}{{/* /range .SortedFlagTypes */}} {{end}}{{/* /range .SortedFlagTypes */}}
// vim{{/* 👻 */}}:ro // vim{{/* 👻 */}}:ro

View File

@ -19,6 +19,22 @@ func Test{{.TypeName}}_SatisfiesFmtStringerInterface(t *testing.T) {
_ = f.String() _ = f.String()
} }
{{end}} {{end}}
{{if .GenerateRequiredFlagInterface}}
func Test{{.TypeName}}_SatisfiesRequiredFlagInterface(t *testing.T) {
var f {{$.UrfaveCLITestNamespace}}RequiredFlag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{}
_ = f.IsRequired()
}
{{end}}
{{if .GenerateVisibleFlagInterface}}
func Test{{.TypeName}}_SatisfiesVisibleFlagInterface(t *testing.T) {
var f {{$.UrfaveCLITestNamespace}}VisibleFlag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{}
_ = f.IsVisible()
}
{{end}}
{{end}} {{end}}
// vim{{/* 👻 */}}:ro // vim{{/* 👻 */}}:ro

View File

@ -83,6 +83,14 @@ func (ft *FlagType) GenerateFlagInterface() bool {
return ft.skipInterfaceNamed("Flag") return ft.skipInterfaceNamed("Flag")
} }
func (ft *FlagType) GenerateRequiredFlagInterface() bool {
return ft.skipInterfaceNamed("RequiredFlag")
}
func (ft *FlagType) GenerateVisibleFlagInterface() bool {
return ft.skipInterfaceNamed("VisibleFlag")
}
func (ft *FlagType) skipInterfaceNamed(name string) bool { func (ft *FlagType) skipInterfaceNamed(name string) bool {
if ft.Config == nil { if ft.Config == nil {
return true return true

5
mkdocs-requirements.txt Normal file
View File

@ -0,0 +1,5 @@
mkdocs-git-revision-date-localized-plugin~=1.0
mkdocs-material-extensions~=1.0
mkdocs-material~=8.2
mkdocs~=1.3
pygments~=2.12

62
mkdocs.yml Normal file
View File

@ -0,0 +1,62 @@
# NOTE: the mkdocs dependencies will need to be installed out of
# band until this whole thing gets more automated:
#
# pip install -r mkdocs-requirements.txt
#
site_name: urfave/cli
site_url: https://cli.urfave.org/
repo_url: https://github.com/urfave/cli
edit_uri: edit/main/docs/
nav:
- Home: index.md
- v2 Manual: v2/index.md
- v1 Manual: v1/index.md
theme:
name: material
palette:
- media: "(prefers-color-scheme: light)"
scheme: default
toggle:
icon: material/brightness-4
name: dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
toggle:
icon: material/brightness-7
name: light mode
plugins:
- git-revision-date-localized
- search
# NOTE: this is the recommended configuration from
# https://squidfunk.github.io/mkdocs-material/setup/extensions/#recommended-configuration
markdown_extensions:
- abbr
- admonition
- attr_list
- def_list
- footnotes
- meta
- md_in_html
- toc:
permalink: true
- pymdownx.arithmatex:
generic: true
- pymdownx.betterem:
smart_enable: all
- pymdownx.caret
- pymdownx.details
- pymdownx.emoji:
emoji_index: !!python/name:materialx.emoji.twemoji
emoji_generator: !!python/name:materialx.emoji.to_svg
- pymdownx.highlight
- pymdownx.inlinehilite
- pymdownx.keys
- pymdownx.mark
- pymdownx.smartsymbols
- pymdownx.superfences
- pymdownx.tabbed:
alternate_style: true
- pymdownx.tasklist:
custom_checkbox: true
- pymdownx.tilde

View File

@ -26,9 +26,8 @@ func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComple
return err return err
} }
errStr := err.Error() trimmed, trimErr := flagFromError(err)
trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: -") if trimErr != nil {
if errStr == trimmed {
return err return err
} }
@ -67,6 +66,19 @@ func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComple
} }
} }
const providedButNotDefinedErrMsg = "flag provided but not defined: -"
// flagFromError tries to parse a provided flag from an error message. If the
// parsing fials, it returns the input error and an empty string
func flagFromError(err error) (string, error) {
errStr := err.Error()
trimmed := strings.TrimPrefix(errStr, providedButNotDefinedErrMsg)
if errStr == trimmed {
return "", err
}
return trimmed, nil
}
func splitShortOptions(set *flag.FlagSet, arg string) []string { func splitShortOptions(set *flag.FlagSet, arg string) []string {
shortFlagsExist := func(s string) bool { shortFlagsExist := func(s string) bool {
for _, c := range s[1:] { for _, c := range s[1:] {

75
suggestions.go Normal file
View File

@ -0,0 +1,75 @@
package cli
import (
"fmt"
"github.com/antzucaro/matchr"
)
const didYouMeanTemplate = "Did you mean '%s'?"
func (a *App) suggestFlagFromError(err error, command string) (string, error) {
flag, parseErr := flagFromError(err)
if parseErr != nil {
return "", err
}
flags := a.Flags
if command != "" {
cmd := a.Command(command)
if cmd == nil {
return "", err
}
flags = cmd.Flags
}
suggestion := a.suggestFlag(flags, flag)
if len(suggestion) == 0 {
return "", err
}
return fmt.Sprintf(didYouMeanTemplate+"\n\n", suggestion), nil
}
func (a *App) suggestFlag(flags []Flag, provided string) (suggestion string) {
distance := 0.0
for _, flag := range flags {
flagNames := flag.Names()
if !a.HideHelp {
flagNames = append(flagNames, HelpFlag.Names()...)
}
for _, name := range flagNames {
newDistance := matchr.JaroWinkler(name, provided, true)
if newDistance > distance {
distance = newDistance
suggestion = name
}
}
}
if len(suggestion) == 1 {
suggestion = "-" + suggestion
} else if len(suggestion) > 1 {
suggestion = "--" + suggestion
}
return suggestion
}
// suggestCommand takes a list of commands and a provided string to suggest a
// command name
func suggestCommand(commands []*Command, provided string) (suggestion string) {
distance := 0.0
for _, command := range commands {
for _, name := range append(command.Names(), helpName, helpAlias) {
newDistance := matchr.JaroWinkler(name, provided, true)
if newDistance > distance {
distance = newDistance
suggestion = name
}
}
}
return fmt.Sprintf(didYouMeanTemplate, suggestion)
}

188
suggestions_test.go Normal file
View File

@ -0,0 +1,188 @@
package cli
import (
"errors"
"fmt"
"testing"
)
func TestSuggestFlag(t *testing.T) {
// Given
app := testApp()
for _, testCase := range []struct {
provided, expected string
}{
{"", ""},
{"a", "--another-flag"},
{"hlp", "--help"},
{"k", ""},
{"s", "-s"},
} {
// When
res := app.suggestFlag(app.Flags, testCase.provided)
// Then
expect(t, res, testCase.expected)
}
}
func TestSuggestFlagHideHelp(t *testing.T) {
// Given
app := testApp()
app.HideHelp = true
// When
res := app.suggestFlag(app.Flags, "hlp")
// Then
expect(t, res, "--fl")
}
func TestSuggestFlagFromError(t *testing.T) {
// Given
app := testApp()
for _, testCase := range []struct {
command, provided, expected string
}{
{"", "hel", "--help"},
{"", "soccer", "--socket"},
{"config", "anot", "--another-flag"},
} {
// When
res, _ := app.suggestFlagFromError(
errors.New(providedButNotDefinedErrMsg+testCase.provided),
testCase.command,
)
// Then
expect(t, res, fmt.Sprintf(didYouMeanTemplate+"\n\n", testCase.expected))
}
}
func TestSuggestFlagFromErrorWrongError(t *testing.T) {
// Given
app := testApp()
// When
_, err := app.suggestFlagFromError(errors.New("invalid"), "")
// Then
expect(t, true, err != nil)
}
func TestSuggestFlagFromErrorWrongCommand(t *testing.T) {
// Given
app := testApp()
// When
_, err := app.suggestFlagFromError(
errors.New(providedButNotDefinedErrMsg+"flag"),
"invalid",
)
// Then
expect(t, true, err != nil)
}
func TestSuggestFlagFromErrorNoSuggestion(t *testing.T) {
// Given
app := testApp()
// When
_, err := app.suggestFlagFromError(
errors.New(providedButNotDefinedErrMsg+""),
"",
)
// Then
expect(t, true, err != nil)
}
func TestSuggestCommand(t *testing.T) {
// Given
app := testApp()
for _, testCase := range []struct {
provided, expected string
}{
{"", ""},
{"conf", "config"},
{"i", "i"},
{"information", "info"},
{"not-existing", "info"},
} {
// When
res := suggestCommand(app.Commands, testCase.provided)
// Then
expect(t, res, fmt.Sprintf(didYouMeanTemplate, testCase.expected))
}
}
func ExampleApp_Suggest() {
app := &App{
Name: "greet",
Suggest: true,
HideHelp: true,
HideHelpCommand: true,
CustomAppHelpTemplate: "(this space intentionally left blank)\n",
Flags: []Flag{
&StringFlag{Name: "name", Value: "squirrel", Usage: "a name to say"},
},
Action: func(cCtx *Context) error {
fmt.Printf("Hello %v\n", cCtx.String("name"))
return nil
},
}
app.Run([]string{"greet", "--nema", "chipmunk"})
// Output:
// Incorrect Usage. flag provided but not defined: -nema
//
// Did you mean '--name'?
//
// (this space intentionally left blank)
}
func ExampleApp_Suggest_command() {
app := &App{
Name: "greet",
Suggest: true,
HideHelp: true,
HideHelpCommand: true,
CustomAppHelpTemplate: "(this space intentionally left blank)\n",
Flags: []Flag{
&StringFlag{Name: "name", Value: "squirrel", Usage: "a name to say"},
},
Action: func(cCtx *Context) error {
fmt.Printf("Hello %v\n", cCtx.String("name"))
return nil
},
Commands: []*Command{
{
Name: "neighbors",
CustomHelpTemplate: "(this space intentionally left blank)\n",
Flags: []Flag{
&BoolFlag{Name: "smiling"},
},
Action: func(cCtx *Context) error {
if cCtx.Bool("smiling") {
fmt.Println("😀")
}
fmt.Println("Hello, neighbors")
return nil
},
},
},
}
app.Run([]string{"greet", "neighbors", "--sliming"})
// Output:
// Incorrect Usage: flag provided but not defined: -sliming
//
// Did you mean '--smiling'?
//
// (this space intentionally left blank)
}

View File

@ -305,6 +305,8 @@ type App struct {
// single-character bool arguments into one // single-character bool arguments into one
// i.e. foobar -o -v -> foobar -ov // i.e. foobar -o -v -> foobar -ov
UseShortOptionHandling bool UseShortOptionHandling bool
// Enable suggestions for commands and flags
Suggest bool
// Has unexported fields. // Has unexported fields.
} }

View File

@ -33,6 +33,16 @@ func (f *Float64SliceFlag) Names() []string {
return FlagNames(f.Name, f.Aliases) return FlagNames(f.Name, f.Aliases)
} }
// IsRequired returns whether or not the flag is required
func (f *Float64SliceFlag) IsRequired() bool {
return f.Required
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *Float64SliceFlag) IsVisible() bool {
return !f.Hidden
}
// GenericFlag is a flag with type Generic // GenericFlag is a flag with type Generic
type GenericFlag struct { type GenericFlag struct {
Name string Name string
@ -69,6 +79,16 @@ func (f *GenericFlag) Names() []string {
return FlagNames(f.Name, f.Aliases) return FlagNames(f.Name, f.Aliases)
} }
// IsRequired returns whether or not the flag is required
func (f *GenericFlag) IsRequired() bool {
return f.Required
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *GenericFlag) IsVisible() bool {
return !f.Hidden
}
// Int64SliceFlag is a flag with type *Int64Slice // Int64SliceFlag is a flag with type *Int64Slice
type Int64SliceFlag struct { type Int64SliceFlag struct {
Name string Name string
@ -98,6 +118,16 @@ func (f *Int64SliceFlag) Names() []string {
return FlagNames(f.Name, f.Aliases) return FlagNames(f.Name, f.Aliases)
} }
// IsRequired returns whether or not the flag is required
func (f *Int64SliceFlag) IsRequired() bool {
return f.Required
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *Int64SliceFlag) IsVisible() bool {
return !f.Hidden
}
// IntSliceFlag is a flag with type *IntSlice // IntSliceFlag is a flag with type *IntSlice
type IntSliceFlag struct { type IntSliceFlag struct {
Name string Name string
@ -127,6 +157,16 @@ func (f *IntSliceFlag) Names() []string {
return FlagNames(f.Name, f.Aliases) return FlagNames(f.Name, f.Aliases)
} }
// IsRequired returns whether or not the flag is required
func (f *IntSliceFlag) IsRequired() bool {
return f.Required
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *IntSliceFlag) IsVisible() bool {
return !f.Hidden
}
// PathFlag is a flag with type Path // PathFlag is a flag with type Path
type PathFlag struct { type PathFlag struct {
Name string Name string
@ -163,6 +203,16 @@ func (f *PathFlag) Names() []string {
return FlagNames(f.Name, f.Aliases) return FlagNames(f.Name, f.Aliases)
} }
// IsRequired returns whether or not the flag is required
func (f *PathFlag) IsRequired() bool {
return f.Required
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *PathFlag) IsVisible() bool {
return !f.Hidden
}
// StringSliceFlag is a flag with type *StringSlice // StringSliceFlag is a flag with type *StringSlice
type StringSliceFlag struct { type StringSliceFlag struct {
Name string Name string
@ -194,6 +244,16 @@ func (f *StringSliceFlag) Names() []string {
return FlagNames(f.Name, f.Aliases) return FlagNames(f.Name, f.Aliases)
} }
// IsRequired returns whether or not the flag is required
func (f *StringSliceFlag) IsRequired() bool {
return f.Required
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *StringSliceFlag) IsVisible() bool {
return !f.Hidden
}
// TimestampFlag is a flag with type *Timestamp // TimestampFlag is a flag with type *Timestamp
type TimestampFlag struct { type TimestampFlag struct {
Name string Name string
@ -230,6 +290,16 @@ func (f *TimestampFlag) Names() []string {
return FlagNames(f.Name, f.Aliases) return FlagNames(f.Name, f.Aliases)
} }
// IsRequired returns whether or not the flag is required
func (f *TimestampFlag) IsRequired() bool {
return f.Required
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *TimestampFlag) IsVisible() bool {
return !f.Hidden
}
// BoolFlag is a flag with type bool // BoolFlag is a flag with type bool
type BoolFlag struct { type BoolFlag struct {
Name string Name string
@ -264,6 +334,16 @@ func (f *BoolFlag) Names() []string {
return FlagNames(f.Name, f.Aliases) return FlagNames(f.Name, f.Aliases)
} }
// IsRequired returns whether or not the flag is required
func (f *BoolFlag) IsRequired() bool {
return f.Required
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *BoolFlag) IsVisible() bool {
return !f.Hidden
}
// Float64Flag is a flag with type float64 // Float64Flag is a flag with type float64
type Float64Flag struct { type Float64Flag struct {
Name string Name string
@ -298,6 +378,16 @@ func (f *Float64Flag) Names() []string {
return FlagNames(f.Name, f.Aliases) return FlagNames(f.Name, f.Aliases)
} }
// IsRequired returns whether or not the flag is required
func (f *Float64Flag) IsRequired() bool {
return f.Required
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *Float64Flag) IsVisible() bool {
return !f.Hidden
}
// IntFlag is a flag with type int // IntFlag is a flag with type int
type IntFlag struct { type IntFlag struct {
Name string Name string
@ -332,6 +422,16 @@ func (f *IntFlag) Names() []string {
return FlagNames(f.Name, f.Aliases) return FlagNames(f.Name, f.Aliases)
} }
// IsRequired returns whether or not the flag is required
func (f *IntFlag) IsRequired() bool {
return f.Required
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *IntFlag) IsVisible() bool {
return !f.Hidden
}
// Int64Flag is a flag with type int64 // Int64Flag is a flag with type int64
type Int64Flag struct { type Int64Flag struct {
Name string Name string
@ -366,6 +466,16 @@ func (f *Int64Flag) Names() []string {
return FlagNames(f.Name, f.Aliases) return FlagNames(f.Name, f.Aliases)
} }
// IsRequired returns whether or not the flag is required
func (f *Int64Flag) IsRequired() bool {
return f.Required
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *Int64Flag) IsVisible() bool {
return !f.Hidden
}
// StringFlag is a flag with type string // StringFlag is a flag with type string
type StringFlag struct { type StringFlag struct {
Name string Name string
@ -402,6 +512,16 @@ func (f *StringFlag) Names() []string {
return FlagNames(f.Name, f.Aliases) return FlagNames(f.Name, f.Aliases)
} }
// IsRequired returns whether or not the flag is required
func (f *StringFlag) IsRequired() bool {
return f.Required
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *StringFlag) IsVisible() bool {
return !f.Hidden
}
// DurationFlag is a flag with type time.Duration // DurationFlag is a flag with type time.Duration
type DurationFlag struct { type DurationFlag struct {
Name string Name string
@ -436,6 +556,16 @@ func (f *DurationFlag) Names() []string {
return FlagNames(f.Name, f.Aliases) return FlagNames(f.Name, f.Aliases)
} }
// IsRequired returns whether or not the flag is required
func (f *DurationFlag) IsRequired() bool {
return f.Required
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *DurationFlag) IsVisible() bool {
return !f.Hidden
}
// UintFlag is a flag with type uint // UintFlag is a flag with type uint
type UintFlag struct { type UintFlag struct {
Name string Name string
@ -470,6 +600,16 @@ func (f *UintFlag) Names() []string {
return FlagNames(f.Name, f.Aliases) return FlagNames(f.Name, f.Aliases)
} }
// IsRequired returns whether or not the flag is required
func (f *UintFlag) IsRequired() bool {
return f.Required
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *UintFlag) IsVisible() bool {
return !f.Hidden
}
// Uint64Flag is a flag with type uint64 // Uint64Flag is a flag with type uint64
type Uint64Flag struct { type Uint64Flag struct {
Name string Name string
@ -504,4 +644,14 @@ func (f *Uint64Flag) Names() []string {
return FlagNames(f.Name, f.Aliases) return FlagNames(f.Name, f.Aliases)
} }
// IsRequired returns whether or not the flag is required
func (f *Uint64Flag) IsRequired() bool {
return f.Required
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *Uint64Flag) IsVisible() bool {
return !f.Hidden
}
// vim:ro // vim:ro

View File

@ -16,6 +16,18 @@ func TestFloat64SliceFlag_SatisfiesFlagInterface(t *testing.T) {
_ = f.Names() _ = f.Names()
} }
func TestFloat64SliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
var f cli.RequiredFlag = &cli.Float64SliceFlag{}
_ = f.IsRequired()
}
func TestFloat64SliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
var f cli.VisibleFlag = &cli.Float64SliceFlag{}
_ = f.IsVisible()
}
func TestGenericFlag_SatisfiesFlagInterface(t *testing.T) { func TestGenericFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.GenericFlag{} var f cli.Flag = &cli.GenericFlag{}
@ -29,6 +41,18 @@ func TestGenericFlag_SatisfiesFmtStringerInterface(t *testing.T) {
_ = f.String() _ = f.String()
} }
func TestGenericFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
var f cli.RequiredFlag = &cli.GenericFlag{}
_ = f.IsRequired()
}
func TestGenericFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
var f cli.VisibleFlag = &cli.GenericFlag{}
_ = f.IsVisible()
}
func TestInt64SliceFlag_SatisfiesFlagInterface(t *testing.T) { func TestInt64SliceFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.Int64SliceFlag{} var f cli.Flag = &cli.Int64SliceFlag{}
@ -36,6 +60,18 @@ func TestInt64SliceFlag_SatisfiesFlagInterface(t *testing.T) {
_ = f.Names() _ = f.Names()
} }
func TestInt64SliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
var f cli.RequiredFlag = &cli.Int64SliceFlag{}
_ = f.IsRequired()
}
func TestInt64SliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
var f cli.VisibleFlag = &cli.Int64SliceFlag{}
_ = f.IsVisible()
}
func TestIntSliceFlag_SatisfiesFlagInterface(t *testing.T) { func TestIntSliceFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.IntSliceFlag{} var f cli.Flag = &cli.IntSliceFlag{}
@ -43,6 +79,18 @@ func TestIntSliceFlag_SatisfiesFlagInterface(t *testing.T) {
_ = f.Names() _ = f.Names()
} }
func TestIntSliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
var f cli.RequiredFlag = &cli.IntSliceFlag{}
_ = f.IsRequired()
}
func TestIntSliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
var f cli.VisibleFlag = &cli.IntSliceFlag{}
_ = f.IsVisible()
}
func TestPathFlag_SatisfiesFlagInterface(t *testing.T) { func TestPathFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.PathFlag{} var f cli.Flag = &cli.PathFlag{}
@ -56,6 +104,18 @@ func TestPathFlag_SatisfiesFmtStringerInterface(t *testing.T) {
_ = f.String() _ = f.String()
} }
func TestPathFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
var f cli.RequiredFlag = &cli.PathFlag{}
_ = f.IsRequired()
}
func TestPathFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
var f cli.VisibleFlag = &cli.PathFlag{}
_ = f.IsVisible()
}
func TestStringSliceFlag_SatisfiesFlagInterface(t *testing.T) { func TestStringSliceFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.StringSliceFlag{} var f cli.Flag = &cli.StringSliceFlag{}
@ -63,6 +123,18 @@ func TestStringSliceFlag_SatisfiesFlagInterface(t *testing.T) {
_ = f.Names() _ = f.Names()
} }
func TestStringSliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
var f cli.RequiredFlag = &cli.StringSliceFlag{}
_ = f.IsRequired()
}
func TestStringSliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
var f cli.VisibleFlag = &cli.StringSliceFlag{}
_ = f.IsVisible()
}
func TestTimestampFlag_SatisfiesFlagInterface(t *testing.T) { func TestTimestampFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.TimestampFlag{} var f cli.Flag = &cli.TimestampFlag{}
@ -76,6 +148,18 @@ func TestTimestampFlag_SatisfiesFmtStringerInterface(t *testing.T) {
_ = f.String() _ = f.String()
} }
func TestTimestampFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
var f cli.RequiredFlag = &cli.TimestampFlag{}
_ = f.IsRequired()
}
func TestTimestampFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
var f cli.VisibleFlag = &cli.TimestampFlag{}
_ = f.IsVisible()
}
func TestBoolFlag_SatisfiesFlagInterface(t *testing.T) { func TestBoolFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.BoolFlag{} var f cli.Flag = &cli.BoolFlag{}
@ -89,6 +173,18 @@ func TestBoolFlag_SatisfiesFmtStringerInterface(t *testing.T) {
_ = f.String() _ = f.String()
} }
func TestBoolFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
var f cli.RequiredFlag = &cli.BoolFlag{}
_ = f.IsRequired()
}
func TestBoolFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
var f cli.VisibleFlag = &cli.BoolFlag{}
_ = f.IsVisible()
}
func TestFloat64Flag_SatisfiesFlagInterface(t *testing.T) { func TestFloat64Flag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.Float64Flag{} var f cli.Flag = &cli.Float64Flag{}
@ -102,6 +198,18 @@ func TestFloat64Flag_SatisfiesFmtStringerInterface(t *testing.T) {
_ = f.String() _ = f.String()
} }
func TestFloat64Flag_SatisfiesRequiredFlagInterface(t *testing.T) {
var f cli.RequiredFlag = &cli.Float64Flag{}
_ = f.IsRequired()
}
func TestFloat64Flag_SatisfiesVisibleFlagInterface(t *testing.T) {
var f cli.VisibleFlag = &cli.Float64Flag{}
_ = f.IsVisible()
}
func TestIntFlag_SatisfiesFlagInterface(t *testing.T) { func TestIntFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.IntFlag{} var f cli.Flag = &cli.IntFlag{}
@ -115,6 +223,18 @@ func TestIntFlag_SatisfiesFmtStringerInterface(t *testing.T) {
_ = f.String() _ = f.String()
} }
func TestIntFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
var f cli.RequiredFlag = &cli.IntFlag{}
_ = f.IsRequired()
}
func TestIntFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
var f cli.VisibleFlag = &cli.IntFlag{}
_ = f.IsVisible()
}
func TestInt64Flag_SatisfiesFlagInterface(t *testing.T) { func TestInt64Flag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.Int64Flag{} var f cli.Flag = &cli.Int64Flag{}
@ -128,6 +248,18 @@ func TestInt64Flag_SatisfiesFmtStringerInterface(t *testing.T) {
_ = f.String() _ = f.String()
} }
func TestInt64Flag_SatisfiesRequiredFlagInterface(t *testing.T) {
var f cli.RequiredFlag = &cli.Int64Flag{}
_ = f.IsRequired()
}
func TestInt64Flag_SatisfiesVisibleFlagInterface(t *testing.T) {
var f cli.VisibleFlag = &cli.Int64Flag{}
_ = f.IsVisible()
}
func TestStringFlag_SatisfiesFlagInterface(t *testing.T) { func TestStringFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.StringFlag{} var f cli.Flag = &cli.StringFlag{}
@ -141,6 +273,18 @@ func TestStringFlag_SatisfiesFmtStringerInterface(t *testing.T) {
_ = f.String() _ = f.String()
} }
func TestStringFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
var f cli.RequiredFlag = &cli.StringFlag{}
_ = f.IsRequired()
}
func TestStringFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
var f cli.VisibleFlag = &cli.StringFlag{}
_ = f.IsVisible()
}
func TestDurationFlag_SatisfiesFlagInterface(t *testing.T) { func TestDurationFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.DurationFlag{} var f cli.Flag = &cli.DurationFlag{}
@ -154,6 +298,18 @@ func TestDurationFlag_SatisfiesFmtStringerInterface(t *testing.T) {
_ = f.String() _ = f.String()
} }
func TestDurationFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
var f cli.RequiredFlag = &cli.DurationFlag{}
_ = f.IsRequired()
}
func TestDurationFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
var f cli.VisibleFlag = &cli.DurationFlag{}
_ = f.IsVisible()
}
func TestUintFlag_SatisfiesFlagInterface(t *testing.T) { func TestUintFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.UintFlag{} var f cli.Flag = &cli.UintFlag{}
@ -167,6 +323,18 @@ func TestUintFlag_SatisfiesFmtStringerInterface(t *testing.T) {
_ = f.String() _ = f.String()
} }
func TestUintFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
var f cli.RequiredFlag = &cli.UintFlag{}
_ = f.IsRequired()
}
func TestUintFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
var f cli.VisibleFlag = &cli.UintFlag{}
_ = f.IsVisible()
}
func TestUint64Flag_SatisfiesFlagInterface(t *testing.T) { func TestUint64Flag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.Uint64Flag{} var f cli.Flag = &cli.Uint64Flag{}
@ -180,4 +348,16 @@ func TestUint64Flag_SatisfiesFmtStringerInterface(t *testing.T) {
_ = f.String() _ = f.String()
} }
func TestUint64Flag_SatisfiesRequiredFlagInterface(t *testing.T) {
var f cli.RequiredFlag = &cli.Uint64Flag{}
_ = f.IsRequired()
}
func TestUint64Flag_SatisfiesVisibleFlagInterface(t *testing.T) {
var f cli.VisibleFlag = &cli.Uint64Flag{}
_ = f.IsVisible()
}
// vim:ro // vim:ro