Merge remote-tracking branch 'origin/main' into applying-pr1218
This commit is contained in:
commit
56837b07d3
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:
|
||||||
|
83
.github/workflows/cli.yml
vendored
83
.github/workflows/cli.yml
vendored
@ -3,56 +3,55 @@ 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.15, 1.16, 1.17]
|
go: [1.16.x, 1.17.x, 1.18.x]
|
||||||
name: ${{ matrix.os }} @ Go ${{ matrix.go }}
|
name: ${{ matrix.os }} @ Go ${{ matrix.go }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Go ${{ matrix.go }}
|
- name: Set up Go ${{ matrix.go }}
|
||||||
uses: actions/setup-go@v1
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
- name: Set GOPATH, PATH and ENV
|
- name: Set PATH
|
||||||
run: |
|
run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}"
|
||||||
echo "GOPATH=$(dirname $GITHUB_WORKSPACE)" >> $GITHUB_ENV
|
|
||||||
echo "GO111MODULE=on" >> $GITHUB_ENV
|
|
||||||
echo "GOPROXY=https://proxy.golang.org" >> $GITHUB_ENV
|
|
||||||
echo "$(dirname $GITHUB_WORKSPACE)/bin" >> $GITHUB_PATH
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v3
|
||||||
with:
|
|
||||||
ref: ${{ github.ref }}
|
|
||||||
|
|
||||||
- name: GOFMT Check
|
- name: GOFMT Check
|
||||||
if: matrix.go == 1.17 && 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
|
||||||
run: go run internal/build/build.go vet
|
run: go run internal/build/build.go vet
|
||||||
|
|
||||||
|
- name: test with tags
|
||||||
|
run: go run internal/build/build.go -tags urfave_cli_no_docs test
|
||||||
|
|
||||||
- name: test
|
- name: test
|
||||||
run: go run internal/build/build.go test
|
run: go run internal/build/build.go test
|
||||||
|
|
||||||
- name: check-binary-size
|
- name: check-binary-size
|
||||||
run: go run internal/build/build.go check-binary-size
|
run: go run internal/build/build.go check-binary-size
|
||||||
|
|
||||||
|
- name: check-binary-size with tags (informational only)
|
||||||
|
run: go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size || true
|
||||||
|
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
if: success() && matrix.go == 1.17 && matrix.os == 'ubuntu-latest'
|
if: success() && matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest'
|
||||||
uses: codecov/codecov-action@v1
|
uses: codecov/codecov-action@v2
|
||||||
with:
|
with:
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
|
|
||||||
@ -61,44 +60,30 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v1
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
# Currently fails on 1.16+
|
go-version: 1.18.x
|
||||||
go-version: 1.15
|
|
||||||
|
|
||||||
- name: Use Node.js 12.x
|
- name: Use Node.js 16
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 12.x
|
node-version: '16'
|
||||||
|
|
||||||
- name: Set GOPATH, PATH and ENV
|
- name: Set PATH
|
||||||
run: |
|
run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}"
|
||||||
echo "GOPATH=$(dirname $GITHUB_WORKSPACE)" >> $GITHUB_ENV
|
|
||||||
echo "GO111MODULE=on" >> $GITHUB_ENV
|
|
||||||
echo "GOPROXY=https://proxy.golang.org" >> $GITHUB_ENV
|
|
||||||
echo "$(dirname $GITHUB_WORKSPACE)/bin" >> $GITHUB_PATH
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v3
|
||||||
with:
|
|
||||||
ref: ${{ github.ref }}
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: |
|
run:
|
||||||
mkdir -p $GOPATH/bin
|
mkdir -p "${GITHUB_WORKSPACE}/.local/bin" &&
|
||||||
curl -L -o $GOPATH/bin/gfmrun "https://github.com/urfave/gfmrun/releases/download/v1.2.14/gfmrun-$(go env GOOS)-amd64-v1.2.14"
|
curl -fsSL -o "${GITHUB_WORKSPACE}/.local/bin/gfmrun" "https://github.com/urfave/gfmrun/releases/download/v1.3.0/gfmrun-$(go env GOOS)-$(go env GOARCH)-v1.3.0" &&
|
||||||
chmod +x $GOPATH/bin/gfmrun
|
chmod +x "${GITHUB_WORKSPACE}/.local/bin/gfmrun" &&
|
||||||
npm install -g markdown-toc@1.2.0
|
npm install -g markdown-toc@1.2.0
|
||||||
|
|
||||||
- name: Run Tests (v1)
|
- name: gfmrun
|
||||||
if: contains(github.base_ref, 'v1')
|
run: go run internal/build/build.go gfmrun docs/v2/manual.md
|
||||||
run: |
|
|
||||||
go run internal/build/build.go gfmrun docs/v1/manual.md
|
|
||||||
go run internal/build/build.go toc docs/v1/manual.md
|
|
||||||
|
|
||||||
- name: Run Tests (v2)
|
- name: toc
|
||||||
if: contains(github.base_ref, 'master')
|
run: go run internal/build/build.go toc docs/v2/manual.md
|
||||||
run: |
|
|
||||||
go run internal/build/build.go gfmrun docs/v2/manual.md
|
|
||||||
go run internal/build/build.go toc docs/v2/manual.md
|
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,5 +5,6 @@ vendor
|
|||||||
.idea
|
.idea
|
||||||
internal/*/built-example
|
internal/*/built-example
|
||||||
coverage.txt
|
coverage.txt
|
||||||
|
/.local/
|
||||||
|
|
||||||
*.exe
|
*.exe
|
||||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2016 Jeremy Saenz & Contributors
|
Copyright (c) 2022 urfave/cli maintainers
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
24
README.md
24
README.md
@ -4,7 +4,7 @@ cli
|
|||||||
[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://pkg.go.dev/github.com/urfave/cli/v2)
|
[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://pkg.go.dev/github.com/urfave/cli/v2)
|
||||||
[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli)
|
[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli)
|
||||||
[![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli)
|
[![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli)
|
||||||
[![codecov](https://codecov.io/gh/urfave/cli/branch/master/graph/badge.svg)](https://codecov.io/gh/urfave/cli)
|
[![codecov](https://codecov.io/gh/urfave/cli/branch/main/graph/badge.svg)](https://codecov.io/gh/urfave/cli)
|
||||||
|
|
||||||
cli is a simple, fast, and fun package for building command line apps in Go. The
|
cli is a simple, fast, and fun package for building command line apps in Go. The
|
||||||
goal is to enable developers to write fast and distributable command line
|
goal is to enable developers to write fast and distributable command line
|
||||||
@ -12,7 +12,7 @@ applications in an expressive way.
|
|||||||
|
|
||||||
## Usage Documentation
|
## Usage Documentation
|
||||||
|
|
||||||
Usage documentation exists for each major version. Don't know what version you're on? You're probably using the version from the `master` branch, which is currently `v2`.
|
Usage documentation exists for each major version. Don't know what version you're on? You're probably using the version from the `main` branch, which is currently `v2`.
|
||||||
|
|
||||||
- `v2` - [./docs/v2/manual.md](./docs/v2/manual.md)
|
- `v2` - [./docs/v2/manual.md](./docs/v2/manual.md)
|
||||||
- `v1` - [./docs/v1/manual.md](./docs/v1/manual.md)
|
- `v1` - [./docs/v1/manual.md](./docs/v1/manual.md)
|
||||||
@ -30,7 +30,7 @@ Go Modules are required when using this package. [See the go blog guide on using
|
|||||||
### Using `v2` releases
|
### Using `v2` releases
|
||||||
|
|
||||||
```
|
```
|
||||||
$ GO111MODULE=on go get github.com/urfave/cli/v2
|
$ go get github.com/urfave/cli/v2
|
||||||
```
|
```
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@ -44,7 +44,7 @@ import (
|
|||||||
### Using `v1` releases
|
### Using `v1` releases
|
||||||
|
|
||||||
```
|
```
|
||||||
$ GO111MODULE=on go get github.com/urfave/cli
|
$ go get github.com/urfave/cli
|
||||||
```
|
```
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@ -55,6 +55,16 @@ import (
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Build tags
|
||||||
|
|
||||||
|
You can use the following build tags:
|
||||||
|
|
||||||
|
#### `urfave_cli_no_docs`
|
||||||
|
|
||||||
|
When set, this removes `ToMarkdown` and `ToMan` methods, so your application
|
||||||
|
won't be able to call those. This reduces the resulting binary size by about
|
||||||
|
300-400 KB (measured using Go 1.18.1 on Linux/amd64), due to less dependencies.
|
||||||
|
|
||||||
### GOPATH
|
### GOPATH
|
||||||
|
|
||||||
Make sure your `PATH` includes the `$GOPATH/bin` directory so your commands can
|
Make sure your `PATH` includes the `$GOPATH/bin` directory so your commands can
|
||||||
@ -67,4 +77,8 @@ 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).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
See [`LICENSE`](./LICENSE)
|
||||||
|
224
altsrc/flag.go
224
altsrc/flag.go
@ -13,18 +13,18 @@ import (
|
|||||||
// allows a value to be set on the existing parsed flags.
|
// allows a value to be set on the existing parsed flags.
|
||||||
type FlagInputSourceExtension interface {
|
type FlagInputSourceExtension interface {
|
||||||
cli.Flag
|
cli.Flag
|
||||||
ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error
|
ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyInputSourceValues iterates over all provided flags and
|
// ApplyInputSourceValues iterates over all provided flags and
|
||||||
// executes ApplyInputSourceValue on flags implementing the
|
// executes ApplyInputSourceValue on flags implementing the
|
||||||
// FlagInputSourceExtension interface to initialize these flags
|
// FlagInputSourceExtension interface to initialize these flags
|
||||||
// to an alternate input source.
|
// to an alternate input source.
|
||||||
func ApplyInputSourceValues(context *cli.Context, inputSourceContext InputSourceContext, flags []cli.Flag) error {
|
func ApplyInputSourceValues(cCtx *cli.Context, inputSourceContext InputSourceContext, flags []cli.Flag) error {
|
||||||
for _, f := range flags {
|
for _, f := range flags {
|
||||||
inputSourceExtendedFlag, isType := f.(FlagInputSourceExtension)
|
inputSourceExtendedFlag, isType := f.(FlagInputSourceExtension)
|
||||||
if isType {
|
if isType {
|
||||||
err := inputSourceExtendedFlag.ApplyInputSourceValue(context, inputSourceContext)
|
err := inputSourceExtendedFlag.ApplyInputSourceValue(cCtx, inputSourceContext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -38,42 +38,40 @@ func ApplyInputSourceValues(context *cli.Context, inputSourceContext InputSource
|
|||||||
// input source based on the func provided. If there is no error it will then apply the new input source to any flags
|
// input source based on the func provided. If there is no error it will then apply the new input source to any flags
|
||||||
// that are supported by the input source
|
// that are supported by the input source
|
||||||
func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceContext, error)) cli.BeforeFunc {
|
func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceContext, error)) cli.BeforeFunc {
|
||||||
return func(context *cli.Context) error {
|
return func(cCtx *cli.Context) error {
|
||||||
inputSource, err := createInputSource()
|
inputSource, err := createInputSource()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Unable to create input source: inner error: \n'%v'", err.Error())
|
return fmt.Errorf("Unable to create input source: inner error: \n'%v'", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return ApplyInputSourceValues(context, inputSource, flags)
|
return ApplyInputSourceValues(cCtx, inputSource, flags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitInputSourceWithContext is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new
|
// InitInputSourceWithContext is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new
|
||||||
// input source based on the func provided with potentially using existing cli.Context values to initialize itself. If there is
|
// input source based on the func provided with potentially using existing cli.Context values to initialize itself. If there is
|
||||||
// no error it will then apply the new input source to any flags that are supported by the input source
|
// no error it will then apply the new input source to any flags that are supported by the input source
|
||||||
func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context *cli.Context) (InputSourceContext, error)) cli.BeforeFunc {
|
func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(cCtx *cli.Context) (InputSourceContext, error)) cli.BeforeFunc {
|
||||||
return func(context *cli.Context) error {
|
return func(cCtx *cli.Context) error {
|
||||||
inputSource, err := createInputSource(context)
|
inputSource, err := createInputSource(cCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Unable to create input source with context: inner error: \n'%v'", err.Error())
|
return fmt.Errorf("Unable to create input source with context: inner error: \n'%v'", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return ApplyInputSourceValues(context, inputSource, flags)
|
return ApplyInputSourceValues(cCtx, inputSource, flags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyInputSourceValue applies a generic value to the flagSet if required
|
// ApplyInputSourceValue applies a generic value to the flagSet if required
|
||||||
func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
func (f *GenericFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
|
||||||
if f.set != nil {
|
if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.isSet(f.GenericFlag.Name) {
|
||||||
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) {
|
value, err := isc.Generic(f.GenericFlag.Name)
|
||||||
value, err := isc.Generic(f.GenericFlag.Name)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
}
|
if value != nil {
|
||||||
if value != nil {
|
for _, name := range f.Names() {
|
||||||
for _, name := range f.Names() {
|
_ = f.set.Set(name, value.String())
|
||||||
_ = f.set.Set(name, value.String())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,20 +80,18 @@ func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourc
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ApplyInputSourceValue applies a StringSlice value to the flagSet if required
|
// ApplyInputSourceValue applies a StringSlice value to the flagSet if required
|
||||||
func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
func (f *StringSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
|
||||||
if f.set != nil {
|
if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.isSet(f.StringSliceFlag.Name) {
|
||||||
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) {
|
value, err := isc.StringSlice(f.StringSliceFlag.Name)
|
||||||
value, err := isc.StringSlice(f.StringSliceFlag.Name)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
}
|
if value != nil {
|
||||||
if value != nil {
|
var sliceValue cli.StringSlice = *(cli.NewStringSlice(value...))
|
||||||
var sliceValue cli.StringSlice = *(cli.NewStringSlice(value...))
|
for _, name := range f.Names() {
|
||||||
for _, name := range f.Names() {
|
underlyingFlag := f.set.Lookup(name)
|
||||||
underlyingFlag := f.set.Lookup(name)
|
if underlyingFlag != nil {
|
||||||
if underlyingFlag != nil {
|
underlyingFlag.Value = &sliceValue
|
||||||
underlyingFlag.Value = &sliceValue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,20 +100,18 @@ func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputS
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ApplyInputSourceValue applies a IntSlice value if required
|
// ApplyInputSourceValue applies a IntSlice value if required
|
||||||
func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
func (f *IntSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
|
||||||
if f.set != nil {
|
if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.isSet(f.IntSliceFlag.Name) {
|
||||||
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) {
|
value, err := isc.IntSlice(f.IntSliceFlag.Name)
|
||||||
value, err := isc.IntSlice(f.IntSliceFlag.Name)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
}
|
if value != nil {
|
||||||
if value != nil {
|
var sliceValue cli.IntSlice = *(cli.NewIntSlice(value...))
|
||||||
var sliceValue cli.IntSlice = *(cli.NewIntSlice(value...))
|
for _, name := range f.Names() {
|
||||||
for _, name := range f.Names() {
|
underlyingFlag := f.set.Lookup(name)
|
||||||
underlyingFlag := f.set.Lookup(name)
|
if underlyingFlag != nil {
|
||||||
if underlyingFlag != nil {
|
underlyingFlag.Value = &sliceValue
|
||||||
underlyingFlag.Value = &sliceValue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,17 +120,15 @@ func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSour
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ApplyInputSourceValue applies a Bool value to the flagSet if required
|
// ApplyInputSourceValue applies a Bool value to the flagSet if required
|
||||||
func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
func (f *BoolFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
|
||||||
if f.set != nil {
|
if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.isSet(f.BoolFlag.Name) {
|
||||||
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) {
|
value, err := isc.Bool(f.BoolFlag.Name)
|
||||||
value, err := isc.Bool(f.BoolFlag.Name)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
}
|
if value {
|
||||||
if value {
|
for _, name := range f.Names() {
|
||||||
for _, name := range f.Names() {
|
_ = f.set.Set(name, strconv.FormatBool(value))
|
||||||
_ = f.set.Set(name, strconv.FormatBool(value))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,17 +136,15 @@ func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ApplyInputSourceValue applies a String value to the flagSet if required
|
// ApplyInputSourceValue applies a String value to the flagSet if required
|
||||||
func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
func (f *StringFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
|
||||||
if f.set != nil {
|
if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.isSet(f.StringFlag.Name) {
|
||||||
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) {
|
value, err := isc.String(f.StringFlag.Name)
|
||||||
value, err := isc.String(f.StringFlag.Name)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
}
|
if value != "" {
|
||||||
if value != "" {
|
for _, name := range f.Names() {
|
||||||
for _, name := range f.Names() {
|
_ = f.set.Set(name, value)
|
||||||
_ = f.set.Set(name, value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,27 +152,25 @@ func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSource
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ApplyInputSourceValue applies a Path value to the flagSet if required
|
// ApplyInputSourceValue applies a Path value to the flagSet if required
|
||||||
func (f *PathFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
func (f *PathFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
|
||||||
if f.set != nil {
|
if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.isSet(f.PathFlag.Name) {
|
||||||
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) {
|
value, err := isc.String(f.PathFlag.Name)
|
||||||
value, err := isc.String(f.PathFlag.Name)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
}
|
if value != "" {
|
||||||
if value != "" {
|
for _, name := range f.Names() {
|
||||||
for _, name := range f.Names() {
|
|
||||||
|
|
||||||
if !filepath.IsAbs(value) && isc.Source() != "" {
|
if !filepath.IsAbs(value) && isc.Source() != "" {
|
||||||
basePathAbs, err := filepath.Abs(isc.Source())
|
basePathAbs, err := filepath.Abs(isc.Source())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
value = filepath.Join(filepath.Dir(basePathAbs), value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = f.set.Set(name, value)
|
value = filepath.Join(filepath.Dir(basePathAbs), value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = f.set.Set(name, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,55 +178,43 @@ func (f *PathFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ApplyInputSourceValue applies a int value to the flagSet if required
|
// ApplyInputSourceValue applies a int value to the flagSet if required
|
||||||
func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
func (f *IntFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
|
||||||
if f.set != nil {
|
if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.isSet(f.IntFlag.Name) {
|
||||||
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) {
|
value, err := isc.Int(f.IntFlag.Name)
|
||||||
value, err := isc.Int(f.IntFlag.Name)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
}
|
for _, name := range f.Names() {
|
||||||
if value > 0 {
|
_ = f.set.Set(name, strconv.FormatInt(int64(value), 10))
|
||||||
for _, name := range f.Names() {
|
|
||||||
_ = f.set.Set(name, strconv.FormatInt(int64(value), 10))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyInputSourceValue applies a Duration value to the flagSet if required
|
// ApplyInputSourceValue applies a Duration value to the flagSet if required
|
||||||
func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
func (f *DurationFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
|
||||||
if f.set != nil {
|
if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.isSet(f.DurationFlag.Name) {
|
||||||
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) {
|
value, err := isc.Duration(f.DurationFlag.Name)
|
||||||
value, err := isc.Duration(f.DurationFlag.Name)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
}
|
for _, name := range f.Names() {
|
||||||
if value > 0 {
|
_ = f.set.Set(name, value.String())
|
||||||
for _, name := range f.Names() {
|
|
||||||
_ = f.set.Set(name, value.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyInputSourceValue applies a Float64 value to the flagSet if required
|
// ApplyInputSourceValue applies a Float64 value to the flagSet if required
|
||||||
func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
func (f *Float64Flag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
|
||||||
if f.set != nil {
|
if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.isSet(f.Float64Flag.Name) {
|
||||||
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) {
|
value, err := isc.Float64(f.Float64Flag.Name)
|
||||||
value, err := isc.Float64(f.Float64Flag.Name)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
}
|
floatStr := float64ToString(value)
|
||||||
if value > 0 {
|
for _, name := range f.Names() {
|
||||||
floatStr := float64ToString(value)
|
_ = f.set.Set(name, floatStr)
|
||||||
for _, name := range f.Names() {
|
|
||||||
_ = f.set.Set(name, floatStr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -26,29 +26,48 @@ type testApplyInputSource struct {
|
|||||||
MapValue interface{}
|
MapValue interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type racyInputSource struct {
|
||||||
|
*MapInputSource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ris *racyInputSource) isSet(name string) bool {
|
||||||
|
if _, ok := ris.MapInputSource.valueMap[name]; ok {
|
||||||
|
ris.MapInputSource.valueMap[name] = bogus{0}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func TestGenericApplyInputSourceValue(t *testing.T) {
|
func TestGenericApplyInputSourceValue(t *testing.T) {
|
||||||
v := &Parser{"abc", "def"}
|
v := &Parser{"abc", "def"}
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Value: &Parser{}}),
|
Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Value: &Parser{}}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: v,
|
MapValue: v,
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, v, c.Generic("test"))
|
expect(t, v, c.Generic("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, v, c.Generic("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenericApplyInputSourceMethodContextSet(t *testing.T) {
|
func TestGenericApplyInputSourceMethodContextSet(t *testing.T) {
|
||||||
p := &Parser{"abc", "def"}
|
p := &Parser{"abc", "def"}
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Value: &Parser{}}),
|
Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Value: &Parser{}}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: &Parser{"efg", "hig"},
|
MapValue: &Parser{"efg", "hig"},
|
||||||
ContextValueString: p.String(),
|
ContextValueString: p.String(),
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, p, c.Generic("test"))
|
expect(t, p, c.Generic("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, p, c.Generic("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewGenericFlag(&cli.GenericFlag{
|
Flag: NewGenericFlag(&cli.GenericFlag{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Value: &Parser{},
|
Value: &Parser{},
|
||||||
@ -58,17 +77,25 @@ func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
|||||||
MapValue: &Parser{"efg", "hij"},
|
MapValue: &Parser{"efg", "hij"},
|
||||||
EnvVarName: "TEST",
|
EnvVarName: "TEST",
|
||||||
EnvVarValue: "abc,def",
|
EnvVarValue: "abc,def",
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, &Parser{"abc", "def"}, c.Generic("test"))
|
expect(t, &Parser{"abc", "def"}, c.Generic("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, &Parser{"abc", "def"}, c.Generic("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringSliceApplyInputSourceValue(t *testing.T) {
|
func TestStringSliceApplyInputSourceValue(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}),
|
Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: []interface{}{"hello", "world"},
|
MapValue: []interface{}{"hello", "world"},
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, c.StringSlice("test"), []string{"hello", "world"})
|
expect(t, c.StringSlice("test"), []string{"hello", "world"})
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, c.StringSlice("test"), []string{"hello", "world"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) {
|
func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) {
|
||||||
@ -82,112 +109,154 @@ func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", EnvVars: []string{"TEST"}}),
|
Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", EnvVars: []string{"TEST"}}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: []interface{}{"hello", "world"},
|
MapValue: []interface{}{"hello", "world"},
|
||||||
EnvVarName: "TEST",
|
EnvVarName: "TEST",
|
||||||
EnvVarValue: "oh,no",
|
EnvVarValue: "oh,no",
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, c.StringSlice("test"), []string{"oh", "no"})
|
expect(t, c.StringSlice("test"), []string{"oh", "no"})
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, c.StringSlice("test"), []string{"oh", "no"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntSliceApplyInputSourceValue(t *testing.T) {
|
func TestIntSliceApplyInputSourceValue(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}),
|
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: []interface{}{1, 2},
|
MapValue: []interface{}{1, 2},
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, c.IntSlice("test"), []int{1, 2})
|
expect(t, c.IntSlice("test"), []int{1, 2})
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, c.IntSlice("test"), []int{1, 2})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) {
|
func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}),
|
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: []interface{}{1, 2},
|
MapValue: []interface{}{1, 2},
|
||||||
ContextValueString: "3",
|
ContextValueString: "3",
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, c.IntSlice("test"), []int{3})
|
expect(t, c.IntSlice("test"), []int{3})
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, c.IntSlice("test"), []int{3})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", EnvVars: []string{"TEST"}}),
|
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", EnvVars: []string{"TEST"}}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: []interface{}{1, 2},
|
MapValue: []interface{}{1, 2},
|
||||||
EnvVarName: "TEST",
|
EnvVarName: "TEST",
|
||||||
EnvVarValue: "3,4",
|
EnvVarValue: "3,4",
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, c.IntSlice("test"), []int{3, 4})
|
expect(t, c.IntSlice("test"), []int{3, 4})
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, c.IntSlice("test"), []int{3, 4})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBoolApplyInputSourceMethodSet(t *testing.T) {
|
func TestBoolApplyInputSourceMethodSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewBoolFlag(&cli.BoolFlag{Name: "test"}),
|
Flag: NewBoolFlag(&cli.BoolFlag{Name: "test"}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: true,
|
MapValue: true,
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, true, c.Bool("test"))
|
expect(t, true, c.Bool("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, true, c.Bool("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBoolApplyInputSourceMethodContextSet(t *testing.T) {
|
func TestBoolApplyInputSourceMethodContextSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewBoolFlag(&cli.BoolFlag{Name: "test"}),
|
Flag: NewBoolFlag(&cli.BoolFlag{Name: "test"}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: false,
|
MapValue: false,
|
||||||
ContextValueString: "true",
|
ContextValueString: "true",
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, true, c.Bool("test"))
|
expect(t, true, c.Bool("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, true, c.Bool("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewBoolFlag(&cli.BoolFlag{Name: "test", EnvVars: []string{"TEST"}}),
|
Flag: NewBoolFlag(&cli.BoolFlag{Name: "test", EnvVars: []string{"TEST"}}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: false,
|
MapValue: false,
|
||||||
EnvVarName: "TEST",
|
EnvVarName: "TEST",
|
||||||
EnvVarValue: "true",
|
EnvVarValue: "true",
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, true, c.Bool("test"))
|
expect(t, true, c.Bool("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, true, c.Bool("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringApplyInputSourceMethodSet(t *testing.T) {
|
func TestStringApplyInputSourceMethodSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewStringFlag(&cli.StringFlag{Name: "test"}),
|
Flag: NewStringFlag(&cli.StringFlag{Name: "test"}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: "hello",
|
MapValue: "hello",
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, "hello", c.String("test"))
|
expect(t, "hello", c.String("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, "hello", c.String("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringApplyInputSourceMethodContextSet(t *testing.T) {
|
func TestStringApplyInputSourceMethodContextSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewStringFlag(&cli.StringFlag{Name: "test"}),
|
Flag: NewStringFlag(&cli.StringFlag{Name: "test"}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: "hello",
|
MapValue: "hello",
|
||||||
ContextValueString: "goodbye",
|
ContextValueString: "goodbye",
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, "goodbye", c.String("test"))
|
expect(t, "goodbye", c.String("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, "goodbye", c.String("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewStringFlag(&cli.StringFlag{Name: "test", EnvVars: []string{"TEST"}}),
|
Flag: NewStringFlag(&cli.StringFlag{Name: "test", EnvVars: []string{"TEST"}}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: "hello",
|
MapValue: "hello",
|
||||||
EnvVarName: "TEST",
|
EnvVarName: "TEST",
|
||||||
EnvVarValue: "goodbye",
|
EnvVarValue: "goodbye",
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, "goodbye", c.String("test"))
|
expect(t, "goodbye", c.String("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, "goodbye", c.String("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathApplyInputSourceMethodSet(t *testing.T) {
|
func TestPathApplyInputSourceMethodSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewPathFlag(&cli.PathFlag{Name: "test"}),
|
Flag: NewPathFlag(&cli.PathFlag{Name: "test"}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: "hello",
|
MapValue: "hello",
|
||||||
SourcePath: "/path/to/source/file",
|
SourcePath: "/path/to/source/file",
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
|
|
||||||
expected := "/path/to/source/hello"
|
expected := "/path/to/source/hello"
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
@ -200,119 +269,214 @@ func TestPathApplyInputSourceMethodSet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
expect(t, expected, c.String("test"))
|
expect(t, expected, c.String("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, expected, c.String("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathApplyInputSourceMethodContextSet(t *testing.T) {
|
func TestPathApplyInputSourceMethodContextSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewPathFlag(&cli.PathFlag{Name: "test"}),
|
Flag: NewPathFlag(&cli.PathFlag{Name: "test"}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: "hello",
|
MapValue: "hello",
|
||||||
ContextValueString: "goodbye",
|
ContextValueString: "goodbye",
|
||||||
SourcePath: "/path/to/source/file",
|
SourcePath: "/path/to/source/file",
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, "goodbye", c.String("test"))
|
expect(t, "goodbye", c.String("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, "goodbye", c.String("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
func TestPathApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewPathFlag(&cli.PathFlag{Name: "test", EnvVars: []string{"TEST"}}),
|
Flag: NewPathFlag(&cli.PathFlag{Name: "test", EnvVars: []string{"TEST"}}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: "hello",
|
MapValue: "hello",
|
||||||
EnvVarName: "TEST",
|
EnvVarName: "TEST",
|
||||||
EnvVarValue: "goodbye",
|
EnvVarValue: "goodbye",
|
||||||
SourcePath: "/path/to/source/file",
|
SourcePath: "/path/to/source/file",
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, "goodbye", c.String("test"))
|
expect(t, "goodbye", c.String("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, "goodbye", c.String("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntApplyInputSourceMethodSet(t *testing.T) {
|
func TestIntApplyInputSourceMethodSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewIntFlag(&cli.IntFlag{Name: "test"}),
|
Flag: NewIntFlag(&cli.IntFlag{Name: "test"}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: 15,
|
MapValue: 15,
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, 15, c.Int("test"))
|
expect(t, 15, c.Int("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, 15, c.Int("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntApplyInputSourceMethodSetNegativeValue(t *testing.T) {
|
||||||
|
tis := testApplyInputSource{
|
||||||
|
Flag: NewIntFlag(&cli.IntFlag{Name: "test"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: -1,
|
||||||
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
|
expect(t, -1, c.Int("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, -1, c.Int("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntApplyInputSourceMethodContextSet(t *testing.T) {
|
func TestIntApplyInputSourceMethodContextSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewIntFlag(&cli.IntFlag{Name: "test"}),
|
Flag: NewIntFlag(&cli.IntFlag{Name: "test"}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: 15,
|
MapValue: 15,
|
||||||
ContextValueString: "7",
|
ContextValueString: "7",
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, 7, c.Int("test"))
|
expect(t, 7, c.Int("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, 7, c.Int("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewIntFlag(&cli.IntFlag{Name: "test", EnvVars: []string{"TEST"}}),
|
Flag: NewIntFlag(&cli.IntFlag{Name: "test", EnvVars: []string{"TEST"}}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: 15,
|
MapValue: 15,
|
||||||
EnvVarName: "TEST",
|
EnvVarName: "TEST",
|
||||||
EnvVarValue: "12",
|
EnvVarValue: "12",
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, 12, c.Int("test"))
|
expect(t, 12, c.Int("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, 12, c.Int("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDurationApplyInputSourceMethodSet(t *testing.T) {
|
func TestDurationApplyInputSourceMethodSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}),
|
Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: 30 * time.Second,
|
MapValue: 30 * time.Second,
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, 30*time.Second, c.Duration("test"))
|
expect(t, 30*time.Second, c.Duration("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, 30*time.Second, c.Duration("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDurationApplyInputSourceMethodSetNegativeValue(t *testing.T) {
|
||||||
|
tis := testApplyInputSource{
|
||||||
|
Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: -30 * time.Second,
|
||||||
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
|
expect(t, -30*time.Second, c.Duration("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, -30*time.Second, c.Duration("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDurationApplyInputSourceMethodContextSet(t *testing.T) {
|
func TestDurationApplyInputSourceMethodContextSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}),
|
Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: 30 * time.Second,
|
MapValue: 30 * time.Second,
|
||||||
ContextValueString: (15 * time.Second).String(),
|
ContextValueString: (15 * time.Second).String(),
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, 15*time.Second, c.Duration("test"))
|
expect(t, 15*time.Second, c.Duration("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, 15*time.Second, c.Duration("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewDurationFlag(&cli.DurationFlag{Name: "test", EnvVars: []string{"TEST"}}),
|
Flag: NewDurationFlag(&cli.DurationFlag{Name: "test", EnvVars: []string{"TEST"}}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: 30 * time.Second,
|
MapValue: 30 * time.Second,
|
||||||
EnvVarName: "TEST",
|
EnvVarName: "TEST",
|
||||||
EnvVarValue: (15 * time.Second).String(),
|
EnvVarValue: (15 * time.Second).String(),
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, 15*time.Second, c.Duration("test"))
|
expect(t, 15*time.Second, c.Duration("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, 15*time.Second, c.Duration("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFloat64ApplyInputSourceMethodSet(t *testing.T) {
|
func TestFloat64ApplyInputSourceMethodSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}),
|
Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: 1.3,
|
MapValue: 1.3,
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, 1.3, c.Float64("test"))
|
expect(t, 1.3, c.Float64("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, 1.3, c.Float64("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloat64ApplyInputSourceMethodSetNegativeValue(t *testing.T) {
|
||||||
|
tis := testApplyInputSource{
|
||||||
|
Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}),
|
||||||
|
FlagName: "test",
|
||||||
|
MapValue: -1.3,
|
||||||
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
|
expect(t, -1.3, c.Float64("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, -1.3, c.Float64("test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloat64ApplyInputSourceMethodSetNegativeValueNotSet(t *testing.T) {
|
||||||
|
c := runTest(t, testApplyInputSource{
|
||||||
|
Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test1"}),
|
||||||
|
FlagName: "test1",
|
||||||
|
// dont set map value
|
||||||
|
})
|
||||||
|
expect(t, 0.0, c.Float64("test1"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) {
|
func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}),
|
Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: 1.3,
|
MapValue: 1.3,
|
||||||
ContextValueString: fmt.Sprintf("%v", 1.4),
|
ContextValueString: fmt.Sprintf("%v", 1.4),
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, 1.4, c.Float64("test"))
|
expect(t, 1.4, c.Float64("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, 1.4, c.Float64("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||||
c := runTest(t, testApplyInputSource{
|
tis := testApplyInputSource{
|
||||||
Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test", EnvVars: []string{"TEST"}}),
|
Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test", EnvVars: []string{"TEST"}}),
|
||||||
FlagName: "test",
|
FlagName: "test",
|
||||||
MapValue: 1.3,
|
MapValue: 1.3,
|
||||||
EnvVarName: "TEST",
|
EnvVarName: "TEST",
|
||||||
EnvVarValue: fmt.Sprintf("%v", 1.4),
|
EnvVarValue: fmt.Sprintf("%v", 1.4),
|
||||||
})
|
}
|
||||||
|
c := runTest(t, tis)
|
||||||
expect(t, 1.4, c.Float64("test"))
|
expect(t, 1.4, c.Float64("test"))
|
||||||
|
|
||||||
|
c = runRacyTest(t, tis)
|
||||||
|
refute(t, 1.4, c.Float64("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func runTest(t *testing.T, test testApplyInputSource) *cli.Context {
|
func runTest(t *testing.T, test testApplyInputSource) *cli.Context {
|
||||||
@ -340,6 +504,19 @@ func runTest(t *testing.T, test testApplyInputSource) *cli.Context {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runRacyTest(t *testing.T, test testApplyInputSource) *cli.Context {
|
||||||
|
set := flag.NewFlagSet(test.FlagSetName, flag.ContinueOnError)
|
||||||
|
c := cli.NewContext(nil, set, nil)
|
||||||
|
_ = test.Flag.ApplyInputSourceValue(c, &racyInputSource{
|
||||||
|
MapInputSource: &MapInputSource{
|
||||||
|
file: test.SourcePath,
|
||||||
|
valueMap: map[interface{}]interface{}{test.FlagName: test.MapValue},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
type Parser [2]string
|
type Parser [2]string
|
||||||
|
|
||||||
func (p *Parser) Set(value string) error {
|
func (p *Parser) Set(value string) error {
|
||||||
@ -357,3 +534,5 @@ func (p *Parser) Set(value string) error {
|
|||||||
func (p *Parser) String() string {
|
func (p *Parser) String() string {
|
||||||
return fmt.Sprintf("%s,%s", p[0], p[1])
|
return fmt.Sprintf("%s,%s", p[0], p[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type bogus [1]uint
|
||||||
|
@ -22,7 +22,10 @@ func expect(t *testing.T, a interface{}, b interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func refute(t *testing.T, a interface{}, b interface{}) {
|
func refute(t *testing.T, a interface{}, b interface{}) {
|
||||||
if a == b {
|
_, fn, line, _ := runtime.Caller(1)
|
||||||
t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
|
fn = strings.Replace(fn, wd+"/", "", -1)
|
||||||
|
|
||||||
|
if reflect.DeepEqual(a, b) {
|
||||||
|
t.Errorf("(%s:%d) Did not expect %v (type %v) - Got %v (type %v)", fn, line, b, reflect.TypeOf(b), a, reflect.TypeOf(a))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,4 +22,6 @@ type InputSourceContext interface {
|
|||||||
IntSlice(name string) ([]int, error)
|
IntSlice(name string) ([]int, error)
|
||||||
Generic(name string) (cli.Generic, error)
|
Generic(name string) (cli.Generic, error)
|
||||||
Bool(name string) (bool, error)
|
Bool(name string) (bool, error)
|
||||||
|
|
||||||
|
isSet(name string) bool
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,9 @@ import (
|
|||||||
// variables from a file containing JSON data with the file name defined
|
// variables from a file containing JSON data with the file name defined
|
||||||
// by the given flag.
|
// by the given flag.
|
||||||
func NewJSONSourceFromFlagFunc(flag string) func(c *cli.Context) (InputSourceContext, error) {
|
func NewJSONSourceFromFlagFunc(flag string) func(c *cli.Context) (InputSourceContext, error) {
|
||||||
return func(context *cli.Context) (InputSourceContext, error) {
|
return func(cCtx *cli.Context) (InputSourceContext, error) {
|
||||||
if context.IsSet(flag) {
|
if cCtx.IsSet(flag) {
|
||||||
return NewJSONSourceFromFile(context.String(flag))
|
return NewJSONSourceFromFile(cCtx.String(flag))
|
||||||
}
|
}
|
||||||
|
|
||||||
return defaultInputSource()
|
return defaultInputSource()
|
||||||
@ -184,6 +184,11 @@ func (x *jsonSource) Bool(name string) (bool, error) {
|
|||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *jsonSource) isSet(name string) bool {
|
||||||
|
_, err := x.getValue(name)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
func (x *jsonSource) getValue(key string) (interface{}, error) {
|
func (x *jsonSource) getValue(key string) (interface{}, error) {
|
||||||
return jsonGetValue(key, x.deserialized)
|
return jsonGetValue(key, x.deserialized)
|
||||||
}
|
}
|
||||||
|
@ -244,6 +244,15 @@ func (fsm *MapInputSource) Bool(name string) (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fsm *MapInputSource) isSet(name string) bool {
|
||||||
|
if _, exists := fsm.valueMap[name]; exists {
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
|
||||||
|
_, exists := nestedVal(name, fsm.valueMap)
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
|
||||||
func incorrectTypeForFlagError(name, expectedTypeName string, value interface{}) error {
|
func incorrectTypeForFlagError(name, expectedTypeName string, value interface{}) error {
|
||||||
valueType := reflect.TypeOf(value)
|
valueType := reflect.TypeOf(value)
|
||||||
valueTypeName := ""
|
valueTypeName := ""
|
||||||
|
@ -85,10 +85,10 @@ func NewTomlSourceFromFile(file string) (InputSourceContext, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewTomlSourceFromFlagFunc creates a new TOML InputSourceContext from a provided flag name and source context.
|
// NewTomlSourceFromFlagFunc creates a new TOML InputSourceContext from a provided flag name and source context.
|
||||||
func NewTomlSourceFromFlagFunc(flagFileName string) func(context *cli.Context) (InputSourceContext, error) {
|
func NewTomlSourceFromFlagFunc(flagFileName string) func(cCtx *cli.Context) (InputSourceContext, error) {
|
||||||
return func(context *cli.Context) (InputSourceContext, error) {
|
return func(cCtx *cli.Context) (InputSourceContext, error) {
|
||||||
if context.IsSet(flagFileName) {
|
if cCtx.IsSet(flagFileName) {
|
||||||
filePath := context.String(flagFileName)
|
filePath := cCtx.String(flagFileName)
|
||||||
return NewTomlSourceFromFile(filePath)
|
return NewTomlSourceFromFile(filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,10 +31,10 @@ func NewYamlSourceFromFile(file string) (InputSourceContext, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewYamlSourceFromFlagFunc creates a new Yaml InputSourceContext from a provided flag name and source context.
|
// NewYamlSourceFromFlagFunc creates a new Yaml InputSourceContext from a provided flag name and source context.
|
||||||
func NewYamlSourceFromFlagFunc(flagFileName string) func(context *cli.Context) (InputSourceContext, error) {
|
func NewYamlSourceFromFlagFunc(flagFileName string) func(cCtx *cli.Context) (InputSourceContext, error) {
|
||||||
return func(context *cli.Context) (InputSourceContext, error) {
|
return func(cCtx *cli.Context) (InputSourceContext, error) {
|
||||||
if context.IsSet(flagFileName) {
|
if cCtx.IsSet(flagFileName) {
|
||||||
filePath := context.String(flagFileName)
|
filePath := cCtx.String(flagFileName)
|
||||||
return NewYamlSourceFromFile(filePath)
|
return NewYamlSourceFromFile(filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
87
altsrc/yaml_file_loader_test.go
Normal file
87
altsrc/yaml_file_loader_test.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package altsrc_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"github.com/urfave/cli/v2/altsrc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleApp_Run_yamlFileLoaderDuration() {
|
||||||
|
execServe := func(c *cli.Context) error {
|
||||||
|
keepaliveInterval := c.Duration("keepalive-interval")
|
||||||
|
fmt.Printf("keepalive %s\n", keepaliveInterval)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fileExists := func(filename string) bool {
|
||||||
|
stat, _ := os.Stat(filename)
|
||||||
|
return stat != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// initConfigFileInputSource is like altsrc.InitInputSourceWithContext and altsrc.NewYamlSourceFromFlagFunc, but checks
|
||||||
|
// if the config flag is exists and only loads it if it does. If the flag is set and the file exists, it fails.
|
||||||
|
initConfigFileInputSource := func(configFlag string, flags []cli.Flag) cli.BeforeFunc {
|
||||||
|
return func(context *cli.Context) error {
|
||||||
|
configFile := context.String(configFlag)
|
||||||
|
if context.IsSet(configFlag) && !fileExists(configFile) {
|
||||||
|
return fmt.Errorf("config file %s does not exist", configFile)
|
||||||
|
} else if !context.IsSet(configFlag) && !fileExists(configFile) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
inputSource, err := altsrc.NewYamlSourceFromFile(configFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return altsrc.ApplyInputSourceValues(context, inputSource, flags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flagsServe := []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "config",
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
EnvVars: []string{"CONFIG_FILE"},
|
||||||
|
Value: "../testdata/empty.yml",
|
||||||
|
DefaultText: "../testdata/empty.yml",
|
||||||
|
Usage: "config file",
|
||||||
|
},
|
||||||
|
altsrc.NewDurationFlag(
|
||||||
|
&cli.DurationFlag{
|
||||||
|
Name: "keepalive-interval",
|
||||||
|
Aliases: []string{"k"},
|
||||||
|
EnvVars: []string{"KEEPALIVE_INTERVAL"},
|
||||||
|
Value: 45 * time.Second,
|
||||||
|
Usage: "interval of keepalive messages",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdServe := &cli.Command{
|
||||||
|
Name: "serve",
|
||||||
|
Usage: "Run the server",
|
||||||
|
UsageText: "serve [OPTIONS..]",
|
||||||
|
Action: execServe,
|
||||||
|
Flags: flagsServe,
|
||||||
|
Before: initConfigFileInputSource("config", flagsServe),
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &cli.App{
|
||||||
|
Name: "cmd",
|
||||||
|
HideVersion: true,
|
||||||
|
UseShortOptionHandling: true,
|
||||||
|
Commands: []*cli.Command{
|
||||||
|
cmdServe,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Run([]string{"cmd", "serve", "--config", "../testdata/empty.yml"}); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// keepalive 45s
|
||||||
|
}
|
92
app.go
92
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. "+
|
||||||
@ -245,48 +245,48 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
|
|||||||
|
|
||||||
err = parseIter(set, a, arguments[1:], shellComplete)
|
err = parseIter(set, a, arguments[1:], shellComplete)
|
||||||
nerr := normalizeFlags(a.Flags, set)
|
nerr := normalizeFlags(a.Flags, set)
|
||||||
context := NewContext(a, set, &Context{Context: ctx})
|
cCtx := NewContext(a, set, &Context{Context: ctx})
|
||||||
if nerr != nil {
|
if nerr != nil {
|
||||||
_, _ = fmt.Fprintln(a.Writer, nerr)
|
_, _ = fmt.Fprintln(a.Writer, nerr)
|
||||||
_ = ShowAppHelp(context)
|
_ = ShowAppHelp(cCtx)
|
||||||
return nerr
|
return nerr
|
||||||
}
|
}
|
||||||
context.shellComplete = shellComplete
|
cCtx.shellComplete = shellComplete
|
||||||
|
|
||||||
if checkCompletions(context) {
|
if checkCompletions(cCtx) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if a.OnUsageError != nil {
|
if a.OnUsageError != nil {
|
||||||
err := a.OnUsageError(context, err, false)
|
err := a.OnUsageError(cCtx, err, false)
|
||||||
a.handleExitCoder(context, err)
|
a.handleExitCoder(cCtx, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
||||||
_ = ShowAppHelp(context)
|
_ = ShowAppHelp(cCtx)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !a.HideHelp && checkHelp(context) {
|
if !a.HideHelp && checkHelp(cCtx) {
|
||||||
_ = ShowAppHelp(context)
|
_ = ShowAppHelp(cCtx)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !a.HideVersion && checkVersion(context) {
|
if !a.HideVersion && checkVersion(cCtx) {
|
||||||
ShowVersion(context)
|
ShowVersion(cCtx)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cerr := context.checkRequiredFlags(a.Flags)
|
cerr := cCtx.checkRequiredFlags(a.Flags)
|
||||||
if cerr != nil {
|
if cerr != nil {
|
||||||
_ = ShowAppHelp(context)
|
_ = ShowAppHelp(cCtx)
|
||||||
return cerr
|
return cerr
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.After != nil {
|
if a.After != nil {
|
||||||
defer func() {
|
defer func() {
|
||||||
if afterErr := a.After(context); afterErr != nil {
|
if afterErr := a.After(cCtx); afterErr != nil {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = newMultiError(err, afterErr)
|
err = newMultiError(err, afterErr)
|
||||||
} else {
|
} else {
|
||||||
@ -297,20 +297,20 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if a.Before != nil {
|
if a.Before != nil {
|
||||||
beforeErr := a.Before(context)
|
beforeErr := a.Before(cCtx)
|
||||||
if beforeErr != nil {
|
if beforeErr != nil {
|
||||||
a.handleExitCoder(context, beforeErr)
|
a.handleExitCoder(cCtx, beforeErr)
|
||||||
err = beforeErr
|
err = beforeErr
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
args := context.Args()
|
args := cCtx.Args()
|
||||||
if args.Present() {
|
if args.Present() {
|
||||||
name := args.First()
|
name := args.First()
|
||||||
c := a.Command(name)
|
c := a.Command(name)
|
||||||
if c != nil {
|
if c != nil {
|
||||||
return c.Run(context)
|
return c.Run(cCtx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,9 +319,9 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run default Action
|
// Run default Action
|
||||||
err = a.Action(context)
|
err = a.Action(cCtx)
|
||||||
|
|
||||||
a.handleExitCoder(context, err)
|
a.handleExitCoder(cCtx, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,55 +359,55 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
|||||||
|
|
||||||
err = parseIter(set, a, ctx.Args().Tail(), ctx.shellComplete)
|
err = parseIter(set, a, ctx.Args().Tail(), ctx.shellComplete)
|
||||||
nerr := normalizeFlags(a.Flags, set)
|
nerr := normalizeFlags(a.Flags, set)
|
||||||
context := NewContext(a, set, ctx)
|
cCtx := NewContext(a, set, ctx)
|
||||||
|
|
||||||
if nerr != nil {
|
if nerr != nil {
|
||||||
_, _ = fmt.Fprintln(a.Writer, nerr)
|
_, _ = fmt.Fprintln(a.Writer, nerr)
|
||||||
_, _ = fmt.Fprintln(a.Writer)
|
_, _ = fmt.Fprintln(a.Writer)
|
||||||
if len(a.Commands) > 0 {
|
if len(a.Commands) > 0 {
|
||||||
_ = ShowSubcommandHelp(context)
|
_ = ShowSubcommandHelp(cCtx)
|
||||||
} else {
|
} else {
|
||||||
_ = ShowCommandHelp(ctx, context.Args().First())
|
_ = ShowCommandHelp(ctx, cCtx.Args().First())
|
||||||
}
|
}
|
||||||
return nerr
|
return nerr
|
||||||
}
|
}
|
||||||
|
|
||||||
if checkCompletions(context) {
|
if checkCompletions(cCtx) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if a.OnUsageError != nil {
|
if a.OnUsageError != nil {
|
||||||
err = a.OnUsageError(context, err, true)
|
err = a.OnUsageError(cCtx, err, true)
|
||||||
a.handleExitCoder(context, err)
|
a.handleExitCoder(cCtx, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
||||||
_ = ShowSubcommandHelp(context)
|
_ = ShowSubcommandHelp(cCtx)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(a.Commands) > 0 {
|
if len(a.Commands) > 0 {
|
||||||
if checkSubcommandHelp(context) {
|
if checkSubcommandHelp(cCtx) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if checkCommandHelp(ctx, context.Args().First()) {
|
if checkCommandHelp(ctx, cCtx.Args().First()) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cerr := context.checkRequiredFlags(a.Flags)
|
cerr := cCtx.checkRequiredFlags(a.Flags)
|
||||||
if cerr != nil {
|
if cerr != nil {
|
||||||
_ = ShowSubcommandHelp(context)
|
_ = ShowSubcommandHelp(cCtx)
|
||||||
return cerr
|
return cerr
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.After != nil {
|
if a.After != nil {
|
||||||
defer func() {
|
defer func() {
|
||||||
afterErr := a.After(context)
|
afterErr := a.After(cCtx)
|
||||||
if afterErr != nil {
|
if afterErr != nil {
|
||||||
a.handleExitCoder(context, err)
|
a.handleExitCoder(cCtx, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = newMultiError(err, afterErr)
|
err = newMultiError(err, afterErr)
|
||||||
} else {
|
} else {
|
||||||
@ -418,27 +418,27 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if a.Before != nil {
|
if a.Before != nil {
|
||||||
beforeErr := a.Before(context)
|
beforeErr := a.Before(cCtx)
|
||||||
if beforeErr != nil {
|
if beforeErr != nil {
|
||||||
a.handleExitCoder(context, beforeErr)
|
a.handleExitCoder(cCtx, beforeErr)
|
||||||
err = beforeErr
|
err = beforeErr
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
args := context.Args()
|
args := cCtx.Args()
|
||||||
if args.Present() {
|
if args.Present() {
|
||||||
name := args.First()
|
name := args.First()
|
||||||
c := a.Command(name)
|
c := a.Command(name)
|
||||||
if c != nil {
|
if c != nil {
|
||||||
return c.Run(context)
|
return c.Run(cCtx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run default Action
|
// Run default Action
|
||||||
err = a.Action(context)
|
err = a.Action(cCtx)
|
||||||
|
|
||||||
a.handleExitCoder(context, err)
|
a.handleExitCoder(cCtx, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -498,9 +498,9 @@ func (a *App) appendCommand(c *Command) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) handleExitCoder(context *Context, err error) {
|
func (a *App) handleExitCoder(cCtx *Context, err error) {
|
||||||
if a.ExitErrHandler != nil {
|
if a.ExitErrHandler != nil {
|
||||||
a.ExitErrHandler(context, err)
|
a.ExitErrHandler(cCtx, err)
|
||||||
} else {
|
} else {
|
||||||
HandleExitCoder(err)
|
HandleExitCoder(err)
|
||||||
}
|
}
|
||||||
@ -525,14 +525,14 @@ func (a *Author) String() string {
|
|||||||
// HandleAction attempts to figure out which Action signature was used. If
|
// HandleAction attempts to figure out which Action signature was used. If
|
||||||
// it's an ActionFunc or a func with the legacy signature for Action, the func
|
// it's an ActionFunc or a func with the legacy signature for Action, the func
|
||||||
// is run!
|
// is run!
|
||||||
func HandleAction(action interface{}, context *Context) (err error) {
|
func HandleAction(action interface{}, cCtx *Context) (err error) {
|
||||||
switch a := action.(type) {
|
switch a := action.(type) {
|
||||||
case ActionFunc:
|
case ActionFunc:
|
||||||
return a(context)
|
return a(cCtx)
|
||||||
case func(*Context) error:
|
case func(*Context) error:
|
||||||
return a(context)
|
return a(cCtx)
|
||||||
case func(*Context): // deprecated function signature
|
case func(*Context): // deprecated function signature
|
||||||
a(context)
|
a(cCtx)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
42
app_test.go
42
app_test.go
@ -390,6 +390,40 @@ func ExampleApp_Run_zshComplete() {
|
|||||||
// h:Shows a list of commands or help for one command
|
// h:Shows a list of commands or help for one command
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExampleApp_Run_sliceValues() {
|
||||||
|
// set args for examples sake
|
||||||
|
os.Args = []string{"multi_values",
|
||||||
|
"--stringSclice", "parsed1,parsed2", "--stringSclice", "parsed3,parsed4",
|
||||||
|
"--float64Sclice", "13.3,14.4", "--float64Sclice", "15.5,16.6",
|
||||||
|
"--int64Sclice", "13,14", "--int64Sclice", "15,16",
|
||||||
|
"--intSclice", "13,14", "--intSclice", "15,16",
|
||||||
|
}
|
||||||
|
app := NewApp()
|
||||||
|
app.Name = "multi_values"
|
||||||
|
app.Flags = []Flag{
|
||||||
|
&StringSliceFlag{Name: "stringSclice"},
|
||||||
|
&Float64SliceFlag{Name: "float64Sclice"},
|
||||||
|
&Int64SliceFlag{Name: "int64Sclice"},
|
||||||
|
&IntSliceFlag{Name: "intSclice"},
|
||||||
|
}
|
||||||
|
app.Action = func(ctx *Context) error {
|
||||||
|
for i, v := range ctx.FlagNames() {
|
||||||
|
fmt.Printf("%d-%s %#v\n", i, v, ctx.Value(v))
|
||||||
|
}
|
||||||
|
err := ctx.Err()
|
||||||
|
fmt.Println("error:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = app.Run(os.Args)
|
||||||
|
// Output:
|
||||||
|
// 0-float64Sclice cli.Float64Slice{slice:[]float64{13.3, 14.4, 15.5, 16.6}, hasBeenSet:true}
|
||||||
|
// 1-int64Sclice cli.Int64Slice{slice:[]int64{13, 14, 15, 16}, hasBeenSet:true}
|
||||||
|
// 2-intSclice cli.IntSlice{slice:[]int{13, 14, 15, 16}, hasBeenSet:true}
|
||||||
|
// 3-stringSclice cli.StringSlice{slice:[]string{"parsed1", "parsed2", "parsed3", "parsed4"}, hasBeenSet:true}
|
||||||
|
// error: <nil>
|
||||||
|
}
|
||||||
|
|
||||||
func TestApp_Run(t *testing.T) {
|
func TestApp_Run(t *testing.T) {
|
||||||
s := ""
|
s := ""
|
||||||
|
|
||||||
@ -445,14 +479,14 @@ func TestApp_Setup_defaultsWriter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
|
func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
|
||||||
var context *Context
|
var cCtx *Context
|
||||||
|
|
||||||
a := &App{
|
a := &App{
|
||||||
Commands: []*Command{
|
Commands: []*Command{
|
||||||
{
|
{
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
Action: func(c *Context) error {
|
Action: func(c *Context) error {
|
||||||
context = c
|
cCtx = c
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Flags: []Flag{
|
Flags: []Flag{
|
||||||
@ -468,8 +502,8 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
|
|||||||
}
|
}
|
||||||
_ = a.Run([]string{"", "foo", "--lang", "spanish", "abcd"})
|
_ = a.Run([]string{"", "foo", "--lang", "spanish", "abcd"})
|
||||||
|
|
||||||
expect(t, context.Args().Get(0), "abcd")
|
expect(t, cCtx.Args().Get(0), "abcd")
|
||||||
expect(t, context.String("lang"), "spanish")
|
expect(t, cCtx.String("lang"), "spanish")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApp_RunAsSubCommandIncorrectUsage(t *testing.T) {
|
func TestApp_RunAsSubCommandIncorrectUsage(t *testing.T) {
|
||||||
|
36
command.go
36
command.go
@ -105,39 +105,39 @@ func (c *Command) Run(ctx *Context) (err error) {
|
|||||||
|
|
||||||
set, err := c.parseFlags(ctx.Args(), ctx.shellComplete)
|
set, err := c.parseFlags(ctx.Args(), ctx.shellComplete)
|
||||||
|
|
||||||
context := NewContext(ctx.App, set, ctx)
|
cCtx := NewContext(ctx.App, set, ctx)
|
||||||
context.Command = c
|
cCtx.Command = c
|
||||||
if checkCommandCompletions(context, c.Name) {
|
if checkCommandCompletions(cCtx, c.Name) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if c.OnUsageError != nil {
|
if c.OnUsageError != nil {
|
||||||
err = c.OnUsageError(context, err, false)
|
err = c.OnUsageError(cCtx, err, false)
|
||||||
context.App.handleExitCoder(context, err)
|
cCtx.App.handleExitCoder(cCtx, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
|
_, _ = fmt.Fprintln(cCtx.App.Writer, "Incorrect Usage:", err.Error())
|
||||||
_, _ = fmt.Fprintln(context.App.Writer)
|
_, _ = fmt.Fprintln(cCtx.App.Writer)
|
||||||
_ = ShowCommandHelp(context, c.Name)
|
_ = ShowCommandHelp(cCtx, c.Name)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if checkCommandHelp(context, c.Name) {
|
if checkCommandHelp(cCtx, c.Name) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cerr := context.checkRequiredFlags(c.Flags)
|
cerr := cCtx.checkRequiredFlags(c.Flags)
|
||||||
if cerr != nil {
|
if cerr != nil {
|
||||||
_ = ShowCommandHelp(context, c.Name)
|
_ = ShowCommandHelp(cCtx, c.Name)
|
||||||
return cerr
|
return cerr
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.After != nil {
|
if c.After != nil {
|
||||||
defer func() {
|
defer func() {
|
||||||
afterErr := c.After(context)
|
afterErr := c.After(cCtx)
|
||||||
if afterErr != nil {
|
if afterErr != nil {
|
||||||
context.App.handleExitCoder(context, err)
|
cCtx.App.handleExitCoder(cCtx, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = newMultiError(err, afterErr)
|
err = newMultiError(err, afterErr)
|
||||||
} else {
|
} else {
|
||||||
@ -148,9 +148,9 @@ func (c *Command) Run(ctx *Context) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.Before != nil {
|
if c.Before != nil {
|
||||||
err = c.Before(context)
|
err = c.Before(cCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context.App.handleExitCoder(context, err)
|
cCtx.App.handleExitCoder(cCtx, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,11 +159,11 @@ func (c *Command) Run(ctx *Context) (err error) {
|
|||||||
c.Action = helpSubcommand.Action
|
c.Action = helpSubcommand.Action
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Command = c
|
cCtx.Command = c
|
||||||
err = c.Action(context)
|
err = c.Action(cCtx)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context.App.handleExitCoder(context, err)
|
cCtx.App.handleExitCoder(cCtx, err)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ func TestCommandFlagParsing(t *testing.T) {
|
|||||||
set := flag.NewFlagSet("test", 0)
|
set := flag.NewFlagSet("test", 0)
|
||||||
_ = set.Parse(c.testArgs)
|
_ = set.Parse(c.testArgs)
|
||||||
|
|
||||||
context := NewContext(app, set, nil)
|
cCtx := NewContext(app, set, nil)
|
||||||
|
|
||||||
command := Command{
|
command := Command{
|
||||||
Name: "test-cmd",
|
Name: "test-cmd",
|
||||||
@ -41,10 +41,10 @@ func TestCommandFlagParsing(t *testing.T) {
|
|||||||
SkipFlagParsing: c.skipFlagParsing,
|
SkipFlagParsing: c.skipFlagParsing,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := command.Run(context)
|
err := command.Run(cCtx)
|
||||||
|
|
||||||
expect(t, err, c.expectedErr)
|
expect(t, err, c.expectedErr)
|
||||||
expect(t, context.Args().Slice(), c.testArgs)
|
expect(t, cCtx.Args().Slice(), c.testArgs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
56
context.go
56
context.go
@ -40,18 +40,18 @@ func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NumFlags returns the number of flags set
|
// NumFlags returns the number of flags set
|
||||||
func (c *Context) NumFlags() int {
|
func (cCtx *Context) NumFlags() int {
|
||||||
return c.flagSet.NFlag()
|
return cCtx.flagSet.NFlag()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set sets a context flag to a value.
|
// Set sets a context flag to a value.
|
||||||
func (c *Context) Set(name, value string) error {
|
func (cCtx *Context) Set(name, value string) error {
|
||||||
return c.flagSet.Set(name, value)
|
return cCtx.flagSet.Set(name, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSet determines if the flag was actually set
|
// IsSet determines if the flag was actually set
|
||||||
func (c *Context) IsSet(name string) bool {
|
func (cCtx *Context) IsSet(name string) bool {
|
||||||
if fs := c.lookupFlagSet(name); fs != nil {
|
if fs := cCtx.lookupFlagSet(name); 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 {
|
||||||
@ -62,7 +62,7 @@ func (c *Context) IsSet(name string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
f := c.lookupFlag(name)
|
f := cCtx.lookupFlag(name)
|
||||||
if f == nil {
|
if f == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -74,28 +74,28 @@ func (c *Context) IsSet(name string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LocalFlagNames returns a slice of flag names used in this context.
|
// LocalFlagNames returns a slice of flag names used in this context.
|
||||||
func (c *Context) LocalFlagNames() []string {
|
func (cCtx *Context) LocalFlagNames() []string {
|
||||||
var names []string
|
var names []string
|
||||||
c.flagSet.Visit(makeFlagNameVisitor(&names))
|
cCtx.flagSet.Visit(makeFlagNameVisitor(&names))
|
||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlagNames returns a slice of flag names used by the this context and all of
|
// FlagNames returns a slice of flag names used by the this context and all of
|
||||||
// its parent contexts.
|
// its parent contexts.
|
||||||
func (c *Context) FlagNames() []string {
|
func (cCtx *Context) FlagNames() []string {
|
||||||
var names []string
|
var names []string
|
||||||
for _, ctx := range c.Lineage() {
|
for _, pCtx := range cCtx.Lineage() {
|
||||||
ctx.flagSet.Visit(makeFlagNameVisitor(&names))
|
pCtx.flagSet.Visit(makeFlagNameVisitor(&names))
|
||||||
}
|
}
|
||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lineage returns *this* context and all of its ancestor contexts in order from
|
// Lineage returns *this* context and all of its ancestor contexts in order from
|
||||||
// child to parent
|
// child to parent
|
||||||
func (c *Context) Lineage() []*Context {
|
func (cCtx *Context) Lineage() []*Context {
|
||||||
var lineage []*Context
|
var lineage []*Context
|
||||||
|
|
||||||
for cur := c; cur != nil; cur = cur.parentContext {
|
for cur := cCtx; cur != nil; cur = cur.parentContext {
|
||||||
lineage = append(lineage, cur)
|
lineage = append(lineage, cur)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,26 +103,26 @@ 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 (cCtx *Context) Value(name string) interface{} {
|
||||||
if fs := c.lookupFlagSet(name); fs != nil {
|
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||||
return fs.Lookup(name).Value.(flag.Getter).Get()
|
return fs.Lookup(name).Value.(flag.Getter).Get()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Args returns the command line arguments associated with the context.
|
// Args returns the command line arguments associated with the context.
|
||||||
func (c *Context) Args() Args {
|
func (cCtx *Context) Args() Args {
|
||||||
ret := args(c.flagSet.Args())
|
ret := args(cCtx.flagSet.Args())
|
||||||
return &ret
|
return &ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// NArg returns the number of the command line arguments.
|
// NArg returns the number of the command line arguments.
|
||||||
func (c *Context) NArg() int {
|
func (cCtx *Context) NArg() int {
|
||||||
return c.Args().Len()
|
return cCtx.Args().Len()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *Context) lookupFlag(name string) Flag {
|
func (cCtx *Context) lookupFlag(name string) Flag {
|
||||||
for _, c := range ctx.Lineage() {
|
for _, c := range cCtx.Lineage() {
|
||||||
if c.Command == nil {
|
if c.Command == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -136,8 +136,8 @@ func (ctx *Context) lookupFlag(name string) Flag {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.App != nil {
|
if cCtx.App != nil {
|
||||||
for _, f := range ctx.App.Flags {
|
for _, f := range cCtx.App.Flags {
|
||||||
for _, n := range f.Names() {
|
for _, n := range f.Names() {
|
||||||
if n == name {
|
if n == name {
|
||||||
return f
|
return f
|
||||||
@ -149,8 +149,8 @@ func (ctx *Context) lookupFlag(name string) Flag {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *Context) lookupFlagSet(name string) *flag.FlagSet {
|
func (cCtx *Context) lookupFlagSet(name string) *flag.FlagSet {
|
||||||
for _, c := range ctx.Lineage() {
|
for _, c := range cCtx.Lineage() {
|
||||||
if c.flagSet == nil {
|
if c.flagSet == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -162,7 +162,7 @@ func (ctx *Context) lookupFlagSet(name string) *flag.FlagSet {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (context *Context) checkRequiredFlags(flags []Flag) requiredFlagsErr {
|
func (cCtx *Context) checkRequiredFlags(flags []Flag) 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() {
|
||||||
@ -174,7 +174,7 @@ func (context *Context) checkRequiredFlags(flags []Flag) requiredFlagsErr {
|
|||||||
flagName = key
|
flagName = key
|
||||||
}
|
}
|
||||||
|
|
||||||
if context.IsSet(strings.TrimSpace(key)) {
|
if cCtx.IsSet(strings.TrimSpace(key)) {
|
||||||
flagPresent = true
|
flagPresent = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
docs.go
7
docs.go
@ -1,3 +1,6 @@
|
|||||||
|
//go:build !urfave_cli_no_docs
|
||||||
|
// +build !urfave_cli_no_docs
|
||||||
|
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -80,14 +83,14 @@ func prepareCommands(commands []*Command, level int) []string {
|
|||||||
usageText,
|
usageText,
|
||||||
)
|
)
|
||||||
|
|
||||||
flags := prepareArgsWithValues(command.Flags)
|
flags := prepareArgsWithValues(command.VisibleFlags())
|
||||||
if len(flags) > 0 {
|
if len(flags) > 0 {
|
||||||
prepared += fmt.Sprintf("\n%s", strings.Join(flags, "\n"))
|
prepared += fmt.Sprintf("\n%s", strings.Join(flags, "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
coms = append(coms, prepared)
|
coms = append(coms, prepared)
|
||||||
|
|
||||||
// recursevly iterate subcommands
|
// recursively iterate subcommands
|
||||||
if len(command.Subcommands) > 0 {
|
if len(command.Subcommands) > 0 {
|
||||||
coms = append(
|
coms = append(
|
||||||
coms,
|
coms,
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
> :warning: This document is no longer being actively maintained. Please see the
|
||||||
|
> [releases page](https://github.com/urfave/cli/releases) for all release notes
|
||||||
|
> and related hypermedia for releases `>= 1.22.5`, `>= 2.3.0`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
**ATTN**: This project uses [semantic versioning](http://semver.org/).
|
**ATTN**: This project uses [semantic versioning](http://semver.org/).
|
||||||
|
@ -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
|
||||||
|
61
docs/RELEASING.md
Normal file
61
docs/RELEASING.md
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# Releasing urfave/cli
|
||||||
|
|
||||||
|
Releasing small batches often is [backed by
|
||||||
|
research](https://itrevolution.com/accelerate-book/) as part of the
|
||||||
|
virtuous cycles that keep teams and products healthy.
|
||||||
|
|
||||||
|
To that end, the overall goal of the release process is to send
|
||||||
|
changes out into the world as close to the time the commits were
|
||||||
|
merged to the `main` branch as possible. In this way, the community
|
||||||
|
of humans depending on this library are able to make use of the
|
||||||
|
changes they need **quickly**, which means they shouldn't have to
|
||||||
|
maintain long-lived forks of the project, which means they can get
|
||||||
|
back to focusing on the work on which they want to focus. This also
|
||||||
|
means that the @urfave/cli team should be able to focus on
|
||||||
|
delivering a steadily improving product with significantly eased
|
||||||
|
ability to associate bugs and regressions with specific releases.
|
||||||
|
|
||||||
|
## Process
|
||||||
|
|
||||||
|
- Release versions follow [semantic versioning](https://semver.org/)
|
||||||
|
- Releases are associated with **signed, annotated git tags**[^1].
|
||||||
|
- Release notes are **automatically generated**[^2].
|
||||||
|
|
||||||
|
In the `main` or `v1` branch, the current version is always
|
||||||
|
available via:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git describe --always --dirty --tags
|
||||||
|
```
|
||||||
|
|
||||||
|
**NOTE**: if the version reported contains `-dirty`, this is
|
||||||
|
indicative of a "dirty" work tree, which is not a great state for
|
||||||
|
creating a new release tag. Seek help from @urfave/cli teammates.
|
||||||
|
|
||||||
|
For example, given a described version of `v2.4.7-3-g68da1cd` and a
|
||||||
|
diff of `v2.4.7...` that contains only bug fixes, the next version
|
||||||
|
should be `v2.4.8`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git tag -a -s -m 'Release 2.4.8' v2.4.8
|
||||||
|
git push origin v2.4.8
|
||||||
|
```
|
||||||
|
|
||||||
|
The tag push will trigger a GitHub Actions workflow. The remaining
|
||||||
|
steps require human intervention through the GitHub web view
|
||||||
|
although [automated solutions
|
||||||
|
exist](https://github.com/softprops/action-gh-release) that may be
|
||||||
|
adopted in the future.
|
||||||
|
|
||||||
|
- Open the [the new release page](https://github.com/urfave/cli/releases/new)
|
||||||
|
- At the top of the form, click on the `Choose a tag` select control and select `v2.4.8`
|
||||||
|
- In the `Write` tab below, click the `Auto-generate release notes` button
|
||||||
|
- At the bottom of the form, click the `Publish release` button
|
||||||
|
- :white_check_mark: you're done!
|
||||||
|
|
||||||
|
[^1]: This was not always true. There are many **lightweight git
|
||||||
|
tags** present in the repository history.
|
||||||
|
|
||||||
|
[^2]: This was not always true. The
|
||||||
|
[`docs/CHANGELOG.md`](./CHANGELOG.md) document used to be
|
||||||
|
manually maintained.
|
@ -674,8 +674,10 @@ Take for example this app that requires the `lang` flag:
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
126
docs_test.go
126
docs_test.go
@ -1,133 +1,13 @@
|
|||||||
|
//go:build !urfave_cli_no_docs
|
||||||
|
// +build !urfave_cli_no_docs
|
||||||
|
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testApp() *App {
|
|
||||||
app := newTestApp()
|
|
||||||
app.Name = "greet"
|
|
||||||
app.Flags = []Flag{
|
|
||||||
&StringFlag{
|
|
||||||
Name: "socket",
|
|
||||||
Aliases: []string{"s"},
|
|
||||||
Usage: "some 'usage' text",
|
|
||||||
Value: "value",
|
|
||||||
TakesFile: true,
|
|
||||||
},
|
|
||||||
&StringFlag{Name: "flag", Aliases: []string{"fl", "f"}},
|
|
||||||
&BoolFlag{
|
|
||||||
Name: "another-flag",
|
|
||||||
Aliases: []string{"b"},
|
|
||||||
Usage: "another usage text",
|
|
||||||
},
|
|
||||||
&BoolFlag{
|
|
||||||
Name: "hidden-flag",
|
|
||||||
Hidden: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
app.Commands = []*Command{{
|
|
||||||
Aliases: []string{"c"},
|
|
||||||
Flags: []Flag{
|
|
||||||
&StringFlag{
|
|
||||||
Name: "flag",
|
|
||||||
Aliases: []string{"fl", "f"},
|
|
||||||
TakesFile: true,
|
|
||||||
},
|
|
||||||
&BoolFlag{
|
|
||||||
Name: "another-flag",
|
|
||||||
Aliases: []string{"b"},
|
|
||||||
Usage: "another usage text",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Name: "config",
|
|
||||||
Usage: "another usage test",
|
|
||||||
Subcommands: []*Command{{
|
|
||||||
Aliases: []string{"s", "ss"},
|
|
||||||
Flags: []Flag{
|
|
||||||
&StringFlag{Name: "sub-flag", Aliases: []string{"sub-fl", "s"}},
|
|
||||||
&BoolFlag{
|
|
||||||
Name: "sub-command-flag",
|
|
||||||
Aliases: []string{"s"},
|
|
||||||
Usage: "some usage text",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Name: "sub-config",
|
|
||||||
Usage: "another usage test",
|
|
||||||
}},
|
|
||||||
}, {
|
|
||||||
Aliases: []string{"i", "in"},
|
|
||||||
Name: "info",
|
|
||||||
Usage: "retrieve generic information",
|
|
||||||
}, {
|
|
||||||
Name: "some-command",
|
|
||||||
}, {
|
|
||||||
Name: "hidden-command",
|
|
||||||
Hidden: true,
|
|
||||||
}, {
|
|
||||||
Aliases: []string{"u"},
|
|
||||||
Flags: []Flag{
|
|
||||||
&StringFlag{
|
|
||||||
Name: "flag",
|
|
||||||
Aliases: []string{"fl", "f"},
|
|
||||||
TakesFile: true,
|
|
||||||
},
|
|
||||||
&BoolFlag{
|
|
||||||
Name: "another-flag",
|
|
||||||
Aliases: []string{"b"},
|
|
||||||
Usage: "another usage text",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Name: "usage",
|
|
||||||
Usage: "standard usage text",
|
|
||||||
UsageText: `
|
|
||||||
Usage for the usage text
|
|
||||||
- formatted: Based on the specified ConfigMap and summon secrets.yml
|
|
||||||
- list: Inspect the environment for a specific process running on a Pod
|
|
||||||
- for_effect: Compare 'namespace' environment with 'local'
|
|
||||||
|
|
||||||
` + "```" + `
|
|
||||||
func() { ... }
|
|
||||||
` + "```" + `
|
|
||||||
|
|
||||||
Should be a part of the same code block
|
|
||||||
`,
|
|
||||||
Subcommands: []*Command{{
|
|
||||||
Aliases: []string{"su"},
|
|
||||||
Flags: []Flag{
|
|
||||||
&BoolFlag{
|
|
||||||
Name: "sub-command-flag",
|
|
||||||
Aliases: []string{"s"},
|
|
||||||
Usage: "some usage text",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Name: "sub-usage",
|
|
||||||
Usage: "standard usage text",
|
|
||||||
UsageText: "Single line of UsageText",
|
|
||||||
}},
|
|
||||||
}}
|
|
||||||
app.UsageText = "app [first_arg] [second_arg]"
|
|
||||||
app.Description = `Description of the application.`
|
|
||||||
app.Usage = "Some app"
|
|
||||||
app.Authors = []*Author{
|
|
||||||
{Name: "Harrison", Email: "harrison@lolwut.com"},
|
|
||||||
{Name: "Oliver Allen", Email: "oliver@toyshop.com"},
|
|
||||||
}
|
|
||||||
return app
|
|
||||||
}
|
|
||||||
|
|
||||||
func expectFileContent(t *testing.T, file, got string) {
|
|
||||||
data, err := ioutil.ReadFile(file)
|
|
||||||
// Ignore windows line endings
|
|
||||||
// TODO: Replace with bytes.ReplaceAll when support for Go 1.11 is dropped
|
|
||||||
data = bytes.Replace(data, []byte("\r\n"), []byte("\n"), -1)
|
|
||||||
expect(t, err, nil)
|
|
||||||
expect(t, got, string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestToMarkdownFull(t *testing.T) {
|
func TestToMarkdownFull(t *testing.T) {
|
||||||
// Given
|
// Given
|
||||||
app := testApp()
|
app := testApp()
|
||||||
|
2
fish.go
2
fish.go
@ -95,7 +95,7 @@ func (a *App) prepareFishCommands(commands []*Command, allCommands *[]string, pr
|
|||||||
completions = append(completions, completion.String())
|
completions = append(completions, completion.String())
|
||||||
completions = append(
|
completions = append(
|
||||||
completions,
|
completions,
|
||||||
a.prepareFishFlags(command.Flags, command.Names())...,
|
a.prepareFishFlags(command.VisibleFlags(), command.Names())...,
|
||||||
)
|
)
|
||||||
|
|
||||||
// recursevly iterate subcommands
|
// recursevly iterate subcommands
|
||||||
|
123
fish_test.go
123
fish_test.go
@ -1,6 +1,8 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -19,3 +21,124 @@ func TestFishCompletion(t *testing.T) {
|
|||||||
expect(t, err, nil)
|
expect(t, err, nil)
|
||||||
expectFileContent(t, "testdata/expected-fish-full.fish", res)
|
expectFileContent(t, "testdata/expected-fish-full.fish", res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testApp() *App {
|
||||||
|
app := newTestApp()
|
||||||
|
app.Name = "greet"
|
||||||
|
app.Flags = []Flag{
|
||||||
|
&StringFlag{
|
||||||
|
Name: "socket",
|
||||||
|
Aliases: []string{"s"},
|
||||||
|
Usage: "some 'usage' text",
|
||||||
|
Value: "value",
|
||||||
|
TakesFile: true,
|
||||||
|
},
|
||||||
|
&StringFlag{Name: "flag", Aliases: []string{"fl", "f"}},
|
||||||
|
&BoolFlag{
|
||||||
|
Name: "another-flag",
|
||||||
|
Aliases: []string{"b"},
|
||||||
|
Usage: "another usage text",
|
||||||
|
},
|
||||||
|
&BoolFlag{
|
||||||
|
Name: "hidden-flag",
|
||||||
|
Hidden: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Commands = []*Command{{
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
Flags: []Flag{
|
||||||
|
&StringFlag{
|
||||||
|
Name: "flag",
|
||||||
|
Aliases: []string{"fl", "f"},
|
||||||
|
TakesFile: true,
|
||||||
|
},
|
||||||
|
&BoolFlag{
|
||||||
|
Name: "another-flag",
|
||||||
|
Aliases: []string{"b"},
|
||||||
|
Usage: "another usage text",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Name: "config",
|
||||||
|
Usage: "another usage test",
|
||||||
|
Subcommands: []*Command{{
|
||||||
|
Aliases: []string{"s", "ss"},
|
||||||
|
Flags: []Flag{
|
||||||
|
&StringFlag{Name: "sub-flag", Aliases: []string{"sub-fl", "s"}},
|
||||||
|
&BoolFlag{
|
||||||
|
Name: "sub-command-flag",
|
||||||
|
Aliases: []string{"s"},
|
||||||
|
Usage: "some usage text",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Name: "sub-config",
|
||||||
|
Usage: "another usage test",
|
||||||
|
}},
|
||||||
|
}, {
|
||||||
|
Aliases: []string{"i", "in"},
|
||||||
|
Name: "info",
|
||||||
|
Usage: "retrieve generic information",
|
||||||
|
}, {
|
||||||
|
Name: "some-command",
|
||||||
|
}, {
|
||||||
|
Name: "hidden-command",
|
||||||
|
Hidden: true,
|
||||||
|
}, {
|
||||||
|
Aliases: []string{"u"},
|
||||||
|
Flags: []Flag{
|
||||||
|
&StringFlag{
|
||||||
|
Name: "flag",
|
||||||
|
Aliases: []string{"fl", "f"},
|
||||||
|
TakesFile: true,
|
||||||
|
},
|
||||||
|
&BoolFlag{
|
||||||
|
Name: "another-flag",
|
||||||
|
Aliases: []string{"b"},
|
||||||
|
Usage: "another usage text",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Name: "usage",
|
||||||
|
Usage: "standard usage text",
|
||||||
|
UsageText: `
|
||||||
|
Usage for the usage text
|
||||||
|
- formatted: Based on the specified ConfigMap and summon secrets.yml
|
||||||
|
- list: Inspect the environment for a specific process running on a Pod
|
||||||
|
- for_effect: Compare 'namespace' environment with 'local'
|
||||||
|
|
||||||
|
` + "```" + `
|
||||||
|
func() { ... }
|
||||||
|
` + "```" + `
|
||||||
|
|
||||||
|
Should be a part of the same code block
|
||||||
|
`,
|
||||||
|
Subcommands: []*Command{{
|
||||||
|
Aliases: []string{"su"},
|
||||||
|
Flags: []Flag{
|
||||||
|
&BoolFlag{
|
||||||
|
Name: "sub-command-flag",
|
||||||
|
Aliases: []string{"s"},
|
||||||
|
Usage: "some usage text",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Name: "sub-usage",
|
||||||
|
Usage: "standard usage text",
|
||||||
|
UsageText: "Single line of UsageText",
|
||||||
|
}},
|
||||||
|
}}
|
||||||
|
app.UsageText = "app [first_arg] [second_arg]"
|
||||||
|
app.Description = `Description of the application.`
|
||||||
|
app.Usage = "Some app"
|
||||||
|
app.Authors = []*Author{
|
||||||
|
{Name: "Harrison", Email: "harrison@lolwut.com"},
|
||||||
|
{Name: "Oliver Allen", Email: "oliver@toyshop.com"},
|
||||||
|
}
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectFileContent(t *testing.T, file, got string) {
|
||||||
|
data, err := ioutil.ReadFile(file)
|
||||||
|
// Ignore windows line endings
|
||||||
|
// TODO: Replace with bytes.ReplaceAll when support for Go 1.11 is dropped
|
||||||
|
data = bytes.Replace(data, []byte("\r\n"), []byte("\n"), -1)
|
||||||
|
expect(t, err, nil)
|
||||||
|
expect(t, got, string(data))
|
||||||
|
}
|
||||||
|
86
flag.go
86
flag.go
@ -5,7 +5,6 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"reflect"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -117,6 +116,12 @@ type DocGenerationFlag interface {
|
|||||||
// GetValue returns the flags value as string representation and an empty
|
// GetValue returns the flags value as string representation and an empty
|
||||||
// string if the flag takes no value at all.
|
// string if the flag takes no value at all.
|
||||||
GetValue() string
|
GetValue() string
|
||||||
|
|
||||||
|
// GetDefaultText returns the default text for this flag
|
||||||
|
GetDefaultText() string
|
||||||
|
|
||||||
|
// GetEnvVars returns the env vars for this flag
|
||||||
|
GetEnvVars() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// VisibleFlag is an interface that allows to check if a flag is visible
|
// VisibleFlag is an interface that allows to check if a flag is visible
|
||||||
@ -238,7 +243,7 @@ func prefixedNames(names []string, placeholder string) string {
|
|||||||
|
|
||||||
func withEnvHint(envVars []string, str string) string {
|
func withEnvHint(envVars []string, str string) string {
|
||||||
envText := ""
|
envText := ""
|
||||||
if envVars != nil && len(envVars) > 0 {
|
if len(envVars) > 0 {
|
||||||
prefix := "$"
|
prefix := "$"
|
||||||
suffix := ""
|
suffix := ""
|
||||||
sep := ", $"
|
sep := ", $"
|
||||||
@ -267,17 +272,6 @@ func flagNames(name string, aliases []string) []string {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func flagStringSliceField(f Flag, name string) []string {
|
|
||||||
fv := flagValue(f)
|
|
||||||
field := fv.FieldByName(name)
|
|
||||||
|
|
||||||
if field.IsValid() {
|
|
||||||
return field.Interface().([]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func withFileHint(filePath, str string) string {
|
func withFileHint(filePath, str string) string {
|
||||||
fileText := ""
|
fileText := ""
|
||||||
if filePath != "" {
|
if filePath != "" {
|
||||||
@ -286,68 +280,34 @@ func withFileHint(filePath, str string) string {
|
|||||||
return str + fileText
|
return str + fileText
|
||||||
}
|
}
|
||||||
|
|
||||||
func flagValue(f Flag) reflect.Value {
|
|
||||||
fv := reflect.ValueOf(f)
|
|
||||||
for fv.Kind() == reflect.Ptr {
|
|
||||||
fv = reflect.Indirect(fv)
|
|
||||||
}
|
|
||||||
return fv
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatDefault(format string) string {
|
func formatDefault(format string) string {
|
||||||
return " (default: " + format + ")"
|
return " (default: " + format + ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringifyFlag(f Flag) string {
|
func stringifyFlag(f Flag) string {
|
||||||
fv := flagValue(f)
|
// enforce DocGeneration interface on flags to avoid reflection
|
||||||
|
df, ok := f.(DocGenerationFlag)
|
||||||
switch f := f.(type) {
|
if !ok {
|
||||||
case *IntSliceFlag:
|
return ""
|
||||||
return withEnvHint(flagStringSliceField(f, "EnvVars"),
|
|
||||||
stringifyIntSliceFlag(f))
|
|
||||||
case *Int64SliceFlag:
|
|
||||||
return withEnvHint(flagStringSliceField(f, "EnvVars"),
|
|
||||||
stringifyInt64SliceFlag(f))
|
|
||||||
case *Float64SliceFlag:
|
|
||||||
return withEnvHint(flagStringSliceField(f, "EnvVars"),
|
|
||||||
stringifyFloat64SliceFlag(f))
|
|
||||||
case *StringSliceFlag:
|
|
||||||
return withEnvHint(flagStringSliceField(f, "EnvVars"),
|
|
||||||
stringifyStringSliceFlag(f))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String())
|
placeholder, usage := unquoteUsage(df.GetUsage())
|
||||||
|
needsPlaceholder := df.TakesValue()
|
||||||
needsPlaceholder := false
|
|
||||||
defaultValueString := ""
|
|
||||||
val := fv.FieldByName("Value")
|
|
||||||
if val.IsValid() {
|
|
||||||
needsPlaceholder = val.Kind() != reflect.Bool
|
|
||||||
defaultValueString = fmt.Sprintf(formatDefault("%v"), val.Interface())
|
|
||||||
|
|
||||||
if val.Kind() == reflect.String && val.String() != "" {
|
|
||||||
defaultValueString = fmt.Sprintf(formatDefault("%q"), val.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
helpText := fv.FieldByName("DefaultText")
|
|
||||||
if helpText.IsValid() && helpText.String() != "" {
|
|
||||||
needsPlaceholder = val.Kind() != reflect.Bool
|
|
||||||
defaultValueString = fmt.Sprintf(formatDefault("%s"), helpText.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if defaultValueString == formatDefault("") {
|
|
||||||
defaultValueString = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if needsPlaceholder && placeholder == "" {
|
if needsPlaceholder && placeholder == "" {
|
||||||
placeholder = defaultPlaceholder
|
placeholder = defaultPlaceholder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defaultValueString := ""
|
||||||
|
|
||||||
|
if s := df.GetDefaultText(); s != "" {
|
||||||
|
defaultValueString = fmt.Sprintf(formatDefault("%s"), s)
|
||||||
|
}
|
||||||
|
|
||||||
usageWithDefault := strings.TrimSpace(usage + defaultValueString)
|
usageWithDefault := strings.TrimSpace(usage + defaultValueString)
|
||||||
|
|
||||||
return withEnvHint(flagStringSliceField(f, "EnvVars"),
|
return withEnvHint(df.GetEnvVars(),
|
||||||
fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault))
|
fmt.Sprintf("%s\t%s", prefixedNames(df.Names(), placeholder), usageWithDefault))
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringifyIntSliceFlag(f *IntSliceFlag) string {
|
func stringifyIntSliceFlag(f *IntSliceFlag) string {
|
||||||
@ -442,3 +402,7 @@ func flagFromEnvOrFile(envVars []string, filePath string) (val string, ok bool)
|
|||||||
}
|
}
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func flagSplitMultiValues(val string) []string {
|
||||||
|
return strings.Split(val, ",")
|
||||||
|
}
|
||||||
|
17
flag_bool.go
17
flag_bool.go
@ -63,6 +63,19 @@ func (f *BoolFlag) IsVisible() bool {
|
|||||||
return !f.Hidden
|
return !f.Hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultText returns the default text for this flag
|
||||||
|
func (f *BoolFlag) GetDefaultText() string {
|
||||||
|
if f.DefaultText != "" {
|
||||||
|
return f.DefaultText
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v", f.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEnvVars returns the env vars for this flag
|
||||||
|
func (f *BoolFlag) GetEnvVars() []string {
|
||||||
|
return f.EnvVars
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
@ -91,8 +104,8 @@ 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 (cCtx *Context) Bool(name string) bool {
|
||||||
if fs := c.lookupFlagSet(name); fs != nil {
|
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||||
return lookupBool(name, fs)
|
return lookupBool(name, fs)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -63,6 +63,19 @@ func (f *DurationFlag) IsVisible() bool {
|
|||||||
return !f.Hidden
|
return !f.Hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultText returns the default text for this flag
|
||||||
|
func (f *DurationFlag) GetDefaultText() string {
|
||||||
|
if f.DefaultText != "" {
|
||||||
|
return f.DefaultText
|
||||||
|
}
|
||||||
|
return f.GetValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEnvVars returns the env vars for this flag
|
||||||
|
func (f *DurationFlag) GetEnvVars() []string {
|
||||||
|
return f.EnvVars
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
@ -90,8 +103,8 @@ 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 (cCtx *Context) Duration(name string) time.Duration {
|
||||||
if fs := c.lookupFlagSet(name); fs != nil {
|
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||||
return lookupDuration(name, fs)
|
return lookupDuration(name, fs)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
@ -55,7 +55,20 @@ func (f *Float64Flag) GetUsage() string {
|
|||||||
// GetValue returns the flags value as string representation and an empty
|
// GetValue returns the flags value as string representation and an empty
|
||||||
// string if the flag takes no value at all.
|
// string if the flag takes no value at all.
|
||||||
func (f *Float64Flag) GetValue() string {
|
func (f *Float64Flag) GetValue() string {
|
||||||
return fmt.Sprintf("%f", f.Value)
|
return fmt.Sprintf("%v", f.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDefaultText returns the default text for this flag
|
||||||
|
func (f *Float64Flag) GetDefaultText() string {
|
||||||
|
if f.DefaultText != "" {
|
||||||
|
return f.DefaultText
|
||||||
|
}
|
||||||
|
return f.GetValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEnvVars returns the env vars for this flag
|
||||||
|
func (f *Float64Flag) GetEnvVars() []string {
|
||||||
|
return f.EnvVars
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
@ -90,8 +103,8 @@ 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 (cCtx *Context) Float64(name string) float64 {
|
||||||
if fs := c.lookupFlagSet(name); fs != nil {
|
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||||
return lookupFloat64(name, fs)
|
return lookupFloat64(name, fs)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
@ -43,12 +43,14 @@ func (f *Float64Slice) Set(value string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp, err := strconv.ParseFloat(value, 64)
|
for _, s := range flagSplitMultiValues(value) {
|
||||||
if err != nil {
|
tmp, err := strconv.ParseFloat(strings.TrimSpace(s), 64)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
f.slice = append(f.slice, tmp)
|
f.slice = append(f.slice, tmp)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +97,7 @@ func (f *Float64SliceFlag) IsSet() bool {
|
|||||||
// String returns a readable representation of this value
|
// String returns a readable representation of this value
|
||||||
// (for usage defaults)
|
// (for usage defaults)
|
||||||
func (f *Float64SliceFlag) String() string {
|
func (f *Float64SliceFlag) String() string {
|
||||||
return FlagStringer(f)
|
return withEnvHint(f.GetEnvVars(), stringifyFloat64SliceFlag(f))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Names returns the names of the flag
|
// Names returns the names of the flag
|
||||||
@ -132,13 +134,26 @@ func (f *Float64SliceFlag) IsVisible() bool {
|
|||||||
return !f.Hidden
|
return !f.Hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultText returns the default text for this flag
|
||||||
|
func (f *Float64SliceFlag) GetDefaultText() string {
|
||||||
|
if f.DefaultText != "" {
|
||||||
|
return f.DefaultText
|
||||||
|
}
|
||||||
|
return f.GetValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEnvVars returns the env vars for this flag
|
||||||
|
func (f *Float64SliceFlag) GetEnvVars() []string {
|
||||||
|
return f.EnvVars
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
if val != "" {
|
if val != "" {
|
||||||
f.Value = &Float64Slice{}
|
f.Value = &Float64Slice{}
|
||||||
|
|
||||||
for _, s := range strings.Split(val, ",") {
|
for _, s := range flagSplitMultiValues(val) {
|
||||||
if err := f.Value.Set(strings.TrimSpace(s)); err != nil {
|
if err := f.Value.Set(strings.TrimSpace(s)); err != nil {
|
||||||
return fmt.Errorf("could not parse %q as float64 slice value for flag %s: %s", f.Value, f.Name, err)
|
return fmt.Errorf("could not parse %q as float64 slice value for flag %s: %s", f.Value, f.Name, err)
|
||||||
}
|
}
|
||||||
@ -164,8 +179,8 @@ 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 (cCtx *Context) Float64Slice(name string) []float64 {
|
||||||
if fs := c.lookupFlagSet(name); fs != nil {
|
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||||
return lookupFloat64Slice(name, fs)
|
return lookupFloat64Slice(name, fs)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -71,6 +71,19 @@ func (f *GenericFlag) IsVisible() bool {
|
|||||||
return !f.Hidden
|
return !f.Hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultText returns the default text for this flag
|
||||||
|
func (f *GenericFlag) GetDefaultText() string {
|
||||||
|
if f.DefaultText != "" {
|
||||||
|
return f.DefaultText
|
||||||
|
}
|
||||||
|
return f.GetValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEnvVars returns the env vars for this flag
|
||||||
|
func (f *GenericFlag) GetEnvVars() []string {
|
||||||
|
return f.EnvVars
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
@ -93,8 +106,8 @@ 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 (cCtx *Context) Generic(name string) interface{} {
|
||||||
if fs := c.lookupFlagSet(name); fs != nil {
|
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||||
return lookupGeneric(name, fs)
|
return lookupGeneric(name, fs)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
17
flag_int.go
17
flag_int.go
@ -63,6 +63,19 @@ func (f *IntFlag) IsVisible() bool {
|
|||||||
return !f.Hidden
|
return !f.Hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultText returns the default text for this flag
|
||||||
|
func (f *IntFlag) GetDefaultText() string {
|
||||||
|
if f.DefaultText != "" {
|
||||||
|
return f.DefaultText
|
||||||
|
}
|
||||||
|
return f.GetValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEnvVars returns the env vars for this flag
|
||||||
|
func (f *IntFlag) GetEnvVars() []string {
|
||||||
|
return f.EnvVars
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
@ -91,8 +104,8 @@ 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 (cCtx *Context) Int(name string) int {
|
||||||
if fs := c.lookupFlagSet(name); fs != nil {
|
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||||
return lookupInt(name, fs)
|
return lookupInt(name, fs)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
@ -63,6 +63,19 @@ func (f *Int64Flag) IsVisible() bool {
|
|||||||
return !f.Hidden
|
return !f.Hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultText returns the default text for this flag
|
||||||
|
func (f *Int64Flag) GetDefaultText() string {
|
||||||
|
if f.DefaultText != "" {
|
||||||
|
return f.DefaultText
|
||||||
|
}
|
||||||
|
return f.GetValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEnvVars returns the env vars for this flag
|
||||||
|
func (f *Int64Flag) GetEnvVars() []string {
|
||||||
|
return f.EnvVars
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
@ -90,8 +103,8 @@ 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 (cCtx *Context) Int64(name string) int64 {
|
||||||
if fs := c.lookupFlagSet(name); fs != nil {
|
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||||
return lookupInt64(name, fs)
|
return lookupInt64(name, fs)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
@ -43,12 +43,14 @@ func (i *Int64Slice) Set(value string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp, err := strconv.ParseInt(value, 0, 64)
|
for _, s := range flagSplitMultiValues(value) {
|
||||||
if err != nil {
|
tmp, err := strconv.ParseInt(strings.TrimSpace(s), 0, 64)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
i.slice = append(i.slice, tmp)
|
i.slice = append(i.slice, tmp)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -96,7 +98,7 @@ func (f *Int64SliceFlag) IsSet() bool {
|
|||||||
// String returns a readable representation of this value
|
// String returns a readable representation of this value
|
||||||
// (for usage defaults)
|
// (for usage defaults)
|
||||||
func (f *Int64SliceFlag) String() string {
|
func (f *Int64SliceFlag) String() string {
|
||||||
return FlagStringer(f)
|
return withEnvHint(f.GetEnvVars(), stringifyInt64SliceFlag(f))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Names returns the names of the flag
|
// Names returns the names of the flag
|
||||||
@ -133,12 +135,25 @@ func (f *Int64SliceFlag) IsVisible() bool {
|
|||||||
return !f.Hidden
|
return !f.Hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultText returns the default text for this flag
|
||||||
|
func (f *Int64SliceFlag) GetDefaultText() string {
|
||||||
|
if f.DefaultText != "" {
|
||||||
|
return f.DefaultText
|
||||||
|
}
|
||||||
|
return f.GetValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEnvVars returns the env vars for this flag
|
||||||
|
func (f *Int64SliceFlag) GetEnvVars() []string {
|
||||||
|
return f.EnvVars
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
f.Value = &Int64Slice{}
|
f.Value = &Int64Slice{}
|
||||||
|
|
||||||
for _, s := range strings.Split(val, ",") {
|
for _, s := range flagSplitMultiValues(val) {
|
||||||
if err := f.Value.Set(strings.TrimSpace(s)); err != nil {
|
if err := f.Value.Set(strings.TrimSpace(s)); err != nil {
|
||||||
return fmt.Errorf("could not parse %q as int64 slice value for flag %s: %s", val, f.Name, err)
|
return fmt.Errorf("could not parse %q as int64 slice value for flag %s: %s", val, f.Name, err)
|
||||||
}
|
}
|
||||||
@ -163,8 +178,8 @@ 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 (cCtx *Context) Int64Slice(name string) []int64 {
|
||||||
if fs := c.lookupFlagSet(name); fs != nil {
|
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||||
return lookupInt64Slice(name, fs)
|
return lookupInt64Slice(name, fs)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -54,12 +54,14 @@ func (i *IntSlice) Set(value string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp, err := strconv.ParseInt(value, 0, 64)
|
for _, s := range flagSplitMultiValues(value) {
|
||||||
if err != nil {
|
tmp, err := strconv.ParseInt(strings.TrimSpace(s), 0, 64)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
i.slice = append(i.slice, int(tmp))
|
i.slice = append(i.slice, int(tmp))
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -107,7 +109,7 @@ func (f *IntSliceFlag) IsSet() bool {
|
|||||||
// String returns a readable representation of this value
|
// String returns a readable representation of this value
|
||||||
// (for usage defaults)
|
// (for usage defaults)
|
||||||
func (f *IntSliceFlag) String() string {
|
func (f *IntSliceFlag) String() string {
|
||||||
return FlagStringer(f)
|
return withEnvHint(f.GetEnvVars(), stringifyIntSliceFlag(f))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Names returns the names of the flag
|
// Names returns the names of the flag
|
||||||
@ -144,12 +146,25 @@ func (f *IntSliceFlag) IsVisible() bool {
|
|||||||
return !f.Hidden
|
return !f.Hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultText returns the default text for this flag
|
||||||
|
func (f *IntSliceFlag) GetDefaultText() string {
|
||||||
|
if f.DefaultText != "" {
|
||||||
|
return f.DefaultText
|
||||||
|
}
|
||||||
|
return f.GetValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEnvVars returns the env vars for this flag
|
||||||
|
func (f *IntSliceFlag) GetEnvVars() []string {
|
||||||
|
return f.EnvVars
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
f.Value = &IntSlice{}
|
f.Value = &IntSlice{}
|
||||||
|
|
||||||
for _, s := range strings.Split(val, ",") {
|
for _, s := range flagSplitMultiValues(val) {
|
||||||
if err := f.Value.Set(strings.TrimSpace(s)); err != nil {
|
if err := f.Value.Set(strings.TrimSpace(s)); err != nil {
|
||||||
return fmt.Errorf("could not parse %q as int slice value for flag %s: %s", val, f.Name, err)
|
return fmt.Errorf("could not parse %q as int slice value for flag %s: %s", val, f.Name, err)
|
||||||
}
|
}
|
||||||
@ -174,8 +189,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 (cCtx *Context) IntSlice(name string) []int {
|
||||||
if fs := c.lookupFlagSet(name); fs != nil {
|
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||||
return lookupIntSlice(name, fs)
|
return lookupIntSlice(name, fs)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
25
flag_path.go
25
flag_path.go
@ -1,6 +1,9 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import "flag"
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
type PathFlag struct {
|
type PathFlag struct {
|
||||||
Name string
|
Name string
|
||||||
@ -59,6 +62,22 @@ func (f *PathFlag) IsVisible() bool {
|
|||||||
return !f.Hidden
|
return !f.Hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultText returns the default text for this flag
|
||||||
|
func (f *PathFlag) GetDefaultText() string {
|
||||||
|
if f.DefaultText != "" {
|
||||||
|
return f.DefaultText
|
||||||
|
}
|
||||||
|
if f.Value == "" {
|
||||||
|
return f.Value
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%q", f.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEnvVars returns the env vars for this flag
|
||||||
|
func (f *PathFlag) GetEnvVars() []string {
|
||||||
|
return f.EnvVars
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
@ -79,8 +98,8 @@ 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 (cCtx *Context) Path(name string) string {
|
||||||
if fs := c.lookupFlagSet(name); fs != nil {
|
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||||
return lookupPath(name, fs)
|
return lookupPath(name, fs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import "flag"
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
// StringFlag is a flag with type string
|
// StringFlag is a flag with type string
|
||||||
type StringFlag struct {
|
type StringFlag struct {
|
||||||
@ -60,6 +63,22 @@ func (f *StringFlag) IsVisible() bool {
|
|||||||
return !f.Hidden
|
return !f.Hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultText returns the default text for this flag
|
||||||
|
func (f *StringFlag) GetDefaultText() string {
|
||||||
|
if f.DefaultText != "" {
|
||||||
|
return f.DefaultText
|
||||||
|
}
|
||||||
|
if f.Value == "" {
|
||||||
|
return f.Value
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%q", f.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEnvVars returns the env vars for this flag
|
||||||
|
func (f *StringFlag) GetEnvVars() []string {
|
||||||
|
return f.EnvVars
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
@ -80,8 +99,8 @@ 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 (cCtx *Context) String(name string) string {
|
||||||
if fs := c.lookupFlagSet(name); fs != nil {
|
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||||
return lookupString(name, fs)
|
return lookupString(name, fs)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
|
@ -42,7 +42,9 @@ func (s *StringSlice) Set(value string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
s.slice = append(s.slice, value)
|
for _, t := range flagSplitMultiValues(value) {
|
||||||
|
s.slice = append(s.slice, strings.TrimSpace(t))
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -92,7 +94,7 @@ func (f *StringSliceFlag) IsSet() bool {
|
|||||||
// String returns a readable representation of this value
|
// String returns a readable representation of this value
|
||||||
// (for usage defaults)
|
// (for usage defaults)
|
||||||
func (f *StringSliceFlag) String() string {
|
func (f *StringSliceFlag) String() string {
|
||||||
return FlagStringer(f)
|
return withEnvHint(f.GetEnvVars(), stringifyStringSliceFlag(f))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Names returns the names of the flag
|
// Names returns the names of the flag
|
||||||
@ -129,6 +131,19 @@ func (f *StringSliceFlag) IsVisible() bool {
|
|||||||
return !f.Hidden
|
return !f.Hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultText returns the default text for this flag
|
||||||
|
func (f *StringSliceFlag) GetDefaultText() string {
|
||||||
|
if f.DefaultText != "" {
|
||||||
|
return f.DefaultText
|
||||||
|
}
|
||||||
|
return f.GetValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEnvVars returns the env vars for this flag
|
||||||
|
func (f *StringSliceFlag) GetEnvVars() []string {
|
||||||
|
return f.EnvVars
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
|
|
||||||
@ -147,7 +162,7 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
|
|||||||
destination = f.Destination
|
destination = f.Destination
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range strings.Split(val, ",") {
|
for _, s := range flagSplitMultiValues(val) {
|
||||||
if err := destination.Set(strings.TrimSpace(s)); err != nil {
|
if err := destination.Set(strings.TrimSpace(s)); err != nil {
|
||||||
return fmt.Errorf("could not parse %q as string value for flag %s: %s", val, f.Name, err)
|
return fmt.Errorf("could not parse %q as string value for flag %s: %s", val, f.Name, err)
|
||||||
}
|
}
|
||||||
@ -175,8 +190,8 @@ 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 (cCtx *Context) StringSlice(name string) []string {
|
||||||
if fs := c.lookupFlagSet(name); fs != nil {
|
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||||
return lookupStringSlice(name, fs)
|
return lookupStringSlice(name, fs)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
291
flag_test.go
291
flag_test.go
@ -132,8 +132,13 @@ func TestFlagsFromEnv(t *testing.T) {
|
|||||||
for i, test := range flagTests {
|
for i, test := range flagTests {
|
||||||
defer resetEnv(os.Environ())
|
defer resetEnv(os.Environ())
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
envVarSlice := reflect.Indirect(reflect.ValueOf(test.flag)).FieldByName("EnvVars").Slice(0, 1)
|
|
||||||
_ = os.Setenv(envVarSlice.Index(0).String(), test.input)
|
f, ok := test.flag.(DocGenerationFlag)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("flag %v needs to implement DocGenerationFlag to retrieve env vars", test.flag)
|
||||||
|
}
|
||||||
|
envVarSlice := f.GetEnvVars()
|
||||||
|
_ = os.Setenv(envVarSlice[0], test.input)
|
||||||
|
|
||||||
a := App{
|
a := App{
|
||||||
Flags: []Flag{test.flag},
|
Flags: []Flag{test.flag},
|
||||||
@ -163,6 +168,183 @@ func TestFlagsFromEnv(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type nodocFlag struct {
|
||||||
|
Flag
|
||||||
|
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlagStringifying(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
fl Flag
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "bool-flag",
|
||||||
|
fl: &BoolFlag{Name: "vividly"},
|
||||||
|
expected: "--vividly\t(default: false)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bool-flag-with-default-text",
|
||||||
|
fl: &BoolFlag{Name: "wildly", DefaultText: "scrambled"},
|
||||||
|
expected: "--wildly\t(default: scrambled)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duration-flag",
|
||||||
|
fl: &DurationFlag{Name: "scream-for"},
|
||||||
|
expected: "--scream-for value\t(default: 0s)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duration-flag-with-default-text",
|
||||||
|
fl: &DurationFlag{Name: "feels-about", DefaultText: "whimsically"},
|
||||||
|
expected: "--feels-about value\t(default: whimsically)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float64-flag",
|
||||||
|
fl: &Float64Flag{Name: "arduous"},
|
||||||
|
expected: "--arduous value\t(default: 0)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float64-flag-with-default-text",
|
||||||
|
fl: &Float64Flag{Name: "filibuster", DefaultText: "42"},
|
||||||
|
expected: "--filibuster value\t(default: 42)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float64-slice-flag",
|
||||||
|
fl: &Float64SliceFlag{Name: "pizzas"},
|
||||||
|
expected: "--pizzas value\t",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float64-slice-flag-with-default-text",
|
||||||
|
fl: &Float64SliceFlag{Name: "pepperonis", DefaultText: "shaved"},
|
||||||
|
expected: "--pepperonis value\t(default: shaved)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "generic-flag",
|
||||||
|
fl: &GenericFlag{Name: "yogurt"},
|
||||||
|
expected: "--yogurt value\t",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "generic-flag-with-default-text",
|
||||||
|
fl: &GenericFlag{Name: "ricotta", DefaultText: "plops"},
|
||||||
|
expected: "--ricotta value\t(default: plops)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int-flag",
|
||||||
|
fl: &IntFlag{Name: "grubs"},
|
||||||
|
expected: "--grubs value\t(default: 0)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int-flag-with-default-text",
|
||||||
|
fl: &IntFlag{Name: "poisons", DefaultText: "11ty"},
|
||||||
|
expected: "--poisons value\t(default: 11ty)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int-slice-flag",
|
||||||
|
fl: &IntSliceFlag{Name: "pencils"},
|
||||||
|
expected: "--pencils value\t",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int-slice-flag-with-default-text",
|
||||||
|
fl: &IntFlag{Name: "pens", DefaultText: "-19"},
|
||||||
|
expected: "--pens value\t(default: -19)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int64-flag",
|
||||||
|
fl: &Int64Flag{Name: "flume"},
|
||||||
|
expected: "--flume value\t(default: 0)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int64-flag-with-default-text",
|
||||||
|
fl: &Int64Flag{Name: "shattering", DefaultText: "22"},
|
||||||
|
expected: "--shattering value\t(default: 22)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int64-slice-flag",
|
||||||
|
fl: &Int64SliceFlag{Name: "drawers"},
|
||||||
|
expected: "--drawers value\t",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int64-slice-flag-with-default-text",
|
||||||
|
fl: &Int64SliceFlag{Name: "handles", DefaultText: "-2"},
|
||||||
|
expected: "--handles value\t(default: -2)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "path-flag",
|
||||||
|
fl: &PathFlag{Name: "soup"},
|
||||||
|
expected: "--soup value\t",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "path-flag-with-default-text",
|
||||||
|
fl: &PathFlag{Name: "stew", DefaultText: "charred/beans"},
|
||||||
|
expected: "--stew value\t(default: charred/beans)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "string-flag",
|
||||||
|
fl: &StringFlag{Name: "arf-sound"},
|
||||||
|
expected: "--arf-sound value\t",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "string-flag-with-default-text",
|
||||||
|
fl: &StringFlag{Name: "woof-sound", DefaultText: "urp"},
|
||||||
|
expected: "--woof-sound value\t(default: urp)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "string-slice-flag",
|
||||||
|
fl: &StringSliceFlag{Name: "meow-sounds"},
|
||||||
|
expected: "--meow-sounds value\t",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "string-slice-flag-with-default-text",
|
||||||
|
fl: &StringSliceFlag{Name: "moo-sounds", DefaultText: "awoo"},
|
||||||
|
expected: "--moo-sounds value\t(default: awoo)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "timestamp-flag",
|
||||||
|
fl: &TimestampFlag{Name: "eating"},
|
||||||
|
expected: "--eating value\t",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "timestamp-flag-with-default-text",
|
||||||
|
fl: &TimestampFlag{Name: "sleeping", DefaultText: "earlier"},
|
||||||
|
expected: "--sleeping value\t(default: earlier)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint-flag",
|
||||||
|
fl: &UintFlag{Name: "jars"},
|
||||||
|
expected: "--jars value\t(default: 0)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint-flag-with-default-text",
|
||||||
|
fl: &UintFlag{Name: "bottles", DefaultText: "99"},
|
||||||
|
expected: "--bottles value\t(default: 99)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint64-flag",
|
||||||
|
fl: &Uint64Flag{Name: "cans"},
|
||||||
|
expected: "--cans value\t(default: 0)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint64-flag-with-default-text",
|
||||||
|
fl: &UintFlag{Name: "tubes", DefaultText: "13"},
|
||||||
|
expected: "--tubes value\t(default: 13)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nodoc-flag",
|
||||||
|
fl: &nodocFlag{Name: "scarecrow"},
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(ct *testing.T) {
|
||||||
|
s := stringifyFlag(tc.fl)
|
||||||
|
if s != tc.expected {
|
||||||
|
ct.Errorf("stringified flag %q does not match expected %q", s, tc.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var stringFlagTests = []struct {
|
var stringFlagTests = []struct {
|
||||||
name string
|
name string
|
||||||
aliases []string
|
aliases []string
|
||||||
@ -218,7 +400,7 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var prefixStringFlagTests = []struct {
|
var _ = []struct {
|
||||||
name string
|
name string
|
||||||
aliases []string
|
aliases []string
|
||||||
usage string
|
usage string
|
||||||
@ -308,7 +490,7 @@ func TestPathFlagApply_SetsAllNames(t *testing.T) {
|
|||||||
expect(t, v, "/path/to/file/PATH")
|
expect(t, v, "/path/to/file/PATH")
|
||||||
}
|
}
|
||||||
|
|
||||||
var envHintFlagTests = []struct {
|
var _ = []struct {
|
||||||
name string
|
name string
|
||||||
env string
|
env string
|
||||||
hinter FlagEnvHintFunc
|
hinter FlagEnvHintFunc
|
||||||
@ -1992,43 +2174,43 @@ type flagDefaultTestCase struct {
|
|||||||
|
|
||||||
func TestFlagDefaultValue(t *testing.T) {
|
func TestFlagDefaultValue(t *testing.T) {
|
||||||
cases := []*flagDefaultTestCase{
|
cases := []*flagDefaultTestCase{
|
||||||
&flagDefaultTestCase{
|
{
|
||||||
name: "stringSclice",
|
name: "stringSclice",
|
||||||
flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")},
|
flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")},
|
||||||
toParse: []string{"--flag", "parsed"},
|
toParse: []string{"--flag", "parsed"},
|
||||||
expect: `--flag value (default: "default1", "default2") (accepts multiple inputs)`,
|
expect: `--flag value (default: "default1", "default2") (accepts multiple inputs)`,
|
||||||
},
|
},
|
||||||
&flagDefaultTestCase{
|
{
|
||||||
name: "float64Sclice",
|
name: "float64Sclice",
|
||||||
flag: &Float64SliceFlag{Name: "flag", Value: NewFloat64Slice(1.1, 2.2)},
|
flag: &Float64SliceFlag{Name: "flag", Value: NewFloat64Slice(1.1, 2.2)},
|
||||||
toParse: []string{"--flag", "13.3"},
|
toParse: []string{"--flag", "13.3"},
|
||||||
expect: `--flag value (default: 1.1, 2.2) (accepts multiple inputs)`,
|
expect: `--flag value (default: 1.1, 2.2) (accepts multiple inputs)`,
|
||||||
},
|
},
|
||||||
&flagDefaultTestCase{
|
{
|
||||||
name: "int64Sclice",
|
name: "int64Sclice",
|
||||||
flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2)},
|
flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2)},
|
||||||
toParse: []string{"--flag", "13"},
|
toParse: []string{"--flag", "13"},
|
||||||
expect: `--flag value (default: 1, 2) (accepts multiple inputs)`,
|
expect: `--flag value (default: 1, 2) (accepts multiple inputs)`,
|
||||||
},
|
},
|
||||||
&flagDefaultTestCase{
|
{
|
||||||
name: "intSclice",
|
name: "intSclice",
|
||||||
flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)},
|
flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)},
|
||||||
toParse: []string{"--flag", "13"},
|
toParse: []string{"--flag", "13"},
|
||||||
expect: `--flag value (default: 1, 2) (accepts multiple inputs)`,
|
expect: `--flag value (default: 1, 2) (accepts multiple inputs)`,
|
||||||
},
|
},
|
||||||
&flagDefaultTestCase{
|
{
|
||||||
name: "string",
|
name: "string",
|
||||||
flag: &StringFlag{Name: "flag", Value: "default"},
|
flag: &StringFlag{Name: "flag", Value: "default"},
|
||||||
toParse: []string{"--flag", "parsed"},
|
toParse: []string{"--flag", "parsed"},
|
||||||
expect: `--flag value (default: "default")`,
|
expect: `--flag value (default: "default")`,
|
||||||
},
|
},
|
||||||
&flagDefaultTestCase{
|
{
|
||||||
name: "bool",
|
name: "bool",
|
||||||
flag: &BoolFlag{Name: "flag", Value: true},
|
flag: &BoolFlag{Name: "flag", Value: true},
|
||||||
toParse: []string{"--flag", "false"},
|
toParse: []string{"--flag", "false"},
|
||||||
expect: `--flag (default: true)`,
|
expect: `--flag (default: true)`,
|
||||||
},
|
},
|
||||||
&flagDefaultTestCase{
|
{
|
||||||
name: "uint64",
|
name: "uint64",
|
||||||
flag: &Uint64Flag{Name: "flag", Value: 1},
|
flag: &Uint64Flag{Name: "flag", Value: 1},
|
||||||
toParse: []string{"--flag", "13"},
|
toParse: []string{"--flag", "13"},
|
||||||
@ -2048,6 +2230,54 @@ func TestFlagDefaultValue(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type flagValueTestCase struct {
|
||||||
|
name string
|
||||||
|
flag Flag
|
||||||
|
toParse []string
|
||||||
|
expect string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlagValue(t *testing.T) {
|
||||||
|
cases := []*flagValueTestCase{
|
||||||
|
&flagValueTestCase{
|
||||||
|
name: "stringSclice",
|
||||||
|
flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")},
|
||||||
|
toParse: []string{"--flag", "parsed,parsed2", "--flag", "parsed3,parsed4"},
|
||||||
|
expect: `[parsed parsed2 parsed3 parsed4]`,
|
||||||
|
},
|
||||||
|
&flagValueTestCase{
|
||||||
|
name: "float64Sclice",
|
||||||
|
flag: &Float64SliceFlag{Name: "flag", Value: NewFloat64Slice(1.1, 2.2)},
|
||||||
|
toParse: []string{"--flag", "13.3,14.4", "--flag", "15.5,16.6"},
|
||||||
|
expect: `[]float64{13.3, 14.4, 15.5, 16.6}`,
|
||||||
|
},
|
||||||
|
&flagValueTestCase{
|
||||||
|
name: "int64Sclice",
|
||||||
|
flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2)},
|
||||||
|
toParse: []string{"--flag", "13,14", "--flag", "15,16"},
|
||||||
|
expect: `[]int64{13, 14, 15, 16}`,
|
||||||
|
},
|
||||||
|
&flagValueTestCase{
|
||||||
|
name: "intSclice",
|
||||||
|
flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)},
|
||||||
|
toParse: []string{"--flag", "13,14", "--flag", "15,16"},
|
||||||
|
expect: `[]int{13, 14, 15, 16}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
f := set.Lookup("flag")
|
||||||
|
if got := f.Value.String(); got != v.expect {
|
||||||
|
t.Errorf("TestFlagValue %d-%s\nexpect:%s\ngot:%s", i, v.name, v.expect, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTimestampFlagApply_WithDestination(t *testing.T) {
|
func TestTimestampFlagApply_WithDestination(t *testing.T) {
|
||||||
var destination Timestamp
|
var destination Timestamp
|
||||||
expectedResult, _ := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
expectedResult, _ := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
||||||
@ -2059,3 +2289,42 @@ func TestTimestampFlagApply_WithDestination(t *testing.T) {
|
|||||||
expect(t, err, nil)
|
expect(t, err, nil)
|
||||||
expect(t, *fl.Destination.timestamp, expectedResult)
|
expect(t, *fl.Destination.timestamp, expectedResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test issue #1254
|
||||||
|
// StringSlice() with UseShortOptionHandling causes duplicated entries, depending on the ordering of the flags
|
||||||
|
func TestSliceShortOptionHandle(t *testing.T) {
|
||||||
|
wasCalled := false
|
||||||
|
err := (&App{
|
||||||
|
Commands: []*Command{
|
||||||
|
{
|
||||||
|
Name: "foobar",
|
||||||
|
UseShortOptionHandling: true,
|
||||||
|
Action: func(ctx *Context) error {
|
||||||
|
wasCalled = true
|
||||||
|
if ctx.Bool("i") != true {
|
||||||
|
t.Error("bool i not set")
|
||||||
|
}
|
||||||
|
if ctx.Bool("t") != true {
|
||||||
|
t.Error("bool i not set")
|
||||||
|
}
|
||||||
|
ss := ctx.StringSlice("net")
|
||||||
|
if !reflect.DeepEqual(ss, []string{"foo"}) {
|
||||||
|
t.Errorf("Got different slice(%v) than expected", ss)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
Flags: []Flag{
|
||||||
|
&StringSliceFlag{Name: "net"},
|
||||||
|
&BoolFlag{Name: "i"},
|
||||||
|
&BoolFlag{Name: "t"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).Run([]string{"run", "foobar", "--net=foo", "-it"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !wasCalled {
|
||||||
|
t.Fatal("Action callback was never called")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -119,6 +119,19 @@ func (f *TimestampFlag) IsVisible() bool {
|
|||||||
return !f.Hidden
|
return !f.Hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultText returns the default text for this flag
|
||||||
|
func (f *TimestampFlag) GetDefaultText() string {
|
||||||
|
if f.DefaultText != "" {
|
||||||
|
return f.DefaultText
|
||||||
|
}
|
||||||
|
return f.GetValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEnvVars returns the env vars for this flag
|
||||||
|
func (f *TimestampFlag) GetEnvVars() []string {
|
||||||
|
return f.EnvVars
|
||||||
|
}
|
||||||
|
|
||||||
// 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 == "" {
|
||||||
@ -152,8 +165,8 @@ 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 (cCtx *Context) Timestamp(name string) *time.Time {
|
||||||
if fs := c.lookupFlagSet(name); fs != nil {
|
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||||
return lookupTimestamp(name, fs)
|
return lookupTimestamp(name, fs)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
17
flag_uint.go
17
flag_uint.go
@ -88,10 +88,23 @@ func (f *UintFlag) GetValue() string {
|
|||||||
return fmt.Sprintf("%d", f.Value)
|
return fmt.Sprintf("%d", f.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultText returns the default text for this flag
|
||||||
|
func (f *UintFlag) GetDefaultText() string {
|
||||||
|
if f.DefaultText != "" {
|
||||||
|
return f.DefaultText
|
||||||
|
}
|
||||||
|
return f.GetValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEnvVars returns the env vars for this flag
|
||||||
|
func (f *UintFlag) GetEnvVars() []string {
|
||||||
|
return f.EnvVars
|
||||||
|
}
|
||||||
|
|
||||||
// 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 (cCtx *Context) Uint(name string) uint {
|
||||||
if fs := c.lookupFlagSet(name); fs != nil {
|
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||||
return lookupUint(name, fs)
|
return lookupUint(name, fs)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
@ -88,10 +88,23 @@ func (f *Uint64Flag) GetValue() string {
|
|||||||
return fmt.Sprintf("%d", f.Value)
|
return fmt.Sprintf("%d", f.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultText returns the default text for this flag
|
||||||
|
func (f *Uint64Flag) GetDefaultText() string {
|
||||||
|
if f.DefaultText != "" {
|
||||||
|
return f.DefaultText
|
||||||
|
}
|
||||||
|
return f.GetValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEnvVars returns the env vars for this flag
|
||||||
|
func (f *Uint64Flag) GetEnvVars() []string {
|
||||||
|
return f.EnvVars
|
||||||
|
}
|
||||||
|
|
||||||
// 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 (cCtx *Context) Uint64(name string) uint64 {
|
||||||
if fs := c.lookupFlagSet(name); fs != nil {
|
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||||
return lookupUint64(name, fs)
|
return lookupUint64(name, fs)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
4
funcs.go
4
funcs.go
@ -21,11 +21,11 @@ type CommandNotFoundFunc func(*Context, string)
|
|||||||
// 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.
|
||||||
type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error
|
type OnUsageErrorFunc func(cCtx *Context, err error, isSubcommand bool) error
|
||||||
|
|
||||||
// ExitErrHandlerFunc is executed if provided in order to handle exitError values
|
// ExitErrHandlerFunc is executed if provided in order to handle exitError values
|
||||||
// returned by Actions and Before/After functions.
|
// returned by Actions and Before/After functions.
|
||||||
type ExitErrHandlerFunc func(context *Context, err error)
|
type ExitErrHandlerFunc func(cCtx *Context, err error)
|
||||||
|
|
||||||
// FlagStringFunc is used by the help generation to display a flag, which is
|
// FlagStringFunc is used by the help generation to display a flag, which is
|
||||||
// expected to be a single line.
|
// expected to be a single line.
|
||||||
|
8
go.mod
8
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.1
|
github.com/cpuguy83/go-md2man/v2 v2.0.1
|
||||||
gopkg.in/yaml.v2 v2.2.8
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
6
go.sum
6
go.sum
@ -1,10 +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/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
|
||||||
|
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
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.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
101
help.go
101
help.go
@ -15,13 +15,13 @@ var helpCommand = &Command{
|
|||||||
Aliases: []string{"h"},
|
Aliases: []string{"h"},
|
||||||
Usage: "Shows a list of commands or help for one command",
|
Usage: "Shows a list of commands or help for one command",
|
||||||
ArgsUsage: "[command]",
|
ArgsUsage: "[command]",
|
||||||
Action: func(c *Context) error {
|
Action: func(cCtx *Context) error {
|
||||||
args := c.Args()
|
args := cCtx.Args()
|
||||||
if args.Present() {
|
if args.Present() {
|
||||||
return ShowCommandHelp(c, args.First())
|
return ShowCommandHelp(cCtx, args.First())
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = ShowAppHelp(c)
|
_ = ShowAppHelp(cCtx)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -31,13 +31,13 @@ var helpSubcommand = &Command{
|
|||||||
Aliases: []string{"h"},
|
Aliases: []string{"h"},
|
||||||
Usage: "Shows a list of commands or help for one command",
|
Usage: "Shows a list of commands or help for one command",
|
||||||
ArgsUsage: "[command]",
|
ArgsUsage: "[command]",
|
||||||
Action: func(c *Context) error {
|
Action: func(cCtx *Context) error {
|
||||||
args := c.Args()
|
args := cCtx.Args()
|
||||||
if args.Present() {
|
if args.Present() {
|
||||||
return ShowCommandHelp(c, args.First())
|
return ShowCommandHelp(cCtx, args.First())
|
||||||
}
|
}
|
||||||
|
|
||||||
return ShowSubcommandHelp(c)
|
return ShowSubcommandHelp(cCtx)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,30 +71,30 @@ 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(cCtx *Context) error {
|
||||||
tpl := c.App.CustomAppHelpTemplate
|
tpl := cCtx.App.CustomAppHelpTemplate
|
||||||
if tpl == "" {
|
if tpl == "" {
|
||||||
tpl = AppHelpTemplate
|
tpl = AppHelpTemplate
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.App.ExtraInfo == nil {
|
if cCtx.App.ExtraInfo == nil {
|
||||||
HelpPrinter(c.App.Writer, tpl, c.App)
|
HelpPrinter(cCtx.App.Writer, tpl, cCtx.App)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
customAppData := func() map[string]interface{} {
|
customAppData := func() map[string]interface{} {
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"ExtraInfo": c.App.ExtraInfo,
|
"ExtraInfo": cCtx.App.ExtraInfo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HelpPrinterCustom(c.App.Writer, tpl, c.App, customAppData())
|
HelpPrinterCustom(cCtx.App.Writer, tpl, cCtx.App, customAppData())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultAppComplete prints the list of subcommands as the default app completion method
|
// DefaultAppComplete prints the list of subcommands as the default app completion method
|
||||||
func DefaultAppComplete(c *Context) {
|
func DefaultAppComplete(cCtx *Context) {
|
||||||
DefaultCompleteWithFlags(nil)(c)
|
DefaultCompleteWithFlags(nil)(cCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func printCommandSuggestions(commands []*Command, writer io.Writer) {
|
func printCommandSuggestions(commands []*Command, writer io.Writer) {
|
||||||
@ -159,23 +159,30 @@ func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultCompleteWithFlags(cmd *Command) func(c *Context) {
|
func DefaultCompleteWithFlags(cmd *Command) func(cCtx *Context) {
|
||||||
return func(c *Context) {
|
return func(cCtx *Context) {
|
||||||
if len(os.Args) > 2 {
|
if len(os.Args) > 2 {
|
||||||
lastArg := os.Args[len(os.Args)-2]
|
lastArg := os.Args[len(os.Args)-2]
|
||||||
|
|
||||||
if strings.HasPrefix(lastArg, "-") {
|
if strings.HasPrefix(lastArg, "-") {
|
||||||
printFlagSuggestions(lastArg, c.App.Flags, c.App.Writer)
|
|
||||||
if cmd != nil {
|
if cmd != nil {
|
||||||
printFlagSuggestions(lastArg, cmd.Flags, c.App.Writer)
|
printFlagSuggestions(lastArg, cmd.Flags, cCtx.App.Writer)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printFlagSuggestions(lastArg, cCtx.App.Flags, cCtx.App.Writer)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd != nil {
|
if cmd != nil {
|
||||||
printCommandSuggestions(cmd.Subcommands, c.App.Writer)
|
printCommandSuggestions(cmd.Subcommands, cCtx.App.Writer)
|
||||||
} else {
|
return
|
||||||
printCommandSuggestions(c.App.Commands, c.App.Writer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printCommandSuggestions(cCtx.App.Commands, cCtx.App.Writer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,32 +228,32 @@ func ShowSubcommandHelpAndExit(c *Context, exitCode int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ShowSubcommandHelp prints help for the given subcommand
|
// ShowSubcommandHelp prints help for the given subcommand
|
||||||
func ShowSubcommandHelp(c *Context) error {
|
func ShowSubcommandHelp(cCtx *Context) error {
|
||||||
if c == nil {
|
if cCtx == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Command != nil {
|
if cCtx.Command != nil {
|
||||||
return ShowCommandHelp(c, c.Command.Name)
|
return ShowCommandHelp(cCtx, cCtx.Command.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ShowCommandHelp(c, "")
|
return ShowCommandHelp(cCtx, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShowVersion prints the version number of the App
|
// ShowVersion prints the version number of the App
|
||||||
func ShowVersion(c *Context) {
|
func ShowVersion(cCtx *Context) {
|
||||||
VersionPrinter(c)
|
VersionPrinter(cCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func printVersion(c *Context) {
|
func printVersion(cCtx *Context) {
|
||||||
_, _ = fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
|
_, _ = fmt.Fprintf(cCtx.App.Writer, "%v version %v\n", cCtx.App.Name, cCtx.App.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShowCompletions prints the lists of commands within a given context
|
// ShowCompletions prints the lists of commands within a given context
|
||||||
func ShowCompletions(c *Context) {
|
func ShowCompletions(cCtx *Context) {
|
||||||
a := c.App
|
a := cCtx.App
|
||||||
if a != nil && a.BashComplete != nil {
|
if a != nil && a.BashComplete != nil {
|
||||||
a.BashComplete(c)
|
a.BashComplete(cCtx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,20 +304,20 @@ func printHelp(out io.Writer, templ string, data interface{}) {
|
|||||||
HelpPrinterCustom(out, templ, data, nil)
|
HelpPrinterCustom(out, templ, data, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkVersion(c *Context) bool {
|
func checkVersion(cCtx *Context) bool {
|
||||||
found := false
|
found := false
|
||||||
for _, name := range VersionFlag.Names() {
|
for _, name := range VersionFlag.Names() {
|
||||||
if c.Bool(name) {
|
if cCtx.Bool(name) {
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return found
|
return found
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkHelp(c *Context) bool {
|
func checkHelp(cCtx *Context) bool {
|
||||||
found := false
|
found := false
|
||||||
for _, name := range HelpFlag.Names() {
|
for _, name := range HelpFlag.Names() {
|
||||||
if c.Bool(name) {
|
if cCtx.Bool(name) {
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -326,9 +333,9 @@ func checkCommandHelp(c *Context, name string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkSubcommandHelp(c *Context) bool {
|
func checkSubcommandHelp(cCtx *Context) bool {
|
||||||
if c.Bool("h") || c.Bool("help") {
|
if cCtx.Bool("h") || cCtx.Bool("help") {
|
||||||
_ = ShowSubcommandHelp(c)
|
_ = ShowSubcommandHelp(cCtx)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,20 +357,20 @@ func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) {
|
|||||||
return true, arguments[:pos]
|
return true, arguments[:pos]
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkCompletions(c *Context) bool {
|
func checkCompletions(cCtx *Context) bool {
|
||||||
if !c.shellComplete {
|
if !cCtx.shellComplete {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if args := c.Args(); args.Present() {
|
if args := cCtx.Args(); args.Present() {
|
||||||
name := args.First()
|
name := args.First()
|
||||||
if cmd := c.App.Command(name); cmd != nil {
|
if cmd := cCtx.App.Command(name); cmd != nil {
|
||||||
// let the command handle the completion
|
// let the command handle the completion
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ShowCompletions(c)
|
ShowCompletions(cCtx)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
83
help_test.go
83
help_test.go
@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@ -1037,3 +1038,85 @@ func TestHideHelpCommand_WithSubcommands(t *testing.T) {
|
|||||||
t.Errorf("Run returned unexpected error: %v", err)
|
t.Errorf("Run returned unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDefaultCompleteWithFlags(t *testing.T) {
|
||||||
|
origArgv := os.Args
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
os.Args = origArgv
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
c *Context
|
||||||
|
cmd *Command
|
||||||
|
argv []string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
c: &Context{App: &App{}},
|
||||||
|
cmd: &Command{},
|
||||||
|
argv: []string{"prog", "cmd"},
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "typical-flag-suggestion",
|
||||||
|
c: &Context{App: &App{
|
||||||
|
Name: "cmd",
|
||||||
|
Flags: []Flag{
|
||||||
|
&BoolFlag{Name: "happiness"},
|
||||||
|
&Int64Flag{Name: "everybody-jump-on"},
|
||||||
|
},
|
||||||
|
Commands: []*Command{
|
||||||
|
{Name: "putz"},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
cmd: &Command{
|
||||||
|
Flags: []Flag{
|
||||||
|
&BoolFlag{Name: "excitement"},
|
||||||
|
&StringFlag{Name: "hat-shape"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
argv: []string{"cmd", "--e", "--generate-bash-completion"},
|
||||||
|
expected: "--excitement\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "typical-command-suggestion",
|
||||||
|
c: &Context{App: &App{
|
||||||
|
Name: "cmd",
|
||||||
|
Flags: []Flag{
|
||||||
|
&BoolFlag{Name: "happiness"},
|
||||||
|
&Int64Flag{Name: "everybody-jump-on"},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
cmd: &Command{
|
||||||
|
Name: "putz",
|
||||||
|
Subcommands: []*Command{
|
||||||
|
{Name: "futz"},
|
||||||
|
},
|
||||||
|
Flags: []Flag{
|
||||||
|
&BoolFlag{Name: "excitement"},
|
||||||
|
&StringFlag{Name: "hat-shape"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
argv: []string{"cmd", "--generate-bash-completion"},
|
||||||
|
expected: "futz\n",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(ct *testing.T) {
|
||||||
|
writer := &bytes.Buffer{}
|
||||||
|
tc.c.App.Writer = writer
|
||||||
|
|
||||||
|
os.Args = tc.argv
|
||||||
|
f := DefaultCompleteWithFlags(tc.cmd)
|
||||||
|
f(tc.c)
|
||||||
|
|
||||||
|
written := writer.String()
|
||||||
|
|
||||||
|
if written != tc.expected {
|
||||||
|
ct.Errorf("written help does not match expected %q != %q", written, tc.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -46,6 +46,12 @@ func main() {
|
|||||||
Action: checkBinarySizeActionFunc,
|
Action: checkBinarySizeActionFunc,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "tags",
|
||||||
|
Usage: "set build tags",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
err := app.Run(os.Args)
|
err := app.Run(os.Args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -68,6 +74,8 @@ func VetActionFunc(_ *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestActionFunc(c *cli.Context) error {
|
func TestActionFunc(c *cli.Context) error {
|
||||||
|
tags := c.String("tags")
|
||||||
|
|
||||||
for _, pkg := range packages {
|
for _, pkg := range packages {
|
||||||
var packageName string
|
var packageName string
|
||||||
|
|
||||||
@ -79,7 +87,7 @@ func TestActionFunc(c *cli.Context) error {
|
|||||||
|
|
||||||
coverProfile := fmt.Sprintf("--coverprofile=%s.coverprofile", pkg)
|
coverProfile := fmt.Sprintf("--coverprofile=%s.coverprofile", pkg)
|
||||||
|
|
||||||
err := runCmd("go", "test", "-v", coverProfile, packageName)
|
err := runCmd("go", "test", "-tags", tags, "-v", coverProfile, packageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -193,7 +201,7 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) {
|
|||||||
cliBuiltFilePath = "./internal/example-cli/built-example"
|
cliBuiltFilePath = "./internal/example-cli/built-example"
|
||||||
helloSourceFilePath = "./internal/example-hello-world/example-hello-world.go"
|
helloSourceFilePath = "./internal/example-hello-world/example-hello-world.go"
|
||||||
helloBuiltFilePath = "./internal/example-hello-world/built-example"
|
helloBuiltFilePath = "./internal/example-hello-world/built-example"
|
||||||
desiredMinBinarySize = 1.9
|
desiredMinBinarySize = 1.675
|
||||||
desiredMaxBinarySize = 2.2
|
desiredMaxBinarySize = 2.2
|
||||||
badNewsEmoji = "🚨"
|
badNewsEmoji = "🚨"
|
||||||
goodNewsEmoji = "✨"
|
goodNewsEmoji = "✨"
|
||||||
@ -201,14 +209,16 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) {
|
|||||||
mbStringFormatter = "%.1fMB"
|
mbStringFormatter = "%.1fMB"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tags := c.String("tags")
|
||||||
|
|
||||||
// get cli example size
|
// get cli example size
|
||||||
cliSize, err := getSize(cliSourceFilePath, cliBuiltFilePath)
|
cliSize, err := getSize(cliSourceFilePath, cliBuiltFilePath, tags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// get hello world size
|
// get hello world size
|
||||||
helloSize, err := getSize(helloSourceFilePath, helloBuiltFilePath)
|
helloSize, err := getSize(helloSourceFilePath, helloBuiltFilePath, tags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -270,9 +280,9 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSize(sourcePath string, builtPath string) (size int64, err error) {
|
func getSize(sourcePath string, builtPath string, tags string) (size int64, err error) {
|
||||||
// build example binary
|
// build example binary
|
||||||
err = runCmd("go", "build", "-o", builtPath, "-ldflags", "-s -w", sourcePath)
|
err = runCmd("go", "build", "-tags", tags, "-o", builtPath, "-ldflags", "-s -w", sourcePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("issue getting size for example binary")
|
fmt.Println("issue getting size for example binary")
|
||||||
return 0, err
|
return 0, err
|
||||||
|
@ -7,5 +7,5 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
(&cli.App{}).Run([]string{})
|
(&cli.App{}).Run([]string{""})
|
||||||
}
|
}
|
||||||
|
1
testdata/empty.yml
vendored
Normal file
1
testdata/empty.yml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
# empty file
|
Loading…
Reference in New Issue
Block a user