Merge pull request #1437 from urfave/merging-main-to-v3-dev-main
Merging main to v3 dev main
This commit is contained in:
commit
cb7532a1df
17
.github/stale.yml
vendored
17
.github/stale.yml
vendored
@ -1,17 +0,0 @@
|
|||||||
# Number of days of inactivity before an issue becomes stale
|
|
||||||
daysUntilStale: 365
|
|
||||||
# Number of days of inactivity before a stale issue is closed
|
|
||||||
daysUntilClose: 90
|
|
||||||
# Issues with these labels will never be considered stale
|
|
||||||
exemptLabels:
|
|
||||||
- pinned
|
|
||||||
- security
|
|
||||||
# Label to use when marking an issue as stale
|
|
||||||
staleLabel: wontfix
|
|
||||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
|
||||||
markComment: >
|
|
||||||
This issue has been automatically marked as stale because it has not had
|
|
||||||
recent activity. It will be closed if no further activity occurs. Thank you
|
|
||||||
for your contributions.
|
|
||||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
|
||||||
closeComment: false
|
|
47
.github/workflows/cli.yml
vendored
47
.github/workflows/cli.yml
vendored
@ -34,20 +34,26 @@ jobs:
|
|||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: GOFMT Check
|
- name: GOFMT Check
|
||||||
if: matrix.go == '1.17.x' && 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 urfave_cli_no_docs tag
|
||||||
|
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
|
||||||
|
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
if: success() && matrix.go == '1.17.x' && matrix.os == 'ubuntu-latest'
|
if: success() && matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest'
|
||||||
uses: codecov/codecov-action@v2
|
uses: codecov/codecov-action@v2
|
||||||
with:
|
with:
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
@ -73,14 +79,37 @@ jobs:
|
|||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run:
|
run: |
|
||||||
mkdir -p "${GITHUB_WORKSPACE}/.local/bin" &&
|
mkdir -p "${GITHUB_WORKSPACE}/.local/bin"
|
||||||
curl -fsSL -o "${GITHUB_WORKSPACE}/.local/bin/gfmrun" "https://github.com/urfave/gfmrun/releases/download/v1.3.0/gfmrun-$(go env GOOS)-$(go env GOARCH)-v1.3.0" &&
|
curl -fsSL -o "${GITHUB_WORKSPACE}/.local/bin/gfmrun" "https://github.com/urfave/gfmrun/releases/download/v1.3.0/gfmrun-$(go env GOOS)-$(go env GOARCH)-v1.3.0"
|
||||||
chmod +x "${GITHUB_WORKSPACE}/.local/bin/gfmrun" &&
|
chmod +x "${GITHUB_WORKSPACE}/.local/bin/gfmrun"
|
||||||
npm install -g markdown-toc@1.2.0
|
|
||||||
|
|
||||||
- name: gfmrun
|
- name: gfmrun
|
||||||
run: go run internal/build/build.go gfmrun docs/v2/manual.md
|
run: go run internal/build/build.go gfmrun docs/v2/manual.md
|
||||||
|
|
||||||
- name: toc
|
- name: diff check
|
||||||
run: go run internal/build/build.go toc docs/v2/manual.md
|
run: |
|
||||||
|
git diff --exit-code
|
||||||
|
git diff --cached --exit-code
|
||||||
|
|
||||||
|
publish:
|
||||||
|
if: startswith(github.ref, 'refs/tags/')
|
||||||
|
name: publish
|
||||||
|
needs: [test-docs]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup mkdocs
|
||||||
|
run: |
|
||||||
|
pip install -U pip
|
||||||
|
pip install -r mkdocs-requirements.txt
|
||||||
|
git remote rm origin
|
||||||
|
git remote add origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/urfave/cli.git
|
||||||
|
|
||||||
|
- name: Publish Docs
|
||||||
|
run: |
|
||||||
|
mkdocs gh-deploy --force
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,10 +1,10 @@
|
|||||||
*.coverprofile
|
*.coverprofile
|
||||||
*.orig
|
*.orig
|
||||||
node_modules/
|
|
||||||
vendor
|
vendor
|
||||||
.idea
|
.idea
|
||||||
internal/*/built-example
|
internal/*/built-example
|
||||||
coverage.txt
|
coverage.txt
|
||||||
/.local/
|
/.local/
|
||||||
|
/site/
|
||||||
|
|
||||||
*.exe
|
*.exe
|
||||||
|
@ -55,11 +55,12 @@ further defined and clarified by project maintainers.
|
|||||||
## Enforcement
|
## Enforcement
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported by contacting Dan Buch at dan@meatballhat.com. All complaints will be
|
reported by contacting urfave-governance@googlegroups.com, a members-only group
|
||||||
reviewed and investigated and will result in a response that is deemed necessary
|
that is world-postable. All complaints will be reviewed and investigated and
|
||||||
and appropriate to the circumstances. The project team is obligated to maintain
|
will result in a response that is deemed necessary and appropriate to the
|
||||||
confidentiality with regard to the reporter of an incident. Further details of
|
circumstances. The project team is obligated to maintain confidentiality with
|
||||||
specific enforcement policies may be posted separately.
|
regard to the reporter of an incident. Further details of specific enforcement
|
||||||
|
policies may be posted separately.
|
||||||
|
|
||||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||||
faith may face temporary or permanent repercussions as determined by other
|
faith may face temporary or permanent repercussions as determined by other
|
||||||
|
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
|
||||||
|
40
Makefile
Normal file
40
Makefile
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# NOTE: this Makefile is meant to provide a simplified entry point for humans to
|
||||||
|
# run all of the critical steps to verify one's changes are harmonious in
|
||||||
|
# nature. Keeping target bodies to one line each and abstaining from make magic
|
||||||
|
# are very important so that maintainers and contributors can focus their
|
||||||
|
# attention on files that are primarily Go.
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun v2diff
|
||||||
|
|
||||||
|
# NOTE: this is a special catch-all rule to run any of the commands
|
||||||
|
# defined in internal/build/build.go with optional arguments passed
|
||||||
|
# via GFLAGS (global flags) and FLAGS (command-specific flags), e.g.:
|
||||||
|
#
|
||||||
|
# $ make test GFLAGS='--packages cli'
|
||||||
|
%:
|
||||||
|
go run internal/build/build.go $(GFLAGS) $* $(FLAGS)
|
||||||
|
|
||||||
|
.PHONY: tag-test
|
||||||
|
tag-test:
|
||||||
|
go run internal/build/build.go -tags urfave_cli_no_docs test
|
||||||
|
|
||||||
|
.PHONY: tag-check-binary-size
|
||||||
|
tag-check-binary-size:
|
||||||
|
go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size
|
||||||
|
|
||||||
|
.PHONY: gfmrun
|
||||||
|
gfmrun:
|
||||||
|
go run internal/build/build.go gfmrun docs/v2/manual.md
|
||||||
|
|
||||||
|
.PHONY: docs
|
||||||
|
docs:
|
||||||
|
mkdocs build
|
||||||
|
|
||||||
|
.PHONY: docs-deps
|
||||||
|
docs-deps:
|
||||||
|
pip install -r mkdocs-requirements.txt
|
||||||
|
|
||||||
|
.PHONY: serve-docs
|
||||||
|
serve-docs:
|
||||||
|
mkdocs serve
|
63
README.md
63
README.md
@ -1,5 +1,4 @@
|
|||||||
cli
|
# 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)
|
||||||
@ -10,61 +9,11 @@ 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
|
||||||
applications in an expressive way.
|
applications in an expressive way.
|
||||||
|
|
||||||
## Usage Documentation
|
## Documentation
|
||||||
|
|
||||||
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`.
|
More documentation is available in [`./docs`](./docs) or the hosted
|
||||||
|
documentation site at <https://cli.urfave.org>.
|
||||||
|
|
||||||
- `v2` - [./docs/v2/manual.md](./docs/v2/manual.md)
|
## License
|
||||||
- `v1` - [./docs/v1/manual.md](./docs/v1/manual.md)
|
|
||||||
|
|
||||||
Guides for migrating to newer versions:
|
See [`LICENSE`](./LICENSE)
|
||||||
|
|
||||||
- `v1-to-v2` - [./docs/migrate-v1-to-v2.md](./docs/migrate-v1-to-v2.md)
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Using this package requires a working Go environment. [See the install instructions for Go](http://golang.org/doc/install.html).
|
|
||||||
|
|
||||||
Go Modules are required when using this package. [See the go blog guide on using Go Modules](https://blog.golang.org/using-go-modules).
|
|
||||||
|
|
||||||
### Using `v2` releases
|
|
||||||
|
|
||||||
```
|
|
||||||
$ go get github.com/urfave/cli/v2
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
...
|
|
||||||
import (
|
|
||||||
"github.com/urfave/cli/v2" // imports as package "cli"
|
|
||||||
)
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using `v1` releases
|
|
||||||
|
|
||||||
```
|
|
||||||
$ go get github.com/urfave/cli
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
...
|
|
||||||
import (
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
### GOPATH
|
|
||||||
|
|
||||||
Make sure your `PATH` includes the `$GOPATH/bin` directory so your commands can
|
|
||||||
be easily used:
|
|
||||||
```
|
|
||||||
export PATH=$PATH:$GOPATH/bin
|
|
||||||
```
|
|
||||||
|
|
||||||
### Supported platforms
|
|
||||||
|
|
||||||
cli is tested against multiple versions of Go on Linux, and against the latest
|
|
||||||
released version of Go on OS X and Windows. This project uses Github Actions for
|
|
||||||
builds. To see our currently supported go versions and platforms, look at the [./.github/workflows/cli.yml](https://github.com/urfave/cli/blob/main/.github/workflows/cli.yml).
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
@ -32,11 +32,18 @@ func nestedVal(name string, tree map[interface{}]interface{}) (interface{}, bool
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
ctype, ok := child.(map[interface{}]interface{})
|
|
||||||
if !ok {
|
switch child := child.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
node = make(map[interface{}]interface{}, len(child))
|
||||||
|
for k, v := range child {
|
||||||
|
node[k] = v
|
||||||
|
}
|
||||||
|
case map[interface{}]interface{}:
|
||||||
|
node = child
|
||||||
|
default:
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
node = ctype
|
|
||||||
}
|
}
|
||||||
if val, ok := node[sections[len(sections)-1]]; ok {
|
if val, ok := node[sections[len(sections)-1]]; ok {
|
||||||
return val, true
|
return val, true
|
||||||
@ -244,6 +251,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type yamlSourceContext struct {
|
type yamlSourceContext struct {
|
||||||
@ -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
|
||||||
|
}
|
241
app.go
241
app.go
@ -7,10 +7,13 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const suggestDidYouMeanTemplate = "Did you mean %q?"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
changeLogURL = "https://github.com/urfave/cli/blob/main/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)
|
||||||
@ -18,6 +21,10 @@ var (
|
|||||||
errInvalidActionType = NewExitError("ERROR invalid Action type. "+
|
errInvalidActionType = NewExitError("ERROR invalid Action type. "+
|
||||||
fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+
|
fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+
|
||||||
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
|
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
|
||||||
|
|
||||||
|
SuggestFlag SuggestFlagFunc = suggestFlag
|
||||||
|
SuggestCommand SuggestCommandFunc = suggestCommand
|
||||||
|
SuggestDidYouMeanTemplate string = suggestDidYouMeanTemplate
|
||||||
)
|
)
|
||||||
|
|
||||||
// App is the main structure of a cli application. It is recommended that
|
// App is the main structure of a cli application. It is recommended that
|
||||||
@ -37,6 +44,9 @@ type App struct {
|
|||||||
Version string
|
Version string
|
||||||
// Description of the program
|
// Description of the program
|
||||||
Description string
|
Description string
|
||||||
|
// DefaultCommand is the (optional) name of a command
|
||||||
|
// to run if no command names are passed as CLI arguments.
|
||||||
|
DefaultCommand string
|
||||||
// List of commands to execute
|
// List of commands to execute
|
||||||
Commands []*Command
|
Commands []*Command
|
||||||
// List of flags to parse
|
// List of flags to parse
|
||||||
@ -52,6 +62,8 @@ type App struct {
|
|||||||
HideVersion bool
|
HideVersion bool
|
||||||
// categories contains the categorized commands and is populated on app startup
|
// categories contains the categorized commands and is populated on app startup
|
||||||
categories CommandCategories
|
categories CommandCategories
|
||||||
|
// flagCategories contains the categorized flags and is populated on app startup
|
||||||
|
flagCategories FlagCategories
|
||||||
// An action to execute when the shell completion flag is set
|
// An action to execute when the shell completion flag is set
|
||||||
BashComplete BashCompleteFunc
|
BashComplete BashCompleteFunc
|
||||||
// An action to execute before any subcommands are run, but after the context is ready
|
// An action to execute before any subcommands are run, but after the context is ready
|
||||||
@ -94,10 +106,16 @@ type App struct {
|
|||||||
// single-character bool arguments into one
|
// single-character bool arguments into one
|
||||||
// i.e. foobar -o -v -> foobar -ov
|
// i.e. foobar -o -v -> foobar -ov
|
||||||
UseShortOptionHandling bool
|
UseShortOptionHandling bool
|
||||||
|
// Enable suggestions for commands and flags
|
||||||
|
Suggest bool
|
||||||
|
|
||||||
didSetup bool
|
didSetup bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SuggestFlagFunc func(flags []Flag, provided string, hideHelp bool) string
|
||||||
|
|
||||||
|
type SuggestCommandFunc func(commands []*Command, provided string) string
|
||||||
|
|
||||||
// Tries to find out when this binary was compiled.
|
// Tries to find out when this binary was compiled.
|
||||||
// Returns the current time if it fails to find it.
|
// Returns the current time if it fails to find it.
|
||||||
func compileTime() time.Time {
|
func compileTime() time.Time {
|
||||||
@ -181,6 +199,8 @@ func (a *App) Setup() {
|
|||||||
if c.HelpName == "" {
|
if c.HelpName == "" {
|
||||||
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
|
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.flagCategories = newFlagCategoriesFromFlags(c.Flags)
|
||||||
newCommands = append(newCommands, c)
|
newCommands = append(newCommands, c)
|
||||||
}
|
}
|
||||||
a.Commands = newCommands
|
a.Commands = newCommands
|
||||||
@ -205,6 +225,13 @@ func (a *App) Setup() {
|
|||||||
}
|
}
|
||||||
sort.Sort(a.categories.(*commandCategories))
|
sort.Sort(a.categories.(*commandCategories))
|
||||||
|
|
||||||
|
a.flagCategories = newFlagCategories()
|
||||||
|
for _, fl := range a.Flags {
|
||||||
|
if cf, ok := fl.(CategorizableFlag); ok {
|
||||||
|
a.flagCategories.AddFlag(cf.GetCategory(), cf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if a.Metadata == nil {
|
if a.Metadata == nil {
|
||||||
a.Metadata = make(map[string]interface{})
|
a.Metadata = make(map[string]interface{})
|
||||||
}
|
}
|
||||||
@ -245,48 +272,53 @@ 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)
|
if a.Suggest {
|
||||||
|
if suggestion, err := a.suggestFlagFromError(err, ""); err == nil {
|
||||||
|
fmt.Fprintf(a.Writer, suggestion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = 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,21 +329,53 @@ 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()
|
var c *Command
|
||||||
|
args := cCtx.Args()
|
||||||
if args.Present() {
|
if args.Present() {
|
||||||
name := args.First()
|
name := args.First()
|
||||||
c := a.Command(name)
|
if a.validCommandName(name) {
|
||||||
if c != nil {
|
c = a.Command(name)
|
||||||
return c.Run(context)
|
} else {
|
||||||
|
hasDefault := a.DefaultCommand != ""
|
||||||
|
isFlagName := checkStringSliceIncludes(name, cCtx.FlagNames())
|
||||||
|
|
||||||
|
var (
|
||||||
|
isDefaultSubcommand = false
|
||||||
|
defaultHasSubcommands = false
|
||||||
|
)
|
||||||
|
|
||||||
|
if hasDefault {
|
||||||
|
dc := a.Command(a.DefaultCommand)
|
||||||
|
defaultHasSubcommands = len(dc.Subcommands) > 0
|
||||||
|
for _, dcSub := range dc.Subcommands {
|
||||||
|
if checkStringSliceIncludes(name, dcSub.Names()) {
|
||||||
|
isDefaultSubcommand = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isFlagName || (hasDefault && (defaultHasSubcommands && isDefaultSubcommand)) {
|
||||||
|
argsWithDefault := a.argsWithDefaultCommand(args)
|
||||||
|
if !reflect.DeepEqual(args, argsWithDefault) {
|
||||||
|
c = a.Command(argsWithDefault.First())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else if a.DefaultCommand != "" {
|
||||||
|
c = a.Command(a.DefaultCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c != nil {
|
||||||
|
return c.Run(cCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.Action == nil {
|
if a.Action == nil {
|
||||||
@ -319,12 +383,35 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) suggestFlagFromError(err error, command string) (string, error) {
|
||||||
|
flag, parseErr := flagFromError(err)
|
||||||
|
if parseErr != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := a.Flags
|
||||||
|
if command != "" {
|
||||||
|
cmd := a.Command(command)
|
||||||
|
if cmd == nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
flags = cmd.Flags
|
||||||
|
}
|
||||||
|
|
||||||
|
suggestion := SuggestFlag(flags, flag, a.HideHelp)
|
||||||
|
if len(suggestion) == 0 {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(SuggestDidYouMeanTemplate+"\n\n", suggestion), nil
|
||||||
|
}
|
||||||
|
|
||||||
// RunAndExitOnError calls .Run() and exits non-zero if an error was returned
|
// RunAndExitOnError calls .Run() and exits non-zero if an error was returned
|
||||||
//
|
//
|
||||||
// Deprecated: instead you should return an error that fulfills cli.ExitCoder
|
// Deprecated: instead you should return an error that fulfills cli.ExitCoder
|
||||||
@ -359,55 +446,60 @@ 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)
|
if a.Suggest {
|
||||||
|
if suggestion, err := a.suggestFlagFromError(err, cCtx.Command.Name); err == nil {
|
||||||
|
fmt.Fprintf(a.Writer, suggestion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = 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 +510,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,6 +573,14 @@ func (a *App) VisibleCommands() []*Command {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VisibleFlagCategories returns a slice containing all the categories with the flags they contain
|
||||||
|
func (a *App) VisibleFlagCategories() []VisibleFlagCategory {
|
||||||
|
if a.flagCategories == nil {
|
||||||
|
return []VisibleFlagCategory{}
|
||||||
|
}
|
||||||
|
return a.flagCategories.VisibleCategories()
|
||||||
|
}
|
||||||
|
|
||||||
// VisibleFlags returns a slice of the Flags with Hidden=false
|
// VisibleFlags returns a slice of the Flags with Hidden=false
|
||||||
func (a *App) VisibleFlags() []Flag {
|
func (a *App) VisibleFlags() []Flag {
|
||||||
return visibleFlags(a.Flags)
|
return visibleFlags(a.Flags)
|
||||||
@ -498,14 +598,49 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) commandNames() []string {
|
||||||
|
var cmdNames []string
|
||||||
|
|
||||||
|
for _, cmd := range a.Commands {
|
||||||
|
cmdNames = append(cmdNames, cmd.Names()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmdNames
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) validCommandName(checkCmdName string) bool {
|
||||||
|
valid := false
|
||||||
|
allCommandNames := a.commandNames()
|
||||||
|
|
||||||
|
for _, cmdName := range allCommandNames {
|
||||||
|
if checkCmdName == cmdName {
|
||||||
|
valid = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return valid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) argsWithDefaultCommand(oldArgs Args) Args {
|
||||||
|
if a.DefaultCommand != "" {
|
||||||
|
rawArgs := append([]string{a.DefaultCommand}, oldArgs.Slice()...)
|
||||||
|
newArgs := args(rawArgs)
|
||||||
|
|
||||||
|
return &newArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
return oldArgs
|
||||||
|
}
|
||||||
|
|
||||||
// Author represents someone who has contributed to a cli project.
|
// Author represents someone who has contributed to a cli project.
|
||||||
type Author struct {
|
type Author struct {
|
||||||
Name string // The Authors name
|
Name string // The Authors name
|
||||||
@ -525,16 +660,28 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
return errInvalidActionType
|
return errInvalidActionType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkStringSliceIncludes(want string, sSlice []string) bool {
|
||||||
|
found := false
|
||||||
|
for _, s := range sSlice {
|
||||||
|
if want == s {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
228
app_test.go
228
app_test.go
@ -142,8 +142,8 @@ func ExampleApp_Run_appHelp() {
|
|||||||
// help, h Shows a list of commands or help for one command
|
// help, h Shows a list of commands or help for one command
|
||||||
//
|
//
|
||||||
// GLOBAL OPTIONS:
|
// GLOBAL OPTIONS:
|
||||||
// --name value a name to say (default: "bob")
|
|
||||||
// --help, -h show help (default: false)
|
// --help, -h show help (default: false)
|
||||||
|
// --name value a name to say (default: "bob")
|
||||||
// --version, -v print the version (default: false)
|
// --version, -v print the version (default: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,6 +228,7 @@ func ExampleApp_Run_subcommandNoAction() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ExampleApp_Run_bashComplete_withShortFlag() {
|
func ExampleApp_Run_bashComplete_withShortFlag() {
|
||||||
|
os.Setenv("SHELL", "bash")
|
||||||
os.Args = []string{"greet", "-", "--generate-bash-completion"}
|
os.Args = []string{"greet", "-", "--generate-bash-completion"}
|
||||||
|
|
||||||
app := NewApp()
|
app := NewApp()
|
||||||
@ -255,6 +256,7 @@ func ExampleApp_Run_bashComplete_withShortFlag() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ExampleApp_Run_bashComplete_withLongFlag() {
|
func ExampleApp_Run_bashComplete_withLongFlag() {
|
||||||
|
os.Setenv("SHELL", "bash")
|
||||||
os.Args = []string{"greet", "--s", "--generate-bash-completion"}
|
os.Args = []string{"greet", "--s", "--generate-bash-completion"}
|
||||||
|
|
||||||
app := NewApp()
|
app := NewApp()
|
||||||
@ -283,6 +285,7 @@ func ExampleApp_Run_bashComplete_withLongFlag() {
|
|||||||
// --similar-flag
|
// --similar-flag
|
||||||
}
|
}
|
||||||
func ExampleApp_Run_bashComplete_withMultipleLongFlag() {
|
func ExampleApp_Run_bashComplete_withMultipleLongFlag() {
|
||||||
|
os.Setenv("SHELL", "bash")
|
||||||
os.Args = []string{"greet", "--st", "--generate-bash-completion"}
|
os.Args = []string{"greet", "--st", "--generate-bash-completion"}
|
||||||
|
|
||||||
app := NewApp()
|
app := NewApp()
|
||||||
@ -315,7 +318,7 @@ func ExampleApp_Run_bashComplete_withMultipleLongFlag() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ExampleApp_Run_bashComplete() {
|
func ExampleApp_Run_bashComplete() {
|
||||||
// set args for examples sake
|
os.Setenv("SHELL", "bash")
|
||||||
os.Args = []string{"greet", "--generate-bash-completion"}
|
os.Args = []string{"greet", "--generate-bash-completion"}
|
||||||
|
|
||||||
app := &App{
|
app := &App{
|
||||||
@ -355,7 +358,7 @@ func ExampleApp_Run_bashComplete() {
|
|||||||
func ExampleApp_Run_zshComplete() {
|
func ExampleApp_Run_zshComplete() {
|
||||||
// set args for examples sake
|
// set args for examples sake
|
||||||
os.Args = []string{"greet", "--generate-bash-completion"}
|
os.Args = []string{"greet", "--generate-bash-completion"}
|
||||||
_ = os.Setenv("_CLI_ZSH_AUTOCOMPLETE_HACK", "1")
|
_ = os.Setenv("SHELL", "/usr/bin/zsh")
|
||||||
|
|
||||||
app := NewApp()
|
app := NewApp()
|
||||||
app.Name = "greet"
|
app.Name = "greet"
|
||||||
@ -390,6 +393,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 := ""
|
||||||
|
|
||||||
@ -432,6 +469,175 @@ func TestApp_Command(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var defaultCommandAppTests = []struct {
|
||||||
|
cmdName string
|
||||||
|
defaultCmd string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"foobar", "foobar", true},
|
||||||
|
{"batbaz", "foobar", true},
|
||||||
|
{"b", "", true},
|
||||||
|
{"f", "", true},
|
||||||
|
{"", "foobar", true},
|
||||||
|
{"", "", true},
|
||||||
|
{" ", "", false},
|
||||||
|
{"bat", "batbaz", false},
|
||||||
|
{"nothing", "batbaz", false},
|
||||||
|
{"nothing", "", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_RunDefaultCommand(t *testing.T) {
|
||||||
|
for _, test := range defaultCommandAppTests {
|
||||||
|
testTitle := fmt.Sprintf("command=%[1]s-default=%[2]s", test.cmdName, test.defaultCmd)
|
||||||
|
t.Run(testTitle, func(t *testing.T) {
|
||||||
|
app := &App{
|
||||||
|
DefaultCommand: test.defaultCmd,
|
||||||
|
Commands: []*Command{
|
||||||
|
{Name: "foobar", Aliases: []string{"f"}},
|
||||||
|
{Name: "batbaz", Aliases: []string{"b"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.Run([]string{"c", test.cmdName})
|
||||||
|
expect(t, err == nil, test.expected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultCommandSubCmdAppTests = []struct {
|
||||||
|
cmdName string
|
||||||
|
subCmd string
|
||||||
|
defaultCmd string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"foobar", "", "foobar", true},
|
||||||
|
{"foobar", "carly", "foobar", true},
|
||||||
|
{"batbaz", "", "foobar", true},
|
||||||
|
{"b", "", "", true},
|
||||||
|
{"f", "", "", true},
|
||||||
|
{"", "", "foobar", true},
|
||||||
|
{"", "", "", true},
|
||||||
|
{"", "jimbob", "foobar", true},
|
||||||
|
{"", "j", "foobar", true},
|
||||||
|
{"", "carly", "foobar", true},
|
||||||
|
{"", "jimmers", "foobar", true},
|
||||||
|
{"", "jimmers", "", true},
|
||||||
|
{" ", "jimmers", "foobar", false},
|
||||||
|
{"", "", "", true},
|
||||||
|
{" ", "", "", false},
|
||||||
|
{" ", "j", "", false},
|
||||||
|
{"bat", "", "batbaz", false},
|
||||||
|
{"nothing", "", "batbaz", false},
|
||||||
|
{"nothing", "", "", false},
|
||||||
|
{"nothing", "j", "batbaz", false},
|
||||||
|
{"nothing", "carly", "", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_RunDefaultCommandWithSubCommand(t *testing.T) {
|
||||||
|
for _, test := range defaultCommandSubCmdAppTests {
|
||||||
|
testTitle := fmt.Sprintf("command=%[1]s-subcmd=%[2]s-default=%[3]s", test.cmdName, test.subCmd, test.defaultCmd)
|
||||||
|
t.Run(testTitle, func(t *testing.T) {
|
||||||
|
app := &App{
|
||||||
|
DefaultCommand: test.defaultCmd,
|
||||||
|
Commands: []*Command{
|
||||||
|
{
|
||||||
|
Name: "foobar",
|
||||||
|
Aliases: []string{"f"},
|
||||||
|
Subcommands: []*Command{
|
||||||
|
{Name: "jimbob", Aliases: []string{"j"}},
|
||||||
|
{Name: "carly"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{Name: "batbaz", Aliases: []string{"b"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.Run([]string{"c", test.cmdName, test.subCmd})
|
||||||
|
expect(t, err == nil, test.expected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultCommandFlagAppTests = []struct {
|
||||||
|
cmdName string
|
||||||
|
flag string
|
||||||
|
defaultCmd string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"foobar", "", "foobar", true},
|
||||||
|
{"foobar", "-c derp", "foobar", true},
|
||||||
|
{"batbaz", "", "foobar", true},
|
||||||
|
{"b", "", "", true},
|
||||||
|
{"f", "", "", true},
|
||||||
|
{"", "", "foobar", true},
|
||||||
|
{"", "", "", true},
|
||||||
|
{"", "-j", "foobar", true},
|
||||||
|
{"", "-j", "foobar", true},
|
||||||
|
{"", "-c derp", "foobar", true},
|
||||||
|
{"", "--carly=derp", "foobar", true},
|
||||||
|
{"", "-j", "foobar", true},
|
||||||
|
{"", "-j", "", true},
|
||||||
|
{" ", "-j", "foobar", false},
|
||||||
|
{"", "", "", true},
|
||||||
|
{" ", "", "", false},
|
||||||
|
{" ", "-j", "", false},
|
||||||
|
{"bat", "", "batbaz", false},
|
||||||
|
{"nothing", "", "batbaz", false},
|
||||||
|
{"nothing", "", "", false},
|
||||||
|
{"nothing", "--jimbob", "batbaz", false},
|
||||||
|
{"nothing", "--carly", "", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_RunDefaultCommandWithFlags(t *testing.T) {
|
||||||
|
for _, test := range defaultCommandFlagAppTests {
|
||||||
|
testTitle := fmt.Sprintf("command=%[1]s-flag=%[2]s-default=%[3]s", test.cmdName, test.flag, test.defaultCmd)
|
||||||
|
t.Run(testTitle, func(t *testing.T) {
|
||||||
|
app := &App{
|
||||||
|
DefaultCommand: test.defaultCmd,
|
||||||
|
Flags: []Flag{
|
||||||
|
&StringFlag{
|
||||||
|
Name: "carly",
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
Required: false,
|
||||||
|
},
|
||||||
|
&BoolFlag{
|
||||||
|
Name: "jimbob",
|
||||||
|
Aliases: []string{"j"},
|
||||||
|
Required: false,
|
||||||
|
Value: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Commands: []*Command{
|
||||||
|
{
|
||||||
|
Name: "foobar",
|
||||||
|
Aliases: []string{"f"},
|
||||||
|
},
|
||||||
|
{Name: "batbaz", Aliases: []string{"b"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
appArgs := []string{"c"}
|
||||||
|
|
||||||
|
if test.flag != "" {
|
||||||
|
flags := strings.Split(test.flag, " ")
|
||||||
|
if len(flags) > 1 {
|
||||||
|
appArgs = append(appArgs, flags...)
|
||||||
|
}
|
||||||
|
|
||||||
|
flags = strings.Split(test.flag, "=")
|
||||||
|
if len(flags) > 1 {
|
||||||
|
appArgs = append(appArgs, flags...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
appArgs = append(appArgs, test.cmdName)
|
||||||
|
|
||||||
|
err := app.Run(appArgs)
|
||||||
|
expect(t, err == nil, test.expected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestApp_Setup_defaultsReader(t *testing.T) {
|
func TestApp_Setup_defaultsReader(t *testing.T) {
|
||||||
app := &App{}
|
app := &App{}
|
||||||
app.Setup()
|
app.Setup()
|
||||||
@ -445,14 +651,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 +674,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) {
|
||||||
@ -1890,6 +2096,14 @@ func TestApp_VisibleCategories(t *testing.T) {
|
|||||||
expect(t, []CommandCategory{}, app.VisibleCategories())
|
expect(t, []CommandCategory{}, app.VisibleCategories())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApp_VisibleFlagCategories(t *testing.T) {
|
||||||
|
app := &App{}
|
||||||
|
vfc := app.VisibleFlagCategories()
|
||||||
|
if len(vfc) != 0 {
|
||||||
|
t.Errorf("unexpected visible flag categories %+v", vfc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
|
func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
|
||||||
app := &App{
|
app := &App{
|
||||||
Action: func(c *Context) error { return nil },
|
Action: func(c *Context) error { return nil },
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
#compdef $PROG
|
#compdef $PROG
|
||||||
|
|
||||||
_cli_zsh_autocomplete() {
|
_cli_zsh_autocomplete() {
|
||||||
|
|
||||||
local -a opts
|
local -a opts
|
||||||
local cur
|
local cur
|
||||||
cur=${words[-1]}
|
cur=${words[-1]}
|
||||||
if [[ "$cur" == "-"* ]]; then
|
if [[ "$cur" == "-"* ]]; then
|
||||||
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}")
|
opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}")
|
||||||
else
|
else
|
||||||
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}")
|
opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${opts[1]}" != "" ]]; then
|
if [[ "${opts[1]}" != "" ]]; then
|
||||||
@ -16,8 +15,6 @@ _cli_zsh_autocomplete() {
|
|||||||
else
|
else
|
||||||
_files
|
_files
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compdef _cli_zsh_autocomplete $PROG
|
compdef _cli_zsh_autocomplete $PROG
|
||||||
|
94
category.go
94
category.go
@ -1,10 +1,12 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
|
import "sort"
|
||||||
|
|
||||||
// CommandCategories interface allows for category manipulation
|
// CommandCategories interface allows for category manipulation
|
||||||
type CommandCategories interface {
|
type CommandCategories interface {
|
||||||
// AddCommand adds a command to a category, creating a new category if necessary.
|
// AddCommand adds a command to a category, creating a new category if necessary.
|
||||||
AddCommand(category string, command *Command)
|
AddCommand(category string, command *Command)
|
||||||
// categories returns a copy of the category slice
|
// Categories returns a slice of categories sorted by name
|
||||||
Categories() []CommandCategory
|
Categories() []CommandCategory
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,3 +79,93 @@ func (c *commandCategory) VisibleCommands() []*Command {
|
|||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FlagCategories interface allows for category manipulation
|
||||||
|
type FlagCategories interface {
|
||||||
|
// AddFlags adds a flag to a category, creating a new category if necessary.
|
||||||
|
AddFlag(category string, fl Flag)
|
||||||
|
// VisibleCategories returns a slice of visible flag categories sorted by name
|
||||||
|
VisibleCategories() []VisibleFlagCategory
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultFlagCategories struct {
|
||||||
|
m map[string]*defaultVisibleFlagCategory
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFlagCategories() FlagCategories {
|
||||||
|
return &defaultFlagCategories{
|
||||||
|
m: map[string]*defaultVisibleFlagCategory{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFlagCategoriesFromFlags(fs []Flag) FlagCategories {
|
||||||
|
fc := newFlagCategories()
|
||||||
|
for _, fl := range fs {
|
||||||
|
if cf, ok := fl.(CategorizableFlag); ok {
|
||||||
|
fc.AddFlag(cf.GetCategory(), cf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *defaultFlagCategories) AddFlag(category string, fl Flag) {
|
||||||
|
if _, ok := f.m[category]; !ok {
|
||||||
|
f.m[category] = &defaultVisibleFlagCategory{name: category, m: map[string]Flag{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
f.m[category].m[fl.String()] = fl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *defaultFlagCategories) VisibleCategories() []VisibleFlagCategory {
|
||||||
|
catNames := []string{}
|
||||||
|
for name := range f.m {
|
||||||
|
catNames = append(catNames, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(catNames)
|
||||||
|
|
||||||
|
ret := make([]VisibleFlagCategory, len(catNames))
|
||||||
|
for i, name := range catNames {
|
||||||
|
ret[i] = f.m[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisibleFlagCategory is a category containing flags.
|
||||||
|
type VisibleFlagCategory interface {
|
||||||
|
// Name returns the category name string
|
||||||
|
Name() string
|
||||||
|
// Flags returns a slice of VisibleFlag sorted by name
|
||||||
|
Flags() []VisibleFlag
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultVisibleFlagCategory struct {
|
||||||
|
name string
|
||||||
|
m map[string]Flag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fc *defaultVisibleFlagCategory) Name() string {
|
||||||
|
return fc.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fc *defaultVisibleFlagCategory) Flags() []VisibleFlag {
|
||||||
|
vfNames := []string{}
|
||||||
|
for flName, fl := range fc.m {
|
||||||
|
if vf, ok := fl.(VisibleFlag); ok {
|
||||||
|
if vf.IsVisible() {
|
||||||
|
vfNames = append(vfNames, flName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(vfNames)
|
||||||
|
|
||||||
|
ret := make([]VisibleFlag, len(vfNames))
|
||||||
|
for i, flName := range vfNames {
|
||||||
|
ret[i] = fc.m[flName].(VisibleFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
2
cli.go
2
cli.go
@ -20,4 +20,4 @@
|
|||||||
// }
|
// }
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
//go:generate go run flag-gen/main.go flag-gen/assets_vfsdata.go
|
//go:generate go run internal/genflags/cmd/genflags/main.go
|
||||||
|
53
command.go
53
command.go
@ -38,7 +38,8 @@ type Command struct {
|
|||||||
// List of child commands
|
// List of child commands
|
||||||
Subcommands []*Command
|
Subcommands []*Command
|
||||||
// List of flags to parse
|
// List of flags to parse
|
||||||
Flags []Flag
|
Flags []Flag
|
||||||
|
flagCategories FlagCategories
|
||||||
// Treat all flags as normal arguments if true
|
// Treat all flags as normal arguments if true
|
||||||
SkipFlagParsing bool
|
SkipFlagParsing bool
|
||||||
// Boolean to hide built-in help command and help flag
|
// Boolean to hide built-in help command and help flag
|
||||||
@ -105,39 +106,44 @@ 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)
|
if ctx.App.Suggest {
|
||||||
|
if suggestion, err := ctx.App.suggestFlagFromError(err, c.Name); err == nil {
|
||||||
|
fmt.Fprintf(cCtx.App.Writer, suggestion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = ShowCommandHelp(cCtx, c.Name)
|
||||||
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 +154,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 +165,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
|
||||||
}
|
}
|
||||||
@ -249,6 +255,7 @@ func (c *Command) startApp(ctx *Context) error {
|
|||||||
app.ErrWriter = ctx.App.ErrWriter
|
app.ErrWriter = ctx.App.ErrWriter
|
||||||
app.ExitErrHandler = ctx.App.ExitErrHandler
|
app.ExitErrHandler = ctx.App.ExitErrHandler
|
||||||
app.UseShortOptionHandling = ctx.App.UseShortOptionHandling
|
app.UseShortOptionHandling = ctx.App.UseShortOptionHandling
|
||||||
|
app.Suggest = ctx.App.Suggest
|
||||||
|
|
||||||
app.categories = newCommandCategories()
|
app.categories = newCommandCategories()
|
||||||
for _, command := range c.Subcommands {
|
for _, command := range c.Subcommands {
|
||||||
@ -280,6 +287,14 @@ func (c *Command) startApp(ctx *Context) error {
|
|||||||
return app.RunAsSubcommand(ctx)
|
return app.RunAsSubcommand(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VisibleFlagCategories returns a slice containing all the visible flag categories with the flags they contain
|
||||||
|
func (c *Command) VisibleFlagCategories() []VisibleFlagCategory {
|
||||||
|
if c.flagCategories == nil {
|
||||||
|
return []VisibleFlagCategory{}
|
||||||
|
}
|
||||||
|
return c.flagCategories.VisibleCategories()
|
||||||
|
}
|
||||||
|
|
||||||
// VisibleFlags returns a slice of the Flags with Hidden=false
|
// VisibleFlags returns a slice of the Flags with Hidden=false
|
||||||
func (c *Command) VisibleFlags() []Flag {
|
func (c *Command) VisibleFlags() []Flag {
|
||||||
return visibleFlags(c.Flags)
|
return visibleFlags(c.Flags)
|
||||||
|
@ -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/).
|
||||||
|
1
docs/CNAME
Normal file
1
docs/CNAME
Normal file
@ -0,0 +1 @@
|
|||||||
|
cli.urfave.org
|
1
docs/CODE_OF_CONDUCT.md
Symbolic link
1
docs/CODE_OF_CONDUCT.md
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../CODE_OF_CONDUCT.md
|
@ -1,18 +1,143 @@
|
|||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Use @urfave/cli to ping the maintainers.
|
Welcome to the `urfave/cli` contributor docs! This goal of this document is to help those
|
||||||
|
interested in joining the 200+ humans who have contributed to this project over the years.
|
||||||
|
|
||||||
Feel free to put up a pull request to fix a bug or maybe add a feature. We will
|
> As a general guiding principle, the current maintainers may be notified via the
|
||||||
give it a code review and make sure that it does not break backwards
|
> @urfave/cli GitHub team.
|
||||||
compatibility. If collaborators agree that it is in line with
|
|
||||||
the vision of the project, we will work with you to get the code into
|
|
||||||
a mergeable state and merge it into the main branch.
|
|
||||||
|
|
||||||
If you have contributed something significant to the project, we will most
|
All of the current maintainers are *volunteers* who live in various timezones with
|
||||||
likely add you as a collaborator. As a collaborator you are given the ability
|
different scheduling needs, so please understand that your contribution or question may
|
||||||
to merge others pull requests. It is very important that new code does not
|
not get a response for many days.
|
||||||
break existing code, so be careful about what code you do choose to merge.
|
|
||||||
|
|
||||||
If you feel like you have contributed to the project but have not yet been added
|
### semantic versioning adherence
|
||||||
as a collaborator, we probably forgot to add you :sweat_smile:. Please open an
|
|
||||||
issue!
|
The `urfave/cli` project strives to strictly adhere to [semantic
|
||||||
|
versioning](https://semver.org/spec/v2.0.0.html). The active development branches and the
|
||||||
|
milestones and import paths to which they correspond are:
|
||||||
|
|
||||||
|
#### `main` branch
|
||||||
|
|
||||||
|
<https://github.com/urfave/cli/tree/main>
|
||||||
|
|
||||||
|
The majority of active development and issue management is targeting the `main` branch,
|
||||||
|
which **MUST** *only* receive bug fixes and feature *additions*.
|
||||||
|
|
||||||
|
- :arrow_right: [`v2.x`](https://github.com/urfave/cli/milestone/16)
|
||||||
|
- :arrow_right: `github.com/urfave/cli/v2`
|
||||||
|
|
||||||
|
The `main` branch in particular includes tooling to help with keeping the `v2.x` series
|
||||||
|
backward compatible. More details on this process are in the development workflow section
|
||||||
|
below.
|
||||||
|
|
||||||
|
#### `v1` branch
|
||||||
|
|
||||||
|
<https://github.com/urfave/cli/tree/v1>
|
||||||
|
|
||||||
|
The `v1` branch **MUST** only receive bug fixes in the `v1.22.x` series. There is no
|
||||||
|
strict rule regarding bug fixes to the `v2.x` series being backported to the `v1.22.x`
|
||||||
|
series.
|
||||||
|
|
||||||
|
- :arrow_right: [`v1.22.x`](https://github.com/urfave/cli/milestone/11)
|
||||||
|
- :arrow_right: `github.com/urfave/cli`
|
||||||
|
|
||||||
|
#### `v3-dev-main` branch
|
||||||
|
|
||||||
|
<https://github.com/urfave/cli/tree/v3-dev-main>
|
||||||
|
|
||||||
|
The `v3-dev-branch` **MUST** receive all bug fixes and features added to the `main` branch
|
||||||
|
and **MAY** receive feature *removals* and other changes that are otherwise
|
||||||
|
*backward-incompatible* with the `v2.x` series.
|
||||||
|
|
||||||
|
- :arrow_right: [`v3.x`](https://github.com/urfave/cli/milestone/5)
|
||||||
|
- unreleased / unsupported
|
||||||
|
|
||||||
|
### development workflow
|
||||||
|
|
||||||
|
Most of the tooling around the development workflow strives for effective
|
||||||
|
[dogfooding](https://en.wikipedia.org/wiki/Eating_your_own_dog_food). There is a top-level
|
||||||
|
`Makefile` that is maintained strictly for the purpose of easing verification of one's
|
||||||
|
development environment and any changes one may have introduced:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
Running the default `make` target (`all`) will ensure all of the critical steps are run to
|
||||||
|
verify one's changes are harmonious in nature. The same steps are also run during the
|
||||||
|
[continuous integration
|
||||||
|
phase](https://github.com/urfave/cli/blob/main/.github/workflows/cli.yml).
|
||||||
|
|
||||||
|
In the event that the `v2diff` target exits non-zero, this is a signal that the public API
|
||||||
|
surface area has changed. If the changes adhere to semantic versioning, meaning they are
|
||||||
|
*additions* or *bug fixes*, then manually running the approval step will "promote" the
|
||||||
|
current `go doc` output:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make v2approve
|
||||||
|
```
|
||||||
|
|
||||||
|
Because the `generate` step includes updating `godoc-current.txt` and
|
||||||
|
`testdata/godoc-v2.x.txt`, these changes *MUST* be part of any proposed pull request so
|
||||||
|
that reviewers have an opportunity to also make an informed decision about the "promotion"
|
||||||
|
step.
|
||||||
|
|
||||||
|
#### generated code
|
||||||
|
|
||||||
|
A significant portion of the project's source code is generated, with the goal being to
|
||||||
|
eliminate repetitive maintenance where other type-safe abstraction is impractical or
|
||||||
|
impossible with Go versions `< 1.18`. In a future where the eldest Go version supported is
|
||||||
|
`1.18.x`, there will likely be efforts to take advantage of
|
||||||
|
[generics](https://go.dev/doc/tutorial/generics).
|
||||||
|
|
||||||
|
The built-in `go generate` command is used to run the commands specified in
|
||||||
|
`//go:generate` directives. Each such command runs a file that also supports a command
|
||||||
|
line help system which may be consulted for further information, e.g.:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go run internal/genflags/cmd/genflags/main.go --help
|
||||||
|
```
|
||||||
|
|
||||||
|
#### docs output
|
||||||
|
|
||||||
|
The documentation in the `docs` directory is automatically built via `mkdocs` into a
|
||||||
|
static site and published when releases are pushed (see [RELEASING](./RELEASING/)). There
|
||||||
|
is no strict requirement to build the documentation when developing locally, but the
|
||||||
|
following `make` targets may be used if desired:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# install documentation dependencies with `pip`
|
||||||
|
make docs-deps
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# build the static site in `./site`
|
||||||
|
make docs
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# start an mkdocs development server
|
||||||
|
make serve-docs
|
||||||
|
```
|
||||||
|
|
||||||
|
### pull requests
|
||||||
|
|
||||||
|
Please feel free to open a pull request to fix a bug or add a feature. The @urfave/cli
|
||||||
|
team will review it as soon as possible, giving special attention to maintaining backward
|
||||||
|
compatibility. If the @urfave/cli team agrees that your contribution is in line with the
|
||||||
|
vision of the project, they will work with you to get the code into a mergeable state,
|
||||||
|
merged, and then released.
|
||||||
|
|
||||||
|
### granting of commit bit / admin mode
|
||||||
|
|
||||||
|
Those with a history of contributing to this project will likely be invited to join the
|
||||||
|
@urfave/cli team. As a member of the @urfave/cli team, you will have the ability to fully
|
||||||
|
administer pull requests, issues, and other repository bits.
|
||||||
|
|
||||||
|
If you feel that you should be a member of the @urfave/cli team but have not yet been
|
||||||
|
added, the most likely explanation is that this is an accidental oversight! :sweat_smile:.
|
||||||
|
Please open an issue!
|
||||||
|
|
||||||
|
<!--
|
||||||
|
vim:tw=90
|
||||||
|
-->
|
||||||
|
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.
|
27
docs/SECURITY.md
Normal file
27
docs/SECURITY.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
Hello and thank you for your interest in the `urfave/cli` security
|
||||||
|
policy! :tada: :lock:
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| ------------ | ------------------------------------- |
|
||||||
|
| `>= v2.3.x` | :white_check_mark: |
|
||||||
|
| `< v2.3` | :x: |
|
||||||
|
| `>= v1.22.x` | :white_check_mark: :lady_beetle: [^1] |
|
||||||
|
| `< v1.22` | :x: |
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
Please disclose any vulnerabilities by sending an email to:
|
||||||
|
|
||||||
|
[urfave-security@googlegroups.com](mailto:urfave-security@googlegroups.com)
|
||||||
|
|
||||||
|
You should expect a response within 48 hours and further
|
||||||
|
communications to be decided via email. The `urfave/cli` maintainer
|
||||||
|
team comprises volunteers who contribute when possible, so please
|
||||||
|
have patience :bow:
|
||||||
|
|
||||||
|
[^1]: The `v1.22.x` series will receive bug fixes and security
|
||||||
|
patches only.
|
73
docs/index.md
Normal file
73
docs/index.md
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# Welcome to urfave/cli
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://pkg.go.dev/github.com/urfave/cli/v2)
|
||||||
|
[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli)
|
||||||
|
[![codecov](https://codecov.io/gh/urfave/cli/branch/main/graph/badge.svg)](https://codecov.io/gh/urfave/cli)
|
||||||
|
|
||||||
|
`urfave/cli` is a simple, fast, and fun package for building command line apps in Go. The
|
||||||
|
goal is to enable developers to write fast and distributable command line applications in
|
||||||
|
an expressive way.
|
||||||
|
|
||||||
|
These are the guides for each major supported version:
|
||||||
|
|
||||||
|
- [`v2`](./v2/)
|
||||||
|
- [`v1`](./v1/)
|
||||||
|
|
||||||
|
In addition to the version-specific guides, these other documents are available:
|
||||||
|
|
||||||
|
- [CONTRIBUTING](./CONTRIBUTING/)
|
||||||
|
- [CODE OF CONDUCT](./CODE_OF_CONDUCT/)
|
||||||
|
- [RELEASING](./RELEASING/)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Using this package requires a working Go environment. [See the install instructions for Go](http://golang.org/doc/install.html).
|
||||||
|
|
||||||
|
Go Modules are required when using this package. [See the go blog guide on using Go Modules](https://blog.golang.org/using-go-modules).
|
||||||
|
|
||||||
|
### Using `v2` releases
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go get github.com/urfave/cli/v2
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
...
|
||||||
|
import (
|
||||||
|
"github.com/urfave/cli/v2" // imports as package "cli"
|
||||||
|
)
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using `v1` releases
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go get github.com/urfave/cli
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
...
|
||||||
|
import (
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 fewer dependencies.
|
||||||
|
|
||||||
|
### Supported platforms
|
||||||
|
|
||||||
|
cli is tested against multiple versions of Go on Linux, and against the latest
|
||||||
|
released version of Go on OS X and Windows. This project uses Github Actions
|
||||||
|
for builds. To see our currently supported go versions and platforms, look at
|
||||||
|
the [github workflow
|
||||||
|
configuration](https://github.com/urfave/cli/blob/main/.github/workflows/cli.yml).
|
@ -1,6 +1,4 @@
|
|||||||
Migration Guide: v1 to v2
|
# Migration Guide: v1 to v2
|
||||||
===
|
|
||||||
|
|
||||||
|
|
||||||
v2 has a number of breaking changes but converting is relatively
|
v2 has a number of breaking changes but converting is relatively
|
||||||
straightforward: make the changes documented below then resolve any
|
straightforward: make the changes documented below then resolve any
|
||||||
@ -11,25 +9,6 @@ If you find any issues not covered by this document, please post a
|
|||||||
comment on [Issue 921](https://github.com/urfave/cli/issues/921) or
|
comment on [Issue 921](https://github.com/urfave/cli/issues/921) or
|
||||||
consider sending a PR to help improve this guide.
|
consider sending a PR to help improve this guide.
|
||||||
|
|
||||||
<!-- toc -->
|
|
||||||
|
|
||||||
* [Flags before args](#flags-before-args)
|
|
||||||
* [Import string changed](#import-string-changed)
|
|
||||||
* [Flag aliases are done differently](#flag-aliases-are-done-differently)
|
|
||||||
* [EnvVar is now a list (EnvVars)](#envvar-is-now-a-list-envvars)
|
|
||||||
* [Actions returns errors](#actions-returns-errors)
|
|
||||||
* [cli.Flag changed](#cliflag-changed)
|
|
||||||
* [Commands are now lists of pointers](#commands-are-now-lists-of-pointers)
|
|
||||||
* [Lists of commands should be pointers](#lists-of-commands-should-be-pointers)
|
|
||||||
* [Appending Commands](#appending-commands)
|
|
||||||
* [GlobalString, GlobalBool and its likes are deprecated](#globalstring-globalbool-and-its-likes-are-deprecated)
|
|
||||||
* [BoolTFlag and BoolT are deprecated](#booltflag-and-boolt-are-deprecated)
|
|
||||||
* [&cli.StringSlice{""} replaced with cli.NewStringSlice("")](#clistringslice-replaced-with-clinewstringslice)
|
|
||||||
* [Replace deprecated functions](#replace-deprecated-functions)
|
|
||||||
* [Everything else](#everything-else)
|
|
||||||
|
|
||||||
<!-- tocstop -->
|
|
||||||
|
|
||||||
# Flags before args
|
# Flags before args
|
||||||
|
|
||||||
In v2 flags must come before args. This is more POSIX-compliant. You
|
In v2 flags must come before args. This is more POSIX-compliant. You
|
||||||
|
1
docs/v1/index.md
Symbolic link
1
docs/v1/index.md
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
manual.md
|
@ -1,35 +1,4 @@
|
|||||||
cli v1 manual
|
# v1 guide
|
||||||
===
|
|
||||||
|
|
||||||
<!-- toc -->
|
|
||||||
|
|
||||||
- [Getting Started](#getting-started)
|
|
||||||
- [Examples](#examples)
|
|
||||||
* [Arguments](#arguments)
|
|
||||||
* [Flags](#flags)
|
|
||||||
+ [Placeholder Values](#placeholder-values)
|
|
||||||
+ [Alternate Names](#alternate-names)
|
|
||||||
+ [Ordering](#ordering)
|
|
||||||
+ [Values from the Environment](#values-from-the-environment)
|
|
||||||
+ [Values from files](#values-from-files)
|
|
||||||
+ [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others)
|
|
||||||
+ [Precedence](#precedence)
|
|
||||||
* [Subcommands](#subcommands)
|
|
||||||
* [Subcommands categories](#subcommands-categories)
|
|
||||||
* [Exit code](#exit-code)
|
|
||||||
* [Combining short options](#combining-short-options)
|
|
||||||
* [Bash Completion](#bash-completion)
|
|
||||||
+ [Enabling](#enabling)
|
|
||||||
+ [Distribution](#distribution)
|
|
||||||
+ [Customization](#customization)
|
|
||||||
* [Generated Help Text](#generated-help-text)
|
|
||||||
+ [Customization](#customization-1)
|
|
||||||
* [Version Flag](#version-flag)
|
|
||||||
+ [Customization](#customization-2)
|
|
||||||
+ [Full API Example](#full-api-example)
|
|
||||||
* [Migrating to V2](#migrating-to-v2)
|
|
||||||
|
|
||||||
<!-- tocstop -->
|
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
|
1
docs/v2/index.md
Symbolic link
1
docs/v2/index.md
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
manual.md
|
1821
docs/v2/manual.md
1821
docs/v2/manual.md
File diff suppressed because it is too large
Load Diff
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))
|
||||||
|
}
|
||||||
|
51
flag-spec.yaml
Normal file
51
flag-spec.yaml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# NOTE: this file is used by the tool defined in
|
||||||
|
# ./internal/genflags/cmd/genflags/main.go which uses the
|
||||||
|
# `genflags.Spec` type that maps to this file structure.
|
||||||
|
|
||||||
|
flag_types:
|
||||||
|
bool: {}
|
||||||
|
float64: {}
|
||||||
|
int64: {}
|
||||||
|
int: {}
|
||||||
|
time.Duration: {}
|
||||||
|
uint64: {}
|
||||||
|
uint: {}
|
||||||
|
|
||||||
|
string:
|
||||||
|
struct_fields:
|
||||||
|
- { name: TakesFile, type: bool }
|
||||||
|
Generic:
|
||||||
|
struct_fields:
|
||||||
|
- { name: TakesFile, type: bool }
|
||||||
|
Path:
|
||||||
|
struct_fields:
|
||||||
|
- { name: TakesFile, type: bool }
|
||||||
|
|
||||||
|
Float64Slice:
|
||||||
|
value_pointer: true
|
||||||
|
skip_interfaces:
|
||||||
|
- fmt.Stringer
|
||||||
|
Int64Slice:
|
||||||
|
value_pointer: true
|
||||||
|
skip_interfaces:
|
||||||
|
- fmt.Stringer
|
||||||
|
IntSlice:
|
||||||
|
value_pointer: true
|
||||||
|
skip_interfaces:
|
||||||
|
- fmt.Stringer
|
||||||
|
StringSlice:
|
||||||
|
value_pointer: true
|
||||||
|
skip_interfaces:
|
||||||
|
- fmt.Stringer
|
||||||
|
struct_fields:
|
||||||
|
- { name: TakesFile, type: bool }
|
||||||
|
Timestamp:
|
||||||
|
value_pointer: true
|
||||||
|
struct_fields:
|
||||||
|
- { name: Layout, type: string }
|
||||||
|
- { name: Timezone, type: "*time.Location" }
|
||||||
|
|
||||||
|
# TODO: enable UintSlice
|
||||||
|
# UintSlice: {}
|
||||||
|
# TODO: enable Uint64Slice once #1334 lands
|
||||||
|
# Uint64Slice: {}
|
109
flag.go
109
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
|
||||||
@ -127,6 +132,14 @@ type VisibleFlag interface {
|
|||||||
IsVisible() bool
|
IsVisible() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CategorizableFlag is an interface that allows us to potentially
|
||||||
|
// use a flag in a categorized representation.
|
||||||
|
type CategorizableFlag interface {
|
||||||
|
VisibleFlag
|
||||||
|
|
||||||
|
GetCategory() string
|
||||||
|
}
|
||||||
|
|
||||||
func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
|
func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
|
||||||
set := flag.NewFlagSet(name, flag.ContinueOnError)
|
set := flag.NewFlagSet(name, flag.ContinueOnError)
|
||||||
|
|
||||||
@ -238,7 +251,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 := ", $"
|
||||||
@ -253,7 +266,7 @@ func withEnvHint(envVars []string, str string) string {
|
|||||||
return str + envText
|
return str + envText
|
||||||
}
|
}
|
||||||
|
|
||||||
func flagNames(name string, aliases []string) []string {
|
func FlagNames(name string, aliases []string) []string {
|
||||||
var ret []string
|
var ret []string
|
||||||
|
|
||||||
for _, part := range append([]string{name}, aliases...) {
|
for _, part := range append([]string{name}, aliases...) {
|
||||||
@ -267,17 +280,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 +288,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 {
|
||||||
@ -426,19 +394,26 @@ func hasFlag(flags []Flag, fl Flag) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func flagFromEnvOrFile(envVars []string, filePath string) (val string, ok bool) {
|
// Return the first value from a list of environment variables and files
|
||||||
|
// (which may or may not exist), a description of where the value was found,
|
||||||
|
// and a boolean which is true if a value was found.
|
||||||
|
func flagFromEnvOrFile(envVars []string, filePath string) (value string, fromWhere string, found bool) {
|
||||||
for _, envVar := range envVars {
|
for _, envVar := range envVars {
|
||||||
envVar = strings.TrimSpace(envVar)
|
envVar = strings.TrimSpace(envVar)
|
||||||
if val, ok := syscall.Getenv(envVar); ok {
|
if value, found := syscall.Getenv(envVar); found {
|
||||||
return val, true
|
return value, fmt.Sprintf("environment variable %q", envVar), true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, fileVar := range strings.Split(filePath, ",") {
|
for _, fileVar := range strings.Split(filePath, ",") {
|
||||||
if fileVar != "" {
|
if fileVar != "" {
|
||||||
if data, err := ioutil.ReadFile(fileVar); err == nil {
|
if data, err := ioutil.ReadFile(fileVar); err == nil {
|
||||||
return string(data), true
|
return string(data), fmt.Sprintf("file %q", filePath), true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", false
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func flagSplitMultiValues(val string) []string {
|
||||||
|
return strings.Split(val, ",")
|
||||||
}
|
}
|
||||||
|
68
flag_bool.go
68
flag_bool.go
@ -6,42 +6,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BoolFlag is a flag with type bool
|
|
||||||
type BoolFlag struct {
|
|
||||||
Name string
|
|
||||||
Aliases []string
|
|
||||||
Usage string
|
|
||||||
EnvVars []string
|
|
||||||
FilePath string
|
|
||||||
Required bool
|
|
||||||
Hidden bool
|
|
||||||
Value bool
|
|
||||||
DefaultText string
|
|
||||||
Destination *bool
|
|
||||||
HasBeenSet bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSet returns whether or not the flag has been set through env or file
|
|
||||||
func (f *BoolFlag) IsSet() bool {
|
|
||||||
return f.HasBeenSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f *BoolFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Names returns the names of the flag
|
|
||||||
func (f *BoolFlag) Names() []string {
|
|
||||||
return flagNames(f.Name, f.Aliases)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRequired returns whether or not the flag is required
|
|
||||||
func (f *BoolFlag) IsRequired() bool {
|
|
||||||
return f.Required
|
|
||||||
}
|
|
||||||
|
|
||||||
// TakesValue returns true of the flag takes a value, otherwise false
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
func (f *BoolFlag) TakesValue() bool {
|
func (f *BoolFlag) TakesValue() bool {
|
||||||
return false
|
return false
|
||||||
@ -52,25 +16,38 @@ func (f *BoolFlag) GetUsage() string {
|
|||||||
return f.Usage
|
return f.Usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCategory returns the category for the flag
|
||||||
|
func (f *BoolFlag) GetCategory() string {
|
||||||
|
return f.Category
|
||||||
|
}
|
||||||
|
|
||||||
// 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 *BoolFlag) GetValue() string {
|
func (f *BoolFlag) GetValue() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
// GetDefaultText returns the default text for this flag
|
||||||
func (f *BoolFlag) IsVisible() bool {
|
func (f *BoolFlag) GetDefaultText() string {
|
||||||
return !f.Hidden
|
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, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
|
||||||
if val != "" {
|
if val != "" {
|
||||||
valBool, err := strconv.ParseBool(val)
|
valBool, err := strconv.ParseBool(val)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not parse %q as bool value for flag %s: %s", val, f.Name, err)
|
return fmt.Errorf("could not parse %q as bool value from %s for flag %s: %s", val, source, f.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Value = valBool
|
f.Value = valBool
|
||||||
@ -89,10 +66,15 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get returns the flag’s value in the given Context.
|
||||||
|
func (f *BoolFlag) Get(ctx *Context) bool {
|
||||||
|
return ctx.Bool(f.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -6,42 +6,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration)
|
|
||||||
type DurationFlag struct {
|
|
||||||
Name string
|
|
||||||
Aliases []string
|
|
||||||
Usage string
|
|
||||||
EnvVars []string
|
|
||||||
FilePath string
|
|
||||||
Required bool
|
|
||||||
Hidden bool
|
|
||||||
Value time.Duration
|
|
||||||
DefaultText string
|
|
||||||
Destination *time.Duration
|
|
||||||
HasBeenSet bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSet returns whether or not the flag has been set through env or file
|
|
||||||
func (f *DurationFlag) IsSet() bool {
|
|
||||||
return f.HasBeenSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f *DurationFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Names returns the names of the flag
|
|
||||||
func (f *DurationFlag) Names() []string {
|
|
||||||
return flagNames(f.Name, f.Aliases)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRequired returns whether or not the flag is required
|
|
||||||
func (f *DurationFlag) IsRequired() bool {
|
|
||||||
return f.Required
|
|
||||||
}
|
|
||||||
|
|
||||||
// TakesValue returns true of the flag takes a value, otherwise false
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
func (f *DurationFlag) TakesValue() bool {
|
func (f *DurationFlag) TakesValue() bool {
|
||||||
return true
|
return true
|
||||||
@ -52,25 +16,38 @@ func (f *DurationFlag) GetUsage() string {
|
|||||||
return f.Usage
|
return f.Usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCategory returns the category for the flag
|
||||||
|
func (f *DurationFlag) GetCategory() string {
|
||||||
|
return f.Category
|
||||||
|
}
|
||||||
|
|
||||||
// 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 *DurationFlag) GetValue() string {
|
func (f *DurationFlag) GetValue() string {
|
||||||
return f.Value.String()
|
return f.Value.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
// GetDefaultText returns the default text for this flag
|
||||||
func (f *DurationFlag) IsVisible() bool {
|
func (f *DurationFlag) GetDefaultText() string {
|
||||||
return !f.Hidden
|
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, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
|
||||||
if val != "" {
|
if val != "" {
|
||||||
valDuration, err := time.ParseDuration(val)
|
valDuration, err := time.ParseDuration(val)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not parse %q as duration value for flag %s: %s", val, f.Name, err)
|
return fmt.Errorf("could not parse %q as duration value from %s for flag %s: %s", val, source, f.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Value = valDuration
|
f.Value = valDuration
|
||||||
@ -88,10 +65,15 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get returns the flag’s value in the given Context.
|
||||||
|
func (f *DurationFlag) Get(ctx *Context) time.Duration {
|
||||||
|
return ctx.Duration(f.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -6,42 +6,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Float64Flag is a flag with type float64
|
|
||||||
type Float64Flag struct {
|
|
||||||
Name string
|
|
||||||
Aliases []string
|
|
||||||
Usage string
|
|
||||||
EnvVars []string
|
|
||||||
FilePath string
|
|
||||||
Required bool
|
|
||||||
Hidden bool
|
|
||||||
Value float64
|
|
||||||
DefaultText string
|
|
||||||
Destination *float64
|
|
||||||
HasBeenSet bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSet returns whether or not the flag has been set through env or file
|
|
||||||
func (f *Float64Flag) IsSet() bool {
|
|
||||||
return f.HasBeenSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f *Float64Flag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Names returns the names of the flag
|
|
||||||
func (f *Float64Flag) Names() []string {
|
|
||||||
return flagNames(f.Name, f.Aliases)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRequired returns whether or not the flag is required
|
|
||||||
func (f *Float64Flag) IsRequired() bool {
|
|
||||||
return f.Required
|
|
||||||
}
|
|
||||||
|
|
||||||
// TakesValue returns true of the flag takes a value, otherwise false
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
func (f *Float64Flag) TakesValue() bool {
|
func (f *Float64Flag) TakesValue() bool {
|
||||||
return true
|
return true
|
||||||
@ -52,24 +16,37 @@ func (f *Float64Flag) GetUsage() string {
|
|||||||
return f.Usage
|
return f.Usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCategory returns the category for the flag
|
||||||
|
func (f *Float64Flag) GetCategory() string {
|
||||||
|
return f.Category
|
||||||
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
// GetDefaultText returns the default text for this flag
|
||||||
func (f *Float64Flag) IsVisible() bool {
|
func (f *Float64Flag) GetDefaultText() string {
|
||||||
return !f.Hidden
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
func (f *Float64Flag) Apply(set *flag.FlagSet) error {
|
func (f *Float64Flag) Apply(set *flag.FlagSet) error {
|
||||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
|
||||||
if val != "" {
|
if val != "" {
|
||||||
valFloat, err := strconv.ParseFloat(val, 64)
|
valFloat, err := strconv.ParseFloat(val, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not parse %q as float64 value for flag %s: %s", val, f.Name, err)
|
return fmt.Errorf("could not parse %q as float64 value from %s for flag %s: %s", val, source, f.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Value = valFloat
|
f.Value = valFloat
|
||||||
@ -88,10 +65,15 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get returns the flag’s value in the given Context.
|
||||||
|
func (f *Float64Flag) Get(ctx *Context) float64 {
|
||||||
|
return ctx.Float64(f.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// 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,18 +43,25 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns a readable representation of this value (for usage defaults)
|
// String returns a readable representation of this value (for usage defaults)
|
||||||
func (f *Float64Slice) String() string {
|
func (f *Float64Slice) String() string {
|
||||||
return fmt.Sprintf("%#v", f.slice)
|
v := f.slice
|
||||||
|
if v == nil {
|
||||||
|
// treat nil the same as zero length non-nil
|
||||||
|
v = make([]float64, 0)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%#v", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize allows Float64Slice to fulfill Serializer
|
// Serialize allows Float64Slice to fulfill Serializer
|
||||||
@ -73,39 +80,10 @@ func (f *Float64Slice) Get() interface{} {
|
|||||||
return *f
|
return *f
|
||||||
}
|
}
|
||||||
|
|
||||||
// Float64SliceFlag is a flag with type *Float64Slice
|
|
||||||
type Float64SliceFlag struct {
|
|
||||||
Name string
|
|
||||||
Aliases []string
|
|
||||||
Usage string
|
|
||||||
EnvVars []string
|
|
||||||
FilePath string
|
|
||||||
Required bool
|
|
||||||
Hidden bool
|
|
||||||
Value *Float64Slice
|
|
||||||
DefaultText string
|
|
||||||
HasBeenSet bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSet returns whether or not the flag has been set through env or file
|
|
||||||
func (f *Float64SliceFlag) IsSet() bool {
|
|
||||||
return f.HasBeenSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
func (f *Float64SliceFlag) Names() []string {
|
|
||||||
return flagNames(f.Name, f.Aliases)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRequired returns whether or not the flag is required
|
|
||||||
func (f *Float64SliceFlag) IsRequired() bool {
|
|
||||||
return f.Required
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TakesValue returns true if the flag takes a value, otherwise false
|
// TakesValue returns true if the flag takes a value, otherwise false
|
||||||
@ -118,6 +96,11 @@ func (f *Float64SliceFlag) GetUsage() string {
|
|||||||
return f.Usage
|
return f.Usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCategory returns the category for the flag
|
||||||
|
func (f *Float64SliceFlag) GetCategory() string {
|
||||||
|
return f.Category
|
||||||
|
}
|
||||||
|
|
||||||
// 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 *Float64SliceFlag) GetValue() string {
|
func (f *Float64SliceFlag) GetValue() string {
|
||||||
@ -127,45 +110,69 @@ func (f *Float64SliceFlag) GetValue() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
// GetDefaultText returns the default text for this flag
|
||||||
func (f *Float64SliceFlag) IsVisible() bool {
|
func (f *Float64SliceFlag) GetDefaultText() string {
|
||||||
return !f.Hidden
|
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 {
|
// apply any default
|
||||||
if val != "" {
|
if f.Destination != nil && f.Value != nil {
|
||||||
f.Value = &Float64Slice{}
|
f.Destination.slice = make([]float64, len(f.Value.slice))
|
||||||
|
copy(f.Destination.slice, f.Value.slice)
|
||||||
|
}
|
||||||
|
|
||||||
for _, s := range strings.Split(val, ",") {
|
// resolve setValue (what we will assign to the set)
|
||||||
if err := f.Value.Set(strings.TrimSpace(s)); err != nil {
|
var setValue *Float64Slice
|
||||||
return fmt.Errorf("could not parse %q as float64 slice value for flag %s: %s", f.Value, f.Name, err)
|
switch {
|
||||||
|
case f.Destination != nil:
|
||||||
|
setValue = f.Destination
|
||||||
|
case f.Value != nil:
|
||||||
|
setValue = f.Value.clone()
|
||||||
|
default:
|
||||||
|
setValue = new(Float64Slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
|
||||||
|
if val != "" {
|
||||||
|
for _, s := range flagSplitMultiValues(val) {
|
||||||
|
if err := setValue.Set(strings.TrimSpace(s)); err != nil {
|
||||||
|
return fmt.Errorf("could not parse %q as float64 slice value from %s for flag %s: %s", val, source, f.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set this to false so that we reset the slice if we then set values from
|
// Set this to false so that we reset the slice if we then set values from
|
||||||
// flags that have already been set by the environment.
|
// flags that have already been set by the environment.
|
||||||
f.Value.hasBeenSet = false
|
setValue.hasBeenSet = false
|
||||||
f.HasBeenSet = true
|
f.HasBeenSet = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Value == nil {
|
|
||||||
f.Value = &Float64Slice{}
|
|
||||||
}
|
|
||||||
copyValue := f.Value.clone()
|
|
||||||
for _, name := range f.Names() {
|
for _, name := range f.Names() {
|
||||||
set.Var(copyValue, name, f.Usage)
|
set.Var(setValue, name, f.Usage)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get returns the flag’s value in the given Context.
|
||||||
|
func (f *Float64SliceFlag) Get(ctx *Context) []float64 {
|
||||||
|
return ctx.Float64Slice(f.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@ -174,7 +181,7 @@ func (c *Context) Float64Slice(name string) []float64 {
|
|||||||
func lookupFloat64Slice(name string, set *flag.FlagSet) []float64 {
|
func lookupFloat64Slice(name string, set *flag.FlagSet) []float64 {
|
||||||
f := set.Lookup(name)
|
f := set.Lookup(name)
|
||||||
if f != nil {
|
if f != nil {
|
||||||
if slice, ok := f.Value.(*Float64Slice); ok {
|
if slice, ok := unwrapFlagValue(f.Value).(*Float64Slice); ok {
|
||||||
return slice.Value()
|
return slice.Value()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,42 +11,6 @@ type Generic interface {
|
|||||||
String() string
|
String() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenericFlag is a flag with type Generic
|
|
||||||
type GenericFlag struct {
|
|
||||||
Name string
|
|
||||||
Aliases []string
|
|
||||||
Usage string
|
|
||||||
EnvVars []string
|
|
||||||
FilePath string
|
|
||||||
Required bool
|
|
||||||
Hidden bool
|
|
||||||
TakesFile bool
|
|
||||||
Value Generic
|
|
||||||
DefaultText string
|
|
||||||
HasBeenSet bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSet returns whether or not the flag has been set through env or file
|
|
||||||
func (f *GenericFlag) IsSet() bool {
|
|
||||||
return f.HasBeenSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f *GenericFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Names returns the names of the flag
|
|
||||||
func (f *GenericFlag) Names() []string {
|
|
||||||
return flagNames(f.Name, f.Aliases)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRequired returns whether or not the flag is required
|
|
||||||
func (f *GenericFlag) IsRequired() bool {
|
|
||||||
return f.Required
|
|
||||||
}
|
|
||||||
|
|
||||||
// TakesValue returns true of the flag takes a value, otherwise false
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
func (f *GenericFlag) TakesValue() bool {
|
func (f *GenericFlag) TakesValue() bool {
|
||||||
return true
|
return true
|
||||||
@ -57,6 +21,11 @@ func (f *GenericFlag) GetUsage() string {
|
|||||||
return f.Usage
|
return f.Usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCategory returns the category for the flag
|
||||||
|
func (f *GenericFlag) GetCategory() string {
|
||||||
|
return f.Category
|
||||||
|
}
|
||||||
|
|
||||||
// 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 *GenericFlag) GetValue() string {
|
func (f *GenericFlag) GetValue() string {
|
||||||
@ -66,18 +35,26 @@ func (f *GenericFlag) GetValue() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
// GetDefaultText returns the default text for this flag
|
||||||
func (f *GenericFlag) IsVisible() bool {
|
func (f *GenericFlag) GetDefaultText() string {
|
||||||
return !f.Hidden
|
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 {
|
||||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
|
||||||
if val != "" {
|
if val != "" {
|
||||||
if err := f.Value.Set(val); err != nil {
|
if err := f.Value.Set(val); err != nil {
|
||||||
return fmt.Errorf("could not parse %q as value for flag %s: %s", val, f.Name, err)
|
return fmt.Errorf("could not parse %q from %s as value for flag %s: %s", val, source, f.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.HasBeenSet = true
|
f.HasBeenSet = true
|
||||||
@ -91,10 +68,15 @@ func (f GenericFlag) Apply(set *flag.FlagSet) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get returns the flag’s value in the given Context.
|
||||||
|
func (f *GenericFlag) Get(ctx *Context) interface{} {
|
||||||
|
return ctx.Generic(f.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
68
flag_int.go
68
flag_int.go
@ -6,42 +6,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IntFlag is a flag with type int
|
|
||||||
type IntFlag struct {
|
|
||||||
Name string
|
|
||||||
Aliases []string
|
|
||||||
Usage string
|
|
||||||
EnvVars []string
|
|
||||||
FilePath string
|
|
||||||
Required bool
|
|
||||||
Hidden bool
|
|
||||||
Value int
|
|
||||||
DefaultText string
|
|
||||||
Destination *int
|
|
||||||
HasBeenSet bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSet returns whether or not the flag has been set through env or file
|
|
||||||
func (f *IntFlag) IsSet() bool {
|
|
||||||
return f.HasBeenSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f *IntFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Names returns the names of the flag
|
|
||||||
func (f *IntFlag) Names() []string {
|
|
||||||
return flagNames(f.Name, f.Aliases)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRequired returns whether or not the flag is required
|
|
||||||
func (f *IntFlag) IsRequired() bool {
|
|
||||||
return f.Required
|
|
||||||
}
|
|
||||||
|
|
||||||
// TakesValue returns true of the flag takes a value, otherwise false
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
func (f *IntFlag) TakesValue() bool {
|
func (f *IntFlag) TakesValue() bool {
|
||||||
return true
|
return true
|
||||||
@ -52,25 +16,38 @@ func (f *IntFlag) GetUsage() string {
|
|||||||
return f.Usage
|
return f.Usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCategory returns the category for the flag
|
||||||
|
func (f *IntFlag) GetCategory() string {
|
||||||
|
return f.Category
|
||||||
|
}
|
||||||
|
|
||||||
// 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 *IntFlag) GetValue() string {
|
func (f *IntFlag) GetValue() string {
|
||||||
return fmt.Sprintf("%d", f.Value)
|
return fmt.Sprintf("%d", f.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
// GetDefaultText returns the default text for this flag
|
||||||
func (f *IntFlag) IsVisible() bool {
|
func (f *IntFlag) GetDefaultText() string {
|
||||||
return !f.Hidden
|
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, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
|
||||||
if val != "" {
|
if val != "" {
|
||||||
valInt, err := strconv.ParseInt(val, 0, 64)
|
valInt, err := strconv.ParseInt(val, 0, 64)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not parse %q as int value for flag %s: %s", val, f.Name, err)
|
return fmt.Errorf("could not parse %q as int value from %s for flag %s: %s", val, source, f.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Value = int(valInt)
|
f.Value = int(valInt)
|
||||||
@ -89,10 +66,15 @@ func (f *IntFlag) Apply(set *flag.FlagSet) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get returns the flag’s value in the given Context.
|
||||||
|
func (f *IntFlag) Get(ctx *Context) int {
|
||||||
|
return ctx.Int(f.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -6,42 +6,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Int64Flag is a flag with type int64
|
|
||||||
type Int64Flag struct {
|
|
||||||
Name string
|
|
||||||
Aliases []string
|
|
||||||
Usage string
|
|
||||||
EnvVars []string
|
|
||||||
FilePath string
|
|
||||||
Required bool
|
|
||||||
Hidden bool
|
|
||||||
Value int64
|
|
||||||
DefaultText string
|
|
||||||
Destination *int64
|
|
||||||
HasBeenSet bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSet returns whether or not the flag has been set through env or file
|
|
||||||
func (f *Int64Flag) IsSet() bool {
|
|
||||||
return f.HasBeenSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f *Int64Flag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Names returns the names of the flag
|
|
||||||
func (f *Int64Flag) Names() []string {
|
|
||||||
return flagNames(f.Name, f.Aliases)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRequired returns whether or not the flag is required
|
|
||||||
func (f *Int64Flag) IsRequired() bool {
|
|
||||||
return f.Required
|
|
||||||
}
|
|
||||||
|
|
||||||
// TakesValue returns true of the flag takes a value, otherwise false
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
func (f *Int64Flag) TakesValue() bool {
|
func (f *Int64Flag) TakesValue() bool {
|
||||||
return true
|
return true
|
||||||
@ -52,25 +16,38 @@ func (f *Int64Flag) GetUsage() string {
|
|||||||
return f.Usage
|
return f.Usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCategory returns the category for the flag
|
||||||
|
func (f *Int64Flag) GetCategory() string {
|
||||||
|
return f.Category
|
||||||
|
}
|
||||||
|
|
||||||
// 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 *Int64Flag) GetValue() string {
|
func (f *Int64Flag) GetValue() string {
|
||||||
return fmt.Sprintf("%d", f.Value)
|
return fmt.Sprintf("%d", f.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
// GetDefaultText returns the default text for this flag
|
||||||
func (f *Int64Flag) IsVisible() bool {
|
func (f *Int64Flag) GetDefaultText() string {
|
||||||
return !f.Hidden
|
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, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
|
||||||
if val != "" {
|
if val != "" {
|
||||||
valInt, err := strconv.ParseInt(val, 0, 64)
|
valInt, err := strconv.ParseInt(val, 0, 64)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not parse %q as int value for flag %s: %s", val, f.Name, err)
|
return fmt.Errorf("could not parse %q as int value from %s for flag %s: %s", val, source, f.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Value = valInt
|
f.Value = valInt
|
||||||
@ -88,10 +65,15 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get returns the flag’s value in the given Context.
|
||||||
|
func (f *Int64Flag) Get(ctx *Context) int64 {
|
||||||
|
return ctx.Int64(f.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// 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,19 +43,26 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns a readable representation of this value (for usage defaults)
|
// String returns a readable representation of this value (for usage defaults)
|
||||||
func (i *Int64Slice) String() string {
|
func (i *Int64Slice) String() string {
|
||||||
return fmt.Sprintf("%#v", i.slice)
|
v := i.slice
|
||||||
|
if v == nil {
|
||||||
|
// treat nil the same as zero length non-nil
|
||||||
|
v = make([]int64, 0)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%#v", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize allows Int64Slice to fulfill Serializer
|
// Serialize allows Int64Slice to fulfill Serializer
|
||||||
@ -74,39 +81,10 @@ func (i *Int64Slice) Get() interface{} {
|
|||||||
return *i
|
return *i
|
||||||
}
|
}
|
||||||
|
|
||||||
// Int64SliceFlag is a flag with type *Int64Slice
|
|
||||||
type Int64SliceFlag struct {
|
|
||||||
Name string
|
|
||||||
Aliases []string
|
|
||||||
Usage string
|
|
||||||
EnvVars []string
|
|
||||||
FilePath string
|
|
||||||
Required bool
|
|
||||||
Hidden bool
|
|
||||||
Value *Int64Slice
|
|
||||||
DefaultText string
|
|
||||||
HasBeenSet bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSet returns whether or not the flag has been set through env or file
|
|
||||||
func (f *Int64SliceFlag) IsSet() bool {
|
|
||||||
return f.HasBeenSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
func (f *Int64SliceFlag) Names() []string {
|
|
||||||
return flagNames(f.Name, f.Aliases)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRequired returns whether or not the flag is required
|
|
||||||
func (f *Int64SliceFlag) IsRequired() bool {
|
|
||||||
return f.Required
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TakesValue returns true of the flag takes a value, otherwise false
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
@ -115,10 +93,15 @@ func (f *Int64SliceFlag) TakesValue() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetUsage returns the usage string for the flag
|
// GetUsage returns the usage string for the flag
|
||||||
func (f Int64SliceFlag) GetUsage() string {
|
func (f *Int64SliceFlag) GetUsage() string {
|
||||||
return f.Usage
|
return f.Usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCategory returns the category for the flag
|
||||||
|
func (f *Int64SliceFlag) GetCategory() string {
|
||||||
|
return f.Category
|
||||||
|
}
|
||||||
|
|
||||||
// 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 *Int64SliceFlag) GetValue() string {
|
func (f *Int64SliceFlag) GetValue() string {
|
||||||
@ -128,43 +111,67 @@ func (f *Int64SliceFlag) GetValue() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
// GetDefaultText returns the default text for this flag
|
||||||
func (f *Int64SliceFlag) IsVisible() bool {
|
func (f *Int64SliceFlag) GetDefaultText() string {
|
||||||
return !f.Hidden
|
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 {
|
// apply any default
|
||||||
f.Value = &Int64Slice{}
|
if f.Destination != nil && f.Value != nil {
|
||||||
|
f.Destination.slice = make([]int64, len(f.Value.slice))
|
||||||
|
copy(f.Destination.slice, f.Value.slice)
|
||||||
|
}
|
||||||
|
|
||||||
for _, s := range strings.Split(val, ",") {
|
// resolve setValue (what we will assign to the set)
|
||||||
if err := f.Value.Set(strings.TrimSpace(s)); err != nil {
|
var setValue *Int64Slice
|
||||||
return fmt.Errorf("could not parse %q as int64 slice value for flag %s: %s", val, f.Name, err)
|
switch {
|
||||||
|
case f.Destination != nil:
|
||||||
|
setValue = f.Destination
|
||||||
|
case f.Value != nil:
|
||||||
|
setValue = f.Value.clone()
|
||||||
|
default:
|
||||||
|
setValue = new(Int64Slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, source, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok && val != "" {
|
||||||
|
for _, s := range flagSplitMultiValues(val) {
|
||||||
|
if err := setValue.Set(strings.TrimSpace(s)); err != nil {
|
||||||
|
return fmt.Errorf("could not parse %q as int64 slice value from %s for flag %s: %s", val, source, f.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set this to false so that we reset the slice if we then set values from
|
// Set this to false so that we reset the slice if we then set values from
|
||||||
// flags that have already been set by the environment.
|
// flags that have already been set by the environment.
|
||||||
f.Value.hasBeenSet = false
|
setValue.hasBeenSet = false
|
||||||
f.HasBeenSet = true
|
f.HasBeenSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Value == nil {
|
|
||||||
f.Value = &Int64Slice{}
|
|
||||||
}
|
|
||||||
copyValue := f.Value.clone()
|
|
||||||
for _, name := range f.Names() {
|
for _, name := range f.Names() {
|
||||||
set.Var(copyValue, name, f.Usage)
|
set.Var(setValue, name, f.Usage)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get returns the flag’s value in the given Context.
|
||||||
|
func (f *Int64SliceFlag) Get(ctx *Context) []int64 {
|
||||||
|
return ctx.Int64Slice(f.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@ -173,7 +180,7 @@ func (c *Context) Int64Slice(name string) []int64 {
|
|||||||
func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {
|
func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {
|
||||||
f := set.Lookup(name)
|
f := set.Lookup(name)
|
||||||
if f != nil {
|
if f != nil {
|
||||||
if slice, ok := f.Value.(*Int64Slice); ok {
|
if slice, ok := unwrapFlagValue(f.Value).(*Int64Slice); ok {
|
||||||
return slice.Value()
|
return slice.Value()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,19 +54,26 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns a readable representation of this value (for usage defaults)
|
// String returns a readable representation of this value (for usage defaults)
|
||||||
func (i *IntSlice) String() string {
|
func (i *IntSlice) String() string {
|
||||||
return fmt.Sprintf("%#v", i.slice)
|
v := i.slice
|
||||||
|
if v == nil {
|
||||||
|
// treat nil the same as zero length non-nil
|
||||||
|
v = make([]int, 0)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%#v", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize allows IntSlice to fulfill Serializer
|
// Serialize allows IntSlice to fulfill Serializer
|
||||||
@ -85,39 +92,10 @@ func (i *IntSlice) Get() interface{} {
|
|||||||
return *i
|
return *i
|
||||||
}
|
}
|
||||||
|
|
||||||
// IntSliceFlag is a flag with type *IntSlice
|
|
||||||
type IntSliceFlag struct {
|
|
||||||
Name string
|
|
||||||
Aliases []string
|
|
||||||
Usage string
|
|
||||||
EnvVars []string
|
|
||||||
FilePath string
|
|
||||||
Required bool
|
|
||||||
Hidden bool
|
|
||||||
Value *IntSlice
|
|
||||||
DefaultText string
|
|
||||||
HasBeenSet bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSet returns whether or not the flag has been set through env or file
|
|
||||||
func (f *IntSliceFlag) IsSet() bool {
|
|
||||||
return f.HasBeenSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
func (f *IntSliceFlag) Names() []string {
|
|
||||||
return flagNames(f.Name, f.Aliases)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRequired returns whether or not the flag is required
|
|
||||||
func (f *IntSliceFlag) IsRequired() bool {
|
|
||||||
return f.Required
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TakesValue returns true of the flag takes a value, otherwise false
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
@ -126,10 +104,15 @@ func (f *IntSliceFlag) TakesValue() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetUsage returns the usage string for the flag
|
// GetUsage returns the usage string for the flag
|
||||||
func (f IntSliceFlag) GetUsage() string {
|
func (f *IntSliceFlag) GetUsage() string {
|
||||||
return f.Usage
|
return f.Usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCategory returns the category for the flag
|
||||||
|
func (f *IntSliceFlag) GetCategory() string {
|
||||||
|
return f.Category
|
||||||
|
}
|
||||||
|
|
||||||
// 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 *IntSliceFlag) GetValue() string {
|
func (f *IntSliceFlag) GetValue() string {
|
||||||
@ -139,43 +122,67 @@ func (f *IntSliceFlag) GetValue() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
// GetDefaultText returns the default text for this flag
|
||||||
func (f *IntSliceFlag) IsVisible() bool {
|
func (f *IntSliceFlag) GetDefaultText() string {
|
||||||
return !f.Hidden
|
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 {
|
// apply any default
|
||||||
f.Value = &IntSlice{}
|
if f.Destination != nil && f.Value != nil {
|
||||||
|
f.Destination.slice = make([]int, len(f.Value.slice))
|
||||||
|
copy(f.Destination.slice, f.Value.slice)
|
||||||
|
}
|
||||||
|
|
||||||
for _, s := range strings.Split(val, ",") {
|
// resolve setValue (what we will assign to the set)
|
||||||
if err := f.Value.Set(strings.TrimSpace(s)); err != nil {
|
var setValue *IntSlice
|
||||||
return fmt.Errorf("could not parse %q as int slice value for flag %s: %s", val, f.Name, err)
|
switch {
|
||||||
|
case f.Destination != nil:
|
||||||
|
setValue = f.Destination
|
||||||
|
case f.Value != nil:
|
||||||
|
setValue = f.Value.clone()
|
||||||
|
default:
|
||||||
|
setValue = new(IntSlice)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, source, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok && val != "" {
|
||||||
|
for _, s := range flagSplitMultiValues(val) {
|
||||||
|
if err := setValue.Set(strings.TrimSpace(s)); err != nil {
|
||||||
|
return fmt.Errorf("could not parse %q as int slice value from %s for flag %s: %s", val, source, f.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set this to false so that we reset the slice if we then set values from
|
// Set this to false so that we reset the slice if we then set values from
|
||||||
// flags that have already been set by the environment.
|
// flags that have already been set by the environment.
|
||||||
f.Value.hasBeenSet = false
|
setValue.hasBeenSet = false
|
||||||
f.HasBeenSet = true
|
f.HasBeenSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Value == nil {
|
|
||||||
f.Value = &IntSlice{}
|
|
||||||
}
|
|
||||||
copyValue := f.Value.clone()
|
|
||||||
for _, name := range f.Names() {
|
for _, name := range f.Names() {
|
||||||
set.Var(copyValue, name, f.Usage)
|
set.Var(setValue, name, f.Usage)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get returns the flag’s value in the given Context.
|
||||||
|
func (f *IntSliceFlag) Get(ctx *Context) []int {
|
||||||
|
return ctx.IntSlice(f.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@ -184,7 +191,7 @@ func (c *Context) IntSlice(name string) []int {
|
|||||||
func lookupIntSlice(name string, set *flag.FlagSet) []int {
|
func lookupIntSlice(name string, set *flag.FlagSet) []int {
|
||||||
f := set.Lookup(name)
|
f := set.Lookup(name)
|
||||||
if f != nil {
|
if f != nil {
|
||||||
if slice, ok := f.Value.(*IntSlice); ok {
|
if slice, ok := unwrapFlagValue(f.Value).(*IntSlice); ok {
|
||||||
return slice.Value()
|
return slice.Value()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
74
flag_path.go
74
flag_path.go
@ -1,42 +1,11 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import "flag"
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
type PathFlag struct {
|
type Path = string
|
||||||
Name string
|
|
||||||
Aliases []string
|
|
||||||
Usage string
|
|
||||||
EnvVars []string
|
|
||||||
FilePath string
|
|
||||||
Required bool
|
|
||||||
Hidden bool
|
|
||||||
TakesFile bool
|
|
||||||
Value string
|
|
||||||
DefaultText string
|
|
||||||
Destination *string
|
|
||||||
HasBeenSet bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSet returns whether or not the flag has been set through env or file
|
|
||||||
func (f *PathFlag) IsSet() bool {
|
|
||||||
return f.HasBeenSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f *PathFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Names returns the names of the flag
|
|
||||||
func (f *PathFlag) Names() []string {
|
|
||||||
return flagNames(f.Name, f.Aliases)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRequired returns whether or not the flag is required
|
|
||||||
func (f *PathFlag) IsRequired() bool {
|
|
||||||
return f.Required
|
|
||||||
}
|
|
||||||
|
|
||||||
// TakesValue returns true of the flag takes a value, otherwise false
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
func (f *PathFlag) TakesValue() bool {
|
func (f *PathFlag) TakesValue() bool {
|
||||||
@ -48,20 +17,36 @@ func (f *PathFlag) GetUsage() string {
|
|||||||
return f.Usage
|
return f.Usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCategory returns the category for the flag
|
||||||
|
func (f *PathFlag) GetCategory() string {
|
||||||
|
return f.Category
|
||||||
|
}
|
||||||
|
|
||||||
// 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 *PathFlag) GetValue() string {
|
func (f *PathFlag) GetValue() string {
|
||||||
return f.Value
|
return f.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
// GetDefaultText returns the default text for this flag
|
||||||
func (f *PathFlag) IsVisible() bool {
|
func (f *PathFlag) GetDefaultText() string {
|
||||||
return !f.Hidden
|
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, _, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
|
||||||
f.Value = val
|
f.Value = val
|
||||||
f.HasBeenSet = true
|
f.HasBeenSet = true
|
||||||
}
|
}
|
||||||
@ -77,10 +62,15 @@ func (f *PathFlag) Apply(set *flag.FlagSet) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get returns the flag’s value in the given Context.
|
||||||
|
func (f *PathFlag) Get(ctx *Context) string {
|
||||||
|
return ctx.Path(f.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// 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,43 +1,9 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import "flag"
|
import (
|
||||||
|
"flag"
|
||||||
// StringFlag is a flag with type string
|
"fmt"
|
||||||
type StringFlag struct {
|
)
|
||||||
Name string
|
|
||||||
Aliases []string
|
|
||||||
Usage string
|
|
||||||
EnvVars []string
|
|
||||||
FilePath string
|
|
||||||
Required bool
|
|
||||||
Hidden bool
|
|
||||||
TakesFile bool
|
|
||||||
Value string
|
|
||||||
DefaultText string
|
|
||||||
Destination *string
|
|
||||||
HasBeenSet bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSet returns whether or not the flag has been set through env or file
|
|
||||||
func (f *StringFlag) IsSet() bool {
|
|
||||||
return f.HasBeenSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f *StringFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Names returns the names of the flag
|
|
||||||
func (f *StringFlag) Names() []string {
|
|
||||||
return flagNames(f.Name, f.Aliases)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRequired returns whether or not the flag is required
|
|
||||||
func (f *StringFlag) IsRequired() bool {
|
|
||||||
return f.Required
|
|
||||||
}
|
|
||||||
|
|
||||||
// TakesValue returns true of the flag takes a value, otherwise false
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
func (f *StringFlag) TakesValue() bool {
|
func (f *StringFlag) TakesValue() bool {
|
||||||
@ -49,20 +15,36 @@ func (f *StringFlag) GetUsage() string {
|
|||||||
return f.Usage
|
return f.Usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCategory returns the category for the flag
|
||||||
|
func (f *StringFlag) GetCategory() string {
|
||||||
|
return f.Category
|
||||||
|
}
|
||||||
|
|
||||||
// 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 *StringFlag) GetValue() string {
|
func (f *StringFlag) GetValue() string {
|
||||||
return f.Value
|
return f.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
// GetDefaultText returns the default text for this flag
|
||||||
func (f *StringFlag) IsVisible() bool {
|
func (f *StringFlag) GetDefaultText() string {
|
||||||
return !f.Hidden
|
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, _, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
|
||||||
f.Value = val
|
f.Value = val
|
||||||
f.HasBeenSet = true
|
f.HasBeenSet = true
|
||||||
}
|
}
|
||||||
@ -78,10 +60,15 @@ func (f *StringFlag) Apply(set *flag.FlagSet) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get returns the flag’s value in the given Context.
|
||||||
|
func (f *StringFlag) Get(ctx *Context) string {
|
||||||
|
return ctx.String(f.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
}
|
}
|
||||||
@ -68,41 +70,10 @@ func (s *StringSlice) Get() interface{} {
|
|||||||
return *s
|
return *s
|
||||||
}
|
}
|
||||||
|
|
||||||
// StringSliceFlag is a flag with type *StringSlice
|
|
||||||
type StringSliceFlag struct {
|
|
||||||
Name string
|
|
||||||
Aliases []string
|
|
||||||
Usage string
|
|
||||||
EnvVars []string
|
|
||||||
FilePath string
|
|
||||||
Required bool
|
|
||||||
Hidden bool
|
|
||||||
TakesFile bool
|
|
||||||
Value *StringSlice
|
|
||||||
DefaultText string
|
|
||||||
HasBeenSet bool
|
|
||||||
Destination *StringSlice
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSet returns whether or not the flag has been set through env or file
|
|
||||||
func (f *StringSliceFlag) IsSet() bool {
|
|
||||||
return f.HasBeenSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
func (f *StringSliceFlag) Names() []string {
|
|
||||||
return flagNames(f.Name, f.Aliases)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRequired returns whether or not the flag is required
|
|
||||||
func (f *StringSliceFlag) IsRequired() bool {
|
|
||||||
return f.Required
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TakesValue returns true of the flag takes a value, otherwise false
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
@ -115,6 +86,11 @@ func (f *StringSliceFlag) GetUsage() string {
|
|||||||
return f.Usage
|
return f.Usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCategory returns the category for the flag
|
||||||
|
func (f *StringSliceFlag) GetCategory() string {
|
||||||
|
return f.Category
|
||||||
|
}
|
||||||
|
|
||||||
// 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 *StringSliceFlag) GetValue() string {
|
func (f *StringSliceFlag) GetValue() string {
|
||||||
@ -124,48 +100,51 @@ func (f *StringSliceFlag) GetValue() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
// GetDefaultText returns the default text for this flag
|
||||||
func (f *StringSliceFlag) IsVisible() bool {
|
func (f *StringSliceFlag) GetDefaultText() string {
|
||||||
return !f.Hidden
|
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 {
|
||||||
|
// apply any default
|
||||||
if f.Destination != nil && f.Value != nil {
|
if f.Destination != nil && f.Value != nil {
|
||||||
f.Destination.slice = make([]string, len(f.Value.slice))
|
f.Destination.slice = make([]string, len(f.Value.slice))
|
||||||
copy(f.Destination.slice, f.Value.slice)
|
copy(f.Destination.slice, f.Value.slice)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
// resolve setValue (what we will assign to the set)
|
||||||
if f.Value == nil {
|
var setValue *StringSlice
|
||||||
f.Value = &StringSlice{}
|
switch {
|
||||||
}
|
case f.Destination != nil:
|
||||||
destination := f.Value
|
setValue = f.Destination
|
||||||
if f.Destination != nil {
|
case f.Value != nil:
|
||||||
destination = f.Destination
|
setValue = f.Value.clone()
|
||||||
}
|
default:
|
||||||
|
setValue = new(StringSlice)
|
||||||
|
}
|
||||||
|
|
||||||
for _, s := range strings.Split(val, ",") {
|
if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
|
||||||
if err := destination.Set(strings.TrimSpace(s)); err != nil {
|
for _, s := range flagSplitMultiValues(val) {
|
||||||
return fmt.Errorf("could not parse %q as string value for flag %s: %s", val, f.Name, err)
|
if err := setValue.Set(strings.TrimSpace(s)); err != nil {
|
||||||
|
return fmt.Errorf("could not parse %q as string value from %s for flag %s: %s", val, source, f.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set this to false so that we reset the slice if we then set values from
|
// Set this to false so that we reset the slice if we then set values from
|
||||||
// flags that have already been set by the environment.
|
// flags that have already been set by the environment.
|
||||||
destination.hasBeenSet = false
|
setValue.hasBeenSet = false
|
||||||
f.HasBeenSet = true
|
f.HasBeenSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Value == nil {
|
|
||||||
f.Value = &StringSlice{}
|
|
||||||
}
|
|
||||||
setValue := f.Destination
|
|
||||||
if f.Destination == nil {
|
|
||||||
setValue = f.Value.clone()
|
|
||||||
}
|
|
||||||
for _, name := range f.Names() {
|
for _, name := range f.Names() {
|
||||||
set.Var(setValue, name, f.Usage)
|
set.Var(setValue, name, f.Usage)
|
||||||
}
|
}
|
||||||
@ -173,10 +152,15 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get returns the flag’s value in the given Context.
|
||||||
|
func (f *StringSliceFlag) Get(ctx *Context) []string {
|
||||||
|
return ctx.StringSlice(f.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@ -185,7 +169,7 @@ func (c *Context) StringSlice(name string) []string {
|
|||||||
func lookupStringSlice(name string, set *flag.FlagSet) []string {
|
func lookupStringSlice(name string, set *flag.FlagSet) []string {
|
||||||
f := set.Lookup(name)
|
f := set.Lookup(name)
|
||||||
if f != nil {
|
if f != nil {
|
||||||
if slice, ok := f.Value.(*StringSlice); ok {
|
if slice, ok := unwrapFlagValue(f.Value).(*StringSlice); ok {
|
||||||
return slice.Value()
|
return slice.Value()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
549
flag_test.go
549
flag_test.go
@ -51,6 +51,17 @@ func TestBoolFlagApply_SetsAllNames(t *testing.T) {
|
|||||||
expect(t, v, true)
|
expect(t, v, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBoolFlagValueFromContext(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Bool("trueflag", true, "doc")
|
||||||
|
set.Bool("falseflag", false, "doc")
|
||||||
|
ctx := NewContext(nil, set, nil)
|
||||||
|
tf := &BoolFlag{Name: "trueflag"}
|
||||||
|
ff := &BoolFlag{Name: "falseflag"}
|
||||||
|
expect(t, tf.Get(ctx), true)
|
||||||
|
expect(t, ff.Get(ctx), false)
|
||||||
|
}
|
||||||
|
|
||||||
func TestFlagsFromEnv(t *testing.T) {
|
func TestFlagsFromEnv(t *testing.T) {
|
||||||
newSetFloat64Slice := func(defaults ...float64) Float64Slice {
|
newSetFloat64Slice := func(defaults ...float64) Float64Slice {
|
||||||
s := NewFloat64Slice(defaults...)
|
s := NewFloat64Slice(defaults...)
|
||||||
@ -85,33 +96,33 @@ func TestFlagsFromEnv(t *testing.T) {
|
|||||||
{"", false, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""},
|
{"", false, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""},
|
||||||
{"1", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""},
|
{"1", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""},
|
||||||
{"false", false, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""},
|
{"false", false, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""},
|
||||||
{"foobar", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, `could not parse "foobar" as bool value for flag debug: .*`},
|
{"foobar", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, `could not parse "foobar" as bool value from environment variable "DEBUG" for flag debug: .*`},
|
||||||
|
|
||||||
{"1s", 1 * time.Second, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, ""},
|
{"1s", 1 * time.Second, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, ""},
|
||||||
{"foobar", false, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, `could not parse "foobar" as duration value for flag time: .*`},
|
{"foobar", false, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, `could not parse "foobar" as duration value from environment variable "TIME" for flag time: .*`},
|
||||||
|
|
||||||
{"1.2", 1.2, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
{"1.2", 1.2, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
||||||
{"1", 1.0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
{"1", 1.0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
||||||
{"foobar", 0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as float64 value for flag seconds: .*`},
|
{"foobar", 0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as float64 value from environment variable "SECONDS" for flag seconds: .*`},
|
||||||
|
|
||||||
{"1", int64(1), &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
{"1", int64(1), &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
||||||
{"1.2", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value for flag seconds: .*`},
|
{"1.2", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value from environment variable "SECONDS" for flag seconds: .*`},
|
||||||
{"foobar", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value for flag seconds: .*`},
|
{"foobar", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value from environment variable "SECONDS" for flag seconds: .*`},
|
||||||
|
|
||||||
{"1", 1, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
{"1", 1, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
||||||
{"1.2", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value for flag seconds: .*`},
|
{"1.2", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value from environment variable "SECONDS" for flag seconds: .*`},
|
||||||
{"foobar", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value for flag seconds: .*`},
|
{"foobar", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value from environment variable "SECONDS" for flag seconds: .*`},
|
||||||
|
|
||||||
{"1.0,2", newSetFloat64Slice(1, 2), &Float64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
{"1.0,2", newSetFloat64Slice(1, 2), &Float64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
||||||
{"foobar", newSetFloat64Slice(), &Float64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "\[\]float64{}" as float64 slice value for flag seconds: .*`},
|
{"foobar", newSetFloat64Slice(), &Float64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as float64 slice value from environment variable "SECONDS" for flag seconds: .*`},
|
||||||
|
|
||||||
{"1,2", newSetIntSlice(1, 2), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
{"1,2", newSetIntSlice(1, 2), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
||||||
{"1.2,2", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int slice value for flag seconds: .*`},
|
{"1.2,2", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int slice value from environment variable "SECONDS" for flag seconds: .*`},
|
||||||
{"foobar", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int slice value for flag seconds: .*`},
|
{"foobar", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int slice value from environment variable "SECONDS" for flag seconds: .*`},
|
||||||
|
|
||||||
{"1,2", newSetInt64Slice(1, 2), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
{"1,2", newSetInt64Slice(1, 2), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
||||||
{"1.2,2", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int64 slice value for flag seconds: .*`},
|
{"1.2,2", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int64 slice value from environment variable "SECONDS" for flag seconds: .*`},
|
||||||
{"foobar", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int64 slice value for flag seconds: .*`},
|
{"foobar", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int64 slice value from environment variable "SECONDS" for flag seconds: .*`},
|
||||||
|
|
||||||
{"foo", "foo", &StringFlag{Name: "name", EnvVars: []string{"NAME"}}, ""},
|
{"foo", "foo", &StringFlag{Name: "name", EnvVars: []string{"NAME"}}, ""},
|
||||||
{"path", "path", &PathFlag{Name: "path", EnvVars: []string{"PATH"}}, ""},
|
{"path", "path", &PathFlag{Name: "path", EnvVars: []string{"PATH"}}, ""},
|
||||||
@ -119,12 +130,12 @@ func TestFlagsFromEnv(t *testing.T) {
|
|||||||
{"foo,bar", newSetStringSlice("foo", "bar"), &StringSliceFlag{Name: "names", EnvVars: []string{"NAMES"}}, ""},
|
{"foo,bar", newSetStringSlice("foo", "bar"), &StringSliceFlag{Name: "names", EnvVars: []string{"NAMES"}}, ""},
|
||||||
|
|
||||||
{"1", uint(1), &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
{"1", uint(1), &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
||||||
{"1.2", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as uint value for flag seconds: .*`},
|
{"1.2", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as uint value from environment variable "SECONDS" for flag seconds: .*`},
|
||||||
{"foobar", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint value for flag seconds: .*`},
|
{"foobar", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint value from environment variable "SECONDS" for flag seconds: .*`},
|
||||||
|
|
||||||
{"1", uint64(1), &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
{"1", uint64(1), &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
||||||
{"1.2", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as uint64 value for flag seconds: .*`},
|
{"1.2", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as uint64 value from environment variable "SECONDS" for flag seconds: .*`},
|
||||||
{"foobar", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint64 value for flag seconds: .*`},
|
{"foobar", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint64 value from environment variable "SECONDS" for flag seconds: .*`},
|
||||||
|
|
||||||
{"foo,bar", &Parser{"foo", "bar"}, &GenericFlag{Name: "names", Value: &Parser{}, EnvVars: []string{"NAMES"}}, ""},
|
{"foo,bar", &Parser{"foo", "bar"}, &GenericFlag{Name: "names", Value: &Parser{}, EnvVars: []string{"NAMES"}}, ""},
|
||||||
}
|
}
|
||||||
@ -132,8 +143,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 +179,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 +411,7 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var prefixStringFlagTests = []struct {
|
var _ = []struct {
|
||||||
name string
|
name string
|
||||||
aliases []string
|
aliases []string
|
||||||
usage string
|
usage string
|
||||||
@ -257,6 +450,14 @@ func TestStringFlagApply_SetsAllNames(t *testing.T) {
|
|||||||
expect(t, v, "YUUUU")
|
expect(t, v, "YUUUU")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStringFlagValueFromContext(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.String("myflag", "foobar", "doc")
|
||||||
|
ctx := NewContext(nil, set, nil)
|
||||||
|
f := &StringFlag{Name: "myflag"}
|
||||||
|
expect(t, f.Get(ctx), "foobar")
|
||||||
|
}
|
||||||
|
|
||||||
var pathFlagTests = []struct {
|
var pathFlagTests = []struct {
|
||||||
name string
|
name string
|
||||||
aliases []string
|
aliases []string
|
||||||
@ -308,7 +509,15 @@ func TestPathFlagApply_SetsAllNames(t *testing.T) {
|
|||||||
expect(t, v, "/path/to/file/PATH")
|
expect(t, v, "/path/to/file/PATH")
|
||||||
}
|
}
|
||||||
|
|
||||||
var envHintFlagTests = []struct {
|
func TestPathFlagValueFromContext(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.String("myflag", "/my/path", "doc")
|
||||||
|
ctx := NewContext(nil, set, nil)
|
||||||
|
f := &PathFlag{Name: "myflag"}
|
||||||
|
expect(t, f.Get(ctx), "/my/path")
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = []struct {
|
||||||
name string
|
name string
|
||||||
env string
|
env string
|
||||||
hinter FlagEnvHintFunc
|
hinter FlagEnvHintFunc
|
||||||
@ -395,7 +604,7 @@ func TestStringSliceFlagApply_SetsAllNames(t *testing.T) {
|
|||||||
expect(t, err, nil)
|
expect(t, err, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringSliceFlagApply_UsesEnvValues(t *testing.T) {
|
func TestStringSliceFlagApply_UsesEnvValues_noDefault(t *testing.T) {
|
||||||
defer resetEnv(os.Environ())
|
defer resetEnv(os.Environ())
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
_ = os.Setenv("MY_GOAT", "vincent van goat,scape goat")
|
_ = os.Setenv("MY_GOAT", "vincent van goat,scape goat")
|
||||||
@ -406,7 +615,22 @@ func TestStringSliceFlagApply_UsesEnvValues(t *testing.T) {
|
|||||||
|
|
||||||
err := set.Parse(nil)
|
err := set.Parse(nil)
|
||||||
expect(t, err, nil)
|
expect(t, err, nil)
|
||||||
expect(t, val.Value(), NewStringSlice("vincent van goat", "scape goat").Value())
|
expect(t, val.Value(), []string(nil))
|
||||||
|
expect(t, set.Lookup("goat").Value.(*StringSlice).Value(), []string{"vincent van goat", "scape goat"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringSliceFlagApply_UsesEnvValues_withDefault(t *testing.T) {
|
||||||
|
defer resetEnv(os.Environ())
|
||||||
|
os.Clearenv()
|
||||||
|
_ = os.Setenv("MY_GOAT", "vincent van goat,scape goat")
|
||||||
|
val := NewStringSlice(`some default`, `values here`)
|
||||||
|
fl := StringSliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: val}
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
_ = fl.Apply(set)
|
||||||
|
err := set.Parse(nil)
|
||||||
|
expect(t, err, nil)
|
||||||
|
expect(t, val.Value(), []string{`some default`, `values here`})
|
||||||
|
expect(t, set.Lookup("goat").Value.(*StringSlice).Value(), []string{"vincent van goat", "scape goat"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringSliceFlagApply_DefaultValueWithDestination(t *testing.T) {
|
func TestStringSliceFlagApply_DefaultValueWithDestination(t *testing.T) {
|
||||||
@ -421,6 +645,14 @@ func TestStringSliceFlagApply_DefaultValueWithDestination(t *testing.T) {
|
|||||||
expect(t, defValue, fl.Destination.Value())
|
expect(t, defValue, fl.Destination.Value())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStringSliceFlagValueFromContext(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Var(NewStringSlice("a", "b", "c"), "myflag", "doc")
|
||||||
|
ctx := NewContext(nil, set, nil)
|
||||||
|
f := &StringSliceFlag{Name: "myflag"}
|
||||||
|
expect(t, f.Get(ctx), []string{"a", "b", "c"})
|
||||||
|
}
|
||||||
|
|
||||||
var intFlagTests = []struct {
|
var intFlagTests = []struct {
|
||||||
name string
|
name string
|
||||||
expected string
|
expected string
|
||||||
@ -470,6 +702,14 @@ func TestIntFlagApply_SetsAllNames(t *testing.T) {
|
|||||||
expect(t, v, 5)
|
expect(t, v, 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIntFlagValueFromContext(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Int("myflag", 42, "doc")
|
||||||
|
ctx := NewContext(nil, set, nil)
|
||||||
|
f := &IntFlag{Name: "myflag"}
|
||||||
|
expect(t, f.Get(ctx), 42)
|
||||||
|
}
|
||||||
|
|
||||||
var int64FlagTests = []struct {
|
var int64FlagTests = []struct {
|
||||||
name string
|
name string
|
||||||
expected string
|
expected string
|
||||||
@ -508,6 +748,14 @@ func TestInt64FlagWithEnvVarHelpOutput(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInt64FlagValueFromContext(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Int64("myflag", 42, "doc")
|
||||||
|
ctx := NewContext(nil, set, nil)
|
||||||
|
f := &Int64Flag{Name: "myflag"}
|
||||||
|
expect(t, f.Get(ctx), int64(42))
|
||||||
|
}
|
||||||
|
|
||||||
var uintFlagTests = []struct {
|
var uintFlagTests = []struct {
|
||||||
name string
|
name string
|
||||||
expected string
|
expected string
|
||||||
@ -546,6 +794,14 @@ func TestUintFlagWithEnvVarHelpOutput(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUintFlagValueFromContext(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Uint("myflag", 42, "doc")
|
||||||
|
ctx := NewContext(nil, set, nil)
|
||||||
|
f := &UintFlag{Name: "myflag"}
|
||||||
|
expect(t, f.Get(ctx), uint(42))
|
||||||
|
}
|
||||||
|
|
||||||
var uint64FlagTests = []struct {
|
var uint64FlagTests = []struct {
|
||||||
name string
|
name string
|
||||||
expected string
|
expected string
|
||||||
@ -584,6 +840,14 @@ func TestUint64FlagWithEnvVarHelpOutput(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUint64FlagValueFromContext(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Uint64("myflag", 42, "doc")
|
||||||
|
ctx := NewContext(nil, set, nil)
|
||||||
|
f := &Uint64Flag{Name: "myflag"}
|
||||||
|
expect(t, f.Get(ctx), uint64(42))
|
||||||
|
}
|
||||||
|
|
||||||
var durationFlagTests = []struct {
|
var durationFlagTests = []struct {
|
||||||
name string
|
name string
|
||||||
expected string
|
expected string
|
||||||
@ -633,6 +897,14 @@ func TestDurationFlagApply_SetsAllNames(t *testing.T) {
|
|||||||
expect(t, v, time.Hour*30)
|
expect(t, v, time.Hour*30)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDurationFlagValueFromContext(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Duration("myflag", 42*time.Second, "doc")
|
||||||
|
ctx := NewContext(nil, set, nil)
|
||||||
|
f := &DurationFlag{Name: "myflag"}
|
||||||
|
expect(t, f.Get(ctx), 42*time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
var intSliceFlagTests = []struct {
|
var intSliceFlagTests = []struct {
|
||||||
name string
|
name string
|
||||||
aliases []string
|
aliases []string
|
||||||
@ -722,6 +994,14 @@ func TestIntSliceFlag_SetFromParentContext(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIntSliceFlagValueFromContext(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Var(NewIntSlice(1, 2, 3), "myflag", "doc")
|
||||||
|
ctx := NewContext(nil, set, nil)
|
||||||
|
f := &IntSliceFlag{Name: "myflag"}
|
||||||
|
expect(t, f.Get(ctx), []int{1, 2, 3})
|
||||||
|
}
|
||||||
|
|
||||||
var int64SliceFlagTests = []struct {
|
var int64SliceFlagTests = []struct {
|
||||||
name string
|
name string
|
||||||
aliases []string
|
aliases []string
|
||||||
@ -818,6 +1098,14 @@ func TestInt64SliceFlag_ReturnNil(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInt64SliceFlagValueFromContext(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Var(NewInt64Slice(1, 2, 3), "myflag", "doc")
|
||||||
|
ctx := NewContext(nil, set, nil)
|
||||||
|
f := &Int64SliceFlag{Name: "myflag"}
|
||||||
|
expect(t, f.Get(ctx), []int64{1, 2, 3})
|
||||||
|
}
|
||||||
|
|
||||||
var float64FlagTests = []struct {
|
var float64FlagTests = []struct {
|
||||||
name string
|
name string
|
||||||
expected string
|
expected string
|
||||||
@ -867,6 +1155,14 @@ func TestFloat64FlagApply_SetsAllNames(t *testing.T) {
|
|||||||
expect(t, v, float64(43.33333))
|
expect(t, v, float64(43.33333))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFloat64FlagValueFromContext(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Float64("myflag", 1.23, "doc")
|
||||||
|
ctx := NewContext(nil, set, nil)
|
||||||
|
f := &Float64Flag{Name: "myflag"}
|
||||||
|
expect(t, f.Get(ctx), 1.23)
|
||||||
|
}
|
||||||
|
|
||||||
var float64SliceFlagTests = []struct {
|
var float64SliceFlagTests = []struct {
|
||||||
name string
|
name string
|
||||||
aliases []string
|
aliases []string
|
||||||
@ -908,6 +1204,14 @@ func TestFloat64SliceFlagWithEnvVarHelpOutput(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFloat64SliceFlagValueFromContext(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Var(NewFloat64Slice(1.23, 4.56), "myflag", "doc")
|
||||||
|
ctx := NewContext(nil, set, nil)
|
||||||
|
f := &Float64SliceFlag{Name: "myflag"}
|
||||||
|
expect(t, f.Get(ctx), []float64{1.23, 4.56})
|
||||||
|
}
|
||||||
|
|
||||||
var genericFlagTests = []struct {
|
var genericFlagTests = []struct {
|
||||||
name string
|
name string
|
||||||
value Generic
|
value Generic
|
||||||
@ -956,6 +1260,14 @@ func TestGenericFlagApply_SetsAllNames(t *testing.T) {
|
|||||||
expect(t, err, nil)
|
expect(t, err, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGenericFlagValueFromContext(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Var(&Parser{"abc", "def"}, "myflag", "doc")
|
||||||
|
ctx := NewContext(nil, set, nil)
|
||||||
|
f := &GenericFlag{Name: "myflag"}
|
||||||
|
expect(t, f.Get(ctx), &Parser{"abc", "def"})
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseMultiString(t *testing.T) {
|
func TestParseMultiString(t *testing.T) {
|
||||||
_ = (&App{
|
_ = (&App{
|
||||||
Flags: []Flag{
|
Flags: []Flag{
|
||||||
@ -1109,6 +1421,75 @@ func TestParseMultiStringSliceWithDestinationAndEnv(t *testing.T) {
|
|||||||
}).Run([]string{"run", "-s", "10", "-s", "20"})
|
}).Run([]string{"run", "-s", "10", "-s", "20"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseMultiFloat64SliceWithDestinationAndEnv(t *testing.T) {
|
||||||
|
defer resetEnv(os.Environ())
|
||||||
|
os.Clearenv()
|
||||||
|
_ = os.Setenv("APP_INTERVALS", "20,30,40")
|
||||||
|
|
||||||
|
dest := &Float64Slice{}
|
||||||
|
_ = (&App{
|
||||||
|
Flags: []Flag{
|
||||||
|
&Float64SliceFlag{Name: "serve", Aliases: []string{"s"}, Destination: dest, EnvVars: []string{"APP_INTERVALS"}},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) error {
|
||||||
|
expected := []float64{10, 20}
|
||||||
|
if !reflect.DeepEqual(dest.slice, expected) {
|
||||||
|
t.Errorf("main name not set: %v != %v", expected, ctx.StringSlice("serve"))
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(dest.slice, expected) {
|
||||||
|
t.Errorf("short name not set: %v != %v", expected, ctx.StringSlice("s"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}).Run([]string{"run", "-s", "10", "-s", "20"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiInt64SliceWithDestinationAndEnv(t *testing.T) {
|
||||||
|
defer resetEnv(os.Environ())
|
||||||
|
os.Clearenv()
|
||||||
|
_ = os.Setenv("APP_INTERVALS", "20,30,40")
|
||||||
|
|
||||||
|
dest := &Int64Slice{}
|
||||||
|
_ = (&App{
|
||||||
|
Flags: []Flag{
|
||||||
|
&Int64SliceFlag{Name: "serve", Aliases: []string{"s"}, Destination: dest, EnvVars: []string{"APP_INTERVALS"}},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) error {
|
||||||
|
expected := []int64{10, 20}
|
||||||
|
if !reflect.DeepEqual(dest.slice, expected) {
|
||||||
|
t.Errorf("main name not set: %v != %v", expected, ctx.StringSlice("serve"))
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(dest.slice, expected) {
|
||||||
|
t.Errorf("short name not set: %v != %v", expected, ctx.StringSlice("s"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}).Run([]string{"run", "-s", "10", "-s", "20"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiIntSliceWithDestinationAndEnv(t *testing.T) {
|
||||||
|
defer resetEnv(os.Environ())
|
||||||
|
os.Clearenv()
|
||||||
|
_ = os.Setenv("APP_INTERVALS", "20,30,40")
|
||||||
|
|
||||||
|
dest := &IntSlice{}
|
||||||
|
_ = (&App{
|
||||||
|
Flags: []Flag{
|
||||||
|
&IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Destination: dest, EnvVars: []string{"APP_INTERVALS"}},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) error {
|
||||||
|
expected := []int{10, 20}
|
||||||
|
if !reflect.DeepEqual(dest.slice, expected) {
|
||||||
|
t.Errorf("main name not set: %v != %v", expected, ctx.StringSlice("serve"))
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(dest.slice, expected) {
|
||||||
|
t.Errorf("short name not set: %v != %v", expected, ctx.StringSlice("s"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}).Run([]string{"run", "-s", "10", "-s", "20"})
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) {
|
func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) {
|
||||||
_ = (&App{
|
_ = (&App{
|
||||||
Flags: []Flag{
|
Flags: []Flag{
|
||||||
@ -1860,7 +2241,7 @@ func TestFlagFromFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, filePathTest := range filePathTests {
|
for _, filePathTest := range filePathTests {
|
||||||
got, _ := flagFromEnvOrFile(filePathTest.name, filePathTest.path)
|
got, _, _ := flagFromEnvOrFile(filePathTest.name, filePathTest.path)
|
||||||
if want := filePathTest.expected; got != want {
|
if want := filePathTest.expected; got != want {
|
||||||
t.Errorf("Did not expect %v - Want %v", got, want)
|
t.Errorf("Did not expect %v - Want %v", got, want)
|
||||||
}
|
}
|
||||||
@ -1983,6 +2364,27 @@ func TestTimestampFlagApply_Fail_Parse_Wrong_Time(t *testing.T) {
|
|||||||
expect(t, err, fmt.Errorf("invalid value \"2006-01-02T15:04:05Z\" for flag -time: parsing time \"2006-01-02T15:04:05Z\" as \"Jan 2, 2006 at 3:04pm (MST)\": cannot parse \"2006-01-02T15:04:05Z\" as \"Jan\""))
|
expect(t, err, fmt.Errorf("invalid value \"2006-01-02T15:04:05Z\" for flag -time: parsing time \"2006-01-02T15:04:05Z\" as \"Jan 2, 2006 at 3:04pm (MST)\": cannot parse \"2006-01-02T15:04:05Z\" as \"Jan\""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTimestampFlagApply_Timezoned(t *testing.T) {
|
||||||
|
pdt := time.FixedZone("PDT", -7*60*60)
|
||||||
|
expectedResult, _ := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
||||||
|
fl := TimestampFlag{Name: "time", Aliases: []string{"t"}, Layout: time.ANSIC, Timezone: pdt}
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
_ = fl.Apply(set)
|
||||||
|
|
||||||
|
err := set.Parse([]string{"--time", "Mon Jan 2 08:04:05 2006"})
|
||||||
|
expect(t, err, nil)
|
||||||
|
expect(t, *fl.Value.timestamp, expectedResult.In(pdt))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimestampFlagValueFromContext(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
now := time.Now()
|
||||||
|
set.Var(NewTimestamp(now), "myflag", "doc")
|
||||||
|
ctx := NewContext(nil, set, nil)
|
||||||
|
f := &TimestampFlag{Name: "myflag"}
|
||||||
|
expect(t, f.Get(ctx), &now)
|
||||||
|
}
|
||||||
|
|
||||||
type flagDefaultTestCase struct {
|
type flagDefaultTestCase struct {
|
||||||
name string
|
name string
|
||||||
flag Flag
|
flag Flag
|
||||||
@ -1992,43 +2394,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 +2450,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 +2509,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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -11,6 +11,7 @@ type Timestamp struct {
|
|||||||
timestamp *time.Time
|
timestamp *time.Time
|
||||||
hasBeenSet bool
|
hasBeenSet bool
|
||||||
layout string
|
layout string
|
||||||
|
location *time.Location
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timestamp constructor
|
// Timestamp constructor
|
||||||
@ -31,9 +32,22 @@ func (t *Timestamp) SetLayout(layout string) {
|
|||||||
t.layout = layout
|
t.layout = layout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set perceived timezone of the to-be parsed time string
|
||||||
|
func (t *Timestamp) SetLocation(loc *time.Location) {
|
||||||
|
t.location = loc
|
||||||
|
}
|
||||||
|
|
||||||
// Parses the string value to timestamp
|
// Parses the string value to timestamp
|
||||||
func (t *Timestamp) Set(value string) error {
|
func (t *Timestamp) Set(value string) error {
|
||||||
timestamp, err := time.Parse(t.layout, value)
|
var timestamp time.Time
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if t.location != nil {
|
||||||
|
timestamp, err = time.ParseInLocation(t.layout, value, t.location)
|
||||||
|
} else {
|
||||||
|
timestamp, err = time.Parse(t.layout, value)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -58,43 +72,6 @@ func (t *Timestamp) Get() interface{} {
|
|||||||
return *t
|
return *t
|
||||||
}
|
}
|
||||||
|
|
||||||
// TimestampFlag is a flag with type time
|
|
||||||
type TimestampFlag struct {
|
|
||||||
Name string
|
|
||||||
Aliases []string
|
|
||||||
Usage string
|
|
||||||
EnvVars []string
|
|
||||||
FilePath string
|
|
||||||
Required bool
|
|
||||||
Hidden bool
|
|
||||||
Layout string
|
|
||||||
Value *Timestamp
|
|
||||||
DefaultText string
|
|
||||||
HasBeenSet bool
|
|
||||||
Destination *Timestamp
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSet returns whether or not the flag has been set through env or file
|
|
||||||
func (f *TimestampFlag) IsSet() bool {
|
|
||||||
return f.HasBeenSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f *TimestampFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Names returns the names of the flag
|
|
||||||
func (f *TimestampFlag) Names() []string {
|
|
||||||
return flagNames(f.Name, f.Aliases)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRequired returns whether or not the flag is required
|
|
||||||
func (f *TimestampFlag) IsRequired() bool {
|
|
||||||
return f.Required
|
|
||||||
}
|
|
||||||
|
|
||||||
// TakesValue returns true of the flag takes a value, otherwise false
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
func (f *TimestampFlag) TakesValue() bool {
|
func (f *TimestampFlag) TakesValue() bool {
|
||||||
return true
|
return true
|
||||||
@ -105,6 +82,11 @@ func (f *TimestampFlag) GetUsage() string {
|
|||||||
return f.Usage
|
return f.Usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCategory returns the category for the flag
|
||||||
|
func (f *TimestampFlag) GetCategory() string {
|
||||||
|
return f.Category
|
||||||
|
}
|
||||||
|
|
||||||
// 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 *TimestampFlag) GetValue() string {
|
func (f *TimestampFlag) GetValue() string {
|
||||||
@ -114,9 +96,17 @@ func (f *TimestampFlag) GetValue() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
// GetDefaultText returns the default text for this flag
|
||||||
func (f *TimestampFlag) IsVisible() bool {
|
func (f *TimestampFlag) GetDefaultText() string {
|
||||||
return !f.Hidden
|
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
|
||||||
@ -128,14 +118,16 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
|
|||||||
f.Value = &Timestamp{}
|
f.Value = &Timestamp{}
|
||||||
}
|
}
|
||||||
f.Value.SetLayout(f.Layout)
|
f.Value.SetLayout(f.Layout)
|
||||||
|
f.Value.SetLocation(f.Timezone)
|
||||||
|
|
||||||
if f.Destination != nil {
|
if f.Destination != nil {
|
||||||
f.Destination.SetLayout(f.Layout)
|
f.Destination.SetLayout(f.Layout)
|
||||||
|
f.Destination.SetLocation(f.Timezone)
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
|
||||||
if err := f.Value.Set(val); err != nil {
|
if err := f.Value.Set(val); err != nil {
|
||||||
return fmt.Errorf("could not parse %q as timestamp value for flag %s: %s", val, f.Name, err)
|
return fmt.Errorf("could not parse %q as timestamp value from %s for flag %s: %s", val, source, f.Name, err)
|
||||||
}
|
}
|
||||||
f.HasBeenSet = true
|
f.HasBeenSet = true
|
||||||
}
|
}
|
||||||
@ -151,9 +143,14 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get returns the flag’s value in the given Context.
|
||||||
|
func (f *TimestampFlag) Get(ctx *Context) *time.Time {
|
||||||
|
return ctx.Timestamp(f.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
68
flag_uint.go
68
flag_uint.go
@ -6,42 +6,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UintFlag is a flag with type uint
|
|
||||||
type UintFlag struct {
|
|
||||||
Name string
|
|
||||||
Aliases []string
|
|
||||||
Usage string
|
|
||||||
EnvVars []string
|
|
||||||
FilePath string
|
|
||||||
Required bool
|
|
||||||
Hidden bool
|
|
||||||
Value uint
|
|
||||||
DefaultText string
|
|
||||||
Destination *uint
|
|
||||||
HasBeenSet bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSet returns whether or not the flag has been set through env or file
|
|
||||||
func (f *UintFlag) IsSet() bool {
|
|
||||||
return f.HasBeenSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f *UintFlag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Names returns the names of the flag
|
|
||||||
func (f *UintFlag) Names() []string {
|
|
||||||
return flagNames(f.Name, f.Aliases)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRequired returns whether or not the flag is required
|
|
||||||
func (f *UintFlag) IsRequired() bool {
|
|
||||||
return f.Required
|
|
||||||
}
|
|
||||||
|
|
||||||
// TakesValue returns true of the flag takes a value, otherwise false
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
func (f *UintFlag) TakesValue() bool {
|
func (f *UintFlag) TakesValue() bool {
|
||||||
return true
|
return true
|
||||||
@ -52,18 +16,18 @@ func (f *UintFlag) GetUsage() string {
|
|||||||
return f.Usage
|
return f.Usage
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
// GetCategory returns the category for the flag
|
||||||
func (f *UintFlag) IsVisible() bool {
|
func (f *UintFlag) GetCategory() string {
|
||||||
return !f.Hidden
|
return f.Category
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
func (f *UintFlag) Apply(set *flag.FlagSet) error {
|
func (f *UintFlag) Apply(set *flag.FlagSet) error {
|
||||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
|
||||||
if val != "" {
|
if val != "" {
|
||||||
valInt, err := strconv.ParseUint(val, 0, 64)
|
valInt, err := strconv.ParseUint(val, 0, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not parse %q as uint value for flag %s: %s", val, f.Name, err)
|
return fmt.Errorf("could not parse %q as uint value from %s for flag %s: %s", val, source, f.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Value = uint(valInt)
|
f.Value = uint(valInt)
|
||||||
@ -88,10 +52,28 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the flag’s value in the given Context.
|
||||||
|
func (f *UintFlag) Get(ctx *Context) uint {
|
||||||
|
return ctx.Uint(f.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -6,42 +6,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Uint64Flag is a flag with type uint64
|
|
||||||
type Uint64Flag struct {
|
|
||||||
Name string
|
|
||||||
Aliases []string
|
|
||||||
Usage string
|
|
||||||
EnvVars []string
|
|
||||||
FilePath string
|
|
||||||
Required bool
|
|
||||||
Hidden bool
|
|
||||||
Value uint64
|
|
||||||
DefaultText string
|
|
||||||
Destination *uint64
|
|
||||||
HasBeenSet bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSet returns whether or not the flag has been set through env or file
|
|
||||||
func (f *Uint64Flag) IsSet() bool {
|
|
||||||
return f.HasBeenSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a readable representation of this value
|
|
||||||
// (for usage defaults)
|
|
||||||
func (f *Uint64Flag) String() string {
|
|
||||||
return FlagStringer(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Names returns the names of the flag
|
|
||||||
func (f *Uint64Flag) Names() []string {
|
|
||||||
return flagNames(f.Name, f.Aliases)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRequired returns whether or not the flag is required
|
|
||||||
func (f *Uint64Flag) IsRequired() bool {
|
|
||||||
return f.Required
|
|
||||||
}
|
|
||||||
|
|
||||||
// TakesValue returns true of the flag takes a value, otherwise false
|
// TakesValue returns true of the flag takes a value, otherwise false
|
||||||
func (f *Uint64Flag) TakesValue() bool {
|
func (f *Uint64Flag) TakesValue() bool {
|
||||||
return true
|
return true
|
||||||
@ -52,18 +16,18 @@ func (f *Uint64Flag) GetUsage() string {
|
|||||||
return f.Usage
|
return f.Usage
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
// GetCategory returns the category for the flag
|
||||||
func (f *Uint64Flag) IsVisible() bool {
|
func (f *Uint64Flag) GetCategory() string {
|
||||||
return !f.Hidden
|
return f.Category
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply populates the flag given the flag set and environment
|
// Apply populates the flag given the flag set and environment
|
||||||
func (f *Uint64Flag) Apply(set *flag.FlagSet) error {
|
func (f *Uint64Flag) Apply(set *flag.FlagSet) error {
|
||||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
|
||||||
if val != "" {
|
if val != "" {
|
||||||
valInt, err := strconv.ParseUint(val, 0, 64)
|
valInt, err := strconv.ParseUint(val, 0, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not parse %q as uint64 value for flag %s: %s", val, f.Name, err)
|
return fmt.Errorf("could not parse %q as uint64 value from %s for flag %s: %s", val, source, f.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Value = valInt
|
f.Value = valInt
|
||||||
@ -88,10 +52,28 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the flag’s value in the given Context.
|
||||||
|
func (f *Uint64Flag) Get(ctx *Context) uint64 {
|
||||||
|
return ctx.Uint64(f.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
|
12
go.mod
12
go.mod
@ -1,9 +1,13 @@
|
|||||||
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.2
|
||||||
gopkg.in/yaml.v2 v2.2.8
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673
|
||||||
|
golang.org/x/text v0.3.7
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
16
go.sum
16
go.sum
@ -1,10 +1,18 @@
|
|||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
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/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/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=
|
||||||
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||||
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||||
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
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.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
2318
godoc-current.txt
Normal file
2318
godoc-current.txt
Normal file
File diff suppressed because it is too large
Load Diff
198
help.go
198
help.go
@ -10,34 +10,39 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
helpName = "help"
|
||||||
|
helpAlias = "h"
|
||||||
|
)
|
||||||
|
|
||||||
var helpCommand = &Command{
|
var helpCommand = &Command{
|
||||||
Name: "help",
|
Name: helpName,
|
||||||
Aliases: []string{"h"},
|
Aliases: []string{helpAlias},
|
||||||
Usage: "Shows a list of commands or help for one command",
|
Usage: "Shows a list of commands or help for one command",
|
||||||
ArgsUsage: "[command]",
|
ArgsUsage: "[command]",
|
||||||
Action: func(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
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var helpSubcommand = &Command{
|
var helpSubcommand = &Command{
|
||||||
Name: "help",
|
Name: helpName,
|
||||||
Aliases: []string{"h"},
|
Aliases: []string{helpAlias},
|
||||||
Usage: "Shows a list of commands or help for one command",
|
Usage: "Shows a list of commands or help for one command",
|
||||||
ArgsUsage: "[command]",
|
ArgsUsage: "[command]",
|
||||||
Action: func(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)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,6 +64,11 @@ var HelpPrinter helpPrinter = printHelp
|
|||||||
// HelpPrinterCustom is a function that writes the help output. It is used as
|
// HelpPrinterCustom is a function that writes the help output. It is used as
|
||||||
// the default implementation of HelpPrinter, and may be called directly if
|
// the default implementation of HelpPrinter, and may be called directly if
|
||||||
// the ExtraInfo field is set on an App.
|
// the ExtraInfo field is set on an App.
|
||||||
|
//
|
||||||
|
// In the default implementation, if the customFuncs argument contains a
|
||||||
|
// "wrapAt" key, which is a function which takes no arguments and returns
|
||||||
|
// an int, this int value will be used to produce a "wrap" function used
|
||||||
|
// by the default template to wrap long lines.
|
||||||
var HelpPrinterCustom helpPrinterCustom = printHelpCustom
|
var HelpPrinterCustom helpPrinterCustom = printHelpCustom
|
||||||
|
|
||||||
// VersionPrinter prints the version for the App
|
// VersionPrinter prints the version for the App
|
||||||
@ -71,30 +81,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) {
|
||||||
@ -102,7 +112,7 @@ func printCommandSuggestions(commands []*Command, writer io.Writer) {
|
|||||||
if command.Hidden {
|
if command.Hidden {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if os.Getenv("_CLI_ZSH_AUTOCOMPLETE_HACK") == "1" {
|
if strings.HasSuffix(os.Getenv("SHELL"), "zsh") {
|
||||||
for _, name := range command.Names() {
|
for _, name := range command.Names() {
|
||||||
_, _ = fmt.Fprintf(writer, "%s:%s\n", name, command.Usage)
|
_, _ = fmt.Fprintf(writer, "%s:%s\n", name, command.Usage)
|
||||||
}
|
}
|
||||||
@ -159,23 +169,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +224,13 @@ func ShowCommandHelp(ctx *Context, command string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ctx.App.CommandNotFound == nil {
|
if ctx.App.CommandNotFound == nil {
|
||||||
return Exit(fmt.Sprintf("No help topic for '%v'", command), 3)
|
errMsg := fmt.Sprintf("No help topic for '%v'", command)
|
||||||
|
if ctx.App.Suggest {
|
||||||
|
if suggestion := SuggestCommand(ctx.App.Commands, command); suggestion != "" {
|
||||||
|
errMsg += ". " + suggestion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Exit(errMsg, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.App.CommandNotFound(ctx, command)
|
ctx.App.CommandNotFound(ctx, command)
|
||||||
@ -221,32 +244,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,12 +291,29 @@ func ShowCommandCompletions(ctx *Context, command string) {
|
|||||||
// The customFuncs map will be combined with a default template.FuncMap to
|
// The customFuncs map will be combined with a default template.FuncMap to
|
||||||
// allow using arbitrary functions in template rendering.
|
// allow using arbitrary functions in template rendering.
|
||||||
func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs map[string]interface{}) {
|
func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs map[string]interface{}) {
|
||||||
|
|
||||||
|
const maxLineLength = 10000
|
||||||
|
|
||||||
funcMap := template.FuncMap{
|
funcMap := template.FuncMap{
|
||||||
"join": strings.Join,
|
"join": strings.Join,
|
||||||
"indent": indent,
|
"indent": indent,
|
||||||
"nindent": nindent,
|
"nindent": nindent,
|
||||||
"trim": strings.TrimSpace,
|
"trim": strings.TrimSpace,
|
||||||
|
"wrap": func(input string, offset int) string { return wrap(input, offset, maxLineLength) },
|
||||||
|
"offset": offset,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if customFuncs["wrapAt"] != nil {
|
||||||
|
if wa, ok := customFuncs["wrapAt"]; ok {
|
||||||
|
if waf, ok := wa.(func() int); ok {
|
||||||
|
wrapAt := waf()
|
||||||
|
customFuncs["wrap"] = func(input string, offset int) string {
|
||||||
|
return wrap(input, offset, wrapAt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for key, value := range customFuncs {
|
for key, value := range customFuncs {
|
||||||
funcMap[key] = value
|
funcMap[key] = value
|
||||||
}
|
}
|
||||||
@ -297,20 +337,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 +366,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 +390,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -384,3 +424,55 @@ func indent(spaces int, v string) string {
|
|||||||
func nindent(spaces int, v string) string {
|
func nindent(spaces int, v string) string {
|
||||||
return "\n" + indent(spaces, v)
|
return "\n" + indent(spaces, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func wrap(input string, offset int, wrapAt int) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
|
||||||
|
lines := strings.Split(input, "\n")
|
||||||
|
|
||||||
|
padding := strings.Repeat(" ", offset)
|
||||||
|
|
||||||
|
for i, line := range lines {
|
||||||
|
if i != 0 {
|
||||||
|
sb.WriteString(padding)
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString(wrapLine(line, offset, wrapAt, padding))
|
||||||
|
|
||||||
|
if i != len(lines)-1 {
|
||||||
|
sb.WriteString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapLine(input string, offset int, wrapAt int, padding string) string {
|
||||||
|
if wrapAt <= offset || len(input) <= wrapAt-offset {
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
lineWidth := wrapAt - offset
|
||||||
|
words := strings.Fields(input)
|
||||||
|
if len(words) == 0 {
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapped := words[0]
|
||||||
|
spaceLeft := lineWidth - len(wrapped)
|
||||||
|
for _, word := range words[1:] {
|
||||||
|
if len(word)+1 > spaceLeft {
|
||||||
|
wrapped += "\n" + padding + word
|
||||||
|
spaceLeft = lineWidth - len(word)
|
||||||
|
} else {
|
||||||
|
wrapped += " " + word
|
||||||
|
spaceLeft -= 1 + len(word)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
}
|
||||||
|
|
||||||
|
func offset(input string, fixed int) int {
|
||||||
|
return len(input) + fixed
|
||||||
|
}
|
||||||
|
309
help_test.go
309
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,311 @@ 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) {
|
||||||
|
origEnv := os.Environ()
|
||||||
|
origArgv := os.Args
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
os.Args = origArgv
|
||||||
|
resetEnv(origEnv)
|
||||||
|
})
|
||||||
|
|
||||||
|
os.Setenv("SHELL", "bash")
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrappedHelp(t *testing.T) {
|
||||||
|
|
||||||
|
// Reset HelpPrinter after this test.
|
||||||
|
defer func(old helpPrinter) {
|
||||||
|
HelpPrinter = old
|
||||||
|
}(HelpPrinter)
|
||||||
|
|
||||||
|
output := new(bytes.Buffer)
|
||||||
|
app := &App{
|
||||||
|
Writer: output,
|
||||||
|
Flags: []Flag{
|
||||||
|
&BoolFlag{Name: "foo",
|
||||||
|
Aliases: []string{"h"},
|
||||||
|
Usage: "here's a really long help text line, let's see where it wraps. blah blah blah and so on.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Usage: "here's a sample App.Usage string long enough that it should be wrapped in this test",
|
||||||
|
UsageText: "i'm not sure how App.UsageText differs from App.Usage, but this should also be wrapped in this test",
|
||||||
|
// TODO: figure out how to make ArgsUsage appear in the help text, and test that
|
||||||
|
Description: `here's a sample App.Description string long enough that it should be wrapped in this test
|
||||||
|
|
||||||
|
with a newline
|
||||||
|
and an indented line`,
|
||||||
|
Copyright: `Here's a sample copyright text string long enough that it should be wrapped.
|
||||||
|
Including newlines.
|
||||||
|
And also indented lines.
|
||||||
|
|
||||||
|
|
||||||
|
And then another long line. Blah blah blah does anybody ever read these things?`,
|
||||||
|
}
|
||||||
|
|
||||||
|
c := NewContext(app, nil, nil)
|
||||||
|
|
||||||
|
HelpPrinter = func(w io.Writer, templ string, data interface{}) {
|
||||||
|
funcMap := map[string]interface{}{
|
||||||
|
"wrapAt": func() int {
|
||||||
|
return 30
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
HelpPrinterCustom(w, templ, data, funcMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = ShowAppHelp(c)
|
||||||
|
|
||||||
|
expected := `NAME:
|
||||||
|
- here's a sample
|
||||||
|
App.Usage string long
|
||||||
|
enough that it should be
|
||||||
|
wrapped in this test
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
i'm not sure how
|
||||||
|
App.UsageText differs from
|
||||||
|
App.Usage, but this should
|
||||||
|
also be wrapped in this
|
||||||
|
test
|
||||||
|
|
||||||
|
DESCRIPTION:
|
||||||
|
here's a sample
|
||||||
|
App.Description string long
|
||||||
|
enough that it should be
|
||||||
|
wrapped in this test
|
||||||
|
|
||||||
|
with a newline
|
||||||
|
and an indented line
|
||||||
|
|
||||||
|
GLOBAL OPTIONS:
|
||||||
|
--foo, -h here's a
|
||||||
|
really long help text
|
||||||
|
line, let's see where it
|
||||||
|
wraps. blah blah blah
|
||||||
|
and so on. (default:
|
||||||
|
false)
|
||||||
|
|
||||||
|
COPYRIGHT:
|
||||||
|
Here's a sample copyright
|
||||||
|
text string long enough
|
||||||
|
that it should be wrapped.
|
||||||
|
Including newlines.
|
||||||
|
And also indented lines.
|
||||||
|
|
||||||
|
|
||||||
|
And then another long line.
|
||||||
|
Blah blah blah does anybody
|
||||||
|
ever read these things?
|
||||||
|
`
|
||||||
|
|
||||||
|
if output.String() != expected {
|
||||||
|
t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s",
|
||||||
|
output.String(), expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrappedCommandHelp(t *testing.T) {
|
||||||
|
|
||||||
|
// Reset HelpPrinter after this test.
|
||||||
|
defer func(old helpPrinter) {
|
||||||
|
HelpPrinter = old
|
||||||
|
}(HelpPrinter)
|
||||||
|
|
||||||
|
output := new(bytes.Buffer)
|
||||||
|
app := &App{
|
||||||
|
Writer: output,
|
||||||
|
Commands: []*Command{
|
||||||
|
{
|
||||||
|
Name: "add",
|
||||||
|
Aliases: []string{"a"},
|
||||||
|
Usage: "add a task to the list",
|
||||||
|
UsageText: "this is an even longer way of describing adding a task to the list",
|
||||||
|
Description: "and a description long enough to wrap in this test case",
|
||||||
|
Action: func(c *Context) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c := NewContext(app, nil, nil)
|
||||||
|
|
||||||
|
HelpPrinter = func(w io.Writer, templ string, data interface{}) {
|
||||||
|
funcMap := map[string]interface{}{
|
||||||
|
"wrapAt": func() int {
|
||||||
|
return 30
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
HelpPrinterCustom(w, templ, data, funcMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = ShowCommandHelp(c, "add")
|
||||||
|
|
||||||
|
expected := `NAME:
|
||||||
|
- add a task to the list
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
this is an even longer way
|
||||||
|
of describing adding a task
|
||||||
|
to the list
|
||||||
|
|
||||||
|
DESCRIPTION:
|
||||||
|
and a description long
|
||||||
|
enough to wrap in this test
|
||||||
|
case
|
||||||
|
`
|
||||||
|
|
||||||
|
if output.String() != expected {
|
||||||
|
t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s",
|
||||||
|
output.String(), expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrappedSubcommandHelp(t *testing.T) {
|
||||||
|
|
||||||
|
// Reset HelpPrinter after this test.
|
||||||
|
defer func(old helpPrinter) {
|
||||||
|
HelpPrinter = old
|
||||||
|
}(HelpPrinter)
|
||||||
|
|
||||||
|
output := new(bytes.Buffer)
|
||||||
|
app := &App{
|
||||||
|
Name: "cli.test",
|
||||||
|
Writer: output,
|
||||||
|
Commands: []*Command{
|
||||||
|
{
|
||||||
|
Name: "bar",
|
||||||
|
Aliases: []string{"a"},
|
||||||
|
Usage: "add a task to the list",
|
||||||
|
UsageText: "this is an even longer way of describing adding a task to the list",
|
||||||
|
Description: "and a description long enough to wrap in this test case",
|
||||||
|
Action: func(c *Context) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
Subcommands: []*Command{
|
||||||
|
{
|
||||||
|
Name: "grok",
|
||||||
|
Usage: "remove an existing template",
|
||||||
|
UsageText: "longer usage text goes here, la la la, hopefully this is long enough to wrap even more",
|
||||||
|
Action: func(c *Context) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
HelpPrinter = func(w io.Writer, templ string, data interface{}) {
|
||||||
|
funcMap := map[string]interface{}{
|
||||||
|
"wrapAt": func() int {
|
||||||
|
return 30
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
HelpPrinterCustom(w, templ, data, funcMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = app.Run([]string{"foo", "bar", "grok", "--help"})
|
||||||
|
|
||||||
|
expected := `NAME:
|
||||||
|
cli.test bar grok - remove
|
||||||
|
an
|
||||||
|
existing
|
||||||
|
template
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
longer usage text goes
|
||||||
|
here, la la la, hopefully
|
||||||
|
this is long enough to wrap
|
||||||
|
even more
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
--help, -h show help (default: false)
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
if output.String() != expected {
|
||||||
|
t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s",
|
||||||
|
output.String(), expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,19 +6,45 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var packages = []string{"cli", "altsrc"}
|
const (
|
||||||
|
badNewsEmoji = "🚨"
|
||||||
|
goodNewsEmoji = "✨"
|
||||||
|
checksPassedEmoji = "✅"
|
||||||
|
|
||||||
|
v2diffWarning = `
|
||||||
|
# The unified diff above indicates that the public API surface area
|
||||||
|
# has changed. If you feel that the changes are acceptable and adhere
|
||||||
|
# to the semantic versioning promise of the v2.x series described in
|
||||||
|
# docs/CONTRIBUTING.md, please run the following command to promote
|
||||||
|
# the current go docs:
|
||||||
|
#
|
||||||
|
# make v2approve
|
||||||
|
#
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
top, err := func() (string, error) {
|
||||||
|
if v, err := sh("git", "rev-parse", "--show-toplevel"); err == nil {
|
||||||
|
return strings.TrimSpace(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Getwd()
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
|
|
||||||
app.Name = "builder"
|
app.Name = "builder"
|
||||||
@ -37,22 +63,56 @@ func main() {
|
|||||||
Name: "gfmrun",
|
Name: "gfmrun",
|
||||||
Action: GfmrunActionFunc,
|
Action: GfmrunActionFunc,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "toc",
|
|
||||||
Action: TocActionFunc,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "check-binary-size",
|
Name: "check-binary-size",
|
||||||
Action: checkBinarySizeActionFunc,
|
Action: checkBinarySizeActionFunc,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "generate",
|
||||||
|
Action: GenerateActionFunc,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "v2diff",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.BoolFlag{Name: "color", Value: false},
|
||||||
|
},
|
||||||
|
Action: V2Diff,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "v2approve",
|
||||||
|
Action: V2Approve,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "tags",
|
||||||
|
Usage: "set build tags",
|
||||||
|
},
|
||||||
|
&cli.PathFlag{
|
||||||
|
Name: "top",
|
||||||
|
Value: top,
|
||||||
|
},
|
||||||
|
&cli.StringSliceFlag{
|
||||||
|
Name: "packages",
|
||||||
|
Value: cli.NewStringSlice("cli", "altsrc", "internal/build", "internal/genflags"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := app.Run(os.Args)
|
if err := app.Run(os.Args); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sh(exe string, args ...string) (string, error) {
|
||||||
|
cmd := exec.Command(exe, args...)
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "# ---> %s\n", cmd)
|
||||||
|
outBytes, err := cmd.Output()
|
||||||
|
return string(outBytes), err
|
||||||
|
}
|
||||||
|
|
||||||
func runCmd(arg string, args ...string) error {
|
func runCmd(arg string, args ...string) error {
|
||||||
cmd := exec.Command(arg, args...)
|
cmd := exec.Command(arg, args...)
|
||||||
|
|
||||||
@ -60,80 +120,102 @@ func runCmd(arg string, args ...string) error {
|
|||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "# ---> %s\n", cmd)
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func VetActionFunc(_ *cli.Context) error {
|
func VetActionFunc(cCtx *cli.Context) error {
|
||||||
return runCmd("go", "vet")
|
return runCmd("go", "vet", cCtx.Path("top")+"/...")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestActionFunc(c *cli.Context) error {
|
func TestActionFunc(c *cli.Context) error {
|
||||||
for _, pkg := range packages {
|
tags := c.String("tags")
|
||||||
var packageName string
|
|
||||||
|
|
||||||
if pkg == "cli" {
|
for _, pkg := range c.StringSlice("packages") {
|
||||||
packageName = "github.com/urfave/cli/v2"
|
packageName := "github.com/urfave/cli/v2"
|
||||||
} else {
|
|
||||||
|
if pkg != "cli" {
|
||||||
packageName = fmt.Sprintf("github.com/urfave/cli/v2/%s", pkg)
|
packageName = fmt.Sprintf("github.com/urfave/cli/v2/%s", pkg)
|
||||||
}
|
}
|
||||||
|
|
||||||
coverProfile := fmt.Sprintf("--coverprofile=%s.coverprofile", pkg)
|
if err := runCmd(
|
||||||
|
"go", "test",
|
||||||
err := runCmd("go", "test", "-v", coverProfile, packageName)
|
"-tags", tags,
|
||||||
if err != nil {
|
"-v",
|
||||||
|
"--coverprofile", pkg+".coverprofile",
|
||||||
|
"--covermode", "count",
|
||||||
|
"--cover", packageName,
|
||||||
|
packageName,
|
||||||
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return testCleanup()
|
return testCleanup(c.StringSlice("packages"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCleanup() error {
|
func testCleanup(packages []string) error {
|
||||||
var out bytes.Buffer
|
out := &bytes.Buffer{}
|
||||||
|
|
||||||
|
fmt.Fprintf(out, "mode: count\n")
|
||||||
|
|
||||||
for _, pkg := range packages {
|
for _, pkg := range packages {
|
||||||
file, err := os.Open(fmt.Sprintf("%s.coverprofile", pkg))
|
filename := pkg + ".coverprofile"
|
||||||
|
|
||||||
|
lineBytes, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := ioutil.ReadAll(file)
|
lines := strings.Split(string(lineBytes), "\n")
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
out.Write(b)
|
fmt.Fprintf(out, strings.Join(lines[1:], "\n"))
|
||||||
err = file.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.Remove(fmt.Sprintf("%s.coverprofile", pkg))
|
if err := os.Remove(filename); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
outFile, err := os.Create("coverage.txt")
|
return os.WriteFile("coverage.txt", out.Bytes(), 0644)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = out.WriteTo(outFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = outFile.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GfmrunActionFunc(c *cli.Context) error {
|
func GfmrunActionFunc(cCtx *cli.Context) error {
|
||||||
filename := c.Args().Get(0)
|
top := cCtx.Path("top")
|
||||||
|
|
||||||
|
bash, err := exec.LookPath("bash")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Setenv("SHELL", bash)
|
||||||
|
|
||||||
|
tmpDir, err := os.MkdirTemp("", "urfave-cli*")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Chdir(tmpDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(cCtx.App.ErrWriter, "# ---> workspace/TMPDIR is %q\n", tmpDir)
|
||||||
|
|
||||||
|
if err := runCmd("go", "work", "init", top); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Setenv("TMPDIR", tmpDir)
|
||||||
|
|
||||||
|
if err := os.Chdir(wd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := cCtx.Args().Get(0)
|
||||||
if filename == "" {
|
if filename == "" {
|
||||||
filename = "README.md"
|
filename = "README.md"
|
||||||
}
|
}
|
||||||
@ -162,26 +244,11 @@ func GfmrunActionFunc(c *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return runCmd("gfmrun", "-c", fmt.Sprint(counter), "-s", filename)
|
if err := runCmd("gfmrun", "-c", fmt.Sprint(counter), "-s", filename); err != nil {
|
||||||
}
|
|
||||||
|
|
||||||
func TocActionFunc(c *cli.Context) error {
|
|
||||||
filename := c.Args().Get(0)
|
|
||||||
if filename == "" {
|
|
||||||
filename = "README.md"
|
|
||||||
}
|
|
||||||
|
|
||||||
err := runCmd("markdown-toc", "-i", filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = runCmd("git", "diff", "--exit-code")
|
return os.RemoveAll(tmpDir)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkBinarySizeActionFunc checks the size of an example binary to ensure that we are keeping size down
|
// checkBinarySizeActionFunc checks the size of an example binary to ensure that we are keeping size down
|
||||||
@ -193,22 +260,26 @@ 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
|
|
||||||
desiredMaxBinarySize = 2.2
|
desiredMaxBinarySize = 2.2
|
||||||
badNewsEmoji = "🚨"
|
|
||||||
goodNewsEmoji = "✨"
|
|
||||||
checksPassedEmoji = "✅"
|
|
||||||
mbStringFormatter = "%.1fMB"
|
mbStringFormatter = "%.1fMB"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
desiredMinBinarySize := 1.675
|
||||||
|
|
||||||
|
tags := c.String("tags")
|
||||||
|
|
||||||
|
if strings.Contains(tags, "urfave_cli_no_docs") {
|
||||||
|
desiredMinBinarySize = 1.39
|
||||||
|
}
|
||||||
|
|
||||||
// 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 +341,72 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSize(sourcePath string, builtPath string) (size int64, err error) {
|
func GenerateActionFunc(cCtx *cli.Context) error {
|
||||||
|
top := cCtx.Path("top")
|
||||||
|
|
||||||
|
cliDocs, err := sh("go", "doc", "-all", top)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
altsrcDocs, err := sh("go", "doc", "-all", filepath.Join(top, "altsrc"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(
|
||||||
|
filepath.Join(top, "godoc-current.txt"),
|
||||||
|
[]byte(cliDocs+altsrcDocs),
|
||||||
|
0644,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return runCmd("go", "generate", cCtx.Path("top")+"/...")
|
||||||
|
}
|
||||||
|
|
||||||
|
func V2Diff(cCtx *cli.Context) error {
|
||||||
|
os.Chdir(cCtx.Path("top"))
|
||||||
|
|
||||||
|
err := runCmd(
|
||||||
|
"diff",
|
||||||
|
"--ignore-all-space",
|
||||||
|
"--minimal",
|
||||||
|
"--color="+func() string {
|
||||||
|
if cCtx.Bool("color") {
|
||||||
|
return "always"
|
||||||
|
}
|
||||||
|
return "auto"
|
||||||
|
}(),
|
||||||
|
"--unified",
|
||||||
|
"--label=a/godoc",
|
||||||
|
filepath.Join("testdata", "godoc-v2.x.txt"),
|
||||||
|
"--label=b/godoc",
|
||||||
|
"godoc-current.txt",
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("# %v ---> Hey! <---\n", badNewsEmoji)
|
||||||
|
fmt.Println(strings.TrimSpace(v2diffWarning))
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func V2Approve(cCtx *cli.Context) error {
|
||||||
|
top := cCtx.Path("top")
|
||||||
|
|
||||||
|
return runCmd(
|
||||||
|
"cp",
|
||||||
|
"-v",
|
||||||
|
filepath.Join(top, "godoc-current.txt"),
|
||||||
|
filepath.Join(top, "testdata", "godoc-v2.x.txt"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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{""})
|
||||||
}
|
}
|
||||||
|
163
internal/genflags/cmd/genflags/main.go
Normal file
163
internal/genflags/cmd/genflags/main.go
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"github.com/urfave/cli/v2/internal/genflags"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultPackageName = "cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sh(ctx context.Context, exe string, args ...string) (string, error) {
|
||||||
|
cmd := exec.CommandContext(ctx, exe, args...)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
outBytes, err := cmd.Output()
|
||||||
|
return string(outBytes), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
top := "../../"
|
||||||
|
if v, err := sh(ctx, "git", "rev-parse", "--show-toplevel"); err == nil {
|
||||||
|
top = strings.TrimSpace(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
app := &cli.App{
|
||||||
|
Name: "genflags",
|
||||||
|
Usage: "Generate flag types for urfave/cli",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.PathFlag{
|
||||||
|
Name: "flag-spec-yaml",
|
||||||
|
Aliases: []string{"f"},
|
||||||
|
Value: filepath.Join(top, "flag-spec.yaml"),
|
||||||
|
},
|
||||||
|
&cli.PathFlag{
|
||||||
|
Name: "generated-output",
|
||||||
|
Aliases: []string{"o"},
|
||||||
|
Value: filepath.Join(top, "zz_generated.flags.go"),
|
||||||
|
},
|
||||||
|
&cli.PathFlag{
|
||||||
|
Name: "generated-test-output",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Value: filepath.Join(top, "zz_generated.flags_test.go"),
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "generated-package-name",
|
||||||
|
Aliases: []string{"p"},
|
||||||
|
Value: defaultPackageName,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "generated-test-package-name",
|
||||||
|
Aliases: []string{"T"},
|
||||||
|
Value: defaultPackageName + "_test",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "urfave-cli-namespace",
|
||||||
|
Aliases: []string{"n"},
|
||||||
|
Value: "",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "urfave-cli-test-namespace",
|
||||||
|
Aliases: []string{"N"},
|
||||||
|
Value: "cli.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: runGenFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := app.RunContext(ctx, os.Args); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runGenFlags(cCtx *cli.Context) error {
|
||||||
|
specBytes, err := os.ReadFile(cCtx.Path("flag-spec-yaml"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
spec := &genflags.Spec{}
|
||||||
|
if err := yaml.Unmarshal(specBytes, spec); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cCtx.IsSet("generated-package-name") {
|
||||||
|
spec.PackageName = strings.TrimSpace(cCtx.String("generated-package-name"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(spec.PackageName) == "" {
|
||||||
|
spec.PackageName = defaultPackageName
|
||||||
|
}
|
||||||
|
|
||||||
|
if cCtx.IsSet("generated-test-package-name") {
|
||||||
|
spec.TestPackageName = strings.TrimSpace(cCtx.String("generated-test-package-name"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(spec.TestPackageName) == "" {
|
||||||
|
spec.TestPackageName = defaultPackageName + "_test"
|
||||||
|
}
|
||||||
|
|
||||||
|
if cCtx.IsSet("urfave-cli-namespace") {
|
||||||
|
spec.UrfaveCLINamespace = strings.TrimSpace(cCtx.String("urfave-cli-namespace"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if cCtx.IsSet("urfave-cli-test-namespace") {
|
||||||
|
spec.UrfaveCLITestNamespace = strings.TrimSpace(cCtx.String("urfave-cli-test-namespace"))
|
||||||
|
} else {
|
||||||
|
spec.UrfaveCLITestNamespace = "cli."
|
||||||
|
}
|
||||||
|
|
||||||
|
genTmpl, err := template.New("gen").Parse(genflags.TemplateString)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
genTestTmpl, err := template.New("gen_test").Parse(genflags.TestTemplateString)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
genBuf := &bytes.Buffer{}
|
||||||
|
if err := genTmpl.Execute(genBuf, spec); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
genTestBuf := &bytes.Buffer{}
|
||||||
|
if err := genTestTmpl.Execute(genTestBuf, spec); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(cCtx.Path("generated-output"), genBuf.Bytes(), 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(cCtx.Path("generated-test-output"), genTestBuf.Bytes(), 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := sh(cCtx.Context, "goimports", "-w", cCtx.Path("generated-output")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := sh(cCtx.Context, "goimports", "-w", cCtx.Path("generated-test-output")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
68
internal/genflags/generated.gotmpl
Normal file
68
internal/genflags/generated.gotmpl
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// WARNING: this file is generated. DO NOT EDIT
|
||||||
|
|
||||||
|
package {{.PackageName}}
|
||||||
|
|
||||||
|
{{range .SortedFlagTypes}}
|
||||||
|
// {{.TypeName}} is a flag with type {{if .ValuePointer}}*{{end}}{{.GoType}}
|
||||||
|
type {{.TypeName}} struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Category string
|
||||||
|
DefaultText string
|
||||||
|
FilePath string
|
||||||
|
Usage string
|
||||||
|
|
||||||
|
Required bool
|
||||||
|
Hidden bool
|
||||||
|
HasBeenSet bool
|
||||||
|
|
||||||
|
Value {{if .ValuePointer}}*{{end}}{{.GoType}}
|
||||||
|
Destination *{{.GoType}}
|
||||||
|
|
||||||
|
Aliases []string
|
||||||
|
EnvVars []string
|
||||||
|
|
||||||
|
{{range .StructFields}}
|
||||||
|
{{.Name}} {{.Type}}
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
|
||||||
|
{{if .GenerateFmtStringerInterface}}
|
||||||
|
// String returns a readable representation of this value (for usage defaults)
|
||||||
|
func (f *{{.TypeName}}) String() string {
|
||||||
|
return {{$.UrfaveCLINamespace}}FlagStringer(f)
|
||||||
|
}
|
||||||
|
{{end}}{{/* /if .GenerateFmtStringerInterface */}}
|
||||||
|
|
||||||
|
{{if .GenerateFlagInterface}}
|
||||||
|
// IsSet returns whether or not the flag has been set through env or file
|
||||||
|
func (f *{{.TypeName}}) IsSet() bool {
|
||||||
|
return f.HasBeenSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names returns the names of the flag
|
||||||
|
func (f *{{.TypeName}}) Names() []string {
|
||||||
|
return {{$.UrfaveCLINamespace}}FlagNames(f.Name, f.Aliases)
|
||||||
|
}
|
||||||
|
|
||||||
|
{{end}}{{/* /if .GenerateFlagInterface */}}
|
||||||
|
|
||||||
|
{{if .GenerateRequiredFlagInterface}}
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f *{{.TypeName}}) IsRequired() bool {
|
||||||
|
return f.Required
|
||||||
|
}
|
||||||
|
{{end}}{{/* /if .GenerateRequiredFlagInterface */}}
|
||||||
|
|
||||||
|
{{if .GenerateVisibleFlagInterface}}
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *{{.TypeName}}) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
{{end}}{{/* /if .GenerateVisibleFlagInterface */}}
|
||||||
|
{{end}}{{/* /range .SortedFlagTypes */}}
|
||||||
|
|
||||||
|
// vim{{/* 👻 */}}:ro
|
||||||
|
{{/*
|
||||||
|
vim:filetype=gotexttmpl
|
||||||
|
*/}}
|
43
internal/genflags/generated_test.gotmpl
Normal file
43
internal/genflags/generated_test.gotmpl
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// WARNING: this file is generated. DO NOT EDIT
|
||||||
|
|
||||||
|
package {{.TestPackageName}}
|
||||||
|
|
||||||
|
{{range .SortedFlagTypes}}
|
||||||
|
{{if .GenerateFlagInterface}}
|
||||||
|
func Test{{.TypeName}}_SatisfiesFlagInterface(t *testing.T) {
|
||||||
|
var f {{$.UrfaveCLITestNamespace}}Flag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{}
|
||||||
|
|
||||||
|
_ = f.IsSet()
|
||||||
|
_ = f.Names()
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .GenerateFmtStringerInterface}}
|
||||||
|
func Test{{.TypeName}}_SatisfiesFmtStringerInterface(t *testing.T) {
|
||||||
|
var f fmt.Stringer = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{}
|
||||||
|
|
||||||
|
_ = f.String()
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .GenerateRequiredFlagInterface}}
|
||||||
|
func Test{{.TypeName}}_SatisfiesRequiredFlagInterface(t *testing.T) {
|
||||||
|
var f {{$.UrfaveCLITestNamespace}}RequiredFlag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{}
|
||||||
|
|
||||||
|
_ = f.IsRequired()
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .GenerateVisibleFlagInterface}}
|
||||||
|
func Test{{.TypeName}}_SatisfiesVisibleFlagInterface(t *testing.T) {
|
||||||
|
var f {{$.UrfaveCLITestNamespace}}VisibleFlag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{}
|
||||||
|
|
||||||
|
_ = f.IsVisible()
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
// vim{{/* 👻 */}}:ro
|
||||||
|
{{/*
|
||||||
|
vim:filetype=gotexttmpl
|
||||||
|
*/}}
|
34
internal/genflags/package.go
Normal file
34
internal/genflags/package.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package genflags
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//go:embed generated.gotmpl
|
||||||
|
TemplateString string
|
||||||
|
|
||||||
|
//go:embed generated_test.gotmpl
|
||||||
|
TestTemplateString string
|
||||||
|
|
||||||
|
titler = cases.Title(language.Und, cases.NoLower)
|
||||||
|
)
|
||||||
|
|
||||||
|
func TypeName(goType string, fc *FlagTypeConfig) string {
|
||||||
|
if fc != nil && strings.TrimSpace(fc.TypeName) != "" {
|
||||||
|
return strings.TrimSpace(fc.TypeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
dotSplit := strings.Split(goType, ".")
|
||||||
|
goType = dotSplit[len(dotSplit)-1]
|
||||||
|
|
||||||
|
if strings.HasPrefix(goType, "[]") {
|
||||||
|
return titler.String(strings.TrimPrefix(goType, "[]")) + "SliceFlag"
|
||||||
|
}
|
||||||
|
|
||||||
|
return titler.String(goType) + "Flag"
|
||||||
|
}
|
41
internal/genflags/package_test.go
Normal file
41
internal/genflags/package_test.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package genflags_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2/internal/genflags"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTypeName(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
gt string
|
||||||
|
fc *genflags.FlagTypeConfig
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{gt: "int", fc: nil, expected: "IntFlag"},
|
||||||
|
{gt: "int", fc: &genflags.FlagTypeConfig{}, expected: "IntFlag"},
|
||||||
|
{gt: "int", fc: &genflags.FlagTypeConfig{TypeName: "VeryIntyFlag"}, expected: "VeryIntyFlag"},
|
||||||
|
{gt: "[]bool", fc: nil, expected: "BoolSliceFlag"},
|
||||||
|
{gt: "[]bool", fc: &genflags.FlagTypeConfig{}, expected: "BoolSliceFlag"},
|
||||||
|
{gt: "[]bool", fc: &genflags.FlagTypeConfig{TypeName: "ManyTruthsFlag"}, expected: "ManyTruthsFlag"},
|
||||||
|
{gt: "time.Rumination", fc: nil, expected: "RuminationFlag"},
|
||||||
|
{gt: "time.Rumination", fc: &genflags.FlagTypeConfig{}, expected: "RuminationFlag"},
|
||||||
|
{gt: "time.Rumination", fc: &genflags.FlagTypeConfig{TypeName: "PonderFlag"}, expected: "PonderFlag"},
|
||||||
|
} {
|
||||||
|
t.Run(
|
||||||
|
fmt.Sprintf("type=%s,cfg=%v", tc.gt, func() string {
|
||||||
|
if tc.fc != nil {
|
||||||
|
return tc.fc.TypeName
|
||||||
|
}
|
||||||
|
return "nil"
|
||||||
|
}()),
|
||||||
|
func(ct *testing.T) {
|
||||||
|
actual := genflags.TypeName(tc.gt, tc.fc)
|
||||||
|
if tc.expected != actual {
|
||||||
|
ct.Errorf("expected %q, got %q", tc.expected, actual)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
108
internal/genflags/spec.go
Normal file
108
internal/genflags/spec.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package genflags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Spec struct {
|
||||||
|
FlagTypes map[string]*FlagTypeConfig `yaml:"flag_types"`
|
||||||
|
PackageName string `yaml:"package_name"`
|
||||||
|
TestPackageName string `yaml:"test_package_name"`
|
||||||
|
UrfaveCLINamespace string `yaml:"urfave_cli_namespace"`
|
||||||
|
UrfaveCLITestNamespace string `yaml:"urfave_cli_test_namespace"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gfs *Spec) SortedFlagTypes() []*FlagType {
|
||||||
|
typeNames := []string{}
|
||||||
|
|
||||||
|
for name := range gfs.FlagTypes {
|
||||||
|
if strings.HasPrefix(name, "[]") {
|
||||||
|
name = strings.TrimPrefix(name, "[]") + "Slice"
|
||||||
|
}
|
||||||
|
|
||||||
|
typeNames = append(typeNames, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(typeNames)
|
||||||
|
|
||||||
|
ret := make([]*FlagType, len(typeNames))
|
||||||
|
|
||||||
|
for i, typeName := range typeNames {
|
||||||
|
ret[i] = &FlagType{
|
||||||
|
GoType: typeName,
|
||||||
|
Config: gfs.FlagTypes[typeName],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
type FlagTypeConfig struct {
|
||||||
|
SkipInterfaces []string `yaml:"skip_interfaces"`
|
||||||
|
StructFields []*FlagStructField `yaml:"struct_fields"`
|
||||||
|
TypeName string `yaml:"type_name"`
|
||||||
|
ValuePointer bool `yaml:"value_pointer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FlagStructField struct {
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
}
|
||||||
|
|
||||||
|
type FlagType struct {
|
||||||
|
GoType string
|
||||||
|
Config *FlagTypeConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ft *FlagType) StructFields() []*FlagStructField {
|
||||||
|
if ft.Config == nil || ft.Config.StructFields == nil {
|
||||||
|
return []*FlagStructField{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ft.Config.StructFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ft *FlagType) ValuePointer() bool {
|
||||||
|
if ft.Config == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return ft.Config.ValuePointer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ft *FlagType) TypeName() string {
|
||||||
|
return TypeName(ft.GoType, ft.Config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ft *FlagType) GenerateFmtStringerInterface() bool {
|
||||||
|
return ft.skipInterfaceNamed("fmt.Stringer")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ft *FlagType) GenerateFlagInterface() bool {
|
||||||
|
return ft.skipInterfaceNamed("Flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ft *FlagType) GenerateRequiredFlagInterface() bool {
|
||||||
|
return ft.skipInterfaceNamed("RequiredFlag")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ft *FlagType) GenerateVisibleFlagInterface() bool {
|
||||||
|
return ft.skipInterfaceNamed("VisibleFlag")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ft *FlagType) skipInterfaceNamed(name string) bool {
|
||||||
|
if ft.Config == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
lowName := strings.ToLower(name)
|
||||||
|
|
||||||
|
for _, interfaceName := range ft.Config.SkipInterfaces {
|
||||||
|
if strings.ToLower(interfaceName) == lowName {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
112
internal/genflags/spec_test.go
Normal file
112
internal/genflags/spec_test.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package genflags_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2/internal/genflags"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSpec_SortedFlagTypes(t *testing.T) {
|
||||||
|
spec := &genflags.Spec{
|
||||||
|
FlagTypes: map[string]*genflags.FlagTypeConfig{
|
||||||
|
"nerf": &genflags.FlagTypeConfig{},
|
||||||
|
"gerf": nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := spec.SortedFlagTypes()
|
||||||
|
expected := []*genflags.FlagType{
|
||||||
|
{
|
||||||
|
GoType: "gerf",
|
||||||
|
Config: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
GoType: "nerf",
|
||||||
|
Config: &genflags.FlagTypeConfig{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Errorf("expected %#v, got %#v", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func genFlagType() *genflags.FlagType {
|
||||||
|
return &genflags.FlagType{
|
||||||
|
GoType: "blerf",
|
||||||
|
Config: &genflags.FlagTypeConfig{
|
||||||
|
SkipInterfaces: []string{"fmt.Stringer"},
|
||||||
|
StructFields: []*genflags.FlagStructField{
|
||||||
|
{
|
||||||
|
Name: "Foibles",
|
||||||
|
Type: "int",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Hoopled",
|
||||||
|
Type: "bool",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TypeName: "YeOldeBlerfFlag",
|
||||||
|
ValuePointer: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlagType_StructFields(t *testing.T) {
|
||||||
|
ft := genFlagType()
|
||||||
|
|
||||||
|
sf := ft.StructFields()
|
||||||
|
if 2 != len(sf) {
|
||||||
|
t.Errorf("expected 2 struct fields, got %v", len(sf))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if "Foibles" != sf[0].Name {
|
||||||
|
t.Errorf("expected struct field order to be retained")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlagType_ValuePointer(t *testing.T) {
|
||||||
|
ft := genFlagType()
|
||||||
|
|
||||||
|
if !ft.ValuePointer() {
|
||||||
|
t.Errorf("expected ValuePointer to be true")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ft.Config = nil
|
||||||
|
|
||||||
|
if ft.ValuePointer() {
|
||||||
|
t.Errorf("expected ValuePointer to be false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlagType_GenerateFmtStringerInterface(t *testing.T) {
|
||||||
|
ft := genFlagType()
|
||||||
|
|
||||||
|
if ft.GenerateFmtStringerInterface() {
|
||||||
|
t.Errorf("expected GenerateFmtStringerInterface to be false")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ft.Config = nil
|
||||||
|
|
||||||
|
if !ft.GenerateFmtStringerInterface() {
|
||||||
|
t.Errorf("expected GenerateFmtStringerInterface to be true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlagType_GenerateFlagInterface(t *testing.T) {
|
||||||
|
ft := genFlagType()
|
||||||
|
|
||||||
|
if !ft.GenerateFlagInterface() {
|
||||||
|
t.Errorf("expected GenerateFlagInterface to be true")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ft.Config = nil
|
||||||
|
|
||||||
|
if !ft.GenerateFlagInterface() {
|
||||||
|
t.Errorf("expected GenerateFlagInterface to be true")
|
||||||
|
}
|
||||||
|
}
|
5
mkdocs-requirements.txt
Normal file
5
mkdocs-requirements.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
mkdocs-git-revision-date-localized-plugin~=1.0
|
||||||
|
mkdocs-material-extensions~=1.0
|
||||||
|
mkdocs-material~=8.2
|
||||||
|
mkdocs~=1.3
|
||||||
|
pygments~=2.12
|
62
mkdocs.yml
Normal file
62
mkdocs.yml
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# NOTE: the mkdocs dependencies will need to be installed out of
|
||||||
|
# band until this whole thing gets more automated:
|
||||||
|
#
|
||||||
|
# pip install -r mkdocs-requirements.txt
|
||||||
|
#
|
||||||
|
|
||||||
|
site_name: urfave/cli
|
||||||
|
site_url: https://cli.urfave.org/
|
||||||
|
repo_url: https://github.com/urfave/cli
|
||||||
|
edit_uri: edit/main/docs/
|
||||||
|
nav:
|
||||||
|
- Home: index.md
|
||||||
|
- v2 Manual: v2/index.md
|
||||||
|
- v1 Manual: v1/index.md
|
||||||
|
theme:
|
||||||
|
name: material
|
||||||
|
palette:
|
||||||
|
- media: "(prefers-color-scheme: light)"
|
||||||
|
scheme: default
|
||||||
|
toggle:
|
||||||
|
icon: material/brightness-4
|
||||||
|
name: dark mode
|
||||||
|
- media: "(prefers-color-scheme: dark)"
|
||||||
|
scheme: slate
|
||||||
|
toggle:
|
||||||
|
icon: material/brightness-7
|
||||||
|
name: light mode
|
||||||
|
plugins:
|
||||||
|
- git-revision-date-localized
|
||||||
|
- search
|
||||||
|
# NOTE: this is the recommended configuration from
|
||||||
|
# https://squidfunk.github.io/mkdocs-material/setup/extensions/#recommended-configuration
|
||||||
|
markdown_extensions:
|
||||||
|
- abbr
|
||||||
|
- admonition
|
||||||
|
- attr_list
|
||||||
|
- def_list
|
||||||
|
- footnotes
|
||||||
|
- meta
|
||||||
|
- md_in_html
|
||||||
|
- toc:
|
||||||
|
permalink: true
|
||||||
|
- pymdownx.arithmatex:
|
||||||
|
generic: true
|
||||||
|
- pymdownx.betterem:
|
||||||
|
smart_enable: all
|
||||||
|
- pymdownx.caret
|
||||||
|
- pymdownx.details
|
||||||
|
- pymdownx.emoji:
|
||||||
|
emoji_index: !!python/name:materialx.emoji.twemoji
|
||||||
|
emoji_generator: !!python/name:materialx.emoji.to_svg
|
||||||
|
- pymdownx.highlight
|
||||||
|
- pymdownx.inlinehilite
|
||||||
|
- pymdownx.keys
|
||||||
|
- pymdownx.mark
|
||||||
|
- pymdownx.smartsymbols
|
||||||
|
- pymdownx.superfences
|
||||||
|
- pymdownx.tabbed:
|
||||||
|
alternate_style: true
|
||||||
|
- pymdownx.tasklist:
|
||||||
|
custom_checkbox: true
|
||||||
|
- pymdownx.tilde
|
18
parse.go
18
parse.go
@ -26,9 +26,8 @@ func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComple
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
errStr := err.Error()
|
trimmed, trimErr := flagFromError(err)
|
||||||
trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: -")
|
if trimErr != nil {
|
||||||
if errStr == trimmed {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,6 +66,19 @@ func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComple
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const providedButNotDefinedErrMsg = "flag provided but not defined: -"
|
||||||
|
|
||||||
|
// flagFromError tries to parse a provided flag from an error message. If the
|
||||||
|
// parsing fials, it returns the input error and an empty string
|
||||||
|
func flagFromError(err error) (string, error) {
|
||||||
|
errStr := err.Error()
|
||||||
|
trimmed := strings.TrimPrefix(errStr, providedButNotDefinedErrMsg)
|
||||||
|
if errStr == trimmed {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return trimmed, nil
|
||||||
|
}
|
||||||
|
|
||||||
func splitShortOptions(set *flag.FlagSet, arg string) []string {
|
func splitShortOptions(set *flag.FlagSet, arg string) []string {
|
||||||
shortFlagsExist := func(s string) bool {
|
shortFlagsExist := func(s string) bool {
|
||||||
for _, c := range s[1:] {
|
for _, c := range s[1:] {
|
||||||
|
293
sliceflag.go
Normal file
293
sliceflag.go
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
//go:build go1.18
|
||||||
|
// +build go1.18
|
||||||
|
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// SliceFlag extends implementations like StringSliceFlag and IntSliceFlag with support for using slices directly,
|
||||||
|
// as Value and/or Destination.
|
||||||
|
// See also SliceFlagTarget, MultiStringFlag, MultiFloat64Flag, MultiInt64Flag, MultiIntFlag.
|
||||||
|
SliceFlag[T SliceFlagTarget[E], S ~[]E, E any] struct {
|
||||||
|
Target T
|
||||||
|
Value S
|
||||||
|
Destination *S
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceFlagTarget models a target implementation for use with SliceFlag.
|
||||||
|
// The three methods, SetValue, SetDestination, and GetDestination, are necessary to propagate Value and
|
||||||
|
// Destination, where Value is propagated inwards (initially), and Destination is propagated outwards (on every
|
||||||
|
// update).
|
||||||
|
SliceFlagTarget[E any] interface {
|
||||||
|
Flag
|
||||||
|
RequiredFlag
|
||||||
|
DocGenerationFlag
|
||||||
|
VisibleFlag
|
||||||
|
CategorizableFlag
|
||||||
|
|
||||||
|
// SetValue should propagate the given slice to the target, ideally as a new value.
|
||||||
|
// Note that a nil slice should nil/clear any existing value (modelled as ~[]E).
|
||||||
|
SetValue(slice []E)
|
||||||
|
// SetDestination should propagate the given slice to the target, ideally as a new value.
|
||||||
|
// Note that a nil slice should nil/clear any existing value (modelled as ~*[]E).
|
||||||
|
SetDestination(slice []E)
|
||||||
|
// GetDestination should return the current value referenced by any destination, or nil if nil/unset.
|
||||||
|
GetDestination() []E
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiStringFlag extends StringSliceFlag with support for using slices directly, as Value and/or Destination.
|
||||||
|
// See also SliceFlag.
|
||||||
|
MultiStringFlag = SliceFlag[*StringSliceFlag, []string, string]
|
||||||
|
|
||||||
|
// MultiFloat64Flag extends Float64SliceFlag with support for using slices directly, as Value and/or Destination.
|
||||||
|
// See also SliceFlag.
|
||||||
|
MultiFloat64Flag = SliceFlag[*Float64SliceFlag, []float64, float64]
|
||||||
|
|
||||||
|
// MultiInt64Flag extends Int64SliceFlag with support for using slices directly, as Value and/or Destination.
|
||||||
|
// See also SliceFlag.
|
||||||
|
MultiInt64Flag = SliceFlag[*Int64SliceFlag, []int64, int64]
|
||||||
|
|
||||||
|
// MultiIntFlag extends IntSliceFlag with support for using slices directly, as Value and/or Destination.
|
||||||
|
// See also SliceFlag.
|
||||||
|
MultiIntFlag = SliceFlag[*IntSliceFlag, []int, int]
|
||||||
|
|
||||||
|
flagValueHook struct {
|
||||||
|
value Generic
|
||||||
|
hook func()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// compile time assertions
|
||||||
|
|
||||||
|
_ SliceFlagTarget[string] = (*StringSliceFlag)(nil)
|
||||||
|
_ SliceFlagTarget[string] = (*SliceFlag[*StringSliceFlag, []string, string])(nil)
|
||||||
|
_ SliceFlagTarget[string] = (*MultiStringFlag)(nil)
|
||||||
|
_ SliceFlagTarget[float64] = (*MultiFloat64Flag)(nil)
|
||||||
|
_ SliceFlagTarget[int64] = (*MultiInt64Flag)(nil)
|
||||||
|
_ SliceFlagTarget[int] = (*MultiIntFlag)(nil)
|
||||||
|
|
||||||
|
_ Generic = (*flagValueHook)(nil)
|
||||||
|
_ Serializer = (*flagValueHook)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x *SliceFlag[T, S, E]) Apply(set *flag.FlagSet) error {
|
||||||
|
x.Target.SetValue(x.convertSlice(x.Value))
|
||||||
|
|
||||||
|
destination := x.Destination
|
||||||
|
if destination == nil {
|
||||||
|
x.Target.SetDestination(nil)
|
||||||
|
|
||||||
|
return x.Target.Apply(set)
|
||||||
|
}
|
||||||
|
|
||||||
|
x.Target.SetDestination(x.convertSlice(*destination))
|
||||||
|
|
||||||
|
return applyFlagValueHook(set, x.Target.Apply, func() {
|
||||||
|
*destination = x.Target.GetDestination()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SliceFlag[T, S, E]) convertSlice(slice S) []E {
|
||||||
|
result := make([]E, len(slice))
|
||||||
|
copy(result, slice)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SliceFlag[T, S, E]) SetValue(slice S) {
|
||||||
|
x.Value = slice
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SliceFlag[T, S, E]) SetDestination(slice S) {
|
||||||
|
if slice != nil {
|
||||||
|
x.Destination = &slice
|
||||||
|
} else {
|
||||||
|
x.Destination = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SliceFlag[T, S, E]) GetDestination() S {
|
||||||
|
if destination := x.Destination; destination != nil {
|
||||||
|
return *destination
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SliceFlag[T, S, E]) String() string { return x.Target.String() }
|
||||||
|
func (x *SliceFlag[T, S, E]) Names() []string { return x.Target.Names() }
|
||||||
|
func (x *SliceFlag[T, S, E]) IsSet() bool { return x.Target.IsSet() }
|
||||||
|
func (x *SliceFlag[T, S, E]) IsRequired() bool { return x.Target.IsRequired() }
|
||||||
|
func (x *SliceFlag[T, S, E]) TakesValue() bool { return x.Target.TakesValue() }
|
||||||
|
func (x *SliceFlag[T, S, E]) GetUsage() string { return x.Target.GetUsage() }
|
||||||
|
func (x *SliceFlag[T, S, E]) GetValue() string { return x.Target.GetValue() }
|
||||||
|
func (x *SliceFlag[T, S, E]) GetDefaultText() string { return x.Target.GetDefaultText() }
|
||||||
|
func (x *SliceFlag[T, S, E]) GetEnvVars() []string { return x.Target.GetEnvVars() }
|
||||||
|
func (x *SliceFlag[T, S, E]) IsVisible() bool { return x.Target.IsVisible() }
|
||||||
|
func (x *SliceFlag[T, S, E]) GetCategory() string { return x.Target.GetCategory() }
|
||||||
|
|
||||||
|
func (x *flagValueHook) Set(value string) error {
|
||||||
|
if err := x.value.Set(value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
x.hook()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *flagValueHook) String() string {
|
||||||
|
// note: this is necessary due to the way Go's flag package handles defaults
|
||||||
|
isZeroValue := func(f flag.Value, v string) bool {
|
||||||
|
/*
|
||||||
|
https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/flag/flag.go;drc=2580d0e08d5e9f979b943758d3c49877fb2324cb;l=453
|
||||||
|
|
||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
// Build a zero value of the flag's Value type, and see if the
|
||||||
|
// result of calling its String method equals the value passed in.
|
||||||
|
// This works unless the Value type is itself an interface type.
|
||||||
|
typ := reflect.TypeOf(f)
|
||||||
|
var z reflect.Value
|
||||||
|
if typ.Kind() == reflect.Pointer {
|
||||||
|
z = reflect.New(typ.Elem())
|
||||||
|
} else {
|
||||||
|
z = reflect.Zero(typ)
|
||||||
|
}
|
||||||
|
return v == z.Interface().(flag.Value).String()
|
||||||
|
}
|
||||||
|
if x.value != nil {
|
||||||
|
// only return non-empty if not the same string as returned by the zero value
|
||||||
|
if s := x.value.String(); !isZeroValue(x.value, s) {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ``
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *flagValueHook) Serialize() string {
|
||||||
|
if value, ok := x.value.(Serializer); ok {
|
||||||
|
return value.Serialize()
|
||||||
|
}
|
||||||
|
return x.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyFlagValueHook wraps calls apply then wraps flags to call a hook function on update and after initial apply.
|
||||||
|
func applyFlagValueHook(set *flag.FlagSet, apply func(set *flag.FlagSet) error, hook func()) error {
|
||||||
|
if apply == nil || set == nil || hook == nil {
|
||||||
|
panic(`invalid input`)
|
||||||
|
}
|
||||||
|
var tmp flag.FlagSet
|
||||||
|
if err := apply(&tmp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tmp.VisitAll(func(f *flag.Flag) { set.Var(&flagValueHook{value: f.Value, hook: hook}, f.Name, f.Usage) })
|
||||||
|
hook()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSliceFlagValue is for implementing SliceFlagTarget.SetValue and SliceFlagTarget.SetDestination.
|
||||||
|
// It's e.g. as part of StringSliceFlag.SetValue, using the factory NewStringSlice.
|
||||||
|
func newSliceFlagValue[R any, S ~[]E, E any](factory func(defaults ...E) *R, defaults S) *R {
|
||||||
|
if defaults == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return factory(defaults...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unwrapFlagValue strips any/all *flagValueHook wrappers.
|
||||||
|
func unwrapFlagValue(v flag.Value) flag.Value {
|
||||||
|
for {
|
||||||
|
h, ok := v.(*flagValueHook)
|
||||||
|
if !ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
v = h.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: the methods below are in this file to make use of the build constraint
|
||||||
|
|
||||||
|
func (f *Float64SliceFlag) SetValue(slice []float64) {
|
||||||
|
f.Value = newSliceFlagValue(NewFloat64Slice, slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Float64SliceFlag) SetDestination(slice []float64) {
|
||||||
|
f.Destination = newSliceFlagValue(NewFloat64Slice, slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Float64SliceFlag) GetDestination() []float64 {
|
||||||
|
if destination := f.Destination; destination != nil {
|
||||||
|
return destination.Value()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Int64SliceFlag) SetValue(slice []int64) {
|
||||||
|
f.Value = newSliceFlagValue(NewInt64Slice, slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Int64SliceFlag) SetDestination(slice []int64) {
|
||||||
|
f.Destination = newSliceFlagValue(NewInt64Slice, slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Int64SliceFlag) GetDestination() []int64 {
|
||||||
|
if destination := f.Destination; destination != nil {
|
||||||
|
return destination.Value()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *IntSliceFlag) SetValue(slice []int) {
|
||||||
|
f.Value = newSliceFlagValue(NewIntSlice, slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *IntSliceFlag) SetDestination(slice []int) {
|
||||||
|
f.Destination = newSliceFlagValue(NewIntSlice, slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *IntSliceFlag) GetDestination() []int {
|
||||||
|
if destination := f.Destination; destination != nil {
|
||||||
|
return destination.Value()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *StringSliceFlag) SetValue(slice []string) {
|
||||||
|
f.Value = newSliceFlagValue(NewStringSlice, slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *StringSliceFlag) SetDestination(slice []string) {
|
||||||
|
f.Destination = newSliceFlagValue(NewStringSlice, slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *StringSliceFlag) GetDestination() []string {
|
||||||
|
if destination := f.Destination; destination != nil {
|
||||||
|
return destination.Value()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
10
sliceflag_pre18.go
Normal file
10
sliceflag_pre18.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
//go:build !go1.18
|
||||||
|
// +build !go1.18
|
||||||
|
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
)
|
||||||
|
|
||||||
|
func unwrapFlagValue(v flag.Value) flag.Value { return v }
|
1005
sliceflag_test.go
Normal file
1005
sliceflag_test.go
Normal file
File diff suppressed because it is too large
Load Diff
60
suggestions.go
Normal file
60
suggestions.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/xrash/smetrics"
|
||||||
|
)
|
||||||
|
|
||||||
|
func jaroWinkler(a, b string) float64 {
|
||||||
|
// magic values are from https://github.com/xrash/smetrics/blob/039620a656736e6ad994090895784a7af15e0b80/jaro-winkler.go#L8
|
||||||
|
const (
|
||||||
|
boostThreshold = 0.7
|
||||||
|
prefixSize = 4
|
||||||
|
)
|
||||||
|
return smetrics.JaroWinkler(a, b, boostThreshold, prefixSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func suggestFlag(flags []Flag, provided string, hideHelp bool) string {
|
||||||
|
distance := 0.0
|
||||||
|
suggestion := ""
|
||||||
|
|
||||||
|
for _, flag := range flags {
|
||||||
|
flagNames := flag.Names()
|
||||||
|
if !hideHelp {
|
||||||
|
flagNames = append(flagNames, HelpFlag.Names()...)
|
||||||
|
}
|
||||||
|
for _, name := range flagNames {
|
||||||
|
newDistance := jaroWinkler(name, provided)
|
||||||
|
if newDistance > distance {
|
||||||
|
distance = newDistance
|
||||||
|
suggestion = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(suggestion) == 1 {
|
||||||
|
suggestion = "-" + suggestion
|
||||||
|
} else if len(suggestion) > 1 {
|
||||||
|
suggestion = "--" + suggestion
|
||||||
|
}
|
||||||
|
|
||||||
|
return suggestion
|
||||||
|
}
|
||||||
|
|
||||||
|
// suggestCommand takes a list of commands and a provided string to suggest a
|
||||||
|
// command name
|
||||||
|
func suggestCommand(commands []*Command, provided string) (suggestion string) {
|
||||||
|
distance := 0.0
|
||||||
|
for _, command := range commands {
|
||||||
|
for _, name := range append(command.Names(), helpName, helpAlias) {
|
||||||
|
newDistance := jaroWinkler(name, provided)
|
||||||
|
if newDistance > distance {
|
||||||
|
distance = newDistance
|
||||||
|
suggestion = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(SuggestDidYouMeanTemplate, suggestion)
|
||||||
|
}
|
187
suggestions_test.go
Normal file
187
suggestions_test.go
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSuggestFlag(t *testing.T) {
|
||||||
|
// Given
|
||||||
|
app := testApp()
|
||||||
|
|
||||||
|
for _, testCase := range []struct {
|
||||||
|
provided, expected string
|
||||||
|
}{
|
||||||
|
{"", ""},
|
||||||
|
{"a", "--another-flag"},
|
||||||
|
{"hlp", "--help"},
|
||||||
|
{"k", ""},
|
||||||
|
{"s", "-s"},
|
||||||
|
} {
|
||||||
|
// When
|
||||||
|
res := suggestFlag(app.Flags, testCase.provided, false)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(t, res, testCase.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuggestFlagHideHelp(t *testing.T) {
|
||||||
|
// Given
|
||||||
|
app := testApp()
|
||||||
|
|
||||||
|
// When
|
||||||
|
res := suggestFlag(app.Flags, "hlp", true)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(t, res, "--fl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuggestFlagFromError(t *testing.T) {
|
||||||
|
// Given
|
||||||
|
app := testApp()
|
||||||
|
|
||||||
|
for _, testCase := range []struct {
|
||||||
|
command, provided, expected string
|
||||||
|
}{
|
||||||
|
{"", "hel", "--help"},
|
||||||
|
{"", "soccer", "--socket"},
|
||||||
|
{"config", "anot", "--another-flag"},
|
||||||
|
} {
|
||||||
|
// When
|
||||||
|
res, _ := app.suggestFlagFromError(
|
||||||
|
errors.New(providedButNotDefinedErrMsg+testCase.provided),
|
||||||
|
testCase.command,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(t, res, fmt.Sprintf(SuggestDidYouMeanTemplate+"\n\n", testCase.expected))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuggestFlagFromErrorWrongError(t *testing.T) {
|
||||||
|
// Given
|
||||||
|
app := testApp()
|
||||||
|
|
||||||
|
// When
|
||||||
|
_, err := app.suggestFlagFromError(errors.New("invalid"), "")
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(t, true, err != nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuggestFlagFromErrorWrongCommand(t *testing.T) {
|
||||||
|
// Given
|
||||||
|
app := testApp()
|
||||||
|
|
||||||
|
// When
|
||||||
|
_, err := app.suggestFlagFromError(
|
||||||
|
errors.New(providedButNotDefinedErrMsg+"flag"),
|
||||||
|
"invalid",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(t, true, err != nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuggestFlagFromErrorNoSuggestion(t *testing.T) {
|
||||||
|
// Given
|
||||||
|
app := testApp()
|
||||||
|
|
||||||
|
// When
|
||||||
|
_, err := app.suggestFlagFromError(
|
||||||
|
errors.New(providedButNotDefinedErrMsg+""),
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(t, true, err != nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuggestCommand(t *testing.T) {
|
||||||
|
// Given
|
||||||
|
app := testApp()
|
||||||
|
|
||||||
|
for _, testCase := range []struct {
|
||||||
|
provided, expected string
|
||||||
|
}{
|
||||||
|
{"", ""},
|
||||||
|
{"conf", "config"},
|
||||||
|
{"i", "i"},
|
||||||
|
{"information", "info"},
|
||||||
|
{"not-existing", "info"},
|
||||||
|
} {
|
||||||
|
// When
|
||||||
|
res := suggestCommand(app.Commands, testCase.provided)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(t, res, fmt.Sprintf(SuggestDidYouMeanTemplate, testCase.expected))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleApp_Suggest() {
|
||||||
|
app := &App{
|
||||||
|
Name: "greet",
|
||||||
|
Suggest: true,
|
||||||
|
HideHelp: true,
|
||||||
|
HideHelpCommand: true,
|
||||||
|
CustomAppHelpTemplate: "(this space intentionally left blank)\n",
|
||||||
|
Flags: []Flag{
|
||||||
|
&StringFlag{Name: "name", Value: "squirrel", Usage: "a name to say"},
|
||||||
|
},
|
||||||
|
Action: func(cCtx *Context) error {
|
||||||
|
fmt.Printf("Hello %v\n", cCtx.String("name"))
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run([]string{"greet", "--nema", "chipmunk"})
|
||||||
|
// Output:
|
||||||
|
// Incorrect Usage. flag provided but not defined: -nema
|
||||||
|
//
|
||||||
|
// Did you mean "--name"?
|
||||||
|
//
|
||||||
|
// (this space intentionally left blank)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleApp_Suggest_command() {
|
||||||
|
app := &App{
|
||||||
|
Name: "greet",
|
||||||
|
Suggest: true,
|
||||||
|
HideHelp: true,
|
||||||
|
HideHelpCommand: true,
|
||||||
|
CustomAppHelpTemplate: "(this space intentionally left blank)\n",
|
||||||
|
Flags: []Flag{
|
||||||
|
&StringFlag{Name: "name", Value: "squirrel", Usage: "a name to say"},
|
||||||
|
},
|
||||||
|
Action: func(cCtx *Context) error {
|
||||||
|
fmt.Printf("Hello %v\n", cCtx.String("name"))
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
Commands: []*Command{
|
||||||
|
{
|
||||||
|
Name: "neighbors",
|
||||||
|
CustomHelpTemplate: "(this space intentionally left blank)\n",
|
||||||
|
Flags: []Flag{
|
||||||
|
&BoolFlag{Name: "smiling"},
|
||||||
|
},
|
||||||
|
Action: func(cCtx *Context) error {
|
||||||
|
if cCtx.Bool("smiling") {
|
||||||
|
fmt.Println("😀")
|
||||||
|
}
|
||||||
|
fmt.Println("Hello, neighbors")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run([]string{"greet", "neighbors", "--sliming"})
|
||||||
|
// Output:
|
||||||
|
// Incorrect Usage: flag provided but not defined: -sliming
|
||||||
|
//
|
||||||
|
// Did you mean "--smiling"?
|
||||||
|
//
|
||||||
|
// (this space intentionally left blank)
|
||||||
|
}
|
34
template.go
34
template.go
@ -4,16 +4,16 @@ package cli
|
|||||||
// cli.go uses text/template to render templates. You can
|
// cli.go uses text/template to render templates. You can
|
||||||
// render custom help text by setting this variable.
|
// render custom help text by setting this variable.
|
||||||
var AppHelpTemplate = `NAME:
|
var AppHelpTemplate = `NAME:
|
||||||
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
|
{{$v := offset .Name 6}}{{wrap .Name 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}}
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
{{if .UsageText}}{{.UsageText | nindent 3 | trim}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
|
{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
|
||||||
|
|
||||||
VERSION:
|
VERSION:
|
||||||
{{.Version}}{{end}}{{end}}{{if .Description}}
|
{{.Version}}{{end}}{{end}}{{if .Description}}
|
||||||
|
|
||||||
DESCRIPTION:
|
DESCRIPTION:
|
||||||
{{.Description | nindent 3 | trim}}{{end}}{{if len .Authors}}
|
{{wrap .Description 3}}{{end}}{{if len .Authors}}
|
||||||
|
|
||||||
AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
|
AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
|
||||||
{{range $index, $author := .Authors}}{{if $index}}
|
{{range $index, $author := .Authors}}{{if $index}}
|
||||||
@ -22,34 +22,44 @@ AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
|
|||||||
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
|
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
|
||||||
{{.Name}}:{{range .VisibleCommands}}
|
{{.Name}}:{{range .VisibleCommands}}
|
||||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
|
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
|
||||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
|
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}}
|
||||||
|
|
||||||
|
GLOBAL OPTIONS:{{range .VisibleFlagCategories}}
|
||||||
|
{{if .Name}}{{.Name}}
|
||||||
|
{{end}}{{range .Flags}}{{.}}
|
||||||
|
{{end}}{{end}}{{else}}{{if .VisibleFlags}}
|
||||||
|
|
||||||
GLOBAL OPTIONS:
|
GLOBAL OPTIONS:
|
||||||
{{range $index, $option := .VisibleFlags}}{{if $index}}
|
{{range $index, $option := .VisibleFlags}}{{if $index}}
|
||||||
{{end}}{{$option}}{{end}}{{end}}{{if .Copyright}}
|
{{end}}{{wrap $option.String 6}}{{end}}{{end}}{{end}}{{if .Copyright}}
|
||||||
|
|
||||||
COPYRIGHT:
|
COPYRIGHT:
|
||||||
{{.Copyright}}{{end}}
|
{{wrap .Copyright 3}}{{end}}
|
||||||
`
|
`
|
||||||
|
|
||||||
// CommandHelpTemplate is the text template for the command help topic.
|
// CommandHelpTemplate is the text template for the command help topic.
|
||||||
// cli.go uses text/template to render templates. You can
|
// cli.go uses text/template to render templates. You can
|
||||||
// render custom help text by setting this variable.
|
// render custom help text by setting this variable.
|
||||||
var CommandHelpTemplate = `NAME:
|
var CommandHelpTemplate = `NAME:
|
||||||
{{.HelpName}} - {{.Usage}}
|
{{$v := offset .HelpName 6}}{{wrap .HelpName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}}
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
{{if .UsageText}}{{.UsageText | nindent 3 | trim}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
|
{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
|
||||||
|
|
||||||
CATEGORY:
|
CATEGORY:
|
||||||
{{.Category}}{{end}}{{if .Description}}
|
{{.Category}}{{end}}{{if .Description}}
|
||||||
|
|
||||||
DESCRIPTION:
|
DESCRIPTION:
|
||||||
{{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlags}}
|
{{wrap .Description 3}}{{end}}{{if .VisibleFlagCategories}}
|
||||||
|
|
||||||
|
OPTIONS:{{range .VisibleFlagCategories}}
|
||||||
|
{{if .Name}}{{.Name}}
|
||||||
|
{{end}}{{range .Flags}}{{.}}
|
||||||
|
{{end}}{{end}}{{else}}{{if .VisibleFlags}}
|
||||||
|
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
{{range .VisibleFlags}}{{.}}
|
{{range .VisibleFlags}}{{.}}
|
||||||
{{end}}{{end}}
|
{{end}}{{end}}{{end}}
|
||||||
`
|
`
|
||||||
|
|
||||||
// SubcommandHelpTemplate is the text template for the subcommand help topic.
|
// SubcommandHelpTemplate is the text template for the subcommand help topic.
|
||||||
@ -59,10 +69,10 @@ var SubcommandHelpTemplate = `NAME:
|
|||||||
{{.HelpName}} - {{.Usage}}
|
{{.HelpName}} - {{.Usage}}
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
{{if .UsageText}}{{.UsageText | nindent 3 | trim}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}}
|
{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}}
|
||||||
|
|
||||||
DESCRIPTION:
|
DESCRIPTION:
|
||||||
{{.Description | nindent 3 | trim}}{{end}}
|
{{wrap .Description 3}}{{end}}
|
||||||
|
|
||||||
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
|
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
|
||||||
{{.Name}}:{{range .VisibleCommands}}
|
{{.Name}}:{{range .VisibleCommands}}
|
||||||
|
1
testdata/empty.yml
vendored
Normal file
1
testdata/empty.yml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
# empty file
|
2318
testdata/godoc-v2.x.txt
vendored
Normal file
2318
testdata/godoc-v2.x.txt
vendored
Normal file
File diff suppressed because it is too large
Load Diff
674
zz_generated.flags.go
Normal file
674
zz_generated.flags.go
Normal file
@ -0,0 +1,674 @@
|
|||||||
|
// WARNING: this file is generated. DO NOT EDIT
|
||||||
|
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Float64SliceFlag is a flag with type *Float64Slice
|
||||||
|
type Float64SliceFlag struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Category string
|
||||||
|
DefaultText string
|
||||||
|
FilePath string
|
||||||
|
Usage string
|
||||||
|
|
||||||
|
Required bool
|
||||||
|
Hidden bool
|
||||||
|
HasBeenSet bool
|
||||||
|
|
||||||
|
Value *Float64Slice
|
||||||
|
Destination *Float64Slice
|
||||||
|
|
||||||
|
Aliases []string
|
||||||
|
EnvVars []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet returns whether or not the flag has been set through env or file
|
||||||
|
func (f *Float64SliceFlag) IsSet() bool {
|
||||||
|
return f.HasBeenSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names returns the names of the flag
|
||||||
|
func (f *Float64SliceFlag) Names() []string {
|
||||||
|
return FlagNames(f.Name, f.Aliases)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f *Float64SliceFlag) IsRequired() bool {
|
||||||
|
return f.Required
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *Float64SliceFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenericFlag is a flag with type Generic
|
||||||
|
type GenericFlag struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Category string
|
||||||
|
DefaultText string
|
||||||
|
FilePath string
|
||||||
|
Usage string
|
||||||
|
|
||||||
|
Required bool
|
||||||
|
Hidden bool
|
||||||
|
HasBeenSet bool
|
||||||
|
|
||||||
|
Value Generic
|
||||||
|
Destination *Generic
|
||||||
|
|
||||||
|
Aliases []string
|
||||||
|
EnvVars []string
|
||||||
|
|
||||||
|
TakesFile bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a readable representation of this value (for usage defaults)
|
||||||
|
func (f *GenericFlag) String() string {
|
||||||
|
return FlagStringer(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet returns whether or not the flag has been set through env or file
|
||||||
|
func (f *GenericFlag) IsSet() bool {
|
||||||
|
return f.HasBeenSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names returns the names of the flag
|
||||||
|
func (f *GenericFlag) Names() []string {
|
||||||
|
return FlagNames(f.Name, f.Aliases)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f *GenericFlag) IsRequired() bool {
|
||||||
|
return f.Required
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *GenericFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64SliceFlag is a flag with type *Int64Slice
|
||||||
|
type Int64SliceFlag struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Category string
|
||||||
|
DefaultText string
|
||||||
|
FilePath string
|
||||||
|
Usage string
|
||||||
|
|
||||||
|
Required bool
|
||||||
|
Hidden bool
|
||||||
|
HasBeenSet bool
|
||||||
|
|
||||||
|
Value *Int64Slice
|
||||||
|
Destination *Int64Slice
|
||||||
|
|
||||||
|
Aliases []string
|
||||||
|
EnvVars []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet returns whether or not the flag has been set through env or file
|
||||||
|
func (f *Int64SliceFlag) IsSet() bool {
|
||||||
|
return f.HasBeenSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names returns the names of the flag
|
||||||
|
func (f *Int64SliceFlag) Names() []string {
|
||||||
|
return FlagNames(f.Name, f.Aliases)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f *Int64SliceFlag) IsRequired() bool {
|
||||||
|
return f.Required
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *Int64SliceFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntSliceFlag is a flag with type *IntSlice
|
||||||
|
type IntSliceFlag struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Category string
|
||||||
|
DefaultText string
|
||||||
|
FilePath string
|
||||||
|
Usage string
|
||||||
|
|
||||||
|
Required bool
|
||||||
|
Hidden bool
|
||||||
|
HasBeenSet bool
|
||||||
|
|
||||||
|
Value *IntSlice
|
||||||
|
Destination *IntSlice
|
||||||
|
|
||||||
|
Aliases []string
|
||||||
|
EnvVars []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet returns whether or not the flag has been set through env or file
|
||||||
|
func (f *IntSliceFlag) IsSet() bool {
|
||||||
|
return f.HasBeenSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names returns the names of the flag
|
||||||
|
func (f *IntSliceFlag) Names() []string {
|
||||||
|
return FlagNames(f.Name, f.Aliases)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f *IntSliceFlag) IsRequired() bool {
|
||||||
|
return f.Required
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *IntSliceFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathFlag is a flag with type Path
|
||||||
|
type PathFlag struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Category string
|
||||||
|
DefaultText string
|
||||||
|
FilePath string
|
||||||
|
Usage string
|
||||||
|
|
||||||
|
Required bool
|
||||||
|
Hidden bool
|
||||||
|
HasBeenSet bool
|
||||||
|
|
||||||
|
Value Path
|
||||||
|
Destination *Path
|
||||||
|
|
||||||
|
Aliases []string
|
||||||
|
EnvVars []string
|
||||||
|
|
||||||
|
TakesFile bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a readable representation of this value (for usage defaults)
|
||||||
|
func (f *PathFlag) String() string {
|
||||||
|
return FlagStringer(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet returns whether or not the flag has been set through env or file
|
||||||
|
func (f *PathFlag) IsSet() bool {
|
||||||
|
return f.HasBeenSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names returns the names of the flag
|
||||||
|
func (f *PathFlag) Names() []string {
|
||||||
|
return FlagNames(f.Name, f.Aliases)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f *PathFlag) IsRequired() bool {
|
||||||
|
return f.Required
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *PathFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringSliceFlag is a flag with type *StringSlice
|
||||||
|
type StringSliceFlag struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Category string
|
||||||
|
DefaultText string
|
||||||
|
FilePath string
|
||||||
|
Usage string
|
||||||
|
|
||||||
|
Required bool
|
||||||
|
Hidden bool
|
||||||
|
HasBeenSet bool
|
||||||
|
|
||||||
|
Value *StringSlice
|
||||||
|
Destination *StringSlice
|
||||||
|
|
||||||
|
Aliases []string
|
||||||
|
EnvVars []string
|
||||||
|
|
||||||
|
TakesFile bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet returns whether or not the flag has been set through env or file
|
||||||
|
func (f *StringSliceFlag) IsSet() bool {
|
||||||
|
return f.HasBeenSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names returns the names of the flag
|
||||||
|
func (f *StringSliceFlag) Names() []string {
|
||||||
|
return FlagNames(f.Name, f.Aliases)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f *StringSliceFlag) IsRequired() bool {
|
||||||
|
return f.Required
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *StringSliceFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimestampFlag is a flag with type *Timestamp
|
||||||
|
type TimestampFlag struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Category string
|
||||||
|
DefaultText string
|
||||||
|
FilePath string
|
||||||
|
Usage string
|
||||||
|
|
||||||
|
Required bool
|
||||||
|
Hidden bool
|
||||||
|
HasBeenSet bool
|
||||||
|
|
||||||
|
Value *Timestamp
|
||||||
|
Destination *Timestamp
|
||||||
|
|
||||||
|
Aliases []string
|
||||||
|
EnvVars []string
|
||||||
|
|
||||||
|
Layout string
|
||||||
|
|
||||||
|
Timezone *time.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a readable representation of this value (for usage defaults)
|
||||||
|
func (f *TimestampFlag) String() string {
|
||||||
|
return FlagStringer(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet returns whether or not the flag has been set through env or file
|
||||||
|
func (f *TimestampFlag) IsSet() bool {
|
||||||
|
return f.HasBeenSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names returns the names of the flag
|
||||||
|
func (f *TimestampFlag) Names() []string {
|
||||||
|
return FlagNames(f.Name, f.Aliases)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f *TimestampFlag) IsRequired() bool {
|
||||||
|
return f.Required
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *TimestampFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolFlag is a flag with type bool
|
||||||
|
type BoolFlag struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Category string
|
||||||
|
DefaultText string
|
||||||
|
FilePath string
|
||||||
|
Usage string
|
||||||
|
|
||||||
|
Required bool
|
||||||
|
Hidden bool
|
||||||
|
HasBeenSet bool
|
||||||
|
|
||||||
|
Value bool
|
||||||
|
Destination *bool
|
||||||
|
|
||||||
|
Aliases []string
|
||||||
|
EnvVars []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a readable representation of this value (for usage defaults)
|
||||||
|
func (f *BoolFlag) String() string {
|
||||||
|
return FlagStringer(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet returns whether or not the flag has been set through env or file
|
||||||
|
func (f *BoolFlag) IsSet() bool {
|
||||||
|
return f.HasBeenSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names returns the names of the flag
|
||||||
|
func (f *BoolFlag) Names() []string {
|
||||||
|
return FlagNames(f.Name, f.Aliases)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f *BoolFlag) IsRequired() bool {
|
||||||
|
return f.Required
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *BoolFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64Flag is a flag with type float64
|
||||||
|
type Float64Flag struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Category string
|
||||||
|
DefaultText string
|
||||||
|
FilePath string
|
||||||
|
Usage string
|
||||||
|
|
||||||
|
Required bool
|
||||||
|
Hidden bool
|
||||||
|
HasBeenSet bool
|
||||||
|
|
||||||
|
Value float64
|
||||||
|
Destination *float64
|
||||||
|
|
||||||
|
Aliases []string
|
||||||
|
EnvVars []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a readable representation of this value (for usage defaults)
|
||||||
|
func (f *Float64Flag) String() string {
|
||||||
|
return FlagStringer(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet returns whether or not the flag has been set through env or file
|
||||||
|
func (f *Float64Flag) IsSet() bool {
|
||||||
|
return f.HasBeenSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names returns the names of the flag
|
||||||
|
func (f *Float64Flag) Names() []string {
|
||||||
|
return FlagNames(f.Name, f.Aliases)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f *Float64Flag) IsRequired() bool {
|
||||||
|
return f.Required
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *Float64Flag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntFlag is a flag with type int
|
||||||
|
type IntFlag struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Category string
|
||||||
|
DefaultText string
|
||||||
|
FilePath string
|
||||||
|
Usage string
|
||||||
|
|
||||||
|
Required bool
|
||||||
|
Hidden bool
|
||||||
|
HasBeenSet bool
|
||||||
|
|
||||||
|
Value int
|
||||||
|
Destination *int
|
||||||
|
|
||||||
|
Aliases []string
|
||||||
|
EnvVars []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a readable representation of this value (for usage defaults)
|
||||||
|
func (f *IntFlag) String() string {
|
||||||
|
return FlagStringer(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet returns whether or not the flag has been set through env or file
|
||||||
|
func (f *IntFlag) IsSet() bool {
|
||||||
|
return f.HasBeenSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names returns the names of the flag
|
||||||
|
func (f *IntFlag) Names() []string {
|
||||||
|
return FlagNames(f.Name, f.Aliases)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f *IntFlag) IsRequired() bool {
|
||||||
|
return f.Required
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *IntFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Flag is a flag with type int64
|
||||||
|
type Int64Flag struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Category string
|
||||||
|
DefaultText string
|
||||||
|
FilePath string
|
||||||
|
Usage string
|
||||||
|
|
||||||
|
Required bool
|
||||||
|
Hidden bool
|
||||||
|
HasBeenSet bool
|
||||||
|
|
||||||
|
Value int64
|
||||||
|
Destination *int64
|
||||||
|
|
||||||
|
Aliases []string
|
||||||
|
EnvVars []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a readable representation of this value (for usage defaults)
|
||||||
|
func (f *Int64Flag) String() string {
|
||||||
|
return FlagStringer(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet returns whether or not the flag has been set through env or file
|
||||||
|
func (f *Int64Flag) IsSet() bool {
|
||||||
|
return f.HasBeenSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names returns the names of the flag
|
||||||
|
func (f *Int64Flag) Names() []string {
|
||||||
|
return FlagNames(f.Name, f.Aliases)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f *Int64Flag) IsRequired() bool {
|
||||||
|
return f.Required
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *Int64Flag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringFlag is a flag with type string
|
||||||
|
type StringFlag struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Category string
|
||||||
|
DefaultText string
|
||||||
|
FilePath string
|
||||||
|
Usage string
|
||||||
|
|
||||||
|
Required bool
|
||||||
|
Hidden bool
|
||||||
|
HasBeenSet bool
|
||||||
|
|
||||||
|
Value string
|
||||||
|
Destination *string
|
||||||
|
|
||||||
|
Aliases []string
|
||||||
|
EnvVars []string
|
||||||
|
|
||||||
|
TakesFile bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a readable representation of this value (for usage defaults)
|
||||||
|
func (f *StringFlag) String() string {
|
||||||
|
return FlagStringer(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet returns whether or not the flag has been set through env or file
|
||||||
|
func (f *StringFlag) IsSet() bool {
|
||||||
|
return f.HasBeenSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names returns the names of the flag
|
||||||
|
func (f *StringFlag) Names() []string {
|
||||||
|
return FlagNames(f.Name, f.Aliases)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f *StringFlag) IsRequired() bool {
|
||||||
|
return f.Required
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *StringFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
// DurationFlag is a flag with type time.Duration
|
||||||
|
type DurationFlag struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Category string
|
||||||
|
DefaultText string
|
||||||
|
FilePath string
|
||||||
|
Usage string
|
||||||
|
|
||||||
|
Required bool
|
||||||
|
Hidden bool
|
||||||
|
HasBeenSet bool
|
||||||
|
|
||||||
|
Value time.Duration
|
||||||
|
Destination *time.Duration
|
||||||
|
|
||||||
|
Aliases []string
|
||||||
|
EnvVars []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a readable representation of this value (for usage defaults)
|
||||||
|
func (f *DurationFlag) String() string {
|
||||||
|
return FlagStringer(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet returns whether or not the flag has been set through env or file
|
||||||
|
func (f *DurationFlag) IsSet() bool {
|
||||||
|
return f.HasBeenSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names returns the names of the flag
|
||||||
|
func (f *DurationFlag) Names() []string {
|
||||||
|
return FlagNames(f.Name, f.Aliases)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f *DurationFlag) IsRequired() bool {
|
||||||
|
return f.Required
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *DurationFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
// UintFlag is a flag with type uint
|
||||||
|
type UintFlag struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Category string
|
||||||
|
DefaultText string
|
||||||
|
FilePath string
|
||||||
|
Usage string
|
||||||
|
|
||||||
|
Required bool
|
||||||
|
Hidden bool
|
||||||
|
HasBeenSet bool
|
||||||
|
|
||||||
|
Value uint
|
||||||
|
Destination *uint
|
||||||
|
|
||||||
|
Aliases []string
|
||||||
|
EnvVars []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a readable representation of this value (for usage defaults)
|
||||||
|
func (f *UintFlag) String() string {
|
||||||
|
return FlagStringer(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet returns whether or not the flag has been set through env or file
|
||||||
|
func (f *UintFlag) IsSet() bool {
|
||||||
|
return f.HasBeenSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names returns the names of the flag
|
||||||
|
func (f *UintFlag) Names() []string {
|
||||||
|
return FlagNames(f.Name, f.Aliases)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f *UintFlag) IsRequired() bool {
|
||||||
|
return f.Required
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *UintFlag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64Flag is a flag with type uint64
|
||||||
|
type Uint64Flag struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Category string
|
||||||
|
DefaultText string
|
||||||
|
FilePath string
|
||||||
|
Usage string
|
||||||
|
|
||||||
|
Required bool
|
||||||
|
Hidden bool
|
||||||
|
HasBeenSet bool
|
||||||
|
|
||||||
|
Value uint64
|
||||||
|
Destination *uint64
|
||||||
|
|
||||||
|
Aliases []string
|
||||||
|
EnvVars []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a readable representation of this value (for usage defaults)
|
||||||
|
func (f *Uint64Flag) String() string {
|
||||||
|
return FlagStringer(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet returns whether or not the flag has been set through env or file
|
||||||
|
func (f *Uint64Flag) IsSet() bool {
|
||||||
|
return f.HasBeenSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names returns the names of the flag
|
||||||
|
func (f *Uint64Flag) Names() []string {
|
||||||
|
return FlagNames(f.Name, f.Aliases)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRequired returns whether or not the flag is required
|
||||||
|
func (f *Uint64Flag) IsRequired() bool {
|
||||||
|
return f.Required
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||||
|
func (f *Uint64Flag) IsVisible() bool {
|
||||||
|
return !f.Hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
// vim:ro
|
363
zz_generated.flags_test.go
Normal file
363
zz_generated.flags_test.go
Normal file
@ -0,0 +1,363 @@
|
|||||||
|
// WARNING: this file is generated. DO NOT EDIT
|
||||||
|
|
||||||
|
package cli_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFloat64SliceFlag_SatisfiesFlagInterface(t *testing.T) {
|
||||||
|
var f cli.Flag = &cli.Float64SliceFlag{}
|
||||||
|
|
||||||
|
_ = f.IsSet()
|
||||||
|
_ = f.Names()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloat64SliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
|
||||||
|
var f cli.RequiredFlag = &cli.Float64SliceFlag{}
|
||||||
|
|
||||||
|
_ = f.IsRequired()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloat64SliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
|
||||||
|
var f cli.VisibleFlag = &cli.Float64SliceFlag{}
|
||||||
|
|
||||||
|
_ = f.IsVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenericFlag_SatisfiesFlagInterface(t *testing.T) {
|
||||||
|
var f cli.Flag = &cli.GenericFlag{}
|
||||||
|
|
||||||
|
_ = f.IsSet()
|
||||||
|
_ = f.Names()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenericFlag_SatisfiesFmtStringerInterface(t *testing.T) {
|
||||||
|
var f fmt.Stringer = &cli.GenericFlag{}
|
||||||
|
|
||||||
|
_ = f.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenericFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
|
||||||
|
var f cli.RequiredFlag = &cli.GenericFlag{}
|
||||||
|
|
||||||
|
_ = f.IsRequired()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenericFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
|
||||||
|
var f cli.VisibleFlag = &cli.GenericFlag{}
|
||||||
|
|
||||||
|
_ = f.IsVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInt64SliceFlag_SatisfiesFlagInterface(t *testing.T) {
|
||||||
|
var f cli.Flag = &cli.Int64SliceFlag{}
|
||||||
|
|
||||||
|
_ = f.IsSet()
|
||||||
|
_ = f.Names()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInt64SliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
|
||||||
|
var f cli.RequiredFlag = &cli.Int64SliceFlag{}
|
||||||
|
|
||||||
|
_ = f.IsRequired()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInt64SliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
|
||||||
|
var f cli.VisibleFlag = &cli.Int64SliceFlag{}
|
||||||
|
|
||||||
|
_ = f.IsVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntSliceFlag_SatisfiesFlagInterface(t *testing.T) {
|
||||||
|
var f cli.Flag = &cli.IntSliceFlag{}
|
||||||
|
|
||||||
|
_ = f.IsSet()
|
||||||
|
_ = f.Names()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntSliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
|
||||||
|
var f cli.RequiredFlag = &cli.IntSliceFlag{}
|
||||||
|
|
||||||
|
_ = f.IsRequired()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntSliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
|
||||||
|
var f cli.VisibleFlag = &cli.IntSliceFlag{}
|
||||||
|
|
||||||
|
_ = f.IsVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathFlag_SatisfiesFlagInterface(t *testing.T) {
|
||||||
|
var f cli.Flag = &cli.PathFlag{}
|
||||||
|
|
||||||
|
_ = f.IsSet()
|
||||||
|
_ = f.Names()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathFlag_SatisfiesFmtStringerInterface(t *testing.T) {
|
||||||
|
var f fmt.Stringer = &cli.PathFlag{}
|
||||||
|
|
||||||
|
_ = f.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
|
||||||
|
var f cli.RequiredFlag = &cli.PathFlag{}
|
||||||
|
|
||||||
|
_ = f.IsRequired()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
|
||||||
|
var f cli.VisibleFlag = &cli.PathFlag{}
|
||||||
|
|
||||||
|
_ = f.IsVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringSliceFlag_SatisfiesFlagInterface(t *testing.T) {
|
||||||
|
var f cli.Flag = &cli.StringSliceFlag{}
|
||||||
|
|
||||||
|
_ = f.IsSet()
|
||||||
|
_ = f.Names()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringSliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
|
||||||
|
var f cli.RequiredFlag = &cli.StringSliceFlag{}
|
||||||
|
|
||||||
|
_ = f.IsRequired()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringSliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
|
||||||
|
var f cli.VisibleFlag = &cli.StringSliceFlag{}
|
||||||
|
|
||||||
|
_ = f.IsVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimestampFlag_SatisfiesFlagInterface(t *testing.T) {
|
||||||
|
var f cli.Flag = &cli.TimestampFlag{}
|
||||||
|
|
||||||
|
_ = f.IsSet()
|
||||||
|
_ = f.Names()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimestampFlag_SatisfiesFmtStringerInterface(t *testing.T) {
|
||||||
|
var f fmt.Stringer = &cli.TimestampFlag{}
|
||||||
|
|
||||||
|
_ = f.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimestampFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
|
||||||
|
var f cli.RequiredFlag = &cli.TimestampFlag{}
|
||||||
|
|
||||||
|
_ = f.IsRequired()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimestampFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
|
||||||
|
var f cli.VisibleFlag = &cli.TimestampFlag{}
|
||||||
|
|
||||||
|
_ = f.IsVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolFlag_SatisfiesFlagInterface(t *testing.T) {
|
||||||
|
var f cli.Flag = &cli.BoolFlag{}
|
||||||
|
|
||||||
|
_ = f.IsSet()
|
||||||
|
_ = f.Names()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolFlag_SatisfiesFmtStringerInterface(t *testing.T) {
|
||||||
|
var f fmt.Stringer = &cli.BoolFlag{}
|
||||||
|
|
||||||
|
_ = f.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
|
||||||
|
var f cli.RequiredFlag = &cli.BoolFlag{}
|
||||||
|
|
||||||
|
_ = f.IsRequired()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
|
||||||
|
var f cli.VisibleFlag = &cli.BoolFlag{}
|
||||||
|
|
||||||
|
_ = f.IsVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloat64Flag_SatisfiesFlagInterface(t *testing.T) {
|
||||||
|
var f cli.Flag = &cli.Float64Flag{}
|
||||||
|
|
||||||
|
_ = f.IsSet()
|
||||||
|
_ = f.Names()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloat64Flag_SatisfiesFmtStringerInterface(t *testing.T) {
|
||||||
|
var f fmt.Stringer = &cli.Float64Flag{}
|
||||||
|
|
||||||
|
_ = f.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloat64Flag_SatisfiesRequiredFlagInterface(t *testing.T) {
|
||||||
|
var f cli.RequiredFlag = &cli.Float64Flag{}
|
||||||
|
|
||||||
|
_ = f.IsRequired()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloat64Flag_SatisfiesVisibleFlagInterface(t *testing.T) {
|
||||||
|
var f cli.VisibleFlag = &cli.Float64Flag{}
|
||||||
|
|
||||||
|
_ = f.IsVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntFlag_SatisfiesFlagInterface(t *testing.T) {
|
||||||
|
var f cli.Flag = &cli.IntFlag{}
|
||||||
|
|
||||||
|
_ = f.IsSet()
|
||||||
|
_ = f.Names()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntFlag_SatisfiesFmtStringerInterface(t *testing.T) {
|
||||||
|
var f fmt.Stringer = &cli.IntFlag{}
|
||||||
|
|
||||||
|
_ = f.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
|
||||||
|
var f cli.RequiredFlag = &cli.IntFlag{}
|
||||||
|
|
||||||
|
_ = f.IsRequired()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
|
||||||
|
var f cli.VisibleFlag = &cli.IntFlag{}
|
||||||
|
|
||||||
|
_ = f.IsVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInt64Flag_SatisfiesFlagInterface(t *testing.T) {
|
||||||
|
var f cli.Flag = &cli.Int64Flag{}
|
||||||
|
|
||||||
|
_ = f.IsSet()
|
||||||
|
_ = f.Names()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInt64Flag_SatisfiesFmtStringerInterface(t *testing.T) {
|
||||||
|
var f fmt.Stringer = &cli.Int64Flag{}
|
||||||
|
|
||||||
|
_ = f.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInt64Flag_SatisfiesRequiredFlagInterface(t *testing.T) {
|
||||||
|
var f cli.RequiredFlag = &cli.Int64Flag{}
|
||||||
|
|
||||||
|
_ = f.IsRequired()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInt64Flag_SatisfiesVisibleFlagInterface(t *testing.T) {
|
||||||
|
var f cli.VisibleFlag = &cli.Int64Flag{}
|
||||||
|
|
||||||
|
_ = f.IsVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringFlag_SatisfiesFlagInterface(t *testing.T) {
|
||||||
|
var f cli.Flag = &cli.StringFlag{}
|
||||||
|
|
||||||
|
_ = f.IsSet()
|
||||||
|
_ = f.Names()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringFlag_SatisfiesFmtStringerInterface(t *testing.T) {
|
||||||
|
var f fmt.Stringer = &cli.StringFlag{}
|
||||||
|
|
||||||
|
_ = f.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
|
||||||
|
var f cli.RequiredFlag = &cli.StringFlag{}
|
||||||
|
|
||||||
|
_ = f.IsRequired()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
|
||||||
|
var f cli.VisibleFlag = &cli.StringFlag{}
|
||||||
|
|
||||||
|
_ = f.IsVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDurationFlag_SatisfiesFlagInterface(t *testing.T) {
|
||||||
|
var f cli.Flag = &cli.DurationFlag{}
|
||||||
|
|
||||||
|
_ = f.IsSet()
|
||||||
|
_ = f.Names()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDurationFlag_SatisfiesFmtStringerInterface(t *testing.T) {
|
||||||
|
var f fmt.Stringer = &cli.DurationFlag{}
|
||||||
|
|
||||||
|
_ = f.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDurationFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
|
||||||
|
var f cli.RequiredFlag = &cli.DurationFlag{}
|
||||||
|
|
||||||
|
_ = f.IsRequired()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDurationFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
|
||||||
|
var f cli.VisibleFlag = &cli.DurationFlag{}
|
||||||
|
|
||||||
|
_ = f.IsVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUintFlag_SatisfiesFlagInterface(t *testing.T) {
|
||||||
|
var f cli.Flag = &cli.UintFlag{}
|
||||||
|
|
||||||
|
_ = f.IsSet()
|
||||||
|
_ = f.Names()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUintFlag_SatisfiesFmtStringerInterface(t *testing.T) {
|
||||||
|
var f fmt.Stringer = &cli.UintFlag{}
|
||||||
|
|
||||||
|
_ = f.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUintFlag_SatisfiesRequiredFlagInterface(t *testing.T) {
|
||||||
|
var f cli.RequiredFlag = &cli.UintFlag{}
|
||||||
|
|
||||||
|
_ = f.IsRequired()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUintFlag_SatisfiesVisibleFlagInterface(t *testing.T) {
|
||||||
|
var f cli.VisibleFlag = &cli.UintFlag{}
|
||||||
|
|
||||||
|
_ = f.IsVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUint64Flag_SatisfiesFlagInterface(t *testing.T) {
|
||||||
|
var f cli.Flag = &cli.Uint64Flag{}
|
||||||
|
|
||||||
|
_ = f.IsSet()
|
||||||
|
_ = f.Names()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUint64Flag_SatisfiesFmtStringerInterface(t *testing.T) {
|
||||||
|
var f fmt.Stringer = &cli.Uint64Flag{}
|
||||||
|
|
||||||
|
_ = f.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUint64Flag_SatisfiesRequiredFlagInterface(t *testing.T) {
|
||||||
|
var f cli.RequiredFlag = &cli.Uint64Flag{}
|
||||||
|
|
||||||
|
_ = f.IsRequired()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUint64Flag_SatisfiesVisibleFlagInterface(t *testing.T) {
|
||||||
|
var f cli.VisibleFlag = &cli.Uint64Flag{}
|
||||||
|
|
||||||
|
_ = f.IsVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
// vim:ro
|
Loading…
Reference in New Issue
Block a user