Merge branch 'main' into remove_reflect

This commit is contained in:
Dan Buch 2022-04-21 20:22:26 -04:00 committed by GitHub
commit d83bb8d85e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 890 additions and 247 deletions

View File

@ -1,12 +1,10 @@
--- ---
name: ask a question name: ask a question
about: ask us question - assume stackoverflow's guidelines apply here about: ask a question - assume stackoverflow's guidelines apply here
title: 'q: ( your question title goes here )' title: your question title goes here
labels: 'kind/question, status/triage, area/v2' labels: 'kind/question, status/triage, area/v2'
assignees: '' assignees: ''
--- ---
## my question is... my question is...
_**( Put the question text here )**_

View File

@ -1,28 +1,32 @@
--- ---
name: v1 bug report name: v1 bug report
about: Create a report to help us fix v1 bugs about: Create a report to help us fix v1 bugs
title: 'v1 bug: ( your bug title goes here )' title: 'your bug title goes here'
labels: 'kind/bug, status/triage, area/v1' labels: 'kind/bug, status/triage, area/v1'
assignees: '' assignees: ''
--- ---
## my urfave/cli version is ## My urfave/cli version is
_**( Put the version of urfave/cli that you are using here )**_ _**( Put the version of urfave/cli that you are using here )**_
## Checklist ## Checklist
* [ ] Are you running the latest v1 release? The list of releases is [here](https://github.com/urfave/cli/releases). - [ ] Are you running the latest v1 release? The list of releases is [here](https://github.com/urfave/cli/releases).
* [ ] Did you check the manual for your release? The v1 manual is [here](https://github.com/urfave/cli/blob/master/docs/v1/manual.md) - [ ] Did you check the manual for your release? The v1 manual is [here](https://github.com/urfave/cli/blob/main/docs/v1/manual.md).
* [ ] Did you perform a search about this problem? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching. - [ ] Did you perform a search about this problem? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching.
## Dependency Management ## Dependency Management
- [ ] My project is using go modules. <!--
- [ ] My project is using vendoring. Delete any of the following that do not apply:
- [ ] My project is automatically downloading the latest version. -->
- [ ] I am unsure of what my dependency management setup is.
- My project is using go modules.
- My project is using vendoring.
- My project is automatically downloading the latest version.
- I am unsure of what my dependency management setup is.
## Describe the bug ## Describe the bug
@ -34,23 +38,30 @@ Describe the steps or code required to reproduce the behavior
## Observed behavior ## Observed behavior
What did you see happen immediately after the reproduction steps above? What did you see happen immediately after the reproduction steps
above?
## Expected behavior ## Expected behavior
What would you have expected to happen immediately after the reproduction steps above? What would you have expected to happen immediately after the
reproduction steps above?
## Additional context ## Additional context
Add any other context about the problem here. Add any other context about the problem here.
If the issue relates to a specific open source Github repo, please link that repo here. If the issue relates to a specific open source Github repo, please
link that repo here.
If you can reproduce this issue with a public CI system, please link a failing build here. If you can reproduce this issue with a public CI system, please
link a failing build here.
## Want to fix this yourself? ## Want to fix this yourself?
We'd love to have more contributors on this project! If the fix for this bug is easily explained and very small, free free to create a pull request for it. You'll want to base the PR off the `v1` branch, all `v1` bug fix releases will be made from that branch. We'd love to have more contributors on this project! If the fix for
this bug is easily explained and very small, free free to create a
pull request for it. You'll want to base the PR off the `v1`
branch, all `v1` bug fix releases will be made from that branch.
## Run `go version` and paste its output here ## Run `go version` and paste its output here

View File

@ -1,28 +1,32 @@
--- ---
name: v2 bug report name: v2 bug report
about: Create a report to help us fix v2 bugs about: Create a report to help us fix v2 bugs
title: 'v2 bug: ( your bug title goes here )' title: 'your bug title goes here'
labels: 'kind/bug, area/v2, status/triage' labels: 'kind/bug, area/v2, status/triage'
assignees: '' assignees: ''
--- ---
## my urfave/cli version is ## My urfave/cli version is
_**( Put the version of urfave/cli that you are using here )**_ _**( Put the version of urfave/cli that you are using here )**_
## Checklist ## Checklist
* [ ] Are you running the latest v2 release? The list of releases is [here](https://github.com/urfave/cli/releases). - [ ] Are you running the latest v2 release? The list of releases is [here](https://github.com/urfave/cli/releases).
* [ ] Did you check the manual for your release? The v2 manual is [here](https://github.com/urfave/cli/blob/master/docs/v2/manual.md) - [ ] Did you check the manual for your release? The v2 manual is [here](https://github.com/urfave/cli/blob/main/docs/v2/manual.md)
* [ ] Did you perform a search about this problem? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching. - [ ] Did you perform a search about this problem? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching.
## Dependency Management ## Dependency Management
- [ ] My project is using go modules. <!--
- [ ] My project is using vendoring. Delete any of the following that do not apply:
- [ ] My project is automatically downloading the latest version. -->
- [ ] I am unsure of what my dependency management setup is.
- My project is using go modules.
- My project is using vendoring.
- My project is automatically downloading the latest version.
- I am unsure of what my dependency management setup is.
## Describe the bug ## Describe the bug
@ -34,23 +38,30 @@ Describe the steps or code required to reproduce the behavior
## Observed behavior ## Observed behavior
What did you see happen immediately after the reproduction steps above? What did you see happen immediately after the reproduction steps
above?
## Expected behavior ## Expected behavior
What would you have expected to happen immediately after the reproduction steps above? What would you have expected to happen immediately after the
reproduction steps above?
## Additional context ## Additional context
Add any other context about the problem here. Add any other context about the problem here.
If the issue relates to a specific open source Github repo, please link that repo here. If the issue relates to a specific open source Github repo, please
link that repo here.
If you can reproduce this issue with a public CI system, please link a failing build here. If you can reproduce this issue with a public CI system, please
link a failing build here.
## Want to fix this yourself? ## Want to fix this yourself?
We'd love to have more contributors on this project! If the fix for this bug is easily explained and very small, free free to create a pull request for it. We'd love to have more contributors on this project! If the fix for
this bug is easily explained and very small, free free to create a
pull request for it.
## Run `go version` and paste its output here ## Run `go version` and paste its output here
``` ```

View File

@ -1,7 +1,7 @@
--- ---
name: v2 feature request name: v2 feature request
about: Suggest an improvement for v2 about: Suggest an improvement for v2
title: 'v2 feature: ( your feature title goes here )' title: 'your feature title goes here'
labels: 'type/feature, area/v2, status/triage' labels: 'type/feature, area/v2, status/triage'
assignees: '' assignees: ''
@ -10,16 +10,19 @@ assignees: ''
## Checklist ## Checklist
* [ ] Are you running the latest v2 release? The list of releases is [here](https://github.com/urfave/cli/releases). * [ ] Are you running the latest v2 release? The list of releases is [here](https://github.com/urfave/cli/releases).
* [ ] Did you check the manual for your release? The v2 manual is [here](https://github.com/urfave/cli/blob/master/docs/v2/manual.md) * [ ] Did you check the manual for your release? The v2 manual is [here](https://github.com/urfave/cli/blob/main/docs/v2/manual.md).
* [ ] Did you perform a search about this feature? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching. * [ ] Did you perform a search about this feature? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching.
## What problem does this solve? ## What problem does this solve?
A clear and concise description of what problem this feature would solve. For example: A clear and concise description of what problem this feature would solve. For example:
- needing to type out the full flag name takes a long time, so I would like to suggest adding auto-complete - needing to type out the full flag name takes a long time, so I
- I use (osx, windows, linux) and would like support for (some existing feature) to be extended to my platform would like to suggest adding auto-complete
- the terminal output for a particular error case is confusing, and I think it could be improved - I use (osx, windows, linux) and would like support for (some
existing feature) to be extended to my platform
- the terminal output for a particular error case is confusing, and
I think it could be improved
## Solution description ## Solution description
@ -27,4 +30,5 @@ A detailed description of what you want to happen.
## Describe alternatives you've considered ## Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered. A clear and concise description of any alternative solutions or
features you've considered.

View File

@ -8,10 +8,14 @@
_(REQUIRED)_ _(REQUIRED)_
- [ ] bug <!--
- [ ] cleanup Delete any of the following that do not apply:
- [ ] documentation -->
- [ ] feature
- bug
- cleanup
- documentation
- feature
## What this PR does / why we need it: ## What this PR does / why we need it:
@ -28,6 +32,7 @@ _(REQUIRED)_
## Which issue(s) this PR fixes: ## Which issue(s) this PR fixes:
_(REQUIRED)_ _(REQUIRED)_
<!-- <!--
If this PR fixes one of more issues, list them here. If this PR fixes one of more issues, list them here.
One line each, like so: One line each, like so:

73
.github/stale.yml vendored
View File

@ -1,64 +1,17 @@
# Configuration for probot-stale - https://github.com/probot/stale # Number of days of inactivity before an issue becomes stale
daysUntilStale: 365
# Number of days of inactivity before an Issue or Pull Request becomes stale # Number of days of inactivity before a stale issue is closed
daysUntilStale: 90 daysUntilClose: 90
# Issues with these labels will never be considered stale
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 30
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
onlyLabels: []
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels: exemptLabels:
- pinned - pinned
- security - security
- "help wanted" # Label to use when marking an issue as stale
- "kind/maintenance" staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: false
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: false
# Set to true to ignore issues with an assignee (defaults to false)
exemptAssignees: false
# Label to use when marking as stale
staleLabel: "status/stale"
# Comment to post when marking as stale. Set to `false` to disable
markComment: > markComment: >
This issue or PR has been automatically marked as stale because it has not had This issue has been automatically marked as stale because it has not had
recent activity. Please add a comment bumping this if you're still recent activity. It will be closed if no further activity occurs. Thank you
interested in it's resolution! Thanks for your help, please let us know for your contributions.
if you need anything else. # Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
# Comment to post when removing the stale label.
unmarkComment: >
This issue or PR has been bumped and is no longer marked as stale! Feel free
to bump it again in the future, if it's still relevant.
# Comment to post when closing a stale Issue or Pull Request.
closeComment: >
Closing this as it has become stale.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
# Limit to only `issues` or `pulls`
# only: issues
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
# pulls:
# daysUntilStale: 30
# markComment: >
# This pull request has been automatically marked as stale because it has not had
# recent activity. It will be closed if no further activity occurs. Thank you
# for your contributions.
# issues:
# exemptLabels:
# - confirmed

View File

@ -3,42 +3,35 @@ name: Run Tests
on: on:
push: push:
branches: branches:
- master - main
- v1 tags:
- v2.*
pull_request: pull_request:
branches: branches:
- master - main
- v1
jobs: jobs:
test: test:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, macos-latest, windows-latest]
go: [1.14, 1.15, 1.16] go: [1.16.x, 1.17.x, 1.18.x]
name: ${{ matrix.os }} @ Go ${{ matrix.go }} name: ${{ matrix.os }} @ Go ${{ matrix.go }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: Set up Go ${{ matrix.go }} - name: Set up Go ${{ matrix.go }}
uses: actions/setup-go@v1 uses: actions/setup-go@v3
with: with:
go-version: ${{ matrix.go }} go-version: ${{ matrix.go }}
- name: Set GOPATH, PATH and ENV - name: Set PATH
run: | run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}"
echo "GOPATH=$(dirname $GITHUB_WORKSPACE)" >> $GITHUB_ENV
echo "GO111MODULE=on" >> $GITHUB_ENV
echo "GOPROXY=https://proxy.golang.org" >> $GITHUB_ENV
echo "$(dirname $GITHUB_WORKSPACE)/bin" >> $GITHUB_PATH
shell: bash
- name: Checkout Code - name: Checkout Code
uses: actions/checkout@v1 uses: actions/checkout@v3
with:
ref: ${{ github.ref }}
- name: GOFMT Check - name: GOFMT Check
if: matrix.go == 1.16 && matrix.os == 'ubuntu-latest' if: matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest'
run: test -z $(gofmt -l .) run: test -z $(gofmt -l .)
- name: vet - name: vet
@ -51,8 +44,8 @@ jobs:
run: go run internal/build/build.go check-binary-size run: go run internal/build/build.go check-binary-size
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
if: success() && matrix.go == 1.16 && matrix.os == 'ubuntu-latest' if: success() && matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest'
uses: codecov/codecov-action@v1 uses: codecov/codecov-action@v2
with: with:
fail_ci_if_error: true fail_ci_if_error: true
@ -61,44 +54,30 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v1 uses: actions/setup-go@v3
with: with:
# Currently fails on 1.16 go-version: 1.18.x
go-version: 1.15
- name: Use Node.js 12.x - name: Use Node.js 16
uses: actions/setup-node@v1 uses: actions/setup-node@v3
with: with:
node-version: 12.x node-version: '16'
- name: Set GOPATH, PATH and ENV - name: Set PATH
run: | run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}"
echo "GOPATH=$(dirname $GITHUB_WORKSPACE)" >> $GITHUB_ENV
echo "GO111MODULE=on" >> $GITHUB_ENV
echo "GOPROXY=https://proxy.golang.org" >> $GITHUB_ENV
echo "$(dirname $GITHUB_WORKSPACE)/bin" >> $GITHUB_PATH
shell: bash
- name: Checkout Code - name: Checkout Code
uses: actions/checkout@v1 uses: actions/checkout@v3
with:
ref: ${{ github.ref }}
- name: Install Dependencies - name: Install Dependencies
run: | run:
mkdir -p $GOPATH/bin mkdir -p "${GITHUB_WORKSPACE}/.local/bin" &&
curl -L -o $GOPATH/bin/gfmrun "https://github.com/urfave/gfmrun/releases/download/v1.2.14/gfmrun-$(go env GOOS)-amd64-v1.2.14" 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 $GOPATH/bin/gfmrun chmod +x "${GITHUB_WORKSPACE}/.local/bin/gfmrun" &&
npm install -g markdown-toc@1.2.0 npm install -g markdown-toc@1.2.0
- name: Run Tests (v1) - name: gfmrun
if: contains(github.base_ref, 'v1') run: go run internal/build/build.go gfmrun docs/v2/manual.md
run: |
go run internal/build/build.go gfmrun docs/v1/manual.md
go run internal/build/build.go toc docs/v1/manual.md
- name: Run Tests (v2) - name: toc
if: contains(github.base_ref, 'master') run: go run internal/build/build.go toc docs/v2/manual.md
run: |
go run internal/build/build.go gfmrun docs/v2/manual.md
go run internal/build/build.go toc docs/v2/manual.md

1
.gitignore vendored
View File

@ -5,5 +5,6 @@ vendor
.idea .idea
internal/*/built-example internal/*/built-example
coverage.txt coverage.txt
/.local/
*.exe *.exe

View File

@ -4,7 +4,7 @@ cli
[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://pkg.go.dev/github.com/urfave/cli/v2) [![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) [![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) [![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli)
[![codecov](https://codecov.io/gh/urfave/cli/branch/master/graph/badge.svg)](https://codecov.io/gh/urfave/cli) [![codecov](https://codecov.io/gh/urfave/cli/branch/main/graph/badge.svg)](https://codecov.io/gh/urfave/cli)
cli is a simple, fast, and fun package for building command line apps in Go. The 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 goal is to enable developers to write fast and distributable command line
@ -12,7 +12,7 @@ applications in an expressive way.
## Usage Documentation ## Usage Documentation
Usage documentation exists for each major version. Don't know what version you're on? You're probably using the version from the `master` branch, which is currently `v2`. Usage documentation exists for each major version. Don't know what version you're on? You're probably using the version from the `main` branch, which is currently `v2`.
- `v2` - [./docs/v2/manual.md](./docs/v2/manual.md) - `v2` - [./docs/v2/manual.md](./docs/v2/manual.md)
- `v1` - [./docs/v1/manual.md](./docs/v1/manual.md) - `v1` - [./docs/v1/manual.md](./docs/v1/manual.md)
@ -30,7 +30,7 @@ Go Modules are required when using this package. [See the go blog guide on using
### Using `v2` releases ### Using `v2` releases
``` ```
$ GO111MODULE=on go get github.com/urfave/cli/v2 $ go get github.com/urfave/cli/v2
``` ```
```go ```go
@ -44,7 +44,7 @@ import (
### Using `v1` releases ### Using `v1` releases
``` ```
$ GO111MODULE=on go get github.com/urfave/cli $ go get github.com/urfave/cli
``` ```
```go ```go
@ -67,4 +67,4 @@ export PATH=$PATH:$GOPATH/bin
cli is tested against multiple versions of Go on Linux, and against the latest cli is tested against multiple versions of Go on Linux, and against the latest
released version of Go on OS X and Windows. This project uses Github Actions for released version of Go on OS X and Windows. This project uses Github Actions for
builds. To see our currently supported go versions and platforms, look at the [./.github/workflows/cli.yml](https://github.com/urfave/cli/blob/master/.github/workflows/cli.yml). builds. To see our currently supported go versions and platforms, look at the [./.github/workflows/cli.yml](https://github.com/urfave/cli/blob/main/.github/workflows/cli.yml).

2
app.go
View File

@ -12,7 +12,7 @@ import (
) )
var ( var (
changeLogURL = "https://github.com/urfave/cli/blob/master/docs/CHANGELOG.md" changeLogURL = "https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md"
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL) appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you." contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
errInvalidActionType = NewExitError("ERROR invalid Action type. "+ errInvalidActionType = NewExitError("ERROR invalid Action type. "+

View File

@ -476,18 +476,18 @@ func TestApp_RunAsSubCommandIncorrectUsage(t *testing.T) {
a := App{ a := App{
Name: "cmd", Name: "cmd",
Flags: []Flag{ Flags: []Flag{
&StringFlag{Name: "--foo"}, &StringFlag{Name: "foo"},
}, },
Writer: bytes.NewBufferString(""), Writer: bytes.NewBufferString(""),
} }
set := flag.NewFlagSet("", flag.ContinueOnError) set := flag.NewFlagSet("", flag.ContinueOnError)
_ = set.Parse([]string{"", "---foo"}) _ = set.Parse([]string{"", "-bar"})
c := &Context{flagSet: set} c := &Context{flagSet: set}
err := a.RunAsSubcommand(c) err := a.RunAsSubcommand(c)
expect(t, err, errors.New("bad flag syntax: ---foo")) expect(t, err.Error(), "flag provided but not defined: -bar")
} }
func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {

View File

@ -151,6 +151,9 @@ func (ctx *Context) lookupFlag(name string) Flag {
func (ctx *Context) lookupFlagSet(name string) *flag.FlagSet { func (ctx *Context) lookupFlagSet(name string) *flag.FlagSet {
for _, c := range ctx.Lineage() { for _, c := range ctx.Lineage() {
if c.flagSet == nil {
continue
}
if f := c.flagSet.Lookup(name); f != nil { if f := c.flagSet.Lookup(name); f != nil {
return c.flagSet return c.flagSet
} }

View File

@ -112,6 +112,8 @@ func TestContext_String(t *testing.T) {
c := NewContext(nil, set, parentCtx) c := NewContext(nil, set, parentCtx)
expect(t, c.String("myflag"), "hello world") expect(t, c.String("myflag"), "hello world")
expect(t, c.String("top-flag"), "hai veld") expect(t, c.String("top-flag"), "hai veld")
c = NewContext(nil, nil, parentCtx)
expect(t, c.String("top-flag"), "hai veld")
} }
func TestContext_Path(t *testing.T) { func TestContext_Path(t *testing.T) {

50
docs.go
View File

@ -15,7 +15,7 @@ import (
// The function errors if either parsing or writing of the string fails. // The function errors if either parsing or writing of the string fails.
func (a *App) ToMarkdown() (string, error) { func (a *App) ToMarkdown() (string, error) {
var w bytes.Buffer var w bytes.Buffer
if err := a.writeDocTemplate(&w, 8); err != nil { if err := a.writeDocTemplate(&w, 0); err != nil {
return "", err return "", err
} }
return w.String(), nil return w.String(), nil
@ -68,15 +68,16 @@ func prepareCommands(commands []*Command, level int) []string {
if command.Hidden { if command.Hidden {
continue continue
} }
usage := ""
if command.Usage != "" {
usage = command.Usage
}
prepared := fmt.Sprintf("%s %s\n\n%s\n", usageText := prepareUsageText(command)
usage := prepareUsage(command, usageText)
prepared := fmt.Sprintf("%s %s\n\n%s%s",
strings.Repeat("#", level+2), strings.Repeat("#", level+2),
strings.Join(command.Names(), ", "), strings.Join(command.Names(), ", "),
usage, usage,
usageText,
) )
flags := prepareArgsWithValues(command.Flags) flags := prepareArgsWithValues(command.Flags)
@ -155,3 +156,40 @@ func flagDetails(flag DocGenerationFlag) string {
} }
return ": " + description return ": " + description
} }
func prepareUsageText(command *Command) string {
if command.UsageText == "" {
return ""
}
// Remove leading and trailing newlines
preparedUsageText := strings.Trim(command.UsageText, "\n")
var usageText string
if strings.Contains(preparedUsageText, "\n") {
// Format multi-line string as a code block using the 4 space schema to allow for embedded markdown such
// that it will not break the continuous code block.
for _, ln := range strings.Split(preparedUsageText, "\n") {
usageText += fmt.Sprintf(" %s\n", ln)
}
} else {
// Style a single line as a note
usageText = fmt.Sprintf(">%s\n", preparedUsageText)
}
return usageText
}
func prepareUsage(command *Command, usageText string) string {
if command.Usage == "" {
return ""
}
usage := command.Usage + "\n"
// Add a newline to the Usage IFF there is a UsageText
if usageText != "" {
usage += "\n"
}
return usage
}

View File

@ -6,7 +6,7 @@ Feel free to put up a pull request to fix a bug or maybe add a feature. We will
give it a code review and make sure that it does not break backwards give it a code review and make sure that it does not break backwards
compatibility. If collaborators agree that it is in line with compatibility. If collaborators agree that it is in line with
the vision of the project, we will work with you to get the code into the vision of the project, we will work with you to get the code into
a mergeable state and merge it into the master branch. a mergeable state and merge it into the main branch.
If you have contributed something significant to the project, we will most If you have contributed something significant to the project, we will most
likely add you as a collaborator. As a collaborator you are given the ability likely add you as a collaborator. As a collaborator you are given the ability

View File

@ -15,15 +15,18 @@ consider sending a PR to help improve this guide.
* [Flags before args](#flags-before-args) * [Flags before args](#flags-before-args)
* [Import string changed](#import-string-changed) * [Import string changed](#import-string-changed)
* [Flag aliases are done differently.](#flag-aliases-are-done-differently) * [Flag aliases are done differently](#flag-aliases-are-done-differently)
* [EnvVar is now a list (EnvVars)](#envvar-is-now-a-list-envvars) * [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) * [Commands are now lists of pointers](#commands-are-now-lists-of-pointers)
* [Lists of commands should be pointers](#lists-of-commands-should-be-pointers) * [Lists of commands should be pointers](#lists-of-commands-should-be-pointers)
* [cli.Flag changed](#cliflag-changed)
* [Appending Commands](#appending-commands) * [Appending Commands](#appending-commands)
* [Actions returns errors](#actions-returns-errors) * [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) * [Everything else](#everything-else)
* [Full API Example](#full-api-example)
<!-- tocstop --> <!-- tocstop -->
@ -53,7 +56,7 @@ Check each file for this and make the change.
Shell command to find them all: `fgrep -rl github.com/urfave/cli *` Shell command to find them all: `fgrep -rl github.com/urfave/cli *`
# Flag aliases are done differently. # Flag aliases are done differently
Change `Name: "foo, f"` to `Name: "foo", Aliases: []string{"f"}` Change `Name: "foo, f"` to `Name: "foo", Aliases: []string{"f"}`
@ -181,6 +184,52 @@ Compiler messages you might see:
cannot use c (type *cli.Command) as type cli.Command in append cannot use c (type *cli.Command) as type cli.Command in append
``` ```
# GlobalString, GlobalBool and its likes are deprecated
Use simply `String` instead of `GlobalString`, `Bool` instead of `GlobalBool`
# BoolTFlag and BoolT are deprecated
BoolTFlag was a Bool Flag with its default value set to true and BoolT was used to find any BoolTFlag used locally, so both are deprecated.
* OLD:
```go
cli.BoolTFlag{
Name: FlagName,
Usage: FlagUsage,
EnvVar: "FLAG_ENV_VAR",
}
```
* NEW:
```go
cli.BoolFlag{
Name: FlagName,
Value: true,
Usage: FlagUsage,
EnvVar: "FLAG_ENV_VAR",
}
```
# &cli.StringSlice{""} replaced with cli.NewStringSlice("")
Example:
* OLD:
```go
Value: &cli.StringSlice{""},
```
* NEW:
```go
Value: cli.NewStringSlice(""),
}
```
# Replace deprecated functions
`cli.NewExitError()` is deprecated. Use `cli.Exit()` instead. ([Staticcheck](https://staticcheck.io/) detects this automatically and recommends replacement code.)
# Everything else # Everything else
Compile the code and work through any errors. Most should Compile the code and work through any errors. Most should

View File

@ -514,7 +514,7 @@ func main() {
``` ```
If `EnvVars` contains more than one string, the first environment variable that If `EnvVars` contains more than one string, the first environment variable that
resolves is used as the default. resolves is used.
<!-- { <!-- {
"args": ["&#45;&#45;help"], "args": ["&#45;&#45;help"],
@ -674,8 +674,10 @@ Take for example this app that requires the `lang` flag:
package main package main
import ( import (
"fmt"
"log" "log"
"os" "os"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )

View File

@ -67,8 +67,50 @@ func testApp() *App {
}, { }, {
Name: "hidden-command", Name: "hidden-command",
Hidden: true, Hidden: true,
}, {
Aliases: []string{"u"},
Flags: []Flag{
&StringFlag{
Name: "flag",
Aliases: []string{"fl", "f"},
TakesFile: true,
},
&BoolFlag{
Name: "another-flag",
Aliases: []string{"b"},
Usage: "another usage text",
},
},
Name: "usage",
Usage: "standard usage text",
UsageText: `
Usage for the usage text
- formatted: Based on the specified ConfigMap and summon secrets.yml
- list: Inspect the environment for a specific process running on a Pod
- for_effect: Compare 'namespace' environment with 'local'
` + "```" + `
func() { ... }
` + "```" + `
Should be a part of the same code block
`,
Subcommands: []*Command{{
Aliases: []string{"su"},
Flags: []Flag{
&BoolFlag{
Name: "sub-command-flag",
Aliases: []string{"s"},
Usage: "some usage text",
},
},
Name: "sub-usage",
Usage: "standard usage text",
UsageText: "Single line of UsageText",
}},
}} }}
app.UsageText = "app [first_arg] [second_arg]" app.UsageText = "app [first_arg] [second_arg]"
app.Description = `Description of the application.`
app.Usage = "Some app" app.Usage = "Some app"
app.Authors = []*Author{ app.Authors = []*Author{
{Name: "Harrison", Email: "harrison@lolwut.com"}, {Name: "Harrison", Email: "harrison@lolwut.com"},
@ -77,13 +119,13 @@ func testApp() *App {
return app return app
} }
func expectFileContent(t *testing.T, file, expected string) { func expectFileContent(t *testing.T, file, got string) {
data, err := ioutil.ReadFile(file) data, err := ioutil.ReadFile(file)
// Ignore windows line endings // Ignore windows line endings
// TODO: Replace with bytes.ReplaceAll when support for Go 1.11 is dropped // TODO: Replace with bytes.ReplaceAll when support for Go 1.11 is dropped
data = bytes.Replace(data, []byte("\r\n"), []byte("\n"), -1) data = bytes.Replace(data, []byte("\r\n"), []byte("\n"), -1)
expect(t, err, nil) expect(t, err, nil)
expect(t, string(data), expected) expect(t, got, string(data))
} }
func TestToMarkdownFull(t *testing.T) { func TestToMarkdownFull(t *testing.T) {
@ -137,6 +179,19 @@ func TestToMarkdownNoAuthors(t *testing.T) {
expectFileContent(t, "testdata/expected-doc-no-authors.md", res) expectFileContent(t, "testdata/expected-doc-no-authors.md", res)
} }
func TestToMarkdownNoUsageText(t *testing.T) {
// Given
app := testApp()
app.UsageText = ""
// When
res, err := app.ToMarkdown()
// Then
expect(t, err, nil)
expectFileContent(t, "testdata/expected-doc-no-usagetext.md", res)
}
func TestToMan(t *testing.T) { func TestToMan(t *testing.T) {
// Given // Given
app := testApp() app := testApp()
@ -175,3 +230,110 @@ func TestToManWithSection(t *testing.T) {
expect(t, err, nil) expect(t, err, nil)
expectFileContent(t, "testdata/expected-doc-full.man", res) expectFileContent(t, "testdata/expected-doc-full.man", res)
} }
func Test_prepareUsageText(t *testing.T) {
t.Run("no UsageText provided", func(t *testing.T) {
// Given
cmd := Command{}
// When
res := prepareUsageText(&cmd)
// Then
expect(t, res, "")
})
t.Run("single line UsageText", func(t *testing.T) {
// Given
cmd := Command{UsageText: "Single line usage text"}
// When
res := prepareUsageText(&cmd)
// Then
expect(t, res, ">Single line usage text\n")
})
t.Run("multiline UsageText", func(t *testing.T) {
// Given
cmd := Command{
UsageText: `
Usage for the usage text
- Should be a part of the same code block
`,
}
// When
res := prepareUsageText(&cmd)
// Then
test := ` Usage for the usage text
- Should be a part of the same code block
`
expect(t, res, test)
})
t.Run("multiline UsageText has formatted embedded markdown", func(t *testing.T) {
// Given
cmd := Command{
UsageText: `
Usage for the usage text
` + "```" + `
func() { ... }
` + "```" + `
Should be a part of the same code block
`,
}
// When
res := prepareUsageText(&cmd)
// Then
test := ` Usage for the usage text
` + "```" + `
func() { ... }
` + "```" + `
Should be a part of the same code block
`
expect(t, res, test)
})
}
func Test_prepareUsage(t *testing.T) {
t.Run("no Usage provided", func(t *testing.T) {
// Given
cmd := Command{}
// When
res := prepareUsage(&cmd, "")
// Then
expect(t, res, "")
})
t.Run("simple Usage", func(t *testing.T) {
// Given
cmd := Command{Usage: "simple usage text"}
// When
res := prepareUsage(&cmd, "")
// Then
expect(t, res, cmd.Usage+"\n")
})
t.Run("simple Usage with UsageText", func(t *testing.T) {
// Given
cmd := Command{Usage: "simple usage text"}
// When
res := prepareUsage(&cmd, "a non-empty string")
// Then
expect(t, res, cmd.Usage+"\n\n")
})
}

View File

@ -414,8 +414,10 @@ func flagFromEnvOrFile(envVars []string, filePath string) (val string, ok bool)
} }
} }
for _, fileVar := range strings.Split(filePath, ",") { for _, fileVar := range strings.Split(filePath, ",") {
if data, err := ioutil.ReadFile(fileVar); err == nil { if fileVar != "" {
return string(data), true if data, err := ioutil.ReadFile(fileVar); err == nil {
return string(data), true
}
} }
} }
return "", false return "", false

View File

@ -80,8 +80,7 @@ func (f *Float64Flag) IsVisible() bool {
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 {
if val != "" { if val != "" {
valFloat, err := strconv.ParseFloat(val, 10) valFloat, err := strconv.ParseFloat(val, 64)
if err != nil { if err != nil {
return fmt.Errorf("could not parse %q as float64 value for flag %s: %s", val, f.Name, err) return fmt.Errorf("could not parse %q as float64 value for flag %s: %s", val, f.Name, err)
} }

View File

@ -157,6 +157,9 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error {
} }
} }
// Set this to false so that we reset the slice if we then set values from
// flags that have already been set by the environment.
f.Value.hasBeenSet = false
f.HasBeenSet = true f.HasBeenSet = true
} }
} }

View File

@ -157,6 +157,9 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error {
} }
} }
// Set this to false so that we reset the slice if we then set values from
// flags that have already been set by the environment.
f.Value.hasBeenSet = false
f.HasBeenSet = true f.HasBeenSet = true
} }

View File

@ -168,6 +168,9 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error {
} }
} }
// Set this to false so that we reset the slice if we then set values from
// flags that have already been set by the environment.
f.Value.hasBeenSet = false
f.HasBeenSet = true f.HasBeenSet = true
} }

View File

@ -109,10 +109,7 @@ func (c *Context) String(name string) string {
func lookupString(name string, set *flag.FlagSet) string { func lookupString(name string, set *flag.FlagSet) string {
f := set.Lookup(name) f := set.Lookup(name)
if f != nil { if f != nil {
parsed, err := f.Value.String(), error(nil) parsed := f.Value.String()
if err != nil {
return ""
}
return parsed return parsed
} }
return "" return ""

View File

@ -52,15 +52,21 @@ func TestBoolFlagApply_SetsAllNames(t *testing.T) {
} }
func TestFlagsFromEnv(t *testing.T) { func TestFlagsFromEnv(t *testing.T) {
newSetFloat64Slice := func(defaults ...float64) Float64Slice {
s := NewFloat64Slice(defaults...)
s.hasBeenSet = false
return *s
}
newSetIntSlice := func(defaults ...int) IntSlice { newSetIntSlice := func(defaults ...int) IntSlice {
s := NewIntSlice(defaults...) s := NewIntSlice(defaults...)
s.hasBeenSet = true s.hasBeenSet = false
return *s return *s
} }
newSetInt64Slice := func(defaults ...int64) Int64Slice { newSetInt64Slice := func(defaults ...int64) Int64Slice {
s := NewInt64Slice(defaults...) s := NewInt64Slice(defaults...)
s.hasBeenSet = true s.hasBeenSet = false
return *s return *s
} }
@ -96,6 +102,9 @@ func TestFlagsFromEnv(t *testing.T) {
{"1.2", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value for flag seconds: .*`}, {"1.2", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value for flag seconds: .*`},
{"foobar", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value for flag seconds: .*`}, {"foobar", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value for flag seconds: .*`},
{"1.0,2", newSetFloat64Slice(1, 2), &Float64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
{"foobar", newSetFloat64Slice(), &Float64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "\[\]float64{}" as float64 slice value for flag seconds: .*`},
{"1,2", newSetIntSlice(1, 2), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, {"1,2", newSetIntSlice(1, 2), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
{"1.2,2", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int slice value for flag seconds: .*`}, {"1.2,2", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int slice value for flag seconds: .*`},
{"foobar", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int slice value for flag seconds: .*`}, {"foobar", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int slice value for flag seconds: .*`},

View File

@ -17,7 +17,7 @@ type ActionFunc func(*Context) error
// CommandNotFoundFunc is executed if the proper command cannot be found // CommandNotFoundFunc is executed if the proper command cannot be found
type CommandNotFoundFunc func(*Context, string) type CommandNotFoundFunc func(*Context, string)
// OnUsageErrorFunc is executed if an usage error occurs. This is useful for displaying // OnUsageErrorFunc is executed if a usage error occurs. This is useful for displaying
// customized usage error messages. This function is able to replace the // customized usage error messages. This function is able to replace the
// original error messages. If this function is not set, the "Incorrect usage" // original error messages. If this function is not set, the "Incorrect usage"
// is displayed and the execution is interrupted. // is displayed and the execution is interrupted.

10
go.mod
View File

@ -1,9 +1,11 @@
module github.com/urfave/cli/v2 module github.com/urfave/cli/v2
go 1.11 go 1.18
require ( require (
github.com/BurntSushi/toml v0.3.1 github.com/BurntSushi/toml v1.1.0
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d github.com/cpuguy83/go-md2man/v2 v2.0.1
gopkg.in/yaml.v2 v2.2.3 gopkg.in/yaml.v2 v2.4.0
) )
require github.com/russross/blackfriday/v2 v2.1.0 // indirect

18
go.sum
View File

@ -1,14 +1,12 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.0.1/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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

13
help.go
View File

@ -163,19 +163,26 @@ func DefaultCompleteWithFlags(cmd *Command) func(c *Context) {
return func(c *Context) { return func(c *Context) {
if len(os.Args) > 2 { if len(os.Args) > 2 {
lastArg := os.Args[len(os.Args)-2] lastArg := os.Args[len(os.Args)-2]
if strings.HasPrefix(lastArg, "-") { if strings.HasPrefix(lastArg, "-") {
printFlagSuggestions(lastArg, c.App.Flags, c.App.Writer)
if cmd != nil { if cmd != nil {
printFlagSuggestions(lastArg, cmd.Flags, c.App.Writer) printFlagSuggestions(lastArg, cmd.Flags, c.App.Writer)
return
} }
printFlagSuggestions(lastArg, c.App.Flags, c.App.Writer)
return return
} }
} }
if cmd != nil { if cmd != nil {
printCommandSuggestions(cmd.Subcommands, c.App.Writer) printCommandSuggestions(cmd.Subcommands, c.App.Writer)
} else { return
printCommandSuggestions(c.App.Commands, c.App.Writer)
} }
printCommandSuggestions(c.App.Commands, c.App.Writer)
} }
} }

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"os"
"runtime" "runtime"
"strings" "strings"
"testing" "testing"
@ -511,6 +512,36 @@ func TestShowSubcommandHelp_CommandUsageText(t *testing.T) {
} }
} }
func TestShowSubcommandHelp_MultiLine_CommandUsageText(t *testing.T) {
app := &App{
Commands: []*Command{
{
Name: "frobbly",
UsageText: `This is a
multi
line
UsageText`,
},
},
}
output := &bytes.Buffer{}
app.Writer = output
_ = app.Run([]string{"foo", "frobbly", "--help"})
expected := `USAGE:
This is a
multi
line
UsageText
`
if !strings.Contains(output.String(), expected) {
t.Errorf("expected output to include usage text; got: %q", output.String())
}
}
func TestShowSubcommandHelp_SubcommandUsageText(t *testing.T) { func TestShowSubcommandHelp_SubcommandUsageText(t *testing.T) {
app := &App{ app := &App{
Commands: []*Command{ Commands: []*Command{
@ -535,6 +566,40 @@ func TestShowSubcommandHelp_SubcommandUsageText(t *testing.T) {
} }
} }
func TestShowSubcommandHelp_MultiLine_SubcommandUsageText(t *testing.T) {
app := &App{
Commands: []*Command{
{
Name: "frobbly",
Subcommands: []*Command{
{
Name: "bobbly",
UsageText: `This is a
multi
line
UsageText`,
},
},
},
},
}
output := &bytes.Buffer{}
app.Writer = output
_ = app.Run([]string{"foo", "frobbly", "bobbly", "--help"})
expected := `USAGE:
This is a
multi
line
UsageText
`
if !strings.Contains(output.String(), expected) {
t.Errorf("expected output to include usage text; got: %q", output.String())
}
}
func TestShowAppHelp_HiddenCommand(t *testing.T) { func TestShowAppHelp_HiddenCommand(t *testing.T) {
app := &App{ app := &App{
Commands: []*Command{ Commands: []*Command{
@ -780,6 +845,56 @@ VERSION:
} }
} }
func TestShowAppHelp_UsageText(t *testing.T) {
app := &App{
UsageText: "This is a sinlge line of UsageText",
Commands: []*Command{
{
Name: "frobbly",
},
},
}
output := &bytes.Buffer{}
app.Writer = output
_ = app.Run([]string{"foo"})
if !strings.Contains(output.String(), "This is a sinlge line of UsageText") {
t.Errorf("expected output to include usage text; got: %q", output.String())
}
}
func TestShowAppHelp_MultiLine_UsageText(t *testing.T) {
app := &App{
UsageText: `This is a
multi
line
App UsageText`,
Commands: []*Command{
{
Name: "frobbly",
},
},
}
output := &bytes.Buffer{}
app.Writer = output
_ = app.Run([]string{"foo"})
expected := `USAGE:
This is a
multi
line
App UsageText
`
if !strings.Contains(output.String(), expected) {
t.Errorf("expected output to include usage text; got: %q", output.String())
}
}
func TestHideHelpCommand(t *testing.T) { func TestHideHelpCommand(t *testing.T) {
app := &App{ app := &App{
HideHelpCommand: true, HideHelpCommand: true,
@ -923,3 +1038,85 @@ func TestHideHelpCommand_WithSubcommands(t *testing.T) {
t.Errorf("Run returned unexpected error: %v", err) t.Errorf("Run returned unexpected error: %v", err)
} }
} }
func TestDefaultCompleteWithFlags(t *testing.T) {
origArgv := os.Args
t.Cleanup(func() {
os.Args = origArgv
})
for _, tc := range []struct {
name string
c *Context
cmd *Command
argv []string
expected string
}{
{
name: "empty",
c: &Context{App: &App{}},
cmd: &Command{},
argv: []string{"prog", "cmd"},
expected: "",
},
{
name: "typical-flag-suggestion",
c: &Context{App: &App{
Name: "cmd",
Flags: []Flag{
&BoolFlag{Name: "happiness"},
&Int64Flag{Name: "everybody-jump-on"},
},
Commands: []*Command{
{Name: "putz"},
},
}},
cmd: &Command{
Flags: []Flag{
&BoolFlag{Name: "excitement"},
&StringFlag{Name: "hat-shape"},
},
},
argv: []string{"cmd", "--e", "--generate-bash-completion"},
expected: "--excitement\n",
},
{
name: "typical-command-suggestion",
c: &Context{App: &App{
Name: "cmd",
Flags: []Flag{
&BoolFlag{Name: "happiness"},
&Int64Flag{Name: "everybody-jump-on"},
},
}},
cmd: &Command{
Name: "putz",
Subcommands: []*Command{
{Name: "futz"},
},
Flags: []Flag{
&BoolFlag{Name: "excitement"},
&StringFlag{Name: "hat-shape"},
},
},
argv: []string{"cmd", "--generate-bash-completion"},
expected: "futz\n",
},
} {
t.Run(tc.name, func(ct *testing.T) {
writer := &bytes.Buffer{}
tc.c.App.Writer = writer
os.Args = tc.argv
f := DefaultCompleteWithFlags(tc.cmd)
f(tc.c)
written := writer.String()
if written != tc.expected {
ct.Errorf("written help does not match expected %q != %q", written, tc.expected)
}
})
}
}

View File

@ -193,8 +193,8 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) {
cliBuiltFilePath = "./internal/example-cli/built-example" cliBuiltFilePath = "./internal/example-cli/built-example"
helloSourceFilePath = "./internal/example-hello-world/example-hello-world.go" helloSourceFilePath = "./internal/example-hello-world/example-hello-world.go"
helloBuiltFilePath = "./internal/example-hello-world/built-example" helloBuiltFilePath = "./internal/example-hello-world/built-example"
desiredMinBinarySize = 1.8 desiredMinBinarySize = 1.9
desiredMaxBinarySize = 2.1 desiredMaxBinarySize = 2.2
badNewsEmoji = "🚨" badNewsEmoji = "🚨"
goodNewsEmoji = "✨" goodNewsEmoji = "✨"
checksPassedEmoji = "✅" checksPassedEmoji = "✅"

View File

@ -7,7 +7,7 @@ var AppHelpTemplate = `NAME:
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}} {{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
USAGE: USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} {{if .UsageText}}{{.UsageText | nindent 3 | trim}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
VERSION: VERSION:
{{.Version}}{{end}}{{end}}{{if .Description}} {{.Version}}{{end}}{{end}}{{if .Description}}
@ -39,7 +39,7 @@ var CommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}} {{.HelpName}} - {{.Usage}}
USAGE: USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} {{if .UsageText}}{{.UsageText | nindent 3 | trim}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
CATEGORY: CATEGORY:
{{.Category}}{{end}}{{if .Description}} {{.Category}}{{end}}{{if .Description}}
@ -59,7 +59,7 @@ var SubcommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}} {{.HelpName}} - {{.Usage}}
USAGE: USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}} {{if .UsageText}}{{.UsageText | nindent 3 | trim}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}}
DESCRIPTION: DESCRIPTION:
{{.Description | nindent 3 | trim}}{{end}} {{.Description | nindent 3 | trim}}{{end}}
@ -74,9 +74,9 @@ OPTIONS:
{{end}}{{end}} {{end}}{{end}}
` `
var MarkdownDocTemplate = `% {{ .App.Name }} {{ .SectionNum }} var MarkdownDocTemplate = `{{if gt .SectionNum 0}}% {{ .App.Name }} {{ .SectionNum }}
# NAME {{end}}# NAME
{{ .App.Name }}{{ if .App.Usage }} - {{ .App.Usage }}{{ end }} {{ .App.Name }}{{ if .App.Usage }} - {{ .App.Usage }}{{ end }}
@ -86,16 +86,18 @@ var MarkdownDocTemplate = `% {{ .App.Name }} {{ .SectionNum }}
{{ if .SynopsisArgs }} {{ if .SynopsisArgs }}
` + "```" + ` ` + "```" + `
{{ range $v := .SynopsisArgs }}{{ $v }}{{ end }}` + "```" + ` {{ range $v := .SynopsisArgs }}{{ $v }}{{ end }}` + "```" + `
{{ end }}{{ if .App.UsageText }} {{ end }}{{ if .App.Description }}
# DESCRIPTION # DESCRIPTION
{{ .App.UsageText }} {{ .App.Description }}
{{ end }} {{ end }}
**Usage**: **Usage**:
` + "```" + ` ` + "```" + `{{ if .App.UsageText }}
{{ .App.UsageText }}
{{ else }}
{{ .App.Name }} [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] {{ .App.Name }} [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
` + "```" + ` {{ end }}` + "```" + `
{{ if .GlobalArgs }} {{ if .GlobalArgs }}
# GLOBAL OPTIONS # GLOBAL OPTIONS
{{ range $v := .GlobalArgs }} {{ range $v := .GlobalArgs }}

View File

@ -3,7 +3,7 @@
.SH NAME .SH NAME
.PP .PP
greet \- Some app greet - Some app
.SH SYNOPSIS .SH SYNOPSIS
@ -14,9 +14,9 @@ greet
.RS .RS
.nf .nf
[\-\-another\-flag|\-b] [--another-flag|-b]
[\-\-flag|\-\-fl|\-f]=[value] [--flag|--fl|-f]=[value]
[\-\-socket|\-s]=[value] [--socket|-s]=[value]
.fi .fi
.RE .RE
@ -24,7 +24,7 @@ greet
.SH DESCRIPTION .SH DESCRIPTION
.PP .PP
app [first\_arg] [second\_arg] Description of the application.
.PP .PP
\fBUsage\fP: \fBUsage\fP:
@ -33,7 +33,7 @@ app [first\_arg] [second\_arg]
.RS .RS
.nf .nf
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] app [first_arg] [second_arg]
.fi .fi
.RE .RE
@ -41,13 +41,13 @@ greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
.SH GLOBAL OPTIONS .SH GLOBAL OPTIONS
.PP .PP
\fB\-\-another\-flag, \-b\fP: another usage text \fB--another-flag, -b\fP: another usage text
.PP .PP
\fB\-\-flag, \-\-fl, \-f\fP="": \fB--flag, --fl, -f\fP="":
.PP .PP
\fB\-\-socket, \-s\fP="": some 'usage' text (default: value) \fB--socket, -s\fP="": some 'usage' text (default: value)
.SH COMMANDS .SH COMMANDS
@ -56,23 +56,65 @@ greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
another usage test another usage test
.PP .PP
\fB\-\-another\-flag, \-b\fP: another usage text \fB--another-flag, -b\fP: another usage text
.PP .PP
\fB\-\-flag, \-\-fl, \-f\fP="": \fB--flag, --fl, -f\fP="":
.SS sub\-config, s, ss .SS sub-config, s, ss
.PP .PP
another usage test another usage test
.PP .PP
\fB\-\-sub\-command\-flag, \-s\fP: some usage text \fB--sub-command-flag, -s\fP: some usage text
.PP .PP
\fB\-\-sub\-flag, \-\-sub\-fl, \-s\fP="": \fB--sub-flag, --sub-fl, -s\fP="":
.SH info, i, in .SH info, i, in
.PP .PP
retrieve generic information retrieve generic information
.SH some\-command .SH some-command
.SH usage, u
.PP
standard usage text
.PP
.RS
.nf
Usage for the usage text
- formatted: Based on the specified ConfigMap and summon secrets.yml
- list: Inspect the environment for a specific process running on a Pod
- for_effect: Compare 'namespace' environment with 'local'
```
func() { ... }
```
Should be a part of the same code block
.fi
.RE
.PP
\fB--another-flag, -b\fP: another usage text
.PP
\fB--flag, --fl, -f\fP="":
.SS sub-usage, su
.PP
standard usage text
.PP
.RS
.PP
Single line of UsageText
.RE
.PP
\fB--sub-command-flag, -s\fP: some usage text

View File

@ -1,5 +1,3 @@
% greet 8
# NAME # NAME
greet - Some app greet - Some app
@ -16,12 +14,12 @@ greet
# DESCRIPTION # DESCRIPTION
app [first_arg] [second_arg] Description of the application.
**Usage**: **Usage**:
``` ```
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] app [first_arg] [second_arg]
``` ```
# GLOBAL OPTIONS # GLOBAL OPTIONS
@ -58,3 +56,29 @@ retrieve generic information
## some-command ## some-command
## usage, u
standard usage text
Usage for the usage text
- formatted: Based on the specified ConfigMap and summon secrets.yml
- list: Inspect the environment for a specific process running on a Pod
- for_effect: Compare 'namespace' environment with 'local'
```
func() { ... }
```
Should be a part of the same code block
**--another-flag, -b**: another usage text
**--flag, --fl, -f**="":
### sub-usage, su
standard usage text
>Single line of UsageText
**--sub-command-flag, -s**: some usage text

View File

@ -1,5 +1,3 @@
% greet 8
# NAME # NAME
greet - Some app greet - Some app
@ -16,12 +14,12 @@ greet
# DESCRIPTION # DESCRIPTION
app [first_arg] [second_arg] Description of the application.
**Usage**: **Usage**:
``` ```
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] app [first_arg] [second_arg]
``` ```
# GLOBAL OPTIONS # GLOBAL OPTIONS
@ -58,3 +56,29 @@ retrieve generic information
## some-command ## some-command
## usage, u
standard usage text
Usage for the usage text
- formatted: Based on the specified ConfigMap and summon secrets.yml
- list: Inspect the environment for a specific process running on a Pod
- for_effect: Compare 'namespace' environment with 'local'
```
func() { ... }
```
Should be a part of the same code block
**--another-flag, -b**: another usage text
**--flag, --fl, -f**="":
### sub-usage, su
standard usage text
>Single line of UsageText
**--sub-command-flag, -s**: some usage text

View File

@ -1,5 +1,3 @@
% greet 8
# NAME # NAME
greet - Some app greet - Some app
@ -16,12 +14,12 @@ greet
# DESCRIPTION # DESCRIPTION
app [first_arg] [second_arg] Description of the application.
**Usage**: **Usage**:
``` ```
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] app [first_arg] [second_arg]
``` ```
# GLOBAL OPTIONS # GLOBAL OPTIONS

View File

@ -1,5 +1,3 @@
% greet 8
# NAME # NAME
greet - Some app greet - Some app
@ -10,12 +8,12 @@ greet
# DESCRIPTION # DESCRIPTION
app [first_arg] [second_arg] Description of the application.
**Usage**: **Usage**:
``` ```
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] app [first_arg] [second_arg]
``` ```
# COMMANDS # COMMANDS
@ -43,3 +41,29 @@ retrieve generic information
## some-command ## some-command
## usage, u
standard usage text
Usage for the usage text
- formatted: Based on the specified ConfigMap and summon secrets.yml
- list: Inspect the environment for a specific process running on a Pod
- for_effect: Compare 'namespace' environment with 'local'
```
func() { ... }
```
Should be a part of the same code block
**--another-flag, -b**: another usage text
**--flag, --fl, -f**="":
### sub-usage, su
standard usage text
>Single line of UsageText
**--sub-command-flag, -s**: some usage text

84
testdata/expected-doc-no-usagetext.md vendored Normal file
View File

@ -0,0 +1,84 @@
# NAME
greet - Some app
# SYNOPSIS
greet
```
[--another-flag|-b]
[--flag|--fl|-f]=[value]
[--socket|-s]=[value]
```
# DESCRIPTION
Description of the application.
**Usage**:
```
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
```
# GLOBAL OPTIONS
**--another-flag, -b**: another usage text
**--flag, --fl, -f**="":
**--socket, -s**="": some 'usage' text (default: value)
# COMMANDS
## config, c
another usage test
**--another-flag, -b**: another usage text
**--flag, --fl, -f**="":
### sub-config, s, ss
another usage test
**--sub-command-flag, -s**: some usage text
**--sub-flag, --sub-fl, -s**="":
## info, i, in
retrieve generic information
## some-command
## usage, u
standard usage text
Usage for the usage text
- formatted: Based on the specified ConfigMap and summon secrets.yml
- list: Inspect the environment for a specific process running on a Pod
- for_effect: Compare 'namespace' environment with 'local'
```
func() { ... }
```
Should be a part of the same code block
**--another-flag, -b**: another usage text
**--flag, --fl, -f**="":
### sub-usage, su
standard usage text
>Single line of UsageText
**--sub-command-flag, -s**: some usage text

View File

@ -2,7 +2,7 @@
function __fish_greet_no_subcommand --description 'Test if there has been any subcommand yet' function __fish_greet_no_subcommand --description 'Test if there has been any subcommand yet'
for i in (commandline -opc) for i in (commandline -opc)
if contains -- $i config c sub-config s ss info i in some-command if contains -- $i config c sub-config s ss info i in some-command usage u sub-usage su
return 1 return 1
end end
end end
@ -27,3 +27,10 @@ complete -c greet -n '__fish_seen_subcommand_from info i in' -f -l help -s h -d
complete -r -c greet -n '__fish_greet_no_subcommand' -a 'info i in' -d 'retrieve generic information' complete -r -c greet -n '__fish_greet_no_subcommand' -a 'info i in' -d 'retrieve generic information'
complete -c greet -n '__fish_seen_subcommand_from some-command' -f -l help -s h -d 'show help' complete -c greet -n '__fish_seen_subcommand_from some-command' -f -l help -s h -d 'show help'
complete -r -c greet -n '__fish_greet_no_subcommand' -a 'some-command' complete -r -c greet -n '__fish_greet_no_subcommand' -a 'some-command'
complete -c greet -n '__fish_seen_subcommand_from usage u' -f -l help -s h -d 'show help'
complete -r -c greet -n '__fish_greet_no_subcommand' -a 'usage u' -d 'standard usage text'
complete -c greet -n '__fish_seen_subcommand_from usage u' -l flag -s fl -s f -r
complete -c greet -n '__fish_seen_subcommand_from usage u' -f -l another-flag -s b -d 'another usage text'
complete -c greet -n '__fish_seen_subcommand_from sub-usage su' -f -l help -s h -d 'show help'
complete -r -c greet -n '__fish_seen_subcommand_from usage u' -a 'sub-usage su' -d 'standard usage text'
complete -c greet -n '__fish_seen_subcommand_from sub-usage su' -f -l sub-command-flag -s s -d 'some usage text'