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
|
name: ask a question
|
||||||
about: ask us question - assume stackoverflow's guidelines apply here
|
about: ask a question - assume stackoverflow's guidelines apply here
|
||||||
title: 'q: ( your question title goes here )'
|
title: your question title goes here
|
||||||
labels: 'kind/question, status/triage, area/v2'
|
labels: 'kind/question, status/triage, area/v2'
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## my question is...
|
my question is...
|
||||||
|
|
||||||
_**( Put the question text here )**_
|
|
||||||
|
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
|
name: v1 bug report
|
||||||
about: Create a report to help us fix v1 bugs
|
about: Create a report to help us fix v1 bugs
|
||||||
title: 'v1 bug: ( your bug title goes here )'
|
title: 'your bug title goes here'
|
||||||
labels: 'kind/bug, status/triage, area/v1'
|
labels: 'kind/bug, status/triage, area/v1'
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## my urfave/cli version is
|
## My urfave/cli version is
|
||||||
|
|
||||||
_**( Put the version of urfave/cli that you are using here )**_
|
_**( Put the version of urfave/cli that you are using here )**_
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
* [ ] Are you running the latest v1 release? The list of releases is [here](https://github.com/urfave/cli/releases).
|
- [ ] Are you running the latest v1 release? The list of releases is [here](https://github.com/urfave/cli/releases).
|
||||||
* [ ] Did you check the manual for your release? The v1 manual is [here](https://github.com/urfave/cli/blob/master/docs/v1/manual.md)
|
- [ ] Did you check the manual for your release? The v1 manual is [here](https://github.com/urfave/cli/blob/main/docs/v1/manual.md).
|
||||||
* [ ] Did you perform a search about this problem? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching.
|
- [ ] Did you perform a search about this problem? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching.
|
||||||
|
|
||||||
## Dependency Management
|
## Dependency Management
|
||||||
|
|
||||||
- [ ] My project is using go modules.
|
<!--
|
||||||
- [ ] My project is using vendoring.
|
Delete any of the following that do not apply:
|
||||||
- [ ] My project is automatically downloading the latest version.
|
-->
|
||||||
- [ ] I am unsure of what my dependency management setup is.
|
|
||||||
|
- My project is using go modules.
|
||||||
|
- My project is using vendoring.
|
||||||
|
- My project is automatically downloading the latest version.
|
||||||
|
- I am unsure of what my dependency management setup is.
|
||||||
|
|
||||||
## Describe the bug
|
## Describe the bug
|
||||||
|
|
||||||
@ -34,23 +38,30 @@ Describe the steps or code required to reproduce the behavior
|
|||||||
|
|
||||||
## Observed behavior
|
## Observed behavior
|
||||||
|
|
||||||
What did you see happen immediately after the reproduction steps above?
|
What did you see happen immediately after the reproduction steps
|
||||||
|
above?
|
||||||
|
|
||||||
## Expected behavior
|
## Expected behavior
|
||||||
|
|
||||||
What would you have expected to happen immediately after the reproduction steps above?
|
What would you have expected to happen immediately after the
|
||||||
|
reproduction steps above?
|
||||||
|
|
||||||
## Additional context
|
## Additional context
|
||||||
|
|
||||||
Add any other context about the problem here.
|
Add any other context about the problem here.
|
||||||
|
|
||||||
If the issue relates to a specific open source Github repo, please link that repo here.
|
If the issue relates to a specific open source Github repo, please
|
||||||
|
link that repo here.
|
||||||
|
|
||||||
If you can reproduce this issue with a public CI system, please link a failing build here.
|
If you can reproduce this issue with a public CI system, please
|
||||||
|
link a failing build here.
|
||||||
|
|
||||||
## Want to fix this yourself?
|
## Want to fix this yourself?
|
||||||
|
|
||||||
We'd love to have more contributors on this project! If the fix for this bug is easily explained and very small, free free to create a pull request for it. You'll want to base the PR off the `v1` branch, all `v1` bug fix releases will be made from that branch.
|
We'd love to have more contributors on this project! If the fix for
|
||||||
|
this bug is easily explained and very small, free free to create a
|
||||||
|
pull request for it. You'll want to base the PR off the `v1`
|
||||||
|
branch, all `v1` bug fix releases will be made from that branch.
|
||||||
|
|
||||||
## Run `go version` and paste its output here
|
## Run `go version` and paste its output here
|
||||||
|
|
||||||
|
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
|
name: v2 bug report
|
||||||
about: Create a report to help us fix v2 bugs
|
about: Create a report to help us fix v2 bugs
|
||||||
title: 'v2 bug: ( your bug title goes here )'
|
title: 'your bug title goes here'
|
||||||
labels: 'kind/bug, area/v2, status/triage'
|
labels: 'kind/bug, area/v2, status/triage'
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## my urfave/cli version is
|
## My urfave/cli version is
|
||||||
|
|
||||||
_**( Put the version of urfave/cli that you are using here )**_
|
_**( Put the version of urfave/cli that you are using here )**_
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
* [ ] Are you running the latest v2 release? The list of releases is [here](https://github.com/urfave/cli/releases).
|
- [ ] Are you running the latest v2 release? The list of releases is [here](https://github.com/urfave/cli/releases).
|
||||||
* [ ] Did you check the manual for your release? The v2 manual is [here](https://github.com/urfave/cli/blob/master/docs/v2/manual.md)
|
- [ ] Did you check the manual for your release? The v2 manual is [here](https://github.com/urfave/cli/blob/main/docs/v2/manual.md)
|
||||||
* [ ] Did you perform a search about this problem? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching.
|
- [ ] Did you perform a search about this problem? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching.
|
||||||
|
|
||||||
## Dependency Management
|
## Dependency Management
|
||||||
|
|
||||||
- [ ] My project is using go modules.
|
<!--
|
||||||
- [ ] My project is using vendoring.
|
Delete any of the following that do not apply:
|
||||||
- [ ] My project is automatically downloading the latest version.
|
-->
|
||||||
- [ ] I am unsure of what my dependency management setup is.
|
|
||||||
|
- My project is using go modules.
|
||||||
|
- My project is using vendoring.
|
||||||
|
- My project is automatically downloading the latest version.
|
||||||
|
- I am unsure of what my dependency management setup is.
|
||||||
|
|
||||||
## Describe the bug
|
## Describe the bug
|
||||||
|
|
||||||
@ -34,23 +38,30 @@ Describe the steps or code required to reproduce the behavior
|
|||||||
|
|
||||||
## Observed behavior
|
## Observed behavior
|
||||||
|
|
||||||
What did you see happen immediately after the reproduction steps above?
|
What did you see happen immediately after the reproduction steps
|
||||||
|
above?
|
||||||
|
|
||||||
## Expected behavior
|
## Expected behavior
|
||||||
|
|
||||||
What would you have expected to happen immediately after the reproduction steps above?
|
What would you have expected to happen immediately after the
|
||||||
|
reproduction steps above?
|
||||||
|
|
||||||
## Additional context
|
## Additional context
|
||||||
|
|
||||||
Add any other context about the problem here.
|
Add any other context about the problem here.
|
||||||
|
|
||||||
If the issue relates to a specific open source Github repo, please link that repo here.
|
If the issue relates to a specific open source Github repo, please
|
||||||
|
link that repo here.
|
||||||
|
|
||||||
If you can reproduce this issue with a public CI system, please link a failing build here.
|
If you can reproduce this issue with a public CI system, please
|
||||||
|
link a failing build here.
|
||||||
|
|
||||||
## Want to fix this yourself?
|
## Want to fix this yourself?
|
||||||
|
|
||||||
We'd love to have more contributors on this project! If the fix for this bug is easily explained and very small, free free to create a pull request for it.
|
We'd love to have more contributors on this project! If the fix for
|
||||||
|
this bug is easily explained and very small, free free to create a
|
||||||
|
pull request for it.
|
||||||
|
|
||||||
## Run `go version` and paste its output here
|
## Run `go version` and paste its output here
|
||||||
|
|
||||||
```
|
```
|
||||||
|
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
|
name: v2 feature request
|
||||||
about: Suggest an improvement for v2
|
about: Suggest an improvement for v2
|
||||||
title: 'v2 feature: ( your feature title goes here )'
|
title: 'your feature title goes here'
|
||||||
labels: 'type/feature, area/v2, status/triage'
|
labels: 'type/feature, area/v2, status/triage'
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
@ -10,16 +10,19 @@ assignees: ''
|
|||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
* [ ] Are you running the latest v2 release? The list of releases is [here](https://github.com/urfave/cli/releases).
|
* [ ] Are you running the latest v2 release? The list of releases is [here](https://github.com/urfave/cli/releases).
|
||||||
* [ ] Did you check the manual for your release? The v2 manual is [here](https://github.com/urfave/cli/blob/master/docs/v2/manual.md)
|
* [ ] Did you check the manual for your release? The v2 manual is [here](https://github.com/urfave/cli/blob/main/docs/v2/manual.md).
|
||||||
* [ ] Did you perform a search about this feature? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching.
|
* [ ] Did you perform a search about this feature? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching.
|
||||||
|
|
||||||
## What problem does this solve?
|
## What problem does this solve?
|
||||||
|
|
||||||
A clear and concise description of what problem this feature would solve. For example:
|
A clear and concise description of what problem this feature would solve. For example:
|
||||||
|
|
||||||
- needing to type out the full flag name takes a long time, so I would like to suggest adding auto-complete
|
- needing to type out the full flag name takes a long time, so I
|
||||||
- I use (osx, windows, linux) and would like support for (some existing feature) to be extended to my platform
|
would like to suggest adding auto-complete
|
||||||
- the terminal output for a particular error case is confusing, and I think it could be improved
|
- I use (osx, windows, linux) and would like support for (some
|
||||||
|
existing feature) to be extended to my platform
|
||||||
|
- the terminal output for a particular error case is confusing, and
|
||||||
|
I think it could be improved
|
||||||
|
|
||||||
## Solution description
|
## Solution description
|
||||||
|
|
||||||
@ -27,4 +30,5 @@ A detailed description of what you want to happen.
|
|||||||
|
|
||||||
## Describe alternatives you've considered
|
## Describe alternatives you've considered
|
||||||
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
A clear and concise description of any alternative solutions or
|
||||||
|
features you've considered.
|
||||||
|
13
.github/pull_request_template.md
vendored
13
.github/pull_request_template.md
vendored
@ -8,10 +8,14 @@
|
|||||||
|
|
||||||
_(REQUIRED)_
|
_(REQUIRED)_
|
||||||
|
|
||||||
- [ ] bug
|
<!--
|
||||||
- [ ] cleanup
|
Delete any of the following that do not apply:
|
||||||
- [ ] documentation
|
-->
|
||||||
- [ ] feature
|
|
||||||
|
- bug
|
||||||
|
- cleanup
|
||||||
|
- documentation
|
||||||
|
- feature
|
||||||
|
|
||||||
## What this PR does / why we need it:
|
## What this PR does / why we need it:
|
||||||
|
|
||||||
@ -28,6 +32,7 @@ _(REQUIRED)_
|
|||||||
## Which issue(s) this PR fixes:
|
## Which issue(s) this PR fixes:
|
||||||
|
|
||||||
_(REQUIRED)_
|
_(REQUIRED)_
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
If this PR fixes one of more issues, list them here.
|
If this PR fixes one of more issues, list them here.
|
||||||
One line each, like so:
|
One line each, like so:
|
||||||
|
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 becomes stale
|
||||||
|
daysUntilStale: 365
|
||||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
# Number of days of inactivity before a stale issue is closed
|
||||||
daysUntilStale: 90
|
daysUntilClose: 90
|
||||||
|
# Issues with these labels will never be considered stale
|
||||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
|
||||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
|
||||||
daysUntilClose: 30
|
|
||||||
|
|
||||||
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
|
||||||
onlyLabels: []
|
|
||||||
|
|
||||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
|
||||||
exemptLabels:
|
exemptLabels:
|
||||||
- pinned
|
- pinned
|
||||||
- security
|
- security
|
||||||
- "kind/maintenance"
|
# Label to use when marking an issue as stale
|
||||||
|
staleLabel: wontfix
|
||||||
# Set to true to ignore issues in a project (defaults to false)
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
exemptProjects: false
|
|
||||||
|
|
||||||
# Set to true to ignore issues in a milestone (defaults to false)
|
|
||||||
exemptMilestones: false
|
|
||||||
|
|
||||||
# Set to true to ignore issues with an assignee (defaults to false)
|
|
||||||
exemptAssignees: false
|
|
||||||
|
|
||||||
# Label to use when marking as stale
|
|
||||||
staleLabel: "status/stale"
|
|
||||||
|
|
||||||
# Comment to post when marking as stale. Set to `false` to disable
|
|
||||||
markComment: >
|
markComment: >
|
||||||
This issue or PR has been automatically marked as stale because it has not had
|
This issue has been automatically marked as stale because it has not had
|
||||||
recent activity. Please add a comment bumping this if you're still
|
recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
interested in it's resolution! Thanks for your help, please let us know
|
for your contributions.
|
||||||
if you need anything else.
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
|
closeComment: false
|
||||||
# Comment to post when removing the stale label.
|
|
||||||
unmarkComment: >
|
|
||||||
This issue or PR has been bumped and is no longer marked as stale! Feel free
|
|
||||||
to bump it again in the future, if it's still relevant.
|
|
||||||
|
|
||||||
# Comment to post when closing a stale Issue or Pull Request.
|
|
||||||
closeComment: >
|
|
||||||
Closing this as it has become stale.
|
|
||||||
|
|
||||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
|
||||||
limitPerRun: 30
|
|
||||||
|
|
||||||
# Limit to only `issues` or `pulls`
|
|
||||||
# only: issues
|
|
||||||
|
|
||||||
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
|
|
||||||
# pulls:
|
|
||||||
# daysUntilStale: 30
|
|
||||||
# markComment: >
|
|
||||||
# This pull request has been automatically marked as stale because it has not had
|
|
||||||
# recent activity. It will be closed if no further activity occurs. Thank you
|
|
||||||
# for your contributions.
|
|
||||||
|
|
||||||
# issues:
|
|
||||||
# exemptLabels:
|
|
||||||
# - confirmed
|
|
||||||
|
80
.github/workflows/cli.yml
vendored
80
.github/workflows/cli.yml
vendored
@ -3,43 +3,35 @@ name: Run Tests
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- main
|
||||||
- v1
|
tags:
|
||||||
|
- v2.*
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- main
|
||||||
- v1
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
test:
|
test:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
go: [1.12, 1.13, 1.14]
|
go: [1.16.x, 1.17.x, 1.18.x]
|
||||||
name: ${{ matrix.os }} @ Go ${{ matrix.go }}
|
name: ${{ matrix.os }} @ Go ${{ matrix.go }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Go ${{ matrix.go }}
|
- name: Set up Go ${{ matrix.go }}
|
||||||
uses: actions/setup-go@v1
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
- name: Set GOPATH, PATH and ENV
|
- name: Set PATH
|
||||||
run: |
|
run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}"
|
||||||
echo "::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: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v3
|
||||||
with:
|
|
||||||
ref: ${{ github.ref }}
|
|
||||||
|
|
||||||
- name: GOFMT Check
|
- name: GOFMT Check
|
||||||
if: matrix.go == 1.14 && matrix.os == 'ubuntu-latest'
|
if: matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest'
|
||||||
run: test -z $(gofmt -l .)
|
run: test -z $(gofmt -l .)
|
||||||
|
|
||||||
- name: vet
|
- name: vet
|
||||||
@ -52,54 +44,40 @@ jobs:
|
|||||||
run: go run internal/build/build.go check-binary-size
|
run: go run internal/build/build.go check-binary-size
|
||||||
|
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
if: success() && matrix.go == 1.14 && matrix.os == 'ubuntu-latest'
|
if: success() && matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest'
|
||||||
uses: codecov/codecov-action@v1
|
uses: codecov/codecov-action@v2
|
||||||
with:
|
with:
|
||||||
token: 0a8cc73b-bb7c-480b-8626-38a461643761
|
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
|
|
||||||
test-docs:
|
test-docs:
|
||||||
name: test-docs
|
name: test-docs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Go 1.14
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v1
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.14
|
go-version: 1.18.x
|
||||||
|
|
||||||
- name: Use Node.js 12.x
|
- name: Use Node.js 16
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 12.x
|
node-version: '16'
|
||||||
|
|
||||||
- name: Set GOPATH, PATH and ENV
|
- name: Set PATH
|
||||||
run: |
|
run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}"
|
||||||
echo "::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: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v3
|
||||||
with:
|
|
||||||
ref: ${{ github.ref }}
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: |
|
run:
|
||||||
mkdir -p $GOPATH/bin
|
mkdir -p "${GITHUB_WORKSPACE}/.local/bin" &&
|
||||||
curl -L -o $GOPATH/bin/gfmrun "https://github.com/urfave/gfmrun/releases/download/v1.2.14/gfmrun-$(go env GOOS)-amd64-v1.2.14"
|
curl -fsSL -o "${GITHUB_WORKSPACE}/.local/bin/gfmrun" "https://github.com/urfave/gfmrun/releases/download/v1.3.0/gfmrun-$(go env GOOS)-$(go env GOARCH)-v1.3.0" &&
|
||||||
chmod +x $GOPATH/bin/gfmrun
|
chmod +x "${GITHUB_WORKSPACE}/.local/bin/gfmrun" &&
|
||||||
npm install -g markdown-toc@1.2.0
|
npm install -g markdown-toc@1.2.0
|
||||||
|
|
||||||
- name: Run Tests (v1)
|
- name: gfmrun
|
||||||
if: contains(github.base_ref, 'v1')
|
run: go run internal/build/build.go gfmrun docs/v2/manual.md
|
||||||
run: |
|
|
||||||
go run internal/build/build.go gfmrun docs/v1/manual.md
|
|
||||||
go run internal/build/build.go toc docs/v1/manual.md
|
|
||||||
|
|
||||||
- name: Run Tests (v2)
|
- name: toc
|
||||||
if: contains(github.base_ref, 'master')
|
run: go run internal/build/build.go toc docs/v2/manual.md
|
||||||
run: |
|
|
||||||
go run internal/build/build.go gfmrun docs/v2/manual.md
|
|
||||||
go run internal/build/build.go toc docs/v2/manual.md
|
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,3 +5,6 @@ vendor
|
|||||||
.idea
|
.idea
|
||||||
internal/*/built-example
|
internal/*/built-example
|
||||||
coverage.txt
|
coverage.txt
|
||||||
|
/.local/
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
12
README.md
12
README.md
@ -1,10 +1,10 @@
|
|||||||
cli
|
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)
|
[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli)
|
||||||
[![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli)
|
[![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli)
|
||||||
[![codecov](https://codecov.io/gh/urfave/cli/branch/master/graph/badge.svg)](https://codecov.io/gh/urfave/cli)
|
[![codecov](https://codecov.io/gh/urfave/cli/branch/main/graph/badge.svg)](https://codecov.io/gh/urfave/cli)
|
||||||
|
|
||||||
cli is a simple, fast, and fun package for building command line apps in Go. The
|
cli is a simple, fast, and fun package for building command line apps in Go. The
|
||||||
goal is to enable developers to write fast and distributable command line
|
goal is to enable developers to write fast and distributable command line
|
||||||
@ -12,7 +12,7 @@ applications in an expressive way.
|
|||||||
|
|
||||||
## Usage Documentation
|
## Usage Documentation
|
||||||
|
|
||||||
Usage documentation exists for each major version. Don't know what version you're on? You're probably using the version from the `master` branch, which is currently `v2`.
|
Usage documentation exists for each major version. Don't know what version you're on? You're probably using the version from the `main` branch, which is currently `v2`.
|
||||||
|
|
||||||
- `v2` - [./docs/v2/manual.md](./docs/v2/manual.md)
|
- `v2` - [./docs/v2/manual.md](./docs/v2/manual.md)
|
||||||
- `v1` - [./docs/v1/manual.md](./docs/v1/manual.md)
|
- `v1` - [./docs/v1/manual.md](./docs/v1/manual.md)
|
||||||
@ -30,7 +30,7 @@ Go Modules are required when using this package. [See the go blog guide on using
|
|||||||
### Using `v2` releases
|
### Using `v2` releases
|
||||||
|
|
||||||
```
|
```
|
||||||
$ GO111MODULE=on go get github.com/urfave/cli/v2
|
$ go get github.com/urfave/cli/v2
|
||||||
```
|
```
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@ -44,7 +44,7 @@ import (
|
|||||||
### Using `v1` releases
|
### Using `v1` releases
|
||||||
|
|
||||||
```
|
```
|
||||||
$ GO111MODULE=on go get github.com/urfave/cli
|
$ go get github.com/urfave/cli
|
||||||
```
|
```
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@ -67,4 +67,4 @@ export PATH=$PATH:$GOPATH/bin
|
|||||||
|
|
||||||
cli is tested against multiple versions of Go on Linux, and against the latest
|
cli is tested against multiple versions of Go on Linux, and against the latest
|
||||||
released version of Go on OS X and Windows. This project uses Github Actions for
|
released version of Go on OS X and Windows. This project uses Github Actions for
|
||||||
builds. To see our currently supported go versions and platforms, look at the [./.github/workflows/cli.yml](https://github.com/urfave/cli/blob/master/.github/workflows/cli.yml).
|
builds. To see our currently supported go versions and platforms, look at the [./.github/workflows/cli.yml](https://github.com/urfave/cli/blob/main/.github/workflows/cli.yml).
|
||||||
|
@ -16,6 +16,11 @@ type MapInputSource struct {
|
|||||||
valueMap map[interface{}]interface{}
|
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.
|
// nestedVal checks if the name has '.' delimiters.
|
||||||
// If so, it tries to traverse the tree by the '.' delimited sections to find
|
// If so, it tries to traverse the tree by the '.' delimited sections to find
|
||||||
// a nested value for the key.
|
// a nested value for the key.
|
||||||
|
@ -6,14 +6,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestMapDuration(t *testing.T) {
|
func TestMapDuration(t *testing.T) {
|
||||||
inputSource := &MapInputSource{
|
inputSource := NewMapInputSource(
|
||||||
file: "test",
|
"test",
|
||||||
valueMap: map[interface{}]interface{}{
|
map[interface{}]interface{}{
|
||||||
"duration_of_duration_type": time.Minute,
|
"duration_of_duration_type": time.Minute,
|
||||||
"duration_of_string_type": "1m",
|
"duration_of_string_type": "1m",
|
||||||
"duration_of_int_type": 1000,
|
"duration_of_int_type": 1000,
|
||||||
},
|
})
|
||||||
}
|
|
||||||
d, err := inputSource.Duration("duration_of_duration_type")
|
d, err := inputSource.Duration("duration_of_duration_type")
|
||||||
expect(t, time.Minute, d)
|
expect(t, time.Minute, d)
|
||||||
expect(t, nil, err)
|
expect(t, nil, err)
|
||||||
|
19
app.go
19
app.go
@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
changeLogURL = "https://github.com/urfave/cli/blob/master/docs/CHANGELOG.md"
|
changeLogURL = "https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md"
|
||||||
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
|
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
|
||||||
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
|
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
|
||||||
errInvalidActionType = NewExitError("ERROR invalid Action type. "+
|
errInvalidActionType = NewExitError("ERROR invalid Action type. "+
|
||||||
@ -64,7 +64,7 @@ type App struct {
|
|||||||
Action ActionFunc
|
Action ActionFunc
|
||||||
// Execute this function if the proper command cannot be found
|
// Execute this function if the proper command cannot be found
|
||||||
CommandNotFound CommandNotFoundFunc
|
CommandNotFound CommandNotFoundFunc
|
||||||
// Execute this function if an usage error occurs
|
// Execute this function if a usage error occurs
|
||||||
OnUsageError OnUsageErrorFunc
|
OnUsageError OnUsageErrorFunc
|
||||||
// Compilation date
|
// Compilation date
|
||||||
Compiled time.Time
|
Compiled time.Time
|
||||||
@ -72,6 +72,8 @@ type App struct {
|
|||||||
Authors []*Author
|
Authors []*Author
|
||||||
// Copyright of the binary if any
|
// Copyright of the binary if any
|
||||||
Copyright string
|
Copyright string
|
||||||
|
// Reader reader to write input to (useful for tests)
|
||||||
|
Reader io.Reader
|
||||||
// Writer writer to write output to
|
// Writer writer to write output to
|
||||||
Writer io.Writer
|
Writer io.Writer
|
||||||
// ErrWriter writes error output
|
// ErrWriter writes error output
|
||||||
@ -117,6 +119,7 @@ func NewApp() *App {
|
|||||||
BashComplete: DefaultAppComplete,
|
BashComplete: DefaultAppComplete,
|
||||||
Action: helpCommand.Action,
|
Action: helpCommand.Action,
|
||||||
Compiled: compileTime(),
|
Compiled: compileTime(),
|
||||||
|
Reader: os.Stdin,
|
||||||
Writer: os.Stdout,
|
Writer: os.Stdout,
|
||||||
ErrWriter: os.Stderr,
|
ErrWriter: os.Stderr,
|
||||||
}
|
}
|
||||||
@ -137,7 +140,7 @@ func (a *App) Setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if a.HelpName == "" {
|
if a.HelpName == "" {
|
||||||
a.HelpName = filepath.Base(os.Args[0])
|
a.HelpName = a.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.Usage == "" {
|
if a.Usage == "" {
|
||||||
@ -160,6 +163,10 @@ func (a *App) Setup() {
|
|||||||
a.Compiled = compileTime()
|
a.Compiled = compileTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.Reader == nil {
|
||||||
|
a.Reader = os.Stdin
|
||||||
|
}
|
||||||
|
|
||||||
if a.Writer == nil {
|
if a.Writer == nil {
|
||||||
a.Writer = os.Stdout
|
a.Writer = os.Stdout
|
||||||
}
|
}
|
||||||
@ -271,7 +278,7 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cerr := checkRequiredFlags(a.Flags, context)
|
cerr := context.checkRequiredFlags(a.Flags)
|
||||||
if cerr != nil {
|
if cerr != nil {
|
||||||
_ = ShowAppHelp(context)
|
_ = ShowAppHelp(context)
|
||||||
return cerr
|
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
|
// RunAndExitOnError calls .Run() and exits non-zero if an error was returned
|
||||||
//
|
//
|
||||||
// Deprecated: instead you should return an error that fulfills cli.ExitCoder
|
// 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
|
// code in the cli.ExitCoder
|
||||||
func (a *App) RunAndExitOnError() {
|
func (a *App) RunAndExitOnError() {
|
||||||
if err := a.Run(os.Args); err != nil {
|
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 {
|
if cerr != nil {
|
||||||
_ = ShowSubcommandHelp(context)
|
_ = ShowSubcommandHelp(context)
|
||||||
return cerr
|
return cerr
|
||||||
|
78
app_test.go
78
app_test.go
@ -315,7 +315,6 @@ func ExampleApp_Run_bashComplete_withMultipleLongFlag() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ExampleApp_Run_bashComplete() {
|
func ExampleApp_Run_bashComplete() {
|
||||||
// set args for examples sake
|
|
||||||
// set args for examples sake
|
// set args for examples sake
|
||||||
os.Args = []string{"greet", "--generate-bash-completion"}
|
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) {
|
func TestApp_Setup_defaultsWriter(t *testing.T) {
|
||||||
app := &App{}
|
app := &App{}
|
||||||
app.Setup()
|
app.Setup()
|
||||||
@ -471,18 +476,18 @@ func TestApp_RunAsSubCommandIncorrectUsage(t *testing.T) {
|
|||||||
a := App{
|
a := App{
|
||||||
Name: "cmd",
|
Name: "cmd",
|
||||||
Flags: []Flag{
|
Flags: []Flag{
|
||||||
&StringFlag{Name: "--foo"},
|
&StringFlag{Name: "foo"},
|
||||||
},
|
},
|
||||||
Writer: bytes.NewBufferString(""),
|
Writer: bytes.NewBufferString(""),
|
||||||
}
|
}
|
||||||
|
|
||||||
set := flag.NewFlagSet("", flag.ContinueOnError)
|
set := flag.NewFlagSet("", flag.ContinueOnError)
|
||||||
_ = set.Parse([]string{"", "---foo"})
|
_ = set.Parse([]string{"", "-bar"})
|
||||||
c := &Context{flagSet: set}
|
c := &Context{flagSet: set}
|
||||||
|
|
||||||
err := a.RunAsSubcommand(c)
|
err := a.RunAsSubcommand(c)
|
||||||
|
|
||||||
expect(t, err, errors.New("bad flag syntax: ---foo"))
|
expect(t, err.Error(), "flag provided but not defined: -bar")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
|
func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
|
||||||
@ -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) {
|
func TestApp_DefaultStdout(t *testing.T) {
|
||||||
app := &App{}
|
app := &App{}
|
||||||
app.Setup()
|
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) {
|
func TestApp_SetStdout(t *testing.T) {
|
||||||
var w bytes.Buffer
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cerr := checkRequiredFlags(c.Flags, context)
|
cerr := context.checkRequiredFlags(c.Flags)
|
||||||
if cerr != nil {
|
if cerr != nil {
|
||||||
_ = ShowCommandHelp(context, c.Name)
|
_ = ShowCommandHelp(context, c.Name)
|
||||||
return cerr
|
return cerr
|
||||||
@ -227,6 +227,7 @@ func (c *Command) startApp(ctx *Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.Usage = c.Usage
|
app.Usage = c.Usage
|
||||||
|
app.UsageText = c.UsageText
|
||||||
app.Description = c.Description
|
app.Description = c.Description
|
||||||
app.ArgsUsage = c.ArgsUsage
|
app.ArgsUsage = c.ArgsUsage
|
||||||
|
|
||||||
@ -243,6 +244,7 @@ func (c *Command) startApp(ctx *Context) error {
|
|||||||
app.Version = ctx.App.Version
|
app.Version = ctx.App.Version
|
||||||
app.HideVersion = true
|
app.HideVersion = true
|
||||||
app.Compiled = ctx.App.Compiled
|
app.Compiled = ctx.App.Compiled
|
||||||
|
app.Reader = ctx.App.Reader
|
||||||
app.Writer = ctx.App.Writer
|
app.Writer = ctx.App.Writer
|
||||||
app.ErrWriter = ctx.App.ErrWriter
|
app.ErrWriter = ctx.App.ErrWriter
|
||||||
app.ExitErrHandler = ctx.App.ExitErrHandler
|
app.ExitErrHandler = ctx.App.ExitErrHandler
|
||||||
|
122
context.go
122
context.go
@ -2,9 +2,7 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -53,8 +51,7 @@ func (c *Context) Set(name, value string) error {
|
|||||||
|
|
||||||
// IsSet determines if the flag was actually set
|
// IsSet determines if the flag was actually set
|
||||||
func (c *Context) IsSet(name string) bool {
|
func (c *Context) IsSet(name string) bool {
|
||||||
if fs := lookupFlagSet(name, c); fs != nil {
|
if fs := c.lookupFlagSet(name); fs != nil {
|
||||||
if fs := lookupFlagSet(name, c); fs != nil {
|
|
||||||
isSet := false
|
isSet := false
|
||||||
fs.Visit(func(f *flag.Flag) {
|
fs.Visit(func(f *flag.Flag) {
|
||||||
if f.Name == name {
|
if f.Name == name {
|
||||||
@ -64,9 +61,8 @@ func (c *Context) IsSet(name string) bool {
|
|||||||
if isSet {
|
if isSet {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
f := lookupFlag(name, c)
|
f := c.lookupFlag(name)
|
||||||
if f == nil {
|
if f == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -108,7 +104,10 @@ func (c *Context) Lineage() []*Context {
|
|||||||
|
|
||||||
// Value returns the value of the flag corresponding to `name`
|
// Value returns the value of the flag corresponding to `name`
|
||||||
func (c *Context) Value(name string) interface{} {
|
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.
|
// Args returns the command line arguments associated with the context.
|
||||||
@ -122,7 +121,7 @@ func (c *Context) NArg() int {
|
|||||||
return c.Args().Len()
|
return c.Args().Len()
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookupFlag(name string, ctx *Context) Flag {
|
func (ctx *Context) lookupFlag(name string) Flag {
|
||||||
for _, c := range ctx.Lineage() {
|
for _, c := range ctx.Lineage() {
|
||||||
if c.Command == nil {
|
if c.Command == nil {
|
||||||
continue
|
continue
|
||||||
@ -150,8 +149,11 @@ func lookupFlag(name string, ctx *Context) Flag {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookupFlagSet(name string, ctx *Context) *flag.FlagSet {
|
func (ctx *Context) lookupFlagSet(name string) *flag.FlagSet {
|
||||||
for _, c := range ctx.Lineage() {
|
for _, c := range ctx.Lineage() {
|
||||||
|
if c.flagSet == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if f := c.flagSet.Lookup(name); f != nil {
|
if f := c.flagSet.Lookup(name); f != nil {
|
||||||
return c.flagSet
|
return c.flagSet
|
||||||
}
|
}
|
||||||
@ -160,89 +162,7 @@ func lookupFlagSet(name string, ctx *Context) *flag.FlagSet {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
|
func (context *Context) checkRequiredFlags(flags []Flag) requiredFlagsErr {
|
||||||
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 {
|
|
||||||
var missingFlags []string
|
var missingFlags []string
|
||||||
for _, f := range flags {
|
for _, f := range flags {
|
||||||
if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() {
|
if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() {
|
||||||
@ -271,3 +191,21 @@ func checkRequiredFlags(flags []Flag, context *Context) requiredFlagsErr {
|
|||||||
|
|
||||||
return nil
|
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)
|
c := NewContext(nil, set, parentCtx)
|
||||||
expect(t, c.String("myflag"), "hello world")
|
expect(t, c.String("myflag"), "hello world")
|
||||||
expect(t, c.String("top-flag"), "hai veld")
|
expect(t, c.String("top-flag"), "hai veld")
|
||||||
|
c = NewContext(nil, nil, parentCtx)
|
||||||
|
expect(t, c.String("top-flag"), "hai veld")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContext_Path(t *testing.T) {
|
func TestContext_Path(t *testing.T) {
|
||||||
@ -136,6 +138,18 @@ func TestContext_Bool(t *testing.T) {
|
|||||||
expect(t, c.Bool("top-flag"), true)
|
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) {
|
func TestContext_Args(t *testing.T) {
|
||||||
set := flag.NewFlagSet("test", 0)
|
set := flag.NewFlagSet("test", 0)
|
||||||
set.Bool("myflag", false, "doc")
|
set.Bool("myflag", false, "doc")
|
||||||
@ -304,13 +318,13 @@ func TestContext_lookupFlagSet(t *testing.T) {
|
|||||||
_ = set.Parse([]string{"--local-flag"})
|
_ = set.Parse([]string{"--local-flag"})
|
||||||
_ = parentSet.Parse([]string{"--top-flag"})
|
_ = parentSet.Parse([]string{"--top-flag"})
|
||||||
|
|
||||||
fs := lookupFlagSet("top-flag", ctx)
|
fs := ctx.lookupFlagSet("top-flag")
|
||||||
expect(t, fs, parentCtx.flagSet)
|
expect(t, fs, parentCtx.flagSet)
|
||||||
|
|
||||||
fs = lookupFlagSet("local-flag", ctx)
|
fs = ctx.lookupFlagSet("local-flag")
|
||||||
expect(t, fs, ctx.flagSet)
|
expect(t, fs, ctx.flagSet)
|
||||||
|
|
||||||
if fs := lookupFlagSet("frob", ctx); fs != nil {
|
if fs := ctx.lookupFlagSet("frob"); fs != nil {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -564,7 +578,7 @@ func TestCheckRequiredFlags(t *testing.T) {
|
|||||||
ctx.Command.Flags = test.flags
|
ctx.Command.Flags = test.flags
|
||||||
|
|
||||||
// logic under test
|
// logic under test
|
||||||
err := checkRequiredFlags(test.flags, ctx)
|
err := ctx.checkRequiredFlags(test.flags)
|
||||||
|
|
||||||
// assertions
|
// assertions
|
||||||
if test.expectedAnError && err == nil {
|
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.
|
// The function errors if either parsing or writing of the string fails.
|
||||||
func (a *App) ToMarkdown() (string, error) {
|
func (a *App) ToMarkdown() (string, error) {
|
||||||
var w bytes.Buffer
|
var w bytes.Buffer
|
||||||
if err := a.writeDocTemplate(&w); err != nil {
|
if err := a.writeDocTemplate(&w, 0); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return w.String(), nil
|
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.
|
// 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
|
var w bytes.Buffer
|
||||||
if err := a.writeDocTemplate(&w); err != nil {
|
if err := a.writeDocTemplate(&w, sectionNumber); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
man := md2man.Render(w.Bytes())
|
man := md2man.Render(w.Bytes())
|
||||||
return string(man), nil
|
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 {
|
type cliTemplate struct {
|
||||||
App *App
|
App *App
|
||||||
|
SectionNum int
|
||||||
Commands []string
|
Commands []string
|
||||||
GlobalArgs []string
|
GlobalArgs []string
|
||||||
SynopsisArgs []string
|
SynopsisArgs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) writeDocTemplate(w io.Writer) error {
|
func (a *App) writeDocTemplate(w io.Writer, sectionNum int) error {
|
||||||
const name = "cli"
|
const name = "cli"
|
||||||
t, err := template.New(name).Parse(MarkdownDocTemplate)
|
t, err := template.New(name).Parse(MarkdownDocTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -47,6 +55,7 @@ func (a *App) writeDocTemplate(w io.Writer) error {
|
|||||||
}
|
}
|
||||||
return t.ExecuteTemplate(w, name, &cliTemplate{
|
return t.ExecuteTemplate(w, name, &cliTemplate{
|
||||||
App: a,
|
App: a,
|
||||||
|
SectionNum: sectionNum,
|
||||||
Commands: prepareCommands(a.Commands, 0),
|
Commands: prepareCommands(a.Commands, 0),
|
||||||
GlobalArgs: prepareArgsWithValues(a.VisibleFlags()),
|
GlobalArgs: prepareArgsWithValues(a.VisibleFlags()),
|
||||||
SynopsisArgs: prepareArgsSynopsis(a.VisibleFlags()),
|
SynopsisArgs: prepareArgsSynopsis(a.VisibleFlags()),
|
||||||
@ -59,15 +68,16 @@ func prepareCommands(commands []*Command, level int) []string {
|
|||||||
if command.Hidden {
|
if command.Hidden {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
usage := ""
|
|
||||||
if command.Usage != "" {
|
|
||||||
usage = command.Usage
|
|
||||||
}
|
|
||||||
|
|
||||||
prepared := fmt.Sprintf("%s %s\n\n%s\n",
|
usageText := prepareUsageText(command)
|
||||||
|
|
||||||
|
usage := prepareUsage(command, usageText)
|
||||||
|
|
||||||
|
prepared := fmt.Sprintf("%s %s\n\n%s%s",
|
||||||
strings.Repeat("#", level+2),
|
strings.Repeat("#", level+2),
|
||||||
strings.Join(command.Names(), ", "),
|
strings.Join(command.Names(), ", "),
|
||||||
usage,
|
usage,
|
||||||
|
usageText,
|
||||||
)
|
)
|
||||||
|
|
||||||
flags := prepareArgsWithValues(command.Flags)
|
flags := prepareArgsWithValues(command.Flags)
|
||||||
@ -146,3 +156,40 @@ func flagDetails(flag DocGenerationFlag) string {
|
|||||||
}
|
}
|
||||||
return ": " + description
|
return ": " + description
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func prepareUsageText(command *Command) string {
|
||||||
|
if command.UsageText == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove leading and trailing newlines
|
||||||
|
preparedUsageText := strings.Trim(command.UsageText, "\n")
|
||||||
|
|
||||||
|
var usageText string
|
||||||
|
if strings.Contains(preparedUsageText, "\n") {
|
||||||
|
// Format multi-line string as a code block using the 4 space schema to allow for embedded markdown such
|
||||||
|
// that it will not break the continuous code block.
|
||||||
|
for _, ln := range strings.Split(preparedUsageText, "\n") {
|
||||||
|
usageText += fmt.Sprintf(" %s\n", ln)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Style a single line as a note
|
||||||
|
usageText = fmt.Sprintf(">%s\n", preparedUsageText)
|
||||||
|
}
|
||||||
|
|
||||||
|
return usageText
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareUsage(command *Command, usageText string) string {
|
||||||
|
if command.Usage == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
usage := command.Usage + "\n"
|
||||||
|
// Add a newline to the Usage IFF there is a UsageText
|
||||||
|
if usageText != "" {
|
||||||
|
usage += "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
return usage
|
||||||
|
}
|
||||||
|
@ -6,7 +6,7 @@ Feel free to put up a pull request to fix a bug or maybe add a feature. We will
|
|||||||
give it a code review and make sure that it does not break backwards
|
give it a code review and make sure that it does not break backwards
|
||||||
compatibility. If collaborators agree that it is in line with
|
compatibility. If collaborators agree that it is in line with
|
||||||
the vision of the project, we will work with you to get the code into
|
the vision of the project, we will work with you to get the code into
|
||||||
a mergeable state and merge it into the master branch.
|
a mergeable state and merge it into the main branch.
|
||||||
|
|
||||||
If you have contributed something significant to the project, we will most
|
If you have contributed something significant to the project, we will most
|
||||||
likely add you as a collaborator. As a collaborator you are given the ability
|
likely add you as a collaborator. As a collaborator you are given the ability
|
||||||
|
@ -15,15 +15,18 @@ consider sending a PR to help improve this guide.
|
|||||||
|
|
||||||
* [Flags before args](#flags-before-args)
|
* [Flags before args](#flags-before-args)
|
||||||
* [Import string changed](#import-string-changed)
|
* [Import string changed](#import-string-changed)
|
||||||
* [Flag aliases are done differently.](#flag-aliases-are-done-differently)
|
* [Flag aliases are done differently](#flag-aliases-are-done-differently)
|
||||||
* [EnvVar is now a list (EnvVars)](#envvar-is-now-a-list-envvars)
|
* [EnvVar is now a list (EnvVars)](#envvar-is-now-a-list-envvars)
|
||||||
|
* [Actions returns errors](#actions-returns-errors)
|
||||||
|
* [cli.Flag changed](#cliflag-changed)
|
||||||
* [Commands are now lists of pointers](#commands-are-now-lists-of-pointers)
|
* [Commands are now lists of pointers](#commands-are-now-lists-of-pointers)
|
||||||
* [Lists of commands should be pointers](#lists-of-commands-should-be-pointers)
|
* [Lists of commands should be pointers](#lists-of-commands-should-be-pointers)
|
||||||
* [cli.Flag changed](#cliflag-changed)
|
|
||||||
* [Appending Commands](#appending-commands)
|
* [Appending Commands](#appending-commands)
|
||||||
* [Actions returns errors](#actions-returns-errors)
|
* [GlobalString, GlobalBool and its likes are deprecated](#globalstring-globalbool-and-its-likes-are-deprecated)
|
||||||
|
* [BoolTFlag and BoolT are deprecated](#booltflag-and-boolt-are-deprecated)
|
||||||
|
* [&cli.StringSlice{""} replaced with cli.NewStringSlice("")](#clistringslice-replaced-with-clinewstringslice)
|
||||||
|
* [Replace deprecated functions](#replace-deprecated-functions)
|
||||||
* [Everything else](#everything-else)
|
* [Everything else](#everything-else)
|
||||||
* [Full API Example](#full-api-example)
|
|
||||||
|
|
||||||
<!-- tocstop -->
|
<!-- tocstop -->
|
||||||
|
|
||||||
@ -53,7 +56,7 @@ Check each file for this and make the change.
|
|||||||
|
|
||||||
Shell command to find them all: `fgrep -rl github.com/urfave/cli *`
|
Shell command to find them all: `fgrep -rl github.com/urfave/cli *`
|
||||||
|
|
||||||
# Flag aliases are done differently.
|
# Flag aliases are done differently
|
||||||
|
|
||||||
Change `Name: "foo, f"` to `Name: "foo", Aliases: []string{"f"}`
|
Change `Name: "foo, f"` to `Name: "foo", Aliases: []string{"f"}`
|
||||||
|
|
||||||
@ -181,6 +184,52 @@ Compiler messages you might see:
|
|||||||
cannot use c (type *cli.Command) as type cli.Command in append
|
cannot use c (type *cli.Command) as type cli.Command in append
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# GlobalString, GlobalBool and its likes are deprecated
|
||||||
|
|
||||||
|
Use simply `String` instead of `GlobalString`, `Bool` instead of `GlobalBool`
|
||||||
|
|
||||||
|
# BoolTFlag and BoolT are deprecated
|
||||||
|
|
||||||
|
BoolTFlag was a Bool Flag with its default value set to true and BoolT was used to find any BoolTFlag used locally, so both are deprecated.
|
||||||
|
|
||||||
|
* OLD:
|
||||||
|
|
||||||
|
```go
|
||||||
|
cli.BoolTFlag{
|
||||||
|
Name: FlagName,
|
||||||
|
Usage: FlagUsage,
|
||||||
|
EnvVar: "FLAG_ENV_VAR",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
* NEW:
|
||||||
|
```go
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: FlagName,
|
||||||
|
Value: true,
|
||||||
|
Usage: FlagUsage,
|
||||||
|
EnvVar: "FLAG_ENV_VAR",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# &cli.StringSlice{""} replaced with cli.NewStringSlice("")
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
* OLD:
|
||||||
|
|
||||||
|
```go
|
||||||
|
Value: &cli.StringSlice{""},
|
||||||
|
```
|
||||||
|
* NEW:
|
||||||
|
```go
|
||||||
|
Value: cli.NewStringSlice(""),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
# Replace deprecated functions
|
||||||
|
|
||||||
|
`cli.NewExitError()` is deprecated. Use `cli.Exit()` instead. ([Staticcheck](https://staticcheck.io/) detects this automatically and recommends replacement code.)
|
||||||
|
|
||||||
# Everything else
|
# Everything else
|
||||||
|
|
||||||
Compile the code and work through any errors. Most should
|
Compile the code and work through any errors. Most should
|
||||||
|
@ -612,7 +612,7 @@ given sources.
|
|||||||
Here is a more complete sample of a command using YAML support:
|
Here is a more complete sample of a command using YAML support:
|
||||||
|
|
||||||
<!-- {
|
<!-- {
|
||||||
"args": ["test-cmd", "--help"],
|
"args": ["--help"],
|
||||||
"output": "--test value.*default: 0"
|
"output": "--test value.*default: 0"
|
||||||
} -->
|
} -->
|
||||||
``` go
|
``` go
|
||||||
|
@ -30,6 +30,7 @@ cli v2 manual
|
|||||||
+ [ZSH Support](#zsh-support)
|
+ [ZSH Support](#zsh-support)
|
||||||
+ [ZSH default auto-complete example](#zsh-default-auto-complete-example)
|
+ [ZSH default auto-complete example](#zsh-default-auto-complete-example)
|
||||||
+ [ZSH custom auto-complete example](#zsh-custom-auto-complete-example)
|
+ [ZSH custom auto-complete example](#zsh-custom-auto-complete-example)
|
||||||
|
+ [PowerShell Support](#powershell-support)
|
||||||
* [Generated Help Text](#generated-help-text)
|
* [Generated Help Text](#generated-help-text)
|
||||||
+ [Customization](#customization-1)
|
+ [Customization](#customization-1)
|
||||||
* [Version Flag](#version-flag)
|
* [Version Flag](#version-flag)
|
||||||
@ -44,7 +45,7 @@ cli v2 manual
|
|||||||
There are a small set of breaking changes between v1 and v2.
|
There are a small set of breaking changes between v1 and v2.
|
||||||
Converting is relatively straightforward and typically takes less than
|
Converting is relatively straightforward and typically takes less than
|
||||||
an hour. Specific steps are included in
|
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
|
## Getting Started
|
||||||
|
|
||||||
@ -426,12 +427,14 @@ func main() {
|
|||||||
app := &cli.App{
|
app := &cli.App{
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "lang, l",
|
Name: "lang",
|
||||||
|
Aliases: []string{"l"},
|
||||||
Value: "english",
|
Value: "english",
|
||||||
Usage: "Language for the greeting",
|
Usage: "Language for the greeting",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "config, c",
|
Name: "config",
|
||||||
|
Aliases: []string{"c"},
|
||||||
Usage: "Load configuration from `FILE`",
|
Usage: "Load configuration from `FILE`",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -511,7 +514,7 @@ func main() {
|
|||||||
```
|
```
|
||||||
|
|
||||||
If `EnvVars` contains more than one string, the first environment variable that
|
If `EnvVars` contains more than one string, the first environment variable that
|
||||||
resolves is used as the default.
|
resolves is used.
|
||||||
|
|
||||||
<!-- {
|
<!-- {
|
||||||
"args": ["--help"],
|
"args": ["--help"],
|
||||||
@ -570,7 +573,8 @@ func main() {
|
|||||||
|
|
||||||
app.Flags = []cli.Flag {
|
app.Flags = []cli.Flag {
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "password, p",
|
Name: "password",
|
||||||
|
Aliases: []string{"p"},
|
||||||
Usage: "password for the mysql database",
|
Usage: "password for the mysql database",
|
||||||
FilePath: "/etc/mysql/password",
|
FilePath: "/etc/mysql/password",
|
||||||
},
|
},
|
||||||
@ -623,7 +627,7 @@ given sources.
|
|||||||
Here is a more complete sample of a command using YAML support:
|
Here is a more complete sample of a command using YAML support:
|
||||||
|
|
||||||
<!-- {
|
<!-- {
|
||||||
"args": ["test-cmd", "--help"],
|
"args": ["--help"],
|
||||||
"output": "--test value.*default: 0"
|
"output": "--test value.*default: 0"
|
||||||
} -->
|
} -->
|
||||||
``` go
|
``` go
|
||||||
@ -645,7 +649,7 @@ func main() {
|
|||||||
|
|
||||||
app := &cli.App{
|
app := &cli.App{
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
fmt.Println("yaml ist rad")
|
fmt.Println("--test value.*default: 0")
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")),
|
Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")),
|
||||||
@ -670,8 +674,10 @@ Take for example this app that requires the `lang` flag:
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1222,6 +1228,23 @@ source path/to/autocomplete/zsh_autocomplete
|
|||||||
#### ZSH custom auto-complete example
|
#### ZSH custom auto-complete example
|
||||||
![](/docs/v2/images/custom-zsh-autocomplete.gif)
|
![](/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
|
### Generated Help Text
|
||||||
|
|
||||||
The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked
|
The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked
|
||||||
@ -1309,7 +1332,8 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cli.HelpFlag = &cli.BoolFlag{
|
cli.HelpFlag = &cli.BoolFlag{
|
||||||
Name: "haaaaalp", Aliases: []string{"halp"},
|
Name: "haaaaalp",
|
||||||
|
Aliases: []string{"halp"},
|
||||||
Usage: "HALP",
|
Usage: "HALP",
|
||||||
EnvVars: []string{"SHOW_HALP", "HALPPLZ"},
|
EnvVars: []string{"SHOW_HALP", "HALPPLZ"},
|
||||||
}
|
}
|
||||||
@ -1344,7 +1368,8 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cli.VersionFlag = &cli.BoolFlag{
|
cli.VersionFlag = &cli.BoolFlag{
|
||||||
Name: "print-version", Aliases: []string{"V"},
|
Name: "print-version",
|
||||||
|
Aliases: []string{"V"},
|
||||||
Usage: "print only the version",
|
Usage: "print only the version",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
194
docs_test.go
194
docs_test.go
@ -2,6 +2,7 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@ -66,8 +67,50 @@ func testApp() *App {
|
|||||||
}, {
|
}, {
|
||||||
Name: "hidden-command",
|
Name: "hidden-command",
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
|
}, {
|
||||||
|
Aliases: []string{"u"},
|
||||||
|
Flags: []Flag{
|
||||||
|
&StringFlag{
|
||||||
|
Name: "flag",
|
||||||
|
Aliases: []string{"fl", "f"},
|
||||||
|
TakesFile: true,
|
||||||
|
},
|
||||||
|
&BoolFlag{
|
||||||
|
Name: "another-flag",
|
||||||
|
Aliases: []string{"b"},
|
||||||
|
Usage: "another usage text",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Name: "usage",
|
||||||
|
Usage: "standard usage text",
|
||||||
|
UsageText: `
|
||||||
|
Usage for the usage text
|
||||||
|
- formatted: Based on the specified ConfigMap and summon secrets.yml
|
||||||
|
- list: Inspect the environment for a specific process running on a Pod
|
||||||
|
- for_effect: Compare 'namespace' environment with 'local'
|
||||||
|
|
||||||
|
` + "```" + `
|
||||||
|
func() { ... }
|
||||||
|
` + "```" + `
|
||||||
|
|
||||||
|
Should be a part of the same code block
|
||||||
|
`,
|
||||||
|
Subcommands: []*Command{{
|
||||||
|
Aliases: []string{"su"},
|
||||||
|
Flags: []Flag{
|
||||||
|
&BoolFlag{
|
||||||
|
Name: "sub-command-flag",
|
||||||
|
Aliases: []string{"s"},
|
||||||
|
Usage: "some usage text",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Name: "sub-usage",
|
||||||
|
Usage: "standard usage text",
|
||||||
|
UsageText: "Single line of UsageText",
|
||||||
|
}},
|
||||||
}}
|
}}
|
||||||
app.UsageText = "app [first_arg] [second_arg]"
|
app.UsageText = "app [first_arg] [second_arg]"
|
||||||
|
app.Description = `Description of the application.`
|
||||||
app.Usage = "Some app"
|
app.Usage = "Some app"
|
||||||
app.Authors = []*Author{
|
app.Authors = []*Author{
|
||||||
{Name: "Harrison", Email: "harrison@lolwut.com"},
|
{Name: "Harrison", Email: "harrison@lolwut.com"},
|
||||||
@ -76,13 +119,13 @@ func testApp() *App {
|
|||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
func expectFileContent(t *testing.T, file, expected string) {
|
func expectFileContent(t *testing.T, file, got string) {
|
||||||
data, err := ioutil.ReadFile(file)
|
data, err := ioutil.ReadFile(file)
|
||||||
// Ignore windows line endings
|
// Ignore windows line endings
|
||||||
// TODO: Replace with bytes.ReplaceAll when support for Go 1.11 is dropped
|
// TODO: Replace with bytes.ReplaceAll when support for Go 1.11 is dropped
|
||||||
data = bytes.Replace(data, []byte("\r\n"), []byte("\n"), -1)
|
data = bytes.Replace(data, []byte("\r\n"), []byte("\n"), -1)
|
||||||
expect(t, err, nil)
|
expect(t, err, nil)
|
||||||
expect(t, string(data), expected)
|
expect(t, got, string(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToMarkdownFull(t *testing.T) {
|
func TestToMarkdownFull(t *testing.T) {
|
||||||
@ -136,6 +179,19 @@ func TestToMarkdownNoAuthors(t *testing.T) {
|
|||||||
expectFileContent(t, "testdata/expected-doc-no-authors.md", res)
|
expectFileContent(t, "testdata/expected-doc-no-authors.md", res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestToMarkdownNoUsageText(t *testing.T) {
|
||||||
|
// Given
|
||||||
|
app := testApp()
|
||||||
|
app.UsageText = ""
|
||||||
|
|
||||||
|
// When
|
||||||
|
res, err := app.ToMarkdown()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(t, err, nil)
|
||||||
|
expectFileContent(t, "testdata/expected-doc-no-usagetext.md", res)
|
||||||
|
}
|
||||||
|
|
||||||
func TestToMan(t *testing.T) {
|
func TestToMan(t *testing.T) {
|
||||||
// Given
|
// Given
|
||||||
app := testApp()
|
app := testApp()
|
||||||
@ -147,3 +203,137 @@ func TestToMan(t *testing.T) {
|
|||||||
expect(t, err, nil)
|
expect(t, err, nil)
|
||||||
expectFileContent(t, "testdata/expected-doc-full.man", res)
|
expectFileContent(t, "testdata/expected-doc-full.man", res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func 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
|
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
|
// ErrorFormatter is the interface that will suitably format the error output
|
||||||
type ErrorFormatter interface {
|
type ErrorFormatter interface {
|
||||||
Format(s fmt.State, verb rune)
|
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 {
|
if f.TakesFile {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case *PathFlag:
|
||||||
|
if f.TakesFile {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
completion.WriteString(" -f")
|
completion.WriteString(" -f")
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,10 @@ import (
|
|||||||
func TestFishCompletion(t *testing.T) {
|
func TestFishCompletion(t *testing.T) {
|
||||||
// Given
|
// Given
|
||||||
app := testApp()
|
app := testApp()
|
||||||
|
app.Flags = append(app.Flags, &PathFlag{
|
||||||
|
Name: "logfile",
|
||||||
|
TakesFile: true,
|
||||||
|
})
|
||||||
|
|
||||||
// When
|
// When
|
||||||
res, err := app.ToFishCompletion()
|
res, err := app.ToFishCompletion()
|
||||||
|
62
flag.go
62
flag.go
@ -1,6 +1,7 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -118,6 +119,14 @@ type DocGenerationFlag interface {
|
|||||||
GetValue() string
|
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) {
|
func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
|
||||||
set := flag.NewFlagSet(name, flag.ContinueOnError)
|
set := flag.NewFlagSet(name, flag.ContinueOnError)
|
||||||
|
|
||||||
@ -130,11 +139,52 @@ func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
|
|||||||
return set, nil
|
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 {
|
func visibleFlags(fl []Flag) []Flag {
|
||||||
var visible []Flag
|
var visible []Flag
|
||||||
for _, f := range fl {
|
for _, f := range fl {
|
||||||
field := flagValue(f).FieldByName("Hidden")
|
if vf, ok := f.(VisibleFlag); ok && vf.IsVisible() {
|
||||||
if !field.IsValid() || !field.Bool() {
|
|
||||||
visible = append(visible, f)
|
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))
|
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 {
|
func hasFlag(flags []Flag, fl Flag) bool {
|
||||||
@ -380,9 +434,11 @@ func flagFromEnvOrFile(envVars []string, filePath string) (val string, ok bool)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, fileVar := range strings.Split(filePath, ",") {
|
for _, fileVar := range strings.Split(filePath, ",") {
|
||||||
|
if fileVar != "" {
|
||||||
if data, err := ioutil.ReadFile(fileVar); err == nil {
|
if data, err := ioutil.ReadFile(fileVar); err == nil {
|
||||||
return string(data), true
|
return string(data), true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,11 @@ func (f *BoolFlag) GetValue() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *BoolFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
func (f *BoolFlag) Apply(set *flag.FlagSet) error {
|
func (f *BoolFlag) Apply(set *flag.FlagSet) error {
|
||||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
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
|
// Bool looks up the value of a local BoolFlag, returns
|
||||||
// false if not found
|
// false if not found
|
||||||
func (c *Context) Bool(name string) bool {
|
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 lookupBool(name, fs)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -58,6 +58,11 @@ func (f *DurationFlag) GetValue() string {
|
|||||||
return f.Value.String()
|
return f.Value.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *DurationFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
func (f *DurationFlag) Apply(set *flag.FlagSet) error {
|
func (f *DurationFlag) Apply(set *flag.FlagSet) error {
|
||||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
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
|
// Duration looks up the value of a local DurationFlag, returns
|
||||||
// 0 if not found
|
// 0 if not found
|
||||||
func (c *Context) Duration(name string) time.Duration {
|
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 lookupDuration(name, fs)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
@ -58,12 +58,16 @@ func (f *Float64Flag) GetValue() string {
|
|||||||
return fmt.Sprintf("%f", f.Value)
|
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
|
// Apply populates the flag given the flag set and environment
|
||||||
func (f *Float64Flag) Apply(set *flag.FlagSet) error {
|
func (f *Float64Flag) Apply(set *flag.FlagSet) error {
|
||||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||||
if val != "" {
|
if val != "" {
|
||||||
valFloat, err := strconv.ParseFloat(val, 10)
|
valFloat, err := strconv.ParseFloat(val, 64)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not parse %q as float64 value for flag %s: %s", val, f.Name, err)
|
return fmt.Errorf("could not parse %q as float64 value for flag %s: %s", val, f.Name, err)
|
||||||
}
|
}
|
||||||
@ -87,7 +91,7 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) error {
|
|||||||
// Float64 looks up the value of a local Float64Flag, returns
|
// Float64 looks up the value of a local Float64Flag, returns
|
||||||
// 0 if not found
|
// 0 if not found
|
||||||
func (c *Context) Float64(name string) float64 {
|
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 lookupFloat64(name, fs)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
@ -19,6 +19,16 @@ func NewFloat64Slice(defaults ...float64) *Float64Slice {
|
|||||||
return &Float64Slice{slice: append([]float64{}, defaults...)}
|
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
|
// Set parses the value into a float64 and appends it to the list of values
|
||||||
func (f *Float64Slice) Set(value string) error {
|
func (f *Float64Slice) Set(value string) error {
|
||||||
if !f.hasBeenSet {
|
if !f.hasBeenSet {
|
||||||
@ -117,6 +127,11 @@ func (f *Float64SliceFlag) GetValue() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *Float64SliceFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error {
|
func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error {
|
||||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
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
|
f.HasBeenSet = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range f.Names() {
|
|
||||||
if f.Value == nil {
|
if f.Value == nil {
|
||||||
f.Value = &Float64Slice{}
|
f.Value = &Float64Slice{}
|
||||||
}
|
}
|
||||||
set.Var(f.Value, name, f.Usage)
|
copyValue := f.Value.clone()
|
||||||
|
for _, name := range f.Names() {
|
||||||
|
set.Var(copyValue, name, f.Usage)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -146,7 +165,7 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error {
|
|||||||
// Float64Slice looks up the value of a local Float64SliceFlag, returns
|
// Float64Slice looks up the value of a local Float64SliceFlag, returns
|
||||||
// nil if not found
|
// nil if not found
|
||||||
func (c *Context) Float64Slice(name string) []float64 {
|
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 lookupFloat64Slice(name, fs)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -66,6 +66,11 @@ func (f *GenericFlag) GetValue() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *GenericFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
// Apply takes the flagset and calls Set on the generic flag with the value
|
// Apply takes the flagset and calls Set on the generic flag with the value
|
||||||
// provided by the user for parsing by the flag
|
// provided by the user for parsing by the flag
|
||||||
func (f GenericFlag) Apply(set *flag.FlagSet) error {
|
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
|
// Generic looks up the value of a local GenericFlag, returns
|
||||||
// nil if not found
|
// nil if not found
|
||||||
func (c *Context) Generic(name string) interface{} {
|
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 lookupGeneric(name, fs)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -58,6 +58,11 @@ func (f *IntFlag) GetValue() string {
|
|||||||
return fmt.Sprintf("%d", f.Value)
|
return fmt.Sprintf("%d", f.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *IntFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
func (f *IntFlag) Apply(set *flag.FlagSet) error {
|
func (f *IntFlag) Apply(set *flag.FlagSet) error {
|
||||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
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
|
// Int looks up the value of a local IntFlag, returns
|
||||||
// 0 if not found
|
// 0 if not found
|
||||||
func (c *Context) Int(name string) int {
|
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 lookupInt(name, fs)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
@ -58,6 +58,11 @@ func (f *Int64Flag) GetValue() string {
|
|||||||
return fmt.Sprintf("%d", f.Value)
|
return fmt.Sprintf("%d", f.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *Int64Flag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
func (f *Int64Flag) Apply(set *flag.FlagSet) error {
|
func (f *Int64Flag) Apply(set *flag.FlagSet) error {
|
||||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
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
|
// Int64 looks up the value of a local Int64Flag, returns
|
||||||
// 0 if not found
|
// 0 if not found
|
||||||
func (c *Context) Int64(name string) int64 {
|
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 lookupInt64(name, fs)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
@ -19,6 +19,16 @@ func NewInt64Slice(defaults ...int64) *Int64Slice {
|
|||||||
return &Int64Slice{slice: append([]int64{}, defaults...)}
|
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
|
// Set parses the value into an integer and appends it to the list of values
|
||||||
func (i *Int64Slice) Set(value string) error {
|
func (i *Int64Slice) Set(value string) error {
|
||||||
if !i.hasBeenSet {
|
if !i.hasBeenSet {
|
||||||
@ -118,6 +128,11 @@ func (f *Int64SliceFlag) GetValue() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *Int64SliceFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error {
|
func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error {
|
||||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
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
|
f.HasBeenSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range f.Names() {
|
|
||||||
if f.Value == nil {
|
if f.Value == nil {
|
||||||
f.Value = &Int64Slice{}
|
f.Value = &Int64Slice{}
|
||||||
}
|
}
|
||||||
set.Var(f.Value, name, f.Usage)
|
copyValue := f.Value.clone()
|
||||||
|
for _, name := range f.Names() {
|
||||||
|
set.Var(copyValue, name, f.Usage)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -145,7 +164,10 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error {
|
|||||||
// Int64Slice looks up the value of a local Int64SliceFlag, returns
|
// Int64Slice looks up the value of a local Int64SliceFlag, returns
|
||||||
// nil if not found
|
// nil if not found
|
||||||
func (c *Context) Int64Slice(name string) []int64 {
|
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 {
|
func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {
|
||||||
|
@ -19,6 +19,16 @@ func NewIntSlice(defaults ...int) *IntSlice {
|
|||||||
return &IntSlice{slice: append([]int{}, defaults...)}
|
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 ?
|
// TODO: Consistently have specific Set function for Int64 and Float64 ?
|
||||||
// SetInt directly adds an integer to the list of values
|
// SetInt directly adds an integer to the list of values
|
||||||
func (i *IntSlice) SetInt(value int) {
|
func (i *IntSlice) SetInt(value int) {
|
||||||
@ -129,6 +139,11 @@ func (f *IntSliceFlag) GetValue() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *IntSliceFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
func (f *IntSliceFlag) Apply(set *flag.FlagSet) error {
|
func (f *IntSliceFlag) Apply(set *flag.FlagSet) error {
|
||||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
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
|
f.HasBeenSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range f.Names() {
|
|
||||||
if f.Value == nil {
|
if f.Value == nil {
|
||||||
f.Value = &IntSlice{}
|
f.Value = &IntSlice{}
|
||||||
}
|
}
|
||||||
set.Var(f.Value, name, f.Usage)
|
copyValue := f.Value.clone()
|
||||||
|
for _, name := range f.Names() {
|
||||||
|
set.Var(copyValue, name, f.Usage)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -156,8 +175,8 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error {
|
|||||||
// IntSlice looks up the value of a local IntSliceFlag, returns
|
// IntSlice looks up the value of a local IntSliceFlag, returns
|
||||||
// nil if not found
|
// nil if not found
|
||||||
func (c *Context) IntSlice(name string) []int {
|
func (c *Context) IntSlice(name string) []int {
|
||||||
if fs := lookupFlagSet(name, c); fs != nil {
|
if fs := c.lookupFlagSet(name); fs != nil {
|
||||||
return lookupIntSlice(name, c.flagSet)
|
return lookupIntSlice(name, fs)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,11 @@ func (f *PathFlag) GetValue() string {
|
|||||||
return f.Value
|
return f.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *PathFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
func (f *PathFlag) Apply(set *flag.FlagSet) error {
|
func (f *PathFlag) Apply(set *flag.FlagSet) error {
|
||||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
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
|
// Path looks up the value of a local PathFlag, returns
|
||||||
// "" if not found
|
// "" if not found
|
||||||
func (c *Context) Path(name string) string {
|
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)
|
return lookupPath(name, fs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +55,11 @@ func (f *StringFlag) GetValue() string {
|
|||||||
return f.Value
|
return f.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *StringFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
func (f *StringFlag) Apply(set *flag.FlagSet) error {
|
func (f *StringFlag) Apply(set *flag.FlagSet) error {
|
||||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
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
|
// String looks up the value of a local StringFlag, returns
|
||||||
// "" if not found
|
// "" if not found
|
||||||
func (c *Context) String(name string) string {
|
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 lookupString(name, fs)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
@ -85,10 +90,7 @@ func (c *Context) String(name string) string {
|
|||||||
func lookupString(name string, set *flag.FlagSet) string {
|
func lookupString(name string, set *flag.FlagSet) string {
|
||||||
f := set.Lookup(name)
|
f := set.Lookup(name)
|
||||||
if f != nil {
|
if f != nil {
|
||||||
parsed, err := f.Value.String(), error(nil)
|
parsed := f.Value.String()
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return parsed
|
return parsed
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
|
@ -18,6 +18,16 @@ func NewStringSlice(defaults ...string) *StringSlice {
|
|||||||
return &StringSlice{slice: append([]string{}, defaults...)}
|
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
|
// Set appends the string value to the list of values
|
||||||
func (s *StringSlice) Set(value string) error {
|
func (s *StringSlice) Set(value string) error {
|
||||||
if !s.hasBeenSet {
|
if !s.hasBeenSet {
|
||||||
@ -114,6 +124,11 @@ func (f *StringSliceFlag) GetValue() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *StringSliceFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
|
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 {
|
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||||
|
if f.Value == nil {
|
||||||
f.Value = &StringSlice{}
|
f.Value = &StringSlice{}
|
||||||
|
}
|
||||||
destination := f.Value
|
destination := f.Value
|
||||||
if f.Destination != nil {
|
if f.Destination != nil {
|
||||||
destination = f.Destination
|
destination = f.Destination
|
||||||
@ -142,17 +159,15 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
|
|||||||
f.HasBeenSet = true
|
f.HasBeenSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range f.Names() {
|
|
||||||
if f.Value == nil {
|
if f.Value == nil {
|
||||||
f.Value = &StringSlice{}
|
f.Value = &StringSlice{}
|
||||||
}
|
}
|
||||||
|
setValue := f.Destination
|
||||||
if f.Destination != nil {
|
if f.Destination == nil {
|
||||||
set.Var(f.Destination, name, f.Usage)
|
setValue = f.Value.clone()
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
for _, name := range f.Names() {
|
||||||
set.Var(f.Value, name, f.Usage)
|
set.Var(setValue, name, f.Usage)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -161,7 +176,7 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
|
|||||||
// StringSlice looks up the value of a local StringSliceFlag, returns
|
// StringSlice looks up the value of a local StringSliceFlag, returns
|
||||||
// nil if not found
|
// nil if not found
|
||||||
func (c *Context) StringSlice(name string) []string {
|
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 lookupStringSlice(name, fs)
|
||||||
}
|
}
|
||||||
return nil
|
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) {
|
func TestFlagsFromEnv(t *testing.T) {
|
||||||
|
newSetFloat64Slice := func(defaults ...float64) Float64Slice {
|
||||||
|
s := NewFloat64Slice(defaults...)
|
||||||
|
s.hasBeenSet = false
|
||||||
|
return *s
|
||||||
|
}
|
||||||
|
|
||||||
newSetIntSlice := func(defaults ...int) IntSlice {
|
newSetIntSlice := func(defaults ...int) IntSlice {
|
||||||
s := NewIntSlice(defaults...)
|
s := NewIntSlice(defaults...)
|
||||||
s.hasBeenSet = true
|
s.hasBeenSet = false
|
||||||
return *s
|
return *s
|
||||||
}
|
}
|
||||||
|
|
||||||
newSetInt64Slice := func(defaults ...int64) Int64Slice {
|
newSetInt64Slice := func(defaults ...int64) Int64Slice {
|
||||||
s := NewInt64Slice(defaults...)
|
s := NewInt64Slice(defaults...)
|
||||||
s.hasBeenSet = true
|
s.hasBeenSet = false
|
||||||
return *s
|
return *s
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +102,9 @@ func TestFlagsFromEnv(t *testing.T) {
|
|||||||
{"1.2", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value for flag seconds: .*`},
|
{"1.2", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value for flag seconds: .*`},
|
||||||
{"foobar", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value for flag seconds: .*`},
|
{"foobar", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value for flag seconds: .*`},
|
||||||
|
|
||||||
|
{"1.0,2", newSetFloat64Slice(1, 2), &Float64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
||||||
|
{"foobar", newSetFloat64Slice(), &Float64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "\[\]float64{}" as float64 slice value for flag seconds: .*`},
|
||||||
|
|
||||||
{"1,2", newSetIntSlice(1, 2), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
{"1,2", newSetIntSlice(1, 2), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
||||||
{"1.2,2", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int slice value for flag seconds: .*`},
|
{"1.2,2", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int slice value for flag seconds: .*`},
|
||||||
{"foobar", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int slice value for flag seconds: .*`},
|
{"foobar", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int slice value for flag seconds: .*`},
|
||||||
@ -340,11 +349,11 @@ var stringSliceFlagTests = []struct {
|
|||||||
value *StringSlice
|
value *StringSlice
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{"foo", nil, NewStringSlice(""), "--foo value\t"},
|
{"foo", nil, NewStringSlice(""), "--foo value\t(accepts multiple inputs)"},
|
||||||
{"f", nil, NewStringSlice(""), "-f value\t"},
|
{"f", nil, NewStringSlice(""), "-f value\t(accepts multiple inputs)"},
|
||||||
{"f", nil, NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")"},
|
{"f", nil, NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")\t(accepts multiple inputs)"},
|
||||||
{"test", nil, NewStringSlice("Something"), "--test value\t(default: \"Something\")"},
|
{"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\")"},
|
{"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) {
|
func TestStringSliceFlagHelpOutput(t *testing.T) {
|
||||||
@ -386,6 +395,20 @@ func TestStringSliceFlagApply_SetsAllNames(t *testing.T) {
|
|||||||
expect(t, err, nil)
|
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) {
|
func TestStringSliceFlagApply_DefaultValueWithDestination(t *testing.T) {
|
||||||
defValue := []string{"UA", "US"}
|
defValue := []string{"UA", "US"}
|
||||||
|
|
||||||
@ -616,9 +639,9 @@ var intSliceFlagTests = []struct {
|
|||||||
value *IntSlice
|
value *IntSlice
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{"heads", nil, NewIntSlice(), "--heads value\t"},
|
{"heads", nil, NewIntSlice(), "--heads value\t(accepts multiple inputs)"},
|
||||||
{"H", nil, NewIntSlice(), "-H value\t"},
|
{"H", nil, NewIntSlice(), "-H value\t(accepts multiple inputs)"},
|
||||||
{"H", []string{"heads"}, NewIntSlice(9, 3), "-H value, --heads value\t(default: 9, 3)"},
|
{"H", []string{"heads"}, NewIntSlice(9, 3), "-H value, --heads value\t(default: 9, 3)\t(accepts multiple inputs)"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntSliceFlagHelpOutput(t *testing.T) {
|
func TestIntSliceFlagHelpOutput(t *testing.T) {
|
||||||
@ -660,16 +683,55 @@ func TestIntSliceFlagApply_SetsAllNames(t *testing.T) {
|
|||||||
expect(t, err, nil)
|
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 {
|
var int64SliceFlagTests = []struct {
|
||||||
name string
|
name string
|
||||||
aliases []string
|
aliases []string
|
||||||
value *Int64Slice
|
value *Int64Slice
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{"heads", nil, NewInt64Slice(), "--heads value\t"},
|
{"heads", nil, NewInt64Slice(), "--heads value\t(accepts multiple inputs)"},
|
||||||
{"H", nil, NewInt64Slice(), "-H value\t"},
|
{"H", nil, NewInt64Slice(), "-H value\t(accepts multiple inputs)"},
|
||||||
{"heads", []string{"H"}, NewInt64Slice(int64(2), int64(17179869184)),
|
{"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) {
|
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 {
|
var float64FlagTests = []struct {
|
||||||
name string
|
name string
|
||||||
expected string
|
expected string
|
||||||
@ -757,10 +873,10 @@ var float64SliceFlagTests = []struct {
|
|||||||
value *Float64Slice
|
value *Float64Slice
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{"heads", nil, NewFloat64Slice(), "--heads value\t"},
|
{"heads", nil, NewFloat64Slice(), "--heads value\t(accepts multiple inputs)"},
|
||||||
{"H", nil, NewFloat64Slice(), "-H value\t"},
|
{"H", nil, NewFloat64Slice(), "-H value\t(accepts multiple inputs)"},
|
||||||
{"heads", []string{"H"}, NewFloat64Slice(0.1234, -10.5),
|
{"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) {
|
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"})
|
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\""))
|
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
|
Value *Timestamp
|
||||||
DefaultText string
|
DefaultText string
|
||||||
HasBeenSet bool
|
HasBeenSet bool
|
||||||
|
Destination *Timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSet returns whether or not the flag has been set through env or file
|
// IsSet returns whether or not the flag has been set through env or file
|
||||||
@ -113,6 +114,11 @@ func (f *TimestampFlag) GetValue() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *TimestampFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
|
func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
|
||||||
if f.Layout == "" {
|
if f.Layout == "" {
|
||||||
@ -123,6 +129,10 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
|
|||||||
}
|
}
|
||||||
f.Value.SetLayout(f.Layout)
|
f.Value.SetLayout(f.Layout)
|
||||||
|
|
||||||
|
if f.Destination != nil {
|
||||||
|
f.Destination.SetLayout(f.Layout)
|
||||||
|
}
|
||||||
|
|
||||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||||
if err := f.Value.Set(val); err != nil {
|
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)
|
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() {
|
for _, name := range f.Names() {
|
||||||
|
if f.Destination != nil {
|
||||||
|
set.Var(f.Destination, name, f.Usage)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
set.Var(f.Value, name, f.Usage)
|
set.Var(f.Value, name, f.Usage)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -138,7 +153,7 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
|
|||||||
|
|
||||||
// Timestamp gets the timestamp from a flag name
|
// Timestamp gets the timestamp from a flag name
|
||||||
func (c *Context) Timestamp(name string) *time.Time {
|
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 lookupTimestamp(name, fs)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -52,6 +52,11 @@ func (f *UintFlag) GetUsage() string {
|
|||||||
return f.Usage
|
return f.Usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *UintFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
func (f *UintFlag) Apply(set *flag.FlagSet) error {
|
func (f *UintFlag) Apply(set *flag.FlagSet) error {
|
||||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||||
@ -86,7 +91,7 @@ func (f *UintFlag) GetValue() string {
|
|||||||
// Uint looks up the value of a local UintFlag, returns
|
// Uint looks up the value of a local UintFlag, returns
|
||||||
// 0 if not found
|
// 0 if not found
|
||||||
func (c *Context) Uint(name string) uint {
|
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 lookupUint(name, fs)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
@ -52,6 +52,11 @@ func (f *Uint64Flag) GetUsage() string {
|
|||||||
return f.Usage
|
return f.Usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *Uint64Flag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
func (f *Uint64Flag) Apply(set *flag.FlagSet) error {
|
func (f *Uint64Flag) Apply(set *flag.FlagSet) error {
|
||||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||||
@ -86,7 +91,7 @@ func (f *Uint64Flag) GetValue() string {
|
|||||||
// Uint64 looks up the value of a local Uint64Flag, returns
|
// Uint64 looks up the value of a local Uint64Flag, returns
|
||||||
// 0 if not found
|
// 0 if not found
|
||||||
func (c *Context) Uint64(name string) uint64 {
|
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 lookupUint64(name, fs)
|
||||||
}
|
}
|
||||||
return 0
|
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
|
// CommandNotFoundFunc is executed if the proper command cannot be found
|
||||||
type CommandNotFoundFunc func(*Context, string)
|
type CommandNotFoundFunc func(*Context, string)
|
||||||
|
|
||||||
// OnUsageErrorFunc is executed if an usage error occurs. This is useful for displaying
|
// OnUsageErrorFunc is executed if a usage error occurs. This is useful for displaying
|
||||||
// customized usage error messages. This function is able to replace the
|
// customized usage error messages. This function is able to replace the
|
||||||
// original error messages. If this function is not set, the "Incorrect usage"
|
// original error messages. If this function is not set, the "Incorrect usage"
|
||||||
// is displayed and the execution is interrupted.
|
// is displayed and the execution is interrupted.
|
||||||
|
10
go.mod
10
go.mod
@ -1,9 +1,11 @@
|
|||||||
module github.com/urfave/cli/v2
|
module github.com/urfave/cli/v2
|
||||||
|
|
||||||
go 1.11
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v0.3.1
|
github.com/BurntSushi/toml v1.1.0
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d
|
github.com/cpuguy83/go-md2man/v2 v2.0.1
|
||||||
gopkg.in/yaml.v2 v2.2.2
|
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 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
28
help.go
28
help.go
@ -72,13 +72,13 @@ func ShowAppHelpAndExit(c *Context, exitCode int) {
|
|||||||
|
|
||||||
// ShowAppHelp is an action that displays the help.
|
// ShowAppHelp is an action that displays the help.
|
||||||
func ShowAppHelp(c *Context) error {
|
func ShowAppHelp(c *Context) error {
|
||||||
template := c.App.CustomAppHelpTemplate
|
tpl := c.App.CustomAppHelpTemplate
|
||||||
if template == "" {
|
if tpl == "" {
|
||||||
template = AppHelpTemplate
|
tpl = AppHelpTemplate
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.App.ExtraInfo == nil {
|
if c.App.ExtraInfo == nil {
|
||||||
HelpPrinter(c.App.Writer, template, c.App)
|
HelpPrinter(c.App.Writer, tpl, c.App)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ func ShowAppHelp(c *Context) error {
|
|||||||
"ExtraInfo": c.App.ExtraInfo,
|
"ExtraInfo": c.App.ExtraInfo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HelpPrinterCustom(c.App.Writer, template, c.App, customAppData())
|
HelpPrinterCustom(c.App.Writer, tpl, c.App, customAppData())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -215,6 +215,12 @@ func ShowCommandHelp(ctx *Context, command string) error {
|
|||||||
return nil
|
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
|
// ShowSubcommandHelp prints help for the given subcommand
|
||||||
func ShowSubcommandHelp(c *Context) error {
|
func ShowSubcommandHelp(c *Context) error {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
@ -265,6 +271,9 @@ func ShowCommandCompletions(ctx *Context, command string) {
|
|||||||
func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs map[string]interface{}) {
|
func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs map[string]interface{}) {
|
||||||
funcMap := template.FuncMap{
|
funcMap := template.FuncMap{
|
||||||
"join": strings.Join,
|
"join": strings.Join,
|
||||||
|
"indent": indent,
|
||||||
|
"nindent": nindent,
|
||||||
|
"trim": strings.TrimSpace,
|
||||||
}
|
}
|
||||||
for key, value := range customFuncs {
|
for key, value := range customFuncs {
|
||||||
funcMap[key] = value
|
funcMap[key] = value
|
||||||
@ -367,3 +376,12 @@ func checkCommandCompletions(c *Context, name string) bool {
|
|||||||
ShowCommandCompletions(c, name)
|
ShowCommandCompletions(c, name)
|
||||||
return true
|
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) {
|
func Test_Help_Custom_Flags(t *testing.T) {
|
||||||
oldFlag := HelpFlag
|
oldFlag := HelpFlag
|
||||||
defer func() {
|
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) {
|
func TestShowSubcommandHelp_SubcommandUsageText(t *testing.T) {
|
||||||
app := &App{
|
app := &App{
|
||||||
Commands: []*Command{
|
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) {
|
func TestShowAppHelp_HiddenCommand(t *testing.T) {
|
||||||
app := &App{
|
app := &App{
|
||||||
Commands: []*Command{
|
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) {
|
func TestHideHelpCommand(t *testing.T) {
|
||||||
app := &App{
|
app := &App{
|
||||||
HideHelpCommand: true,
|
HideHelpCommand: true,
|
||||||
|
@ -3,24 +3,17 @@ package cli
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
wd, _ = os.Getwd()
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
_ = os.Setenv("CLI_TEMPLATE_REPANIC", "1")
|
_ = os.Setenv("CLI_TEMPLATE_REPANIC", "1")
|
||||||
}
|
}
|
||||||
|
|
||||||
func expect(t *testing.T, a interface{}, b interface{}) {
|
func expect(t *testing.T, a interface{}, b interface{}) {
|
||||||
_, fn, line, _ := runtime.Caller(1)
|
t.Helper()
|
||||||
fn = strings.Replace(fn, wd+"/", "", -1)
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(a, b) {
|
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"
|
cliBuiltFilePath = "./internal/example-cli/built-example"
|
||||||
helloSourceFilePath = "./internal/example-hello-world/example-hello-world.go"
|
helloSourceFilePath = "./internal/example-hello-world/example-hello-world.go"
|
||||||
helloBuiltFilePath = "./internal/example-hello-world/built-example"
|
helloBuiltFilePath = "./internal/example-hello-world/built-example"
|
||||||
desiredMinBinarySize = 2.0
|
desiredMinBinarySize = 1.9
|
||||||
desiredMaxBinarySize = 2.1
|
desiredMaxBinarySize = 2.2
|
||||||
badNewsEmoji = "🚨"
|
badNewsEmoji = "🚨"
|
||||||
goodNewsEmoji = "✨"
|
goodNewsEmoji = "✨"
|
||||||
checksPassedEmoji = "✅"
|
checksPassedEmoji = "✅"
|
||||||
|
26
template.go
26
template.go
@ -7,13 +7,13 @@ var AppHelpTemplate = `NAME:
|
|||||||
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
|
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
|
{{if .UsageText}}{{.UsageText | nindent 3 | trim}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
|
||||||
|
|
||||||
VERSION:
|
VERSION:
|
||||||
{{.Version}}{{end}}{{end}}{{if .Description}}
|
{{.Version}}{{end}}{{end}}{{if .Description}}
|
||||||
|
|
||||||
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}}:
|
AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
|
||||||
{{range $index, $author := .Authors}}{{if $index}}
|
{{range $index, $author := .Authors}}{{if $index}}
|
||||||
@ -39,13 +39,13 @@ var CommandHelpTemplate = `NAME:
|
|||||||
{{.HelpName}} - {{.Usage}}
|
{{.HelpName}} - {{.Usage}}
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
|
{{if .UsageText}}{{.UsageText | nindent 3 | trim}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
|
||||||
|
|
||||||
CATEGORY:
|
CATEGORY:
|
||||||
{{.Category}}{{end}}{{if .Description}}
|
{{.Category}}{{end}}{{if .Description}}
|
||||||
|
|
||||||
DESCRIPTION:
|
DESCRIPTION:
|
||||||
{{.Description}}{{end}}{{if .VisibleFlags}}
|
{{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlags}}
|
||||||
|
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
{{range .VisibleFlags}}{{.}}
|
{{range .VisibleFlags}}{{.}}
|
||||||
@ -59,10 +59,10 @@ var SubcommandHelpTemplate = `NAME:
|
|||||||
{{.HelpName}} - {{.Usage}}
|
{{.HelpName}} - {{.Usage}}
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}}
|
{{if .UsageText}}{{.UsageText | nindent 3 | trim}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}}
|
||||||
|
|
||||||
DESCRIPTION:
|
DESCRIPTION:
|
||||||
{{.Description}}{{end}}
|
{{.Description | nindent 3 | trim}}{{end}}
|
||||||
|
|
||||||
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
|
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
|
||||||
{{.Name}}:{{range .VisibleCommands}}
|
{{.Name}}:{{range .VisibleCommands}}
|
||||||
@ -74,9 +74,9 @@ OPTIONS:
|
|||||||
{{end}}{{end}}
|
{{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 }}
|
{{ .App.Name }}{{ if .App.Usage }} - {{ .App.Usage }}{{ end }}
|
||||||
|
|
||||||
@ -86,16 +86,18 @@ var MarkdownDocTemplate = `% {{ .App.Name }} 8
|
|||||||
{{ if .SynopsisArgs }}
|
{{ if .SynopsisArgs }}
|
||||||
` + "```" + `
|
` + "```" + `
|
||||||
{{ range $v := .SynopsisArgs }}{{ $v }}{{ end }}` + "```" + `
|
{{ range $v := .SynopsisArgs }}{{ $v }}{{ end }}` + "```" + `
|
||||||
{{ end }}{{ if .App.UsageText }}
|
{{ end }}{{ if .App.Description }}
|
||||||
# DESCRIPTION
|
# DESCRIPTION
|
||||||
|
|
||||||
{{ .App.UsageText }}
|
{{ .App.Description }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
**Usage**:
|
**Usage**:
|
||||||
|
|
||||||
` + "```" + `
|
` + "```" + `{{ if .App.UsageText }}
|
||||||
|
{{ .App.UsageText }}
|
||||||
|
{{ else }}
|
||||||
{{ .App.Name }} [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
|
{{ .App.Name }} [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
|
||||||
` + "```" + `
|
{{ end }}` + "```" + `
|
||||||
{{ if .GlobalArgs }}
|
{{ if .GlobalArgs }}
|
||||||
# GLOBAL OPTIONS
|
# GLOBAL OPTIONS
|
||||||
{{ range $v := .GlobalArgs }}
|
{{ range $v := .GlobalArgs }}
|
||||||
|
72
testdata/expected-doc-full.man
vendored
72
testdata/expected-doc-full.man
vendored
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
.PP
|
.PP
|
||||||
greet \- Some app
|
greet - Some app
|
||||||
|
|
||||||
|
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@ -14,9 +14,9 @@ greet
|
|||||||
.RS
|
.RS
|
||||||
|
|
||||||
.nf
|
.nf
|
||||||
[\-\-another\-flag|\-b]
|
[--another-flag|-b]
|
||||||
[\-\-flag|\-\-fl|\-f]=[value]
|
[--flag|--fl|-f]=[value]
|
||||||
[\-\-socket|\-s]=[value]
|
[--socket|-s]=[value]
|
||||||
|
|
||||||
.fi
|
.fi
|
||||||
.RE
|
.RE
|
||||||
@ -24,7 +24,7 @@ greet
|
|||||||
|
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
.PP
|
.PP
|
||||||
app [first\_arg] [second\_arg]
|
Description of the application.
|
||||||
|
|
||||||
.PP
|
.PP
|
||||||
\fBUsage\fP:
|
\fBUsage\fP:
|
||||||
@ -33,7 +33,7 @@ app [first\_arg] [second\_arg]
|
|||||||
.RS
|
.RS
|
||||||
|
|
||||||
.nf
|
.nf
|
||||||
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
|
app [first_arg] [second_arg]
|
||||||
|
|
||||||
.fi
|
.fi
|
||||||
.RE
|
.RE
|
||||||
@ -41,13 +41,13 @@ greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
|
|||||||
|
|
||||||
.SH GLOBAL OPTIONS
|
.SH GLOBAL OPTIONS
|
||||||
.PP
|
.PP
|
||||||
\fB\-\-another\-flag, \-b\fP: another usage text
|
\fB--another-flag, -b\fP: another usage text
|
||||||
|
|
||||||
.PP
|
.PP
|
||||||
\fB\-\-flag, \-\-fl, \-f\fP="":
|
\fB--flag, --fl, -f\fP="":
|
||||||
|
|
||||||
.PP
|
.PP
|
||||||
\fB\-\-socket, \-s\fP="": some 'usage' text (default: value)
|
\fB--socket, -s\fP="": some 'usage' text (default: value)
|
||||||
|
|
||||||
|
|
||||||
.SH COMMANDS
|
.SH COMMANDS
|
||||||
@ -56,23 +56,65 @@ greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
|
|||||||
another usage test
|
another usage test
|
||||||
|
|
||||||
.PP
|
.PP
|
||||||
\fB\-\-another\-flag, \-b\fP: another usage text
|
\fB--another-flag, -b\fP: another usage text
|
||||||
|
|
||||||
.PP
|
.PP
|
||||||
\fB\-\-flag, \-\-fl, \-f\fP="":
|
\fB--flag, --fl, -f\fP="":
|
||||||
|
|
||||||
.SS sub\-config, s, ss
|
.SS sub-config, s, ss
|
||||||
.PP
|
.PP
|
||||||
another usage test
|
another usage test
|
||||||
|
|
||||||
.PP
|
.PP
|
||||||
\fB\-\-sub\-command\-flag, \-s\fP: some usage text
|
\fB--sub-command-flag, -s\fP: some usage text
|
||||||
|
|
||||||
.PP
|
.PP
|
||||||
\fB\-\-sub\-flag, \-\-sub\-fl, \-s\fP="":
|
\fB--sub-flag, --sub-fl, -s\fP="":
|
||||||
|
|
||||||
.SH info, i, in
|
.SH info, i, in
|
||||||
.PP
|
.PP
|
||||||
retrieve generic information
|
retrieve generic information
|
||||||
|
|
||||||
.SH some\-command
|
.SH some-command
|
||||||
|
.SH usage, u
|
||||||
|
.PP
|
||||||
|
standard usage text
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.RS
|
||||||
|
|
||||||
|
.nf
|
||||||
|
Usage for the usage text
|
||||||
|
- formatted: Based on the specified ConfigMap and summon secrets.yml
|
||||||
|
- list: Inspect the environment for a specific process running on a Pod
|
||||||
|
- for_effect: Compare 'namespace' environment with 'local'
|
||||||
|
|
||||||
|
```
|
||||||
|
func() { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
Should be a part of the same code block
|
||||||
|
|
||||||
|
.fi
|
||||||
|
.RE
|
||||||
|
|
||||||
|
.PP
|
||||||
|
\fB--another-flag, -b\fP: another usage text
|
||||||
|
|
||||||
|
.PP
|
||||||
|
\fB--flag, --fl, -f\fP="":
|
||||||
|
|
||||||
|
.SS sub-usage, su
|
||||||
|
.PP
|
||||||
|
standard usage text
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.RS
|
||||||
|
|
||||||
|
.PP
|
||||||
|
Single line of UsageText
|
||||||
|
|
||||||
|
.RE
|
||||||
|
|
||||||
|
.PP
|
||||||
|
\fB--sub-command-flag, -s\fP: some usage text
|
||||||
|
32
testdata/expected-doc-full.md
vendored
32
testdata/expected-doc-full.md
vendored
@ -1,5 +1,3 @@
|
|||||||
% greet 8
|
|
||||||
|
|
||||||
# NAME
|
# NAME
|
||||||
|
|
||||||
greet - Some app
|
greet - Some app
|
||||||
@ -16,12 +14,12 @@ greet
|
|||||||
|
|
||||||
# DESCRIPTION
|
# DESCRIPTION
|
||||||
|
|
||||||
app [first_arg] [second_arg]
|
Description of the application.
|
||||||
|
|
||||||
**Usage**:
|
**Usage**:
|
||||||
|
|
||||||
```
|
```
|
||||||
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
|
app [first_arg] [second_arg]
|
||||||
```
|
```
|
||||||
|
|
||||||
# GLOBAL OPTIONS
|
# GLOBAL OPTIONS
|
||||||
@ -58,3 +56,29 @@ retrieve generic information
|
|||||||
## some-command
|
## some-command
|
||||||
|
|
||||||
|
|
||||||
|
## usage, u
|
||||||
|
|
||||||
|
standard usage text
|
||||||
|
|
||||||
|
Usage for the usage text
|
||||||
|
- formatted: Based on the specified ConfigMap and summon secrets.yml
|
||||||
|
- list: Inspect the environment for a specific process running on a Pod
|
||||||
|
- for_effect: Compare 'namespace' environment with 'local'
|
||||||
|
|
||||||
|
```
|
||||||
|
func() { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
Should be a part of the same code block
|
||||||
|
|
||||||
|
**--another-flag, -b**: another usage text
|
||||||
|
|
||||||
|
**--flag, --fl, -f**="":
|
||||||
|
|
||||||
|
### sub-usage, su
|
||||||
|
|
||||||
|
standard usage text
|
||||||
|
|
||||||
|
>Single line of UsageText
|
||||||
|
|
||||||
|
**--sub-command-flag, -s**: some usage text
|
||||||
|
32
testdata/expected-doc-no-authors.md
vendored
32
testdata/expected-doc-no-authors.md
vendored
@ -1,5 +1,3 @@
|
|||||||
% greet 8
|
|
||||||
|
|
||||||
# NAME
|
# NAME
|
||||||
|
|
||||||
greet - Some app
|
greet - Some app
|
||||||
@ -16,12 +14,12 @@ greet
|
|||||||
|
|
||||||
# DESCRIPTION
|
# DESCRIPTION
|
||||||
|
|
||||||
app [first_arg] [second_arg]
|
Description of the application.
|
||||||
|
|
||||||
**Usage**:
|
**Usage**:
|
||||||
|
|
||||||
```
|
```
|
||||||
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
|
app [first_arg] [second_arg]
|
||||||
```
|
```
|
||||||
|
|
||||||
# GLOBAL OPTIONS
|
# GLOBAL OPTIONS
|
||||||
@ -58,3 +56,29 @@ retrieve generic information
|
|||||||
## some-command
|
## some-command
|
||||||
|
|
||||||
|
|
||||||
|
## usage, u
|
||||||
|
|
||||||
|
standard usage text
|
||||||
|
|
||||||
|
Usage for the usage text
|
||||||
|
- formatted: Based on the specified ConfigMap and summon secrets.yml
|
||||||
|
- list: Inspect the environment for a specific process running on a Pod
|
||||||
|
- for_effect: Compare 'namespace' environment with 'local'
|
||||||
|
|
||||||
|
```
|
||||||
|
func() { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
Should be a part of the same code block
|
||||||
|
|
||||||
|
**--another-flag, -b**: another usage text
|
||||||
|
|
||||||
|
**--flag, --fl, -f**="":
|
||||||
|
|
||||||
|
### sub-usage, su
|
||||||
|
|
||||||
|
standard usage text
|
||||||
|
|
||||||
|
>Single line of UsageText
|
||||||
|
|
||||||
|
**--sub-command-flag, -s**: some usage text
|
||||||
|
6
testdata/expected-doc-no-commands.md
vendored
6
testdata/expected-doc-no-commands.md
vendored
@ -1,5 +1,3 @@
|
|||||||
% greet 8
|
|
||||||
|
|
||||||
# NAME
|
# NAME
|
||||||
|
|
||||||
greet - Some app
|
greet - Some app
|
||||||
@ -16,12 +14,12 @@ greet
|
|||||||
|
|
||||||
# DESCRIPTION
|
# DESCRIPTION
|
||||||
|
|
||||||
app [first_arg] [second_arg]
|
Description of the application.
|
||||||
|
|
||||||
**Usage**:
|
**Usage**:
|
||||||
|
|
||||||
```
|
```
|
||||||
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
|
app [first_arg] [second_arg]
|
||||||
```
|
```
|
||||||
|
|
||||||
# GLOBAL OPTIONS
|
# GLOBAL OPTIONS
|
||||||
|
32
testdata/expected-doc-no-flags.md
vendored
32
testdata/expected-doc-no-flags.md
vendored
@ -1,5 +1,3 @@
|
|||||||
% greet 8
|
|
||||||
|
|
||||||
# NAME
|
# NAME
|
||||||
|
|
||||||
greet - Some app
|
greet - Some app
|
||||||
@ -10,12 +8,12 @@ greet
|
|||||||
|
|
||||||
# DESCRIPTION
|
# DESCRIPTION
|
||||||
|
|
||||||
app [first_arg] [second_arg]
|
Description of the application.
|
||||||
|
|
||||||
**Usage**:
|
**Usage**:
|
||||||
|
|
||||||
```
|
```
|
||||||
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
|
app [first_arg] [second_arg]
|
||||||
```
|
```
|
||||||
|
|
||||||
# COMMANDS
|
# COMMANDS
|
||||||
@ -43,3 +41,29 @@ retrieve generic information
|
|||||||
## some-command
|
## some-command
|
||||||
|
|
||||||
|
|
||||||
|
## usage, u
|
||||||
|
|
||||||
|
standard usage text
|
||||||
|
|
||||||
|
Usage for the usage text
|
||||||
|
- formatted: Based on the specified ConfigMap and summon secrets.yml
|
||||||
|
- list: Inspect the environment for a specific process running on a Pod
|
||||||
|
- for_effect: Compare 'namespace' environment with 'local'
|
||||||
|
|
||||||
|
```
|
||||||
|
func() { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
Should be a part of the same code block
|
||||||
|
|
||||||
|
**--another-flag, -b**: another usage text
|
||||||
|
|
||||||
|
**--flag, --fl, -f**="":
|
||||||
|
|
||||||
|
### sub-usage, su
|
||||||
|
|
||||||
|
standard usage text
|
||||||
|
|
||||||
|
>Single line of UsageText
|
||||||
|
|
||||||
|
**--sub-command-flag, -s**: some usage text
|
||||||
|
84
testdata/expected-doc-no-usagetext.md
vendored
Normal file
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'
|
function __fish_greet_no_subcommand --description 'Test if there has been any subcommand yet'
|
||||||
for i in (commandline -opc)
|
for i in (commandline -opc)
|
||||||
if contains -- $i config c sub-config s ss info i in some-command
|
if contains -- $i config c sub-config s ss info i in some-command usage u sub-usage su
|
||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -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' -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 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' -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 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_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'
|
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 -r -c greet -n '__fish_greet_no_subcommand' -a 'info i in' -d 'retrieve generic information'
|
||||||
complete -c greet -n '__fish_seen_subcommand_from some-command' -f -l help -s h -d 'show help'
|
complete -c greet -n '__fish_seen_subcommand_from some-command' -f -l help -s h -d 'show help'
|
||||||
complete -r -c greet -n '__fish_greet_no_subcommand' -a 'some-command'
|
complete -r -c greet -n '__fish_greet_no_subcommand' -a 'some-command'
|
||||||
|
complete -c greet -n '__fish_seen_subcommand_from usage u' -f -l help -s h -d 'show help'
|
||||||
|
complete -r -c greet -n '__fish_greet_no_subcommand' -a 'usage u' -d 'standard usage text'
|
||||||
|
complete -c greet -n '__fish_seen_subcommand_from usage u' -l flag -s fl -s f -r
|
||||||
|
complete -c greet -n '__fish_seen_subcommand_from usage u' -f -l another-flag -s b -d 'another usage text'
|
||||||
|
complete -c greet -n '__fish_seen_subcommand_from sub-usage su' -f -l help -s h -d 'show help'
|
||||||
|
complete -r -c greet -n '__fish_seen_subcommand_from usage u' -a 'sub-usage su' -d 'standard usage text'
|
||||||
|
complete -c greet -n '__fish_seen_subcommand_from sub-usage su' -f -l sub-command-flag -s s -d 'some usage text'
|
||||||
|
Loading…
Reference in New Issue
Block a user