Merge remote-tracking branch 'origin/main' into merging-main-to-v3-dev-main

main
Dan Buch 2 years ago
commit a82c9b1433
Signed by: meatballhat
GPG Key ID: A12F782281063434

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

@ -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

@ -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

@ -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

@ -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

@ -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)

@ -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() {
if !filepath.IsAbs(value) && isc.Source() != "" {
basePathAbs, err := filepath.Abs(isc.Source())
if err != nil {
return err
}
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() {
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 TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) {
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) {
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)
}

@ -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

@ -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
}

@ -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

@ -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
}

@ -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

@ -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)
}
}

@ -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
}
}

@ -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/).

@ -0,0 +1 @@
cli.urfave.org

@ -0,0 +1 @@
../CODE_OF_CONDUCT.md

@ -1,18 +1,143 @@
## Contributing
Use @urfave/cli to ping the maintainers.
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.
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.
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!
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.
> As a general guiding principle, the current maintainers may be notified via the
> @urfave/cli GitHub team.
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.
### 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
-->

@ -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.

@ -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.

@ -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

@ -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

@ -0,0 +1 @@
manual.md

File diff suppressed because it is too large Load Diff

@ -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()

@ -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

@ -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))
}

@ -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: {}

@ -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())
placeholder, usage := unquoteUsage(df.GetUsage())
needsPlaceholder := df.TakesValue()
needsPlaceholder := false
defaultValueString := ""
val := fv.FieldByName("Value")
if val.IsValid() {
needsPlaceholder = val.Kind() != reflect.Bool
defaultValueString = fmt.Sprintf(formatDefault("%v"), val.Interface())
if val.Kind() == reflect.String && val.String() != "" {
defaultValueString = fmt.Sprintf(formatDefault("%q"), val.String())
}
}
helpText := fv.FieldByName("DefaultText")
if helpText.IsValid() && helpText.String() != "" {
needsPlaceholder = val.Kind() != reflect.Bool
defaultValueString = fmt.Sprintf(formatDefault("%s"), helpText.String())
if needsPlaceholder && placeholder == "" {
placeholder = defaultPlaceholder
}
if defaultValueString == formatDefault("") {
defaultValueString = ""
}
defaultValueString := ""
if needsPlaceholder && placeholder == "" {
placeholder = defaultPlaceholder
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, ",")
}

@ -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 flags 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 flags 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)
}
// GetDefaultText returns the default text for this flag
func (f *Float64Flag) GetDefaultText() string {
if f.DefaultText != "" {
return f.DefaultText
}
return f.GetValue()
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *Float64Flag) IsVisible() bool {
return !f.Hidden
// 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 flags 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 flags 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 flags 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

@ -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 flags 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 flags 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 flags 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 flags 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()
}
}

@ -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 flags 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 flags 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 flags 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()
}
}

@ -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 flags 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

@ -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 flags 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 flags 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

@ -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.

@ -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

@ -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=

File diff suppressed because it is too large Load Diff

@ -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
}

@ -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")
for _, pkg := range c.StringSlice("packages") {
packageName := "github.com/urfave/cli/v2"
if pkg == "cli" {
packageName = "github.com/urfave/cli/v2"
} else {
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))
if err != nil {
return err
}
filename := pkg + ".coverprofile"
b, err := ioutil.ReadAll(file)
lineBytes, err := os.ReadFile(filename)
if err != nil {
return err
}
out.Write(b)
err = file.Close()
if err != nil {
return err
}
lines := strings.Split(string(lineBytes), "\n")
err = os.Remove(fmt.Sprintf("%s.coverprofile", pkg))
if err != nil {
fmt.Fprintf(out, strings.Join(lines[1:], "\n"))
if err := os.Remove(filename); err != nil {
return err
}
}
outFile, err := os.Create("coverage.txt")
return os.WriteFile("coverage.txt", out.Bytes(), 0644)
}
func GfmrunActionFunc(cCtx *cli.Context) error {
top := cCtx.Path("top")
bash, err := exec.LookPath("bash")
if err != nil {
return err
}
_, err = out.WriteTo(outFile)
os.Setenv("SHELL", bash)
tmpDir, err := os.MkdirTemp("", "urfave-cli*")
if err != nil {
return err
}
err = outFile.Close()
wd, err := os.Getwd()
if err != nil {
return err
}
return nil
}
if err := os.Chdir(tmpDir); err != nil {
return err
}
fmt.Fprintf(cCtx.App.ErrWriter, "# ---> workspace/TMPDIR is %q\n", tmpDir)
func GfmrunActionFunc(c *cli.Context) error {
filename := c.Args().Get(0)
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 {
return err
}
err = runCmd("git", "diff", "--exit-code")
if err != nil {
if err := runCmd("gfmrun", "-c", fmt.Sprint(counter), "-s", filename); 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{""})
}

@ -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
}

@ -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
*/}}

@ -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
*/}}

@ -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"
}

@ -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)
}
},
)
}
}

@ -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
}

@ -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")
}
}

@ -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

@ -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

@ -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:] {

@ -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
}

@ -0,0 +1,10 @@
//go:build !go1.18
// +build !go1.18
package cli
import (
"flag"
)
func unwrapFlagValue(v flag.Value) flag.Value { return v }

File diff suppressed because it is too large Load Diff

@ -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)
}

@ -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)
}

@ -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}}

@ -0,0 +1 @@
# empty file

File diff suppressed because it is too large Load Diff

@ -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

@ -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…
Cancel
Save