Merge branch 'main' into master
This commit is contained in:
commit
7a231c5eb1
8
.github/ISSUE_TEMPLATE/question.md
vendored
8
.github/ISSUE_TEMPLATE/question.md
vendored
@ -1,12 +1,10 @@
|
||||
---
|
||||
name: ask a question
|
||||
about: ask us question - assume stackoverflow's guidelines apply here
|
||||
title: 'q: ( your question title goes here )'
|
||||
about: ask a question - assume stackoverflow's guidelines apply here
|
||||
title: your question title goes here
|
||||
labels: 'kind/question, status/triage, area/v2'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## my question is...
|
||||
|
||||
_**( Put the question text here )**_
|
||||
my question is...
|
||||
|
39
.github/ISSUE_TEMPLATE/v1-bug-report.md
vendored
39
.github/ISSUE_TEMPLATE/v1-bug-report.md
vendored
@ -1,28 +1,32 @@
|
||||
---
|
||||
name: v1 bug report
|
||||
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'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## my urfave/cli version is
|
||||
## My urfave/cli version is
|
||||
|
||||
_**( Put the version of urfave/cli that you are using here )**_
|
||||
|
||||
## Checklist
|
||||
|
||||
* [ ] 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 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.
|
||||
- [ ] 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/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.
|
||||
|
||||
## Dependency Management
|
||||
|
||||
- [ ] 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.
|
||||
<!--
|
||||
Delete any of the following that do not apply:
|
||||
-->
|
||||
|
||||
- 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
|
||||
|
||||
@ -34,23 +38,30 @@ Describe the steps or code required to reproduce the 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
|
||||
|
||||
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
|
||||
|
||||
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?
|
||||
|
||||
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
|
||||
|
||||
|
39
.github/ISSUE_TEMPLATE/v2-bug-report.md
vendored
39
.github/ISSUE_TEMPLATE/v2-bug-report.md
vendored
@ -1,28 +1,32 @@
|
||||
---
|
||||
name: v2 bug report
|
||||
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'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## my urfave/cli version is
|
||||
## My urfave/cli version is
|
||||
|
||||
_**( Put the version of urfave/cli that you are using here )**_
|
||||
|
||||
## Checklist
|
||||
|
||||
* [ ] 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 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.
|
||||
- [ ] 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/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.
|
||||
|
||||
## Dependency Management
|
||||
|
||||
- [ ] 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.
|
||||
<!--
|
||||
Delete any of the following that do not apply:
|
||||
-->
|
||||
|
||||
- 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
|
||||
|
||||
@ -34,23 +38,30 @@ Describe the steps or code required to reproduce the 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
|
||||
|
||||
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
|
||||
|
||||
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?
|
||||
|
||||
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
|
||||
|
||||
```
|
||||
|
16
.github/ISSUE_TEMPLATE/v2-feature-request.md
vendored
16
.github/ISSUE_TEMPLATE/v2-feature-request.md
vendored
@ -1,7 +1,7 @@
|
||||
---
|
||||
name: v2 feature request
|
||||
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'
|
||||
assignees: ''
|
||||
|
||||
@ -10,16 +10,19 @@ assignees: ''
|
||||
## Checklist
|
||||
|
||||
* [ ] 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.
|
||||
|
||||
## What problem does this solve?
|
||||
|
||||
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
|
||||
- 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
|
||||
- needing to type out the full flag name takes a long time, so I
|
||||
would like to suggest adding auto-complete
|
||||
- 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
|
||||
|
||||
@ -27,4 +30,5 @@ A detailed description of what you want to happen.
|
||||
|
||||
## 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.
|
||||
|
13
.github/pull_request_template.md
vendored
13
.github/pull_request_template.md
vendored
@ -8,10 +8,14 @@
|
||||
|
||||
_(REQUIRED)_
|
||||
|
||||
- [ ] bug
|
||||
- [ ] cleanup
|
||||
- [ ] documentation
|
||||
- [ ] feature
|
||||
<!--
|
||||
Delete any of the following that do not apply:
|
||||
-->
|
||||
|
||||
- bug
|
||||
- cleanup
|
||||
- documentation
|
||||
- feature
|
||||
|
||||
## What this PR does / why we need it:
|
||||
|
||||
@ -28,6 +32,7 @@ _(REQUIRED)_
|
||||
## Which issue(s) this PR fixes:
|
||||
|
||||
_(REQUIRED)_
|
||||
|
||||
<!--
|
||||
If this PR fixes one of more issues, list them here.
|
||||
One line each, like so:
|
||||
|
72
.github/stale.yml
vendored
72
.github/stale.yml
vendored
@ -1,63 +1,17 @@
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||
daysUntilStale: 90
|
||||
|
||||
# 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
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 365
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 90
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
- "kind/maintenance"
|
||||
|
||||
# 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
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: wontfix
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue or PR has been automatically marked as stale because it has not had
|
||||
recent activity. Please add a comment bumping this if you're still
|
||||
interested in it's resolution! Thanks for your help, please let us know
|
||||
if you need anything else.
|
||||
|
||||
# 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
|
||||
This issue 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.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
|
80
.github/workflows/cli.yml
vendored
80
.github/workflows/cli.yml
vendored
@ -3,43 +3,35 @@ name: Run Tests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- v1
|
||||
- main
|
||||
tags:
|
||||
- v2.*
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- v1
|
||||
- main
|
||||
|
||||
jobs:
|
||||
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
go: [1.12, 1.13, 1.14]
|
||||
go: [1.16.x, 1.17.x, 1.18.x]
|
||||
name: ${{ matrix.os }} @ Go ${{ matrix.go }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Set up Go ${{ matrix.go }}
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Set GOPATH, PATH and ENV
|
||||
run: |
|
||||
echo "::set-env name=GOPATH::$(dirname $GITHUB_WORKSPACE)"
|
||||
echo "::set-env name=GO111MODULE::on"
|
||||
echo "::set-env name=GOPROXY::https://proxy.golang.org"
|
||||
echo "::add-path::$(dirname $GITHUB_WORKSPACE)/bin"
|
||||
shell: bash
|
||||
- name: Set PATH
|
||||
run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}"
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: GOFMT Check
|
||||
if: matrix.go == 1.14 && matrix.os == 'ubuntu-latest'
|
||||
if: matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest'
|
||||
run: test -z $(gofmt -l .)
|
||||
|
||||
- name: vet
|
||||
@ -52,54 +44,40 @@ jobs:
|
||||
run: go run internal/build/build.go check-binary-size
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
if: success() && matrix.go == 1.14 && matrix.os == 'ubuntu-latest'
|
||||
uses: codecov/codecov-action@v1
|
||||
if: success() && matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest'
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
token: 0a8cc73b-bb7c-480b-8626-38a461643761
|
||||
fail_ci_if_error: true
|
||||
|
||||
test-docs:
|
||||
name: test-docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.14
|
||||
uses: actions/setup-go@v1
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.14
|
||||
go-version: 1.18.x
|
||||
|
||||
- name: Use Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
- name: Use Node.js 16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: '16'
|
||||
|
||||
- name: Set GOPATH, PATH and ENV
|
||||
run: |
|
||||
echo "::set-env name=GOPATH::$(dirname $GITHUB_WORKSPACE)"
|
||||
echo "::set-env name=GO111MODULE::on"
|
||||
echo "::set-env name=GOPROXY::https://proxy.golang.org"
|
||||
echo "::add-path::$(dirname $GITHUB_WORKSPACE)/bin"
|
||||
shell: bash
|
||||
- name: Set PATH
|
||||
run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}"
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
mkdir -p $GOPATH/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"
|
||||
chmod +x $GOPATH/bin/gfmrun
|
||||
run:
|
||||
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" &&
|
||||
chmod +x "${GITHUB_WORKSPACE}/.local/bin/gfmrun" &&
|
||||
npm install -g markdown-toc@1.2.0
|
||||
|
||||
- name: Run Tests (v1)
|
||||
if: contains(github.base_ref, 'v1')
|
||||
run: |
|
||||
go run internal/build/build.go gfmrun docs/v1/manual.md
|
||||
go run internal/build/build.go toc docs/v1/manual.md
|
||||
- name: gfmrun
|
||||
run: go run internal/build/build.go gfmrun docs/v2/manual.md
|
||||
|
||||
- name: Run Tests (v2)
|
||||
if: contains(github.base_ref, 'master')
|
||||
run: |
|
||||
go run internal/build/build.go gfmrun docs/v2/manual.md
|
||||
go run internal/build/build.go toc docs/v2/manual.md
|
||||
- name: toc
|
||||
run: go run internal/build/build.go toc docs/v2/manual.md
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,3 +5,6 @@ vendor
|
||||
.idea
|
||||
internal/*/built-example
|
||||
coverage.txt
|
||||
/.local/
|
||||
|
||||
*.exe
|
||||
|
12
README.md
12
README.md
@ -1,10 +1,10 @@
|
||||
cli
|
||||
===
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/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/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
|
||||
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 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)
|
||||
- `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
|
||||
|
||||
```
|
||||
$ GO111MODULE=on go get github.com/urfave/cli/v2
|
||||
$ go get github.com/urfave/cli/v2
|
||||
```
|
||||
|
||||
```go
|
||||
@ -44,7 +44,7 @@ import (
|
||||
### Using `v1` releases
|
||||
|
||||
```
|
||||
$ GO111MODULE=on go get github.com/urfave/cli
|
||||
$ go get github.com/urfave/cli
|
||||
```
|
||||
|
||||
```go
|
||||
@ -67,4 +67,4 @@ export PATH=$PATH:$GOPATH/bin
|
||||
|
||||
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
|
||||
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).
|
||||
|
@ -16,6 +16,11 @@ type MapInputSource struct {
|
||||
valueMap map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// NewMapInputSource creates a new MapInputSource for implementing custom input sources.
|
||||
func NewMapInputSource(file string, valueMap map[interface{}]interface{}) *MapInputSource {
|
||||
return &MapInputSource{file: file, valueMap: valueMap}
|
||||
}
|
||||
|
||||
// nestedVal checks if the name has '.' delimiters.
|
||||
// If so, it tries to traverse the tree by the '.' delimited sections to find
|
||||
// a nested value for the key.
|
||||
|
@ -6,14 +6,13 @@ import (
|
||||
)
|
||||
|
||||
func TestMapDuration(t *testing.T) {
|
||||
inputSource := &MapInputSource{
|
||||
file: "test",
|
||||
valueMap: map[interface{}]interface{}{
|
||||
inputSource := NewMapInputSource(
|
||||
"test",
|
||||
map[interface{}]interface{}{
|
||||
"duration_of_duration_type": time.Minute,
|
||||
"duration_of_string_type": "1m",
|
||||
"duration_of_int_type": 1000,
|
||||
},
|
||||
}
|
||||
})
|
||||
d, err := inputSource.Duration("duration_of_duration_type")
|
||||
expect(t, time.Minute, d)
|
||||
expect(t, nil, err)
|
||||
|
19
app.go
19
app.go
@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
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)
|
||||
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. "+
|
||||
@ -64,7 +64,7 @@ type App struct {
|
||||
Action ActionFunc
|
||||
// Execute this function if the proper command cannot be found
|
||||
CommandNotFound CommandNotFoundFunc
|
||||
// Execute this function if an usage error occurs
|
||||
// Execute this function if a usage error occurs
|
||||
OnUsageError OnUsageErrorFunc
|
||||
// Compilation date
|
||||
Compiled time.Time
|
||||
@ -72,6 +72,8 @@ type App struct {
|
||||
Authors []*Author
|
||||
// Copyright of the binary if any
|
||||
Copyright string
|
||||
// Reader reader to write input to (useful for tests)
|
||||
Reader io.Reader
|
||||
// Writer writer to write output to
|
||||
Writer io.Writer
|
||||
// ErrWriter writes error output
|
||||
@ -117,6 +119,7 @@ func NewApp() *App {
|
||||
BashComplete: DefaultAppComplete,
|
||||
Action: helpCommand.Action,
|
||||
Compiled: compileTime(),
|
||||
Reader: os.Stdin,
|
||||
Writer: os.Stdout,
|
||||
ErrWriter: os.Stderr,
|
||||
}
|
||||
@ -137,7 +140,7 @@ func (a *App) Setup() {
|
||||
}
|
||||
|
||||
if a.HelpName == "" {
|
||||
a.HelpName = filepath.Base(os.Args[0])
|
||||
a.HelpName = a.Name
|
||||
}
|
||||
|
||||
if a.Usage == "" {
|
||||
@ -160,6 +163,10 @@ func (a *App) Setup() {
|
||||
a.Compiled = compileTime()
|
||||
}
|
||||
|
||||
if a.Reader == nil {
|
||||
a.Reader = os.Stdin
|
||||
}
|
||||
|
||||
if a.Writer == nil {
|
||||
a.Writer = os.Stdout
|
||||
}
|
||||
@ -271,7 +278,7 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
cerr := checkRequiredFlags(a.Flags, context)
|
||||
cerr := context.checkRequiredFlags(a.Flags)
|
||||
if cerr != nil {
|
||||
_ = ShowAppHelp(context)
|
||||
return cerr
|
||||
@ -321,7 +328,7 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
|
||||
// RunAndExitOnError calls .Run() and exits non-zero if an error was returned
|
||||
//
|
||||
// Deprecated: instead you should return an error that fulfills cli.ExitCoder
|
||||
// to cli.App.Run. This will cause the application to exit with the given eror
|
||||
// to cli.App.Run. This will cause the application to exit with the given error
|
||||
// code in the cli.ExitCoder
|
||||
func (a *App) RunAndExitOnError() {
|
||||
if err := a.Run(os.Args); err != nil {
|
||||
@ -390,7 +397,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
cerr := checkRequiredFlags(a.Flags, context)
|
||||
cerr := context.checkRequiredFlags(a.Flags)
|
||||
if cerr != nil {
|
||||
_ = ShowSubcommandHelp(context)
|
||||
return cerr
|
||||
|
78
app_test.go
78
app_test.go
@ -315,7 +315,6 @@ func ExampleApp_Run_bashComplete_withMultipleLongFlag() {
|
||||
}
|
||||
|
||||
func ExampleApp_Run_bashComplete() {
|
||||
// set args for examples sake
|
||||
// set args for examples sake
|
||||
os.Args = []string{"greet", "--generate-bash-completion"}
|
||||
|
||||
@ -433,6 +432,12 @@ func TestApp_Command(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestApp_Setup_defaultsReader(t *testing.T) {
|
||||
app := &App{}
|
||||
app.Setup()
|
||||
expect(t, app.Reader, os.Stdin)
|
||||
}
|
||||
|
||||
func TestApp_Setup_defaultsWriter(t *testing.T) {
|
||||
app := &App{}
|
||||
app.Setup()
|
||||
@ -471,18 +476,18 @@ func TestApp_RunAsSubCommandIncorrectUsage(t *testing.T) {
|
||||
a := App{
|
||||
Name: "cmd",
|
||||
Flags: []Flag{
|
||||
&StringFlag{Name: "--foo"},
|
||||
&StringFlag{Name: "foo"},
|
||||
},
|
||||
Writer: bytes.NewBufferString(""),
|
||||
}
|
||||
|
||||
set := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
_ = set.Parse([]string{"", "---foo"})
|
||||
_ = set.Parse([]string{"", "-bar"})
|
||||
c := &Context{flagSet: set}
|
||||
|
||||
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) {
|
||||
@ -850,6 +855,15 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestApp_DefaultStdin(t *testing.T) {
|
||||
app := &App{}
|
||||
app.Setup()
|
||||
|
||||
if app.Reader != os.Stdin {
|
||||
t.Error("Default input reader not set.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApp_DefaultStdout(t *testing.T) {
|
||||
app := &App{}
|
||||
app.Setup()
|
||||
@ -859,6 +873,62 @@ func TestApp_DefaultStdout(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestApp_SetStdin(t *testing.T) {
|
||||
buf := make([]byte, 12)
|
||||
|
||||
app := &App{
|
||||
Name: "test",
|
||||
Reader: strings.NewReader("Hello World!"),
|
||||
Action: func(c *Context) error {
|
||||
_, err := c.App.Reader.Read(buf)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
err := app.Run([]string{"help"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Run error: %s", err)
|
||||
}
|
||||
|
||||
if string(buf) != "Hello World!" {
|
||||
t.Error("App did not read input from desired reader.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApp_SetStdin_Subcommand(t *testing.T) {
|
||||
buf := make([]byte, 12)
|
||||
|
||||
app := &App{
|
||||
Name: "test",
|
||||
Reader: strings.NewReader("Hello World!"),
|
||||
Commands: []*Command{
|
||||
{
|
||||
Name: "command",
|
||||
Subcommands: []*Command{
|
||||
{
|
||||
Name: "subcommand",
|
||||
Action: func(c *Context) error {
|
||||
_, err := c.App.Reader.Read(buf)
|
||||
return err
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := app.Run([]string{"test", "command", "subcommand"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Run error: %s", err)
|
||||
}
|
||||
|
||||
if string(buf) != "Hello World!" {
|
||||
t.Error("App did not read input from desired reader.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApp_SetStdout(t *testing.T) {
|
||||
var w bytes.Buffer
|
||||
|
||||
|
9
autocomplete/powershell_autocomplete.ps1
Normal file
9
autocomplete/powershell_autocomplete.ps1
Normal file
@ -0,0 +1,9 @@
|
||||
$fn = $($MyInvocation.MyCommand.Name)
|
||||
$name = $fn -replace "(.*)\.ps1$", '$1'
|
||||
Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock {
|
||||
param($commandName, $wordToComplete, $cursorPosition)
|
||||
$other = "$wordToComplete --generate-bash-completion"
|
||||
Invoke-Expression $other | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
|
||||
}
|
||||
}
|
@ -127,7 +127,7 @@ func (c *Command) Run(ctx *Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
cerr := checkRequiredFlags(c.Flags, context)
|
||||
cerr := context.checkRequiredFlags(c.Flags)
|
||||
if cerr != nil {
|
||||
_ = ShowCommandHelp(context, c.Name)
|
||||
return cerr
|
||||
@ -227,6 +227,7 @@ func (c *Command) startApp(ctx *Context) error {
|
||||
}
|
||||
|
||||
app.Usage = c.Usage
|
||||
app.UsageText = c.UsageText
|
||||
app.Description = c.Description
|
||||
app.ArgsUsage = c.ArgsUsage
|
||||
|
||||
@ -243,6 +244,7 @@ func (c *Command) startApp(ctx *Context) error {
|
||||
app.Version = ctx.App.Version
|
||||
app.HideVersion = true
|
||||
app.Compiled = ctx.App.Compiled
|
||||
app.Reader = ctx.App.Reader
|
||||
app.Writer = ctx.App.Writer
|
||||
app.ErrWriter = ctx.App.ErrWriter
|
||||
app.ExitErrHandler = ctx.App.ExitErrHandler
|
||||
|
136
context.go
136
context.go
@ -2,9 +2,7 @@ package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -53,20 +51,18 @@ func (c *Context) Set(name, value string) error {
|
||||
|
||||
// IsSet determines if the flag was actually set
|
||||
func (c *Context) IsSet(name string) bool {
|
||||
if fs := lookupFlagSet(name, c); fs != nil {
|
||||
if fs := lookupFlagSet(name, c); fs != nil {
|
||||
isSet := false
|
||||
fs.Visit(func(f *flag.Flag) {
|
||||
if f.Name == name {
|
||||
isSet = true
|
||||
}
|
||||
})
|
||||
if isSet {
|
||||
return true
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
isSet := false
|
||||
fs.Visit(func(f *flag.Flag) {
|
||||
if f.Name == name {
|
||||
isSet = true
|
||||
}
|
||||
})
|
||||
if isSet {
|
||||
return true
|
||||
}
|
||||
|
||||
f := lookupFlag(name, c)
|
||||
f := c.lookupFlag(name)
|
||||
if f == nil {
|
||||
return false
|
||||
}
|
||||
@ -108,7 +104,10 @@ func (c *Context) Lineage() []*Context {
|
||||
|
||||
// Value returns the value of the flag corresponding to `name`
|
||||
func (c *Context) Value(name string) interface{} {
|
||||
return c.flagSet.Lookup(name).Value.(flag.Getter).Get()
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
return fs.Lookup(name).Value.(flag.Getter).Get()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Args returns the command line arguments associated with the context.
|
||||
@ -122,7 +121,7 @@ func (c *Context) NArg() int {
|
||||
return c.Args().Len()
|
||||
}
|
||||
|
||||
func lookupFlag(name string, ctx *Context) Flag {
|
||||
func (ctx *Context) lookupFlag(name string) Flag {
|
||||
for _, c := range ctx.Lineage() {
|
||||
if c.Command == nil {
|
||||
continue
|
||||
@ -150,8 +149,11 @@ func lookupFlag(name string, ctx *Context) Flag {
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupFlagSet(name string, ctx *Context) *flag.FlagSet {
|
||||
func (ctx *Context) lookupFlagSet(name string) *flag.FlagSet {
|
||||
for _, c := range ctx.Lineage() {
|
||||
if c.flagSet == nil {
|
||||
continue
|
||||
}
|
||||
if f := c.flagSet.Lookup(name); f != nil {
|
||||
return c.flagSet
|
||||
}
|
||||
@ -160,89 +162,7 @@ func lookupFlagSet(name string, ctx *Context) *flag.FlagSet {
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
|
||||
switch ff.Value.(type) {
|
||||
case Serializer:
|
||||
_ = set.Set(name, ff.Value.(Serializer).Serialize())
|
||||
default:
|
||||
_ = set.Set(name, ff.Value.String())
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
|
||||
visited := make(map[string]bool)
|
||||
set.Visit(func(f *flag.Flag) {
|
||||
visited[f.Name] = true
|
||||
})
|
||||
for _, f := range flags {
|
||||
parts := f.Names()
|
||||
if len(parts) == 1 {
|
||||
continue
|
||||
}
|
||||
var ff *flag.Flag
|
||||
for _, name := range parts {
|
||||
name = strings.Trim(name, " ")
|
||||
if visited[name] {
|
||||
if ff != nil {
|
||||
return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name)
|
||||
}
|
||||
ff = set.Lookup(name)
|
||||
}
|
||||
}
|
||||
if ff == nil {
|
||||
continue
|
||||
}
|
||||
for _, name := range parts {
|
||||
name = strings.Trim(name, " ")
|
||||
if !visited[name] {
|
||||
copyFlag(name, ff, set)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeFlagNameVisitor(names *[]string) func(*flag.Flag) {
|
||||
return func(f *flag.Flag) {
|
||||
nameParts := strings.Split(f.Name, ",")
|
||||
name := strings.TrimSpace(nameParts[0])
|
||||
|
||||
for _, part := range nameParts {
|
||||
part = strings.TrimSpace(part)
|
||||
if len(part) > len(name) {
|
||||
name = part
|
||||
}
|
||||
}
|
||||
|
||||
if name != "" {
|
||||
*names = append(*names, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type requiredFlagsErr interface {
|
||||
error
|
||||
getMissingFlags() []string
|
||||
}
|
||||
|
||||
type errRequiredFlags struct {
|
||||
missingFlags []string
|
||||
}
|
||||
|
||||
func (e *errRequiredFlags) Error() string {
|
||||
numberOfMissingFlags := len(e.missingFlags)
|
||||
if numberOfMissingFlags == 1 {
|
||||
return fmt.Sprintf("Required flag %q not set", e.missingFlags[0])
|
||||
}
|
||||
joinedMissingFlags := strings.Join(e.missingFlags, ", ")
|
||||
return fmt.Sprintf("Required flags %q not set", joinedMissingFlags)
|
||||
}
|
||||
|
||||
func (e *errRequiredFlags) getMissingFlags() []string {
|
||||
return e.missingFlags
|
||||
}
|
||||
|
||||
func checkRequiredFlags(flags []Flag, context *Context) requiredFlagsErr {
|
||||
func (context *Context) checkRequiredFlags(flags []Flag) requiredFlagsErr {
|
||||
var missingFlags []string
|
||||
for _, f := range flags {
|
||||
if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() {
|
||||
@ -271,3 +191,21 @@ func checkRequiredFlags(flags []Flag, context *Context) requiredFlagsErr {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeFlagNameVisitor(names *[]string) func(*flag.Flag) {
|
||||
return func(f *flag.Flag) {
|
||||
nameParts := strings.Split(f.Name, ",")
|
||||
name := strings.TrimSpace(nameParts[0])
|
||||
|
||||
for _, part := range nameParts {
|
||||
part = strings.TrimSpace(part)
|
||||
if len(part) > len(name) {
|
||||
name = part
|
||||
}
|
||||
}
|
||||
|
||||
if name != "" {
|
||||
*names = append(*names, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,6 +112,8 @@ func TestContext_String(t *testing.T) {
|
||||
c := NewContext(nil, set, parentCtx)
|
||||
expect(t, c.String("myflag"), "hello world")
|
||||
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) {
|
||||
@ -136,6 +138,18 @@ func TestContext_Bool(t *testing.T) {
|
||||
expect(t, c.Bool("top-flag"), true)
|
||||
}
|
||||
|
||||
func TestContext_Value(t *testing.T) {
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.Int("myflag", 12, "doc")
|
||||
parentSet := flag.NewFlagSet("test", 0)
|
||||
parentSet.Int("top-flag", 13, "doc")
|
||||
parentCtx := NewContext(nil, parentSet, nil)
|
||||
c := NewContext(nil, set, parentCtx)
|
||||
expect(t, c.Value("myflag"), 12)
|
||||
expect(t, c.Value("top-flag"), 13)
|
||||
expect(t, c.Value("unknown-flag"), nil)
|
||||
}
|
||||
|
||||
func TestContext_Args(t *testing.T) {
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.Bool("myflag", false, "doc")
|
||||
@ -304,13 +318,13 @@ func TestContext_lookupFlagSet(t *testing.T) {
|
||||
_ = set.Parse([]string{"--local-flag"})
|
||||
_ = parentSet.Parse([]string{"--top-flag"})
|
||||
|
||||
fs := lookupFlagSet("top-flag", ctx)
|
||||
fs := ctx.lookupFlagSet("top-flag")
|
||||
expect(t, fs, parentCtx.flagSet)
|
||||
|
||||
fs = lookupFlagSet("local-flag", ctx)
|
||||
fs = ctx.lookupFlagSet("local-flag")
|
||||
expect(t, fs, ctx.flagSet)
|
||||
|
||||
if fs := lookupFlagSet("frob", ctx); fs != nil {
|
||||
if fs := ctx.lookupFlagSet("frob"); fs != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
@ -564,7 +578,7 @@ func TestCheckRequiredFlags(t *testing.T) {
|
||||
ctx.Command.Flags = test.flags
|
||||
|
||||
// logic under test
|
||||
err := checkRequiredFlags(test.flags, ctx)
|
||||
err := ctx.checkRequiredFlags(test.flags)
|
||||
|
||||
// assertions
|
||||
if test.expectedAnError && err == nil {
|
||||
|
67
docs.go
67
docs.go
@ -15,31 +15,39 @@ import (
|
||||
// The function errors if either parsing or writing of the string fails.
|
||||
func (a *App) ToMarkdown() (string, error) {
|
||||
var w bytes.Buffer
|
||||
if err := a.writeDocTemplate(&w); err != nil {
|
||||
if err := a.writeDocTemplate(&w, 0); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return w.String(), nil
|
||||
}
|
||||
|
||||
// ToMan creates a man page string for the `*App`
|
||||
// ToMan creates a man page string with section number for the `*App`
|
||||
// The function errors if either parsing or writing of the string fails.
|
||||
func (a *App) ToMan() (string, error) {
|
||||
func (a *App) ToManWithSection(sectionNumber int) (string, error) {
|
||||
var w bytes.Buffer
|
||||
if err := a.writeDocTemplate(&w); err != nil {
|
||||
if err := a.writeDocTemplate(&w, sectionNumber); err != nil {
|
||||
return "", err
|
||||
}
|
||||
man := md2man.Render(w.Bytes())
|
||||
return string(man), nil
|
||||
}
|
||||
|
||||
// ToMan creates a man page string for the `*App`
|
||||
// The function errors if either parsing or writing of the string fails.
|
||||
func (a *App) ToMan() (string, error) {
|
||||
man, err := a.ToManWithSection(8)
|
||||
return man, err
|
||||
}
|
||||
|
||||
type cliTemplate struct {
|
||||
App *App
|
||||
SectionNum int
|
||||
Commands []string
|
||||
GlobalArgs []string
|
||||
SynopsisArgs []string
|
||||
}
|
||||
|
||||
func (a *App) writeDocTemplate(w io.Writer) error {
|
||||
func (a *App) writeDocTemplate(w io.Writer, sectionNum int) error {
|
||||
const name = "cli"
|
||||
t, err := template.New(name).Parse(MarkdownDocTemplate)
|
||||
if err != nil {
|
||||
@ -47,6 +55,7 @@ func (a *App) writeDocTemplate(w io.Writer) error {
|
||||
}
|
||||
return t.ExecuteTemplate(w, name, &cliTemplate{
|
||||
App: a,
|
||||
SectionNum: sectionNum,
|
||||
Commands: prepareCommands(a.Commands, 0),
|
||||
GlobalArgs: prepareArgsWithValues(a.VisibleFlags()),
|
||||
SynopsisArgs: prepareArgsSynopsis(a.VisibleFlags()),
|
||||
@ -59,15 +68,16 @@ func prepareCommands(commands []*Command, level int) []string {
|
||||
if command.Hidden {
|
||||
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.Join(command.Names(), ", "),
|
||||
usage,
|
||||
usageText,
|
||||
)
|
||||
|
||||
flags := prepareArgsWithValues(command.Flags)
|
||||
@ -146,3 +156,40 @@ func flagDetails(flag DocGenerationFlag) string {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -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
|
||||
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
|
||||
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
|
||||
likely add you as a collaborator. As a collaborator you are given the ability
|
||||
|
@ -15,15 +15,18 @@ consider sending a PR to help improve this guide.
|
||||
|
||||
* [Flags before args](#flags-before-args)
|
||||
* [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)
|
||||
* [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)
|
||||
* [cli.Flag changed](#cliflag-changed)
|
||||
* [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)
|
||||
* [Full API Example](#full-api-example)
|
||||
|
||||
<!-- 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 *`
|
||||
|
||||
# Flag aliases are done differently.
|
||||
# Flag aliases are done differently
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
# 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
|
||||
|
||||
Compile the code and work through any errors. Most should
|
||||
|
@ -612,7 +612,7 @@ given sources.
|
||||
Here is a more complete sample of a command using YAML support:
|
||||
|
||||
<!-- {
|
||||
"args": ["test-cmd", "--help"],
|
||||
"args": ["--help"],
|
||||
"output": "--test value.*default: 0"
|
||||
} -->
|
||||
``` go
|
||||
|
@ -30,6 +30,7 @@ cli v2 manual
|
||||
+ [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)
|
||||
@ -44,7 +45,7 @@ cli v2 manual
|
||||
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).
|
||||
[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
|
||||
|
||||
@ -425,15 +426,17 @@ import (
|
||||
func main() {
|
||||
app := &cli.App{
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "lang, l",
|
||||
Value: "english",
|
||||
Usage: "Language for the greeting",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "config, c",
|
||||
Usage: "Load configuration from `FILE`",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "lang",
|
||||
Aliases: []string{"l"},
|
||||
Value: "english",
|
||||
Usage: "Language for the greeting",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "config",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "Load configuration from `FILE`",
|
||||
},
|
||||
},
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
@ -511,7 +514,7 @@ func main() {
|
||||
```
|
||||
|
||||
If `EnvVars` contains more than one string, the first environment variable that
|
||||
resolves is used as the default.
|
||||
resolves is used.
|
||||
|
||||
<!-- {
|
||||
"args": ["--help"],
|
||||
@ -570,7 +573,8 @@ func main() {
|
||||
|
||||
app.Flags = []cli.Flag {
|
||||
&cli.StringFlag{
|
||||
Name: "password, p",
|
||||
Name: "password",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "password for the mysql database",
|
||||
FilePath: "/etc/mysql/password",
|
||||
},
|
||||
@ -623,7 +627,7 @@ given sources.
|
||||
Here is a more complete sample of a command using YAML support:
|
||||
|
||||
<!-- {
|
||||
"args": ["test-cmd", "--help"],
|
||||
"args": ["--help"],
|
||||
"output": "--test value.*default: 0"
|
||||
} -->
|
||||
``` go
|
||||
@ -645,7 +649,7 @@ func main() {
|
||||
|
||||
app := &cli.App{
|
||||
Action: func(c *cli.Context) error {
|
||||
fmt.Println("yaml ist rad")
|
||||
fmt.Println("--test value.*default: 0")
|
||||
return nil
|
||||
},
|
||||
Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")),
|
||||
@ -670,8 +674,10 @@ Take for example this app that requires the `lang` flag:
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
@ -1222,6 +1228,23 @@ source path/to/autocomplete/zsh_autocomplete
|
||||
#### ZSH custom auto-complete example
|
||||
![](/docs/v2/images/custom-zsh-autocomplete.gif)
|
||||
|
||||
#### PowerShell Support
|
||||
Auto-completion for PowerShell is also supported using the `autocomplete/powershell_autocomplete.ps1`
|
||||
file included in this repo.
|
||||
|
||||
Rename the script to `<my program>.ps1` and move it anywhere in your file system.
|
||||
The location of script does not matter, only the file name of the script has to match
|
||||
the your program's binary name.
|
||||
|
||||
To activate it, enter `& path/to/autocomplete/<my program>.ps1`
|
||||
|
||||
To persist across new shells, open the PowerShell profile (with `code $profile` or `notepad $profile`)
|
||||
and add the line:
|
||||
```
|
||||
& path/to/autocomplete/<my program>.ps1
|
||||
```
|
||||
|
||||
|
||||
### Generated Help Text
|
||||
|
||||
The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked
|
||||
@ -1309,7 +1332,8 @@ import (
|
||||
|
||||
func main() {
|
||||
cli.HelpFlag = &cli.BoolFlag{
|
||||
Name: "haaaaalp", Aliases: []string{"halp"},
|
||||
Name: "haaaaalp",
|
||||
Aliases: []string{"halp"},
|
||||
Usage: "HALP",
|
||||
EnvVars: []string{"SHOW_HALP", "HALPPLZ"},
|
||||
}
|
||||
@ -1344,7 +1368,8 @@ import (
|
||||
|
||||
func main() {
|
||||
cli.VersionFlag = &cli.BoolFlag{
|
||||
Name: "print-version", Aliases: []string{"V"},
|
||||
Name: "print-version",
|
||||
Aliases: []string{"V"},
|
||||
Usage: "print only the version",
|
||||
}
|
||||
|
||||
|
194
docs_test.go
194
docs_test.go
@ -2,6 +2,7 @@ package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
@ -66,8 +67,50 @@ func testApp() *App {
|
||||
}, {
|
||||
Name: "hidden-command",
|
||||
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.Description = `Description of the application.`
|
||||
app.Usage = "Some app"
|
||||
app.Authors = []*Author{
|
||||
{Name: "Harrison", Email: "harrison@lolwut.com"},
|
||||
@ -76,13 +119,13 @@ func testApp() *App {
|
||||
return app
|
||||
}
|
||||
|
||||
func expectFileContent(t *testing.T, file, expected string) {
|
||||
func expectFileContent(t *testing.T, file, got string) {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
// Ignore windows line endings
|
||||
// TODO: Replace with bytes.ReplaceAll when support for Go 1.11 is dropped
|
||||
data = bytes.Replace(data, []byte("\r\n"), []byte("\n"), -1)
|
||||
expect(t, err, nil)
|
||||
expect(t, string(data), expected)
|
||||
expect(t, got, string(data))
|
||||
}
|
||||
|
||||
func TestToMarkdownFull(t *testing.T) {
|
||||
@ -136,6 +179,19 @@ func TestToMarkdownNoAuthors(t *testing.T) {
|
||||
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) {
|
||||
// Given
|
||||
app := testApp()
|
||||
@ -147,3 +203,137 @@ func TestToMan(t *testing.T) {
|
||||
expect(t, err, nil)
|
||||
expectFileContent(t, "testdata/expected-doc-full.man", res)
|
||||
}
|
||||
|
||||
func TestToManParseError(t *testing.T) {
|
||||
// Given
|
||||
app := testApp()
|
||||
|
||||
// When
|
||||
// temporarily change the global variable for testing
|
||||
tmp := MarkdownDocTemplate
|
||||
MarkdownDocTemplate = `{{ .App.Name`
|
||||
_, err := app.ToMan()
|
||||
MarkdownDocTemplate = tmp
|
||||
|
||||
// Then
|
||||
expect(t, err, errors.New(`template: cli:1: unclosed action`))
|
||||
}
|
||||
|
||||
func TestToManWithSection(t *testing.T) {
|
||||
// Given
|
||||
app := testApp()
|
||||
|
||||
// When
|
||||
res, err := app.ToManWithSection(8)
|
||||
|
||||
// Then
|
||||
expect(t, err, nil)
|
||||
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")
|
||||
})
|
||||
}
|
||||
|
22
errors.go
22
errors.go
@ -47,6 +47,28 @@ func (m *multiError) Errors() []error {
|
||||
return errs
|
||||
}
|
||||
|
||||
type requiredFlagsErr interface {
|
||||
error
|
||||
getMissingFlags() []string
|
||||
}
|
||||
|
||||
type errRequiredFlags struct {
|
||||
missingFlags []string
|
||||
}
|
||||
|
||||
func (e *errRequiredFlags) Error() string {
|
||||
numberOfMissingFlags := len(e.missingFlags)
|
||||
if numberOfMissingFlags == 1 {
|
||||
return fmt.Sprintf("Required flag %q not set", e.missingFlags[0])
|
||||
}
|
||||
joinedMissingFlags := strings.Join(e.missingFlags, ", ")
|
||||
return fmt.Sprintf("Required flags %q not set", joinedMissingFlags)
|
||||
}
|
||||
|
||||
func (e *errRequiredFlags) getMissingFlags() []string {
|
||||
return e.missingFlags
|
||||
}
|
||||
|
||||
// ErrorFormatter is the interface that will suitably format the error output
|
||||
type ErrorFormatter interface {
|
||||
Format(s fmt.State, verb rune)
|
||||
|
4
fish.go
4
fish.go
@ -171,6 +171,10 @@ func fishAddFileFlag(flag Flag, completion *strings.Builder) {
|
||||
if f.TakesFile {
|
||||
return
|
||||
}
|
||||
case *PathFlag:
|
||||
if f.TakesFile {
|
||||
return
|
||||
}
|
||||
}
|
||||
completion.WriteString(" -f")
|
||||
}
|
||||
|
@ -7,6 +7,10 @@ import (
|
||||
func TestFishCompletion(t *testing.T) {
|
||||
// Given
|
||||
app := testApp()
|
||||
app.Flags = append(app.Flags, &PathFlag{
|
||||
Name: "logfile",
|
||||
TakesFile: true,
|
||||
})
|
||||
|
||||
// When
|
||||
res, err := app.ToFishCompletion()
|
||||
|
66
flag.go
66
flag.go
@ -1,6 +1,7 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
@ -118,6 +119,14 @@ type DocGenerationFlag interface {
|
||||
GetValue() string
|
||||
}
|
||||
|
||||
// VisibleFlag is an interface that allows to check if a flag is visible
|
||||
type VisibleFlag interface {
|
||||
Flag
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
IsVisible() bool
|
||||
}
|
||||
|
||||
func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
|
||||
set := flag.NewFlagSet(name, flag.ContinueOnError)
|
||||
|
||||
@ -130,11 +139,52 @@ func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
|
||||
return set, nil
|
||||
}
|
||||
|
||||
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
|
||||
switch ff.Value.(type) {
|
||||
case Serializer:
|
||||
_ = set.Set(name, ff.Value.(Serializer).Serialize())
|
||||
default:
|
||||
_ = set.Set(name, ff.Value.String())
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
|
||||
visited := make(map[string]bool)
|
||||
set.Visit(func(f *flag.Flag) {
|
||||
visited[f.Name] = true
|
||||
})
|
||||
for _, f := range flags {
|
||||
parts := f.Names()
|
||||
if len(parts) == 1 {
|
||||
continue
|
||||
}
|
||||
var ff *flag.Flag
|
||||
for _, name := range parts {
|
||||
name = strings.Trim(name, " ")
|
||||
if visited[name] {
|
||||
if ff != nil {
|
||||
return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name)
|
||||
}
|
||||
ff = set.Lookup(name)
|
||||
}
|
||||
}
|
||||
if ff == nil {
|
||||
continue
|
||||
}
|
||||
for _, name := range parts {
|
||||
name = strings.Trim(name, " ")
|
||||
if !visited[name] {
|
||||
copyFlag(name, ff, set)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func visibleFlags(fl []Flag) []Flag {
|
||||
var visible []Flag
|
||||
for _, f := range fl {
|
||||
field := flagValue(f).FieldByName("Hidden")
|
||||
if !field.IsValid() || !field.Bool() {
|
||||
if vf, ok := f.(VisibleFlag); ok && vf.IsVisible() {
|
||||
visible = append(visible, f)
|
||||
}
|
||||
}
|
||||
@ -359,7 +409,11 @@ func stringifySliceFlag(usage string, names, defaultVals []string) string {
|
||||
}
|
||||
|
||||
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal))
|
||||
return fmt.Sprintf("%s\t%s", prefixedNames(names, placeholder), usageWithDefault)
|
||||
multiInputString := "(accepts multiple inputs)"
|
||||
if usageWithDefault != "" {
|
||||
multiInputString = "\t" + multiInputString
|
||||
}
|
||||
return fmt.Sprintf("%s\t%s%s", prefixedNames(names, placeholder), usageWithDefault, multiInputString)
|
||||
}
|
||||
|
||||
func hasFlag(flags []Flag, fl Flag) bool {
|
||||
@ -380,8 +434,10 @@ func flagFromEnvOrFile(envVars []string, filePath string) (val string, ok bool)
|
||||
}
|
||||
}
|
||||
for _, fileVar := range strings.Split(filePath, ",") {
|
||||
if data, err := ioutil.ReadFile(fileVar); err == nil {
|
||||
return string(data), true
|
||||
if fileVar != "" {
|
||||
if data, err := ioutil.ReadFile(fileVar); err == nil {
|
||||
return string(data), true
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
|
@ -58,6 +58,11 @@ func (f *BoolFlag) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *BoolFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f *BoolFlag) Apply(set *flag.FlagSet) error {
|
||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||
@ -87,7 +92,7 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error {
|
||||
// Bool looks up the value of a local BoolFlag, returns
|
||||
// false if not found
|
||||
func (c *Context) Bool(name string) bool {
|
||||
if fs := lookupFlagSet(name, c); fs != nil {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
return lookupBool(name, fs)
|
||||
}
|
||||
return false
|
||||
|
@ -58,6 +58,11 @@ func (f *DurationFlag) GetValue() string {
|
||||
return f.Value.String()
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *DurationFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f *DurationFlag) Apply(set *flag.FlagSet) error {
|
||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||
@ -86,7 +91,7 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) error {
|
||||
// Duration looks up the value of a local DurationFlag, returns
|
||||
// 0 if not found
|
||||
func (c *Context) Duration(name string) time.Duration {
|
||||
if fs := lookupFlagSet(name, c); fs != nil {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
return lookupDuration(name, fs)
|
||||
}
|
||||
return 0
|
||||
|
@ -58,12 +58,16 @@ func (f *Float64Flag) GetValue() string {
|
||||
return fmt.Sprintf("%f", f.Value)
|
||||
}
|
||||
|
||||
// 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
|
||||
func (f *Float64Flag) Apply(set *flag.FlagSet) error {
|
||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||
if val != "" {
|
||||
valFloat, err := strconv.ParseFloat(val, 10)
|
||||
|
||||
valFloat, err := strconv.ParseFloat(val, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %q as float64 value for flag %s: %s", val, f.Name, err)
|
||||
}
|
||||
@ -87,7 +91,7 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) error {
|
||||
// Float64 looks up the value of a local Float64Flag, returns
|
||||
// 0 if not found
|
||||
func (c *Context) Float64(name string) float64 {
|
||||
if fs := lookupFlagSet(name, c); fs != nil {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
return lookupFloat64(name, fs)
|
||||
}
|
||||
return 0
|
||||
|
@ -19,6 +19,16 @@ func NewFloat64Slice(defaults ...float64) *Float64Slice {
|
||||
return &Float64Slice{slice: append([]float64{}, defaults...)}
|
||||
}
|
||||
|
||||
// clone allocate a copy of self object
|
||||
func (f *Float64Slice) clone() *Float64Slice {
|
||||
n := &Float64Slice{
|
||||
slice: make([]float64, len(f.slice)),
|
||||
hasBeenSet: f.hasBeenSet,
|
||||
}
|
||||
copy(n.slice, f.slice)
|
||||
return n
|
||||
}
|
||||
|
||||
// Set parses the value into a float64 and appends it to the list of values
|
||||
func (f *Float64Slice) Set(value string) error {
|
||||
if !f.hasBeenSet {
|
||||
@ -117,6 +127,11 @@ func (f *Float64SliceFlag) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *Float64SliceFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error {
|
||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||
@ -129,15 +144,19 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
if f.Value == nil {
|
||||
f.Value = &Float64Slice{}
|
||||
}
|
||||
copyValue := f.Value.clone()
|
||||
for _, name := range f.Names() {
|
||||
if f.Value == nil {
|
||||
f.Value = &Float64Slice{}
|
||||
}
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
set.Var(copyValue, name, f.Usage)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -146,7 +165,7 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error {
|
||||
// Float64Slice looks up the value of a local Float64SliceFlag, returns
|
||||
// nil if not found
|
||||
func (c *Context) Float64Slice(name string) []float64 {
|
||||
if fs := lookupFlagSet(name, c); fs != nil {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
return lookupFloat64Slice(name, fs)
|
||||
}
|
||||
return nil
|
||||
|
@ -66,6 +66,11 @@ func (f *GenericFlag) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *GenericFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
}
|
||||
|
||||
// Apply takes the flagset and calls Set on the generic flag with the value
|
||||
// provided by the user for parsing by the flag
|
||||
func (f GenericFlag) Apply(set *flag.FlagSet) error {
|
||||
@ -89,7 +94,7 @@ func (f GenericFlag) Apply(set *flag.FlagSet) error {
|
||||
// Generic looks up the value of a local GenericFlag, returns
|
||||
// nil if not found
|
||||
func (c *Context) Generic(name string) interface{} {
|
||||
if fs := lookupFlagSet(name, c); fs != nil {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
return lookupGeneric(name, fs)
|
||||
}
|
||||
return nil
|
||||
|
@ -58,6 +58,11 @@ func (f *IntFlag) GetValue() string {
|
||||
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
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f *IntFlag) Apply(set *flag.FlagSet) error {
|
||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||
@ -87,7 +92,7 @@ func (f *IntFlag) Apply(set *flag.FlagSet) error {
|
||||
// Int looks up the value of a local IntFlag, returns
|
||||
// 0 if not found
|
||||
func (c *Context) Int(name string) int {
|
||||
if fs := lookupFlagSet(name, c); fs != nil {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
return lookupInt(name, fs)
|
||||
}
|
||||
return 0
|
||||
|
@ -58,6 +58,11 @@ func (f *Int64Flag) GetValue() string {
|
||||
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
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f *Int64Flag) Apply(set *flag.FlagSet) error {
|
||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||
@ -86,7 +91,7 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) error {
|
||||
// Int64 looks up the value of a local Int64Flag, returns
|
||||
// 0 if not found
|
||||
func (c *Context) Int64(name string) int64 {
|
||||
if fs := lookupFlagSet(name, c); fs != nil {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
return lookupInt64(name, fs)
|
||||
}
|
||||
return 0
|
||||
|
@ -19,6 +19,16 @@ func NewInt64Slice(defaults ...int64) *Int64Slice {
|
||||
return &Int64Slice{slice: append([]int64{}, defaults...)}
|
||||
}
|
||||
|
||||
// clone allocate a copy of self object
|
||||
func (i *Int64Slice) clone() *Int64Slice {
|
||||
n := &Int64Slice{
|
||||
slice: make([]int64, len(i.slice)),
|
||||
hasBeenSet: i.hasBeenSet,
|
||||
}
|
||||
copy(n.slice, i.slice)
|
||||
return n
|
||||
}
|
||||
|
||||
// Set parses the value into an integer and appends it to the list of values
|
||||
func (i *Int64Slice) Set(value string) error {
|
||||
if !i.hasBeenSet {
|
||||
@ -118,6 +128,11 @@ func (f *Int64SliceFlag) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *Int64SliceFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error {
|
||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||
@ -129,14 +144,18 @@ 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
|
||||
}
|
||||
|
||||
if f.Value == nil {
|
||||
f.Value = &Int64Slice{}
|
||||
}
|
||||
copyValue := f.Value.clone()
|
||||
for _, name := range f.Names() {
|
||||
if f.Value == nil {
|
||||
f.Value = &Int64Slice{}
|
||||
}
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
set.Var(copyValue, name, f.Usage)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -145,7 +164,10 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error {
|
||||
// Int64Slice looks up the value of a local Int64SliceFlag, returns
|
||||
// nil if not found
|
||||
func (c *Context) Int64Slice(name string) []int64 {
|
||||
return lookupInt64Slice(name, c.flagSet)
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
return lookupInt64Slice(name, fs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {
|
||||
|
@ -19,6 +19,16 @@ func NewIntSlice(defaults ...int) *IntSlice {
|
||||
return &IntSlice{slice: append([]int{}, defaults...)}
|
||||
}
|
||||
|
||||
// clone allocate a copy of self object
|
||||
func (i *IntSlice) clone() *IntSlice {
|
||||
n := &IntSlice{
|
||||
slice: make([]int, len(i.slice)),
|
||||
hasBeenSet: i.hasBeenSet,
|
||||
}
|
||||
copy(n.slice, i.slice)
|
||||
return n
|
||||
}
|
||||
|
||||
// TODO: Consistently have specific Set function for Int64 and Float64 ?
|
||||
// SetInt directly adds an integer to the list of values
|
||||
func (i *IntSlice) SetInt(value int) {
|
||||
@ -129,6 +139,11 @@ func (f *IntSliceFlag) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *IntSliceFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f *IntSliceFlag) Apply(set *flag.FlagSet) error {
|
||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||
@ -140,14 +155,18 @@ 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
|
||||
}
|
||||
|
||||
if f.Value == nil {
|
||||
f.Value = &IntSlice{}
|
||||
}
|
||||
copyValue := f.Value.clone()
|
||||
for _, name := range f.Names() {
|
||||
if f.Value == nil {
|
||||
f.Value = &IntSlice{}
|
||||
}
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
set.Var(copyValue, name, f.Usage)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -156,8 +175,8 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error {
|
||||
// IntSlice looks up the value of a local IntSliceFlag, returns
|
||||
// nil if not found
|
||||
func (c *Context) IntSlice(name string) []int {
|
||||
if fs := lookupFlagSet(name, c); fs != nil {
|
||||
return lookupIntSlice(name, c.flagSet)
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
return lookupIntSlice(name, fs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -54,6 +54,11 @@ func (f *PathFlag) GetValue() string {
|
||||
return f.Value
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *PathFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f *PathFlag) Apply(set *flag.FlagSet) error {
|
||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||
@ -75,7 +80,7 @@ func (f *PathFlag) Apply(set *flag.FlagSet) error {
|
||||
// Path looks up the value of a local PathFlag, returns
|
||||
// "" if not found
|
||||
func (c *Context) Path(name string) string {
|
||||
if fs := lookupFlagSet(name, c); fs != nil {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
return lookupPath(name, fs)
|
||||
}
|
||||
|
||||
|
@ -55,6 +55,11 @@ func (f *StringFlag) GetValue() string {
|
||||
return f.Value
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *StringFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f *StringFlag) Apply(set *flag.FlagSet) error {
|
||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||
@ -76,7 +81,7 @@ func (f *StringFlag) Apply(set *flag.FlagSet) error {
|
||||
// String looks up the value of a local StringFlag, returns
|
||||
// "" if not found
|
||||
func (c *Context) String(name string) string {
|
||||
if fs := lookupFlagSet(name, c); fs != nil {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
return lookupString(name, fs)
|
||||
}
|
||||
return ""
|
||||
@ -85,10 +90,7 @@ func (c *Context) String(name string) string {
|
||||
func lookupString(name string, set *flag.FlagSet) string {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
parsed, err := f.Value.String(), error(nil)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
parsed := f.Value.String()
|
||||
return parsed
|
||||
}
|
||||
return ""
|
||||
|
@ -18,6 +18,16 @@ func NewStringSlice(defaults ...string) *StringSlice {
|
||||
return &StringSlice{slice: append([]string{}, defaults...)}
|
||||
}
|
||||
|
||||
// clone allocate a copy of self object
|
||||
func (s *StringSlice) clone() *StringSlice {
|
||||
n := &StringSlice{
|
||||
slice: make([]string, len(s.slice)),
|
||||
hasBeenSet: s.hasBeenSet,
|
||||
}
|
||||
copy(n.slice, s.slice)
|
||||
return n
|
||||
}
|
||||
|
||||
// Set appends the string value to the list of values
|
||||
func (s *StringSlice) Set(value string) error {
|
||||
if !s.hasBeenSet {
|
||||
@ -114,6 +124,11 @@ func (f *StringSliceFlag) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *StringSliceFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
|
||||
|
||||
@ -124,7 +139,9 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
|
||||
}
|
||||
|
||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||
f.Value = &StringSlice{}
|
||||
if f.Value == nil {
|
||||
f.Value = &StringSlice{}
|
||||
}
|
||||
destination := f.Value
|
||||
if f.Destination != nil {
|
||||
destination = f.Destination
|
||||
@ -142,17 +159,15 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
|
||||
f.HasBeenSet = true
|
||||
}
|
||||
|
||||
if f.Value == nil {
|
||||
f.Value = &StringSlice{}
|
||||
}
|
||||
setValue := f.Destination
|
||||
if f.Destination == nil {
|
||||
setValue = f.Value.clone()
|
||||
}
|
||||
for _, name := range f.Names() {
|
||||
if f.Value == nil {
|
||||
f.Value = &StringSlice{}
|
||||
}
|
||||
|
||||
if f.Destination != nil {
|
||||
set.Var(f.Destination, name, f.Usage)
|
||||
continue
|
||||
}
|
||||
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
set.Var(setValue, name, f.Usage)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -161,7 +176,7 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
|
||||
// StringSlice looks up the value of a local StringSliceFlag, returns
|
||||
// nil if not found
|
||||
func (c *Context) StringSlice(name string) []string {
|
||||
if fs := lookupFlagSet(name, c); fs != nil {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
return lookupStringSlice(name, fs)
|
||||
}
|
||||
return nil
|
||||
|
225
flag_test.go
225
flag_test.go
@ -52,15 +52,21 @@ func TestBoolFlagApply_SetsAllNames(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 {
|
||||
s := NewIntSlice(defaults...)
|
||||
s.hasBeenSet = true
|
||||
s.hasBeenSet = false
|
||||
return *s
|
||||
}
|
||||
|
||||
newSetInt64Slice := func(defaults ...int64) Int64Slice {
|
||||
s := NewInt64Slice(defaults...)
|
||||
s.hasBeenSet = true
|
||||
s.hasBeenSet = false
|
||||
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: .*`},
|
||||
{"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,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: .*`},
|
||||
@ -340,11 +349,11 @@ var stringSliceFlagTests = []struct {
|
||||
value *StringSlice
|
||||
expected string
|
||||
}{
|
||||
{"foo", nil, NewStringSlice(""), "--foo value\t"},
|
||||
{"f", nil, NewStringSlice(""), "-f value\t"},
|
||||
{"f", nil, NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")"},
|
||||
{"test", nil, NewStringSlice("Something"), "--test value\t(default: \"Something\")"},
|
||||
{"dee", []string{"d"}, NewStringSlice("Inka", "Dinka", "dooo"), "--dee value, -d value\t(default: \"Inka\", \"Dinka\", \"dooo\")"},
|
||||
{"foo", nil, NewStringSlice(""), "--foo value\t(accepts multiple inputs)"},
|
||||
{"f", nil, NewStringSlice(""), "-f value\t(accepts multiple inputs)"},
|
||||
{"f", nil, NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")\t(accepts multiple inputs)"},
|
||||
{"test", nil, NewStringSlice("Something"), "--test value\t(default: \"Something\")\t(accepts multiple inputs)"},
|
||||
{"dee", []string{"d"}, NewStringSlice("Inka", "Dinka", "dooo"), "--dee value, -d value\t(default: \"Inka\", \"Dinka\", \"dooo\")\t(accepts multiple inputs)"},
|
||||
}
|
||||
|
||||
func TestStringSliceFlagHelpOutput(t *testing.T) {
|
||||
@ -386,6 +395,20 @@ func TestStringSliceFlagApply_SetsAllNames(t *testing.T) {
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestStringSliceFlagApply_UsesEnvValues(t *testing.T) {
|
||||
defer resetEnv(os.Environ())
|
||||
os.Clearenv()
|
||||
_ = os.Setenv("MY_GOAT", "vincent van goat,scape goat")
|
||||
var val StringSlice
|
||||
fl := StringSliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: &val}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
_ = fl.Apply(set)
|
||||
|
||||
err := set.Parse(nil)
|
||||
expect(t, err, nil)
|
||||
expect(t, val.Value(), NewStringSlice("vincent van goat", "scape goat").Value())
|
||||
}
|
||||
|
||||
func TestStringSliceFlagApply_DefaultValueWithDestination(t *testing.T) {
|
||||
defValue := []string{"UA", "US"}
|
||||
|
||||
@ -616,9 +639,9 @@ var intSliceFlagTests = []struct {
|
||||
value *IntSlice
|
||||
expected string
|
||||
}{
|
||||
{"heads", nil, NewIntSlice(), "--heads value\t"},
|
||||
{"H", nil, NewIntSlice(), "-H value\t"},
|
||||
{"H", []string{"heads"}, NewIntSlice(9, 3), "-H value, --heads value\t(default: 9, 3)"},
|
||||
{"heads", nil, NewIntSlice(), "--heads value\t(accepts multiple inputs)"},
|
||||
{"H", nil, NewIntSlice(), "-H value\t(accepts multiple inputs)"},
|
||||
{"H", []string{"heads"}, NewIntSlice(9, 3), "-H value, --heads value\t(default: 9, 3)\t(accepts multiple inputs)"},
|
||||
}
|
||||
|
||||
func TestIntSliceFlagHelpOutput(t *testing.T) {
|
||||
@ -660,16 +683,55 @@ func TestIntSliceFlagApply_SetsAllNames(t *testing.T) {
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestIntSliceFlagApply_ParentContext(t *testing.T) {
|
||||
_ = (&App{
|
||||
Flags: []Flag{
|
||||
&IntSliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewIntSlice(1, 2, 3)},
|
||||
},
|
||||
Commands: []*Command{
|
||||
{
|
||||
Name: "child",
|
||||
Action: func(ctx *Context) error {
|
||||
expected := []int{1, 2, 3}
|
||||
if !reflect.DeepEqual(ctx.IntSlice("numbers"), expected) {
|
||||
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.IntSlice("numbers"))
|
||||
}
|
||||
if !reflect.DeepEqual(ctx.IntSlice("n"), expected) {
|
||||
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.IntSlice("n"))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
}).Run([]string{"run", "child"})
|
||||
}
|
||||
|
||||
func TestIntSliceFlag_SetFromParentContext(t *testing.T) {
|
||||
fl := &IntSliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewIntSlice(1, 2, 3, 4)}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
_ = fl.Apply(set)
|
||||
ctx := &Context{
|
||||
parentContext: &Context{
|
||||
flagSet: set,
|
||||
},
|
||||
flagSet: flag.NewFlagSet("empty", 0),
|
||||
}
|
||||
expected := []int{1, 2, 3, 4}
|
||||
if !reflect.DeepEqual(ctx.IntSlice("numbers"), expected) {
|
||||
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.IntSlice("numbers"))
|
||||
}
|
||||
}
|
||||
|
||||
var int64SliceFlagTests = []struct {
|
||||
name string
|
||||
aliases []string
|
||||
value *Int64Slice
|
||||
expected string
|
||||
}{
|
||||
{"heads", nil, NewInt64Slice(), "--heads value\t"},
|
||||
{"H", nil, NewInt64Slice(), "-H value\t"},
|
||||
{"heads", nil, NewInt64Slice(), "--heads value\t(accepts multiple inputs)"},
|
||||
{"H", nil, NewInt64Slice(), "-H value\t(accepts multiple inputs)"},
|
||||
{"heads", []string{"H"}, NewInt64Slice(int64(2), int64(17179869184)),
|
||||
"--heads value, -H value\t(default: 2, 17179869184)"},
|
||||
"--heads value, -H value\t(default: 2, 17179869184)\t(accepts multiple inputs)"},
|
||||
}
|
||||
|
||||
func TestInt64SliceFlagHelpOutput(t *testing.T) {
|
||||
@ -702,6 +764,60 @@ func TestInt64SliceFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt64SliceFlagApply_ParentContext(t *testing.T) {
|
||||
_ = (&App{
|
||||
Flags: []Flag{
|
||||
&Int64SliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewInt64Slice(1, 2, 3)},
|
||||
},
|
||||
Commands: []*Command{
|
||||
{
|
||||
Name: "child",
|
||||
Action: func(ctx *Context) error {
|
||||
expected := []int64{1, 2, 3}
|
||||
if !reflect.DeepEqual(ctx.Int64Slice("numbers"), expected) {
|
||||
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Int64Slice("numbers"))
|
||||
}
|
||||
if !reflect.DeepEqual(ctx.Int64Slice("n"), expected) {
|
||||
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Int64Slice("n"))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
}).Run([]string{"run", "child"})
|
||||
}
|
||||
|
||||
func TestInt64SliceFlag_SetFromParentContext(t *testing.T) {
|
||||
fl := &Int64SliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewInt64Slice(1, 2, 3, 4)}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
_ = fl.Apply(set)
|
||||
ctx := &Context{
|
||||
parentContext: &Context{
|
||||
flagSet: set,
|
||||
},
|
||||
flagSet: flag.NewFlagSet("empty", 0),
|
||||
}
|
||||
expected := []int64{1, 2, 3, 4}
|
||||
if !reflect.DeepEqual(ctx.Int64Slice("numbers"), expected) {
|
||||
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Int64Slice("numbers"))
|
||||
}
|
||||
}
|
||||
func TestInt64SliceFlag_ReturnNil(t *testing.T) {
|
||||
fl := &Int64SliceFlag{}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
_ = fl.Apply(set)
|
||||
ctx := &Context{
|
||||
parentContext: &Context{
|
||||
flagSet: set,
|
||||
},
|
||||
flagSet: flag.NewFlagSet("empty", 0),
|
||||
}
|
||||
expected := []int64(nil)
|
||||
if !reflect.DeepEqual(ctx.Int64Slice("numbers"), expected) {
|
||||
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Int64Slice("numbers"))
|
||||
}
|
||||
}
|
||||
|
||||
var float64FlagTests = []struct {
|
||||
name string
|
||||
expected string
|
||||
@ -757,10 +873,10 @@ var float64SliceFlagTests = []struct {
|
||||
value *Float64Slice
|
||||
expected string
|
||||
}{
|
||||
{"heads", nil, NewFloat64Slice(), "--heads value\t"},
|
||||
{"H", nil, NewFloat64Slice(), "-H value\t"},
|
||||
{"heads", nil, NewFloat64Slice(), "--heads value\t(accepts multiple inputs)"},
|
||||
{"H", nil, NewFloat64Slice(), "-H value\t(accepts multiple inputs)"},
|
||||
{"heads", []string{"H"}, NewFloat64Slice(0.1234, -10.5),
|
||||
"--heads value, -H value\t(default: 0.1234, -10.5)"},
|
||||
"--heads value, -H value\t(default: 0.1234, -10.5)\t(accepts multiple inputs)"},
|
||||
}
|
||||
|
||||
func TestFloat64SliceFlagHelpOutput(t *testing.T) {
|
||||
@ -1866,3 +1982,80 @@ func TestTimestampFlagApply_Fail_Parse_Wrong_Time(t *testing.T) {
|
||||
err := set.Parse([]string{"--time", "2006-01-02T15:04:05Z"})
|
||||
expect(t, err, fmt.Errorf("invalid value \"2006-01-02T15:04:05Z\" for flag -time: parsing time \"2006-01-02T15:04:05Z\" as \"Jan 2, 2006 at 3:04pm (MST)\": cannot parse \"2006-01-02T15:04:05Z\" as \"Jan\""))
|
||||
}
|
||||
|
||||
type flagDefaultTestCase struct {
|
||||
name string
|
||||
flag Flag
|
||||
toParse []string
|
||||
expect string
|
||||
}
|
||||
|
||||
func TestFlagDefaultValue(t *testing.T) {
|
||||
cases := []*flagDefaultTestCase{
|
||||
&flagDefaultTestCase{
|
||||
name: "stringSclice",
|
||||
flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")},
|
||||
toParse: []string{"--flag", "parsed"},
|
||||
expect: `--flag value (default: "default1", "default2") (accepts multiple inputs)`,
|
||||
},
|
||||
&flagDefaultTestCase{
|
||||
name: "float64Sclice",
|
||||
flag: &Float64SliceFlag{Name: "flag", Value: NewFloat64Slice(1.1, 2.2)},
|
||||
toParse: []string{"--flag", "13.3"},
|
||||
expect: `--flag value (default: 1.1, 2.2) (accepts multiple inputs)`,
|
||||
},
|
||||
&flagDefaultTestCase{
|
||||
name: "int64Sclice",
|
||||
flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2)},
|
||||
toParse: []string{"--flag", "13"},
|
||||
expect: `--flag value (default: 1, 2) (accepts multiple inputs)`,
|
||||
},
|
||||
&flagDefaultTestCase{
|
||||
name: "intSclice",
|
||||
flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)},
|
||||
toParse: []string{"--flag", "13"},
|
||||
expect: `--flag value (default: 1, 2) (accepts multiple inputs)`,
|
||||
},
|
||||
&flagDefaultTestCase{
|
||||
name: "string",
|
||||
flag: &StringFlag{Name: "flag", Value: "default"},
|
||||
toParse: []string{"--flag", "parsed"},
|
||||
expect: `--flag value (default: "default")`,
|
||||
},
|
||||
&flagDefaultTestCase{
|
||||
name: "bool",
|
||||
flag: &BoolFlag{Name: "flag", Value: true},
|
||||
toParse: []string{"--flag", "false"},
|
||||
expect: `--flag (default: true)`,
|
||||
},
|
||||
&flagDefaultTestCase{
|
||||
name: "uint64",
|
||||
flag: &Uint64Flag{Name: "flag", Value: 1},
|
||||
toParse: []string{"--flag", "13"},
|
||||
expect: `--flag value (default: 1)`,
|
||||
},
|
||||
}
|
||||
for i, v := range cases {
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.SetOutput(ioutil.Discard)
|
||||
_ = v.flag.Apply(set)
|
||||
if err := set.Parse(v.toParse); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if got := v.flag.String(); got != v.expect {
|
||||
t.Errorf("TestFlagDefaultValue %d %s\nexpect:%s\ngot:%s", i, v.name, v.expect, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimestampFlagApply_WithDestination(t *testing.T) {
|
||||
var destination Timestamp
|
||||
expectedResult, _ := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
||||
fl := TimestampFlag{Name: "time", Aliases: []string{"t"}, Layout: time.RFC3339, Destination: &destination}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
_ = fl.Apply(set)
|
||||
|
||||
err := set.Parse([]string{"--time", "2006-01-02T15:04:05Z"})
|
||||
expect(t, err, nil)
|
||||
expect(t, *fl.Destination.timestamp, expectedResult)
|
||||
}
|
||||
|
@ -71,6 +71,7 @@ type TimestampFlag struct {
|
||||
Value *Timestamp
|
||||
DefaultText string
|
||||
HasBeenSet bool
|
||||
Destination *Timestamp
|
||||
}
|
||||
|
||||
// IsSet returns whether or not the flag has been set through env or file
|
||||
@ -113,6 +114,11 @@ func (f *TimestampFlag) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *TimestampFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
|
||||
if f.Layout == "" {
|
||||
@ -123,6 +129,10 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
|
||||
}
|
||||
f.Value.SetLayout(f.Layout)
|
||||
|
||||
if f.Destination != nil {
|
||||
f.Destination.SetLayout(f.Layout)
|
||||
}
|
||||
|
||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||
if err := f.Value.Set(val); err != nil {
|
||||
return fmt.Errorf("could not parse %q as timestamp value for flag %s: %s", val, f.Name, err)
|
||||
@ -131,6 +141,11 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
|
||||
}
|
||||
|
||||
for _, name := range f.Names() {
|
||||
if f.Destination != nil {
|
||||
set.Var(f.Destination, name, f.Usage)
|
||||
continue
|
||||
}
|
||||
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
}
|
||||
return nil
|
||||
@ -138,7 +153,7 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
|
||||
|
||||
// Timestamp gets the timestamp from a flag name
|
||||
func (c *Context) Timestamp(name string) *time.Time {
|
||||
if fs := lookupFlagSet(name, c); fs != nil {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
return lookupTimestamp(name, fs)
|
||||
}
|
||||
return nil
|
||||
|
@ -52,6 +52,11 @@ func (f *UintFlag) GetUsage() string {
|
||||
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
|
||||
func (f *UintFlag) Apply(set *flag.FlagSet) error {
|
||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||
@ -86,7 +91,7 @@ func (f *UintFlag) GetValue() string {
|
||||
// Uint looks up the value of a local UintFlag, returns
|
||||
// 0 if not found
|
||||
func (c *Context) Uint(name string) uint {
|
||||
if fs := lookupFlagSet(name, c); fs != nil {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
return lookupUint(name, fs)
|
||||
}
|
||||
return 0
|
||||
|
@ -52,6 +52,11 @@ func (f *Uint64Flag) GetUsage() string {
|
||||
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
|
||||
func (f *Uint64Flag) Apply(set *flag.FlagSet) error {
|
||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||
@ -86,7 +91,7 @@ func (f *Uint64Flag) GetValue() string {
|
||||
// Uint64 looks up the value of a local Uint64Flag, returns
|
||||
// 0 if not found
|
||||
func (c *Context) Uint64(name string) uint64 {
|
||||
if fs := lookupFlagSet(name, c); fs != nil {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
return lookupUint64(name, fs)
|
||||
}
|
||||
return 0
|
||||
|
2
funcs.go
2
funcs.go
@ -17,7 +17,7 @@ type ActionFunc func(*Context) error
|
||||
// CommandNotFoundFunc is executed if the proper command cannot be found
|
||||
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
|
||||
// original error messages. If this function is not set, the "Incorrect usage"
|
||||
// is displayed and the execution is interrupted.
|
||||
|
10
go.mod
10
go.mod
@ -1,9 +1,11 @@
|
||||
module github.com/urfave/cli/v2
|
||||
|
||||
go 1.11
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
github.com/BurntSushi/toml v1.1.0
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
|
18
go.sum
18
go.sum
@ -1,14 +1,12 @@
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
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/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
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.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/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=
|
||||
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
|
||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
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/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
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/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
|
30
help.go
30
help.go
@ -72,13 +72,13 @@ func ShowAppHelpAndExit(c *Context, exitCode int) {
|
||||
|
||||
// ShowAppHelp is an action that displays the help.
|
||||
func ShowAppHelp(c *Context) error {
|
||||
template := c.App.CustomAppHelpTemplate
|
||||
if template == "" {
|
||||
template = AppHelpTemplate
|
||||
tpl := c.App.CustomAppHelpTemplate
|
||||
if tpl == "" {
|
||||
tpl = AppHelpTemplate
|
||||
}
|
||||
|
||||
if c.App.ExtraInfo == nil {
|
||||
HelpPrinter(c.App.Writer, template, c.App)
|
||||
HelpPrinter(c.App.Writer, tpl, c.App)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -87,7 +87,7 @@ func ShowAppHelp(c *Context) error {
|
||||
"ExtraInfo": c.App.ExtraInfo,
|
||||
}
|
||||
}
|
||||
HelpPrinterCustom(c.App.Writer, template, c.App, customAppData())
|
||||
HelpPrinterCustom(c.App.Writer, tpl, c.App, customAppData())
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -215,6 +215,12 @@ func ShowCommandHelp(ctx *Context, command string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShowSubcommandHelpAndExit - Prints help for the given subcommand and exits with exit code.
|
||||
func ShowSubcommandHelpAndExit(c *Context, exitCode int) {
|
||||
_ = ShowSubcommandHelp(c)
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
// ShowSubcommandHelp prints help for the given subcommand
|
||||
func ShowSubcommandHelp(c *Context) error {
|
||||
if c == nil {
|
||||
@ -264,7 +270,10 @@ func ShowCommandCompletions(ctx *Context, command string) {
|
||||
// allow using arbitrary functions in template rendering.
|
||||
func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs map[string]interface{}) {
|
||||
funcMap := template.FuncMap{
|
||||
"join": strings.Join,
|
||||
"join": strings.Join,
|
||||
"indent": indent,
|
||||
"nindent": nindent,
|
||||
"trim": strings.TrimSpace,
|
||||
}
|
||||
for key, value := range customFuncs {
|
||||
funcMap[key] = value
|
||||
@ -367,3 +376,12 @@ func checkCommandCompletions(c *Context, name string) bool {
|
||||
ShowCommandCompletions(c, name)
|
||||
return true
|
||||
}
|
||||
|
||||
func indent(spaces int, v string) string {
|
||||
pad := strings.Repeat(" ", spaces)
|
||||
return pad + strings.Replace(v, "\n", "\n"+pad, -1)
|
||||
}
|
||||
|
||||
func nindent(spaces int, v string) string {
|
||||
return "\n" + indent(spaces, v)
|
||||
}
|
||||
|
130
help_test.go
130
help_test.go
@ -54,6 +54,22 @@ func Test_ShowAppHelp_HideVersion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ShowAppHelp_MultiLineDescription(t *testing.T) {
|
||||
output := new(bytes.Buffer)
|
||||
app := &App{Writer: output}
|
||||
|
||||
app.HideVersion = true
|
||||
app.Description = "multi\n line"
|
||||
|
||||
c := NewContext(app, nil, nil)
|
||||
|
||||
_ = ShowAppHelp(c)
|
||||
|
||||
if !bytes.Contains(output.Bytes(), []byte("DESCRIPTION:\n multi\n line")) {
|
||||
t.Errorf("expected\n%s\nto include\n%s", output.String(), "DESCRIPTION:\n multi\n line")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Help_Custom_Flags(t *testing.T) {
|
||||
oldFlag := HelpFlag
|
||||
defer func() {
|
||||
@ -495,6 +511,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) {
|
||||
app := &App{
|
||||
Commands: []*Command{
|
||||
@ -519,6 +565,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) {
|
||||
app := &App{
|
||||
Commands: []*Command{
|
||||
@ -764,6 +844,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) {
|
||||
app := &App{
|
||||
HideHelpCommand: true,
|
||||
|
@ -3,24 +3,17 @@ package cli
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
wd, _ = os.Getwd()
|
||||
)
|
||||
|
||||
func init() {
|
||||
_ = os.Setenv("CLI_TEMPLATE_REPANIC", "1")
|
||||
}
|
||||
|
||||
func expect(t *testing.T, a interface{}, b interface{}) {
|
||||
_, fn, line, _ := runtime.Caller(1)
|
||||
fn = strings.Replace(fn, wd+"/", "", -1)
|
||||
t.Helper()
|
||||
|
||||
if !reflect.DeepEqual(a, b) {
|
||||
t.Errorf("(%s:%d) Expected %v (type %v) - Got %v (type %v)", fn, line, b, reflect.TypeOf(b), a, reflect.TypeOf(a))
|
||||
t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
|
||||
}
|
||||
}
|
||||
|
@ -193,8 +193,8 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) {
|
||||
cliBuiltFilePath = "./internal/example-cli/built-example"
|
||||
helloSourceFilePath = "./internal/example-hello-world/example-hello-world.go"
|
||||
helloBuiltFilePath = "./internal/example-hello-world/built-example"
|
||||
desiredMinBinarySize = 2.0
|
||||
desiredMaxBinarySize = 2.1
|
||||
desiredMinBinarySize = 1.9
|
||||
desiredMaxBinarySize = 2.2
|
||||
badNewsEmoji = "🚨"
|
||||
goodNewsEmoji = "✨"
|
||||
checksPassedEmoji = "✅"
|
||||
|
26
template.go
26
template.go
@ -7,13 +7,13 @@ var AppHelpTemplate = `NAME:
|
||||
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
|
||||
|
||||
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}}{{end}}{{end}}{{if .Description}}
|
||||
|
||||
DESCRIPTION:
|
||||
{{.Description}}{{end}}{{if len .Authors}}
|
||||
{{.Description | nindent 3 | trim}}{{end}}{{if len .Authors}}
|
||||
|
||||
AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
|
||||
{{range $index, $author := .Authors}}{{if $index}}
|
||||
@ -39,13 +39,13 @@ var CommandHelpTemplate = `NAME:
|
||||
{{.HelpName}} - {{.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}}{{end}}{{if .Description}}
|
||||
|
||||
DESCRIPTION:
|
||||
{{.Description}}{{end}}{{if .VisibleFlags}}
|
||||
{{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlags}}
|
||||
|
||||
OPTIONS:
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
@ -59,10 +59,10 @@ var SubcommandHelpTemplate = `NAME:
|
||||
{{.HelpName}} - {{.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}}{{end}}
|
||||
{{.Description | nindent 3 | trim}}{{end}}
|
||||
|
||||
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
|
||||
{{.Name}}:{{range .VisibleCommands}}
|
||||
@ -74,9 +74,9 @@ OPTIONS:
|
||||
{{end}}{{end}}
|
||||
`
|
||||
|
||||
var MarkdownDocTemplate = `% {{ .App.Name }} 8
|
||||
var MarkdownDocTemplate = `{{if gt .SectionNum 0}}% {{ .App.Name }} {{ .SectionNum }}
|
||||
|
||||
# NAME
|
||||
{{end}}# NAME
|
||||
|
||||
{{ .App.Name }}{{ if .App.Usage }} - {{ .App.Usage }}{{ end }}
|
||||
|
||||
@ -86,16 +86,18 @@ var MarkdownDocTemplate = `% {{ .App.Name }} 8
|
||||
{{ if .SynopsisArgs }}
|
||||
` + "```" + `
|
||||
{{ range $v := .SynopsisArgs }}{{ $v }}{{ end }}` + "```" + `
|
||||
{{ end }}{{ if .App.UsageText }}
|
||||
{{ end }}{{ if .App.Description }}
|
||||
# DESCRIPTION
|
||||
|
||||
{{ .App.UsageText }}
|
||||
{{ .App.Description }}
|
||||
{{ end }}
|
||||
**Usage**:
|
||||
|
||||
` + "```" + `
|
||||
` + "```" + `{{ if .App.UsageText }}
|
||||
{{ .App.UsageText }}
|
||||
{{ else }}
|
||||
{{ .App.Name }} [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
|
||||
` + "```" + `
|
||||
{{ end }}` + "```" + `
|
||||
{{ if .GlobalArgs }}
|
||||
# GLOBAL OPTIONS
|
||||
{{ range $v := .GlobalArgs }}
|
||||
|
72
testdata/expected-doc-full.man
vendored
72
testdata/expected-doc-full.man
vendored
@ -3,7 +3,7 @@
|
||||
|
||||
.SH NAME
|
||||
.PP
|
||||
greet \- Some app
|
||||
greet - Some app
|
||||
|
||||
|
||||
.SH SYNOPSIS
|
||||
@ -14,9 +14,9 @@ greet
|
||||
.RS
|
||||
|
||||
.nf
|
||||
[\-\-another\-flag|\-b]
|
||||
[\-\-flag|\-\-fl|\-f]=[value]
|
||||
[\-\-socket|\-s]=[value]
|
||||
[--another-flag|-b]
|
||||
[--flag|--fl|-f]=[value]
|
||||
[--socket|-s]=[value]
|
||||
|
||||
.fi
|
||||
.RE
|
||||
@ -24,7 +24,7 @@ greet
|
||||
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
app [first\_arg] [second\_arg]
|
||||
Description of the application.
|
||||
|
||||
.PP
|
||||
\fBUsage\fP:
|
||||
@ -33,7 +33,7 @@ app [first\_arg] [second\_arg]
|
||||
.RS
|
||||
|
||||
.nf
|
||||
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
|
||||
app [first_arg] [second_arg]
|
||||
|
||||
.fi
|
||||
.RE
|
||||
@ -41,13 +41,13 @@ greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
|
||||
|
||||
.SH GLOBAL OPTIONS
|
||||
.PP
|
||||
\fB\-\-another\-flag, \-b\fP: another usage text
|
||||
\fB--another-flag, -b\fP: another usage text
|
||||
|
||||
.PP
|
||||
\fB\-\-flag, \-\-fl, \-f\fP="":
|
||||
\fB--flag, --fl, -f\fP="":
|
||||
|
||||
.PP
|
||||
\fB\-\-socket, \-s\fP="": some 'usage' text (default: value)
|
||||
\fB--socket, -s\fP="": some 'usage' text (default: value)
|
||||
|
||||
|
||||
.SH COMMANDS
|
||||
@ -56,23 +56,65 @@ greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
|
||||
another usage test
|
||||
|
||||
.PP
|
||||
\fB\-\-another\-flag, \-b\fP: another usage text
|
||||
\fB--another-flag, -b\fP: another usage text
|
||||
|
||||
.PP
|
||||
\fB\-\-flag, \-\-fl, \-f\fP="":
|
||||
\fB--flag, --fl, -f\fP="":
|
||||
|
||||
.SS sub\-config, s, ss
|
||||
.SS sub-config, s, ss
|
||||
.PP
|
||||
another usage test
|
||||
|
||||
.PP
|
||||
\fB\-\-sub\-command\-flag, \-s\fP: some usage text
|
||||
\fB--sub-command-flag, -s\fP: some usage text
|
||||
|
||||
.PP
|
||||
\fB\-\-sub\-flag, \-\-sub\-fl, \-s\fP="":
|
||||
\fB--sub-flag, --sub-fl, -s\fP="":
|
||||
|
||||
.SH info, i, in
|
||||
.PP
|
||||
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
|
||||
|
32
testdata/expected-doc-full.md
vendored
32
testdata/expected-doc-full.md
vendored
@ -1,5 +1,3 @@
|
||||
% greet 8
|
||||
|
||||
# NAME
|
||||
|
||||
greet - Some app
|
||||
@ -16,12 +14,12 @@ greet
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
app [first_arg] [second_arg]
|
||||
Description of the application.
|
||||
|
||||
**Usage**:
|
||||
|
||||
```
|
||||
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
|
||||
app [first_arg] [second_arg]
|
||||
```
|
||||
|
||||
# GLOBAL OPTIONS
|
||||
@ -58,3 +56,29 @@ 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
|
||||
|
32
testdata/expected-doc-no-authors.md
vendored
32
testdata/expected-doc-no-authors.md
vendored
@ -1,5 +1,3 @@
|
||||
% greet 8
|
||||
|
||||
# NAME
|
||||
|
||||
greet - Some app
|
||||
@ -16,12 +14,12 @@ greet
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
app [first_arg] [second_arg]
|
||||
Description of the application.
|
||||
|
||||
**Usage**:
|
||||
|
||||
```
|
||||
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
|
||||
app [first_arg] [second_arg]
|
||||
```
|
||||
|
||||
# GLOBAL OPTIONS
|
||||
@ -58,3 +56,29 @@ 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
|
||||
|
6
testdata/expected-doc-no-commands.md
vendored
6
testdata/expected-doc-no-commands.md
vendored
@ -1,5 +1,3 @@
|
||||
% greet 8
|
||||
|
||||
# NAME
|
||||
|
||||
greet - Some app
|
||||
@ -16,12 +14,12 @@ greet
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
app [first_arg] [second_arg]
|
||||
Description of the application.
|
||||
|
||||
**Usage**:
|
||||
|
||||
```
|
||||
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
|
||||
app [first_arg] [second_arg]
|
||||
```
|
||||
|
||||
# GLOBAL OPTIONS
|
||||
|
32
testdata/expected-doc-no-flags.md
vendored
32
testdata/expected-doc-no-flags.md
vendored
@ -1,5 +1,3 @@
|
||||
% greet 8
|
||||
|
||||
# NAME
|
||||
|
||||
greet - Some app
|
||||
@ -10,12 +8,12 @@ greet
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
app [first_arg] [second_arg]
|
||||
Description of the application.
|
||||
|
||||
**Usage**:
|
||||
|
||||
```
|
||||
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
|
||||
app [first_arg] [second_arg]
|
||||
```
|
||||
|
||||
# COMMANDS
|
||||
@ -43,3 +41,29 @@ 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
|
||||
|
84
testdata/expected-doc-no-usagetext.md
vendored
Normal file
84
testdata/expected-doc-no-usagetext.md
vendored
Normal 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
|
10
testdata/expected-fish-full.fish
vendored
10
testdata/expected-fish-full.fish
vendored
@ -2,7 +2,7 @@
|
||||
|
||||
function __fish_greet_no_subcommand --description 'Test if there has been any subcommand yet'
|
||||
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
|
||||
end
|
||||
end
|
||||
@ -12,6 +12,7 @@ end
|
||||
complete -c greet -n '__fish_greet_no_subcommand' -l socket -s s -r -d 'some \'usage\' text'
|
||||
complete -c greet -n '__fish_greet_no_subcommand' -f -l flag -s fl -s f -r
|
||||
complete -c greet -n '__fish_greet_no_subcommand' -f -l another-flag -s b -d 'another usage text'
|
||||
complete -c greet -n '__fish_greet_no_subcommand' -l logfile -r
|
||||
complete -c greet -n '__fish_greet_no_subcommand' -f -l help -s h -d 'show help'
|
||||
complete -c greet -n '__fish_greet_no_subcommand' -f -l version -s v -d 'print the version'
|
||||
complete -c greet -n '__fish_seen_subcommand_from config c' -f -l help -s h -d 'show help'
|
||||
@ -26,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 -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 -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'
|
||||
|
Loading…
Reference in New Issue
Block a user