Merge remote-tracking branch 'origin/main' into merging-main-to-v3-dev-main
This commit is contained in:
commit
a82c9b1433
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
|
||||
|
||||
- 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 .)
|
||||
|
||||
- name: 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
|
||||
run: go run internal/build/build.go test
|
||||
|
||||
- name: 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
|
||||
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
|
||||
with:
|
||||
fail_ci_if_error: true
|
||||
@ -73,14 +79,37 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Dependencies
|
||||
run:
|
||||
mkdir -p "${GITHUB_WORKSPACE}/.local/bin" &&
|
||||
curl -fsSL -o "${GITHUB_WORKSPACE}/.local/bin/gfmrun" "https://github.com/urfave/gfmrun/releases/download/v1.3.0/gfmrun-$(go env GOOS)-$(go env GOARCH)-v1.3.0" &&
|
||||
chmod +x "${GITHUB_WORKSPACE}/.local/bin/gfmrun" &&
|
||||
npm install -g markdown-toc@1.2.0
|
||||
run: |
|
||||
mkdir -p "${GITHUB_WORKSPACE}/.local/bin"
|
||||
curl -fsSL -o "${GITHUB_WORKSPACE}/.local/bin/gfmrun" "https://github.com/urfave/gfmrun/releases/download/v1.3.0/gfmrun-$(go env GOOS)-$(go env GOARCH)-v1.3.0"
|
||||
chmod +x "${GITHUB_WORKSPACE}/.local/bin/gfmrun"
|
||||
|
||||
- name: gfmrun
|
||||
run: go run internal/build/build.go gfmrun docs/v2/manual.md
|
||||
|
||||
- name: toc
|
||||
run: go run internal/build/build.go toc docs/v2/manual.md
|
||||
- name: diff check
|
||||
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
|
||||
*.orig
|
||||
node_modules/
|
||||
vendor
|
||||
.idea
|
||||
internal/*/built-example
|
||||
coverage.txt
|
||||
/.local/
|
||||
/site/
|
||||
|
||||
*.exe
|
||||
|
@ -55,11 +55,12 @@ further defined and clarified by project maintainers.
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting Dan Buch at dan@meatballhat.com. All complaints will be
|
||||
reviewed and investigated and will result in a response that is deemed necessary
|
||||
and appropriate to the circumstances. The project team is obligated to maintain
|
||||
confidentiality with regard to the reporter of an incident. Further details of
|
||||
specific enforcement policies may be posted separately.
|
||||
reported by contacting urfave-governance@googlegroups.com, a members-only group
|
||||
that is world-postable. All complaints will be reviewed and investigated and
|
||||
will result in a response that is deemed necessary and appropriate to the
|
||||
circumstances. The project team is obligated to maintain confidentiality with
|
||||
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
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
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
|
||||
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)
|
||||
[![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
|
||||
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)
|
||||
- `v1` - [./docs/v1/manual.md](./docs/v1/manual.md)
|
||||
## License
|
||||
|
||||
Guides for migrating to newer versions:
|
||||
|
||||
- `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).
|
||||
See [`LICENSE`](./LICENSE)
|
||||
|
224
altsrc/flag.go
224
altsrc/flag.go
@ -13,18 +13,18 @@ import (
|
||||
// allows a value to be set on the existing parsed flags.
|
||||
type FlagInputSourceExtension interface {
|
||||
cli.Flag
|
||||
ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error
|
||||
ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error
|
||||
}
|
||||
|
||||
// ApplyInputSourceValues iterates over all provided flags and
|
||||
// executes ApplyInputSourceValue on flags implementing the
|
||||
// FlagInputSourceExtension interface to initialize these flags
|
||||
// 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 {
|
||||
inputSourceExtendedFlag, isType := f.(FlagInputSourceExtension)
|
||||
if isType {
|
||||
err := inputSourceExtendedFlag.ApplyInputSourceValue(context, inputSourceContext)
|
||||
err := inputSourceExtendedFlag.ApplyInputSourceValue(cCtx, inputSourceContext)
|
||||
if err != nil {
|
||||
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
|
||||
// that are supported by the input source
|
||||
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()
|
||||
if err != nil {
|
||||
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
|
||||
// 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
|
||||
func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context *cli.Context) (InputSourceContext, error)) cli.BeforeFunc {
|
||||
return func(context *cli.Context) error {
|
||||
inputSource, err := createInputSource(context)
|
||||
func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(cCtx *cli.Context) (InputSourceContext, error)) cli.BeforeFunc {
|
||||
return func(cCtx *cli.Context) error {
|
||||
inputSource, err := createInputSource(cCtx)
|
||||
if err != nil {
|
||||
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
|
||||
func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil {
|
||||
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) {
|
||||
value, err := isc.Generic(f.GenericFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value != nil {
|
||||
for _, name := range f.Names() {
|
||||
_ = f.set.Set(name, value.String())
|
||||
}
|
||||
func (f *GenericFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.isSet(f.GenericFlag.Name) {
|
||||
value, err := isc.Generic(f.GenericFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value != nil {
|
||||
for _, name := range f.Names() {
|
||||
_ = 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
|
||||
func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil {
|
||||
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) {
|
||||
value, err := isc.StringSlice(f.StringSliceFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value != nil {
|
||||
var sliceValue cli.StringSlice = *(cli.NewStringSlice(value...))
|
||||
for _, name := range f.Names() {
|
||||
underlyingFlag := f.set.Lookup(name)
|
||||
if underlyingFlag != nil {
|
||||
underlyingFlag.Value = &sliceValue
|
||||
}
|
||||
func (f *StringSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.isSet(f.StringSliceFlag.Name) {
|
||||
value, err := isc.StringSlice(f.StringSliceFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value != nil {
|
||||
var sliceValue cli.StringSlice = *(cli.NewStringSlice(value...))
|
||||
for _, name := range f.Names() {
|
||||
underlyingFlag := f.set.Lookup(name)
|
||||
if underlyingFlag != nil {
|
||||
underlyingFlag.Value = &sliceValue
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -104,20 +100,18 @@ func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputS
|
||||
}
|
||||
|
||||
// ApplyInputSourceValue applies a IntSlice value if required
|
||||
func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil {
|
||||
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) {
|
||||
value, err := isc.IntSlice(f.IntSliceFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value != nil {
|
||||
var sliceValue cli.IntSlice = *(cli.NewIntSlice(value...))
|
||||
for _, name := range f.Names() {
|
||||
underlyingFlag := f.set.Lookup(name)
|
||||
if underlyingFlag != nil {
|
||||
underlyingFlag.Value = &sliceValue
|
||||
}
|
||||
func (f *IntSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.isSet(f.IntSliceFlag.Name) {
|
||||
value, err := isc.IntSlice(f.IntSliceFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value != nil {
|
||||
var sliceValue cli.IntSlice = *(cli.NewIntSlice(value...))
|
||||
for _, name := range f.Names() {
|
||||
underlyingFlag := f.set.Lookup(name)
|
||||
if underlyingFlag != nil {
|
||||
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
|
||||
func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil {
|
||||
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) {
|
||||
value, err := isc.Bool(f.BoolFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value {
|
||||
for _, name := range f.Names() {
|
||||
_ = f.set.Set(name, strconv.FormatBool(value))
|
||||
}
|
||||
func (f *BoolFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.isSet(f.BoolFlag.Name) {
|
||||
value, err := isc.Bool(f.BoolFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value {
|
||||
for _, name := range f.Names() {
|
||||
_ = 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
|
||||
func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil {
|
||||
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) {
|
||||
value, err := isc.String(f.StringFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value != "" {
|
||||
for _, name := range f.Names() {
|
||||
_ = f.set.Set(name, value)
|
||||
}
|
||||
func (f *StringFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.isSet(f.StringFlag.Name) {
|
||||
value, err := isc.String(f.StringFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value != "" {
|
||||
for _, name := range f.Names() {
|
||||
_ = 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
|
||||
func (f *PathFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil {
|
||||
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) {
|
||||
value, err := isc.String(f.PathFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value != "" {
|
||||
for _, name := range f.Names() {
|
||||
func (f *PathFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.isSet(f.PathFlag.Name) {
|
||||
value, err := isc.String(f.PathFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value != "" {
|
||||
for _, name := range f.Names() {
|
||||
|
||||
if !filepath.IsAbs(value) && isc.Source() != "" {
|
||||
basePathAbs, err := filepath.Abs(isc.Source())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value = filepath.Join(filepath.Dir(basePathAbs), value)
|
||||
if !filepath.IsAbs(value) && isc.Source() != "" {
|
||||
basePathAbs, err := filepath.Abs(isc.Source())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_ = 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
|
||||
func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil {
|
||||
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) {
|
||||
value, err := isc.Int(f.IntFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value > 0 {
|
||||
for _, name := range f.Names() {
|
||||
_ = f.set.Set(name, strconv.FormatInt(int64(value), 10))
|
||||
}
|
||||
}
|
||||
func (f *IntFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.isSet(f.IntFlag.Name) {
|
||||
value, err := isc.Int(f.IntFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, name := range f.Names() {
|
||||
_ = f.set.Set(name, strconv.FormatInt(int64(value), 10))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyInputSourceValue applies a Duration value to the flagSet if required
|
||||
func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil {
|
||||
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) {
|
||||
value, err := isc.Duration(f.DurationFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value > 0 {
|
||||
for _, name := range f.Names() {
|
||||
_ = f.set.Set(name, value.String())
|
||||
}
|
||||
}
|
||||
func (f *DurationFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.isSet(f.DurationFlag.Name) {
|
||||
value, err := isc.Duration(f.DurationFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, name := range f.Names() {
|
||||
_ = f.set.Set(name, value.String())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyInputSourceValue applies a Float64 value to the flagSet if required
|
||||
func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil {
|
||||
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) {
|
||||
value, err := isc.Float64(f.Float64Flag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value > 0 {
|
||||
floatStr := float64ToString(value)
|
||||
for _, name := range f.Names() {
|
||||
_ = f.set.Set(name, floatStr)
|
||||
}
|
||||
}
|
||||
func (f *Float64Flag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.isSet(f.Float64Flag.Name) {
|
||||
value, err := isc.Float64(f.Float64Flag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
floatStr := float64ToString(value)
|
||||
for _, name := range f.Names() {
|
||||
_ = f.set.Set(name, floatStr)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -26,29 +26,48 @@ type testApplyInputSource struct {
|
||||
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) {
|
||||
v := &Parser{"abc", "def"}
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Value: &Parser{}}),
|
||||
FlagName: "test",
|
||||
MapValue: v,
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
expect(t, v, c.Generic("test"))
|
||||
|
||||
c = runRacyTest(t, tis)
|
||||
refute(t, v, c.Generic("test"))
|
||||
}
|
||||
|
||||
func TestGenericApplyInputSourceMethodContextSet(t *testing.T) {
|
||||
p := &Parser{"abc", "def"}
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Value: &Parser{}}),
|
||||
FlagName: "test",
|
||||
MapValue: &Parser{"efg", "hig"},
|
||||
ContextValueString: p.String(),
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
expect(t, p, c.Generic("test"))
|
||||
|
||||
c = runRacyTest(t, tis)
|
||||
refute(t, p, c.Generic("test"))
|
||||
}
|
||||
|
||||
func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewGenericFlag(&cli.GenericFlag{
|
||||
Name: "test",
|
||||
Value: &Parser{},
|
||||
@ -58,17 +77,25 @@ func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||
MapValue: &Parser{"efg", "hij"},
|
||||
EnvVarName: "TEST",
|
||||
EnvVarValue: "abc,def",
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
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) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: []interface{}{"hello", "world"},
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
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) {
|
||||
@ -82,112 +109,154 @@ func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", EnvVars: []string{"TEST"}}),
|
||||
FlagName: "test",
|
||||
MapValue: []interface{}{"hello", "world"},
|
||||
EnvVarName: "TEST",
|
||||
EnvVarValue: "oh,no",
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
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) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: []interface{}{1, 2},
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
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) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: []interface{}{1, 2},
|
||||
ContextValueString: "3",
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
expect(t, c.IntSlice("test"), []int{3})
|
||||
|
||||
c = runRacyTest(t, tis)
|
||||
refute(t, c.IntSlice("test"), []int{3})
|
||||
}
|
||||
|
||||
func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", EnvVars: []string{"TEST"}}),
|
||||
FlagName: "test",
|
||||
MapValue: []interface{}{1, 2},
|
||||
EnvVarName: "TEST",
|
||||
EnvVarValue: "3,4",
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
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) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewBoolFlag(&cli.BoolFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: true,
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
expect(t, true, c.Bool("test"))
|
||||
|
||||
c = runRacyTest(t, tis)
|
||||
refute(t, true, c.Bool("test"))
|
||||
}
|
||||
|
||||
func TestBoolApplyInputSourceMethodContextSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewBoolFlag(&cli.BoolFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: false,
|
||||
ContextValueString: "true",
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
expect(t, true, c.Bool("test"))
|
||||
|
||||
c = runRacyTest(t, tis)
|
||||
refute(t, true, c.Bool("test"))
|
||||
}
|
||||
|
||||
func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewBoolFlag(&cli.BoolFlag{Name: "test", EnvVars: []string{"TEST"}}),
|
||||
FlagName: "test",
|
||||
MapValue: false,
|
||||
EnvVarName: "TEST",
|
||||
EnvVarValue: "true",
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
expect(t, true, c.Bool("test"))
|
||||
|
||||
c = runRacyTest(t, tis)
|
||||
refute(t, true, c.Bool("test"))
|
||||
}
|
||||
|
||||
func TestStringApplyInputSourceMethodSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewStringFlag(&cli.StringFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: "hello",
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
expect(t, "hello", c.String("test"))
|
||||
|
||||
c = runRacyTest(t, tis)
|
||||
refute(t, "hello", c.String("test"))
|
||||
}
|
||||
|
||||
func TestStringApplyInputSourceMethodContextSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewStringFlag(&cli.StringFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: "hello",
|
||||
ContextValueString: "goodbye",
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
expect(t, "goodbye", c.String("test"))
|
||||
|
||||
c = runRacyTest(t, tis)
|
||||
refute(t, "goodbye", c.String("test"))
|
||||
}
|
||||
|
||||
func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewStringFlag(&cli.StringFlag{Name: "test", EnvVars: []string{"TEST"}}),
|
||||
FlagName: "test",
|
||||
MapValue: "hello",
|
||||
EnvVarName: "TEST",
|
||||
EnvVarValue: "goodbye",
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
expect(t, "goodbye", c.String("test"))
|
||||
|
||||
c = runRacyTest(t, tis)
|
||||
refute(t, "goodbye", c.String("test"))
|
||||
}
|
||||
|
||||
func TestPathApplyInputSourceMethodSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewPathFlag(&cli.PathFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: "hello",
|
||||
SourcePath: "/path/to/source/file",
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
|
||||
expected := "/path/to/source/hello"
|
||||
if runtime.GOOS == "windows" {
|
||||
@ -200,119 +269,214 @@ func TestPathApplyInputSourceMethodSet(t *testing.T) {
|
||||
}
|
||||
}
|
||||
expect(t, expected, c.String("test"))
|
||||
|
||||
c = runRacyTest(t, tis)
|
||||
refute(t, expected, c.String("test"))
|
||||
}
|
||||
|
||||
func TestPathApplyInputSourceMethodContextSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewPathFlag(&cli.PathFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: "hello",
|
||||
ContextValueString: "goodbye",
|
||||
SourcePath: "/path/to/source/file",
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
expect(t, "goodbye", c.String("test"))
|
||||
|
||||
c = runRacyTest(t, tis)
|
||||
refute(t, "goodbye", c.String("test"))
|
||||
}
|
||||
|
||||
func TestPathApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewPathFlag(&cli.PathFlag{Name: "test", EnvVars: []string{"TEST"}}),
|
||||
FlagName: "test",
|
||||
MapValue: "hello",
|
||||
EnvVarName: "TEST",
|
||||
EnvVarValue: "goodbye",
|
||||
SourcePath: "/path/to/source/file",
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
expect(t, "goodbye", c.String("test"))
|
||||
|
||||
c = runRacyTest(t, tis)
|
||||
refute(t, "goodbye", c.String("test"))
|
||||
}
|
||||
|
||||
func TestIntApplyInputSourceMethodSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewIntFlag(&cli.IntFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: 15,
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
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) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewIntFlag(&cli.IntFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: 15,
|
||||
ContextValueString: "7",
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
expect(t, 7, c.Int("test"))
|
||||
|
||||
c = runRacyTest(t, tis)
|
||||
refute(t, 7, c.Int("test"))
|
||||
}
|
||||
|
||||
func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewIntFlag(&cli.IntFlag{Name: "test", EnvVars: []string{"TEST"}}),
|
||||
FlagName: "test",
|
||||
MapValue: 15,
|
||||
EnvVarName: "TEST",
|
||||
EnvVarValue: "12",
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
expect(t, 12, c.Int("test"))
|
||||
|
||||
c = runRacyTest(t, tis)
|
||||
refute(t, 12, c.Int("test"))
|
||||
}
|
||||
|
||||
func TestDurationApplyInputSourceMethodSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
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 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) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: 30 * time.Second,
|
||||
ContextValueString: (15 * time.Second).String(),
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
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) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewDurationFlag(&cli.DurationFlag{Name: "test", EnvVars: []string{"TEST"}}),
|
||||
FlagName: "test",
|
||||
MapValue: 30 * time.Second,
|
||||
EnvVarName: "TEST",
|
||||
EnvVarValue: (15 * time.Second).String(),
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
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) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
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 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) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: 1.3,
|
||||
ContextValueString: fmt.Sprintf("%v", 1.4),
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
expect(t, 1.4, c.Float64("test"))
|
||||
|
||||
c = runRacyTest(t, tis)
|
||||
refute(t, 1.4, c.Float64("test"))
|
||||
}
|
||||
|
||||
func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
tis := testApplyInputSource{
|
||||
Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test", EnvVars: []string{"TEST"}}),
|
||||
FlagName: "test",
|
||||
MapValue: 1.3,
|
||||
EnvVarName: "TEST",
|
||||
EnvVarValue: fmt.Sprintf("%v", 1.4),
|
||||
})
|
||||
}
|
||||
c := runTest(t, tis)
|
||||
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 {
|
||||
@ -340,6 +504,19 @@ func runTest(t *testing.T, test testApplyInputSource) *cli.Context {
|
||||
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
|
||||
|
||||
func (p *Parser) Set(value string) error {
|
||||
@ -357,3 +534,5 @@ func (p *Parser) Set(value string) error {
|
||||
func (p *Parser) String() string {
|
||||
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{}) {
|
||||
if a == b {
|
||||
t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
|
||||
_, fn, line, _ := runtime.Caller(1)
|
||||
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)
|
||||
Generic(name string) (cli.Generic, 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
|
||||
// by the given flag.
|
||||
func NewJSONSourceFromFlagFunc(flag string) func(c *cli.Context) (InputSourceContext, error) {
|
||||
return func(context *cli.Context) (InputSourceContext, error) {
|
||||
if context.IsSet(flag) {
|
||||
return NewJSONSourceFromFile(context.String(flag))
|
||||
return func(cCtx *cli.Context) (InputSourceContext, error) {
|
||||
if cCtx.IsSet(flag) {
|
||||
return NewJSONSourceFromFile(cCtx.String(flag))
|
||||
}
|
||||
|
||||
return defaultInputSource()
|
||||
@ -184,6 +184,11 @@ func (x *jsonSource) Bool(name string) (bool, error) {
|
||||
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) {
|
||||
return jsonGetValue(key, x.deserialized)
|
||||
}
|
||||
|
@ -32,11 +32,18 @@ func nestedVal(name string, tree map[interface{}]interface{}) (interface{}, bool
|
||||
if !ok {
|
||||
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
|
||||
}
|
||||
node = ctype
|
||||
}
|
||||
if val, ok := node[sections[len(sections)-1]]; ok {
|
||||
return val, true
|
||||
@ -244,6 +251,15 @@ func (fsm *MapInputSource) Bool(name string) (bool, error) {
|
||||
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 {
|
||||
valueType := reflect.TypeOf(value)
|
||||
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.
|
||||
func NewTomlSourceFromFlagFunc(flagFileName string) func(context *cli.Context) (InputSourceContext, error) {
|
||||
return func(context *cli.Context) (InputSourceContext, error) {
|
||||
if context.IsSet(flagFileName) {
|
||||
filePath := context.String(flagFileName)
|
||||
func NewTomlSourceFromFlagFunc(flagFileName string) func(cCtx *cli.Context) (InputSourceContext, error) {
|
||||
return func(cCtx *cli.Context) (InputSourceContext, error) {
|
||||
if cCtx.IsSet(flagFileName) {
|
||||
filePath := cCtx.String(flagFileName)
|
||||
return NewTomlSourceFromFile(filePath)
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
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.
|
||||
func NewYamlSourceFromFlagFunc(flagFileName string) func(context *cli.Context) (InputSourceContext, error) {
|
||||
return func(context *cli.Context) (InputSourceContext, error) {
|
||||
if context.IsSet(flagFileName) {
|
||||
filePath := context.String(flagFileName)
|
||||
func NewYamlSourceFromFlagFunc(flagFileName string) func(cCtx *cli.Context) (InputSourceContext, error) {
|
||||
return func(cCtx *cli.Context) (InputSourceContext, error) {
|
||||
if cCtx.IsSet(flagFileName) {
|
||||
filePath := cCtx.String(flagFileName)
|
||||
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"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
const suggestDidYouMeanTemplate = "Did you mean %q?"
|
||||
|
||||
var (
|
||||
changeLogURL = "https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md"
|
||||
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
|
||||
@ -18,6 +21,10 @@ var (
|
||||
errInvalidActionType = NewExitError("ERROR invalid Action type. "+
|
||||
fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+
|
||||
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
|
||||
@ -37,6 +44,9 @@ type App struct {
|
||||
Version string
|
||||
// Description of the program
|
||||
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
|
||||
Commands []*Command
|
||||
// List of flags to parse
|
||||
@ -52,6 +62,8 @@ type App struct {
|
||||
HideVersion bool
|
||||
// categories contains the categorized commands and is populated on app startup
|
||||
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
|
||||
BashComplete BashCompleteFunc
|
||||
// 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
|
||||
// i.e. foobar -o -v -> foobar -ov
|
||||
UseShortOptionHandling bool
|
||||
// Enable suggestions for commands and flags
|
||||
Suggest 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.
|
||||
// Returns the current time if it fails to find it.
|
||||
func compileTime() time.Time {
|
||||
@ -181,6 +199,8 @@ func (a *App) Setup() {
|
||||
if c.HelpName == "" {
|
||||
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
|
||||
}
|
||||
|
||||
c.flagCategories = newFlagCategoriesFromFlags(c.Flags)
|
||||
newCommands = append(newCommands, c)
|
||||
}
|
||||
a.Commands = newCommands
|
||||
@ -205,6 +225,13 @@ func (a *App) Setup() {
|
||||
}
|
||||
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 {
|
||||
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)
|
||||
nerr := normalizeFlags(a.Flags, set)
|
||||
context := NewContext(a, set, &Context{Context: ctx})
|
||||
cCtx := NewContext(a, set, &Context{Context: ctx})
|
||||
if nerr != nil {
|
||||
_, _ = fmt.Fprintln(a.Writer, nerr)
|
||||
_ = ShowAppHelp(context)
|
||||
_ = ShowAppHelp(cCtx)
|
||||
return nerr
|
||||
}
|
||||
context.shellComplete = shellComplete
|
||||
cCtx.shellComplete = shellComplete
|
||||
|
||||
if checkCompletions(context) {
|
||||
if checkCompletions(cCtx) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if a.OnUsageError != nil {
|
||||
err := a.OnUsageError(context, err, false)
|
||||
a.handleExitCoder(context, err)
|
||||
err := a.OnUsageError(cCtx, err, false)
|
||||
a.handleExitCoder(cCtx, err)
|
||||
return err
|
||||
}
|
||||
_, _ = 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
|
||||
}
|
||||
|
||||
if !a.HideHelp && checkHelp(context) {
|
||||
_ = ShowAppHelp(context)
|
||||
if !a.HideHelp && checkHelp(cCtx) {
|
||||
_ = ShowAppHelp(cCtx)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !a.HideVersion && checkVersion(context) {
|
||||
ShowVersion(context)
|
||||
if !a.HideVersion && checkVersion(cCtx) {
|
||||
ShowVersion(cCtx)
|
||||
return nil
|
||||
}
|
||||
|
||||
cerr := context.checkRequiredFlags(a.Flags)
|
||||
cerr := cCtx.checkRequiredFlags(a.Flags)
|
||||
if cerr != nil {
|
||||
_ = ShowAppHelp(context)
|
||||
_ = ShowAppHelp(cCtx)
|
||||
return cerr
|
||||
}
|
||||
|
||||
if a.After != nil {
|
||||
defer func() {
|
||||
if afterErr := a.After(context); afterErr != nil {
|
||||
if afterErr := a.After(cCtx); afterErr != nil {
|
||||
if err != nil {
|
||||
err = newMultiError(err, afterErr)
|
||||
} else {
|
||||
@ -297,21 +329,53 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
|
||||
}
|
||||
|
||||
if a.Before != nil {
|
||||
beforeErr := a.Before(context)
|
||||
beforeErr := a.Before(cCtx)
|
||||
if beforeErr != nil {
|
||||
a.handleExitCoder(context, beforeErr)
|
||||
a.handleExitCoder(cCtx, beforeErr)
|
||||
err = beforeErr
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
args := context.Args()
|
||||
var c *Command
|
||||
args := cCtx.Args()
|
||||
if args.Present() {
|
||||
name := args.First()
|
||||
c := a.Command(name)
|
||||
if c != nil {
|
||||
return c.Run(context)
|
||||
if a.validCommandName(name) {
|
||||
c = a.Command(name)
|
||||
} 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 {
|
||||
@ -319,12 +383,35 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
|
||||
}
|
||||
|
||||
// Run default Action
|
||||
err = a.Action(context)
|
||||
err = a.Action(cCtx)
|
||||
|
||||
a.handleExitCoder(context, err)
|
||||
a.handleExitCoder(cCtx, 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
|
||||
//
|
||||
// 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)
|
||||
nerr := normalizeFlags(a.Flags, set)
|
||||
context := NewContext(a, set, ctx)
|
||||
cCtx := NewContext(a, set, ctx)
|
||||
|
||||
if nerr != nil {
|
||||
_, _ = fmt.Fprintln(a.Writer, nerr)
|
||||
_, _ = fmt.Fprintln(a.Writer)
|
||||
if len(a.Commands) > 0 {
|
||||
_ = ShowSubcommandHelp(context)
|
||||
_ = ShowSubcommandHelp(cCtx)
|
||||
} else {
|
||||
_ = ShowCommandHelp(ctx, context.Args().First())
|
||||
_ = ShowCommandHelp(ctx, cCtx.Args().First())
|
||||
}
|
||||
return nerr
|
||||
}
|
||||
|
||||
if checkCompletions(context) {
|
||||
if checkCompletions(cCtx) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if a.OnUsageError != nil {
|
||||
err = a.OnUsageError(context, err, true)
|
||||
a.handleExitCoder(context, err)
|
||||
err = a.OnUsageError(cCtx, err, true)
|
||||
a.handleExitCoder(cCtx, err)
|
||||
return err
|
||||
}
|
||||
_, _ = 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
|
||||
}
|
||||
|
||||
if len(a.Commands) > 0 {
|
||||
if checkSubcommandHelp(context) {
|
||||
if checkSubcommandHelp(cCtx) {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if checkCommandHelp(ctx, context.Args().First()) {
|
||||
if checkCommandHelp(ctx, cCtx.Args().First()) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
cerr := context.checkRequiredFlags(a.Flags)
|
||||
cerr := cCtx.checkRequiredFlags(a.Flags)
|
||||
if cerr != nil {
|
||||
_ = ShowSubcommandHelp(context)
|
||||
_ = ShowSubcommandHelp(cCtx)
|
||||
return cerr
|
||||
}
|
||||
|
||||
if a.After != nil {
|
||||
defer func() {
|
||||
afterErr := a.After(context)
|
||||
afterErr := a.After(cCtx)
|
||||
if afterErr != nil {
|
||||
a.handleExitCoder(context, err)
|
||||
a.handleExitCoder(cCtx, err)
|
||||
if err != nil {
|
||||
err = newMultiError(err, afterErr)
|
||||
} else {
|
||||
@ -418,27 +510,27 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
||||
}
|
||||
|
||||
if a.Before != nil {
|
||||
beforeErr := a.Before(context)
|
||||
beforeErr := a.Before(cCtx)
|
||||
if beforeErr != nil {
|
||||
a.handleExitCoder(context, beforeErr)
|
||||
a.handleExitCoder(cCtx, beforeErr)
|
||||
err = beforeErr
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
args := context.Args()
|
||||
args := cCtx.Args()
|
||||
if args.Present() {
|
||||
name := args.First()
|
||||
c := a.Command(name)
|
||||
if c != nil {
|
||||
return c.Run(context)
|
||||
return c.Run(cCtx)
|
||||
}
|
||||
}
|
||||
|
||||
// Run default Action
|
||||
err = a.Action(context)
|
||||
err = a.Action(cCtx)
|
||||
|
||||
a.handleExitCoder(context, err)
|
||||
a.handleExitCoder(cCtx, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -481,6 +573,14 @@ func (a *App) VisibleCommands() []*Command {
|
||||
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
|
||||
func (a *App) VisibleFlags() []Flag {
|
||||
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 {
|
||||
a.ExitErrHandler(context, err)
|
||||
a.ExitErrHandler(cCtx, err)
|
||||
} else {
|
||||
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.
|
||||
type Author struct {
|
||||
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
|
||||
// it's an ActionFunc or a func with the legacy signature for Action, the func
|
||||
// is run!
|
||||
func HandleAction(action interface{}, context *Context) (err error) {
|
||||
func HandleAction(action interface{}, cCtx *Context) (err error) {
|
||||
switch a := action.(type) {
|
||||
case ActionFunc:
|
||||
return a(context)
|
||||
return a(cCtx)
|
||||
case func(*Context) error:
|
||||
return a(context)
|
||||
return a(cCtx)
|
||||
case func(*Context): // deprecated function signature
|
||||
a(context)
|
||||
a(cCtx)
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
//
|
||||
// GLOBAL OPTIONS:
|
||||
// --name value a name to say (default: "bob")
|
||||
// --help, -h show help (default: false)
|
||||
// --name value a name to say (default: "bob")
|
||||
// --version, -v print the version (default: false)
|
||||
}
|
||||
|
||||
@ -228,6 +228,7 @@ func ExampleApp_Run_subcommandNoAction() {
|
||||
}
|
||||
|
||||
func ExampleApp_Run_bashComplete_withShortFlag() {
|
||||
os.Setenv("SHELL", "bash")
|
||||
os.Args = []string{"greet", "-", "--generate-bash-completion"}
|
||||
|
||||
app := NewApp()
|
||||
@ -255,6 +256,7 @@ func ExampleApp_Run_bashComplete_withShortFlag() {
|
||||
}
|
||||
|
||||
func ExampleApp_Run_bashComplete_withLongFlag() {
|
||||
os.Setenv("SHELL", "bash")
|
||||
os.Args = []string{"greet", "--s", "--generate-bash-completion"}
|
||||
|
||||
app := NewApp()
|
||||
@ -283,6 +285,7 @@ func ExampleApp_Run_bashComplete_withLongFlag() {
|
||||
// --similar-flag
|
||||
}
|
||||
func ExampleApp_Run_bashComplete_withMultipleLongFlag() {
|
||||
os.Setenv("SHELL", "bash")
|
||||
os.Args = []string{"greet", "--st", "--generate-bash-completion"}
|
||||
|
||||
app := NewApp()
|
||||
@ -315,7 +318,7 @@ func ExampleApp_Run_bashComplete_withMultipleLongFlag() {
|
||||
}
|
||||
|
||||
func ExampleApp_Run_bashComplete() {
|
||||
// set args for examples sake
|
||||
os.Setenv("SHELL", "bash")
|
||||
os.Args = []string{"greet", "--generate-bash-completion"}
|
||||
|
||||
app := &App{
|
||||
@ -355,7 +358,7 @@ func ExampleApp_Run_bashComplete() {
|
||||
func ExampleApp_Run_zshComplete() {
|
||||
// set args for examples sake
|
||||
os.Args = []string{"greet", "--generate-bash-completion"}
|
||||
_ = os.Setenv("_CLI_ZSH_AUTOCOMPLETE_HACK", "1")
|
||||
_ = os.Setenv("SHELL", "/usr/bin/zsh")
|
||||
|
||||
app := NewApp()
|
||||
app.Name = "greet"
|
||||
@ -390,6 +393,40 @@ func ExampleApp_Run_zshComplete() {
|
||||
// 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) {
|
||||
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) {
|
||||
app := &App{}
|
||||
app.Setup()
|
||||
@ -445,14 +651,14 @@ func TestApp_Setup_defaultsWriter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
|
||||
var context *Context
|
||||
var cCtx *Context
|
||||
|
||||
a := &App{
|
||||
Commands: []*Command{
|
||||
{
|
||||
Name: "foo",
|
||||
Action: func(c *Context) error {
|
||||
context = c
|
||||
cCtx = c
|
||||
return nil
|
||||
},
|
||||
Flags: []Flag{
|
||||
@ -468,8 +674,8 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
|
||||
}
|
||||
_ = a.Run([]string{"", "foo", "--lang", "spanish", "abcd"})
|
||||
|
||||
expect(t, context.Args().Get(0), "abcd")
|
||||
expect(t, context.String("lang"), "spanish")
|
||||
expect(t, cCtx.Args().Get(0), "abcd")
|
||||
expect(t, cCtx.String("lang"), "spanish")
|
||||
}
|
||||
|
||||
func TestApp_RunAsSubCommandIncorrectUsage(t *testing.T) {
|
||||
@ -1890,6 +2096,14 @@ func TestApp_VisibleCategories(t *testing.T) {
|
||||
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) {
|
||||
app := &App{
|
||||
Action: func(c *Context) error { return nil },
|
||||
|
@ -1,14 +1,13 @@
|
||||
#compdef $PROG
|
||||
|
||||
_cli_zsh_autocomplete() {
|
||||
|
||||
local -a opts
|
||||
local cur
|
||||
cur=${words[-1]}
|
||||
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
|
||||
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}")
|
||||
opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}")
|
||||
fi
|
||||
|
||||
if [[ "${opts[1]}" != "" ]]; then
|
||||
@ -16,8 +15,6 @@ _cli_zsh_autocomplete() {
|
||||
else
|
||||
_files
|
||||
fi
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
compdef _cli_zsh_autocomplete $PROG
|
||||
|
94
category.go
94
category.go
@ -1,10 +1,12 @@
|
||||
package cli
|
||||
|
||||
import "sort"
|
||||
|
||||
// CommandCategories interface allows for category manipulation
|
||||
type CommandCategories interface {
|
||||
// AddCommand adds a command to a category, creating a new category if necessary.
|
||||
AddCommand(category string, command *Command)
|
||||
// categories returns a copy of the category slice
|
||||
// Categories returns a slice of categories sorted by name
|
||||
Categories() []CommandCategory
|
||||
}
|
||||
|
||||
@ -77,3 +79,93 @@ func (c *commandCategory) VisibleCommands() []*Command {
|
||||
}
|
||||
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
|
||||
|
||||
//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
|
||||
Subcommands []*Command
|
||||
// List of flags to parse
|
||||
Flags []Flag
|
||||
Flags []Flag
|
||||
flagCategories FlagCategories
|
||||
// Treat all flags as normal arguments if true
|
||||
SkipFlagParsing bool
|
||||
// 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)
|
||||
|
||||
context := NewContext(ctx.App, set, ctx)
|
||||
context.Command = c
|
||||
if checkCommandCompletions(context, c.Name) {
|
||||
cCtx := NewContext(ctx.App, set, ctx)
|
||||
cCtx.Command = c
|
||||
if checkCommandCompletions(cCtx, c.Name) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if c.OnUsageError != nil {
|
||||
err = c.OnUsageError(context, err, false)
|
||||
context.App.handleExitCoder(context, err)
|
||||
err = c.OnUsageError(cCtx, err, false)
|
||||
cCtx.App.handleExitCoder(cCtx, err)
|
||||
return err
|
||||
}
|
||||
_, _ = fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
|
||||
_, _ = fmt.Fprintln(context.App.Writer)
|
||||
_ = ShowCommandHelp(context, c.Name)
|
||||
_, _ = fmt.Fprintln(cCtx.App.Writer, "Incorrect Usage:", err.Error())
|
||||
_, _ = fmt.Fprintln(cCtx.App.Writer)
|
||||
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
|
||||
}
|
||||
|
||||
if checkCommandHelp(context, c.Name) {
|
||||
if checkCommandHelp(cCtx, c.Name) {
|
||||
return nil
|
||||
}
|
||||
|
||||
cerr := context.checkRequiredFlags(c.Flags)
|
||||
cerr := cCtx.checkRequiredFlags(c.Flags)
|
||||
if cerr != nil {
|
||||
_ = ShowCommandHelp(context, c.Name)
|
||||
_ = ShowCommandHelp(cCtx, c.Name)
|
||||
return cerr
|
||||
}
|
||||
|
||||
if c.After != nil {
|
||||
defer func() {
|
||||
afterErr := c.After(context)
|
||||
afterErr := c.After(cCtx)
|
||||
if afterErr != nil {
|
||||
context.App.handleExitCoder(context, err)
|
||||
cCtx.App.handleExitCoder(cCtx, err)
|
||||
if err != nil {
|
||||
err = newMultiError(err, afterErr)
|
||||
} else {
|
||||
@ -148,9 +154,9 @@ func (c *Command) Run(ctx *Context) (err error) {
|
||||
}
|
||||
|
||||
if c.Before != nil {
|
||||
err = c.Before(context)
|
||||
err = c.Before(cCtx)
|
||||
if err != nil {
|
||||
context.App.handleExitCoder(context, err)
|
||||
cCtx.App.handleExitCoder(cCtx, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -159,11 +165,11 @@ func (c *Command) Run(ctx *Context) (err error) {
|
||||
c.Action = helpSubcommand.Action
|
||||
}
|
||||
|
||||
context.Command = c
|
||||
err = c.Action(context)
|
||||
cCtx.Command = c
|
||||
err = c.Action(cCtx)
|
||||
|
||||
if err != nil {
|
||||
context.App.handleExitCoder(context, err)
|
||||
cCtx.App.handleExitCoder(cCtx, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@ -249,6 +255,7 @@ func (c *Command) startApp(ctx *Context) error {
|
||||
app.ErrWriter = ctx.App.ErrWriter
|
||||
app.ExitErrHandler = ctx.App.ExitErrHandler
|
||||
app.UseShortOptionHandling = ctx.App.UseShortOptionHandling
|
||||
app.Suggest = ctx.App.Suggest
|
||||
|
||||
app.categories = newCommandCategories()
|
||||
for _, command := range c.Subcommands {
|
||||
@ -280,6 +287,14 @@ func (c *Command) startApp(ctx *Context) error {
|
||||
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
|
||||
func (c *Command) VisibleFlags() []Flag {
|
||||
return visibleFlags(c.Flags)
|
||||
|
@ -30,7 +30,7 @@ func TestCommandFlagParsing(t *testing.T) {
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
_ = set.Parse(c.testArgs)
|
||||
|
||||
context := NewContext(app, set, nil)
|
||||
cCtx := NewContext(app, set, nil)
|
||||
|
||||
command := Command{
|
||||
Name: "test-cmd",
|
||||
@ -41,10 +41,10 @@ func TestCommandFlagParsing(t *testing.T) {
|
||||
SkipFlagParsing: c.skipFlagParsing,
|
||||
}
|
||||
|
||||
err := command.Run(context)
|
||||
err := command.Run(cCtx)
|
||||
|
||||
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
|
||||
func (c *Context) NumFlags() int {
|
||||
return c.flagSet.NFlag()
|
||||
func (cCtx *Context) NumFlags() int {
|
||||
return cCtx.flagSet.NFlag()
|
||||
}
|
||||
|
||||
// Set sets a context flag to a value.
|
||||
func (c *Context) Set(name, value string) error {
|
||||
return c.flagSet.Set(name, value)
|
||||
func (cCtx *Context) Set(name, value string) error {
|
||||
return cCtx.flagSet.Set(name, value)
|
||||
}
|
||||
|
||||
// IsSet determines if the flag was actually set
|
||||
func (c *Context) IsSet(name string) bool {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
func (cCtx *Context) IsSet(name string) bool {
|
||||
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||
isSet := false
|
||||
fs.Visit(func(f *flag.Flag) {
|
||||
if f.Name == name {
|
||||
@ -62,7 +62,7 @@ func (c *Context) IsSet(name string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
f := c.lookupFlag(name)
|
||||
f := cCtx.lookupFlag(name)
|
||||
if f == nil {
|
||||
return false
|
||||
}
|
||||
@ -74,28 +74,28 @@ func (c *Context) IsSet(name string) bool {
|
||||
}
|
||||
|
||||
// LocalFlagNames returns a slice of flag names used in this context.
|
||||
func (c *Context) LocalFlagNames() []string {
|
||||
func (cCtx *Context) LocalFlagNames() []string {
|
||||
var names []string
|
||||
c.flagSet.Visit(makeFlagNameVisitor(&names))
|
||||
cCtx.flagSet.Visit(makeFlagNameVisitor(&names))
|
||||
return names
|
||||
}
|
||||
|
||||
// FlagNames returns a slice of flag names used by the this context and all of
|
||||
// its parent contexts.
|
||||
func (c *Context) FlagNames() []string {
|
||||
func (cCtx *Context) FlagNames() []string {
|
||||
var names []string
|
||||
for _, ctx := range c.Lineage() {
|
||||
ctx.flagSet.Visit(makeFlagNameVisitor(&names))
|
||||
for _, pCtx := range cCtx.Lineage() {
|
||||
pCtx.flagSet.Visit(makeFlagNameVisitor(&names))
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// Lineage returns *this* context and all of its ancestor contexts in order from
|
||||
// child to parent
|
||||
func (c *Context) Lineage() []*Context {
|
||||
func (cCtx *Context) 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)
|
||||
}
|
||||
|
||||
@ -103,26 +103,26 @@ func (c *Context) Lineage() []*Context {
|
||||
}
|
||||
|
||||
// Value returns the value of the flag corresponding to `name`
|
||||
func (c *Context) Value(name string) interface{} {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
func (cCtx *Context) Value(name string) interface{} {
|
||||
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||
return fs.Lookup(name).Value.(flag.Getter).Get()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Args returns the command line arguments associated with the context.
|
||||
func (c *Context) Args() Args {
|
||||
ret := args(c.flagSet.Args())
|
||||
func (cCtx *Context) Args() Args {
|
||||
ret := args(cCtx.flagSet.Args())
|
||||
return &ret
|
||||
}
|
||||
|
||||
// NArg returns the number of the command line arguments.
|
||||
func (c *Context) NArg() int {
|
||||
return c.Args().Len()
|
||||
func (cCtx *Context) NArg() int {
|
||||
return cCtx.Args().Len()
|
||||
}
|
||||
|
||||
func (ctx *Context) lookupFlag(name string) Flag {
|
||||
for _, c := range ctx.Lineage() {
|
||||
func (cCtx *Context) lookupFlag(name string) Flag {
|
||||
for _, c := range cCtx.Lineage() {
|
||||
if c.Command == nil {
|
||||
continue
|
||||
}
|
||||
@ -136,8 +136,8 @@ func (ctx *Context) lookupFlag(name string) Flag {
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.App != nil {
|
||||
for _, f := range ctx.App.Flags {
|
||||
if cCtx.App != nil {
|
||||
for _, f := range cCtx.App.Flags {
|
||||
for _, n := range f.Names() {
|
||||
if n == name {
|
||||
return f
|
||||
@ -149,8 +149,8 @@ func (ctx *Context) lookupFlag(name string) Flag {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *Context) lookupFlagSet(name string) *flag.FlagSet {
|
||||
for _, c := range ctx.Lineage() {
|
||||
func (cCtx *Context) lookupFlagSet(name string) *flag.FlagSet {
|
||||
for _, c := range cCtx.Lineage() {
|
||||
if c.flagSet == nil {
|
||||
continue
|
||||
}
|
||||
@ -162,7 +162,7 @@ func (ctx *Context) lookupFlagSet(name string) *flag.FlagSet {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (context *Context) checkRequiredFlags(flags []Flag) requiredFlagsErr {
|
||||
func (cCtx *Context) checkRequiredFlags(flags []Flag) requiredFlagsErr {
|
||||
var missingFlags []string
|
||||
for _, f := range flags {
|
||||
if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() {
|
||||
@ -174,7 +174,7 @@ func (context *Context) checkRequiredFlags(flags []Flag) requiredFlagsErr {
|
||||
flagName = key
|
||||
}
|
||||
|
||||
if context.IsSet(strings.TrimSpace(key)) {
|
||||
if cCtx.IsSet(strings.TrimSpace(key)) {
|
||||
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
|
||||
|
||||
import (
|
||||
@ -80,14 +83,14 @@ func prepareCommands(commands []*Command, level int) []string {
|
||||
usageText,
|
||||
)
|
||||
|
||||
flags := prepareArgsWithValues(command.Flags)
|
||||
flags := prepareArgsWithValues(command.VisibleFlags())
|
||||
if len(flags) > 0 {
|
||||
prepared += fmt.Sprintf("\n%s", strings.Join(flags, "\n"))
|
||||
}
|
||||
|
||||
coms = append(coms, prepared)
|
||||
|
||||
// recursevly iterate subcommands
|
||||
// recursively iterate subcommands
|
||||
if len(command.Subcommands) > 0 {
|
||||
coms = append(
|
||||
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
|
||||
|
||||
**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
|
||||
|
||||
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
|
||||
give it a code review and make sure that it does not break backwards
|
||||
compatibility. If collaborators agree that it is in line with
|
||||
the vision of the project, we will work with you to get the code into
|
||||
a mergeable state and merge it into the main branch.
|
||||
> As a general guiding principle, the current maintainers may be notified via the
|
||||
> @urfave/cli GitHub team.
|
||||
|
||||
If you have contributed something significant to the project, we will most
|
||||
likely add you as a collaborator. As a collaborator you are given the ability
|
||||
to merge others pull requests. It is very important that new code does not
|
||||
break existing code, so be careful about what code you do choose to merge.
|
||||
All of the current maintainers are *volunteers* who live in various timezones with
|
||||
different scheduling needs, so please understand that your contribution or question may
|
||||
not get a response for many days.
|
||||
|
||||
If you feel like you have contributed to the project but have not yet been added
|
||||
as a collaborator, we probably forgot to add you :sweat_smile:. Please open an
|
||||
issue!
|
||||
### semantic versioning adherence
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
===
|
||||
|
||||
<!-- 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 -->
|
||||
# v1 guide
|
||||
|
||||
## 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
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"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) {
|
||||
// Given
|
||||
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,
|
||||
a.prepareFishFlags(command.Flags, command.Names())...,
|
||||
a.prepareFishFlags(command.VisibleFlags(), command.Names())...,
|
||||
)
|
||||
|
||||
// recursevly iterate subcommands
|
||||
|
123
fish_test.go
123
fish_test.go
@ -1,6 +1,8 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -19,3 +21,124 @@ func TestFishCompletion(t *testing.T) {
|
||||
expect(t, err, nil)
|
||||
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"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
@ -117,6 +116,12 @@ type DocGenerationFlag interface {
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
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
|
||||
@ -127,6 +132,14 @@ type VisibleFlag interface {
|
||||
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) {
|
||||
set := flag.NewFlagSet(name, flag.ContinueOnError)
|
||||
|
||||
@ -238,7 +251,7 @@ func prefixedNames(names []string, placeholder string) string {
|
||||
|
||||
func withEnvHint(envVars []string, str string) string {
|
||||
envText := ""
|
||||
if envVars != nil && len(envVars) > 0 {
|
||||
if len(envVars) > 0 {
|
||||
prefix := "$"
|
||||
suffix := ""
|
||||
sep := ", $"
|
||||
@ -253,7 +266,7 @@ func withEnvHint(envVars []string, str string) string {
|
||||
return str + envText
|
||||
}
|
||||
|
||||
func flagNames(name string, aliases []string) []string {
|
||||
func FlagNames(name string, aliases []string) []string {
|
||||
var ret []string
|
||||
|
||||
for _, part := range append([]string{name}, aliases...) {
|
||||
@ -267,17 +280,6 @@ func flagNames(name string, aliases []string) []string {
|
||||
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 {
|
||||
fileText := ""
|
||||
if filePath != "" {
|
||||
@ -286,68 +288,34 @@ func withFileHint(filePath, str string) string {
|
||||
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 {
|
||||
return " (default: " + format + ")"
|
||||
}
|
||||
|
||||
func stringifyFlag(f Flag) string {
|
||||
fv := flagValue(f)
|
||||
|
||||
switch f := f.(type) {
|
||||
case *IntSliceFlag:
|
||||
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))
|
||||
// enforce DocGeneration interface on flags to avoid reflection
|
||||
df, ok := f.(DocGenerationFlag)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String())
|
||||
|
||||
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 = ""
|
||||
}
|
||||
placeholder, usage := unquoteUsage(df.GetUsage())
|
||||
needsPlaceholder := df.TakesValue()
|
||||
|
||||
if needsPlaceholder && placeholder == "" {
|
||||
placeholder = defaultPlaceholder
|
||||
}
|
||||
|
||||
defaultValueString := ""
|
||||
|
||||
if s := df.GetDefaultText(); s != "" {
|
||||
defaultValueString = fmt.Sprintf(formatDefault("%s"), s)
|
||||
}
|
||||
|
||||
usageWithDefault := strings.TrimSpace(usage + defaultValueString)
|
||||
|
||||
return withEnvHint(flagStringSliceField(f, "EnvVars"),
|
||||
fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault))
|
||||
return withEnvHint(df.GetEnvVars(),
|
||||
fmt.Sprintf("%s\t%s", prefixedNames(df.Names(), placeholder), usageWithDefault))
|
||||
}
|
||||
|
||||
func stringifyIntSliceFlag(f *IntSliceFlag) string {
|
||||
@ -426,19 +394,26 @@ func hasFlag(flags []Flag, fl Flag) bool {
|
||||
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 {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if val, ok := syscall.Getenv(envVar); ok {
|
||||
return val, true
|
||||
if value, found := syscall.Getenv(envVar); found {
|
||||
return value, fmt.Sprintf("environment variable %q", envVar), true
|
||||
}
|
||||
}
|
||||
for _, fileVar := range strings.Split(filePath, ",") {
|
||||
if fileVar != "" {
|
||||
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"
|
||||
)
|
||||
|
||||
// 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
|
||||
func (f *BoolFlag) TakesValue() bool {
|
||||
return false
|
||||
@ -52,25 +16,38 @@ func (f *BoolFlag) GetUsage() string {
|
||||
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
|
||||
// string if the flag takes no value at all.
|
||||
func (f *BoolFlag) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *BoolFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
// GetDefaultText returns the default text for this flag
|
||||
func (f *BoolFlag) GetDefaultText() string {
|
||||
if f.DefaultText != "" {
|
||||
return f.DefaultText
|
||||
}
|
||||
return fmt.Sprintf("%v", f.Value)
|
||||
}
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
func (f *BoolFlag) GetEnvVars() []string {
|
||||
return f.EnvVars
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
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 != "" {
|
||||
valBool, err := strconv.ParseBool(val)
|
||||
|
||||
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
|
||||
@ -89,10 +66,15 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error {
|
||||
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
|
||||
// false if not found
|
||||
func (c *Context) Bool(name string) bool {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
func (cCtx *Context) Bool(name string) bool {
|
||||
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||
return lookupBool(name, fs)
|
||||
}
|
||||
return false
|
||||
|
@ -6,42 +6,6 @@ import (
|
||||
"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
|
||||
func (f *DurationFlag) TakesValue() bool {
|
||||
return true
|
||||
@ -52,25 +16,38 @@ func (f *DurationFlag) GetUsage() string {
|
||||
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
|
||||
// string if the flag takes no value at all.
|
||||
func (f *DurationFlag) GetValue() string {
|
||||
return f.Value.String()
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *DurationFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
// GetDefaultText returns the default text for this flag
|
||||
func (f *DurationFlag) GetDefaultText() string {
|
||||
if f.DefaultText != "" {
|
||||
return f.DefaultText
|
||||
}
|
||||
return f.GetValue()
|
||||
}
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
func (f *DurationFlag) GetEnvVars() []string {
|
||||
return f.EnvVars
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
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 != "" {
|
||||
valDuration, err := time.ParseDuration(val)
|
||||
|
||||
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
|
||||
@ -88,10 +65,15 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) error {
|
||||
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
|
||||
// 0 if not found
|
||||
func (c *Context) Duration(name string) time.Duration {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
func (cCtx *Context) Duration(name string) time.Duration {
|
||||
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||
return lookupDuration(name, fs)
|
||||
}
|
||||
return 0
|
||||
|
@ -6,42 +6,6 @@ import (
|
||||
"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
|
||||
func (f *Float64Flag) TakesValue() bool {
|
||||
return true
|
||||
@ -52,24 +16,37 @@ func (f *Float64Flag) GetUsage() string {
|
||||
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
|
||||
// string if the flag takes no value at all.
|
||||
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
|
||||
func (f *Float64Flag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
// GetDefaultText returns the default text for this flag
|
||||
func (f *Float64Flag) GetDefaultText() string {
|
||||
if f.DefaultText != "" {
|
||||
return f.DefaultText
|
||||
}
|
||||
return f.GetValue()
|
||||
}
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
func (f *Float64Flag) GetEnvVars() []string {
|
||||
return f.EnvVars
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f *Float64Flag) Apply(set *flag.FlagSet) error {
|
||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||
if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
|
||||
if val != "" {
|
||||
valFloat, err := strconv.ParseFloat(val, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse %q as float64 value for flag %s: %s", val, f.Name, err)
|
||||
return fmt.Errorf("could not parse %q as float64 value from %s for flag %s: %s", val, source, f.Name, err)
|
||||
}
|
||||
|
||||
f.Value = valFloat
|
||||
@ -88,10 +65,15 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) error {
|
||||
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
|
||||
// 0 if not found
|
||||
func (c *Context) Float64(name string) float64 {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
func (cCtx *Context) Float64(name string) float64 {
|
||||
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||
return lookupFloat64(name, fs)
|
||||
}
|
||||
return 0
|
||||
|
@ -43,18 +43,25 @@ func (f *Float64Slice) Set(value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
tmp, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range flagSplitMultiValues(value) {
|
||||
tmp, err := strconv.ParseFloat(strings.TrimSpace(s), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.slice = append(f.slice, tmp)
|
||||
f.slice = append(f.slice, tmp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
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
|
||||
@ -73,39 +80,10 @@ func (f *Float64Slice) Get() interface{} {
|
||||
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
|
||||
// (for usage defaults)
|
||||
func (f *Float64SliceFlag) String() string {
|
||||
return FlagStringer(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
|
||||
return withEnvHint(f.GetEnvVars(), stringifyFloat64SliceFlag(f))
|
||||
}
|
||||
|
||||
// TakesValue returns true if the flag takes a value, otherwise false
|
||||
@ -118,6 +96,11 @@ func (f *Float64SliceFlag) GetUsage() string {
|
||||
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
|
||||
// string if the flag takes no value at all.
|
||||
func (f *Float64SliceFlag) GetValue() string {
|
||||
@ -127,45 +110,69 @@ func (f *Float64SliceFlag) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *Float64SliceFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
// GetDefaultText returns the default text for this flag
|
||||
func (f *Float64SliceFlag) GetDefaultText() string {
|
||||
if f.DefaultText != "" {
|
||||
return f.DefaultText
|
||||
}
|
||||
return f.GetValue()
|
||||
}
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
func (f *Float64SliceFlag) GetEnvVars() []string {
|
||||
return f.EnvVars
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error {
|
||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||
if val != "" {
|
||||
f.Value = &Float64Slice{}
|
||||
// apply any default
|
||||
if f.Destination != nil && f.Value != nil {
|
||||
f.Destination.slice = make([]float64, len(f.Value.slice))
|
||||
copy(f.Destination.slice, f.Value.slice)
|
||||
}
|
||||
|
||||
for _, s := range strings.Split(val, ",") {
|
||||
if err := f.Value.Set(strings.TrimSpace(s)); err != nil {
|
||||
return fmt.Errorf("could not parse %q as float64 slice value for flag %s: %s", f.Value, f.Name, err)
|
||||
// resolve setValue (what we will assign to the set)
|
||||
var setValue *Float64Slice
|
||||
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
|
||||
// flags that have already been set by the environment.
|
||||
f.Value.hasBeenSet = false
|
||||
setValue.hasBeenSet = false
|
||||
f.HasBeenSet = true
|
||||
}
|
||||
}
|
||||
|
||||
if f.Value == nil {
|
||||
f.Value = &Float64Slice{}
|
||||
}
|
||||
copyValue := f.Value.clone()
|
||||
for _, name := range f.Names() {
|
||||
set.Var(copyValue, name, f.Usage)
|
||||
set.Var(setValue, name, f.Usage)
|
||||
}
|
||||
|
||||
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
|
||||
// nil if not found
|
||||
func (c *Context) Float64Slice(name string) []float64 {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
func (cCtx *Context) Float64Slice(name string) []float64 {
|
||||
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||
return lookupFloat64Slice(name, fs)
|
||||
}
|
||||
return nil
|
||||
@ -174,7 +181,7 @@ func (c *Context) Float64Slice(name string) []float64 {
|
||||
func lookupFloat64Slice(name string, set *flag.FlagSet) []float64 {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
if slice, ok := f.Value.(*Float64Slice); ok {
|
||||
if slice, ok := unwrapFlagValue(f.Value).(*Float64Slice); ok {
|
||||
return slice.Value()
|
||||
}
|
||||
}
|
||||
|
@ -11,42 +11,6 @@ type Generic interface {
|
||||
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
|
||||
func (f *GenericFlag) TakesValue() bool {
|
||||
return true
|
||||
@ -57,6 +21,11 @@ func (f *GenericFlag) GetUsage() string {
|
||||
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
|
||||
// string if the flag takes no value at all.
|
||||
func (f *GenericFlag) GetValue() string {
|
||||
@ -66,18 +35,26 @@ func (f *GenericFlag) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *GenericFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
// GetDefaultText returns the default text for this flag
|
||||
func (f *GenericFlag) GetDefaultText() string {
|
||||
if f.DefaultText != "" {
|
||||
return f.DefaultText
|
||||
}
|
||||
return f.GetValue()
|
||||
}
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
func (f *GenericFlag) GetEnvVars() []string {
|
||||
return f.EnvVars
|
||||
}
|
||||
|
||||
// Apply takes the flagset and calls Set on the generic flag with the value
|
||||
// provided by the user for parsing by the flag
|
||||
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 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
|
||||
@ -91,10 +68,15 @@ func (f GenericFlag) Apply(set *flag.FlagSet) error {
|
||||
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
|
||||
// nil if not found
|
||||
func (c *Context) Generic(name string) interface{} {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
func (cCtx *Context) Generic(name string) interface{} {
|
||||
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||
return lookupGeneric(name, fs)
|
||||
}
|
||||
return nil
|
||||
|
68
flag_int.go
68
flag_int.go
@ -6,42 +6,6 @@ import (
|
||||
"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
|
||||
func (f *IntFlag) TakesValue() bool {
|
||||
return true
|
||||
@ -52,25 +16,38 @@ func (f *IntFlag) GetUsage() string {
|
||||
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
|
||||
// string if the flag takes no value at all.
|
||||
func (f *IntFlag) GetValue() string {
|
||||
return fmt.Sprintf("%d", f.Value)
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *IntFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
// GetDefaultText returns the default text for this flag
|
||||
func (f *IntFlag) GetDefaultText() string {
|
||||
if f.DefaultText != "" {
|
||||
return f.DefaultText
|
||||
}
|
||||
return f.GetValue()
|
||||
}
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
func (f *IntFlag) GetEnvVars() []string {
|
||||
return f.EnvVars
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
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 != "" {
|
||||
valInt, err := strconv.ParseInt(val, 0, 64)
|
||||
|
||||
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)
|
||||
@ -89,10 +66,15 @@ func (f *IntFlag) Apply(set *flag.FlagSet) error {
|
||||
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
|
||||
// 0 if not found
|
||||
func (c *Context) Int(name string) int {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
func (cCtx *Context) Int(name string) int {
|
||||
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||
return lookupInt(name, fs)
|
||||
}
|
||||
return 0
|
||||
|
@ -6,42 +6,6 @@ import (
|
||||
"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
|
||||
func (f *Int64Flag) TakesValue() bool {
|
||||
return true
|
||||
@ -52,25 +16,38 @@ func (f *Int64Flag) GetUsage() string {
|
||||
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
|
||||
// string if the flag takes no value at all.
|
||||
func (f *Int64Flag) GetValue() string {
|
||||
return fmt.Sprintf("%d", f.Value)
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *Int64Flag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
// GetDefaultText returns the default text for this flag
|
||||
func (f *Int64Flag) GetDefaultText() string {
|
||||
if f.DefaultText != "" {
|
||||
return f.DefaultText
|
||||
}
|
||||
return f.GetValue()
|
||||
}
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
func (f *Int64Flag) GetEnvVars() []string {
|
||||
return f.EnvVars
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
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 != "" {
|
||||
valInt, err := strconv.ParseInt(val, 0, 64)
|
||||
|
||||
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
|
||||
@ -88,10 +65,15 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) error {
|
||||
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
|
||||
// 0 if not found
|
||||
func (c *Context) Int64(name string) int64 {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
func (cCtx *Context) Int64(name string) int64 {
|
||||
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||
return lookupInt64(name, fs)
|
||||
}
|
||||
return 0
|
||||
|
@ -43,19 +43,26 @@ func (i *Int64Slice) Set(value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
tmp, err := strconv.ParseInt(value, 0, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range flagSplitMultiValues(value) {
|
||||
tmp, err := strconv.ParseInt(strings.TrimSpace(s), 0, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.slice = append(i.slice, tmp)
|
||||
i.slice = append(i.slice, tmp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
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
|
||||
@ -74,39 +81,10 @@ func (i *Int64Slice) Get() interface{} {
|
||||
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
|
||||
// (for usage defaults)
|
||||
func (f *Int64SliceFlag) String() string {
|
||||
return FlagStringer(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
|
||||
return withEnvHint(f.GetEnvVars(), stringifyInt64SliceFlag(f))
|
||||
}
|
||||
|
||||
// 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
|
||||
func (f Int64SliceFlag) GetUsage() string {
|
||||
func (f *Int64SliceFlag) GetUsage() string {
|
||||
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
|
||||
// string if the flag takes no value at all.
|
||||
func (f *Int64SliceFlag) GetValue() string {
|
||||
@ -128,43 +111,67 @@ func (f *Int64SliceFlag) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *Int64SliceFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
// GetDefaultText returns the default text for this flag
|
||||
func (f *Int64SliceFlag) GetDefaultText() string {
|
||||
if f.DefaultText != "" {
|
||||
return f.DefaultText
|
||||
}
|
||||
return f.GetValue()
|
||||
}
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
func (f *Int64SliceFlag) GetEnvVars() []string {
|
||||
return f.EnvVars
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error {
|
||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||
f.Value = &Int64Slice{}
|
||||
// apply any default
|
||||
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, ",") {
|
||||
if err := f.Value.Set(strings.TrimSpace(s)); err != nil {
|
||||
return fmt.Errorf("could not parse %q as int64 slice value for flag %s: %s", val, f.Name, err)
|
||||
// resolve setValue (what we will assign to the set)
|
||||
var setValue *Int64Slice
|
||||
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
|
||||
// flags that have already been set by the environment.
|
||||
f.Value.hasBeenSet = false
|
||||
setValue.hasBeenSet = false
|
||||
f.HasBeenSet = true
|
||||
}
|
||||
|
||||
if f.Value == nil {
|
||||
f.Value = &Int64Slice{}
|
||||
}
|
||||
copyValue := f.Value.clone()
|
||||
for _, name := range f.Names() {
|
||||
set.Var(copyValue, name, f.Usage)
|
||||
set.Var(setValue, name, f.Usage)
|
||||
}
|
||||
|
||||
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
|
||||
// nil if not found
|
||||
func (c *Context) Int64Slice(name string) []int64 {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
func (cCtx *Context) Int64Slice(name string) []int64 {
|
||||
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||
return lookupInt64Slice(name, fs)
|
||||
}
|
||||
return nil
|
||||
@ -173,7 +180,7 @@ func (c *Context) Int64Slice(name string) []int64 {
|
||||
func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
if slice, ok := f.Value.(*Int64Slice); ok {
|
||||
if slice, ok := unwrapFlagValue(f.Value).(*Int64Slice); ok {
|
||||
return slice.Value()
|
||||
}
|
||||
}
|
||||
|
@ -54,19 +54,26 @@ func (i *IntSlice) Set(value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
tmp, err := strconv.ParseInt(value, 0, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range flagSplitMultiValues(value) {
|
||||
tmp, err := strconv.ParseInt(strings.TrimSpace(s), 0, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.slice = append(i.slice, int(tmp))
|
||||
i.slice = append(i.slice, int(tmp))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
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
|
||||
@ -85,39 +92,10 @@ func (i *IntSlice) Get() interface{} {
|
||||
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
|
||||
// (for usage defaults)
|
||||
func (f *IntSliceFlag) String() string {
|
||||
return FlagStringer(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
|
||||
return withEnvHint(f.GetEnvVars(), stringifyIntSliceFlag(f))
|
||||
}
|
||||
|
||||
// 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
|
||||
func (f IntSliceFlag) GetUsage() string {
|
||||
func (f *IntSliceFlag) GetUsage() string {
|
||||
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
|
||||
// string if the flag takes no value at all.
|
||||
func (f *IntSliceFlag) GetValue() string {
|
||||
@ -139,43 +122,67 @@ func (f *IntSliceFlag) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *IntSliceFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
// GetDefaultText returns the default text for this flag
|
||||
func (f *IntSliceFlag) GetDefaultText() string {
|
||||
if f.DefaultText != "" {
|
||||
return f.DefaultText
|
||||
}
|
||||
return f.GetValue()
|
||||
}
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
func (f *IntSliceFlag) GetEnvVars() []string {
|
||||
return f.EnvVars
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f *IntSliceFlag) Apply(set *flag.FlagSet) error {
|
||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||
f.Value = &IntSlice{}
|
||||
// apply any default
|
||||
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, ",") {
|
||||
if err := f.Value.Set(strings.TrimSpace(s)); err != nil {
|
||||
return fmt.Errorf("could not parse %q as int slice value for flag %s: %s", val, f.Name, err)
|
||||
// resolve setValue (what we will assign to the set)
|
||||
var setValue *IntSlice
|
||||
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
|
||||
// flags that have already been set by the environment.
|
||||
f.Value.hasBeenSet = false
|
||||
setValue.hasBeenSet = false
|
||||
f.HasBeenSet = true
|
||||
}
|
||||
|
||||
if f.Value == nil {
|
||||
f.Value = &IntSlice{}
|
||||
}
|
||||
copyValue := f.Value.clone()
|
||||
for _, name := range f.Names() {
|
||||
set.Var(copyValue, name, f.Usage)
|
||||
set.Var(setValue, name, f.Usage)
|
||||
}
|
||||
|
||||
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
|
||||
// nil if not found
|
||||
func (c *Context) IntSlice(name string) []int {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
func (cCtx *Context) IntSlice(name string) []int {
|
||||
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||
return lookupIntSlice(name, fs)
|
||||
}
|
||||
return nil
|
||||
@ -184,7 +191,7 @@ func (c *Context) IntSlice(name string) []int {
|
||||
func lookupIntSlice(name string, set *flag.FlagSet) []int {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
if slice, ok := f.Value.(*IntSlice); ok {
|
||||
if slice, ok := unwrapFlagValue(f.Value).(*IntSlice); ok {
|
||||
return slice.Value()
|
||||
}
|
||||
}
|
||||
|
74
flag_path.go
74
flag_path.go
@ -1,42 +1,11 @@
|
||||
package cli
|
||||
|
||||
import "flag"
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type PathFlag 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 *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
|
||||
}
|
||||
type Path = string
|
||||
|
||||
// TakesValue returns true of the flag takes a value, otherwise false
|
||||
func (f *PathFlag) TakesValue() bool {
|
||||
@ -48,20 +17,36 @@ func (f *PathFlag) GetUsage() string {
|
||||
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
|
||||
// string if the flag takes no value at all.
|
||||
func (f *PathFlag) GetValue() string {
|
||||
return f.Value
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *PathFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
// GetDefaultText returns the default text for this flag
|
||||
func (f *PathFlag) GetDefaultText() string {
|
||||
if f.DefaultText != "" {
|
||||
return f.DefaultText
|
||||
}
|
||||
if f.Value == "" {
|
||||
return f.Value
|
||||
}
|
||||
return fmt.Sprintf("%q", f.Value)
|
||||
}
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
func (f *PathFlag) GetEnvVars() []string {
|
||||
return f.EnvVars
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
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.HasBeenSet = true
|
||||
}
|
||||
@ -77,10 +62,15 @@ func (f *PathFlag) Apply(set *flag.FlagSet) error {
|
||||
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
|
||||
// "" if not found
|
||||
func (c *Context) Path(name string) string {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
func (cCtx *Context) Path(name string) string {
|
||||
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||
return lookupPath(name, fs)
|
||||
}
|
||||
|
||||
|
@ -1,43 +1,9 @@
|
||||
package cli
|
||||
|
||||
import "flag"
|
||||
|
||||
// StringFlag is a flag with type string
|
||||
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
|
||||
}
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// TakesValue returns true of the flag takes a value, otherwise false
|
||||
func (f *StringFlag) TakesValue() bool {
|
||||
@ -49,20 +15,36 @@ func (f *StringFlag) GetUsage() string {
|
||||
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
|
||||
// string if the flag takes no value at all.
|
||||
func (f *StringFlag) GetValue() string {
|
||||
return f.Value
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *StringFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
// GetDefaultText returns the default text for this flag
|
||||
func (f *StringFlag) GetDefaultText() string {
|
||||
if f.DefaultText != "" {
|
||||
return f.DefaultText
|
||||
}
|
||||
if f.Value == "" {
|
||||
return f.Value
|
||||
}
|
||||
return fmt.Sprintf("%q", f.Value)
|
||||
}
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
func (f *StringFlag) GetEnvVars() []string {
|
||||
return f.EnvVars
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
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.HasBeenSet = true
|
||||
}
|
||||
@ -78,10 +60,15 @@ func (f *StringFlag) Apply(set *flag.FlagSet) error {
|
||||
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
|
||||
// "" if not found
|
||||
func (c *Context) String(name string) string {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
func (cCtx *Context) String(name string) string {
|
||||
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||
return lookupString(name, fs)
|
||||
}
|
||||
return ""
|
||||
|
@ -42,7 +42,9 @@ func (s *StringSlice) Set(value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
s.slice = append(s.slice, value)
|
||||
for _, t := range flagSplitMultiValues(value) {
|
||||
s.slice = append(s.slice, strings.TrimSpace(t))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -68,41 +70,10 @@ func (s *StringSlice) Get() interface{} {
|
||||
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
|
||||
// (for usage defaults)
|
||||
func (f *StringSliceFlag) String() string {
|
||||
return FlagStringer(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
|
||||
return withEnvHint(f.GetEnvVars(), stringifyStringSliceFlag(f))
|
||||
}
|
||||
|
||||
// TakesValue returns true of the flag takes a value, otherwise false
|
||||
@ -115,6 +86,11 @@ func (f *StringSliceFlag) GetUsage() string {
|
||||
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
|
||||
// string if the flag takes no value at all.
|
||||
func (f *StringSliceFlag) GetValue() string {
|
||||
@ -124,48 +100,51 @@ func (f *StringSliceFlag) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *StringSliceFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
// GetDefaultText returns the default text for this flag
|
||||
func (f *StringSliceFlag) GetDefaultText() string {
|
||||
if f.DefaultText != "" {
|
||||
return f.DefaultText
|
||||
}
|
||||
return f.GetValue()
|
||||
}
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
func (f *StringSliceFlag) GetEnvVars() []string {
|
||||
return f.EnvVars
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
|
||||
|
||||
// apply any default
|
||||
if f.Destination != nil && f.Value != nil {
|
||||
f.Destination.slice = make([]string, len(f.Value.slice))
|
||||
copy(f.Destination.slice, f.Value.slice)
|
||||
|
||||
}
|
||||
|
||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||
if f.Value == nil {
|
||||
f.Value = &StringSlice{}
|
||||
}
|
||||
destination := f.Value
|
||||
if f.Destination != nil {
|
||||
destination = f.Destination
|
||||
}
|
||||
// resolve setValue (what we will assign to the set)
|
||||
var setValue *StringSlice
|
||||
switch {
|
||||
case f.Destination != nil:
|
||||
setValue = f.Destination
|
||||
case f.Value != nil:
|
||||
setValue = f.Value.clone()
|
||||
default:
|
||||
setValue = new(StringSlice)
|
||||
}
|
||||
|
||||
for _, s := range strings.Split(val, ",") {
|
||||
if err := destination.Set(strings.TrimSpace(s)); err != nil {
|
||||
return fmt.Errorf("could not parse %q as string value for flag %s: %s", val, f.Name, err)
|
||||
if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
|
||||
for _, s := range flagSplitMultiValues(val) {
|
||||
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
|
||||
// flags that have already been set by the environment.
|
||||
destination.hasBeenSet = false
|
||||
setValue.hasBeenSet = false
|
||||
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() {
|
||||
set.Var(setValue, name, f.Usage)
|
||||
}
|
||||
@ -173,10 +152,15 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
|
||||
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
|
||||
// nil if not found
|
||||
func (c *Context) StringSlice(name string) []string {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
func (cCtx *Context) StringSlice(name string) []string {
|
||||
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||
return lookupStringSlice(name, fs)
|
||||
}
|
||||
return nil
|
||||
@ -185,7 +169,7 @@ func (c *Context) StringSlice(name string) []string {
|
||||
func lookupStringSlice(name string, set *flag.FlagSet) []string {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
if slice, ok := f.Value.(*StringSlice); ok {
|
||||
if slice, ok := unwrapFlagValue(f.Value).(*StringSlice); ok {
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
newSetFloat64Slice := func(defaults ...float64) Float64Slice {
|
||||
s := NewFloat64Slice(defaults...)
|
||||
@ -85,33 +96,33 @@ func TestFlagsFromEnv(t *testing.T) {
|
||||
{"", false, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""},
|
||||
{"1", true, &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"}}, ""},
|
||||
{"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", 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.2", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value for flag seconds: .*`},
|
||||
{"foobar", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" 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 from environment variable "SECONDS" for flag 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: .*`},
|
||||
{"foobar", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" 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 from environment variable "SECONDS" for flag seconds: .*`},
|
||||
|
||||
{"1.0,2", newSetFloat64Slice(1, 2), &Float64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
|
||||
{"foobar", newSetFloat64Slice(), &Float64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "\[\]float64{}" as float64 slice value for flag seconds: .*`},
|
||||
{"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,2", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int slice value for flag seconds: .*`},
|
||||
{"foobar", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int slice value for flag seconds: .*`},
|
||||
{"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 from environment variable "SECONDS" for flag 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: .*`},
|
||||
{"foobar", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" 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 from environment variable "SECONDS" for flag seconds: .*`},
|
||||
|
||||
{"foo", "foo", &StringFlag{Name: "name", EnvVars: []string{"NAME"}}, ""},
|
||||
{"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"}}, ""},
|
||||
|
||||
{"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: .*`},
|
||||
{"foobar", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" 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 from environment variable "SECONDS" for flag 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: .*`},
|
||||
{"foobar", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" 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 from environment variable "SECONDS" for flag seconds: .*`},
|
||||
|
||||
{"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 {
|
||||
defer resetEnv(os.Environ())
|
||||
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{
|
||||
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 {
|
||||
name string
|
||||
aliases []string
|
||||
@ -218,7 +411,7 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var prefixStringFlagTests = []struct {
|
||||
var _ = []struct {
|
||||
name string
|
||||
aliases []string
|
||||
usage string
|
||||
@ -257,6 +450,14 @@ func TestStringFlagApply_SetsAllNames(t *testing.T) {
|
||||
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 {
|
||||
name string
|
||||
aliases []string
|
||||
@ -308,7 +509,15 @@ func TestPathFlagApply_SetsAllNames(t *testing.T) {
|
||||
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
|
||||
env string
|
||||
hinter FlagEnvHintFunc
|
||||
@ -395,7 +604,7 @@ func TestStringSliceFlagApply_SetsAllNames(t *testing.T) {
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestStringSliceFlagApply_UsesEnvValues(t *testing.T) {
|
||||
func TestStringSliceFlagApply_UsesEnvValues_noDefault(t *testing.T) {
|
||||
defer resetEnv(os.Environ())
|
||||
os.Clearenv()
|
||||
_ = os.Setenv("MY_GOAT", "vincent van goat,scape goat")
|
||||
@ -406,7 +615,22 @@ func TestStringSliceFlagApply_UsesEnvValues(t *testing.T) {
|
||||
|
||||
err := set.Parse(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) {
|
||||
@ -421,6 +645,14 @@ func TestStringSliceFlagApply_DefaultValueWithDestination(t *testing.T) {
|
||||
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 {
|
||||
name string
|
||||
expected string
|
||||
@ -470,6 +702,14 @@ func TestIntFlagApply_SetsAllNames(t *testing.T) {
|
||||
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 {
|
||||
name 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 {
|
||||
name 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 {
|
||||
name 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 {
|
||||
name string
|
||||
expected string
|
||||
@ -633,6 +897,14 @@ func TestDurationFlagApply_SetsAllNames(t *testing.T) {
|
||||
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 {
|
||||
name 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 {
|
||||
name 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 {
|
||||
name string
|
||||
expected string
|
||||
@ -867,6 +1155,14 @@ func TestFloat64FlagApply_SetsAllNames(t *testing.T) {
|
||||
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 {
|
||||
name 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 {
|
||||
name string
|
||||
value Generic
|
||||
@ -956,6 +1260,14 @@ func TestGenericFlagApply_SetsAllNames(t *testing.T) {
|
||||
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) {
|
||||
_ = (&App{
|
||||
Flags: []Flag{
|
||||
@ -1109,6 +1421,75 @@ func TestParseMultiStringSliceWithDestinationAndEnv(t *testing.T) {
|
||||
}).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) {
|
||||
_ = (&App{
|
||||
Flags: []Flag{
|
||||
@ -1860,7 +2241,7 @@ func TestFlagFromFile(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, filePathTest := range filePathTests {
|
||||
got, _ := flagFromEnvOrFile(filePathTest.name, filePathTest.path)
|
||||
got, _, _ := flagFromEnvOrFile(filePathTest.name, filePathTest.path)
|
||||
if want := filePathTest.expected; 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\""))
|
||||
}
|
||||
|
||||
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 {
|
||||
name string
|
||||
flag Flag
|
||||
@ -1992,43 +2394,43 @@ type flagDefaultTestCase struct {
|
||||
|
||||
func TestFlagDefaultValue(t *testing.T) {
|
||||
cases := []*flagDefaultTestCase{
|
||||
&flagDefaultTestCase{
|
||||
{
|
||||
name: "stringSclice",
|
||||
flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")},
|
||||
toParse: []string{"--flag", "parsed"},
|
||||
expect: `--flag value (default: "default1", "default2") (accepts multiple inputs)`,
|
||||
},
|
||||
&flagDefaultTestCase{
|
||||
{
|
||||
name: "float64Sclice",
|
||||
flag: &Float64SliceFlag{Name: "flag", Value: NewFloat64Slice(1.1, 2.2)},
|
||||
toParse: []string{"--flag", "13.3"},
|
||||
expect: `--flag value (default: 1.1, 2.2) (accepts multiple inputs)`,
|
||||
},
|
||||
&flagDefaultTestCase{
|
||||
{
|
||||
name: "int64Sclice",
|
||||
flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2)},
|
||||
toParse: []string{"--flag", "13"},
|
||||
expect: `--flag value (default: 1, 2) (accepts multiple inputs)`,
|
||||
},
|
||||
&flagDefaultTestCase{
|
||||
{
|
||||
name: "intSclice",
|
||||
flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)},
|
||||
toParse: []string{"--flag", "13"},
|
||||
expect: `--flag value (default: 1, 2) (accepts multiple inputs)`,
|
||||
},
|
||||
&flagDefaultTestCase{
|
||||
{
|
||||
name: "string",
|
||||
flag: &StringFlag{Name: "flag", Value: "default"},
|
||||
toParse: []string{"--flag", "parsed"},
|
||||
expect: `--flag value (default: "default")`,
|
||||
},
|
||||
&flagDefaultTestCase{
|
||||
{
|
||||
name: "bool",
|
||||
flag: &BoolFlag{Name: "flag", Value: true},
|
||||
toParse: []string{"--flag", "false"},
|
||||
expect: `--flag (default: true)`,
|
||||
},
|
||||
&flagDefaultTestCase{
|
||||
{
|
||||
name: "uint64",
|
||||
flag: &Uint64Flag{Name: "flag", Value: 1},
|
||||
toParse: []string{"--flag", "13"},
|
||||
@ -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) {
|
||||
var destination Timestamp
|
||||
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, *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
|
||||
hasBeenSet bool
|
||||
layout string
|
||||
location *time.Location
|
||||
}
|
||||
|
||||
// Timestamp constructor
|
||||
@ -31,9 +32,22 @@ func (t *Timestamp) SetLayout(layout string) {
|
||||
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
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@ -58,43 +72,6 @@ func (t *Timestamp) Get() interface{} {
|
||||
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
|
||||
func (f *TimestampFlag) TakesValue() bool {
|
||||
return true
|
||||
@ -105,6 +82,11 @@ func (f *TimestampFlag) GetUsage() string {
|
||||
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
|
||||
// string if the flag takes no value at all.
|
||||
func (f *TimestampFlag) GetValue() string {
|
||||
@ -114,9 +96,17 @@ func (f *TimestampFlag) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *TimestampFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
// GetDefaultText returns the default text for this flag
|
||||
func (f *TimestampFlag) GetDefaultText() string {
|
||||
if f.DefaultText != "" {
|
||||
return f.DefaultText
|
||||
}
|
||||
return f.GetValue()
|
||||
}
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
func (f *TimestampFlag) GetEnvVars() []string {
|
||||
return f.EnvVars
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
@ -128,14 +118,16 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
|
||||
f.Value = &Timestamp{}
|
||||
}
|
||||
f.Value.SetLayout(f.Layout)
|
||||
f.Value.SetLocation(f.Timezone)
|
||||
|
||||
if f.Destination != nil {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
@ -151,9 +143,14 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
|
||||
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
|
||||
func (c *Context) Timestamp(name string) *time.Time {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
func (cCtx *Context) Timestamp(name string) *time.Time {
|
||||
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||
return lookupTimestamp(name, fs)
|
||||
}
|
||||
return nil
|
||||
|
68
flag_uint.go
68
flag_uint.go
@ -6,42 +6,6 @@ import (
|
||||
"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
|
||||
func (f *UintFlag) TakesValue() bool {
|
||||
return true
|
||||
@ -52,18 +16,18 @@ func (f *UintFlag) GetUsage() string {
|
||||
return f.Usage
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *UintFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
// GetCategory returns the category for the flag
|
||||
func (f *UintFlag) GetCategory() string {
|
||||
return f.Category
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f *UintFlag) Apply(set *flag.FlagSet) error {
|
||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||
if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
|
||||
if val != "" {
|
||||
valInt, err := strconv.ParseUint(val, 0, 64)
|
||||
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)
|
||||
@ -88,10 +52,28 @@ func (f *UintFlag) GetValue() string {
|
||||
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
|
||||
// 0 if not found
|
||||
func (c *Context) Uint(name string) uint {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
func (cCtx *Context) Uint(name string) uint {
|
||||
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||
return lookupUint(name, fs)
|
||||
}
|
||||
return 0
|
||||
|
@ -6,42 +6,6 @@ import (
|
||||
"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
|
||||
func (f *Uint64Flag) TakesValue() bool {
|
||||
return true
|
||||
@ -52,18 +16,18 @@ func (f *Uint64Flag) GetUsage() string {
|
||||
return f.Usage
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *Uint64Flag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
// GetCategory returns the category for the flag
|
||||
func (f *Uint64Flag) GetCategory() string {
|
||||
return f.Category
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f *Uint64Flag) Apply(set *flag.FlagSet) error {
|
||||
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
|
||||
if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
|
||||
if val != "" {
|
||||
valInt, err := strconv.ParseUint(val, 0, 64)
|
||||
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
|
||||
@ -88,10 +52,28 @@ func (f *Uint64Flag) GetValue() string {
|
||||
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
|
||||
// 0 if not found
|
||||
func (c *Context) Uint64(name string) uint64 {
|
||||
if fs := c.lookupFlagSet(name); fs != nil {
|
||||
func (cCtx *Context) Uint64(name string) uint64 {
|
||||
if fs := cCtx.lookupFlagSet(name); fs != nil {
|
||||
return lookupUint64(name, fs)
|
||||
}
|
||||
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
|
||||
// original error messages. If this function is not set, the "Incorrect usage"
|
||||
// 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
|
||||
// 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
|
||||
// expected to be a single line.
|
||||
|
12
go.mod
12
go.mod
@ -1,9 +1,13 @@
|
||||
module github.com/urfave/cli/v2
|
||||
|
||||
go 1.11
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
github.com/BurntSushi/toml v1.1.0
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2
|
||||
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 v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
|
||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/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/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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
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"
|
||||
)
|
||||
|
||||
const (
|
||||
helpName = "help"
|
||||
helpAlias = "h"
|
||||
)
|
||||
|
||||
var helpCommand = &Command{
|
||||
Name: "help",
|
||||
Aliases: []string{"h"},
|
||||
Name: helpName,
|
||||
Aliases: []string{helpAlias},
|
||||
Usage: "Shows a list of commands or help for one command",
|
||||
ArgsUsage: "[command]",
|
||||
Action: func(c *Context) error {
|
||||
args := c.Args()
|
||||
Action: func(cCtx *Context) error {
|
||||
args := cCtx.Args()
|
||||
if args.Present() {
|
||||
return ShowCommandHelp(c, args.First())
|
||||
return ShowCommandHelp(cCtx, args.First())
|
||||
}
|
||||
|
||||
_ = ShowAppHelp(c)
|
||||
_ = ShowAppHelp(cCtx)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var helpSubcommand = &Command{
|
||||
Name: "help",
|
||||
Aliases: []string{"h"},
|
||||
Name: helpName,
|
||||
Aliases: []string{helpAlias},
|
||||
Usage: "Shows a list of commands or help for one command",
|
||||
ArgsUsage: "[command]",
|
||||
Action: func(c *Context) error {
|
||||
args := c.Args()
|
||||
Action: func(cCtx *Context) error {
|
||||
args := cCtx.Args()
|
||||
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
|
||||
// the default implementation of HelpPrinter, and may be called directly if
|
||||
// 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
|
||||
|
||||
// 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.
|
||||
func ShowAppHelp(c *Context) error {
|
||||
tpl := c.App.CustomAppHelpTemplate
|
||||
func ShowAppHelp(cCtx *Context) error {
|
||||
tpl := cCtx.App.CustomAppHelpTemplate
|
||||
if tpl == "" {
|
||||
tpl = AppHelpTemplate
|
||||
}
|
||||
|
||||
if c.App.ExtraInfo == nil {
|
||||
HelpPrinter(c.App.Writer, tpl, c.App)
|
||||
if cCtx.App.ExtraInfo == nil {
|
||||
HelpPrinter(cCtx.App.Writer, tpl, cCtx.App)
|
||||
return nil
|
||||
}
|
||||
|
||||
customAppData := func() 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
|
||||
}
|
||||
|
||||
// DefaultAppComplete prints the list of subcommands as the default app completion method
|
||||
func DefaultAppComplete(c *Context) {
|
||||
DefaultCompleteWithFlags(nil)(c)
|
||||
func DefaultAppComplete(cCtx *Context) {
|
||||
DefaultCompleteWithFlags(nil)(cCtx)
|
||||
}
|
||||
|
||||
func printCommandSuggestions(commands []*Command, writer io.Writer) {
|
||||
@ -102,7 +112,7 @@ func printCommandSuggestions(commands []*Command, writer io.Writer) {
|
||||
if command.Hidden {
|
||||
continue
|
||||
}
|
||||
if os.Getenv("_CLI_ZSH_AUTOCOMPLETE_HACK") == "1" {
|
||||
if strings.HasSuffix(os.Getenv("SHELL"), "zsh") {
|
||||
for _, name := range command.Names() {
|
||||
_, _ = 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) {
|
||||
return func(c *Context) {
|
||||
func DefaultCompleteWithFlags(cmd *Command) func(cCtx *Context) {
|
||||
return func(cCtx *Context) {
|
||||
if len(os.Args) > 2 {
|
||||
lastArg := os.Args[len(os.Args)-2]
|
||||
|
||||
if strings.HasPrefix(lastArg, "-") {
|
||||
printFlagSuggestions(lastArg, c.App.Flags, c.App.Writer)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
if cmd != nil {
|
||||
printCommandSuggestions(cmd.Subcommands, c.App.Writer)
|
||||
} else {
|
||||
printCommandSuggestions(c.App.Commands, c.App.Writer)
|
||||
printCommandSuggestions(cmd.Subcommands, cCtx.App.Writer)
|
||||
return
|
||||
}
|
||||
|
||||
printCommandSuggestions(cCtx.App.Commands, cCtx.App.Writer)
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,7 +224,13 @@ func ShowCommandHelp(ctx *Context, command string) error {
|
||||
}
|
||||
|
||||
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)
|
||||
@ -221,32 +244,32 @@ func ShowSubcommandHelpAndExit(c *Context, exitCode int) {
|
||||
}
|
||||
|
||||
// ShowSubcommandHelp prints help for the given subcommand
|
||||
func ShowSubcommandHelp(c *Context) error {
|
||||
if c == nil {
|
||||
func ShowSubcommandHelp(cCtx *Context) error {
|
||||
if cCtx == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.Command != nil {
|
||||
return ShowCommandHelp(c, c.Command.Name)
|
||||
if cCtx.Command != nil {
|
||||
return ShowCommandHelp(cCtx, cCtx.Command.Name)
|
||||
}
|
||||
|
||||
return ShowCommandHelp(c, "")
|
||||
return ShowCommandHelp(cCtx, "")
|
||||
}
|
||||
|
||||
// ShowVersion prints the version number of the App
|
||||
func ShowVersion(c *Context) {
|
||||
VersionPrinter(c)
|
||||
func ShowVersion(cCtx *Context) {
|
||||
VersionPrinter(cCtx)
|
||||
}
|
||||
|
||||
func printVersion(c *Context) {
|
||||
_, _ = fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
|
||||
func printVersion(cCtx *Context) {
|
||||
_, _ = 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
|
||||
func ShowCompletions(c *Context) {
|
||||
a := c.App
|
||||
func ShowCompletions(cCtx *Context) {
|
||||
a := cCtx.App
|
||||
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
|
||||
// allow using arbitrary functions in template rendering.
|
||||
func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs map[string]interface{}) {
|
||||
|
||||
const maxLineLength = 10000
|
||||
|
||||
funcMap := template.FuncMap{
|
||||
"join": strings.Join,
|
||||
"indent": indent,
|
||||
"nindent": nindent,
|
||||
"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 {
|
||||
funcMap[key] = value
|
||||
}
|
||||
@ -297,20 +337,20 @@ func printHelp(out io.Writer, templ string, data interface{}) {
|
||||
HelpPrinterCustom(out, templ, data, nil)
|
||||
}
|
||||
|
||||
func checkVersion(c *Context) bool {
|
||||
func checkVersion(cCtx *Context) bool {
|
||||
found := false
|
||||
for _, name := range VersionFlag.Names() {
|
||||
if c.Bool(name) {
|
||||
if cCtx.Bool(name) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
func checkHelp(c *Context) bool {
|
||||
func checkHelp(cCtx *Context) bool {
|
||||
found := false
|
||||
for _, name := range HelpFlag.Names() {
|
||||
if c.Bool(name) {
|
||||
if cCtx.Bool(name) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
@ -326,9 +366,9 @@ func checkCommandHelp(c *Context, name string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func checkSubcommandHelp(c *Context) bool {
|
||||
if c.Bool("h") || c.Bool("help") {
|
||||
_ = ShowSubcommandHelp(c)
|
||||
func checkSubcommandHelp(cCtx *Context) bool {
|
||||
if cCtx.Bool("h") || cCtx.Bool("help") {
|
||||
_ = ShowSubcommandHelp(cCtx)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -350,20 +390,20 @@ func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) {
|
||||
return true, arguments[:pos]
|
||||
}
|
||||
|
||||
func checkCompletions(c *Context) bool {
|
||||
if !c.shellComplete {
|
||||
func checkCompletions(cCtx *Context) bool {
|
||||
if !cCtx.shellComplete {
|
||||
return false
|
||||
}
|
||||
|
||||
if args := c.Args(); args.Present() {
|
||||
if args := cCtx.Args(); args.Present() {
|
||||
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
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
ShowCompletions(c)
|
||||
ShowCompletions(cCtx)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -384,3 +424,55 @@ func indent(spaces int, v string) string {
|
||||
func nindent(spaces int, v string) string {
|
||||
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"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
@ -1037,3 +1038,311 @@ func TestHideHelpCommand_WithSubcommands(t *testing.T) {
|
||||
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"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"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() {
|
||||
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.Name = "builder"
|
||||
@ -37,22 +63,56 @@ func main() {
|
||||
Name: "gfmrun",
|
||||
Action: GfmrunActionFunc,
|
||||
},
|
||||
{
|
||||
Name: "toc",
|
||||
Action: TocActionFunc,
|
||||
},
|
||||
{
|
||||
Name: "check-binary-size",
|
||||
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 != nil {
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
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 {
|
||||
cmd := exec.Command(arg, args...)
|
||||
|
||||
@ -60,80 +120,102 @@ func runCmd(arg string, args ...string) error {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
fmt.Fprintf(os.Stderr, "# ---> %s\n", cmd)
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func VetActionFunc(_ *cli.Context) error {
|
||||
return runCmd("go", "vet")
|
||||
func VetActionFunc(cCtx *cli.Context) error {
|
||||
return runCmd("go", "vet", cCtx.Path("top")+"/...")
|
||||
}
|
||||
|
||||
func TestActionFunc(c *cli.Context) error {
|
||||
for _, pkg := range packages {
|
||||
var packageName string
|
||||
tags := c.String("tags")
|
||||
|
||||
if pkg == "cli" {
|
||||
packageName = "github.com/urfave/cli/v2"
|
||||
} else {
|
||||
for _, pkg := range c.StringSlice("packages") {
|
||||
packageName := "github.com/urfave/cli/v2"
|
||||
|
||||
if pkg != "cli" {
|
||||
packageName = fmt.Sprintf("github.com/urfave/cli/v2/%s", pkg)
|
||||
}
|
||||
|
||||
coverProfile := fmt.Sprintf("--coverprofile=%s.coverprofile", pkg)
|
||||
|
||||
err := runCmd("go", "test", "-v", coverProfile, packageName)
|
||||
if err != nil {
|
||||
if err := runCmd(
|
||||
"go", "test",
|
||||
"-tags", tags,
|
||||
"-v",
|
||||
"--coverprofile", pkg+".coverprofile",
|
||||
"--covermode", "count",
|
||||
"--cover", packageName,
|
||||
packageName,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return testCleanup()
|
||||
return testCleanup(c.StringSlice("packages"))
|
||||
}
|
||||
|
||||
func testCleanup() error {
|
||||
var out bytes.Buffer
|
||||
func testCleanup(packages []string) error {
|
||||
out := &bytes.Buffer{}
|
||||
|
||||
fmt.Fprintf(out, "mode: count\n")
|
||||
|
||||
for _, pkg := range packages {
|
||||
file, err := os.Open(fmt.Sprintf("%s.coverprofile", pkg))
|
||||
filename := pkg + ".coverprofile"
|
||||
|
||||
lineBytes, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lines := strings.Split(string(lineBytes), "\n")
|
||||
|
||||
out.Write(b)
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(out, strings.Join(lines[1:], "\n"))
|
||||
|
||||
err = os.Remove(fmt.Sprintf("%s.coverprofile", pkg))
|
||||
if err != nil {
|
||||
if err := os.Remove(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
outFile, err := os.Create("coverage.txt")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = out.WriteTo(outFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = outFile.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return os.WriteFile("coverage.txt", out.Bytes(), 0644)
|
||||
}
|
||||
|
||||
func GfmrunActionFunc(c *cli.Context) error {
|
||||
filename := c.Args().Get(0)
|
||||
func GfmrunActionFunc(cCtx *cli.Context) error {
|
||||
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 == "" {
|
||||
filename = "README.md"
|
||||
}
|
||||
@ -162,26 +244,11 @@ func GfmrunActionFunc(c *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return runCmd("gfmrun", "-c", fmt.Sprint(counter), "-s", filename)
|
||||
}
|
||||
|
||||
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 {
|
||||
if err := runCmd("gfmrun", "-c", fmt.Sprint(counter), "-s", filename); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = runCmd("git", "diff", "--exit-code")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return os.RemoveAll(tmpDir)
|
||||
}
|
||||
|
||||
// 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"
|
||||
helloSourceFilePath = "./internal/example-hello-world/example-hello-world.go"
|
||||
helloBuiltFilePath = "./internal/example-hello-world/built-example"
|
||||
desiredMinBinarySize = 1.9
|
||||
desiredMaxBinarySize = 2.2
|
||||
badNewsEmoji = "🚨"
|
||||
goodNewsEmoji = "✨"
|
||||
checksPassedEmoji = "✅"
|
||||
mbStringFormatter = "%.1fMB"
|
||||
)
|
||||
|
||||
desiredMinBinarySize := 1.675
|
||||
|
||||
tags := c.String("tags")
|
||||
|
||||
if strings.Contains(tags, "urfave_cli_no_docs") {
|
||||
desiredMinBinarySize = 1.39
|
||||
}
|
||||
|
||||
// get cli example size
|
||||
cliSize, err := getSize(cliSourceFilePath, cliBuiltFilePath)
|
||||
cliSize, err := getSize(cliSourceFilePath, cliBuiltFilePath, tags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get hello world size
|
||||
helloSize, err := getSize(helloSourceFilePath, helloBuiltFilePath)
|
||||
helloSize, err := getSize(helloSourceFilePath, helloBuiltFilePath, tags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -270,9 +341,72 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) {
|
||||
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
|
||||
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 {
|
||||
fmt.Println("issue getting size for example binary")
|
||||
return 0, err
|
||||
|
@ -7,5 +7,5 @@ import (
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
errStr := err.Error()
|
||||
trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: -")
|
||||
if errStr == trimmed {
|
||||
trimmed, trimErr := flagFromError(err)
|
||||
if trimErr != nil {
|
||||
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 {
|
||||
shortFlagsExist := func(s string) bool {
|
||||
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
|
||||
// render custom help text by setting this variable.
|
||||
var AppHelpTemplate = `NAME:
|
||||
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
|
||||
{{$v := offset .Name 6}}{{wrap .Name 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}}
|
||||
|
||||
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}}{{end}}{{end}}{{if .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}}:
|
||||
{{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}}
|
||||
{{.Name}}:{{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:
|
||||
{{range $index, $option := .VisibleFlags}}{{if $index}}
|
||||
{{end}}{{$option}}{{end}}{{end}}{{if .Copyright}}
|
||||
{{end}}{{wrap $option.String 6}}{{end}}{{end}}{{end}}{{if .Copyright}}
|
||||
|
||||
COPYRIGHT:
|
||||
{{.Copyright}}{{end}}
|
||||
{{wrap .Copyright 3}}{{end}}
|
||||
`
|
||||
|
||||
// CommandHelpTemplate is the text template for the command help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
var CommandHelpTemplate = `NAME:
|
||||
{{.HelpName}} - {{.Usage}}
|
||||
{{$v := offset .HelpName 6}}{{wrap .HelpName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}}
|
||||
|
||||
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}}{{end}}{{if .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:
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}
|
||||
{{end}}{{end}}{{end}}
|
||||
`
|
||||
|
||||
// SubcommandHelpTemplate is the text template for the subcommand help topic.
|
||||
@ -59,10 +69,10 @@ var SubcommandHelpTemplate = `NAME:
|
||||
{{.HelpName}} - {{.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 | nindent 3 | trim}}{{end}}
|
||||
{{wrap .Description 3}}{{end}}
|
||||
|
||||
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
|
||||
{{.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