Merge pull request #1510 from urfave/v3-porting

Porting to v3
This commit is contained in:
dearchap 2022-10-05 22:22:08 -04:00 committed by GitHub
commit 9166808eb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
93 changed files with 7532 additions and 4017 deletions

View File

@ -1 +0,0 @@
comment: false

9
.github/codecov.yml vendored Normal file
View File

@ -0,0 +1,9 @@
comment: false
coverage:
status:
project:
default:
threshold: 5%
patch:
default:
threshold: 5%

View File

@ -1,5 +1,4 @@
name: Run Tests
on:
push:
branches:
@ -12,87 +11,68 @@ on:
branches:
- main
- v3-dev-main
permissions:
contents: read
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
go: [1.18.x]
go: [1.18.x, 1.19.x]
name: ${{ matrix.os }} @ Go ${{ matrix.go }}
runs-on: ${{ matrix.os }}
steps:
- name: Set up Go ${{ matrix.go }}
uses: actions/setup-go@v3
- uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go }}
- name: Set PATH
run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}"
- name: Checkout Code
uses: actions/checkout@v3
- name: GOFMT Check
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.18.x' && matrix.os == 'ubuntu-latest'
uses: codecov/codecov-action@v2
- uses: actions/checkout@v3
- if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest'
run: make ensure-goimports
- if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest'
run: make lint
- run: make vet
- run: make test
env:
GFLAGS: -tags urfave_cli_no_docs
- run: make test
- run: make -C cmd/urfave-cli-genflags
- run: make check-binary-size
env:
GFLAGS: -tags urfave_cli_no_docs
- run: make check-binary-size
- run: make yamlfmt
- run: make diffcheck
- if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest'
run: make v2diff
- if: success() && matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest'
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
test-docs:
name: test-docs
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v3
- uses: actions/setup-go@v3
with:
go-version: 1.18.x
- name: Use Node.js 16
uses: actions/setup-node@v3
go-version: 1.19.x
- uses: actions/setup-node@v3
with:
node-version: '16'
- name: Set PATH
run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}"
- name: Checkout Code
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"
- name: gfmrun
run: go run internal/build/build.go gfmrun docs/v3/index.md
- name: diff check
run: |
git diff --exit-code
git diff --cached --exit-code
- uses: actions/checkout@v3
- run: make ensure-gfmrun
- run: make gfmrun
env:
FLAGS: --walk docs/v3/
- run: make diffcheck
publish:
permissions:
contents: write
# TODO: switch once v3 is released {{
# if: startswith(github.ref, 'refs/tags/')
if: 'false'
@ -101,18 +81,13 @@ jobs:
needs: [test-docs]
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- 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
- run: make ensure-mkdocs
env:
FLAGS: --upgrade-pip
- run: make set-mkdocs-remote
env:
MKDOCS_REMOTE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: make deploy-mkdocs

18
.gitignore vendored
View File

@ -1,10 +1,12 @@
*.coverprofile
*.orig
vendor
.idea
internal/*/built-example
coverage.txt
/.local/
/site/
*.exe
*.orig
.*envrc
.envrc
.idea
/.local/
/cmd/urfave-cli-genflags/urfave-cli-genflags
/site/
coverage.txt
internal/*/built-example
vendor

View File

@ -4,8 +4,10 @@
# are very important so that maintainers and contributors can focus their
# attention on files that are primarily Go.
GO_RUN_BUILD := go run internal/build/build.go
.PHONY: all
all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun v2diff
all: generate vet test check-binary-size gfmrun yamlfmt 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
@ -13,28 +15,12 @@ all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun v
#
# $ 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
$(GO_RUN_BUILD) $(GFLAGS) $* $(FLAGS)
.PHONY: docs
docs:
mkdocs build
.PHONY: docs-deps
docs-deps:
pip install -r mkdocs-requirements.txt
.PHONY: serve-docs
serve-docs:
mkdocs serve

View File

@ -64,15 +64,22 @@ func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(cCtx *c
// ApplyInputSourceValue applies a generic value to the flagSet if required
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 f.set == nil || cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars) {
return nil
}
for _, name := range f.GenericFlag.Names() {
if !isc.isSet(name) {
continue
}
value, err := isc.Generic(name)
if err != nil {
return err
}
if value != nil {
for _, name := range f.Names() {
_ = f.set.Set(name, value.String())
}
if value == nil {
continue
}
for _, n := range f.Names() {
_ = f.set.Set(n, value.String())
}
}
@ -81,19 +88,30 @@ func (f *GenericFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceCo
// ApplyInputSourceValue applies a StringSlice value to the flagSet if required
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 f.set == nil || cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars) {
return nil
}
for _, name := range f.StringSliceFlag.Names() {
if !isc.isSet(name) {
continue
}
value, err := isc.StringSlice(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
}
if value == nil {
continue
}
var sliceValue = *(cli.NewStringSlice(value...))
for _, n := range f.Names() {
underlyingFlag := f.set.Lookup(n)
if underlyingFlag == nil {
continue
}
underlyingFlag.Value = &sliceValue
}
if f.Destination != nil {
f.Destination.Set(sliceValue.Serialize())
}
}
return nil
@ -101,19 +119,30 @@ func (f *StringSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSour
// ApplyInputSourceValue applies a IntSlice value if required
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 f.set == nil || cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars) {
return nil
}
for _, name := range f.IntSliceFlag.Names() {
if !isc.isSet(name) {
continue
}
value, err := isc.IntSlice(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
}
if value == nil {
continue
}
var sliceValue = *(cli.NewIntSlice(value...))
for _, n := range f.Names() {
underlyingFlag := f.set.Lookup(n)
if underlyingFlag == nil {
continue
}
underlyingFlag.Value = &sliceValue
}
if f.Destination != nil {
f.Destination.Set(sliceValue.Serialize())
}
}
return nil
@ -121,15 +150,19 @@ func (f *IntSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceC
// ApplyInputSourceValue applies a Bool value to the flagSet if required
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 f.set == nil || cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars) {
return nil
}
for _, name := range f.BoolFlag.Names() {
if !isc.isSet(name) {
continue
}
value, err := isc.Bool(name)
if err != nil {
return err
}
if value {
for _, name := range f.Names() {
_ = f.set.Set(name, strconv.FormatBool(value))
}
for _, n := range f.Names() {
_ = f.set.Set(n, strconv.FormatBool(value))
}
}
return nil
@ -137,15 +170,19 @@ func (f *BoolFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceConte
// ApplyInputSourceValue applies a String value to the flagSet if required
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 f.set == nil || cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars) {
return nil
}
for _, name := range f.StringFlag.Names() {
if !isc.isSet(name) {
continue
}
value, err := isc.String(name)
if err != nil {
return err
}
if value != "" {
for _, name := range f.Names() {
_ = f.set.Set(name, value)
}
for _, n := range f.Names() {
_ = f.set.Set(n, value)
}
}
return nil
@ -153,25 +190,29 @@ func (f *StringFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceCon
// ApplyInputSourceValue applies a Path value to the flagSet if required
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 f.set == nil || cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars) {
return nil
}
for _, name := range f.PathFlag.Names() {
if !isc.isSet(name) {
continue
}
value, err := isc.String(name)
if err != nil {
return err
}
if value != "" {
for _, name := range f.Names() {
if !filepath.IsAbs(value) && isc.Source() != "" {
basePathAbs, err := filepath.Abs(isc.Source())
if err != nil {
return err
}
value = filepath.Join(filepath.Dir(basePathAbs), value)
if value == "" {
continue
}
for _, n := range f.Names() {
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(n, value)
}
}
return nil
@ -179,13 +220,19 @@ func (f *PathFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceConte
// ApplyInputSourceValue applies a int value to the flagSet if required
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 f.set == nil || cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars) {
return nil
}
for _, name := range f.IntFlag.Names() {
if !isc.isSet(name) {
continue
}
value, err := isc.Int(name)
if err != nil {
return err
}
for _, name := range f.Names() {
_ = f.set.Set(name, strconv.FormatInt(int64(value), 10))
for _, n := range f.Names() {
_ = f.set.Set(n, strconv.FormatInt(int64(value), 10))
}
}
return nil
@ -193,13 +240,19 @@ func (f *IntFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContex
// ApplyInputSourceValue applies a Duration value to the flagSet if required
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 f.set == nil || cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars) {
return nil
}
for _, name := range f.DurationFlag.Names() {
if !isc.isSet(name) {
continue
}
value, err := isc.Duration(name)
if err != nil {
return err
}
for _, name := range f.Names() {
_ = f.set.Set(name, value.String())
for _, n := range f.Names() {
_ = f.set.Set(n, value.String())
}
}
return nil
@ -207,14 +260,20 @@ func (f *DurationFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceC
// ApplyInputSourceValue applies a Float64 value to the flagSet if required
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 f.set == nil || cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars) {
return nil
}
for _, name := range f.Float64Flag.Names() {
if !isc.isSet(name) {
continue
}
value, err := isc.Float64(name)
if err != nil {
return err
}
floatStr := float64ToString(value)
for _, name := range f.Names() {
_ = f.set.Set(name, floatStr)
for _, n := range f.Names() {
_ = f.set.Set(n, floatStr)
}
}
return nil

View File

@ -37,6 +37,20 @@ func (ris *racyInputSource) isSet(name string) bool {
return true
}
func TestGenericApplyInputSourceValue_Alias(t *testing.T) {
v := &Parser{"abc", "def"}
tis := testApplyInputSource{
Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Aliases: []string{"test_alias"}, Value: &Parser{}}),
FlagName: "test_alias",
MapValue: v,
}
c := runTest(t, tis)
expect(t, v, c.Generic("test_alias"))
c = runRacyTest(t, tis)
refute(t, v, c.Generic("test_alias"))
}
func TestGenericApplyInputSourceValue(t *testing.T) {
v := &Parser{"abc", "def"}
tis := testApplyInputSource{
@ -85,27 +99,62 @@ func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) {
refute(t, &Parser{"abc", "def"}, c.Generic("test"))
}
func TestStringSliceApplyInputSourceValue(t *testing.T) {
func TestStringSliceApplyInputSourceValue_Alias(t *testing.T) {
dest := cli.NewStringSlice()
tis := testApplyInputSource{
Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}),
Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", Aliases: []string{"test_alias"}, Destination: dest}),
FlagName: "test_alias",
MapValue: []interface{}{"hello", "world"},
}
c := runTest(t, tis)
expect(t, c.StringSlice("test_alias"), []string{"hello", "world"})
expect(t, dest.Value(), []string{"hello", "world"})
// reset dest
dest = cli.NewStringSlice()
tis = testApplyInputSource{
Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", Aliases: []string{"test_alias"}, Destination: dest}),
FlagName: "test_alias",
MapValue: []interface{}{"hello", "world"},
}
c = runRacyTest(t, tis)
refute(t, c.StringSlice("test_alias"), []string{"hello", "world"})
refute(t, dest.Value(), []string{"hello", "world"})
}
func TestStringSliceApplyInputSourceValue(t *testing.T) {
dest := cli.NewStringSlice()
tis := testApplyInputSource{
Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", Destination: dest}),
FlagName: "test",
MapValue: []interface{}{"hello", "world"},
}
c := runTest(t, tis)
expect(t, c.StringSlice("test"), []string{"hello", "world"})
expect(t, dest.Value(), []string{"hello", "world"})
// reset dest
dest = cli.NewStringSlice()
tis = testApplyInputSource{
Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", Destination: dest}),
FlagName: "test",
MapValue: []interface{}{"hello", "world"},
}
c = runRacyTest(t, tis)
refute(t, c.StringSlice("test"), []string{"hello", "world"})
refute(t, dest.Value(), []string{"hello", "world"})
}
func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) {
dest := cli.NewStringSlice()
c := runTest(t, testApplyInputSource{
Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}),
Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", Destination: dest}),
FlagName: "test",
MapValue: []interface{}{"hello", "world"},
ContextValueString: "ohno",
})
expect(t, c.StringSlice("test"), []string{"ohno"})
expect(t, dest.Value(), []string{"ohno"})
}
func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
@ -123,31 +172,74 @@ func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
refute(t, c.StringSlice("test"), []string{"oh", "no"})
}
func TestIntSliceApplyInputSourceValue(t *testing.T) {
func TestIntSliceApplyInputSourceValue_Alias(t *testing.T) {
dest := cli.NewIntSlice()
tis := testApplyInputSource{
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}),
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", Aliases: []string{"test_alias"}, Destination: dest}),
FlagName: "test_alias",
MapValue: []interface{}{1, 2},
}
c := runTest(t, tis)
expect(t, c.IntSlice("test_alias"), []int{1, 2})
expect(t, dest.Value(), []int{1, 2})
dest = cli.NewIntSlice()
tis = testApplyInputSource{
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", Aliases: []string{"test_alias"}, Destination: dest}),
FlagName: "test_alias",
MapValue: []interface{}{1, 2},
}
c = runRacyTest(t, tis)
refute(t, c.IntSlice("test_alias"), []int{1, 2})
refute(t, dest.Value(), []int{1, 2})
}
func TestIntSliceApplyInputSourceValue(t *testing.T) {
dest := cli.NewIntSlice()
tis := testApplyInputSource{
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", Destination: dest}),
FlagName: "test",
MapValue: []interface{}{1, 2},
}
c := runTest(t, tis)
expect(t, c.IntSlice("test"), []int{1, 2})
expect(t, dest.Value(), []int{1, 2})
// reset dest
dest = cli.NewIntSlice()
tis = testApplyInputSource{
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", Destination: dest}),
FlagName: "test",
MapValue: []interface{}{1, 2},
}
c = runRacyTest(t, tis)
refute(t, c.IntSlice("test"), []int{1, 2})
refute(t, dest.Value(), []int{1, 2})
}
func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) {
dest := cli.NewIntSlice()
tis := testApplyInputSource{
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}),
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", Destination: dest}),
FlagName: "test",
MapValue: []interface{}{1, 2},
ContextValueString: "3",
}
c := runTest(t, tis)
expect(t, c.IntSlice("test"), []int{3})
expect(t, dest.Value(), []int{3})
// reset dest
dest = cli.NewIntSlice()
tis = testApplyInputSource{
Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", Destination: dest}),
FlagName: "test",
MapValue: []interface{}{1, 2},
ContextValueString: "3",
}
c = runRacyTest(t, tis)
refute(t, c.IntSlice("test"), []int{3})
refute(t, dest.Value(), []int{3})
}
func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
@ -178,6 +270,19 @@ func TestBoolApplyInputSourceMethodSet(t *testing.T) {
refute(t, true, c.Bool("test"))
}
func TestBoolApplyInputSourceMethodSet_Alias(t *testing.T) {
tis := testApplyInputSource{
Flag: NewBoolFlag(&cli.BoolFlag{Name: "test", Aliases: []string{"test_alias"}}),
FlagName: "test_alias",
MapValue: true,
}
c := runTest(t, tis)
expect(t, true, c.Bool("test_alias"))
c = runRacyTest(t, tis)
refute(t, true, c.Bool("test_alias"))
}
func TestBoolApplyInputSourceMethodContextSet(t *testing.T) {
tis := testApplyInputSource{
Flag: NewBoolFlag(&cli.BoolFlag{Name: "test"}),
@ -207,6 +312,19 @@ func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) {
refute(t, true, c.Bool("test"))
}
func TestStringApplyInputSourceMethodSet_Alias(t *testing.T) {
tis := testApplyInputSource{
Flag: NewStringFlag(&cli.StringFlag{Name: "test", Aliases: []string{"test_alias"}}),
FlagName: "test_alias",
MapValue: "hello",
}
c := runTest(t, tis)
expect(t, "hello", c.String("test_alias"))
c = runRacyTest(t, tis)
refute(t, "hello", c.String("test_alias"))
}
func TestStringApplyInputSourceMethodSet(t *testing.T) {
tis := testApplyInputSource{
Flag: NewStringFlag(&cli.StringFlag{Name: "test"}),
@ -249,6 +367,31 @@ func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) {
refute(t, "goodbye", c.String("test"))
}
func TestPathApplyInputSourceMethodSet_Alias(t *testing.T) {
tis := testApplyInputSource{
Flag: NewPathFlag(&cli.PathFlag{Name: "test", Aliases: []string{"test_alias"}}),
FlagName: "test_alias",
MapValue: "hello",
SourcePath: "/path/to/source/file",
}
c := runTest(t, tis)
expected := "/path/to/source/hello"
if runtime.GOOS == "windows" {
var err error
// Prepend the corresponding drive letter (or UNC path?), and change
// to windows-style path:
expected, err = filepath.Abs(expected)
if err != nil {
t.Fatal(err)
}
}
expect(t, expected, c.String("test_alias"))
c = runRacyTest(t, tis)
refute(t, expected, c.String("test_alias"))
}
func TestPathApplyInputSourceMethodSet(t *testing.T) {
tis := testApplyInputSource{
Flag: NewPathFlag(&cli.PathFlag{Name: "test"}),
@ -305,6 +448,19 @@ func TestPathApplyInputSourceMethodEnvVarSet(t *testing.T) {
refute(t, "goodbye", c.String("test"))
}
func TestIntApplyInputSourceMethodSet_Alias(t *testing.T) {
tis := testApplyInputSource{
Flag: NewIntFlag(&cli.IntFlag{Name: "test", Aliases: []string{"test_alias"}}),
FlagName: "test_alias",
MapValue: 15,
}
c := runTest(t, tis)
expect(t, 15, c.Int("test_alias"))
c = runRacyTest(t, tis)
refute(t, 15, c.Int("test_alias"))
}
func TestIntApplyInputSourceMethodSet(t *testing.T) {
tis := testApplyInputSource{
Flag: NewIntFlag(&cli.IntFlag{Name: "test"}),
@ -360,6 +516,19 @@ func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) {
refute(t, 12, c.Int("test"))
}
func TestDurationApplyInputSourceMethodSet_Alias(t *testing.T) {
tis := testApplyInputSource{
Flag: NewDurationFlag(&cli.DurationFlag{Name: "test", Aliases: []string{"test_alias"}}),
FlagName: "test_alias",
MapValue: 30 * time.Second,
}
c := runTest(t, tis)
expect(t, 30*time.Second, c.Duration("test_alias"))
c = runRacyTest(t, tis)
refute(t, 30*time.Second, c.Duration("test_alias"))
}
func TestDurationApplyInputSourceMethodSet(t *testing.T) {
tis := testApplyInputSource{
Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}),
@ -428,6 +597,19 @@ func TestFloat64ApplyInputSourceMethodSet(t *testing.T) {
refute(t, 1.3, c.Float64("test"))
}
func TestFloat64ApplyInputSourceMethodSetNegativeValue_Alias(t *testing.T) {
tis := testApplyInputSource{
Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test", Aliases: []string{"test_alias"}}),
FlagName: "test_alias",
MapValue: -1.3,
}
c := runTest(t, tis)
expect(t, -1.3, c.Float64("test_alias"))
c = runRacyTest(t, tis)
refute(t, -1.3, c.Float64("test_alias"))
}
func TestFloat64ApplyInputSourceMethodSetNegativeValue(t *testing.T) {
tis := testApplyInputSource{
Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}),

View File

@ -11,7 +11,7 @@ import (
const (
fileName = "current.json"
simpleJSON = `{"test": 15}`
simpleJSON = `{"test": 15, "testb": false}`
nestedJSON = `{"top": {"test": 15}}`
)
@ -34,11 +34,16 @@ func TestCommandJSONFileTest(t *testing.T) {
Action: func(c *cli.Context) error {
val := c.Int("test")
expect(t, val, 15)
valb := c.Bool("testb")
expect(t, valb, false)
return nil
},
Flags: []cli.Flag{
NewIntFlag(&cli.IntFlag{Name: "test"}),
&cli.StringFlag{Name: "load"}},
&cli.StringFlag{Name: "load"},
NewBoolFlag(&cli.BoolFlag{Name: "testb", Value: true}),
},
}
command.Before = InitInputSourceWithContext(command.Flags, NewJSONSourceFromFlagFunc("load"))
err := command.Run(c)

View File

@ -33,11 +33,9 @@ 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(cCtx *cli.Context) (InputSourceContext, error) {
return func(cCtx *cli.Context) (InputSourceContext, error) {
if cCtx.IsSet(flagFileName) {
filePath := cCtx.String(flagFileName)
if filePath := cCtx.String(flagFileName); filePath != "" {
return NewYamlSourceFromFile(filePath)
}
return defaultInputSource()
}
}

67
app.go
View File

@ -78,6 +78,8 @@ type App struct {
CommandNotFound CommandNotFoundFunc
// Execute this function if a usage error occurs
OnUsageError OnUsageErrorFunc
// Execute this function when an invalid flag is accessed from the context
InvalidFlagAccessHandler InvalidFlagAccessFunc
// Compilation date
Compiled time.Time
// List of all authors who contributed
@ -131,7 +133,6 @@ func compileTime() time.Time {
func NewApp() *App {
return &App{
Name: filepath.Base(os.Args[0]),
HelpName: filepath.Base(os.Args[0]),
Usage: "A new cli application",
UsageText: "",
BashComplete: DefaultAppComplete,
@ -273,7 +274,9 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
cCtx := NewContext(a, set, &Context{Context: ctx})
if nerr != nil {
_, _ = fmt.Fprintln(a.Writer, nerr)
_ = ShowAppHelp(cCtx)
if !a.HideHelp {
_ = ShowAppHelp(cCtx)
}
return nerr
}
cCtx.shellComplete = shellComplete
@ -294,10 +297,24 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
fmt.Fprintf(a.Writer, suggestion)
}
}
_ = ShowAppHelp(cCtx)
if !a.HideHelp {
_ = ShowAppHelp(cCtx)
}
return err
}
if a.After != nil && !cCtx.shellComplete {
defer func() {
if afterErr := a.After(cCtx); afterErr != nil {
if err != nil {
err = newMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
}
if !a.HideHelp && checkHelp(cCtx) {
_ = ShowAppHelp(cCtx)
return nil
@ -314,19 +331,7 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
return cerr
}
if a.After != nil {
defer func() {
if afterErr := a.After(cCtx); afterErr != nil {
if err != nil {
err = newMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
}
if a.Before != nil {
if a.Before != nil && !cCtx.shellComplete {
beforeErr := a.Before(cCtx)
if beforeErr != nil {
a.handleExitCoder(cCtx, beforeErr)
@ -335,6 +340,10 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
}
}
if err = runFlagActions(cCtx, a.Flags); err != nil {
return err
}
var c *Command
args := cCtx.Args()
if args.Present() {
@ -493,7 +502,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
return cerr
}
if a.After != nil {
if a.After != nil && !cCtx.shellComplete {
defer func() {
afterErr := a.After(cCtx)
if afterErr != nil {
@ -507,7 +516,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
}()
}
if a.Before != nil {
if a.Before != nil && !cCtx.shellComplete {
beforeErr := a.Before(cCtx)
if beforeErr != nil {
a.handleExitCoder(cCtx, beforeErr)
@ -516,6 +525,10 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
}
}
if err = runFlagActions(cCtx, a.Flags); err != nil {
return err
}
args := cCtx.Args()
if args.Present() {
name := args.First()
@ -639,6 +652,24 @@ func (a *App) argsWithDefaultCommand(oldArgs Args) Args {
return oldArgs
}
func runFlagActions(c *Context, fs []Flag) error {
for _, f := range fs {
isSet := false
for _, name := range f.Names() {
if c.IsSet(name) {
isSet = true
break
}
}
if isSet {
if err := f.RunAction(c); err != nil {
return err
}
}
}
return nil
}
// Author represents someone who has contributed to a cli project.
type Author struct {
Name string // The Authors name

View File

@ -9,8 +9,10 @@ import (
"io/ioutil"
"os"
"reflect"
"strconv"
"strings"
"testing"
"time"
)
var (
@ -175,10 +177,13 @@ func ExampleApp_Run_commandHelp() {
// greet describeit - use it to see a description
//
// USAGE:
// greet describeit [arguments...]
// greet describeit [command options] [arguments...]
//
// DESCRIPTION:
// This is how we describe describeit the function
//
// OPTIONS:
// --help, -h show help (default: false)
}
func ExampleApp_Run_noAction() {
@ -1245,6 +1250,58 @@ func TestApp_BeforeFunc(t *testing.T) {
}
}
func TestApp_BeforeAfterFuncShellCompletion(t *testing.T) {
counts := &opCounts{}
var err error
app := &App{
EnableBashCompletion: true,
Before: func(c *Context) error {
counts.Total++
counts.Before = counts.Total
return nil
},
After: func(c *Context) error {
counts.Total++
counts.After = counts.Total
return nil
},
Commands: []*Command{
{
Name: "sub",
Action: func(c *Context) error {
counts.Total++
counts.SubCommand = counts.Total
return nil
},
},
},
Flags: []Flag{
&StringFlag{Name: "opt"},
},
Writer: ioutil.Discard,
}
// run with the Before() func succeeding
err = app.Run([]string{"command", "--opt", "succeed", "sub", "--generate-bash-completion"})
if err != nil {
t.Fatalf("Run error: %s", err)
}
if counts.Before != 0 {
t.Errorf("Before() executed when not expected")
}
if counts.After != 0 {
t.Errorf("After() executed when not expected")
}
if counts.SubCommand != 0 {
t.Errorf("Subcommand executed more than expected")
}
}
func TestApp_AfterFunc(t *testing.T) {
counts := &opCounts{}
afterError := fmt.Errorf("fail")
@ -1309,6 +1366,27 @@ func TestApp_AfterFunc(t *testing.T) {
if counts.SubCommand != 1 {
t.Errorf("Subcommand not executed when expected")
}
/*
reset
*/
counts = &opCounts{}
// run with none args
err = app.Run([]string{"command"})
// should be the same error produced by the Before func
if err != nil {
t.Fatalf("Run error: %s", err)
}
if counts.After != 1 {
t.Errorf("After() not executed when expected")
}
if counts.SubCommand != 0 {
t.Errorf("Subcommand not executed when expected")
}
}
func TestAppNoHelpFlag(t *testing.T) {
@ -1873,31 +1951,67 @@ func TestApp_Run_CommandSubcommandHelpName(t *testing.T) {
}
func TestApp_Run_Help(t *testing.T) {
var helpArguments = [][]string{{"boom", "--help"}, {"boom", "-h"}, {"boom", "help"}}
for _, args := range helpArguments {
t.Run(fmt.Sprintf("checking with arguments %v", args), func(t *testing.T) {
var tests = []struct {
helpArguments []string
hideHelp bool
wantContains string
wantErr error
}{
{
helpArguments: []string{"boom", "--help"},
hideHelp: false,
wantContains: "boom - make an explosive entrance",
},
{
helpArguments: []string{"boom", "-h"},
hideHelp: false,
wantContains: "boom - make an explosive entrance",
},
{
helpArguments: []string{"boom", "help"},
hideHelp: false,
wantContains: "boom - make an explosive entrance",
},
{
helpArguments: []string{"boom", "--help"},
hideHelp: true,
wantErr: fmt.Errorf("flag: help requested"),
},
{
helpArguments: []string{"boom", "-h"},
hideHelp: true,
wantErr: fmt.Errorf("flag: help requested"),
},
{
helpArguments: []string{"boom", "help"},
hideHelp: true,
wantContains: "boom I say!",
},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("checking with arguments %v", tt.helpArguments), func(t *testing.T) {
buf := new(bytes.Buffer)
app := &App{
Name: "boom",
Usage: "make an explosive entrance",
Writer: buf,
Name: "boom",
Usage: "make an explosive entrance",
Writer: buf,
HideHelp: tt.hideHelp,
Action: func(c *Context) error {
buf.WriteString("boom I say!")
return nil
},
}
err := app.Run(args)
if err != nil {
t.Error(err)
err := app.Run(tt.helpArguments)
if err != nil && err.Error() != tt.wantErr.Error() {
t.Errorf("want err: %s, did note %s\n", tt.wantErr, err)
}
output := buf.String()
if !strings.Contains(output, "boom - make an explosive entrance") {
if !strings.Contains(output, tt.wantContains) {
t.Errorf("want help to contain %q, did not: \n%q", "boom - make an explosive entrance", output)
}
})
@ -2248,6 +2362,10 @@ func (c *customBoolFlag) Apply(set *flag.FlagSet) error {
return nil
}
func (c *customBoolFlag) RunAction(*Context) error {
return nil
}
func (c *customBoolFlag) IsSet() bool {
return false
}
@ -2487,3 +2605,392 @@ func TestSetupInitializesOnlyNilWriters(t *testing.T) {
t.Errorf("expected a.Writer to be os.Stdout")
}
}
type stringGeneric struct {
value string
}
func (s *stringGeneric) Set(value string) error {
s.value = value
return nil
}
func (s *stringGeneric) String() string {
return s.value
}
func TestFlagAction(t *testing.T) {
stringFlag := &StringFlag{
Name: "f_string",
Action: func(c *Context, v string) error {
if v == "" {
return fmt.Errorf("empty string")
}
c.App.Writer.Write([]byte(v + " "))
return nil
},
}
app := &App{
Name: "app",
Commands: []*Command{
{
Name: "c1",
Flags: []Flag{stringFlag},
Action: func(ctx *Context) error { return nil },
Subcommands: []*Command{
{
Name: "sub1",
Action: func(ctx *Context) error { return nil },
Flags: []Flag{stringFlag},
},
},
},
},
Flags: []Flag{
stringFlag,
&StringFlag{
Name: "f_no_action",
},
&StringSliceFlag{
Name: "f_string_slice",
Action: func(c *Context, v []string) error {
if v[0] == "err" {
return fmt.Errorf("error string slice")
}
c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
return nil
},
},
&BoolFlag{
Name: "f_bool",
Action: func(c *Context, v bool) error {
if !v {
return fmt.Errorf("value is false")
}
c.App.Writer.Write([]byte(fmt.Sprintf("%t ", v)))
return nil
},
},
&DurationFlag{
Name: "f_duration",
Action: func(c *Context, v time.Duration) error {
if v == 0 {
return fmt.Errorf("empty duration")
}
c.App.Writer.Write([]byte(v.String() + " "))
return nil
},
},
&Float64Flag{
Name: "f_float64",
Action: func(c *Context, v float64) error {
if v < 0 {
return fmt.Errorf("negative float64")
}
c.App.Writer.Write([]byte(strconv.FormatFloat(v, 'f', -1, 64) + " "))
return nil
},
},
&Float64SliceFlag{
Name: "f_float64_slice",
Action: func(c *Context, v []float64) error {
if len(v) > 0 && v[0] < 0 {
return fmt.Errorf("invalid float64 slice")
}
c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
return nil
},
},
&GenericFlag{
Name: "f_generic",
Value: new(stringGeneric),
Action: func(c *Context, v interface{}) error {
fmt.Printf("%T %v\n", v, v)
switch vv := v.(type) {
case *stringGeneric:
if vv.value == "" {
return fmt.Errorf("generic value not set")
}
}
c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
return nil
},
},
&IntFlag{
Name: "f_int",
Action: func(c *Context, v int) error {
if v < 0 {
return fmt.Errorf("negative int")
}
c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
return nil
},
},
&IntSliceFlag{
Name: "f_int_slice",
Action: func(c *Context, v []int) error {
if len(v) > 0 && v[0] < 0 {
return fmt.Errorf("invalid int slice")
}
c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
return nil
},
},
&Int64Flag{
Name: "f_int64",
Action: func(c *Context, v int64) error {
if v < 0 {
return fmt.Errorf("negative int64")
}
c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
return nil
},
},
&Int64SliceFlag{
Name: "f_int64_slice",
Action: func(c *Context, v []int64) error {
if len(v) > 0 && v[0] < 0 {
return fmt.Errorf("invalid int64 slice")
}
c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
return nil
},
},
&PathFlag{
Name: "f_path",
Action: func(c *Context, v string) error {
if v == "" {
return fmt.Errorf("empty path")
}
c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
return nil
},
},
&TimestampFlag{
Name: "f_timestamp",
Layout: "2006-01-02 15:04:05",
Action: func(c *Context, v *time.Time) error {
if v.IsZero() {
return fmt.Errorf("zero timestamp")
}
c.App.Writer.Write([]byte(v.Format(time.RFC3339) + " "))
return nil
},
},
&UintFlag{
Name: "f_uint",
Action: func(c *Context, v uint) error {
if v == 0 {
return fmt.Errorf("zero uint")
}
c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
return nil
},
},
&Uint64Flag{
Name: "f_uint64",
Action: func(c *Context, v uint64) error {
if v == 0 {
return fmt.Errorf("zero uint64")
}
c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v)))
return nil
},
},
},
Action: func(ctx *Context) error { return nil },
}
tests := []struct {
name string
args []string
err error
exp string
}{
{
name: "flag_string",
args: []string{"app", "--f_string=string"},
exp: "string ",
},
{
name: "flag_string_error",
args: []string{"app", "--f_string="},
err: fmt.Errorf("empty string"),
},
{
name: "flag_string_slice",
args: []string{"app", "--f_string_slice=s1,s2,s3"},
exp: "[s1 s2 s3] ",
},
{
name: "flag_string_slice_error",
args: []string{"app", "--f_string_slice=err"},
err: fmt.Errorf("error string slice"),
},
{
name: "flag_bool",
args: []string{"app", "--f_bool"},
exp: "true ",
},
{
name: "flag_bool_error",
args: []string{"app", "--f_bool=false"},
err: fmt.Errorf("value is false"),
},
{
name: "flag_duration",
args: []string{"app", "--f_duration=1h30m20s"},
exp: "1h30m20s ",
},
{
name: "flag_duration_error",
args: []string{"app", "--f_duration=0"},
err: fmt.Errorf("empty duration"),
},
{
name: "flag_float64",
args: []string{"app", "--f_float64=3.14159"},
exp: "3.14159 ",
},
{
name: "flag_float64_error",
args: []string{"app", "--f_float64=-1"},
err: fmt.Errorf("negative float64"),
},
{
name: "flag_float64_slice",
args: []string{"app", "--f_float64_slice=1.1,2.2,3.3"},
exp: "[1.1 2.2 3.3] ",
},
{
name: "flag_float64_slice_error",
args: []string{"app", "--f_float64_slice=-1"},
err: fmt.Errorf("invalid float64 slice"),
},
{
name: "flag_generic",
args: []string{"app", "--f_generic=1"},
exp: "1 ",
},
{
name: "flag_generic_error",
args: []string{"app", "--f_generic="},
err: fmt.Errorf("generic value not set"),
},
{
name: "flag_int",
args: []string{"app", "--f_int=1"},
exp: "1 ",
},
{
name: "flag_int_error",
args: []string{"app", "--f_int=-1"},
err: fmt.Errorf("negative int"),
},
{
name: "flag_int_slice",
args: []string{"app", "--f_int_slice=1,2,3"},
exp: "[1 2 3] ",
},
{
name: "flag_int_slice_error",
args: []string{"app", "--f_int_slice=-1"},
err: fmt.Errorf("invalid int slice"),
},
{
name: "flag_int64",
args: []string{"app", "--f_int64=1"},
exp: "1 ",
},
{
name: "flag_int64_error",
args: []string{"app", "--f_int64=-1"},
err: fmt.Errorf("negative int64"),
},
{
name: "flag_int64_slice",
args: []string{"app", "--f_int64_slice=1,2,3"},
exp: "[1 2 3] ",
},
{
name: "flag_int64_slice",
args: []string{"app", "--f_int64_slice=-1"},
err: fmt.Errorf("invalid int64 slice"),
},
{
name: "flag_path",
args: []string{"app", "--f_path=/root"},
exp: "/root ",
},
{
name: "flag_path_error",
args: []string{"app", "--f_path="},
err: fmt.Errorf("empty path"),
},
{
name: "flag_timestamp",
args: []string{"app", "--f_timestamp", "2022-05-01 02:26:20"},
exp: "2022-05-01T02:26:20Z ",
},
{
name: "flag_timestamp_error",
args: []string{"app", "--f_timestamp", "0001-01-01 00:00:00"},
err: fmt.Errorf("zero timestamp"),
},
{
name: "flag_uint",
args: []string{"app", "--f_uint=1"},
exp: "1 ",
},
{
name: "flag_uint_error",
args: []string{"app", "--f_uint=0"},
err: fmt.Errorf("zero uint"),
},
{
name: "flag_uint64",
args: []string{"app", "--f_uint64=1"},
exp: "1 ",
},
{
name: "flag_uint64_error",
args: []string{"app", "--f_uint64=0"},
err: fmt.Errorf("zero uint64"),
},
{
name: "flag_no_action",
args: []string{"app", "--f_no_action="},
exp: "",
},
{
name: "command_flag",
args: []string{"app", "c1", "--f_string=c1"},
exp: "c1 ",
},
{
name: "subCommand_flag",
args: []string{"app", "c1", "sub1", "--f_string=sub1"},
exp: "sub1 ",
},
{
name: "mixture",
args: []string{"app", "--f_string=app", "--f_uint=1", "--f_int_slice=1,2,3", "--f_duration=1h30m20s", "c1", "--f_string=c1", "sub1", "--f_string=sub1"},
exp: "app 1h30m20s [1 2 3] 1 c1 sub1 ",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
buf := new(bytes.Buffer)
app.Writer = buf
err := app.Run(test.args)
if test.err != nil {
expect(t, err, test.err)
} else {
expect(t, err, nil)
expect(t, buf.String(), test.exp)
}
})
}
}

32
cli.go
View File

@ -1,23 +1,25 @@
// Package cli provides a minimal framework for creating and organizing command line
// Go applications. cli is designed to be easy to understand and write, the most simple
// cli application can be written as follows:
// func main() {
// (&cli.App{}).Run(os.Args)
// }
//
// func main() {
// (&cli.App{}).Run(os.Args)
// }
//
// Of course this application does not do much, so let's make this an actual application:
// func main() {
// app := &cli.App{
// Name: "greet",
// Usage: "say a greeting",
// Action: func(c *cli.Context) error {
// fmt.Println("Greetings")
// return nil
// },
// }
//
// app.Run(os.Args)
// }
// func main() {
// app := &cli.App{
// Name: "greet",
// Usage: "say a greeting",
// Action: func(c *cli.Context) error {
// fmt.Println("Greetings")
// return nil
// },
// }
//
// app.Run(os.Args)
// }
package cli
//go:generate go run internal/genflags/cmd/genflags/main.go
//go:generate go run cmd/urfave-cli-genflags/main.go

View File

@ -0,0 +1,21 @@
GOTEST_FLAGS ?= -v --coverprofile main.coverprofile --covermode count --cover github.com/urfave/cli/v2/cmd/urfave-cli-genflags
GOBUILD_FLAGS ?= -x
.PHONY: all
all: test build smoke-test
.PHONY: test
test:
go test $(GOTEST_FLAGS) ./...
.PHONY: build
build:
go build $(GOBUILD_FLAGS) ./...
.PHONY: smoke-test
smoke-test: build
./urfave-cli-genflags --help
.PHONY: show-cover
show-cover:
go tool cover -func main.coverprofile

View File

@ -0,0 +1,15 @@
# urfave-cli-genflags
This is a tool that is used internally by [urfave/cli] to generate
flag types and methods from a YAML input. It intentionally pins
usage of `github.com/urfave/cli/v2` to a *release* rather than
using the adjacent code so that changes don't result in *this* tool
refusing to compile. It's almost like dogfooding?
## support warning
This tool is maintained as a sub-project and is not covered by the
API and backward compatibility guaranteed by releases of
[urfave/cli].
[urfave/cli]: https://github.com/urfave/cli

View File

@ -17,13 +17,13 @@ type {{.TypeName}} struct {
HasBeenSet bool
Value {{if .ValuePointer}}*{{end}}{{.GoType}}
Destination *{{.GoType}}
Destination {{if .NoDestinationPointer}}{{else}}*{{end}}{{.GoType}}
Aliases []string
EnvVars []string
{{range .StructFields}}
{{.Name}} {{.Type}}
{{.Name}} {{if .Pointer}}*{{end}}{{.Type}}
{{end}}
}

View File

@ -0,0 +1,15 @@
module github.com/urfave/cli/v2/cmd/urfave-cli-genflags
go 1.18
require (
github.com/urfave/cli/v2 v2.11.2
golang.org/x/text v0.3.7
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
)

View File

@ -0,0 +1,14 @@
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/urfave/cli/v2 v2.11.2 h1:FVfNg4m3vbjbBpLYxW//WjxUoHvJ9TlppXcqY9Q9ZfA=
github.com/urfave/cli/v2 v2.11.2/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo=
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=
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.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -9,12 +9,14 @@ import (
"os/exec"
"os/signal"
"path/filepath"
"sort"
"strings"
"syscall"
"text/template"
"github.com/urfave/cli/v3"
"github.com/urfave/cli/v3/internal/genflags"
"github.com/urfave/cli/v2"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"gopkg.in/yaml.v3"
)
@ -22,6 +24,16 @@ const (
defaultPackageName = "cli"
)
var (
//go:embed generated.gotmpl
TemplateString string
//go:embed generated_test.gotmpl
TestTemplateString string
titler = cases.Title(language.Und, cases.NoLower)
)
func sh(ctx context.Context, exe string, args ...string) (string, error) {
cmd := exec.CommandContext(ctx, exe, args...)
cmd.Stderr = os.Stderr
@ -92,7 +104,7 @@ func runGenFlags(cCtx *cli.Context) error {
return err
}
spec := &genflags.Spec{}
spec := &Spec{}
if err := yaml.Unmarshal(specBytes, spec); err != nil {
return err
}
@ -123,12 +135,12 @@ func runGenFlags(cCtx *cli.Context) error {
spec.UrfaveCLITestNamespace = "cli."
}
genTmpl, err := template.New("gen").Parse(genflags.TemplateString)
genTmpl, err := template.New("gen").Parse(TemplateString)
if err != nil {
return err
}
genTestTmpl, err := template.New("gen_test").Parse(genflags.TestTemplateString)
genTestTmpl, err := template.New("gen_test").Parse(TestTemplateString)
if err != nil {
return err
}
@ -161,3 +173,130 @@ func runGenFlags(cCtx *cli.Context) error {
return nil
}
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"
}
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"`
NoDestinationPointer bool `yaml:"no_destination_pointer"`
}
type FlagStructField struct {
Name string
Type string
Pointer bool
}
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) NoDestinationPointer() bool {
if ft.Config == nil {
return false
}
return ft.Config.NoDestinationPointer
}
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
}

View File

@ -1,29 +1,63 @@
package genflags_test
package main_test
import (
"fmt"
"reflect"
"testing"
"github.com/urfave/cli/v3/internal/genflags"
main "github.com/urfave/cli/v2/cmd/urfave-cli-genflags"
)
func TestTypeName(t *testing.T) {
for _, tc := range []struct {
gt string
fc *main.FlagTypeConfig
expected string
}{
{gt: "int", fc: nil, expected: "IntFlag"},
{gt: "int", fc: &main.FlagTypeConfig{}, expected: "IntFlag"},
{gt: "int", fc: &main.FlagTypeConfig{TypeName: "VeryIntyFlag"}, expected: "VeryIntyFlag"},
{gt: "[]bool", fc: nil, expected: "BoolSliceFlag"},
{gt: "[]bool", fc: &main.FlagTypeConfig{}, expected: "BoolSliceFlag"},
{gt: "[]bool", fc: &main.FlagTypeConfig{TypeName: "ManyTruthsFlag"}, expected: "ManyTruthsFlag"},
{gt: "time.Rumination", fc: nil, expected: "RuminationFlag"},
{gt: "time.Rumination", fc: &main.FlagTypeConfig{}, expected: "RuminationFlag"},
{gt: "time.Rumination", fc: &main.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 := main.TypeName(tc.gt, tc.fc)
if tc.expected != actual {
ct.Errorf("expected %q, got %q", tc.expected, actual)
}
},
)
}
}
func TestSpec_SortedFlagTypes(t *testing.T) {
spec := &genflags.Spec{
FlagTypes: map[string]*genflags.FlagTypeConfig{
"nerf": &genflags.FlagTypeConfig{},
spec := &main.Spec{
FlagTypes: map[string]*main.FlagTypeConfig{
"nerf": &main.FlagTypeConfig{},
"gerf": nil,
},
}
actual := spec.SortedFlagTypes()
expected := []*genflags.FlagType{
expected := []*main.FlagType{
{
GoType: "gerf",
Config: nil,
},
{
GoType: "nerf",
Config: &genflags.FlagTypeConfig{},
Config: &main.FlagTypeConfig{},
},
}
if !reflect.DeepEqual(expected, actual) {
@ -31,12 +65,12 @@ func TestSpec_SortedFlagTypes(t *testing.T) {
}
}
func genFlagType() *genflags.FlagType {
return &genflags.FlagType{
func genFlagType() *main.FlagType {
return &main.FlagType{
GoType: "blerf",
Config: &genflags.FlagTypeConfig{
Config: &main.FlagTypeConfig{
SkipInterfaces: []string{"fmt.Stringer"},
StructFields: []*genflags.FlagStructField{
StructFields: []*main.FlagStructField{
{
Name: "Foibles",
Type: "int",

View File

@ -125,7 +125,9 @@ func (c *Command) Run(ctx *Context) (err error) {
fmt.Fprintf(cCtx.App.Writer, suggestion)
}
}
_ = ShowCommandHelp(cCtx, c.Name)
if !c.HideHelp {
_ = ShowCommandHelp(cCtx, c.Name)
}
return err
}
@ -135,7 +137,9 @@ func (c *Command) Run(ctx *Context) (err error) {
cerr := cCtx.checkRequiredFlags(c.Flags)
if cerr != nil {
_ = ShowCommandHelp(cCtx, c.Name)
if !c.HideHelp {
_ = ShowCommandHelp(cCtx, c.Name)
}
return cerr
}
@ -161,8 +165,12 @@ func (c *Command) Run(ctx *Context) (err error) {
}
}
if err = runFlagActions(cCtx, c.Flags); err != nil {
return err
}
if c.Action == nil {
c.Action = helpSubcommand.Action
c.Action = helpCommand.Action
}
cCtx.Command = c
@ -276,7 +284,7 @@ func (c *Command) startApp(ctx *Context) error {
if c.Action != nil {
app.Action = c.Action
} else {
app.Action = helpSubcommand.Action
app.Action = helpCommand.Action
}
app.OnUsageError = c.OnUsageError
@ -290,7 +298,10 @@ func (c *Command) startApp(ctx *Context) error {
// 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{}
c.flagCategories = newFlagCategories()
for _, fl := range c.Flags {
c.flagCategories.AddFlag(fl.GetCategory(), fl)
}
}
return c.flagCategories.VisibleCategories()
}

View File

@ -3,6 +3,7 @@ package cli
import (
"context"
"flag"
"fmt"
"strings"
)
@ -46,7 +47,11 @@ func (cCtx *Context) NumFlags() int {
// Set sets a context flag to a value.
func (cCtx *Context) Set(name, value string) error {
return cCtx.flagSet.Set(name, value)
if fs := cCtx.lookupFlagSet(name); fs != nil {
return fs.Set(name, value)
}
return fmt.Errorf("no such flag -%s", name)
}
// IsSet determines if the flag was actually set
@ -102,6 +107,16 @@ func (cCtx *Context) Lineage() []*Context {
return lineage
}
// Count returns the num of occurences of this flag
func (cCtx *Context) Count(name string) int {
if fs := cCtx.lookupFlagSet(name); fs != nil {
if cf, ok := fs.Lookup(name).Value.(Countable); ok {
return cf.Count()
}
}
return 0
}
// Value returns the value of the flag corresponding to `name`
func (cCtx *Context) Value(name string) interface{} {
if fs := cCtx.lookupFlagSet(name); fs != nil {
@ -158,7 +173,7 @@ func (cCtx *Context) lookupFlagSet(name string) *flag.FlagSet {
return c.flagSet
}
}
cCtx.onInvalidFlag(name)
return nil
}
@ -170,9 +185,7 @@ func (cCtx *Context) checkRequiredFlags(flags []Flag) requiredFlagsErr {
var flagName string
for _, key := range f.Names() {
if len(key) > 1 {
flagName = key
}
flagName = key
if cCtx.IsSet(strings.TrimSpace(key)) {
flagPresent = true
@ -192,6 +205,16 @@ func (cCtx *Context) checkRequiredFlags(flags []Flag) requiredFlagsErr {
return nil
}
func (cCtx *Context) onInvalidFlag(name string) {
for cCtx != nil {
if cCtx.App != nil && cCtx.App.InvalidFlagAccessHandler != nil {
cCtx.App.InvalidFlagAccessHandler(cCtx, name)
break
}
cCtx = cCtx.parentContext
}
}
func makeFlagNameVisitor(names *[]string) func(*flag.Flag) {
return func(f *flag.Flag) {
nameParts := strings.Split(f.Name, ",")

View File

@ -150,6 +150,31 @@ func TestContext_Value(t *testing.T) {
expect(t, c.Value("unknown-flag"), nil)
}
func TestContext_Value_InvalidFlagAccessHandler(t *testing.T) {
var flagName string
app := &App{
InvalidFlagAccessHandler: func(_ *Context, name string) {
flagName = name
},
Commands: []*Command{
{
Name: "command",
Subcommands: []*Command{
{
Name: "subcommand",
Action: func(ctx *Context) error {
ctx.Value("missing")
return nil
},
},
},
},
},
}
expect(t, app.Run([]string{"run", "command", "subcommand"}), nil)
expect(t, flagName, "missing")
}
func TestContext_Args(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("myflag", false, "doc")
@ -258,6 +283,19 @@ func TestContext_Set(t *testing.T) {
expect(t, c.IsSet("int"), true)
}
func TestContext_Set_InvalidFlagAccessHandler(t *testing.T) {
set := flag.NewFlagSet("test", 0)
var flagName string
app := &App{
InvalidFlagAccessHandler: func(_ *Context, name string) {
flagName = name
},
}
c := NewContext(app, set, nil)
c.Set("missing", "")
expect(t, flagName, "missing")
}
func TestContext_LocalFlagNames(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("one-flag", false, "doc")
@ -556,6 +594,14 @@ func TestCheckRequiredFlags(t *testing.T) {
&StringSliceFlag{Name: "names, n", Required: true},
},
},
{
testCase: "required_flag_with_one_character",
expectedAnError: true,
expectedErrorContents: []string{"Required flag \"n\" not set"},
flags: []Flag{
&StringFlag{Name: "n", Required: true},
},
},
}
for _, test := range tdata {
@ -597,3 +643,19 @@ func TestCheckRequiredFlags(t *testing.T) {
})
}
}
func TestContext_ParentContext_Set(t *testing.T) {
parentSet := flag.NewFlagSet("parent", flag.ContinueOnError)
parentSet.String("Name", "", "")
context := NewContext(
nil,
flag.NewFlagSet("child", flag.ContinueOnError),
NewContext(nil, parentSet, nil),
)
err := context.Set("Name", "aaa")
if err != nil {
t.Errorf("expect nil. set parent context flag return err: %s", err)
}
}

View File

@ -95,7 +95,7 @@ The built-in `go generate` command is used to run the commands specified in
line help system which may be consulted for further information, e.g.:
```sh
go run internal/genflags/cmd/genflags/main.go --help
go run cmd/urfave-cli-genflags/main.go --help
```
#### docs output
@ -107,7 +107,7 @@ following `make` targets may be used if desired:
```sh
# install documentation dependencies with `pip`
make docs-deps
make ensure-mkdocs
```
```sh

View File

@ -41,9 +41,11 @@ 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
The tag push will trigger a GitHub Actions workflow and will be
**immediately available** to the [Go module mirror, index, and
checksum database](https://proxy.golang.org/). 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.
@ -58,4 +60,6 @@ adopted in the future.
[^2]: This was not always true. The
[`docs/CHANGELOG.md`](./CHANGELOG.md) document used to be
manually maintained.
manually maintained. Relying on the automatic release notes
generation requires the use of **merge commits** as opposed to
squash merging or rebase merging.

View File

@ -11,8 +11,8 @@ an expressive way.
These are the guides for each major supported version:
- [`v2`](./v2/)
- [`v1`](./v1/)
- [`v2`](./v2/getting-started)
- [`v1`](./v1/getting-started)
In addition to the version-specific guides, these other documents are available:

View File

@ -0,0 +1,35 @@
---
tags:
- v1
---
You can lookup arguments by calling the `Args` function on `cli.Context`, e.g.:
<!-- {
"output": "Hello \""
} -->
``` go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Action = func(c *cli.Context) error {
fmt.Printf("Hello %q", c.Args().Get(0))
return nil
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```

View File

@ -0,0 +1,119 @@
---
tags:
- v1
---
You can enable completion commands by setting the `EnableBashCompletion`
flag on the `App` object. By default, this setting will only auto-complete to
show an app's subcommands, but you can write your own completion methods for
the App or its subcommands.
<!-- {
"args": ["complete", "&#45;&#45;generate&#45;bash&#45;completion"],
"output": "laundry"
} -->
``` go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli"
)
func main() {
tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"}
app := cli.NewApp()
app.EnableBashCompletion = true
app.Commands = []cli.Command{
{
Name: "complete",
Aliases: []string{"c"},
Usage: "complete a task on the list",
Action: func(c *cli.Context) error {
fmt.Println("completed task: ", c.Args().First())
return nil
},
BashComplete: func(c *cli.Context) {
// This will complete if no args are passed
if c.NArg() > 0 {
return
}
for _, t := range tasks {
fmt.Println(t)
}
},
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
#### Enabling
Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while
setting the `PROG` variable to the name of your program:
`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete`
#### Distribution
Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename
it to the name of the program you wish to add autocomplete support for (or
automatically install it there if you are distributing a package). Don't forget
to source the file to make it active in the current shell.
```
sudo cp src/bash_autocomplete /etc/bash_completion.d/<myprogram>
source /etc/bash_completion.d/<myprogram>
```
Alternatively, you can just document that users should source the generic
`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set
to the name of their program (as above).
#### Customization
The default bash completion flag (`--generate-bash-completion`) is defined as
`cli.BashCompletionFlag`, and may be redefined if desired, e.g.:
<!-- {
"args": ["&#45;&#45;compgen"],
"output": "wat\nhelp\nh"
} -->
``` go
package main
import (
"log"
"os"
"github.com/urfave/cli"
)
func main() {
cli.BashCompletionFlag = cli.BoolFlag{
Name: "compgen",
Hidden: true,
}
app := cli.NewApp()
app.EnableBashCompletion = true
app.Commands = []cli.Command{
{
Name: "wat",
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```

View File

@ -0,0 +1,72 @@
---
tags:
- v1
---
Traditional use of options using their shortnames look like this:
```
$ cmd -s -o -m "Some message"
```
Suppose you want users to be able to combine options with their shortnames. This
can be done using the `UseShortOptionHandling` bool in your app configuration,
or for individual commands by attaching it to the command configuration. For
example:
<!-- {
"args": ["short", "&#45;som", "Some message"],
"output": "serve: true\noption: true\nmessage: Some message\n"
} -->
``` go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.UseShortOptionHandling = true
app.Commands = []cli.Command{
{
Name: "short",
Usage: "complete a task on the list",
Flags: []cli.Flag{
cli.BoolFlag{Name: "serve, s"},
cli.BoolFlag{Name: "option, o"},
cli.StringFlag{Name: "message, m"},
},
Action: func(c *cli.Context) error {
fmt.Println("serve:", c.Bool("serve"))
fmt.Println("option:", c.Bool("option"))
fmt.Println("message:", c.String("message"))
return nil
},
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
If your program has any number of bool flags such as `serve` and `option`, and
optionally one non-bool flag `message`, with the short options of `-s`, `-o`,
and `-m` respectively, setting `UseShortOptionHandling` will also support the
following syntax:
```
$ cmd -som "Some message"
```
If you enable `UseShortOptionHandling`, then you must not use any flags that
have a single leading `-` or this will result in failures. For example,
`-option` can no longer be used. Flags with two leading dashes (such as
`--options`) are still valid.

View File

@ -0,0 +1,43 @@
---
tags:
- v1
---
Calling `App.Run` will not automatically call `os.Exit`, which means that by
default the exit code will "fall through" to being `0`. An explicit exit code
may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a
`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.:
<!-- {
"error": "Ginger croutons are not in the soup"
} -->
``` go
package main
import (
"log"
"os"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Flags = []cli.Flag{
cli.BoolFlag{
Name: "ginger-crouton",
Usage: "Add ginger croutons to the soup",
},
}
app.Action = func(ctx *cli.Context) error {
if !ctx.Bool("ginger-crouton") {
return cli.NewExitError("Ginger croutons are not in the soup", 86)
}
return nil
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```

462
docs/v1/examples/flags.md Normal file
View File

@ -0,0 +1,462 @@
---
tags:
- v1
---
Setting and querying flags is simple.
<!-- {
"output": "Hello Nefertiti"
} -->
``` go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang",
Value: "english",
Usage: "language for the greeting",
},
}
app.Action = func(c *cli.Context) error {
name := "Nefertiti"
if c.NArg() > 0 {
name = c.Args().Get(0)
}
if c.String("lang") == "spanish" {
fmt.Println("Hola", name)
} else {
fmt.Println("Hello", name)
}
return nil
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
You can also set a destination variable for a flag, to which the content will be
scanned.
<!-- {
"output": "Hello someone"
} -->
``` go
package main
import (
"log"
"os"
"fmt"
"github.com/urfave/cli"
)
func main() {
var language string
app := cli.NewApp()
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang",
Value: "english",
Usage: "language for the greeting",
Destination: &language,
},
}
app.Action = func(c *cli.Context) error {
name := "someone"
if c.NArg() > 0 {
name = c.Args()[0]
}
if language == "spanish" {
fmt.Println("Hola", name)
} else {
fmt.Println("Hello", name)
}
return nil
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
See full list of flags at http://godoc.org/github.com/urfave/cli
#### Placeholder Values
Sometimes it's useful to specify a flag's value within the usage string itself.
Such placeholders are indicated with back quotes.
For example this:
<!-- {
"args": ["&#45;&#45;help"],
"output": "&#45;&#45;config FILE, &#45;c FILE"
} -->
```go
package main
import (
"log"
"os"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "config, c",
Usage: "Load configuration from `FILE`",
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
Will result in help output like:
```
--config FILE, -c FILE Load configuration from FILE
```
Note that only the first placeholder is used. Subsequent back-quoted words will
be left as-is.
#### Alternate Names
You can set alternate (or short) names for flags by providing a comma-delimited
list for the `Name`. e.g.
<!-- {
"args": ["&#45;&#45;help"],
"output": "&#45;&#45;lang value, &#45;l value.*language for the greeting.*default: \"english\""
} -->
``` go
package main
import (
"log"
"os"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
That flag can then be set with `--lang spanish` or `-l spanish`. Note that
giving two different forms of the same flag in the same command invocation is an
error.
#### Ordering
Flags for the application and commands are shown in the order they are defined.
However, it's possible to sort them from outside this library by using `FlagsByName`
or `CommandsByName` with `sort`.
For example this:
<!-- {
"args": ["&#45;&#45;help"],
"output": "add a task to the list\n.*complete a task on the list\n.*\n\n.*\n.*Load configuration from FILE\n.*Language for the greeting.*"
} -->
``` go
package main
import (
"log"
"os"
"sort"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "Language for the greeting",
},
cli.StringFlag{
Name: "config, c",
Usage: "Load configuration from `FILE`",
},
}
app.Commands = []cli.Command{
{
Name: "complete",
Aliases: []string{"c"},
Usage: "complete a task on the list",
Action: func(c *cli.Context) error {
return nil
},
},
{
Name: "add",
Aliases: []string{"a"},
Usage: "add a task to the list",
Action: func(c *cli.Context) error {
return nil
},
},
}
sort.Sort(cli.FlagsByName(app.Flags))
sort.Sort(cli.CommandsByName(app.Commands))
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
Will result in help output like:
```
--config FILE, -c FILE Load configuration from FILE
--lang value, -l value Language for the greeting (default: "english")
```
#### Values from the Environment
You can also have the default value set from the environment via `EnvVar`. e.g.
<!-- {
"args": ["&#45;&#45;help"],
"output": "language for the greeting.*APP_LANG"
} -->
``` go
package main
import (
"log"
"os"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
EnvVar: "APP_LANG",
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
The `EnvVar` may also be given as a comma-delimited "cascade", where the first
environment variable that resolves is used as the default.
<!-- {
"args": ["&#45;&#45;help"],
"output": "language for the greeting.*LEGACY_COMPAT_LANG.*APP_LANG.*LANG"
} -->
``` go
package main
import (
"log"
"os"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG",
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
#### Values from files
You can also have the default value set from file via `FilePath`. e.g.
<!-- {
"args": ["&#45;&#45;help"],
"output": "password for the mysql database"
} -->
``` go
package main
import (
"log"
"os"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "password, p",
Usage: "password for the mysql database",
FilePath: "/etc/mysql/password",
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
Note that default values set from file (e.g. `FilePath`) take precedence over
default values set from the environment (e.g. `EnvVar`).
#### Values from alternate input sources (YAML, TOML, and others)
There is a separate package altsrc that adds support for getting flag values
from other file input sources.
Currently supported input source formats:
* YAML
* JSON
* TOML
In order to get values for a flag from an alternate input source the following
code would be added to wrap an existing cli.Flag like below:
``` go
altsrc.NewIntFlag(cli.IntFlag{Name: "test"})
```
Initialization must also occur for these flags. Below is an example initializing
getting data from a yaml file below.
``` go
command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
```
The code above will use the "load" string as a flag name to get the file name of
a yaml file from the cli.Context. It will then use that file name to initialize
the yaml input source for any flags that are defined on that command. As a note
the "load" flag used would also have to be defined on the command flags in order
for this code snippet to work.
Currently only YAML, JSON, and TOML files are supported but developers can add support
for other input sources by implementing the altsrc.InputSourceContext for their
given sources.
Here is a more complete sample of a command using YAML support:
<!-- {
"args": ["&#45;&#45;help"],
"output": "&#45&#45;test value.*default: 0"
} -->
``` go
package notmain
import (
"fmt"
"log"
"os"
"github.com/urfave/cli"
"github.com/urfave/cli/altsrc"
)
func main() {
app := cli.NewApp()
flags := []cli.Flag{
altsrc.NewIntFlag(cli.IntFlag{Name: "test"}),
cli.StringFlag{Name: "load"},
}
app.Action = func(c *cli.Context) error {
fmt.Println("yaml ist rad")
return nil
}
app.Before = altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load"))
app.Flags = flags
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
#### Precedence
The precedence for flag value sources is as follows (highest to lowest):
0. Command line flag value from user
0. Environment variable (if specified)
0. Configuration file (if specified)
0. Default defined on the flag

View File

@ -0,0 +1,106 @@
---
tags:
- v1
---
The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked
by the cli internals in order to print generated help text for the app, command,
or subcommand, and break execution.
#### Customization
All of the help text generation may be customized, and at multiple levels. The
templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and
`SubcommandHelpTemplate` which may be reassigned or augmented, and full override
is possible by assigning a compatible func to the `cli.HelpPrinter` variable,
e.g.:
<!-- {
"output": "Ha HA. I pwnd the help!!1"
} -->
``` go
package main
import (
"fmt"
"log"
"io"
"os"
"github.com/urfave/cli"
)
func main() {
// EXAMPLE: Append to an existing template
cli.AppHelpTemplate = fmt.Sprintf(`%s
WEBSITE: http://awesometown.example.com
SUPPORT: support@awesometown.example.com
`, cli.AppHelpTemplate)
// EXAMPLE: Override a template
cli.AppHelpTemplate = `NAME:
{{.Name}} - {{.Usage}}
USAGE:
{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
{{if len .Authors}}
AUTHOR:
{{range .Authors}}{{ . }}{{end}}
{{end}}{{if .Commands}}
COMMANDS:
{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
GLOBAL OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}{{if .Copyright }}
COPYRIGHT:
{{.Copyright}}
{{end}}{{if .Version}}
VERSION:
{{.Version}}
{{end}}
`
// EXAMPLE: Replace the `HelpPrinter` func
cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) {
fmt.Println("Ha HA. I pwnd the help!!1")
}
err := cli.NewApp().Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
The default flag may be customized to something other than `-h/--help` by
setting `cli.HelpFlag`, e.g.:
<!-- {
"args": ["&#45;&#45halp"],
"output": "haaaaalp.*HALP"
} -->
``` go
package main
import (
"log"
"os"
"github.com/urfave/cli"
)
func main() {
cli.HelpFlag = cli.BoolFlag{
Name: "halp, haaaaalp",
Usage: "HALP",
EnvVar: "SHOW_HALP,HALPPLZ",
}
err := cli.NewApp().Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```

74
docs/v1/examples/greet.md Normal file
View File

@ -0,0 +1,74 @@
---
tags:
- v1
---
Being a programmer can be a lonely job. Thankfully by the power of automation
that is not the case! Let's create a greeter app to fend off our demons of
loneliness!
Start by creating a directory named `greet`, and within it, add a file,
`greet.go` with the following code in it:
<!-- {
"output": "Hello friend!"
} -->
``` go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Name = "greet"
app.Usage = "fight the loneliness!"
app.Action = func(c *cli.Context) error {
fmt.Println("Hello friend!")
return nil
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
Install our command to the `$GOPATH/bin` directory:
```
$ go install
```
Finally run our new command:
```
$ greet
Hello friend!
```
cli also generates neat help text:
```
$ greet help
NAME:
greet - fight the loneliness!
USAGE:
greet [global options] command [command options] [arguments...]
VERSION:
0.0.0
COMMANDS:
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS
--version Shows version information
```

View File

@ -0,0 +1,55 @@
---
tags:
- v1
---
For additional organization in apps that have many subcommands, you can
associate a category for each command to group them together in the help
output.
E.g.
```go
package main
import (
"log"
"os"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Commands = []cli.Command{
{
Name: "noop",
},
{
Name: "add",
Category: "Template actions",
},
{
Name: "remove",
Category: "Template actions",
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
Will include:
```
COMMANDS:
noop
Template actions:
add
remove
```

View File

@ -0,0 +1,75 @@
---
tags:
- v1
---
Subcommands can be defined for a more git-like command line app.
<!-- {
"args": ["template", "add"],
"output": "new task template: .+"
} -->
```go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Commands = []cli.Command{
{
Name: "add",
Aliases: []string{"a"},
Usage: "add a task to the list",
Action: func(c *cli.Context) error {
fmt.Println("added task: ", c.Args().First())
return nil
},
},
{
Name: "complete",
Aliases: []string{"c"},
Usage: "complete a task on the list",
Action: func(c *cli.Context) error {
fmt.Println("completed task: ", c.Args().First())
return nil
},
},
{
Name: "template",
Aliases: []string{"t"},
Usage: "options for task templates",
Subcommands: []cli.Command{
{
Name: "add",
Usage: "add a new template",
Action: func(c *cli.Context) error {
fmt.Println("new task template: ", c.Args().First())
return nil
},
},
{
Name: "remove",
Usage: "remove an existing template",
Action: func(c *cli.Context) error {
fmt.Println("removed task template: ", c.Args().First())
return nil
},
},
},
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```

View File

@ -0,0 +1,363 @@
---
tags:
- v1
---
The default version flag (`-v/--version`) is defined as `cli.VersionFlag`, which
is checked by the cli internals in order to print the `App.Version` via
`cli.VersionPrinter` and break execution.
#### Customization
The default flag may be customized to something other than `-v/--version` by
setting `cli.VersionFlag`, e.g.:
<!-- {
"args": ["&#45;&#45print-version"],
"output": "partay version 19\\.99\\.0"
} -->
``` go
package main
import (
"log"
"os"
"github.com/urfave/cli"
)
func main() {
cli.VersionFlag = cli.BoolFlag{
Name: "print-version, V",
Usage: "print only the version",
}
app := cli.NewApp()
app.Name = "partay"
app.Version = "19.99.0"
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
Alternatively, the version printer at `cli.VersionPrinter` may be overridden, e.g.:
<!-- {
"args": ["&#45;&#45version"],
"output": "version=19\\.99\\.0 revision=fafafaf"
} -->
``` go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli"
)
var (
Revision = "fafafaf"
)
func main() {
cli.VersionPrinter = func(c *cli.Context) {
fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision)
}
app := cli.NewApp()
app.Name = "partay"
app.Version = "19.99.0"
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
#### Full API Example
**Notice**: This is a contrived (functioning) example meant strictly for API
demonstration purposes. Use of one's imagination is encouraged.
<!-- {
"output": "made it!\nPhew!"
} -->
``` go
package main
import (
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"time"
"github.com/urfave/cli"
)
func init() {
cli.AppHelpTemplate += "\nCUSTOMIZED: you bet ur muffins\n"
cli.CommandHelpTemplate += "\nYMMV\n"
cli.SubcommandHelpTemplate += "\nor something\n"
cli.HelpFlag = cli.BoolFlag{Name: "halp"}
cli.BashCompletionFlag = cli.BoolFlag{Name: "compgen", Hidden: true}
cli.VersionFlag = cli.BoolFlag{Name: "print-version, V"}
cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) {
fmt.Fprintf(w, "best of luck to you\n")
}
cli.VersionPrinter = func(c *cli.Context) {
fmt.Fprintf(c.App.Writer, "version=%s\n", c.App.Version)
}
cli.OsExiter = func(c int) {
fmt.Fprintf(cli.ErrWriter, "refusing to exit %d\n", c)
}
cli.ErrWriter = ioutil.Discard
cli.FlagStringer = func(fl cli.Flag) string {
return fmt.Sprintf("\t\t%s", fl.GetName())
}
}
type hexWriter struct{}
func (w *hexWriter) Write(p []byte) (int, error) {
for _, b := range p {
fmt.Printf("%x", b)
}
fmt.Printf("\n")
return len(p), nil
}
type genericType struct{
s string
}
func (g *genericType) Set(value string) error {
g.s = value
return nil
}
func (g *genericType) String() string {
return g.s
}
func main() {
app := cli.NewApp()
app.Name = "kənˈtrīv"
app.Version = "19.99.0"
app.Compiled = time.Now()
app.Authors = []cli.Author{
cli.Author{
Name: "Example Human",
Email: "human@example.com",
},
}
app.Copyright = "(c) 1999 Serious Enterprise"
app.HelpName = "contrive"
app.Usage = "demonstrate available API"
app.UsageText = "contrive - demonstrating the available API"
app.ArgsUsage = "[args and such]"
app.Commands = []cli.Command{
cli.Command{
Name: "doo",
Aliases: []string{"do"},
Category: "motion",
Usage: "do the doo",
UsageText: "doo - does the dooing",
Description: "no really, there is a lot of dooing to be done",
ArgsUsage: "[arrgh]",
Flags: []cli.Flag{
cli.BoolFlag{Name: "forever, forevvarr"},
},
Subcommands: cli.Commands{
cli.Command{
Name: "wop",
Action: wopAction,
},
},
SkipFlagParsing: false,
HideHelp: false,
Hidden: false,
HelpName: "doo!",
BashComplete: func(c *cli.Context) {
fmt.Fprintf(c.App.Writer, "--better\n")
},
Before: func(c *cli.Context) error {
fmt.Fprintf(c.App.Writer, "brace for impact\n")
return nil
},
After: func(c *cli.Context) error {
fmt.Fprintf(c.App.Writer, "did we lose anyone?\n")
return nil
},
Action: func(c *cli.Context) error {
c.Command.FullName()
c.Command.HasName("wop")
c.Command.Names()
c.Command.VisibleFlags()
fmt.Fprintf(c.App.Writer, "dodododododoodododddooooododododooo\n")
if c.Bool("forever") {
c.Command.Run(c)
}
return nil
},
OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error {
fmt.Fprintf(c.App.Writer, "for shame\n")
return err
},
},
}
app.Flags = []cli.Flag{
cli.BoolFlag{Name: "fancy"},
cli.BoolTFlag{Name: "fancier"},
cli.DurationFlag{Name: "howlong, H", Value: time.Second * 3},
cli.Float64Flag{Name: "howmuch"},
cli.GenericFlag{Name: "wat", Value: &genericType{}},
cli.Int64Flag{Name: "longdistance"},
cli.Int64SliceFlag{Name: "intervals"},
cli.IntFlag{Name: "distance"},
cli.IntSliceFlag{Name: "times"},
cli.StringFlag{Name: "dance-move, d"},
cli.StringSliceFlag{Name: "names, N"},
cli.UintFlag{Name: "age"},
cli.Uint64Flag{Name: "bigage"},
}
app.EnableBashCompletion = true
app.UseShortOptionHandling = true
app.HideHelp = false
app.HideVersion = false
app.BashComplete = func(c *cli.Context) {
fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n")
}
app.Before = func(c *cli.Context) error {
fmt.Fprintf(c.App.Writer, "HEEEERE GOES\n")
return nil
}
app.After = func(c *cli.Context) error {
fmt.Fprintf(c.App.Writer, "Phew!\n")
return nil
}
app.CommandNotFound = func(c *cli.Context, command string) {
fmt.Fprintf(c.App.Writer, "Thar be no %q here.\n", command)
}
app.OnUsageError = func(c *cli.Context, err error, isSubcommand bool) error {
if isSubcommand {
return err
}
fmt.Fprintf(c.App.Writer, "WRONG: %#v\n", err)
return nil
}
app.Action = func(c *cli.Context) error {
cli.DefaultAppComplete(c)
cli.HandleExitCoder(errors.New("not an exit coder, though"))
cli.ShowAppHelp(c)
cli.ShowCommandCompletions(c, "nope")
cli.ShowCommandHelp(c, "also-nope")
cli.ShowCompletions(c)
cli.ShowSubcommandHelp(c)
cli.ShowVersion(c)
categories := c.App.Categories()
categories.AddCommand("sounds", cli.Command{
Name: "bloop",
})
for _, category := range c.App.Categories() {
fmt.Fprintf(c.App.Writer, "%s\n", category.Name)
fmt.Fprintf(c.App.Writer, "%#v\n", category.Commands)
fmt.Fprintf(c.App.Writer, "%#v\n", category.VisibleCommands())
}
fmt.Printf("%#v\n", c.App.Command("doo"))
if c.Bool("infinite") {
c.App.Run([]string{"app", "doo", "wop"})
}
if c.Bool("forevar") {
c.App.RunAsSubcommand(c)
}
c.App.Setup()
fmt.Printf("%#v\n", c.App.VisibleCategories())
fmt.Printf("%#v\n", c.App.VisibleCommands())
fmt.Printf("%#v\n", c.App.VisibleFlags())
fmt.Printf("%#v\n", c.Args().First())
if len(c.Args()) > 0 {
fmt.Printf("%#v\n", c.Args()[1])
}
fmt.Printf("%#v\n", c.Args().Present())
fmt.Printf("%#v\n", c.Args().Tail())
set := flag.NewFlagSet("contrive", 0)
nc := cli.NewContext(c.App, set, c)
fmt.Printf("%#v\n", nc.Args())
fmt.Printf("%#v\n", nc.Bool("nope"))
fmt.Printf("%#v\n", nc.BoolT("nerp"))
fmt.Printf("%#v\n", nc.Duration("howlong"))
fmt.Printf("%#v\n", nc.Float64("hay"))
fmt.Printf("%#v\n", nc.Generic("bloop"))
fmt.Printf("%#v\n", nc.Int64("bonk"))
fmt.Printf("%#v\n", nc.Int64Slice("burnks"))
fmt.Printf("%#v\n", nc.Int("bips"))
fmt.Printf("%#v\n", nc.IntSlice("blups"))
fmt.Printf("%#v\n", nc.String("snurt"))
fmt.Printf("%#v\n", nc.StringSlice("snurkles"))
fmt.Printf("%#v\n", nc.Uint("flub"))
fmt.Printf("%#v\n", nc.Uint64("florb"))
fmt.Printf("%#v\n", nc.GlobalBool("global-nope"))
fmt.Printf("%#v\n", nc.GlobalBoolT("global-nerp"))
fmt.Printf("%#v\n", nc.GlobalDuration("global-howlong"))
fmt.Printf("%#v\n", nc.GlobalFloat64("global-hay"))
fmt.Printf("%#v\n", nc.GlobalGeneric("global-bloop"))
fmt.Printf("%#v\n", nc.GlobalInt("global-bips"))
fmt.Printf("%#v\n", nc.GlobalIntSlice("global-blups"))
fmt.Printf("%#v\n", nc.GlobalString("global-snurt"))
fmt.Printf("%#v\n", nc.GlobalStringSlice("global-snurkles"))
fmt.Printf("%#v\n", nc.FlagNames())
fmt.Printf("%#v\n", nc.GlobalFlagNames())
fmt.Printf("%#v\n", nc.GlobalIsSet("wat"))
fmt.Printf("%#v\n", nc.GlobalSet("wat", "nope"))
fmt.Printf("%#v\n", nc.NArg())
fmt.Printf("%#v\n", nc.NumFlags())
fmt.Printf("%#v\n", nc.Parent())
nc.Set("wat", "also-nope")
ec := cli.NewExitError("ohwell", 86)
fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode())
fmt.Printf("made it!\n")
return nil
}
if os.Getenv("HEXY") != "" {
app.Writer = &hexWriter{}
app.ErrWriter = &hexWriter{}
}
app.Metadata = map[string]interface{}{
"layers": "many",
"explicable": false,
"whatever-values": 19.99,
}
// ignore error so we don't exit non-zero and break gfmrun README example tests
_ = app.Run(os.Args)
}
func wopAction(c *cli.Context) error {
fmt.Fprintf(c.App.Writer, ":wave: over here, eh\n")
return nil
}
```

View File

@ -0,0 +1,65 @@
---
tags:
- v1
---
One of the philosophies behind cli is that an API should be playful and full of
discovery. So a cli app can be as little as one line of code in `main()`.
<!-- {
"args": ["&#45;&#45;help"],
"output": "A new cli application"
} -->
``` go
package main
import (
"log"
"os"
"github.com/urfave/cli"
)
func main() {
err := cli.NewApp().Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
This app will run and show help text, but is not very useful. Let's give an
action to execute and some help documentation:
<!-- {
"output": "boom! I say!"
} -->
``` go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Name = "boom"
app.Usage = "make an explosive entrance"
app.Action = func(c *cli.Context) error {
fmt.Println("boom! I say!")
return nil
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
Running this already gives you a ton of functionality, plus support for things
like subcommands and flags, which are covered below.

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
---
tags:
- v1
---
There are a small set of breaking changes between v1 and v2.
Converting is relatively straightforward and typically takes less than
an hour. Specific steps are included in
[Migration Guide: v1 to v2](../migrate-v1-to-v2.md).

View File

@ -0,0 +1,36 @@
---
tags:
- v2
search:
boost: 2
---
You can lookup arguments by calling the `Args` function on `cli.Context`, e.g.:
<!-- {
"output": "Hello \""
} -->
```go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Action: func(cCtx *cli.Context) error {
fmt.Printf("Hello %q", cCtx.Args().Get(0))
return nil
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```

View File

@ -0,0 +1,257 @@
---
tags:
- v2
search:
boost: 2
---
You can enable completion commands by setting the `EnableBashCompletion` flag on
the `App` object to `true`. By default, this setting will allow auto-completion
for an app's subcommands, but you can write your own completion methods for the
App or its subcommands as well.
#### Default auto-completion
```go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
EnableBashCompletion: true,
Commands: []*cli.Command{
{
Name: "add",
Aliases: []string{"a"},
Usage: "add a task to the list",
Action: func(cCtx *cli.Context) error {
fmt.Println("added task: ", cCtx.Args().First())
return nil
},
},
{
Name: "complete",
Aliases: []string{"c"},
Usage: "complete a task on the list",
Action: func(cCtx *cli.Context) error {
fmt.Println("completed task: ", cCtx.Args().First())
return nil
},
},
{
Name: "template",
Aliases: []string{"t"},
Usage: "options for task templates",
Subcommands: []*cli.Command{
{
Name: "add",
Usage: "add a new template",
Action: func(cCtx *cli.Context) error {
fmt.Println("new task template: ", cCtx.Args().First())
return nil
},
},
{
Name: "remove",
Usage: "remove an existing template",
Action: func(cCtx *cli.Context) error {
fmt.Println("removed task template: ", cCtx.Args().First())
return nil
},
},
},
},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```
![](/docs/v2/images/default-bash-autocomplete.gif)
#### Custom auto-completion
<!-- {
"args": ["complete", "&#45;&#45;generate&#45;bash&#45;completion"],
"output": "laundry"
} -->
```go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"}
app := &cli.App{
EnableBashCompletion: true,
Commands: []*cli.Command{
{
Name: "complete",
Aliases: []string{"c"},
Usage: "complete a task on the list",
Action: func(cCtx *cli.Context) error {
fmt.Println("completed task: ", cCtx.Args().First())
return nil
},
BashComplete: func(cCtx *cli.Context) {
// This will complete if no args are passed
if cCtx.NArg() > 0 {
return
}
for _, t := range tasks {
fmt.Println(t)
}
},
},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```
![](/docs/v2/images/custom-bash-autocomplete.gif)
#### Enabling
To enable auto-completion for the current shell session, a bash script,
`autocomplete/bash_autocomplete` is included in this repo.
To use `autocomplete/bash_autocomplete` set an environment variable named `PROG`
to the name of your program and then `source` the
`autocomplete/bash_autocomplete` file.
For example, if your cli program is called `myprogram`:
```sh-session
$ PROG=myprogram source path/to/cli/autocomplete/bash_autocomplete
```
Auto-completion is now enabled for the current shell, but will not persist into
a new shell.
#### Distribution and Persistent Autocompletion
Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename
it to the name of the program you wish to add autocomplete support for (or
automatically install it there if you are distributing a package). Don't forget
to source the file or restart your shell to activate the auto-completion.
```sh-session
$ sudo cp path/to/autocomplete/bash_autocomplete /etc/bash_completion.d/<myprogram>
$ source /etc/bash_completion.d/<myprogram>
```
Alternatively, you can just document that users should `source` the generic
`autocomplete/bash_autocomplete` and set `$PROG` within their bash configuration
file, adding these lines:
```sh-session
$ PROG=<myprogram>
$ source path/to/cli/autocomplete/bash_autocomplete
```
Keep in mind that if they are enabling auto-completion for more than one
program, they will need to set `PROG` and source
`autocomplete/bash_autocomplete` for each program, like so:
```sh-session
$ PROG=<program1>
$ source path/to/cli/autocomplete/bash_autocomplete
$ PROG=<program2>
$ source path/to/cli/autocomplete/bash_autocomplete
```
#### Customization
The default shell completion flag (`--generate-bash-completion`) is defined as
`cli.EnableBashCompletion`, and may be redefined if desired, e.g.:
<!-- {
"args": ["&#45;&#45;generate&#45;bash&#45;completion"],
"output": "wat\nhelp\nh"
} -->
```go
package main
import (
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
EnableBashCompletion: true,
Commands: []*cli.Command{
{
Name: "wat",
},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```
#### ZSH Support
Auto-completion for ZSH is also supported using the
`autocomplete/zsh_autocomplete` file included in this repo. One environment
variable is used, `PROG`. Set `PROG` to the program name as before, and then
`source path/to/autocomplete/zsh_autocomplete`. Adding the following lines to
your ZSH configuration file (usually `.zshrc`) will allow the auto-completion to
persist across new shells:
```sh-session
$ PROG=<myprogram>
$ source path/to/autocomplete/zsh_autocomplete
```
#### ZSH default auto-complete example
![](/docs/v2/images/default-zsh-autocomplete.gif)
#### ZSH custom auto-complete example
![](/docs/v2/images/custom-zsh-autocomplete.gif)
#### PowerShell Support
Auto-completion for PowerShell is also supported using the
`autocomplete/powershell_autocomplete.ps1` file included in this repo.
Rename the script to `<my program>.ps1` and move it anywhere in your file
system. The location of script does not matter, only the file name of the
script has to match the your program's binary name.
To activate it, enter:
```powershell
& path/to/autocomplete/<my program>.ps1
```
To persist across new shells, open the PowerShell profile (with `code $profile`
or `notepad $profile`) and add the line:
```powershell
& path/to/autocomplete/<my program>.ps1
```

View File

@ -0,0 +1,74 @@
---
tags:
- v2
search:
boost: 2
---
Traditional use of options using their shortnames look like this:
```sh-session
$ cmd -s -o -m "Some message"
```
Suppose you want users to be able to combine options with their shortnames. This
can be done using the `UseShortOptionHandling` bool in your app configuration,
or for individual commands by attaching it to the command configuration. For
example:
<!-- {
"args": ["short", "&#45;som", "Some message"],
"output": "serve: true\noption: true\nmessage: Some message\n"
} -->
```go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
UseShortOptionHandling: true,
Commands: []*cli.Command{
{
Name: "short",
Usage: "complete a task on the list",
Flags: []cli.Flag{
&cli.BoolFlag{Name: "serve", Aliases: []string{"s"}},
&cli.BoolFlag{Name: "option", Aliases: []string{"o"}},
&cli.StringFlag{Name: "message", Aliases: []string{"m"}},
},
Action: func(cCtx *cli.Context) error {
fmt.Println("serve:", cCtx.Bool("serve"))
fmt.Println("option:", cCtx.Bool("option"))
fmt.Println("message:", cCtx.String("message"))
return nil
},
},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```
If your program has any number of bool flags such as `serve` and `option`, and
optionally one non-bool flag `message`, with the short options of `-s`, `-o`,
and `-m` respectively, setting `UseShortOptionHandling` will also support the
following syntax:
```sh-session
$ cmd -som "Some message"
```
If you enable `UseShortOptionHandling`, then you must not use any flags that
have a single leading `-` or this will result in failures. For example,
`-option` can no longer be used. Flags with two leading dashes (such as
`--options`) are still valid.

View File

@ -0,0 +1,45 @@
---
tags:
- v2
search:
boost: 2
---
Calling `App.Run` will not automatically call `os.Exit`, which means that by
default the exit code will "fall through" to being `0`. An explicit exit code
may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a
`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.:
<!-- {
"error": "Ginger croutons are not in the soup"
} -->
```go
package main
import (
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "ginger-crouton",
Usage: "is it in the soup?",
},
},
Action: func(ctx *cli.Context) error {
if !ctx.Bool("ginger-crouton") {
return cli.Exit("Ginger croutons are not in the soup", 86)
}
return nil
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```

597
docs/v2/examples/flags.md Normal file
View File

@ -0,0 +1,597 @@
---
tags:
- v2
search:
boost: 2
---
Setting and querying flags is simple.
<!-- {
"output": "Hello Nefertiti"
} -->
```go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Flags: []cli.Flag{
&cli.StringFlag{
Name: "lang",
Value: "english",
Usage: "language for the greeting",
},
},
Action: func(cCtx *cli.Context) error {
name := "Nefertiti"
if cCtx.NArg() > 0 {
name = cCtx.Args().Get(0)
}
if cCtx.String("lang") == "spanish" {
fmt.Println("Hola", name)
} else {
fmt.Println("Hello", name)
}
return nil
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```
You can also set a destination variable for a flag, to which the content will be
scanned.
<!-- {
"output": "Hello someone"
} -->
```go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
var language string
app := &cli.App{
Flags: []cli.Flag{
&cli.StringFlag{
Name: "lang",
Value: "english",
Usage: "language for the greeting",
Destination: &language,
},
},
Action: func(cCtx *cli.Context) error {
name := "someone"
if cCtx.NArg() > 0 {
name = cCtx.Args().Get(0)
}
if language == "spanish" {
fmt.Println("Hola", name)
} else {
fmt.Println("Hello", name)
}
return nil
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```
See full list of flags at https://pkg.go.dev/github.com/urfave/cli/v2
For bool flags you can specify the flag multiple times to get a count(e.g -v -v -v or -vvv)
<!-- {
"args": ["&#45;&#45;foo", "&#45;&#45;foo"],
"output": "count 2"
} -->
```go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
var count int
app := &cli.App{
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "foo",
Usage: "foo greeting",
Count: &count,
},
},
Action: func(cCtx *cli.Context) error {
fmt.Println("count", count)
return nil
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```
#### Placeholder Values
Sometimes it's useful to specify a flag's value within the usage string itself.
Such placeholders are indicated with back quotes.
For example this:
<!-- {
"args": ["&#45;&#45;help"],
"output": "&#45;&#45;config FILE, &#45;c FILE"
} -->
```go
package main
import (
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Flags: []cli.Flag{
&cli.StringFlag{
Name: "config",
Aliases: []string{"c"},
Usage: "Load configuration from `FILE`",
},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```
Will result in help output like:
```
--config FILE, -c FILE Load configuration from FILE
```
Note that only the first placeholder is used. Subsequent back-quoted words will
be left as-is.
#### Alternate Names
You can set alternate (or short) names for flags by providing a comma-delimited
list for the `Name`. e.g.
<!-- {
"args": ["&#45;&#45;help"],
"output": "&#45;&#45;lang value, &#45;l value.*language for the greeting.*default: \"english\""
} -->
```go
package main
import (
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Flags: []cli.Flag{
&cli.StringFlag{
Name: "lang",
Aliases: []string{"l"},
Value: "english",
Usage: "language for the greeting",
},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```
That flag can then be set with `--lang spanish` or `-l spanish`. Note that
giving two different forms of the same flag in the same command invocation is an
error.
#### Ordering
Flags for the application and commands are shown in the order they are defined.
However, it's possible to sort them from outside this library by using `FlagsByName`
or `CommandsByName` with `sort`.
For example this:
<!-- {
"args": ["&#45;&#45;help"],
"output": ".*Load configuration from FILE\n.*\n.*Language for the greeting.*"
} -->
```go
package main
import (
"log"
"os"
"sort"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Flags: []cli.Flag{
&cli.StringFlag{
Name: "lang",
Aliases: []string{"l"},
Value: "english",
Usage: "Language for the greeting",
},
&cli.StringFlag{
Name: "config",
Aliases: []string{"c"},
Usage: "Load configuration from `FILE`",
},
},
Commands: []*cli.Command{
{
Name: "complete",
Aliases: []string{"c"},
Usage: "complete a task on the list",
Action: func(*cli.Context) error {
return nil
},
},
{
Name: "add",
Aliases: []string{"a"},
Usage: "add a task to the list",
Action: func(*cli.Context) error {
return nil
},
},
},
}
sort.Sort(cli.FlagsByName(app.Flags))
sort.Sort(cli.CommandsByName(app.Commands))
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```
Will result in help output like:
```
--config FILE, -c FILE Load configuration from FILE
--lang value, -l value Language for the greeting (default: "english")
```
#### Values from the Environment
You can also have the default value set from the environment via `EnvVars`. e.g.
<!-- {
"args": ["&#45;&#45;help"],
"output": "language for the greeting.*APP_LANG"
} -->
```go
package main
import (
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Flags: []cli.Flag{
&cli.StringFlag{
Name: "lang",
Aliases: []string{"l"},
Value: "english",
Usage: "language for the greeting",
EnvVars: []string{"APP_LANG"},
},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```
If `EnvVars` contains more than one string, the first environment variable that
resolves is used.
<!-- {
"args": ["&#45;&#45;help"],
"output": "language for the greeting.*LEGACY_COMPAT_LANG.*APP_LANG.*LANG"
} -->
```go
package main
import (
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Flags: []cli.Flag{
&cli.StringFlag{
Name: "lang",
Aliases: []string{"l"},
Value: "english",
Usage: "language for the greeting",
EnvVars: []string{"LEGACY_COMPAT_LANG", "APP_LANG", "LANG"},
},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```
#### Values from files
You can also have the default value set from file via `FilePath`. e.g.
<!-- {
"args": ["&#45;&#45;help"],
"output": "password for the mysql database"
} -->
```go
package main
import (
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Flags: []cli.Flag{
&cli.StringFlag{
Name: "password",
Aliases: []string{"p"},
Usage: "password for the mysql database",
FilePath: "/etc/mysql/password",
},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```
Note that default values set from file (e.g. `FilePath`) take precedence over
default values set from the environment (e.g. `EnvVar`).
#### Values from alternate input sources (YAML, TOML, and others)
There is a separate package altsrc that adds support for getting flag values
from other file input sources.
Currently supported input source formats:
- YAML
- JSON
- TOML
In order to get values for a flag from an alternate input source the following
code would be added to wrap an existing cli.Flag like below:
```go
// --- >8 ---
altsrc.NewIntFlag(&cli.IntFlag{Name: "test"})
```
Initialization must also occur for these flags. Below is an example initializing
getting data from a yaml file below.
```go
// --- >8 ---
command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
```
The code above will use the "load" string as a flag name to get the file name of
a yaml file from the cli.Context. It will then use that file name to initialize
the yaml input source for any flags that are defined on that command. As a note
the "load" flag used would also have to be defined on the command flags in order
for this code snippet to work.
Currently only YAML, JSON, and TOML files are supported but developers can add
support for other input sources by implementing the altsrc.InputSourceContext
for their given sources.
Here is a more complete sample of a command using YAML support:
<!-- {
"args": ["&#45;&#45;help"],
"output": "&#45&#45;test value.*default: 0"
} -->
```go
package main
import (
"fmt"
"os"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
)
func main() {
flags := []cli.Flag{
altsrc.NewIntFlag(&cli.IntFlag{Name: "test"}),
&cli.StringFlag{Name: "load"},
}
app := &cli.App{
Action: func(*cli.Context) error {
fmt.Println("--test value.*default: 0")
return nil
},
Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")),
Flags: flags,
}
app.Run(os.Args)
}
```
#### Required Flags
You can make a flag required by setting the `Required` field to `true`. If a user
does not provide a required flag, they will be shown an error message.
Take for example this app that requires the `lang` flag:
<!-- {
"error": "Required flag \"lang\" not set"
} -->
```go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Flags: []cli.Flag{
&cli.StringFlag{
Name: "lang",
Value: "english",
Usage: "language for the greeting",
Required: true,
},
},
Action: func(cCtx *cli.Context) error {
output := "Hello"
if cCtx.String("lang") == "spanish" {
output = "Hola"
}
fmt.Println(output)
return nil
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```
If the app is run without the `lang` flag, the user will see the following message
```
Required flag "lang" not set
```
#### Default Values for help output
Sometimes it's useful to specify a flag's default help-text value within the
flag declaration. This can be useful if the default value for a flag is a
computed value. The default value can be set via the `DefaultText` struct field.
For example this:
<!-- {
"args": ["&#45;&#45;help"],
"output": "&#45;&#45;port value"
} -->
```go
package main
import (
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Flags: []cli.Flag{
&cli.IntFlag{
Name: "port",
Usage: "Use a randomized port",
Value: 0,
DefaultText: "random",
},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```
Will result in help output like:
```
--port value Use a randomized port (default: random)
```
#### Precedence
The precedence for flag value sources is as follows (highest to lowest):
0. Command line flag value from user
0. Environment variable (if specified)
0. Configuration file (if specified)
0. Default defined on the flag

View File

@ -0,0 +1,262 @@
---
tags:
- v2
search:
boost: 2
---
**Notice**: This is a contrived (functioning) example meant strictly for API
demonstration purposes. Use of one's imagination is encouraged.
<!-- {
"output": "made it!\nPhew!"
} -->
```go
package main
import (
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"time"
"github.com/urfave/cli/v2"
)
func init() {
cli.AppHelpTemplate += "\nCUSTOMIZED: you bet ur muffins\n"
cli.CommandHelpTemplate += "\nYMMV\n"
cli.SubcommandHelpTemplate += "\nor something\n"
cli.HelpFlag = &cli.BoolFlag{Name: "halp"}
cli.VersionFlag = &cli.BoolFlag{Name: "print-version", Aliases: []string{"V"}}
cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) {
fmt.Fprintf(w, "best of luck to you\n")
}
cli.VersionPrinter = func(cCtx *cli.Context) {
fmt.Fprintf(cCtx.App.Writer, "version=%s\n", cCtx.App.Version)
}
cli.OsExiter = func(cCtx int) {
fmt.Fprintf(cli.ErrWriter, "refusing to exit %d\n", cCtx)
}
cli.ErrWriter = ioutil.Discard
cli.FlagStringer = func(fl cli.Flag) string {
return fmt.Sprintf("\t\t%s", fl.Names()[0])
}
}
type hexWriter struct{}
func (w *hexWriter) Write(p []byte) (int, error) {
for _, b := range p {
fmt.Printf("%x", b)
}
fmt.Printf("\n")
return len(p), nil
}
type genericType struct {
s string
}
func (g *genericType) Set(value string) error {
g.s = value
return nil
}
func (g *genericType) String() string {
return g.s
}
func main() {
app := &cli.App{
Name: "kənˈtrīv",
Version: "v19.99.0",
Compiled: time.Now(),
Authors: []*cli.Author{
&cli.Author{
Name: "Example Human",
Email: "human@example.com",
},
},
Copyright: "(c) 1999 Serious Enterprise",
HelpName: "contrive",
Usage: "demonstrate available API",
UsageText: "contrive - demonstrating the available API",
ArgsUsage: "[args and such]",
Commands: []*cli.Command{
&cli.Command{
Name: "doo",
Aliases: []string{"do"},
Category: "motion",
Usage: "do the doo",
UsageText: "doo - does the dooing",
Description: "no really, there is a lot of dooing to be done",
ArgsUsage: "[arrgh]",
Flags: []cli.Flag{
&cli.BoolFlag{Name: "forever", Aliases: []string{"forevvarr"}},
},
Subcommands: []*cli.Command{
&cli.Command{
Name: "wop",
Action: wopAction,
},
},
SkipFlagParsing: false,
HideHelp: false,
Hidden: false,
HelpName: "doo!",
BashComplete: func(cCtx *cli.Context) {
fmt.Fprintf(cCtx.App.Writer, "--better\n")
},
Before: func(cCtx *cli.Context) error {
fmt.Fprintf(cCtx.App.Writer, "brace for impact\n")
return nil
},
After: func(cCtx *cli.Context) error {
fmt.Fprintf(cCtx.App.Writer, "did we lose anyone?\n")
return nil
},
Action: func(cCtx *cli.Context) error {
cCtx.Command.FullName()
cCtx.Command.HasName("wop")
cCtx.Command.Names()
cCtx.Command.VisibleFlags()
fmt.Fprintf(cCtx.App.Writer, "dodododododoodododddooooododododooo\n")
if cCtx.Bool("forever") {
cCtx.Command.Run(cCtx)
}
return nil
},
OnUsageError: func(cCtx *cli.Context, err error, isSubcommand bool) error {
fmt.Fprintf(cCtx.App.Writer, "for shame\n")
return err
},
},
},
Flags: []cli.Flag{
&cli.BoolFlag{Name: "fancy"},
&cli.BoolFlag{Value: true, Name: "fancier"},
&cli.DurationFlag{Name: "howlong", Aliases: []string{"H"}, Value: time.Second * 3},
&cli.Float64Flag{Name: "howmuch"},
&cli.GenericFlag{Name: "wat", Value: &genericType{}},
&cli.Int64Flag{Name: "longdistance"},
&cli.Int64SliceFlag{Name: "intervals"},
&cli.IntFlag{Name: "distance"},
&cli.IntSliceFlag{Name: "times"},
&cli.StringFlag{Name: "dance-move", Aliases: []string{"d"}},
&cli.StringSliceFlag{Name: "names", Aliases: []string{"N"}},
&cli.UintFlag{Name: "age"},
&cli.Uint64Flag{Name: "bigage"},
},
EnableBashCompletion: true,
HideHelp: false,
HideVersion: false,
BashComplete: func(cCtx *cli.Context) {
fmt.Fprintf(cCtx.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n")
},
Before: func(cCtx *cli.Context) error {
fmt.Fprintf(cCtx.App.Writer, "HEEEERE GOES\n")
return nil
},
After: func(cCtx *cli.Context) error {
fmt.Fprintf(cCtx.App.Writer, "Phew!\n")
return nil
},
CommandNotFound: func(cCtx *cli.Context, command string) {
fmt.Fprintf(cCtx.App.Writer, "Thar be no %q here.\n", command)
},
OnUsageError: func(cCtx *cli.Context, err error, isSubcommand bool) error {
if isSubcommand {
return err
}
fmt.Fprintf(cCtx.App.Writer, "WRONG: %#v\n", err)
return nil
},
Action: func(cCtx *cli.Context) error {
cli.DefaultAppComplete(cCtx)
cli.HandleExitCoder(errors.New("not an exit coder, though"))
cli.ShowAppHelp(cCtx)
cli.ShowCommandCompletions(cCtx, "nope")
cli.ShowCommandHelp(cCtx, "also-nope")
cli.ShowCompletions(cCtx)
cli.ShowSubcommandHelp(cCtx)
cli.ShowVersion(cCtx)
fmt.Printf("%#v\n", cCtx.App.Command("doo"))
if cCtx.Bool("infinite") {
cCtx.App.Run([]string{"app", "doo", "wop"})
}
if cCtx.Bool("forevar") {
cCtx.App.RunAsSubcommand(cCtx)
}
cCtx.App.Setup()
fmt.Printf("%#v\n", cCtx.App.VisibleCategories())
fmt.Printf("%#v\n", cCtx.App.VisibleCommands())
fmt.Printf("%#v\n", cCtx.App.VisibleFlags())
fmt.Printf("%#v\n", cCtx.Args().First())
if cCtx.Args().Len() > 0 {
fmt.Printf("%#v\n", cCtx.Args().Get(1))
}
fmt.Printf("%#v\n", cCtx.Args().Present())
fmt.Printf("%#v\n", cCtx.Args().Tail())
set := flag.NewFlagSet("contrive", 0)
nc := cli.NewContext(cCtx.App, set, cCtx)
fmt.Printf("%#v\n", nc.Args())
fmt.Printf("%#v\n", nc.Bool("nope"))
fmt.Printf("%#v\n", !nc.Bool("nerp"))
fmt.Printf("%#v\n", nc.Duration("howlong"))
fmt.Printf("%#v\n", nc.Float64("hay"))
fmt.Printf("%#v\n", nc.Generic("bloop"))
fmt.Printf("%#v\n", nc.Int64("bonk"))
fmt.Printf("%#v\n", nc.Int64Slice("burnks"))
fmt.Printf("%#v\n", nc.Int("bips"))
fmt.Printf("%#v\n", nc.IntSlice("blups"))
fmt.Printf("%#v\n", nc.String("snurt"))
fmt.Printf("%#v\n", nc.StringSlice("snurkles"))
fmt.Printf("%#v\n", nc.Uint("flub"))
fmt.Printf("%#v\n", nc.Uint64("florb"))
fmt.Printf("%#v\n", nc.FlagNames())
fmt.Printf("%#v\n", nc.IsSet("wat"))
fmt.Printf("%#v\n", nc.Set("wat", "nope"))
fmt.Printf("%#v\n", nc.NArg())
fmt.Printf("%#v\n", nc.NumFlags())
fmt.Printf("%#v\n", nc.Lineage()[1])
nc.Set("wat", "also-nope")
ec := cli.Exit("ohwell", 86)
fmt.Fprintf(cCtx.App.Writer, "%d", ec.ExitCode())
fmt.Printf("made it!\n")
return ec
},
Metadata: map[string]interface{}{
"layers": "many",
"explicable": false,
"whatever-values": 19.99,
},
}
if os.Getenv("HEXY") != "" {
app.Writer = &hexWriter{}
app.ErrWriter = &hexWriter{}
}
app.Run(os.Args)
}
func wopAction(cCtx *cli.Context) error {
fmt.Fprintf(cCtx.App.Writer, ":wave: over here, eh\n")
return nil
}
```

View File

@ -0,0 +1,101 @@
---
tags:
- v2
search:
boost: 2
---
The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked
by the cli internals in order to print generated help text for the app, command,
or subcommand, and break execution.
#### Customization
All of the help text generation may be customized, and at multiple levels. The
templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and
`SubcommandHelpTemplate` which may be reassigned or augmented, and full override
is possible by assigning a compatible func to the `cli.HelpPrinter` variable,
e.g.:
<!-- {
"output": "Ha HA. I pwnd the help!!1"
} -->
```go
package main
import (
"fmt"
"io"
"os"
"github.com/urfave/cli/v2"
)
func main() {
// EXAMPLE: Append to an existing template
cli.AppHelpTemplate = fmt.Sprintf(`%s
WEBSITE: http://awesometown.example.com
SUPPORT: support@awesometown.example.com
`, cli.AppHelpTemplate)
// EXAMPLE: Override a template
cli.AppHelpTemplate = `NAME:
{{.Name}} - {{.Usage}}
USAGE:
{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
{{if len .Authors}}
AUTHOR:
{{range .Authors}}{{ . }}{{end}}
{{end}}{{if .Commands}}
COMMANDS:
{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
GLOBAL OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}{{if .Copyright }}
COPYRIGHT:
{{.Copyright}}
{{end}}{{if .Version}}
VERSION:
{{.Version}}
{{end}}
`
// EXAMPLE: Replace the `HelpPrinter` func
cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) {
fmt.Println("Ha HA. I pwnd the help!!1")
}
(&cli.App{}).Run(os.Args)
}
```
The default flag may be customized to something other than `-h/--help` by
setting `cli.HelpFlag`, e.g.:
<!-- {
"args": ["&#45;&#45halp"],
"output": "haaaaalp.*HALP"
} -->
```go
package main
import (
"os"
"github.com/urfave/cli/v2"
)
func main() {
cli.HelpFlag = &cli.BoolFlag{
Name: "haaaaalp",
Aliases: []string{"halp"},
Usage: "HALP",
EnvVars: []string{"SHOW_HALP", "HALPPLZ"},
}
(&cli.App{}).Run(os.Args)
}
```

73
docs/v2/examples/greet.md Normal file
View File

@ -0,0 +1,73 @@
---
tags:
- v2
search:
boost: 2
---
Being a programmer can be a lonely job. Thankfully by the power of automation
that is not the case! Let's create a greeter app to fend off our demons of
loneliness!
Start by creating a directory named `greet`, and within it, add a file,
`greet.go` with the following code in it:
<!-- {
"output": "Hello friend!"
} -->
```go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Name: "greet",
Usage: "fight the loneliness!",
Action: func(*cli.Context) error {
fmt.Println("Hello friend!")
return nil
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```
Install our command to the `$GOPATH/bin` directory:
```sh-session
$ go install
```
Finally run our new command:
```sh-session
$ greet
Hello friend!
```
cli also generates neat help text:
```sh-session
$ greet help
NAME:
greet - fight the loneliness!
USAGE:
greet [global options] command [command options] [arguments...]
COMMANDS:
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS
--help, -h show help (default: false)
```

View File

@ -0,0 +1,54 @@
---
tags:
- v2
search:
boost: 2
---
For additional organization in apps that have many subcommands, you can
associate a category for each command to group them together in the help
output, e.g.:
```go
package main
import (
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Commands: []*cli.Command{
{
Name: "noop",
},
{
Name: "add",
Category: "template",
},
{
Name: "remove",
Category: "template",
},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```
Will include:
```
COMMANDS:
noop
Template actions:
add
remove
```

View File

@ -0,0 +1,76 @@
---
tags:
- v2
search:
boost: 2
---
Subcommands can be defined for a more git-like command line app.
<!-- {
"args": ["template", "add"],
"output": "new task template: .+"
} -->
```go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Commands: []*cli.Command{
{
Name: "add",
Aliases: []string{"a"},
Usage: "add a task to the list",
Action: func(cCtx *cli.Context) error {
fmt.Println("added task: ", cCtx.Args().First())
return nil
},
},
{
Name: "complete",
Aliases: []string{"c"},
Usage: "complete a task on the list",
Action: func(cCtx *cli.Context) error {
fmt.Println("completed task: ", cCtx.Args().First())
return nil
},
},
{
Name: "template",
Aliases: []string{"t"},
Usage: "options for task templates",
Subcommands: []*cli.Command{
{
Name: "add",
Usage: "add a new template",
Action: func(cCtx *cli.Context) error {
fmt.Println("new task template: ", cCtx.Args().First())
return nil
},
},
{
Name: "remove",
Usage: "remove an existing template",
Action: func(cCtx *cli.Context) error {
fmt.Println("removed task template: ", cCtx.Args().First())
return nil
},
},
},
},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```

View File

@ -0,0 +1,11 @@
---
tags:
- v2
search:
boost: 2
---
To enable flag and command suggestions, set `app.Suggest = true`. If the suggest
feature is enabled, then the help output of the corresponding command will
provide an appropriate suggestion for the provided flag or subcommand if
available.

View File

@ -0,0 +1,64 @@
---
tags:
- v2
search:
boost: 2
---
Using the timestamp flag is simple. Please refer to
[`time.Parse`](https://golang.org/pkg/time/#example_Parse) to get possible
formats.
<!-- {
"args": ["&#45;&#45;meeting", "2019-08-12T15:04:05"],
"output": "2019\\-08\\-12 15\\:04\\:05 \\+0000 UTC"
} -->
```go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Flags: []cli.Flag{
&cli.TimestampFlag{Name: "meeting", Layout: "2006-01-02T15:04:05"},
},
Action: func(cCtx *cli.Context) error {
fmt.Printf("%s", cCtx.Timestamp("meeting").String())
return nil
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```
In this example the flag could be used like this:
```sh-session
$ myapp --meeting 2019-08-12T15:04:05
```
When the layout doesn't contain timezones, timestamp will render with UTC. To
change behavior, a default timezone can be provided with flag definition:
```go
app := &cli.App{
Flags: []cli.Flag{
&cli.TimestampFlag{Name: "meeting", Layout: "2006-01-02T15:04:05", Timezone: time.Local},
},
}
```
(time.Local contains the system's local time zone.)
Side note: quotes may be necessary around the date depending on your layout (if
you have spaces for instance)

View File

@ -0,0 +1,77 @@
---
tags:
- v2
search:
boost: 2
---
The default version flag (`-v/--version`) is defined as `cli.VersionFlag`, which
is checked by the cli internals in order to print the `App.Version` via
`cli.VersionPrinter` and break execution.
#### Customization
The default flag may be customized to something other than `-v/--version` by
setting `cli.VersionFlag`, e.g.:
<!-- {
"args": ["&#45;&#45print-version"],
"output": "partay version v19\\.99\\.0"
} -->
```go
package main
import (
"os"
"github.com/urfave/cli/v2"
)
func main() {
cli.VersionFlag = &cli.BoolFlag{
Name: "print-version",
Aliases: []string{"V"},
Usage: "print only the version",
}
app := &cli.App{
Name: "partay",
Version: "v19.99.0",
}
app.Run(os.Args)
}
```
Alternatively, the version printer at `cli.VersionPrinter` may be overridden,
e.g.:
<!-- {
"args": ["&#45;&#45version"],
"output": "version=v19\\.99\\.0 revision=fafafaf"
} -->
```go
package main
import (
"fmt"
"os"
"github.com/urfave/cli/v2"
)
var (
Revision = "fafafaf"
)
func main() {
cli.VersionPrinter = func(cCtx *cli.Context) {
fmt.Printf("version=%s revision=%s\n", cCtx.App.Version, Revision)
}
app := &cli.App{
Name: "partay",
Version: "v19.99.0",
}
app.Run(os.Args)
}
```

View File

@ -0,0 +1,63 @@
---
tags:
- v2
search:
boost: 2
---
One of the philosophies behind cli is that an API should be playful and full of
discovery. So a cli app can be as little as one line of code in `main()`.
<!-- {
"args": ["&#45;&#45;help"],
"output": "A new cli application"
} -->
```go
package main
import (
"os"
"github.com/urfave/cli/v2"
)
func main() {
(&cli.App{}).Run(os.Args)
}
```
This app will run and show help text, but is not very useful. Let's give an
action to execute and some help documentation:
<!-- {
"output": "boom! I say!"
} -->
```go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Name: "boom",
Usage: "make an explosive entrance",
Action: func(*cli.Context) error {
fmt.Println("boom! I say!")
return nil
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
```
Running this already gives you a ton of functionality, plus support for things
like subcommands and flags, which are covered below.

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,12 @@
---
tags:
- v2
search:
boost: 2
---
There are a small set of breaking changes between v1 and v2. Converting is
relatively straightforward and typically takes less than an hour. Specific steps
are included in [Migration Guide: v1 to v2](../migrate-v1-to-v2.md). Also see
the [pkg.go.dev docs](https://pkg.go.dev/github.com/urfave/cli/v2) for v2 API
documentation.

View File

@ -1,54 +1,116 @@
# 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.
# ./cmd/urfave-cli-genflags/main.go which uses the
# `Spec` type that maps to this file structure.
flag_types:
bool:
no_default_text: true
float64: {}
int64: {}
int: {}
time.Duration: {}
uint64: {}
uint: {}
string:
bool:
no_default_text: true
struct_fields:
- { name: TakesFile, type: bool }
Generic:
- name: Count
type: int
pointer: true
- name: Action
type: "func(*Context, bool) error"
float64:
struct_fields:
- { name: TakesFile, type: bool }
Path:
no_default_text: true
struct_fields:
- { name: TakesFile, type: bool }
- name: Action
type: "func(*Context, float64) error"
Float64Slice:
value_pointer: true
skip_interfaces:
- fmt.Stringer
Int64Slice:
value_pointer: true
skip_interfaces:
- fmt.Stringer
struct_fields:
- name: Action
type: "func(*Context, []float64) error"
int:
struct_fields:
- name: Base
type: int
- name: Action
type: "func(*Context, int) error"
IntSlice:
value_pointer: true
skip_interfaces:
- fmt.Stringer
struct_fields:
- name: Action
type: "func(*Context, []int) error"
int64:
struct_fields:
- name: Base
type: int
- name: Action
type: "func(*Context, int64) error"
Int64Slice:
value_pointer: true
skip_interfaces:
- fmt.Stringer
struct_fields:
- name: Action
type: "func(*Context, []int64) error"
uint:
struct_fields:
- name: Base
type: int
- name: Action
type: "func(*Context, uint) error"
UintSlice:
value_pointer: true
skip_interfaces:
- fmt.Stringer
struct_fields:
- name: Action
type: "func(*Context, []uint) error"
uint64:
struct_fields:
- name: Base
type: int
- name: Action
type: "func(*Context, uint64) error"
Uint64Slice:
value_pointer: true
skip_interfaces:
- fmt.Stringer
struct_fields:
- name: Action
type: "func(*Context, []uint64) error"
string:
struct_fields:
- name: TakesFile
type: bool
- name: Action
type: "func(*Context, string) error"
StringSlice:
value_pointer: true
skip_interfaces:
- fmt.Stringer
struct_fields:
- { name: TakesFile, type: bool }
- name: TakesFile
type: bool
- name: Action
type: "func(*Context, []string) error"
time.Duration:
struct_fields:
- name: Action
type: "func(*Context, time.Duration) error"
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: {}
- name: Layout
type: string
- name: Timezone
type: "*time.Location"
- name: Action
type: "func(*Context, *time.Time) error"
Generic:
no_destination_pointer: true
struct_fields:
- name: TakesFile
type: bool
- name: Action
type: "func(*Context, interface{}) error"
Path:
struct_fields:
- name: TakesFile
type: bool
- name: Action
type: "func(*Context, Path) error"

63
flag.go
View File

@ -7,7 +7,6 @@ import (
"io/ioutil"
"regexp"
"runtime"
"strconv"
"strings"
"syscall"
"time"
@ -123,6 +122,14 @@ type Flag interface {
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
GetValue() string
RunAction(*Context) error
}
// Countable is an interface to enable detection of flag values which support
// repetitive flags
type Countable interface {
Count() int
}
func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
@ -297,53 +304,6 @@ func stringifyFlag(f Flag) string {
fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault))
}
func stringifyIntSliceFlag(f *IntSliceFlag) string {
var defaultVals []string
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, i := range f.Value.Value() {
defaultVals = append(defaultVals, strconv.Itoa(i))
}
}
return stringifySliceFlag(f.Usage, f.Names(), defaultVals)
}
func stringifyInt64SliceFlag(f *Int64SliceFlag) string {
var defaultVals []string
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, i := range f.Value.Value() {
defaultVals = append(defaultVals, strconv.FormatInt(i, 10))
}
}
return stringifySliceFlag(f.Usage, f.Names(), defaultVals)
}
func stringifyFloat64SliceFlag(f *Float64SliceFlag) string {
var defaultVals []string
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, i := range f.Value.Value() {
defaultVals = append(defaultVals, strings.TrimRight(strings.TrimRight(fmt.Sprintf("%f", i), "0"), "."))
}
}
return stringifySliceFlag(f.Usage, f.Names(), defaultVals)
}
func stringifyStringSliceFlag(f *StringSliceFlag) string {
var defaultVals []string
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, s := range f.Value.Value() {
if len(s) > 0 {
defaultVals = append(defaultVals, strconv.Quote(s))
}
}
}
return stringifySliceFlag(f.Usage, f.Names(), defaultVals)
}
func stringifySliceFlag(usage string, names, defaultVals []string) string {
placeholder, usage := unquoteUsage(usage)
if placeholder == "" {
@ -356,11 +316,8 @@ func stringifySliceFlag(usage string, names, defaultVals []string) string {
}
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal))
multiInputString := "(accepts multiple inputs)"
if usageWithDefault != "" {
multiInputString = "\t" + multiInputString
}
return fmt.Sprintf("%s\t%s%s", prefixedNames(names, placeholder), usageWithDefault, multiInputString)
pn := prefixedNames(names, placeholder)
return fmt.Sprintf("%s [ %s ]\t%s", pn, pn, usageWithDefault)
}
func hasFlag(flags []Flag, fl Flag) bool {

View File

@ -1,11 +1,63 @@
package cli
import (
"errors"
"flag"
"fmt"
"strconv"
)
// boolValue needs to implement the boolFlag internal interface in flag
// to be able to capture bool fields and values
//
// type boolFlag interface {
// Value
// IsBoolFlag() bool
// }
type boolValue struct {
destination *bool
count *int
}
func newBoolValue(val bool, p *bool, count *int) *boolValue {
*p = val
return &boolValue{
destination: p,
count: count,
}
}
func (b *boolValue) Set(s string) error {
v, err := strconv.ParseBool(s)
if err != nil {
err = errors.New("parse error")
return err
}
*b.destination = v
if b.count != nil {
*b.count = *b.count + 1
}
return err
}
func (b *boolValue) Get() interface{} { return *b.destination }
func (b *boolValue) String() string {
if b.destination != nil {
return strconv.FormatBool(*b.destination)
}
return strconv.FormatBool(false)
}
func (b *boolValue) IsBoolFlag() bool { return true }
func (b *boolValue) Count() int {
if b.count != nil {
return *b.count
}
return 0
}
// 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 {
@ -20,6 +72,15 @@ func (f *BoolFlag) GetDefaultText() string {
return fmt.Sprintf("%v", f.Value)
}
// RunAction executes flag action if set
func (f *BoolFlag) RunAction(c *Context) error {
if f.Action != nil {
return f.Action(c, c.Bool(f.Name))
}
return nil
}
// Apply populates the flag given the flag set and environment
func (f *BoolFlag) Apply(set *flag.FlagSet) error {
if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
@ -31,16 +92,28 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error {
}
f.Value = valBool
f.HasBeenSet = true
} else {
// empty value implies that the env is defined but set to empty string, we have to assume that this is
// what the user wants. If user doesnt want this then the env needs to be deleted or the flag removed from
// file
f.Value = false
}
f.HasBeenSet = true
}
count := f.Count
dest := f.Destination
if count == nil {
count = new(int)
}
if dest == nil {
dest = new(bool)
}
for _, name := range f.Names() {
if f.Destination != nil {
set.BoolVar(f.Destination, name, f.Value, f.Usage)
continue
}
set.Bool(name, f.Value, f.Usage)
value := newBoolValue(f.Value, dest, count)
set.Var(value, name, f.Usage)
}
return nil

View File

@ -42,6 +42,15 @@ func (f *DurationFlag) Get(ctx *Context) time.Duration {
return ctx.Duration(f.Name)
}
// RunAction executes flag action if set
func (f *DurationFlag) RunAction(c *Context) error {
if f.Action != nil {
return f.Action(c, c.Duration(f.Name))
}
return nil
}
// Duration looks up the value of a local DurationFlag, returns
// 0 if not found
func (cCtx *Context) Duration(name string) time.Duration {

View File

@ -42,6 +42,15 @@ func (f *Float64Flag) Get(ctx *Context) float64 {
return ctx.Float64(f.Name)
}
// RunAction executes flag action if set
func (f *Float64Flag) RunAction(c *Context) error {
if f.Action != nil {
return f.Action(c, c.Float64(f.Name))
}
return nil
}
// Float64 looks up the value of a local Float64Flag, returns
// 0 if not found
func (cCtx *Context) Float64(name string) float64 {

View File

@ -83,7 +83,7 @@ func (f *Float64Slice) Get() interface{} {
// String returns a readable representation of this value
// (for usage defaults)
func (f *Float64SliceFlag) String() string {
return withEnvHint(f.GetEnvVars(), stringifyFloat64SliceFlag(f))
return withEnvHint(f.GetEnvVars(), f.stringify())
}
// GetValue returns the flags value as string representation and an empty
@ -141,6 +141,27 @@ func (f *Float64SliceFlag) Get(ctx *Context) []float64 {
return ctx.Float64Slice(f.Name)
}
func (f *Float64SliceFlag) stringify() string {
var defaultVals []string
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, i := range f.Value.Value() {
defaultVals = append(defaultVals, strings.TrimRight(strings.TrimRight(fmt.Sprintf("%f", i), "0"), "."))
}
}
return stringifySliceFlag(f.Usage, f.Names(), defaultVals)
}
// RunAction executes flag action if set
func (f *Float64SliceFlag) RunAction(c *Context) error {
if f.Action != nil {
return f.Action(c, c.Float64Slice(f.Name))
}
return nil
}
// Float64Slice looks up the value of a local Float64SliceFlag, returns
// nil if not found
func (cCtx *Context) Float64Slice(name string) []float64 {

View File

@ -22,7 +22,7 @@ func (f *GenericFlag) GetValue() string {
// 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 {
func (f *GenericFlag) Apply(set *flag.FlagSet) error {
if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
if val != "" {
if err := f.Value.Set(val); err != nil {
@ -45,6 +45,15 @@ func (f *GenericFlag) Get(ctx *Context) interface{} {
return ctx.Generic(f.Name)
}
// RunAction executes flag action if set
func (f *GenericFlag) RunAction(c *Context) error {
if f.Action != nil {
return f.Action(c, c.Generic(f.Name))
}
return nil
}
// Generic looks up the value of a local GenericFlag, returns
// nil if not found
func (cCtx *Context) Generic(name string) interface{} {

View File

@ -16,7 +16,7 @@ func (f *IntFlag) GetValue() string {
func (f *IntFlag) Apply(set *flag.FlagSet) error {
if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
if val != "" {
valInt, err := strconv.ParseInt(val, 0, 64)
valInt, err := strconv.ParseInt(val, f.Base, 64)
if err != nil {
return fmt.Errorf("could not parse %q as int value from %s for flag %s: %s", val, source, f.Name, err)
@ -43,6 +43,15 @@ func (f *IntFlag) Get(ctx *Context) int {
return ctx.Int(f.Name)
}
// RunAction executes flag action if set
func (f *IntFlag) RunAction(c *Context) error {
if f.Action != nil {
return f.Action(c, c.Int(f.Name))
}
return nil
}
// Int looks up the value of a local IntFlag, returns
// 0 if not found
func (cCtx *Context) Int(name string) int {

View File

@ -16,7 +16,7 @@ func (f *Int64Flag) GetValue() string {
func (f *Int64Flag) Apply(set *flag.FlagSet) error {
if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
if val != "" {
valInt, err := strconv.ParseInt(val, 0, 64)
valInt, err := strconv.ParseInt(val, f.Base, 64)
if err != nil {
return fmt.Errorf("could not parse %q as int value from %s for flag %s: %s", val, source, f.Name, err)
@ -42,6 +42,15 @@ func (f *Int64Flag) Get(ctx *Context) int64 {
return ctx.Int64(f.Name)
}
// RunAction executes flag action if set
func (f *Int64Flag) RunAction(c *Context) error {
if f.Action != nil {
return f.Action(c, c.Int64(f.Name))
}
return nil
}
// Int64 looks up the value of a local Int64Flag, returns
// 0 if not found
func (cCtx *Context) Int64(name string) int64 {

View File

@ -84,7 +84,7 @@ func (i *Int64Slice) Get() interface{} {
// String returns a readable representation of this value
// (for usage defaults)
func (f *Int64SliceFlag) String() string {
return withEnvHint(f.GetEnvVars(), stringifyInt64SliceFlag(f))
return withEnvHint(f.GetEnvVars(), f.stringify())
}
// GetValue returns the flags value as string representation and an empty
@ -140,6 +140,26 @@ func (f *Int64SliceFlag) Get(ctx *Context) []int64 {
return ctx.Int64Slice(f.Name)
}
func (f *Int64SliceFlag) stringify() string {
var defaultVals []string
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, i := range f.Value.Value() {
defaultVals = append(defaultVals, strconv.FormatInt(i, 10))
}
}
return stringifySliceFlag(f.Usage, f.Names(), defaultVals)
}
// RunAction executes flag action if set
func (f *Int64SliceFlag) RunAction(c *Context) error {
if f.Action != nil {
return f.Action(c, c.Int64Slice(f.Name))
}
return nil
}
// Int64Slice looks up the value of a local Int64SliceFlag, returns
// nil if not found
func (cCtx *Context) Int64Slice(name string) []int64 {

View File

@ -95,7 +95,7 @@ func (i *IntSlice) Get() interface{} {
// String returns a readable representation of this value
// (for usage defaults)
func (f *IntSliceFlag) String() string {
return withEnvHint(f.GetEnvVars(), stringifyIntSliceFlag(f))
return withEnvHint(f.GetEnvVars(), f.stringify())
}
// GetValue returns the flags value as string representation and an empty
@ -151,6 +151,26 @@ func (f *IntSliceFlag) Get(ctx *Context) []int {
return ctx.IntSlice(f.Name)
}
func (f *IntSliceFlag) stringify() string {
var defaultVals []string
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, i := range f.Value.Value() {
defaultVals = append(defaultVals, strconv.Itoa(i))
}
}
return stringifySliceFlag(f.Usage, f.Names(), defaultVals)
}
// RunAction executes flag action if set
func (f *IntSliceFlag) RunAction(c *Context) error {
if f.Action != nil {
return f.Action(c, c.IntSlice(f.Name))
}
return nil
}
// IntSlice looks up the value of a local IntSliceFlag, returns
// nil if not found
func (cCtx *Context) IntSlice(name string) []int {

View File

@ -47,6 +47,15 @@ func (f *PathFlag) Get(ctx *Context) string {
return ctx.Path(f.Name)
}
// RunAction executes flag action if set
func (f *PathFlag) RunAction(c *Context) error {
if f.Action != nil {
return f.Action(c, c.Path(f.Name))
}
return nil
}
// Path looks up the value of a local PathFlag, returns
// "" if not found
func (cCtx *Context) Path(name string) string {

View File

@ -45,6 +45,15 @@ func (f *StringFlag) Get(ctx *Context) string {
return ctx.String(f.Name)
}
// RunAction executes flag action if set
func (f *StringFlag) RunAction(c *Context) error {
if f.Action != nil {
return f.Action(c, c.String(f.Name))
}
return nil
}
// String looks up the value of a local StringFlag, returns
// "" if not found
func (cCtx *Context) String(name string) string {

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"flag"
"fmt"
"strconv"
"strings"
)
@ -73,7 +74,7 @@ func (s *StringSlice) Get() interface{} {
// String returns a readable representation of this value
// (for usage defaults)
func (f *StringSliceFlag) String() string {
return withEnvHint(f.GetEnvVars(), stringifyStringSliceFlag(f))
return withEnvHint(f.GetEnvVars(), f.stringify())
}
// GetValue returns the flags value as string representation and an empty
@ -129,6 +130,28 @@ func (f *StringSliceFlag) Get(ctx *Context) []string {
return ctx.StringSlice(f.Name)
}
func (f *StringSliceFlag) stringify() string {
var defaultVals []string
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, s := range f.Value.Value() {
if len(s) > 0 {
defaultVals = append(defaultVals, strconv.Quote(s))
}
}
}
return stringifySliceFlag(f.Usage, f.Names(), defaultVals)
}
// RunAction executes flag action if set
func (f *StringSliceFlag) RunAction(c *Context) error {
if f.Action != nil {
return f.Action(c, c.StringSlice(f.Name))
}
return nil
}
// StringSlice looks up the value of a local StringSliceFlag, returns
// nil if not found
func (cCtx *Context) StringSlice(name string) []string {

View File

@ -62,6 +62,53 @@ func TestBoolFlagValueFromContext(t *testing.T) {
expect(t, ff.Get(ctx), false)
}
func TestBoolFlagApply_SetsCount(t *testing.T) {
v := false
count := 0
fl := BoolFlag{Name: "wat", Aliases: []string{"W", "huh"}, Destination: &v, Count: &count}
set := flag.NewFlagSet("test", 0)
err := fl.Apply(set)
expect(t, err, nil)
err = set.Parse([]string{"--wat", "-W", "--huh"})
expect(t, err, nil)
expect(t, v, true)
expect(t, count, 3)
}
func TestBoolFlagCountFromContext(t *testing.T) {
boolCountTests := []struct {
input []string
expectedVal bool
expectedCount int
}{
{
input: []string{"-tf", "-w", "-huh"},
expectedVal: true,
expectedCount: 3,
},
{
input: []string{},
expectedVal: false,
expectedCount: 0,
},
}
for _, bct := range boolCountTests {
set := flag.NewFlagSet("test", 0)
ctx := NewContext(nil, set, nil)
tf := &BoolFlag{Name: "tf", Aliases: []string{"w", "huh"}}
err := tf.Apply(set)
expect(t, err, nil)
err = set.Parse(bct.input)
expect(t, err, nil)
expect(t, tf.Get(ctx), bct.expectedVal)
expect(t, ctx.Count("tf"), bct.expectedCount)
}
}
func TestFlagsFromEnv(t *testing.T) {
newSetFloat64Slice := func(defaults ...float64) Float64Slice {
s := NewFloat64Slice(defaults...)
@ -75,12 +122,24 @@ func TestFlagsFromEnv(t *testing.T) {
return *s
}
newSetUintSlice := func(defaults ...uint) UintSlice {
s := NewUintSlice(defaults...)
s.hasBeenSet = false
return *s
}
newSetInt64Slice := func(defaults ...int64) Int64Slice {
s := NewInt64Slice(defaults...)
s.hasBeenSet = false
return *s
}
newSetUint64Slice := func(defaults ...uint64) Uint64Slice {
s := NewUint64Slice(defaults...)
s.hasBeenSet = false
return *s
}
newSetStringSlice := func(defaults ...string) StringSlice {
s := NewStringSlice(defaults...)
s.hasBeenSet = false
@ -110,6 +169,10 @@ func TestFlagsFromEnv(t *testing.T) {
{"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"}}, ""},
{"08", 8, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}, Base: 10}, ""},
{"755", 493, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}, Base: 8}, ""},
{"deadBEEF", 3735928559, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}, Base: 16}, ""},
{"08", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}, Base: 0}, `could not parse "08" as int value from environment variable "SECONDS" 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: .*`},
@ -120,20 +183,36 @@ func TestFlagsFromEnv(t *testing.T) {
{"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", newSetUintSlice(1, 2), &UintSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
{"1.2,2", newSetUintSlice(), &UintSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as uint slice value from environment variable "SECONDS" for flag seconds: .*`},
{"foobar", newSetUintSlice(), &UintSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint 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 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: .*`},
{"1,2", newSetUint64Slice(1, 2), &Uint64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
{"1.2,2", newSetUint64Slice(), &Uint64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as uint64 slice value from environment variable "SECONDS" for flag seconds: .*`},
{"foobar", newSetUint64Slice(), &Uint64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint64 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"}}, ""},
{"foo,bar", newSetStringSlice("foo", "bar"), &StringSliceFlag{Name: "names", EnvVars: []string{"NAMES"}}, ""},
{"1", uint(1), &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
{"08", uint(8), &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}, Base: 10}, ""},
{"755", uint(493), &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}, Base: 8}, ""},
{"deadBEEF", uint(3735928559), &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}, Base: 16}, ""},
{"08", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}, Base: 0}, `could not parse "08" as uint value from environment variable "SECONDS" 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"}}, ""},
{"08", uint64(8), &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}, Base: 10}, ""},
{"755", uint64(493), &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}, Base: 8}, ""},
{"deadBEEF", uint64(3735928559), &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}, Base: 16}, ""},
{"08", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}, Base: 0}, `could not parse "08" as uint64 value from environment variable "SECONDS" 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: .*`},
@ -154,6 +233,10 @@ func TestFlagsFromEnv(t *testing.T) {
if !reflect.DeepEqual(ctx.Value(test.flag.Names()[0]), test.output) {
t.Errorf("ex:%01d expected %q to be parsed as %#v, instead was %#v", i, test.input, test.output, ctx.Value(test.flag.Names()[0]))
}
if !f.IsSet() {
t.Errorf("Flag %s not set", f.Names()[0])
}
return nil
},
}
@ -252,6 +335,16 @@ func TestFlagStringifying(t *testing.T) {
fl: &IntFlag{Name: "pens", DefaultText: "-19"},
expected: "--pens value\t(default: -19)",
},
{
name: "uint-slice-flag",
fl: &UintSliceFlag{Name: "pencils"},
expected: "--pencils value\t",
},
{
name: "uint-slice-flag-with-default-text",
fl: &UintFlag{Name: "pens", DefaultText: "29"},
expected: "--pens value\t(default: 29)",
},
{
name: "int64-flag",
fl: &Int64Flag{Name: "flume"},
@ -272,6 +365,16 @@ func TestFlagStringifying(t *testing.T) {
fl: &Int64SliceFlag{Name: "handles", DefaultText: "-2"},
expected: "--handles value\t(default: -2)",
},
{
name: "uint64-slice-flag",
fl: &Uint64SliceFlag{Name: "drawers"},
expected: "--drawers value\t",
},
{
name: "uint64-slice-flag-with-default-text",
fl: &Uint64SliceFlag{Name: "handles", DefaultText: "-2"},
expected: "--handles value\t(default: -2)",
},
{
name: "path-flag",
fl: &PathFlag{Name: "soup"},
@ -544,11 +647,11 @@ var stringSliceFlagTests = []struct {
value *StringSlice
expected string
}{
{"foo", nil, NewStringSlice(""), "--foo value\t(accepts multiple inputs)"},
{"f", nil, NewStringSlice(""), "-f value\t(accepts multiple inputs)"},
{"f", nil, NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")\t(accepts multiple inputs)"},
{"test", nil, NewStringSlice("Something"), "--test value\t(default: \"Something\")\t(accepts multiple inputs)"},
{"dee", []string{"d"}, NewStringSlice("Inka", "Dinka", "dooo"), "--dee value, -d value\t(default: \"Inka\", \"Dinka\", \"dooo\")\t(accepts multiple inputs)"},
{"foo", nil, NewStringSlice(""), "--foo value [ --foo value ]\t"},
{"f", nil, NewStringSlice(""), "-f value [ -f value ]\t"},
{"f", nil, NewStringSlice("Lipstick"), "-f value [ -f value ]\t(default: \"Lipstick\")"},
{"test", nil, NewStringSlice("Something"), "--test value [ --test value ]\t(default: \"Something\")"},
{"dee", []string{"d"}, NewStringSlice("Inka", "Dinka", "dooo"), "--dee value, -d value [ --dee value, -d value ]\t(default: \"Inka\", \"Dinka\", \"dooo\")"},
}
func TestStringSliceFlagHelpOutput(t *testing.T) {
@ -897,9 +1000,9 @@ var intSliceFlagTests = []struct {
value *IntSlice
expected string
}{
{"heads", nil, NewIntSlice(), "--heads value\t(accepts multiple inputs)"},
{"H", nil, NewIntSlice(), "-H value\t(accepts multiple inputs)"},
{"H", []string{"heads"}, NewIntSlice(9, 3), "-H value, --heads value\t(default: 9, 3)\t(accepts multiple inputs)"},
{"heads", nil, NewIntSlice(), "--heads value [ --heads value ]\t"},
{"H", nil, NewIntSlice(), "-H value [ -H value ]\t"},
{"H", []string{"heads"}, NewIntSlice(9, 3), "-H value, --heads value [ -H value, --heads value ]\t(default: 9, 3)"},
}
func TestIntSliceFlagHelpOutput(t *testing.T) {
@ -941,6 +1044,47 @@ func TestIntSliceFlagApply_SetsAllNames(t *testing.T) {
expect(t, err, nil)
}
func TestIntSliceFlagApply_UsesEnvValues_noDefault(t *testing.T) {
defer resetEnv(os.Environ())
os.Clearenv()
_ = os.Setenv("MY_GOAT", "1 , 2")
var val IntSlice
fl := IntSliceFlag{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(), []int(nil))
expect(t, set.Lookup("goat").Value.(*IntSlice).Value(), []int{1, 2})
}
func TestIntSliceFlagApply_UsesEnvValues_withDefault(t *testing.T) {
defer resetEnv(os.Environ())
os.Clearenv()
_ = os.Setenv("MY_GOAT", "1 , 2")
val := NewIntSlice(3, 4)
fl := IntSliceFlag{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(), []int{3, 4})
expect(t, set.Lookup("goat").Value.(*IntSlice).Value(), []int{1, 2})
}
func TestIntSliceFlagApply_DefaultValueWithDestination(t *testing.T) {
defValue := []int{1, 2}
fl := IntSliceFlag{Name: "country", Value: NewIntSlice(defValue...), Destination: NewIntSlice(3)}
set := flag.NewFlagSet("test", 0)
_ = fl.Apply(set)
err := set.Parse([]string{})
expect(t, err, nil)
expect(t, defValue, fl.Destination.Value())
}
func TestIntSliceFlagApply_ParentContext(t *testing.T) {
_ = (&App{
Flags: []Flag{
@ -994,10 +1138,10 @@ var int64SliceFlagTests = []struct {
value *Int64Slice
expected string
}{
{"heads", nil, NewInt64Slice(), "--heads value\t(accepts multiple inputs)"},
{"H", nil, NewInt64Slice(), "-H value\t(accepts multiple inputs)"},
{"heads", nil, NewInt64Slice(), "--heads value [ --heads value ]\t"},
{"H", nil, NewInt64Slice(), "-H value [ -H value ]\t"},
{"heads", []string{"H"}, NewInt64Slice(int64(2), int64(17179869184)),
"--heads value, -H value\t(default: 2, 17179869184)\t(accepts multiple inputs)"},
"--heads value, -H value [ --heads value, -H value ]\t(default: 2, 17179869184)"},
}
func TestInt64SliceFlagHelpOutput(t *testing.T) {
@ -1030,6 +1174,56 @@ func TestInt64SliceFlagWithEnvVarHelpOutput(t *testing.T) {
}
}
func TestInt64SliceFlagApply_SetsAllNames(t *testing.T) {
fl := Int64SliceFlag{Name: "bits", Aliases: []string{"B", "bips"}}
set := flag.NewFlagSet("test", 0)
_ = fl.Apply(set)
err := set.Parse([]string{"--bits", "23", "-B", "3", "--bips", "99"})
expect(t, err, nil)
}
func TestInt64SliceFlagApply_UsesEnvValues_noDefault(t *testing.T) {
defer resetEnv(os.Environ())
os.Clearenv()
_ = os.Setenv("MY_GOAT", "1 , 2")
var val Int64Slice
fl := Int64SliceFlag{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(), []int64(nil))
expect(t, set.Lookup("goat").Value.(*Int64Slice).Value(), []int64{1, 2})
}
func TestInt64SliceFlagApply_UsesEnvValues_withDefault(t *testing.T) {
defer resetEnv(os.Environ())
os.Clearenv()
_ = os.Setenv("MY_GOAT", "1 , 2")
val := NewInt64Slice(3, 4)
fl := Int64SliceFlag{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(), []int64{3, 4})
expect(t, set.Lookup("goat").Value.(*Int64Slice).Value(), []int64{1, 2})
}
func TestInt64SliceFlagApply_DefaultValueWithDestination(t *testing.T) {
defValue := []int64{1, 2}
fl := Int64SliceFlag{Name: "country", Value: NewInt64Slice(defValue...), Destination: NewInt64Slice(3)}
set := flag.NewFlagSet("test", 0)
_ = fl.Apply(set)
err := set.Parse([]string{})
expect(t, err, nil)
expect(t, defValue, fl.Destination.Value())
}
func TestInt64SliceFlagApply_ParentContext(t *testing.T) {
_ = (&App{
Flags: []Flag{
@ -1092,6 +1286,298 @@ func TestInt64SliceFlagValueFromContext(t *testing.T) {
expect(t, f.Get(ctx), []int64{1, 2, 3})
}
var uintSliceFlagTests = []struct {
name string
aliases []string
value *UintSlice
expected string
}{
{"heads", nil, NewUintSlice(), "--heads value [ --heads value ]\t"},
{"H", nil, NewUintSlice(), "-H value [ -H value ]\t"},
{"heads", []string{"H"}, NewUintSlice(uint(2), uint(17179869184)),
"--heads value, -H value [ --heads value, -H value ]\t(default: 2, 17179869184)"},
}
func TestUintSliceFlagHelpOutput(t *testing.T) {
for _, test := range uintSliceFlagTests {
fl := UintSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value}
output := fl.String()
if output != test.expected {
t.Errorf("%q does not match %q", output, test.expected)
}
}
}
func TestUintSliceFlagWithEnvVarHelpOutput(t *testing.T) {
defer resetEnv(os.Environ())
os.Clearenv()
_ = os.Setenv("APP_SMURF", "42,17179869184")
for _, test := range uintSliceFlagTests {
fl := UintSliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}}
output := fl.String()
expectedSuffix := " [$APP_SMURF]"
if runtime.GOOS == "windows" {
expectedSuffix = " [%APP_SMURF%]"
}
if !strings.HasSuffix(output, expectedSuffix) {
t.Errorf("%q does not end with"+expectedSuffix, output)
}
}
}
func TestUintSliceFlagApply_SetsAllNames(t *testing.T) {
fl := UintSliceFlag{Name: "bits", Aliases: []string{"B", "bips"}}
set := flag.NewFlagSet("test", 0)
_ = fl.Apply(set)
err := set.Parse([]string{"--bits", "23", "-B", "3", "--bips", "99"})
expect(t, err, nil)
}
func TestUintSliceFlagApply_UsesEnvValues_noDefault(t *testing.T) {
defer resetEnv(os.Environ())
os.Clearenv()
_ = os.Setenv("MY_GOAT", "1 , 2")
var val UintSlice
fl := UintSliceFlag{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(), []uint(nil))
expect(t, set.Lookup("goat").Value.(*UintSlice).Value(), []uint{1, 2})
}
func TestUintSliceFlagApply_UsesEnvValues_withDefault(t *testing.T) {
defer resetEnv(os.Environ())
os.Clearenv()
_ = os.Setenv("MY_GOAT", "1 , 2")
val := NewUintSlice(3, 4)
fl := UintSliceFlag{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(), []uint{3, 4})
expect(t, set.Lookup("goat").Value.(*UintSlice).Value(), []uint{1, 2})
}
func TestUintSliceFlagApply_DefaultValueWithDestination(t *testing.T) {
defValue := []uint{1, 2}
fl := UintSliceFlag{Name: "country", Value: NewUintSlice(defValue...), Destination: NewUintSlice(3)}
set := flag.NewFlagSet("test", 0)
_ = fl.Apply(set)
err := set.Parse([]string{})
expect(t, err, nil)
expect(t, defValue, fl.Destination.Value())
}
func TestUintSliceFlagApply_ParentContext(t *testing.T) {
_ = (&App{
Flags: []Flag{
&UintSliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewUintSlice(1, 2, 3)},
},
Commands: []*Command{
{
Name: "child",
Action: func(ctx *Context) error {
expected := []uint{1, 2, 3}
if !reflect.DeepEqual(ctx.UintSlice("numbers"), expected) {
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.UintSlice("numbers"))
}
if !reflect.DeepEqual(ctx.UintSlice("n"), expected) {
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.UintSlice("n"))
}
return nil
},
},
},
}).Run([]string{"run", "child"})
}
func TestUintSliceFlag_SetFromParentContext(t *testing.T) {
fl := &UintSliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewUintSlice(1, 2, 3, 4)}
set := flag.NewFlagSet("test", 0)
_ = fl.Apply(set)
ctx := &Context{
parentContext: &Context{
flagSet: set,
},
flagSet: flag.NewFlagSet("empty", 0),
}
expected := []uint{1, 2, 3, 4}
if !reflect.DeepEqual(ctx.UintSlice("numbers"), expected) {
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.UintSlice("numbers"))
}
}
func TestUintSliceFlag_ReturnNil(t *testing.T) {
fl := &UintSliceFlag{}
set := flag.NewFlagSet("test", 0)
_ = fl.Apply(set)
ctx := &Context{
parentContext: &Context{
flagSet: set,
},
flagSet: flag.NewFlagSet("empty", 0),
}
expected := []uint(nil)
if !reflect.DeepEqual(ctx.UintSlice("numbers"), expected) {
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.UintSlice("numbers"))
}
}
var uint64SliceFlagTests = []struct {
name string
aliases []string
value *Uint64Slice
expected string
}{
{"heads", nil, NewUint64Slice(), "--heads value [ --heads value ]\t"},
{"H", nil, NewUint64Slice(), "-H value [ -H value ]\t"},
{"heads", []string{"H"}, NewUint64Slice(uint64(2), uint64(17179869184)),
"--heads value, -H value [ --heads value, -H value ]\t(default: 2, 17179869184)"},
}
func TestUint64SliceFlagHelpOutput(t *testing.T) {
for _, test := range uint64SliceFlagTests {
fl := Uint64SliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value}
output := fl.String()
if output != test.expected {
t.Errorf("%q does not match %q", output, test.expected)
}
}
}
func TestUint64SliceFlagWithEnvVarHelpOutput(t *testing.T) {
defer resetEnv(os.Environ())
os.Clearenv()
_ = os.Setenv("APP_SMURF", "42,17179869184")
for _, test := range uint64SliceFlagTests {
fl := Uint64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}}
output := fl.String()
expectedSuffix := " [$APP_SMURF]"
if runtime.GOOS == "windows" {
expectedSuffix = " [%APP_SMURF%]"
}
if !strings.HasSuffix(output, expectedSuffix) {
t.Errorf("%q does not end with"+expectedSuffix, output)
}
}
}
func TestUint64SliceFlagApply_SetsAllNames(t *testing.T) {
fl := Uint64SliceFlag{Name: "bits", Aliases: []string{"B", "bips"}}
set := flag.NewFlagSet("test", 0)
_ = fl.Apply(set)
err := set.Parse([]string{"--bits", "23", "-B", "3", "--bips", "99"})
expect(t, err, nil)
}
func TestUint64SliceFlagApply_UsesEnvValues_noDefault(t *testing.T) {
defer resetEnv(os.Environ())
os.Clearenv()
_ = os.Setenv("MY_GOAT", "1 , 2")
var val Uint64Slice
fl := Uint64SliceFlag{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(), []uint64(nil))
expect(t, set.Lookup("goat").Value.(*Uint64Slice).Value(), []uint64{1, 2})
}
func TestUint64SliceFlagApply_UsesEnvValues_withDefault(t *testing.T) {
defer resetEnv(os.Environ())
os.Clearenv()
_ = os.Setenv("MY_GOAT", "1 , 2")
val := NewUint64Slice(3, 4)
fl := Uint64SliceFlag{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(), []uint64{3, 4})
expect(t, set.Lookup("goat").Value.(*Uint64Slice).Value(), []uint64{1, 2})
}
func TestUint64SliceFlagApply_DefaultValueWithDestination(t *testing.T) {
defValue := []uint64{1, 2}
fl := Uint64SliceFlag{Name: "country", Value: NewUint64Slice(defValue...), Destination: NewUint64Slice(3)}
set := flag.NewFlagSet("test", 0)
_ = fl.Apply(set)
err := set.Parse([]string{})
expect(t, err, nil)
expect(t, defValue, fl.Destination.Value())
}
func TestUint64SliceFlagApply_ParentContext(t *testing.T) {
_ = (&App{
Flags: []Flag{
&Uint64SliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewUint64Slice(1, 2, 3)},
},
Commands: []*Command{
{
Name: "child",
Action: func(ctx *Context) error {
expected := []uint64{1, 2, 3}
if !reflect.DeepEqual(ctx.Uint64Slice("numbers"), expected) {
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Uint64Slice("numbers"))
}
if !reflect.DeepEqual(ctx.Uint64Slice("n"), expected) {
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Uint64Slice("n"))
}
return nil
},
},
},
}).Run([]string{"run", "child"})
}
func TestUint64SliceFlag_SetFromParentContext(t *testing.T) {
fl := &Uint64SliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewUint64Slice(1, 2, 3, 4)}
set := flag.NewFlagSet("test", 0)
_ = fl.Apply(set)
ctx := &Context{
parentContext: &Context{
flagSet: set,
},
flagSet: flag.NewFlagSet("empty", 0),
}
expected := []uint64{1, 2, 3, 4}
if !reflect.DeepEqual(ctx.Uint64Slice("numbers"), expected) {
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Uint64Slice("numbers"))
}
}
func TestUint64SliceFlag_ReturnNil(t *testing.T) {
fl := &Uint64SliceFlag{}
set := flag.NewFlagSet("test", 0)
_ = fl.Apply(set)
ctx := &Context{
parentContext: &Context{
flagSet: set,
},
flagSet: flag.NewFlagSet("empty", 0),
}
expected := []uint64(nil)
if !reflect.DeepEqual(ctx.Uint64Slice("numbers"), expected) {
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Uint64Slice("numbers"))
}
}
var float64FlagTests = []struct {
name string
expected string
@ -1155,10 +1641,10 @@ var float64SliceFlagTests = []struct {
value *Float64Slice
expected string
}{
{"heads", nil, NewFloat64Slice(), "--heads value\t(accepts multiple inputs)"},
{"H", nil, NewFloat64Slice(), "-H value\t(accepts multiple inputs)"},
{"heads", nil, NewFloat64Slice(), "--heads value [ --heads value ]\t"},
{"H", nil, NewFloat64Slice(), "-H value [ -H value ]\t"},
{"heads", []string{"H"}, NewFloat64Slice(0.1234, -10.5),
"--heads value, -H value\t(default: 0.1234, -10.5)\t(accepts multiple inputs)"},
"--heads value, -H value [ --heads value, -H value ]\t(default: 0.1234, -10.5)"},
}
func TestFloat64SliceFlagHelpOutput(t *testing.T) {
@ -1190,6 +1676,56 @@ func TestFloat64SliceFlagWithEnvVarHelpOutput(t *testing.T) {
}
}
func TestFloat64SliceFlagApply_SetsAllNames(t *testing.T) {
fl := Float64SliceFlag{Name: "bits", Aliases: []string{"B", "bips"}}
set := flag.NewFlagSet("test", 0)
_ = fl.Apply(set)
err := set.Parse([]string{"--bits", "23", "-B", "3", "--bips", "99"})
expect(t, err, nil)
}
func TestFloat64SliceFlagApply_UsesEnvValues_noDefault(t *testing.T) {
defer resetEnv(os.Environ())
os.Clearenv()
_ = os.Setenv("MY_GOAT", "1.0 , 2.0")
var val Float64Slice
fl := Float64SliceFlag{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(), []float64(nil))
expect(t, set.Lookup("goat").Value.(*Float64Slice).Value(), []float64{1, 2})
}
func TestFloat64SliceFlagApply_UsesEnvValues_withDefault(t *testing.T) {
defer resetEnv(os.Environ())
os.Clearenv()
_ = os.Setenv("MY_GOAT", "1.0 , 2.0")
val := NewFloat64Slice(3.0, 4.0)
fl := Float64SliceFlag{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(), []float64{3, 4})
expect(t, set.Lookup("goat").Value.(*Float64Slice).Value(), []float64{1, 2})
}
func TestFloat64SliceFlagApply_DefaultValueWithDestination(t *testing.T) {
defValue := []float64{1.0, 2.0}
fl := Float64SliceFlag{Name: "country", Value: NewFloat64Slice(defValue...), Destination: NewFloat64Slice(3)}
set := flag.NewFlagSet("test", 0)
_ = fl.Apply(set)
err := set.Parse([]string{})
expect(t, err, nil)
expect(t, defValue, fl.Destination.Value())
}
func TestFloat64SliceFlagValueFromContext(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Var(NewFloat64Slice(1.23, 4.56), "myflag", "doc")
@ -1198,6 +1734,29 @@ func TestFloat64SliceFlagValueFromContext(t *testing.T) {
expect(t, f.Get(ctx), []float64{1.23, 4.56})
}
func TestFloat64SliceFlagApply_ParentContext(t *testing.T) {
_ = (&App{
Flags: []Flag{
&Float64SliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewFloat64Slice(1.0, 2.0, 3.0)},
},
Commands: []*Command{
{
Name: "child",
Action: func(ctx *Context) error {
expected := []float64{1.0, 2.0, 3.0}
if !reflect.DeepEqual(ctx.Float64Slice("numbers"), expected) {
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Float64Slice("numbers"))
}
if !reflect.DeepEqual(ctx.Float64Slice("n"), expected) {
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Float64Slice("n"))
}
return nil
},
},
},
}).Run([]string{"run", "child"})
}
var genericFlagTests = []struct {
name string
value Generic
@ -2282,6 +2841,38 @@ func TestInt64Slice_Serialized_Set(t *testing.T) {
}
}
func TestUintSlice_Serialized_Set(t *testing.T) {
sl0 := NewUintSlice(1, 2)
ser0 := sl0.Serialize()
if len(ser0) < len(slPfx) {
t.Fatalf("serialized shorter than expected: %q", ser0)
}
sl1 := NewUintSlice(3, 4)
_ = sl1.Set(ser0)
if sl0.String() != sl1.String() {
t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1)
}
}
func TestUint64Slice_Serialized_Set(t *testing.T) {
sl0 := NewUint64Slice(1, 2)
ser0 := sl0.Serialize()
if len(ser0) < len(slPfx) {
t.Fatalf("serialized shorter than expected: %q", ser0)
}
sl1 := NewUint64Slice(3, 4)
_ = sl1.Set(ser0)
if sl0.String() != sl1.String() {
t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1)
}
}
func TestTimestamp_set(t *testing.T) {
ts := Timestamp{
timestamp: nil,
@ -2381,46 +2972,58 @@ type flagDefaultTestCase struct {
func TestFlagDefaultValue(t *testing.T) {
cases := []*flagDefaultTestCase{
{
name: "stringSclice",
name: "stringSlice",
flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")},
toParse: []string{"--flag", "parsed"},
expect: `--flag value (default: "default1", "default2") (accepts multiple inputs)`,
expect: `--flag value [ --flag value ] (default: "default1", "default2")`,
},
{
name: "float64Sclice",
name: "float64Slice",
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)`,
expect: `--flag value [ --flag value ] (default: 1.1, 2.2)`,
},
{
name: "int64Sclice",
name: "int64Slice",
flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2)},
toParse: []string{"--flag", "13"},
expect: `--flag value (default: 1, 2) (accepts multiple inputs)`,
expect: `--flag value [ --flag value ] (default: 1, 2)`,
},
{
name: "intSclice",
name: "intSlice",
flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)},
toParse: []string{"--flag", "13"},
expect: `--flag value (default: 1, 2) (accepts multiple inputs)`,
expect: `--flag value [ --flag value ] (default: 1, 2)`,
},
{
name: "uint64Slice",
flag: &Uint64SliceFlag{Name: "flag", Value: NewUint64Slice(1, 2)},
toParse: []string{"--flag", "13"},
expect: `--flag value [ --flag value ] (default: 1, 2)`,
},
{
name: "uintSlice",
flag: &UintSliceFlag{Name: "flag", Value: NewUintSlice(1, 2)},
toParse: []string{"--flag", "13"},
expect: `--flag value [ --flag value ] (default: 1, 2)`,
},
{
name: "string",
flag: &StringFlag{Name: "flag", Value: "default"},
toParse: []string{"--flag", "parsed"},
expect: `--flag value (default: "default")`,
expect: `--flag value (default: "default")`,
},
{
name: "bool",
flag: &BoolFlag{Name: "flag", Value: true},
toParse: []string{"--flag", "false"},
expect: `--flag (default: true)`,
expect: `--flag (default: true)`,
},
{
name: "uint64",
flag: &Uint64Flag{Name: "flag", Value: 1},
toParse: []string{"--flag", "13"},
expect: `--flag value (default: 1)`,
expect: `--flag value (default: 1)`,
},
}
for i, v := range cases {
@ -2446,29 +3049,41 @@ type flagValueTestCase struct {
func TestFlagValue(t *testing.T) {
cases := []*flagValueTestCase{
&flagValueTestCase{
name: "stringSclice",
name: "stringSlice",
flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")},
toParse: []string{"--flag", "parsed,parsed2", "--flag", "parsed3,parsed4"},
expect: `[parsed parsed2 parsed3 parsed4]`,
},
&flagValueTestCase{
name: "float64Sclice",
name: "float64Slice",
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",
name: "int64Slice",
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",
name: "intSlice",
flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)},
toParse: []string{"--flag", "13,14", "--flag", "15,16"},
expect: `[]int{13, 14, 15, 16}`,
},
&flagValueTestCase{
name: "uint64Slice",
flag: &Uint64SliceFlag{Name: "flag", Value: NewUint64Slice(1, 2)},
toParse: []string{"--flag", "13,14", "--flag", "15,16"},
expect: `[]uint64{13, 14, 15, 16}`,
},
&flagValueTestCase{
name: "uintSlice",
flag: &UintSliceFlag{Name: "flag", Value: NewUintSlice(1, 2)},
toParse: []string{"--flag", "13,14", "--flag", "15,16"},
expect: `[]uint{13, 14, 15, 16}`,
},
}
for i, v := range cases {
set := flag.NewFlagSet("test", 0)

View File

@ -75,7 +75,7 @@ func (t *Timestamp) Get() interface{} {
// 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 {
if f.Value != nil {
if f.Value != nil && f.Value.timestamp != nil {
return f.Value.timestamp.String()
}
return ""
@ -120,6 +120,15 @@ func (f *TimestampFlag) Get(ctx *Context) *time.Time {
return ctx.Timestamp(f.Name)
}
// RunAction executes flag action if set
func (f *TimestampFlag) RunAction(c *Context) error {
if f.Action != nil {
return f.Action(c, c.Timestamp(f.Name))
}
return nil
}
// Timestamp gets the timestamp from a flag name
func (cCtx *Context) Timestamp(name string) *time.Time {
if fs := cCtx.lookupFlagSet(name); fs != nil {

View File

@ -10,7 +10,7 @@ import (
func (f *UintFlag) Apply(set *flag.FlagSet) error {
if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
if val != "" {
valInt, err := strconv.ParseUint(val, 0, 64)
valInt, err := strconv.ParseUint(val, f.Base, 64)
if err != nil {
return fmt.Errorf("could not parse %q as uint value from %s for flag %s: %s", val, source, f.Name, err)
}
@ -42,6 +42,15 @@ func (f *UintFlag) Get(ctx *Context) uint {
return ctx.Uint(f.Name)
}
// RunAction executes flag action if set
func (f *UintFlag) RunAction(c *Context) error {
if f.Action != nil {
return f.Action(c, c.Uint(f.Name))
}
return nil
}
// Uint looks up the value of a local UintFlag, returns
// 0 if not found
func (cCtx *Context) Uint(name string) uint {

View File

@ -10,7 +10,7 @@ import (
func (f *Uint64Flag) Apply(set *flag.FlagSet) error {
if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found {
if val != "" {
valInt, err := strconv.ParseUint(val, 0, 64)
valInt, err := strconv.ParseUint(val, f.Base, 64)
if err != nil {
return fmt.Errorf("could not parse %q as uint64 value from %s for flag %s: %s", val, source, f.Name, err)
}
@ -42,6 +42,15 @@ func (f *Uint64Flag) Get(ctx *Context) uint64 {
return ctx.Uint64(f.Name)
}
// RunAction executes flag action if set
func (f *Uint64Flag) RunAction(c *Context) error {
if f.Action != nil {
return f.Action(c, c.Uint64(f.Name))
}
return nil
}
// Uint64 looks up the value of a local Uint64Flag, returns
// 0 if not found
func (cCtx *Context) Uint64(name string) uint64 {

212
flag_uint64_slice.go Normal file
View File

@ -0,0 +1,212 @@
package cli
import (
"encoding/json"
"flag"
"fmt"
"strconv"
"strings"
)
// Uint64Slice wraps []int64 to satisfy flag.Value
type Uint64Slice struct {
slice []uint64
hasBeenSet bool
}
// NewUint64Slice makes an *Uint64Slice with default values
func NewUint64Slice(defaults ...uint64) *Uint64Slice {
return &Uint64Slice{slice: append([]uint64{}, defaults...)}
}
// clone allocate a copy of self object
func (i *Uint64Slice) clone() *Uint64Slice {
n := &Uint64Slice{
slice: make([]uint64, len(i.slice)),
hasBeenSet: i.hasBeenSet,
}
copy(n.slice, i.slice)
return n
}
// Set parses the value into an integer and appends it to the list of values
func (i *Uint64Slice) Set(value string) error {
if !i.hasBeenSet {
i.slice = []uint64{}
i.hasBeenSet = true
}
if strings.HasPrefix(value, slPfx) {
// Deserializing assumes overwrite
_ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.slice)
i.hasBeenSet = true
return nil
}
for _, s := range flagSplitMultiValues(value) {
tmp, err := strconv.ParseUint(strings.TrimSpace(s), 0, 64)
if err != nil {
return err
}
i.slice = append(i.slice, tmp)
}
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (i *Uint64Slice) String() string {
v := i.slice
if v == nil {
// treat nil the same as zero length non-nil
v = make([]uint64, 0)
}
str := fmt.Sprintf("%d", v)
str = strings.Replace(str, " ", ", ", -1)
str = strings.Replace(str, "[", "{", -1)
str = strings.Replace(str, "]", "}", -1)
return fmt.Sprintf("[]uint64%s", str)
}
// Serialize allows Uint64Slice to fulfill Serializer
func (i *Uint64Slice) Serialize() string {
jsonBytes, _ := json.Marshal(i.slice)
return fmt.Sprintf("%s%s", slPfx, string(jsonBytes))
}
// Value returns the slice of ints set by this flag
func (i *Uint64Slice) Value() []uint64 {
return i.slice
}
// Get returns the slice of ints set by this flag
func (i *Uint64Slice) Get() interface{} {
return *i
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *Uint64SliceFlag) String() string {
return withEnvHint(f.GetEnvVars(), f.stringify())
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f *Uint64SliceFlag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f *Uint64SliceFlag) GetUsage() string {
return f.Usage
}
// GetCategory returns the category for the flag
func (f *Uint64SliceFlag) 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 *Uint64SliceFlag) GetValue() string {
if f.Value != nil {
return f.Value.String()
}
return ""
}
// GetDefaultText returns the default text for this flag
func (f *Uint64SliceFlag) GetDefaultText() string {
if f.DefaultText != "" {
return f.DefaultText
}
return f.GetValue()
}
// GetEnvVars returns the env vars for this flag
func (f *Uint64SliceFlag) GetEnvVars() []string {
return f.EnvVars
}
// Apply populates the flag given the flag set and environment
func (f *Uint64SliceFlag) Apply(set *flag.FlagSet) error {
// apply any default
if f.Destination != nil && f.Value != nil {
f.Destination.slice = make([]uint64, len(f.Value.slice))
copy(f.Destination.slice, f.Value.slice)
}
// resolve setValue (what we will assign to the set)
var setValue *Uint64Slice
switch {
case f.Destination != nil:
setValue = f.Destination
case f.Value != nil:
setValue = f.Value.clone()
default:
setValue = new(Uint64Slice)
}
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 uint64 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.
setValue.hasBeenSet = false
f.HasBeenSet = true
}
for _, name := range f.Names() {
set.Var(setValue, name, f.Usage)
}
return nil
}
// Get returns the flags value in the given Context.
func (f *Uint64SliceFlag) Get(ctx *Context) []uint64 {
return ctx.Uint64Slice(f.Name)
}
func (f *Uint64SliceFlag) stringify() string {
var defaultVals []string
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, i := range f.Value.Value() {
defaultVals = append(defaultVals, strconv.FormatUint(i, 10))
}
}
return stringifySliceFlag(f.Usage, f.Names(), defaultVals)
}
// RunAction executes flag action if set
func (f *Uint64SliceFlag) RunAction(c *Context) error {
if f.Action != nil {
return f.Action(c, c.Uint64Slice(f.Name))
}
return nil
}
// Uint64Slice looks up the value of a local Uint64SliceFlag, returns
// nil if not found
func (cCtx *Context) Uint64Slice(name string) []uint64 {
if fs := cCtx.lookupFlagSet(name); fs != nil {
return lookupUint64Slice(name, fs)
}
return nil
}
func lookupUint64Slice(name string, set *flag.FlagSet) []uint64 {
f := set.Lookup(name)
if f != nil {
if slice, ok := unwrapFlagValue(f.Value).(*Uint64Slice); ok {
return slice.Value()
}
}
return nil
}

223
flag_uint_slice.go Normal file
View File

@ -0,0 +1,223 @@
package cli
import (
"encoding/json"
"flag"
"fmt"
"strconv"
"strings"
)
// UintSlice wraps []int to satisfy flag.Value
type UintSlice struct {
slice []uint
hasBeenSet bool
}
// NewUintSlice makes an *UintSlice with default values
func NewUintSlice(defaults ...uint) *UintSlice {
return &UintSlice{slice: append([]uint{}, defaults...)}
}
// clone allocate a copy of self object
func (i *UintSlice) clone() *UintSlice {
n := &UintSlice{
slice: make([]uint, len(i.slice)),
hasBeenSet: i.hasBeenSet,
}
copy(n.slice, i.slice)
return n
}
// TODO: Consistently have specific Set function for Int64 and Float64 ?
// SetInt directly adds an integer to the list of values
func (i *UintSlice) SetUint(value uint) {
if !i.hasBeenSet {
i.slice = []uint{}
i.hasBeenSet = true
}
i.slice = append(i.slice, value)
}
// Set parses the value into an integer and appends it to the list of values
func (i *UintSlice) Set(value string) error {
if !i.hasBeenSet {
i.slice = []uint{}
i.hasBeenSet = true
}
if strings.HasPrefix(value, slPfx) {
// Deserializing assumes overwrite
_ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.slice)
i.hasBeenSet = true
return nil
}
for _, s := range flagSplitMultiValues(value) {
tmp, err := strconv.ParseUint(strings.TrimSpace(s), 0, 32)
if err != nil {
return err
}
i.slice = append(i.slice, uint(tmp))
}
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (i *UintSlice) String() string {
v := i.slice
if v == nil {
// treat nil the same as zero length non-nil
v = make([]uint, 0)
}
str := fmt.Sprintf("%d", v)
str = strings.Replace(str, " ", ", ", -1)
str = strings.Replace(str, "[", "{", -1)
str = strings.Replace(str, "]", "}", -1)
return fmt.Sprintf("[]uint%s", str)
}
// Serialize allows UintSlice to fulfill Serializer
func (i *UintSlice) Serialize() string {
jsonBytes, _ := json.Marshal(i.slice)
return fmt.Sprintf("%s%s", slPfx, string(jsonBytes))
}
// Value returns the slice of ints set by this flag
func (i *UintSlice) Value() []uint {
return i.slice
}
// Get returns the slice of ints set by this flag
func (i *UintSlice) Get() interface{} {
return *i
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *UintSliceFlag) String() string {
return withEnvHint(f.GetEnvVars(), f.stringify())
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f *UintSliceFlag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f *UintSliceFlag) GetUsage() string {
return f.Usage
}
// GetCategory returns the category for the flag
func (f *UintSliceFlag) 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 *UintSliceFlag) GetValue() string {
if f.Value != nil {
return f.Value.String()
}
return ""
}
// GetDefaultText returns the default text for this flag
func (f *UintSliceFlag) GetDefaultText() string {
if f.DefaultText != "" {
return f.DefaultText
}
return f.GetValue()
}
// GetEnvVars returns the env vars for this flag
func (f *UintSliceFlag) GetEnvVars() []string {
return f.EnvVars
}
// Apply populates the flag given the flag set and environment
func (f *UintSliceFlag) Apply(set *flag.FlagSet) error {
// apply any default
if f.Destination != nil && f.Value != nil {
f.Destination.slice = make([]uint, len(f.Value.slice))
copy(f.Destination.slice, f.Value.slice)
}
// resolve setValue (what we will assign to the set)
var setValue *UintSlice
switch {
case f.Destination != nil:
setValue = f.Destination
case f.Value != nil:
setValue = f.Value.clone()
default:
setValue = new(UintSlice)
}
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 uint 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.
setValue.hasBeenSet = false
f.HasBeenSet = true
}
for _, name := range f.Names() {
set.Var(setValue, name, f.Usage)
}
return nil
}
// Get returns the flags value in the given Context.
func (f *UintSliceFlag) Get(ctx *Context) []uint {
return ctx.UintSlice(f.Name)
}
func (f *UintSliceFlag) stringify() string {
var defaultVals []string
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, i := range f.Value.Value() {
defaultVals = append(defaultVals, strconv.FormatUint(uint64(i), 10))
}
}
return stringifySliceFlag(f.Usage, f.Names(), defaultVals)
}
// RunAction executes flag action if set
func (f *UintSliceFlag) RunAction(c *Context) error {
if f.Action != nil {
return f.Action(c, c.UintSlice(f.Name))
}
return nil
}
// UintSlice looks up the value of a local UintSliceFlag, returns
// nil if not found
func (cCtx *Context) UintSlice(name string) []uint {
if fs := cCtx.lookupFlagSet(name); fs != nil {
return lookupUintSlice(name, fs)
}
return nil
}
func lookupUintSlice(name string, set *flag.FlagSet) []uint {
f := set.Lookup(name)
if f != nil {
if slice, ok := unwrapFlagValue(f.Value).(*UintSlice); ok {
return slice.Value()
}
}
return nil
}

View File

@ -23,6 +23,9 @@ type CommandNotFoundFunc func(*Context, string)
// is displayed and the execution is interrupted.
type OnUsageErrorFunc func(cCtx *Context, err error, isSubcommand bool) error
// InvalidFlagAccessFunc is executed when an invalid flag is accessed from the context.
type InvalidFlagAccessFunc func(*Context, string)
// ExitErrHandlerFunc is executed if provided in order to handle exitError values
// returned by Actions and Before/After functions.
type ExitErrHandlerFunc func(cCtx *Context, err error)

6
go.mod
View File

@ -6,8 +6,10 @@ require (
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
require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect
golang.org/x/text v0.3.7 // indirect
)

View File

@ -5,24 +5,24 @@ line Go applications. cli is designed to be easy to understand and write,
the most simple cli application can be written as follows:
func main() {
(&cli.App{}).Run(os.Args)
(&cli.App{}).Run(os.Args)
}
Of course this application does not do much, so let's make this an actual
application:
func main() {
app := &cli.App{
Name: "greet",
Usage: "say a greeting",
Action: func(c *cli.Context) error {
fmt.Println("Greetings")
return nil
},
}
func main() {
app := &cli.App{
Name: "greet",
Usage: "say a greeting",
Action: func(c *cli.Context) error {
fmt.Println("Greetings")
return nil
},
}
app.Run(os.Args)
}
app.Run(os.Args)
}
VARIABLES
@ -49,8 +49,8 @@ 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 .VisibleFlagCategories}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}}
{{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}}
GLOBAL OPTIONS:{{range .VisibleFlagCategories}}
{{if .Name}}{{.Name}}
@ -82,12 +82,10 @@ DESCRIPTION:
OPTIONS:{{range .VisibleFlagCategories}}
{{if .Name}}{{.Name}}
{{end}}{{range .Flags}}{{.}}
{{end}}{{end}}{{else}}{{if .VisibleFlags}}
{{end}}{{range .Flags}}{{.}}{{end}}{{end}}{{else}}{{if .VisibleFlags}}
OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}{{end}}
{{range .VisibleFlags}}{{.}}{{end}}{{end}}{{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
@ -157,12 +155,11 @@ DESCRIPTION:
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}}
{{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
{{range .VisibleFlags}}{{.}}{{end}}{{end}}
`
SubcommandHelpTemplate is the text template for the subcommand help topic.
cli.go uses text/template to render templates. You can render custom help
@ -300,6 +297,8 @@ type App struct {
CommandNotFound CommandNotFoundFunc
// Execute this function if a usage error occurs
OnUsageError OnUsageErrorFunc
// Execute this function when an invalid flag is accessed from the context
InvalidFlagAccessHandler InvalidFlagAccessFunc
// Compilation date
Compiled time.Time
// List of all authors who contributed
@ -450,6 +449,10 @@ type BoolFlag struct {
Aliases []string
EnvVars []string
Count *int
Action func(*Context, bool) error
}
BoolFlag is a flag with type bool
@ -487,6 +490,9 @@ func (f *BoolFlag) IsVisible() bool
func (f *BoolFlag) Names() []string
Names returns the names of the flag
func (f *BoolFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *BoolFlag) String() string
String returns a readable representation of this value (for usage defaults)
@ -621,6 +627,9 @@ func (cCtx *Context) Args() Args
func (cCtx *Context) Bool(name string) bool
Bool looks up the value of a local BoolFlag, returns false if not found
func (cCtx *Context) Count(name string) int
Count returns the num of occurences of this flag
func (cCtx *Context) Duration(name string) time.Duration
Duration looks up the value of a local DurationFlag, returns 0 if not found
@ -690,9 +699,23 @@ func (cCtx *Context) Uint(name string) uint
func (cCtx *Context) Uint64(name string) uint64
Uint64 looks up the value of a local Uint64Flag, returns 0 if not found
func (cCtx *Context) Uint64Slice(name string) []uint64
Uint64Slice looks up the value of a local Uint64SliceFlag, returns nil if
not found
func (cCtx *Context) UintSlice(name string) []uint
UintSlice looks up the value of a local UintSliceFlag, returns nil if not
found
func (cCtx *Context) Value(name string) interface{}
Value returns the value of the flag corresponding to `name`
type Countable interface {
Count() int
}
Countable is an interface to enable detection of flag values which support
repetitive flags
type DurationFlag struct {
Name string
@ -710,6 +733,8 @@ type DurationFlag struct {
Aliases []string
EnvVars []string
Action func(*Context, time.Duration) error
}
DurationFlag is a flag with type time.Duration
@ -747,6 +772,9 @@ func (f *DurationFlag) IsVisible() bool
func (f *DurationFlag) Names() []string
Names returns the names of the flag
func (f *DurationFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *DurationFlag) String() string
String returns a readable representation of this value (for usage defaults)
@ -820,6 +848,8 @@ type Flag interface {
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
GetValue() string
RunAction(*Context) error
}
Flag is a common interface related to parsing flags in cli. For more
advanced flag parsing techniques, it is recommended that this interface be
@ -913,6 +943,8 @@ type Float64Flag struct {
Aliases []string
EnvVars []string
Action func(*Context, float64) error
}
Float64Flag is a flag with type float64
@ -950,6 +982,9 @@ func (f *Float64Flag) IsVisible() bool
func (f *Float64Flag) Names() []string
Names returns the names of the flag
func (f *Float64Flag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *Float64Flag) String() string
String returns a readable representation of this value (for usage defaults)
@ -996,6 +1031,8 @@ type Float64SliceFlag struct {
Aliases []string
EnvVars []string
Action func(*Context, []float64) error
}
Float64SliceFlag is a flag with type *Float64Slice
@ -1035,6 +1072,9 @@ func (f *Float64SliceFlag) IsVisible() bool
func (f *Float64SliceFlag) Names() []string
Names returns the names of the flag
func (f *Float64SliceFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *Float64SliceFlag) SetDestination(slice []float64)
func (f *Float64SliceFlag) SetValue(slice []float64)
@ -1064,16 +1104,18 @@ type GenericFlag struct {
HasBeenSet bool
Value Generic
Destination *Generic
Destination Generic
Aliases []string
EnvVars []string
TakesFile bool
Action func(*Context, interface{}) error
}
GenericFlag is a flag with type Generic
func (f GenericFlag) Apply(set *flag.FlagSet) error
func (f *GenericFlag) Apply(set *flag.FlagSet) error
Apply takes the flagset and calls Set on the generic flag with the value
provided by the user for parsing by the flag
@ -1108,6 +1150,9 @@ func (f *GenericFlag) IsVisible() bool
func (f *GenericFlag) Names() []string
Names returns the names of the flag
func (f *GenericFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *GenericFlag) String() string
String returns a readable representation of this value (for usage defaults)
@ -1131,6 +1176,10 @@ type Int64Flag struct {
Aliases []string
EnvVars []string
Base int
Action func(*Context, int64) error
}
Int64Flag is a flag with type int64
@ -1168,6 +1217,9 @@ func (f *Int64Flag) IsVisible() bool
func (f *Int64Flag) Names() []string
Names returns the names of the flag
func (f *Int64Flag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *Int64Flag) String() string
String returns a readable representation of this value (for usage defaults)
@ -1214,6 +1266,8 @@ type Int64SliceFlag struct {
Aliases []string
EnvVars []string
Action func(*Context, []int64) error
}
Int64SliceFlag is a flag with type *Int64Slice
@ -1253,6 +1307,9 @@ func (f *Int64SliceFlag) IsVisible() bool
func (f *Int64SliceFlag) Names() []string
Names returns the names of the flag
func (f *Int64SliceFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *Int64SliceFlag) SetDestination(slice []int64)
func (f *Int64SliceFlag) SetValue(slice []int64)
@ -1280,6 +1337,10 @@ type IntFlag struct {
Aliases []string
EnvVars []string
Base int
Action func(*Context, int) error
}
IntFlag is a flag with type int
@ -1317,6 +1378,9 @@ func (f *IntFlag) IsVisible() bool
func (f *IntFlag) Names() []string
Names returns the names of the flag
func (f *IntFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *IntFlag) String() string
String returns a readable representation of this value (for usage defaults)
@ -1367,6 +1431,8 @@ type IntSliceFlag struct {
Aliases []string
EnvVars []string
Action func(*Context, []int) error
}
IntSliceFlag is a flag with type *IntSlice
@ -1406,6 +1472,9 @@ func (f *IntSliceFlag) IsVisible() bool
func (f *IntSliceFlag) Names() []string
Names returns the names of the flag
func (f *IntSliceFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *IntSliceFlag) SetDestination(slice []int)
func (f *IntSliceFlag) SetValue(slice []int)
@ -1416,6 +1485,10 @@ func (f *IntSliceFlag) String() string
func (f *IntSliceFlag) TakesValue() bool
TakesValue returns true if the flag takes a value, otherwise false
type InvalidFlagAccessFunc func(*Context, string)
InvalidFlagAccessFunc is executed when an invalid flag is accessed from the
context.
type MultiError interface {
error
Errors() []error
@ -1465,6 +1538,8 @@ type PathFlag struct {
EnvVars []string
TakesFile bool
Action func(*Context, Path) error
}
PathFlag is a flag with type Path
@ -1502,6 +1577,9 @@ func (f *PathFlag) IsVisible() bool
func (f *PathFlag) Names() []string
Names returns the names of the flag
func (f *PathFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *PathFlag) String() string
String returns a readable representation of this value (for usage defaults)
@ -1545,6 +1623,8 @@ func (x *SliceFlag[T, S, E]) IsVisible() bool
func (x *SliceFlag[T, S, E]) Names() []string
func (x *SliceFlag[T, S, E]) RunAction(c *Context) error
func (x *SliceFlag[T, S, E]) SetDestination(slice S)
func (x *SliceFlag[T, S, E]) SetValue(slice S)
@ -1589,6 +1669,8 @@ type StringFlag struct {
EnvVars []string
TakesFile bool
Action func(*Context, string) error
}
StringFlag is a flag with type string
@ -1626,6 +1708,9 @@ func (f *StringFlag) IsVisible() bool
func (f *StringFlag) Names() []string
Names returns the names of the flag
func (f *StringFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *StringFlag) String() string
String returns a readable representation of this value (for usage defaults)
@ -1674,6 +1759,8 @@ type StringSliceFlag struct {
EnvVars []string
TakesFile bool
Action func(*Context, []string) error
}
StringSliceFlag is a flag with type *StringSlice
@ -1713,6 +1800,9 @@ func (f *StringSliceFlag) IsVisible() bool
func (f *StringSliceFlag) Names() []string
Names returns the names of the flag
func (f *StringSliceFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *StringSliceFlag) SetDestination(slice []string)
func (f *StringSliceFlag) SetValue(slice []string)
@ -1777,6 +1867,8 @@ type TimestampFlag struct {
Layout string
Timezone *time.Location
Action func(*Context, *time.Time) error
}
TimestampFlag is a flag with type *Timestamp
@ -1814,6 +1906,9 @@ func (f *TimestampFlag) IsVisible() bool
func (f *TimestampFlag) Names() []string
Names returns the names of the flag
func (f *TimestampFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *TimestampFlag) String() string
String returns a readable representation of this value (for usage defaults)
@ -1837,6 +1932,10 @@ type Uint64Flag struct {
Aliases []string
EnvVars []string
Base int
Action func(*Context, uint64) error
}
Uint64Flag is a flag with type uint64
@ -1874,12 +1973,103 @@ func (f *Uint64Flag) IsVisible() bool
func (f *Uint64Flag) Names() []string
Names returns the names of the flag
func (f *Uint64Flag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *Uint64Flag) String() string
String returns a readable representation of this value (for usage defaults)
func (f *Uint64Flag) TakesValue() bool
TakesValue returns true if the flag takes a value, otherwise false
type Uint64Slice struct {
// Has unexported fields.
}
Uint64Slice wraps []int64 to satisfy flag.Value
func NewUint64Slice(defaults ...uint64) *Uint64Slice
NewUint64Slice makes an *Uint64Slice with default values
func (i *Uint64Slice) Get() interface{}
Get returns the slice of ints set by this flag
func (i *Uint64Slice) Serialize() string
Serialize allows Uint64Slice to fulfill Serializer
func (i *Uint64Slice) Set(value string) error
Set parses the value into an integer and appends it to the list of values
func (i *Uint64Slice) String() string
String returns a readable representation of this value (for usage defaults)
func (i *Uint64Slice) Value() []uint64
Value returns the slice of ints set by this flag
type Uint64SliceFlag struct {
Name string
Category string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value *Uint64Slice
Destination *Uint64Slice
Aliases []string
EnvVars []string
Action func(*Context, []uint64) error
}
Uint64SliceFlag is a flag with type *Uint64Slice
func (f *Uint64SliceFlag) Apply(set *flag.FlagSet) error
Apply populates the flag given the flag set and environment
func (f *Uint64SliceFlag) Get(ctx *Context) []uint64
Get returns the flags value in the given Context.
func (f *Uint64SliceFlag) GetCategory() string
GetCategory returns the category for the flag
func (f *Uint64SliceFlag) GetDefaultText() string
GetDefaultText returns the default text for this flag
func (f *Uint64SliceFlag) GetEnvVars() []string
GetEnvVars returns the env vars for this flag
func (f *Uint64SliceFlag) GetUsage() string
GetUsage returns the usage string for the flag
func (f *Uint64SliceFlag) GetValue() string
GetValue returns the flags value as string representation and an empty
string if the flag takes no value at all.
func (f *Uint64SliceFlag) IsRequired() bool
IsRequired returns whether or not the flag is required
func (f *Uint64SliceFlag) IsSet() bool
IsSet returns whether or not the flag has been set through env or file
func (f *Uint64SliceFlag) IsVisible() bool
IsVisible returns true if the flag is not hidden, otherwise false
func (f *Uint64SliceFlag) Names() []string
Names returns the names of the flag
func (f *Uint64SliceFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *Uint64SliceFlag) String() string
String returns a readable representation of this value (for usage defaults)
func (f *Uint64SliceFlag) TakesValue() bool
TakesValue returns true of the flag takes a value, otherwise false
type UintFlag struct {
Name string
@ -1897,6 +2087,10 @@ type UintFlag struct {
Aliases []string
EnvVars []string
Base int
Action func(*Context, uint) error
}
UintFlag is a flag with type uint
@ -1934,12 +2128,107 @@ func (f *UintFlag) IsVisible() bool
func (f *UintFlag) Names() []string
Names returns the names of the flag
func (f *UintFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *UintFlag) String() string
String returns a readable representation of this value (for usage defaults)
func (f *UintFlag) TakesValue() bool
TakesValue returns true if the flag takes a value, otherwise false
type UintSlice struct {
// Has unexported fields.
}
UintSlice wraps []int to satisfy flag.Value
func NewUintSlice(defaults ...uint) *UintSlice
NewUintSlice makes an *UintSlice with default values
func (i *UintSlice) Get() interface{}
Get returns the slice of ints set by this flag
func (i *UintSlice) Serialize() string
Serialize allows UintSlice to fulfill Serializer
func (i *UintSlice) Set(value string) error
Set parses the value into an integer and appends it to the list of values
func (i *UintSlice) SetUint(value uint)
TODO: Consistently have specific Set function for Int64 and Float64 ? SetInt
directly adds an integer to the list of values
func (i *UintSlice) String() string
String returns a readable representation of this value (for usage defaults)
func (i *UintSlice) Value() []uint
Value returns the slice of ints set by this flag
type UintSliceFlag struct {
Name string
Category string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value *UintSlice
Destination *UintSlice
Aliases []string
EnvVars []string
Action func(*Context, []uint) error
}
UintSliceFlag is a flag with type *UintSlice
func (f *UintSliceFlag) Apply(set *flag.FlagSet) error
Apply populates the flag given the flag set and environment
func (f *UintSliceFlag) Get(ctx *Context) []uint
Get returns the flags value in the given Context.
func (f *UintSliceFlag) GetCategory() string
GetCategory returns the category for the flag
func (f *UintSliceFlag) GetDefaultText() string
GetDefaultText returns the default text for this flag
func (f *UintSliceFlag) GetEnvVars() []string
GetEnvVars returns the env vars for this flag
func (f *UintSliceFlag) GetUsage() string
GetUsage returns the usage string for the flag
func (f *UintSliceFlag) GetValue() string
GetValue returns the flags value as string representation and an empty
string if the flag takes no value at all.
func (f *UintSliceFlag) IsRequired() bool
IsRequired returns whether or not the flag is required
func (f *UintSliceFlag) IsSet() bool
IsSet returns whether or not the flag has been set through env or file
func (f *UintSliceFlag) IsVisible() bool
IsVisible returns true if the flag is not hidden, otherwise false
func (f *UintSliceFlag) Names() []string
Names returns the names of the flag
func (f *UintSliceFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *UintSliceFlag) String() string
String returns a readable representation of this value (for usage defaults)
func (f *UintSliceFlag) TakesValue() bool
TakesValue returns true of the flag takes a value, otherwise false
type VisibleFlagCategory interface {
// Name returns the category name string
Name() string

113
help.go
View File

@ -15,6 +15,15 @@ const (
helpAlias = "h"
)
// this instance is to avoid recursion in the ShowCommandHelp which can
// add a help command again
var helpCommandDontUse = &Command{
Name: helpName,
Aliases: []string{helpAlias},
Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]",
}
var helpCommand = &Command{
Name: helpName,
Aliases: []string{helpAlias},
@ -22,26 +31,43 @@ var helpCommand = &Command{
ArgsUsage: "[command]",
Action: func(cCtx *Context) error {
args := cCtx.Args()
if args.Present() {
return ShowCommandHelp(cCtx, args.First())
argsPresent := args.First() != ""
firstArg := args.First()
// This action can be triggered by a "default" action of a command
// or via cmd.Run when cmd == helpCmd. So we have following possibilities
//
// 1 $ app
// 2 $ app help
// 3 $ app foo
// 4 $ app help foo
// 5 $ app foo help
// Case 4. when executing a help command set the context to parent
// to allow resolution of subsequent args. This will transform
// $ app help foo
// to
// $ app foo
// which will then be handled as case 3
if cCtx.Command.Name == helpName || cCtx.Command.Name == helpAlias {
cCtx = cCtx.parentContext
}
_ = ShowAppHelp(cCtx)
return nil
},
}
var helpSubcommand = &Command{
Name: helpName,
Aliases: []string{helpAlias},
Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]",
Action: func(cCtx *Context) error {
args := cCtx.Args()
if args.Present() {
return ShowCommandHelp(cCtx, args.First())
// Case 4. $ app hello foo
// foo is the command for which help needs to be shown
if argsPresent {
return ShowCommandHelp(cCtx, firstArg)
}
// Case 1 & 2
// Special case when running help on main app itself as opposed to indivdual
// commands/subcommands
if cCtx.parentContext.App == nil {
_ = ShowAppHelp(cCtx)
return nil
}
// Case 3, 5
return ShowSubcommandHelp(cCtx)
},
}
@ -212,9 +238,19 @@ func ShowCommandHelp(ctx *Context, command string) error {
for _, c := range ctx.App.Commands {
if c.HasName(command) {
if !ctx.App.HideHelpCommand && !c.HasName(helpName) && len(c.Subcommands) != 0 {
c.Subcommands = append(c.Subcommands, helpCommandDontUse)
}
if !ctx.App.HideHelp && HelpFlag != nil {
c.appendFlag(HelpFlag)
}
templ := c.CustomHelpTemplate
if templ == "" {
templ = CommandHelpTemplate
if len(c.Subcommands) == 0 {
templ = CommandHelpTemplate
} else {
templ = SubcommandHelpTemplate
}
}
HelpPrinter(ctx.App.Writer, templ, c)
@ -295,12 +331,14 @@ func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs
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,
"join": strings.Join,
"subtract": subtract,
"indent": indent,
"nindent": nindent,
"trim": strings.TrimSpace,
"wrap": func(input string, offset int) string { return wrap(input, offset, maxLineLength) },
"offset": offset,
"offsetCommands": offsetCommands,
}
if customFuncs["wrapAt"] != nil {
@ -416,6 +454,10 @@ func checkCommandCompletions(c *Context, name string) bool {
return true
}
func subtract(a, b int) int {
return a - b
}
func indent(spaces int, v string) string {
pad := strings.Repeat(" ", spaces)
return pad + strings.Replace(v, "\n", "\n"+pad, -1)
@ -476,3 +518,28 @@ func wrapLine(input string, offset int, wrapAt int, padding string) string {
func offset(input string, fixed int) int {
return len(input) + fixed
}
// this function tries to find the max width of the names column
// so say we have the following rows for help
//
// foo1, foo2, foo3 some string here
// bar1, b2 some other string here
//
// We want to offset the 2nd row usage by some amount so that everything
// is aligned
//
// foo1, foo2, foo3 some string here
// bar1, b2 some other string here
//
// to find that offset we find the length of all the rows and use the max
// to calculate the offset
func offsetCommands(cmds []*Command, fixed int) int {
var max int = 0
for _, cmd := range cmds {
s := strings.Join(cmd.Names(), ", ")
if len(s) > max {
max = len(s)
}
}
return max + fixed
}

View File

@ -186,7 +186,7 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) {
c := NewContext(app, set, nil)
err := helpSubcommand.Action(c)
err := helpCommand.Action(c)
if err == nil {
t.Fatalf("expected error from helpCommand.Action(), but got nil")
@ -248,7 +248,7 @@ func TestShowCommandHelp_HelpPrinter(t *testing.T) {
fmt.Fprint(w, "yo")
},
command: "",
wantTemplate: SubcommandHelpTemplate,
wantTemplate: AppHelpTemplate,
wantOutput: "yo",
},
{
@ -333,7 +333,7 @@ func TestShowCommandHelp_HelpPrinterCustom(t *testing.T) {
fmt.Fprint(w, "yo")
},
command: "",
wantTemplate: SubcommandHelpTemplate,
wantTemplate: AppHelpTemplate,
wantOutput: "yo",
},
{
@ -428,6 +428,58 @@ func TestShowCommandHelp_CommandAliases(t *testing.T) {
}
}
func TestHelpNameConsistency(t *testing.T) {
// Setup some very basic templates based on actual AppHelp, CommandHelp
// and SubcommandHelp templates to display the help name
// The inconsistency shows up when users use NewApp() as opposed to
// using App{...} directly
SubcommandHelpTemplate = `{{.HelpName}}`
app := NewApp()
app.Name = "bar"
app.CustomAppHelpTemplate = `{{.HelpName}}`
app.Commands = []*Command{
{
Name: "command1",
CustomHelpTemplate: `{{.HelpName}}`,
Subcommands: []*Command{
{
Name: "subcommand1",
CustomHelpTemplate: `{{.HelpName}}`,
},
},
},
}
tests := []struct {
name string
args []string
}{
{
name: "App help",
args: []string{"foo"},
},
{
name: "Command help",
args: []string{"foo", "command1"},
},
{
name: "Subcommand help",
args: []string{"foo", "command1", "subcommand1"},
},
}
for _, tt := range tests {
output := &bytes.Buffer{}
app.Writer = output
if err := app.Run(tt.args); err != nil {
t.Error(err)
}
if !strings.Contains(output.String(), "bar") {
t.Errorf("expected output to contain bar; got: %q", output.String())
}
}
}
func TestShowSubcommandHelp_CommandAliases(t *testing.T) {
app := &App{
Commands: []*Command{
@ -895,6 +947,42 @@ App UsageText`,
}
}
func TestShowAppHelp_CommandMultiLine_UsageText(t *testing.T) {
app := &App{
UsageText: `This is a
multi
line
App UsageText`,
Commands: []*Command{
{
Name: "frobbly",
Aliases: []string{"frb1", "frbb2", "frl2"},
Usage: "this is a long help output for the run command, long usage \noutput, long usage output, long usage output, long usage output\noutput, long usage output, long usage output",
},
{
Name: "grobbly",
Aliases: []string{"grb1", "grbb2"},
Usage: "this is another long help output for the run command, long usage \noutput, long usage output",
},
},
}
output := &bytes.Buffer{}
app.Writer = output
_ = app.Run([]string{"foo"})
expected := "COMMANDS:\n" +
" frobbly, frb1, frbb2, frl2 this is a long help output for the run command, long usage \n" +
" output, long usage output, long usage output, long usage output\n" +
" output, long usage output, long usage output\n" +
" grobbly, grb1, grbb2 this is another long help output for the run command, long usage \n" +
" output, long usage output"
if !strings.Contains(output.String(), expected) {
t.Errorf("expected output to include usage text; got: %q", output.String())
}
}
func TestHideHelpCommand(t *testing.T) {
app := &App{
HideHelpCommand: true,
@ -1269,10 +1357,13 @@ DESCRIPTION:
and a description long
enough to wrap in this test
case
OPTIONS:
--help, -h show help (default: false)
`
if output.String() != expected {
t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s",
t.Errorf("Unexpected wrapping, got:\n%s\nexpected:\n%s",
output.String(), expected)
}
}
@ -1338,7 +1429,6 @@ USAGE:
OPTIONS:
--help, -h show help (default: false)
`
if output.String() != expected {

View File

@ -5,12 +5,17 @@ package main
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"log"
"math"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/urfave/cli/v3"
@ -21,6 +26,8 @@ const (
goodNewsEmoji = "✨"
checksPassedEmoji = "✅"
gfmrunVersion = "v1.3.0"
v2diffWarning = `
# The unified diff above indicates that the public API surface area
# has changed. If you feel that the changes are acceptable and adhere
@ -45,56 +52,112 @@ func main() {
log.Fatal(err)
}
app := cli.NewApp()
app.Name = "builder"
app.Usage = "Generates a new urfave/cli build!"
app.Commands = cli.Commands{
{
Name: "vet",
Action: VetActionFunc,
},
{
Name: "test",
Action: TestActionFunc,
},
{
Name: "gfmrun",
Action: GfmrunActionFunc,
},
{
Name: "check-binary-size",
Action: checkBinarySizeActionFunc,
},
{
Name: "generate",
Action: GenerateActionFunc,
},
{
Name: "v2diff",
Flags: []cli.Flag{
&cli.BoolFlag{Name: "color", Value: false},
app := &cli.App{
Name: "builder",
Usage: "Do a thing for urfave/cli! (maybe build?)",
Commands: cli.Commands{
{
Name: "vet",
Action: topRunAction("go", "vet", "./..."),
},
{
Name: "test",
Action: TestActionFunc,
},
{
Name: "gfmrun",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "walk",
Value: false,
Usage: "Walk the specified directory and perform validation on all markdown files",
},
},
Action: GfmrunActionFunc,
},
{
Name: "check-binary-size",
Action: checkBinarySizeActionFunc,
},
{
Name: "generate",
Action: GenerateActionFunc,
},
{
Name: "yamlfmt",
Flags: []cli.Flag{
&cli.BoolFlag{Name: "strict", Value: false, Usage: "require presence of yq"},
},
Action: YAMLFmtActionFunc,
},
{
Name: "diffcheck",
Action: DiffCheckActionFunc,
},
{
Name: "ensure-goimports",
Action: EnsureGoimportsActionFunc,
},
{
Name: "ensure-gfmrun",
Action: EnsureGfmrunActionFunc,
},
{
Name: "ensure-mkdocs",
Action: EnsureMkdocsActionFunc,
Flags: []cli.Flag{
&cli.BoolFlag{Name: "upgrade-pip"},
},
},
{
Name: "set-mkdocs-remote",
Action: SetMkdocsRemoteActionFunc,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "github-token",
EnvVars: []string{"MKDOCS_REMOTE_GITHUB_TOKEN"},
Required: true,
},
},
},
{
Name: "deploy-mkdocs",
Action: topRunAction("mkdocs", "gh-deploy", "--force"),
},
{
Name: "lint",
Action: LintActionFunc,
},
{
Name: "v2diff",
Flags: []cli.Flag{
&cli.BoolFlag{Name: "color", Value: false},
},
Action: V2Diff,
},
{
Name: "v2approve",
Action: topRunAction(
"cp",
"-v",
"godoc-current.txt",
filepath.Join("testdata", "godoc-v2.x.txt"),
),
},
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"),
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"),
},
},
}
@ -113,6 +176,14 @@ func sh(exe string, args ...string) (string, error) {
return string(outBytes), err
}
func topRunAction(arg string, args ...string) cli.ActionFunc {
return func(cCtx *cli.Context) error {
os.Chdir(cCtx.Path("top"))
return runCmd(arg, args...)
}
}
func runCmd(arg string, args ...string) error {
cmd := exec.Command(arg, args...)
@ -124,6 +195,43 @@ func runCmd(arg string, args ...string) error {
return cmd.Run()
}
func downloadFile(src, dest string, dirPerm, perm os.FileMode) error {
req, err := http.NewRequest(http.MethodGet, src, nil)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 300 {
return fmt.Errorf("download response %[1]v", resp.StatusCode)
}
if err := os.MkdirAll(filepath.Dir(dest), dirPerm); err != nil {
return err
}
out, err := os.Create(dest)
if err != nil {
return err
}
if _, err := io.Copy(out, resp.Body); err != nil {
return err
}
if err := out.Close(); err != nil {
return err
}
return os.Chmod(dest, perm)
}
func VetActionFunc(cCtx *cli.Context) error {
return runCmd("go", "vet", cCtx.Path("top")+"/...")
}
@ -138,15 +246,20 @@ func TestActionFunc(c *cli.Context) error {
packageName = fmt.Sprintf("github.com/urfave/cli/v3/%s", pkg)
}
if err := runCmd(
"go", "test",
"-tags", tags,
args := []string{"test"}
if tags != "" {
args = append(args, []string{"-tags", tags}...)
}
args = append(args, []string{
"-v",
"--coverprofile", pkg+".coverprofile",
"--coverprofile", pkg + ".coverprofile",
"--covermode", "count",
"--cover", packageName,
packageName,
); err != nil {
}...)
if err := runCmd("go", args...); err != nil {
return err
}
}
@ -215,36 +328,75 @@ func GfmrunActionFunc(cCtx *cli.Context) error {
return err
}
filename := cCtx.Args().Get(0)
if filename == "" {
filename = "README.md"
dirPath := cCtx.Args().Get(0)
if dirPath == "" {
dirPath = "README.md"
}
file, err := os.Open(filename)
if err != nil {
return err
walk := cCtx.Bool("walk")
sources := []string{}
if walk {
// Walk the directory and find all markdown files.
err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
if filepath.Ext(path) != ".md" {
return nil
}
sources = append(sources, path)
return nil
})
if err != nil {
return err
}
} else {
sources = append(sources, dirPath)
}
defer file.Close()
var counter int
scanner := bufio.NewScanner(file)
for scanner.Scan() {
if strings.Contains(scanner.Text(), "package main") {
counter++
for _, src := range sources {
file, err := os.Open(src)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
if strings.Contains(scanner.Text(), "package main") {
counter++
}
}
err = file.Close()
if err != nil {
return err
}
err = scanner.Err()
if err != nil {
return err
}
}
err = file.Close()
if err != nil {
return err
gfmArgs := []string{
"--count",
fmt.Sprint(counter),
}
for _, src := range sources {
gfmArgs = append(gfmArgs, "--sources", src)
}
err = scanner.Err()
if err != nil {
return err
}
if err := runCmd("gfmrun", "-c", fmt.Sprint(counter), "-s", filename); err != nil {
if err := runCmd("gfmrun", gfmArgs...); err != nil {
return err
}
@ -365,6 +517,125 @@ func GenerateActionFunc(cCtx *cli.Context) error {
return runCmd("go", "generate", cCtx.Path("top")+"/...")
}
func YAMLFmtActionFunc(cCtx *cli.Context) error {
yqBin, err := exec.LookPath("yq")
if err != nil {
if !cCtx.Bool("strict") {
fmt.Fprintln(cCtx.App.ErrWriter, "# ---> no yq found; skipping")
return nil
}
return err
}
os.Chdir(cCtx.Path("top"))
return runCmd(yqBin, "eval", "--inplace", "flag-spec.yaml")
}
func DiffCheckActionFunc(cCtx *cli.Context) error {
os.Chdir(cCtx.Path("top"))
if err := runCmd("git", "diff", "--exit-code"); err != nil {
return err
}
return runCmd("git", "diff", "--cached", "--exit-code")
}
func EnsureGoimportsActionFunc(cCtx *cli.Context) error {
top := cCtx.Path("top")
os.Chdir(top)
if err := runCmd(
"goimports",
"-d",
filepath.Join(top, "internal/build/build.go"),
); err == nil {
return nil
}
os.Setenv("GOBIN", filepath.Join(top, ".local/bin"))
return runCmd("go", "install", "golang.org/x/tools/cmd/goimports@latest")
}
func EnsureGfmrunActionFunc(cCtx *cli.Context) error {
top := cCtx.Path("top")
gfmrunExe := filepath.Join(top, ".local/bin/gfmrun")
os.Chdir(top)
if v, err := sh(gfmrunExe, "--version"); err == nil && strings.TrimSpace(v) == gfmrunVersion {
return nil
}
gfmrunURL, err := url.Parse(
fmt.Sprintf(
"https://github.com/urfave/gfmrun/releases/download/%[1]s/gfmrun-%[2]s-%[3]s-%[1]s",
gfmrunVersion, runtime.GOOS, runtime.GOARCH,
),
)
if err != nil {
return err
}
return downloadFile(gfmrunURL.String(), gfmrunExe, 0755, 0755)
}
func EnsureMkdocsActionFunc(cCtx *cli.Context) error {
os.Chdir(cCtx.Path("top"))
if err := runCmd("mkdocs", "--version"); err == nil {
return nil
}
if cCtx.Bool("upgrade-pip") {
if err := runCmd("pip", "install", "-U", "pip"); err != nil {
return err
}
}
return runCmd("pip", "install", "-r", "mkdocs-requirements.txt")
}
func SetMkdocsRemoteActionFunc(cCtx *cli.Context) error {
ghToken := strings.TrimSpace(cCtx.String("github-token"))
if ghToken == "" {
return errors.New("empty github token")
}
os.Chdir(cCtx.Path("top"))
if err := runCmd("git", "remote", "rm", "origin"); err != nil {
return err
}
return runCmd(
"git", "remote", "add", "origin",
fmt.Sprintf("https://x-access-token:%[1]s@github.com/urfave/cli.git", ghToken),
)
}
func LintActionFunc(cCtx *cli.Context) error {
top := cCtx.Path("top")
os.Chdir(top)
out, err := sh(filepath.Join(top, ".local/bin/goimports"), "-l", ".")
if err != nil {
return err
}
if strings.TrimSpace(out) != "" {
fmt.Fprintln(cCtx.App.ErrWriter, "# ---> goimports -l is non-empty:")
fmt.Fprintln(cCtx.App.ErrWriter, out)
return errors.New("goimports needed")
}
return nil
}
func V2Diff(cCtx *cli.Context) error {
os.Chdir(cCtx.Path("top"))
@ -393,34 +664,29 @@ func V2Diff(cCtx *cli.Context) error {
return err
}
func V2Approve(cCtx *cli.Context) error {
top := cCtx.Path("top")
func getSize(sourcePath, builtPath, tags string) (int64, error) {
args := []string{"build"}
return runCmd(
"cp",
"-v",
filepath.Join(top, "godoc-current.txt"),
filepath.Join(top, "testdata", "godoc-v2.x.txt"),
)
}
if tags != "" {
args = append(args, []string{"-tags", tags}...)
}
func getSize(sourcePath string, builtPath string, tags string) (size int64, err error) {
// build example binary
err = runCmd("go", "build", "-tags", tags, "-o", builtPath, "-ldflags", "-s -w", sourcePath)
if err != nil {
args = append(args, []string{
"-o", builtPath,
"-ldflags", "-s -w",
sourcePath,
}...)
if err := runCmd("go", args...); err != nil {
fmt.Println("issue getting size for example binary")
return 0, err
}
// get file info
fileInfo, err := os.Stat(builtPath)
if err != nil {
fmt.Println("issue getting size for example binary")
return 0, err
}
// size!
size = fileInfo.Size()
return size, nil
return fileInfo.Size(), nil
}

View File

@ -1,34 +0,0 @@
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"
}

View File

@ -1,41 +0,0 @@
package genflags_test
import (
"fmt"
"testing"
"github.com/urfave/cli/v3/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)
}
},
)
}
}

View File

@ -1,105 +0,0 @@
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"`
NoDefaultText bool `yaml:"no_default_text"`
}
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) GenerateDefaultText() bool {
return !ft.Config.NoDefaultText
}
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
}

View File

@ -9,9 +9,45 @@ 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
- Home:
- Welcome: index.md
- Contributing: CONTRIBUTING.md
- Code of Conduct: CODE_OF_CONDUCT.md
- Releasing: RELEASING.md
- Security: SECURITY.md
- Migrate v1 to v2: migrate-v1-to-v2.md
- v2 Manual:
- Getting Started: v2/getting-started.md
- Migrating From Older Releases: v2/migrating-from-older-releases.md
- Examples:
- Greet: v2/examples/greet.md
- Arguments: v2/examples/arguments.md
- Flags: v2/examples/flags.md
- Subcommands: v2/examples/subcommands.md
- Subcommands Categories: v2/examples/subcommands-categories.md
- Exit Codes: v2/examples/exit-codes.md
- Combining Short Options: v2/examples/combining-short-options.md
- Bash Completions: v2/examples/bash-completions.md
- Generated Help Text: v2/examples/generated-help-text.md
- Version Flag: v2/examples/version-flag.md
- Timestamp Flag: v2/examples/timestamp-flag.md
- Suggestions: v2/examples/suggestions.md
- Full API Example: v2/examples/full-api-example.md
- v1 Manual:
- Getting Started: v1/getting-started.md
- Migrating to v2: v1/migrating-to-v2.md
- Examples:
- Greet: v1/examples/greet.md
- Arguments: v1/examples/arguments.md
- Flags: v1/examples/flags.md
- Subcommands: v1/examples/subcommands.md
- Subcommands (Categories): v1/examples/subcommands-categories.md
- Exit Codes: v1/examples/exit-codes.md
- Combining Short Options: v1/examples/combining-short-options.md
- Bash Completions: v1/examples/bash-completions.md
- Generated Help Text: v1/examples/generated-help-text.md
- Version Flag: v1/examples/version-flag.md
theme:
name: material
palette:
@ -25,9 +61,18 @@ theme:
toggle:
icon: material/brightness-7
name: light mode
features:
- content.code.annotate
- navigation.top
- navigation.instant
- navigation.expand
- navigation.sections
- navigation.tabs
- navigation.tabs.sticky
plugins:
- git-revision-date-localized
- search
- tags
# NOTE: this is the recommended configuration from
# https://squidfunk.github.io/mkdocs-material/setup/extensions/#recommended-configuration
markdown_extensions:

View File

@ -110,17 +110,18 @@ func (x *SliceFlag[T, S, E]) GetDestination() S {
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 *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 *SliceFlag[T, S, E]) RunAction(c *Context) error { return x.Target.RunAction(c) }
func (x *flagValueHook) Set(value string) error {
if err := x.value.Set(value); err != nil {

View File

@ -124,7 +124,7 @@ func ExampleApp_Suggest() {
app := &App{
Name: "greet",
Suggest: true,
HideHelp: true,
HideHelp: false,
HideHelpCommand: true,
CustomAppHelpTemplate: "(this space intentionally left blank)\n",
Flags: []Flag{
@ -149,7 +149,6 @@ func ExampleApp_Suggest_command() {
app := &App{
Name: "greet",
Suggest: true,
HideHelp: true,
HideHelpCommand: true,
CustomAppHelpTemplate: "(this space intentionally left blank)\n",
Flags: []Flag{
@ -162,6 +161,7 @@ func ExampleApp_Suggest_command() {
Commands: []*Command{
{
Name: "neighbors",
HideHelp: false,
CustomHelpTemplate: "(this space intentionally left blank)\n",
Flags: []Flag{
&BoolFlag{Name: "smiling"},

View File

@ -21,8 +21,8 @@ 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 .VisibleFlagCategories}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}}
{{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}}
GLOBAL OPTIONS:{{range .VisibleFlagCategories}}
{{if .Name}}{{.Name}}
@ -54,12 +54,10 @@ DESCRIPTION:
OPTIONS:{{range .VisibleFlagCategories}}
{{if .Name}}{{.Name}}
{{end}}{{range .Flags}}{{.}}
{{end}}{{end}}{{else}}{{if .VisibleFlags}}
{{end}}{{range .Flags}}{{.}}{{end}}{{end}}{{else}}{{if .VisibleFlags}}
OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}{{end}}
{{range .VisibleFlags}}{{.}}{{end}}{{end}}{{end}}
`
// SubcommandHelpTemplate is the text template for the subcommand help topic.
@ -76,12 +74,11 @@ DESCRIPTION:
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}}
{{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
{{range .VisibleFlags}}{{.}}{{end}}{{end}}
`
var MarkdownDocTemplate = `{{if gt .SectionNum 0}}% {{ .App.Name }} {{ .SectionNum }}

View File

@ -1,28 +1,28 @@
package cli // import "github.com/urfave/cli/v2"
package cli // import "github.com/urfave/cli/v3"
Package cli provides a minimal framework for creating and organizing command
line Go applications. cli is designed to be easy to understand and write,
the most simple cli application can be written as follows:
func main() {
(&cli.App{}).Run(os.Args)
(&cli.App{}).Run(os.Args)
}
Of course this application does not do much, so let's make this an actual
application:
func main() {
app := &cli.App{
Name: "greet",
Usage: "say a greeting",
Action: func(c *cli.Context) error {
fmt.Println("Greetings")
return nil
},
}
func main() {
app := &cli.App{
Name: "greet",
Usage: "say a greeting",
Action: func(c *cli.Context) error {
fmt.Println("Greetings")
return nil
},
}
app.Run(os.Args)
}
app.Run(os.Args)
}
VARIABLES
@ -49,8 +49,8 @@ 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 .VisibleFlagCategories}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}}
{{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}}
GLOBAL OPTIONS:{{range .VisibleFlagCategories}}
{{if .Name}}{{.Name}}
@ -64,8 +64,8 @@ GLOBAL OPTIONS:
COPYRIGHT:
{{wrap .Copyright 3}}{{end}}
`
AppHelpTemplate is the text template for the Default help topic. cli.go uses
text/template to render templates. You can render custom help text by
AppHelpTemplate is the text template for the Default help topic. cli.go
uses text/template to render templates. You can render custom help text by
setting this variable.
var CommandHelpTemplate = `NAME:
@ -82,12 +82,10 @@ DESCRIPTION:
OPTIONS:{{range .VisibleFlagCategories}}
{{if .Name}}{{.Name}}
{{end}}{{range .Flags}}{{.}}
{{end}}{{end}}{{else}}{{if .VisibleFlags}}
{{end}}{{range .Flags}}{{.}}{{end}}{{end}}{{else}}{{if .VisibleFlags}}
OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}{{end}}
{{range .VisibleFlags}}{{.}}{{end}}{{end}}{{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
@ -157,12 +155,11 @@ DESCRIPTION:
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}}
{{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
{{range .VisibleFlags}}{{.}}{{end}}{{end}}
`
SubcommandHelpTemplate is the text template for the subcommand help topic.
cli.go uses text/template to render templates. You can render custom help
@ -201,9 +198,9 @@ func DefaultAppComplete(cCtx *Context)
func DefaultCompleteWithFlags(cmd *Command) func(cCtx *Context)
func FlagNames(name string, aliases []string) []string
func HandleAction(action interface{}, cCtx *Context) (err error)
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!
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 HandleExitCoder(err error)
HandleExitCoder handles errors implementing ExitCoder by printing their
@ -300,6 +297,8 @@ type App struct {
CommandNotFound CommandNotFoundFunc
// Execute this function if a usage error occurs
OnUsageError OnUsageErrorFunc
// Execute this function when an invalid flag is accessed from the context
InvalidFlagAccessHandler InvalidFlagAccessFunc
// Compilation date
Compiled time.Time
// List of all authors who contributed
@ -360,14 +359,14 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error)
to generate command-specific flags
func (a *App) RunContext(ctx context.Context, arguments []string) (err error)
RunContext is like Run except it takes a Context that will be passed to its
commands and sub-commands. Through this, you can propagate timeouts and
RunContext is like Run except it takes a Context that will be passed to
its commands and sub-commands. Through this, you can propagate timeouts and
cancellation requests
func (a *App) Setup()
Setup runs initialization code to ensure all data structures are ready for
`Run` or inspection prior to `Run`. It is internally called by `Run`, but
will return early if setup has already happened.
Setup runs initialization code to ensure all data structures are ready
for `Run` or inspection prior to `Run`. It is internally called by `Run`,
but will return early if setup has already happened.
func (a *App) ToFishCompletion() (string, error)
ToFishCompletion creates a fish completion string for the `*App` The
@ -450,6 +449,10 @@ type BoolFlag struct {
Aliases []string
EnvVars []string
Count *int
Action func(*Context, bool) error
}
BoolFlag is a flag with type bool
@ -460,7 +463,7 @@ func (f *BoolFlag) Get(ctx *Context) bool
Get returns the flags value in the given Context.
func (f *BoolFlag) GetCategory() string
GetCategory returns the category for the flag
GetCategory returns the category of the flag
func (f *BoolFlag) GetDefaultText() string
GetDefaultText returns the default text for this flag
@ -487,19 +490,14 @@ func (f *BoolFlag) IsVisible() bool
func (f *BoolFlag) Names() []string
Names returns the names of the flag
func (f *BoolFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *BoolFlag) String() string
String returns a readable representation of this value (for usage defaults)
func (f *BoolFlag) TakesValue() bool
TakesValue returns true of the flag takes a value, otherwise false
type CategorizableFlag interface {
VisibleFlag
GetCategory() string
}
CategorizableFlag is an interface that allows us to potentially use a flag
in a categorized representation.
TakesValue returns true if the flag takes a value, otherwise false
type Command struct {
// The name of the command
@ -629,6 +627,9 @@ func (cCtx *Context) Args() Args
func (cCtx *Context) Bool(name string) bool
Bool looks up the value of a local BoolFlag, returns false if not found
func (cCtx *Context) Count(name string) int
Count returns the num of occurences of this flag
func (cCtx *Context) Duration(name string) time.Duration
Duration looks up the value of a local DurationFlag, returns 0 if not found
@ -698,30 +699,22 @@ func (cCtx *Context) Uint(name string) uint
func (cCtx *Context) Uint64(name string) uint64
Uint64 looks up the value of a local Uint64Flag, returns 0 if not found
func (cCtx *Context) Uint64Slice(name string) []uint64
Uint64Slice looks up the value of a local Uint64SliceFlag, returns nil if
not found
func (cCtx *Context) UintSlice(name string) []uint
UintSlice looks up the value of a local UintSliceFlag, returns nil if not
found
func (cCtx *Context) Value(name string) interface{}
Value returns the value of the flag corresponding to `name`
type DocGenerationFlag interface {
Flag
// TakesValue returns true if the flag takes a value, otherwise false
TakesValue() bool
// GetUsage returns the usage string for the flag
GetUsage() string
// 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
type Countable interface {
Count() int
}
DocGenerationFlag is an interface that allows documentation generation for
the flag
Countable is an interface to enable detection of flag values which support
repetitive flags
type DurationFlag struct {
Name string
@ -740,6 +733,8 @@ type DurationFlag struct {
Aliases []string
EnvVars []string
Action func(*Context, time.Duration) error
}
DurationFlag is a flag with type time.Duration
@ -750,7 +745,7 @@ func (f *DurationFlag) Get(ctx *Context) time.Duration
Get returns the flags value in the given Context.
func (f *DurationFlag) GetCategory() string
GetCategory returns the category for the flag
GetCategory returns the category of the flag
func (f *DurationFlag) GetDefaultText() string
GetDefaultText returns the default text for this flag
@ -777,11 +772,14 @@ func (f *DurationFlag) IsVisible() bool
func (f *DurationFlag) Names() []string
Names returns the names of the flag
func (f *DurationFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *DurationFlag) String() string
String returns a readable representation of this value (for usage defaults)
func (f *DurationFlag) TakesValue() bool
TakesValue returns true of the flag takes a value, otherwise false
TakesValue returns true if the flag takes a value, otherwise false
type ErrorFormatter interface {
Format(s fmt.State, verb rune)
@ -799,9 +797,9 @@ func Exit(message interface{}, exitCode int) ExitCoder
Exit wraps a message and exit code into an error, which by default is
handled with a call to os.Exit during default error handling.
This is the simplest way to trigger a non-zero exit code for an App without
having to call os.Exit manually. During testing, this behavior can be
avoided by overiding the ExitErrHandler function on an App or the
This is the simplest way to trigger a non-zero exit code for an App
without having to call os.Exit manually. During testing, this behavior
can be avoided by overiding the ExitErrHandler function on an App or the
package-global OsExiter function.
func NewExitError(message interface{}, exitCode int) ExitCoder
@ -816,10 +814,42 @@ type ExitErrHandlerFunc func(cCtx *Context, err error)
type Flag interface {
fmt.Stringer
// Apply Flag settings to the given flag set
Apply(*flag.FlagSet) error
// All possible names for this flag
Names() []string
// Whether the flag has been set or not
IsSet() bool
// whether the flag is a required flag or not
IsRequired() bool
// IsVisible returns true if the flag is not hidden, otherwise false
IsVisible() bool
// Returns the category of the flag
GetCategory() string
// GetUsage returns the usage string for the flag
GetUsage() string
// GetEnvVars returns the env vars for this flag
GetEnvVars() []string
// TakesValue returns true if the flag takes a value, otherwise false
TakesValue() bool
// GetDefaultText returns the default text for this flag
GetDefaultText() string
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
GetValue() string
RunAction(*Context) error
}
Flag is a common interface related to parsing flags in cli. For more
advanced flag parsing techniques, it is recommended that this interface be
@ -913,6 +943,8 @@ type Float64Flag struct {
Aliases []string
EnvVars []string
Action func(*Context, float64) error
}
Float64Flag is a flag with type float64
@ -923,7 +955,7 @@ func (f *Float64Flag) Get(ctx *Context) float64
Get returns the flags value in the given Context.
func (f *Float64Flag) GetCategory() string
GetCategory returns the category for the flag
GetCategory returns the category of the flag
func (f *Float64Flag) GetDefaultText() string
GetDefaultText returns the default text for this flag
@ -950,11 +982,14 @@ func (f *Float64Flag) IsVisible() bool
func (f *Float64Flag) Names() []string
Names returns the names of the flag
func (f *Float64Flag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *Float64Flag) String() string
String returns a readable representation of this value (for usage defaults)
func (f *Float64Flag) TakesValue() bool
TakesValue returns true of the flag takes a value, otherwise false
TakesValue returns true if the flag takes a value, otherwise false
type Float64Slice struct {
// Has unexported fields.
@ -996,6 +1031,8 @@ type Float64SliceFlag struct {
Aliases []string
EnvVars []string
Action func(*Context, []float64) error
}
Float64SliceFlag is a flag with type *Float64Slice
@ -1006,7 +1043,7 @@ func (f *Float64SliceFlag) Get(ctx *Context) []float64
Get returns the flags value in the given Context.
func (f *Float64SliceFlag) GetCategory() string
GetCategory returns the category for the flag
GetCategory returns the category of the flag
func (f *Float64SliceFlag) GetDefaultText() string
GetDefaultText returns the default text for this flag
@ -1035,6 +1072,9 @@ func (f *Float64SliceFlag) IsVisible() bool
func (f *Float64SliceFlag) Names() []string
Names returns the names of the flag
func (f *Float64SliceFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *Float64SliceFlag) SetDestination(slice []float64)
func (f *Float64SliceFlag) SetValue(slice []float64)
@ -1064,16 +1104,18 @@ type GenericFlag struct {
HasBeenSet bool
Value Generic
Destination *Generic
Destination Generic
Aliases []string
EnvVars []string
TakesFile bool
Action func(*Context, interface{}) error
}
GenericFlag is a flag with type Generic
func (f GenericFlag) Apply(set *flag.FlagSet) error
func (f *GenericFlag) Apply(set *flag.FlagSet) error
Apply takes the flagset and calls Set on the generic flag with the value
provided by the user for parsing by the flag
@ -1081,7 +1123,7 @@ func (f *GenericFlag) Get(ctx *Context) interface{}
Get returns the flags value in the given Context.
func (f *GenericFlag) GetCategory() string
GetCategory returns the category for the flag
GetCategory returns the category of the flag
func (f *GenericFlag) GetDefaultText() string
GetDefaultText returns the default text for this flag
@ -1108,11 +1150,14 @@ func (f *GenericFlag) IsVisible() bool
func (f *GenericFlag) Names() []string
Names returns the names of the flag
func (f *GenericFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *GenericFlag) String() string
String returns a readable representation of this value (for usage defaults)
func (f *GenericFlag) TakesValue() bool
TakesValue returns true of the flag takes a value, otherwise false
TakesValue returns true if the flag takes a value, otherwise false
type Int64Flag struct {
Name string
@ -1131,6 +1176,10 @@ type Int64Flag struct {
Aliases []string
EnvVars []string
Base int
Action func(*Context, int64) error
}
Int64Flag is a flag with type int64
@ -1141,7 +1190,7 @@ func (f *Int64Flag) Get(ctx *Context) int64
Get returns the flags value in the given Context.
func (f *Int64Flag) GetCategory() string
GetCategory returns the category for the flag
GetCategory returns the category of the flag
func (f *Int64Flag) GetDefaultText() string
GetDefaultText returns the default text for this flag
@ -1168,11 +1217,14 @@ func (f *Int64Flag) IsVisible() bool
func (f *Int64Flag) Names() []string
Names returns the names of the flag
func (f *Int64Flag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *Int64Flag) String() string
String returns a readable representation of this value (for usage defaults)
func (f *Int64Flag) TakesValue() bool
TakesValue returns true of the flag takes a value, otherwise false
TakesValue returns true if the flag takes a value, otherwise false
type Int64Slice struct {
// Has unexported fields.
@ -1214,6 +1266,8 @@ type Int64SliceFlag struct {
Aliases []string
EnvVars []string
Action func(*Context, []int64) error
}
Int64SliceFlag is a flag with type *Int64Slice
@ -1224,7 +1278,7 @@ func (f *Int64SliceFlag) Get(ctx *Context) []int64
Get returns the flags value in the given Context.
func (f *Int64SliceFlag) GetCategory() string
GetCategory returns the category for the flag
GetCategory returns the category of the flag
func (f *Int64SliceFlag) GetDefaultText() string
GetDefaultText returns the default text for this flag
@ -1253,6 +1307,9 @@ func (f *Int64SliceFlag) IsVisible() bool
func (f *Int64SliceFlag) Names() []string
Names returns the names of the flag
func (f *Int64SliceFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *Int64SliceFlag) SetDestination(slice []int64)
func (f *Int64SliceFlag) SetValue(slice []int64)
@ -1261,7 +1318,7 @@ func (f *Int64SliceFlag) String() string
String returns a readable representation of this value (for usage defaults)
func (f *Int64SliceFlag) TakesValue() bool
TakesValue returns true of the flag takes a value, otherwise false
TakesValue returns true if the flag takes a value, otherwise false
type IntFlag struct {
Name string
@ -1280,6 +1337,10 @@ type IntFlag struct {
Aliases []string
EnvVars []string
Base int
Action func(*Context, int) error
}
IntFlag is a flag with type int
@ -1290,7 +1351,7 @@ func (f *IntFlag) Get(ctx *Context) int
Get returns the flags value in the given Context.
func (f *IntFlag) GetCategory() string
GetCategory returns the category for the flag
GetCategory returns the category of the flag
func (f *IntFlag) GetDefaultText() string
GetDefaultText returns the default text for this flag
@ -1317,11 +1378,14 @@ func (f *IntFlag) IsVisible() bool
func (f *IntFlag) Names() []string
Names returns the names of the flag
func (f *IntFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *IntFlag) String() string
String returns a readable representation of this value (for usage defaults)
func (f *IntFlag) TakesValue() bool
TakesValue returns true of the flag takes a value, otherwise false
TakesValue returns true if the flag takes a value, otherwise false
type IntSlice struct {
// Has unexported fields.
@ -1367,6 +1431,8 @@ type IntSliceFlag struct {
Aliases []string
EnvVars []string
Action func(*Context, []int) error
}
IntSliceFlag is a flag with type *IntSlice
@ -1377,7 +1443,7 @@ func (f *IntSliceFlag) Get(ctx *Context) []int
Get returns the flags value in the given Context.
func (f *IntSliceFlag) GetCategory() string
GetCategory returns the category for the flag
GetCategory returns the category of the flag
func (f *IntSliceFlag) GetDefaultText() string
GetDefaultText returns the default text for this flag
@ -1406,6 +1472,9 @@ func (f *IntSliceFlag) IsVisible() bool
func (f *IntSliceFlag) Names() []string
Names returns the names of the flag
func (f *IntSliceFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *IntSliceFlag) SetDestination(slice []int)
func (f *IntSliceFlag) SetValue(slice []int)
@ -1414,7 +1483,11 @@ func (f *IntSliceFlag) String() string
String returns a readable representation of this value (for usage defaults)
func (f *IntSliceFlag) TakesValue() bool
TakesValue returns true of the flag takes a value, otherwise false
TakesValue returns true if the flag takes a value, otherwise false
type InvalidFlagAccessFunc func(*Context, string)
InvalidFlagAccessFunc is executed when an invalid flag is accessed from the
context.
type MultiError interface {
error
@ -1431,8 +1504,8 @@ type MultiInt64Flag = SliceFlag[*Int64SliceFlag, []int64, int64]
directly, as Value and/or Destination. See also SliceFlag.
type MultiIntFlag = SliceFlag[*IntSliceFlag, []int, int]
MultiIntFlag extends IntSliceFlag with support for using slices directly, as
Value and/or Destination. See also SliceFlag.
MultiIntFlag extends IntSliceFlag with support for using slices directly,
as Value and/or Destination. See also SliceFlag.
type MultiStringFlag = SliceFlag[*StringSliceFlag, []string, string]
MultiStringFlag extends StringSliceFlag with support for using slices
@ -1465,6 +1538,8 @@ type PathFlag struct {
EnvVars []string
TakesFile bool
Action func(*Context, Path) error
}
PathFlag is a flag with type Path
@ -1475,7 +1550,7 @@ func (f *PathFlag) Get(ctx *Context) string
Get returns the flags value in the given Context.
func (f *PathFlag) GetCategory() string
GetCategory returns the category for the flag
GetCategory returns the category of the flag
func (f *PathFlag) GetDefaultText() string
GetDefaultText returns the default text for this flag
@ -1502,20 +1577,14 @@ func (f *PathFlag) IsVisible() bool
func (f *PathFlag) Names() []string
Names returns the names of the flag
func (f *PathFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *PathFlag) String() string
String returns a readable representation of this value (for usage defaults)
func (f *PathFlag) TakesValue() bool
TakesValue returns true of the flag takes a value, otherwise false
type RequiredFlag interface {
Flag
IsRequired() bool
}
RequiredFlag is an interface that allows us to mark flags as required it
allows flags required flags to be backwards compatible with the Flag
interface
TakesValue returns true if the flag takes a value, otherwise false
type Serializer interface {
Serialize() string
@ -1527,9 +1596,9 @@ type SliceFlag[T SliceFlagTarget[E], S ~[]E, E any] struct {
Value S
Destination *S
}
SliceFlag extends implementations like StringSliceFlag and IntSliceFlag with
support for using slices directly, as Value and/or Destination. See also
SliceFlagTarget, MultiStringFlag, MultiFloat64Flag, MultiInt64Flag,
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.
func (x *SliceFlag[T, S, E]) Apply(set *flag.FlagSet) error
@ -1554,6 +1623,8 @@ func (x *SliceFlag[T, S, E]) IsVisible() bool
func (x *SliceFlag[T, S, E]) Names() []string
func (x *SliceFlag[T, S, E]) RunAction(c *Context) error
func (x *SliceFlag[T, S, E]) SetDestination(slice S)
func (x *SliceFlag[T, S, E]) SetValue(slice S)
@ -1564,10 +1635,6 @@ func (x *SliceFlag[T, S, E]) TakesValue() bool
type 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).
@ -1602,6 +1669,8 @@ type StringFlag struct {
EnvVars []string
TakesFile bool
Action func(*Context, string) error
}
StringFlag is a flag with type string
@ -1612,7 +1681,7 @@ func (f *StringFlag) Get(ctx *Context) string
Get returns the flags value in the given Context.
func (f *StringFlag) GetCategory() string
GetCategory returns the category for the flag
GetCategory returns the category of the flag
func (f *StringFlag) GetDefaultText() string
GetDefaultText returns the default text for this flag
@ -1639,11 +1708,14 @@ func (f *StringFlag) IsVisible() bool
func (f *StringFlag) Names() []string
Names returns the names of the flag
func (f *StringFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *StringFlag) String() string
String returns a readable representation of this value (for usage defaults)
func (f *StringFlag) TakesValue() bool
TakesValue returns true of the flag takes a value, otherwise false
TakesValue returns true if the flag takes a value, otherwise false
type StringSlice struct {
// Has unexported fields.
@ -1687,6 +1759,8 @@ type StringSliceFlag struct {
EnvVars []string
TakesFile bool
Action func(*Context, []string) error
}
StringSliceFlag is a flag with type *StringSlice
@ -1697,7 +1771,7 @@ func (f *StringSliceFlag) Get(ctx *Context) []string
Get returns the flags value in the given Context.
func (f *StringSliceFlag) GetCategory() string
GetCategory returns the category for the flag
GetCategory returns the category of the flag
func (f *StringSliceFlag) GetDefaultText() string
GetDefaultText returns the default text for this flag
@ -1726,6 +1800,9 @@ func (f *StringSliceFlag) IsVisible() bool
func (f *StringSliceFlag) Names() []string
Names returns the names of the flag
func (f *StringSliceFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *StringSliceFlag) SetDestination(slice []string)
func (f *StringSliceFlag) SetValue(slice []string)
@ -1734,7 +1811,7 @@ func (f *StringSliceFlag) String() string
String returns a readable representation of this value (for usage defaults)
func (f *StringSliceFlag) TakesValue() bool
TakesValue returns true of the flag takes a value, otherwise false
TakesValue returns true if the flag takes a value, otherwise false
type SuggestCommandFunc func(commands []*Command, provided string) string
@ -1790,6 +1867,8 @@ type TimestampFlag struct {
Layout string
Timezone *time.Location
Action func(*Context, *time.Time) error
}
TimestampFlag is a flag with type *Timestamp
@ -1800,7 +1879,7 @@ func (f *TimestampFlag) Get(ctx *Context) *time.Time
Get returns the flags value in the given Context.
func (f *TimestampFlag) GetCategory() string
GetCategory returns the category for the flag
GetCategory returns the category of the flag
func (f *TimestampFlag) GetDefaultText() string
GetDefaultText returns the default text for this flag
@ -1827,11 +1906,14 @@ func (f *TimestampFlag) IsVisible() bool
func (f *TimestampFlag) Names() []string
Names returns the names of the flag
func (f *TimestampFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *TimestampFlag) String() string
String returns a readable representation of this value (for usage defaults)
func (f *TimestampFlag) TakesValue() bool
TakesValue returns true of the flag takes a value, otherwise false
TakesValue returns true if the flag takes a value, otherwise false
type Uint64Flag struct {
Name string
@ -1850,6 +1932,10 @@ type Uint64Flag struct {
Aliases []string
EnvVars []string
Base int
Action func(*Context, uint64) error
}
Uint64Flag is a flag with type uint64
@ -1860,7 +1946,7 @@ func (f *Uint64Flag) Get(ctx *Context) uint64
Get returns the flags value in the given Context.
func (f *Uint64Flag) GetCategory() string
GetCategory returns the category for the flag
GetCategory returns the category of the flag
func (f *Uint64Flag) GetDefaultText() string
GetDefaultText returns the default text for this flag
@ -1887,10 +1973,101 @@ func (f *Uint64Flag) IsVisible() bool
func (f *Uint64Flag) Names() []string
Names returns the names of the flag
func (f *Uint64Flag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *Uint64Flag) String() string
String returns a readable representation of this value (for usage defaults)
func (f *Uint64Flag) TakesValue() bool
TakesValue returns true if the flag takes a value, otherwise false
type Uint64Slice struct {
// Has unexported fields.
}
Uint64Slice wraps []int64 to satisfy flag.Value
func NewUint64Slice(defaults ...uint64) *Uint64Slice
NewUint64Slice makes an *Uint64Slice with default values
func (i *Uint64Slice) Get() interface{}
Get returns the slice of ints set by this flag
func (i *Uint64Slice) Serialize() string
Serialize allows Uint64Slice to fulfill Serializer
func (i *Uint64Slice) Set(value string) error
Set parses the value into an integer and appends it to the list of values
func (i *Uint64Slice) String() string
String returns a readable representation of this value (for usage defaults)
func (i *Uint64Slice) Value() []uint64
Value returns the slice of ints set by this flag
type Uint64SliceFlag struct {
Name string
Category string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value *Uint64Slice
Destination *Uint64Slice
Aliases []string
EnvVars []string
Action func(*Context, []uint64) error
}
Uint64SliceFlag is a flag with type *Uint64Slice
func (f *Uint64SliceFlag) Apply(set *flag.FlagSet) error
Apply populates the flag given the flag set and environment
func (f *Uint64SliceFlag) Get(ctx *Context) []uint64
Get returns the flags value in the given Context.
func (f *Uint64SliceFlag) GetCategory() string
GetCategory returns the category for the flag
func (f *Uint64SliceFlag) GetDefaultText() string
GetDefaultText returns the default text for this flag
func (f *Uint64SliceFlag) GetEnvVars() []string
GetEnvVars returns the env vars for this flag
func (f *Uint64SliceFlag) GetUsage() string
GetUsage returns the usage string for the flag
func (f *Uint64SliceFlag) GetValue() string
GetValue returns the flags value as string representation and an empty
string if the flag takes no value at all.
func (f *Uint64SliceFlag) IsRequired() bool
IsRequired returns whether or not the flag is required
func (f *Uint64SliceFlag) IsSet() bool
IsSet returns whether or not the flag has been set through env or file
func (f *Uint64SliceFlag) IsVisible() bool
IsVisible returns true if the flag is not hidden, otherwise false
func (f *Uint64SliceFlag) Names() []string
Names returns the names of the flag
func (f *Uint64SliceFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *Uint64SliceFlag) String() string
String returns a readable representation of this value (for usage defaults)
func (f *Uint64SliceFlag) TakesValue() bool
TakesValue returns true of the flag takes a value, otherwise false
type UintFlag struct {
@ -1910,6 +2087,10 @@ type UintFlag struct {
Aliases []string
EnvVars []string
Base int
Action func(*Context, uint) error
}
UintFlag is a flag with type uint
@ -1920,7 +2101,7 @@ func (f *UintFlag) Get(ctx *Context) uint
Get returns the flags value in the given Context.
func (f *UintFlag) GetCategory() string
GetCategory returns the category for the flag
GetCategory returns the category of the flag
func (f *UintFlag) GetDefaultText() string
GetDefaultText returns the default text for this flag
@ -1947,29 +2128,116 @@ func (f *UintFlag) IsVisible() bool
func (f *UintFlag) Names() []string
Names returns the names of the flag
func (f *UintFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *UintFlag) String() string
String returns a readable representation of this value (for usage defaults)
func (f *UintFlag) TakesValue() bool
TakesValue returns true of the flag takes a value, otherwise false
TakesValue returns true if the flag takes a value, otherwise false
type VisibleFlag interface {
Flag
// IsVisible returns true if the flag is not hidden, otherwise false
IsVisible() bool
type UintSlice struct {
// Has unexported fields.
}
VisibleFlag is an interface that allows to check if a flag is visible
UintSlice wraps []int to satisfy flag.Value
func NewUintSlice(defaults ...uint) *UintSlice
NewUintSlice makes an *UintSlice with default values
func (i *UintSlice) Get() interface{}
Get returns the slice of ints set by this flag
func (i *UintSlice) Serialize() string
Serialize allows UintSlice to fulfill Serializer
func (i *UintSlice) Set(value string) error
Set parses the value into an integer and appends it to the list of values
func (i *UintSlice) SetUint(value uint)
TODO: Consistently have specific Set function for Int64 and Float64 ? SetInt
directly adds an integer to the list of values
func (i *UintSlice) String() string
String returns a readable representation of this value (for usage defaults)
func (i *UintSlice) Value() []uint
Value returns the slice of ints set by this flag
type UintSliceFlag struct {
Name string
Category string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value *UintSlice
Destination *UintSlice
Aliases []string
EnvVars []string
Action func(*Context, []uint) error
}
UintSliceFlag is a flag with type *UintSlice
func (f *UintSliceFlag) Apply(set *flag.FlagSet) error
Apply populates the flag given the flag set and environment
func (f *UintSliceFlag) Get(ctx *Context) []uint
Get returns the flags value in the given Context.
func (f *UintSliceFlag) GetCategory() string
GetCategory returns the category for the flag
func (f *UintSliceFlag) GetDefaultText() string
GetDefaultText returns the default text for this flag
func (f *UintSliceFlag) GetEnvVars() []string
GetEnvVars returns the env vars for this flag
func (f *UintSliceFlag) GetUsage() string
GetUsage returns the usage string for the flag
func (f *UintSliceFlag) GetValue() string
GetValue returns the flags value as string representation and an empty
string if the flag takes no value at all.
func (f *UintSliceFlag) IsRequired() bool
IsRequired returns whether or not the flag is required
func (f *UintSliceFlag) IsSet() bool
IsSet returns whether or not the flag has been set through env or file
func (f *UintSliceFlag) IsVisible() bool
IsVisible returns true if the flag is not hidden, otherwise false
func (f *UintSliceFlag) Names() []string
Names returns the names of the flag
func (f *UintSliceFlag) RunAction(c *Context) error
RunAction executes flag action if set
func (f *UintSliceFlag) String() string
String returns a readable representation of this value (for usage defaults)
func (f *UintSliceFlag) TakesValue() bool
TakesValue returns true of the flag takes a value, otherwise false
type VisibleFlagCategory interface {
// Name returns the category name string
Name() string
// Flags returns a slice of VisibleFlag sorted by name
Flags() []VisibleFlag
Flags() []Flag
}
VisibleFlagCategory is a category containing flags.
package altsrc // import "github.com/urfave/cli/v2/altsrc"
package altsrc // import "github.com/urfave/cli/v3/altsrc"
FUNCTIONS
@ -1986,9 +2254,9 @@ func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceCont
that are supported by the input source
func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(cCtx *cli.Context) (InputSourceContext, error)) cli.BeforeFunc
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
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

View File

@ -22,6 +22,8 @@ type Float64SliceFlag struct {
Aliases []string
EnvVars []string
Action func(*Context, []float64) error
}
// IsSet returns whether or not the flag has been set through env or file
@ -86,12 +88,14 @@ type GenericFlag struct {
HasBeenSet bool
Value Generic
Destination *Generic
Destination Generic
Aliases []string
EnvVars []string
TakesFile bool
Action func(*Context, interface{}) error
}
// String returns a readable representation of this value (for usage defaults)
@ -165,6 +169,8 @@ type Int64SliceFlag struct {
Aliases []string
EnvVars []string
Action func(*Context, []int64) error
}
// IsSet returns whether or not the flag has been set through env or file
@ -233,6 +239,8 @@ type IntSliceFlag struct {
Aliases []string
EnvVars []string
Action func(*Context, []int) error
}
// IsSet returns whether or not the flag has been set through env or file
@ -303,6 +311,8 @@ type PathFlag struct {
EnvVars []string
TakesFile bool
Action func(*Context, Path) error
}
// String returns a readable representation of this value (for usage defaults)
@ -370,6 +380,8 @@ type StringSliceFlag struct {
EnvVars []string
TakesFile bool
Action func(*Context, []string) error
}
// IsSet returns whether or not the flag has been set through env or file
@ -442,6 +454,8 @@ type TimestampFlag struct {
Layout string
Timezone *time.Location
Action func(*Context, *time.Time) error
}
// String returns a readable representation of this value (for usage defaults)
@ -497,6 +511,90 @@ func (f *TimestampFlag) GetDefaultText() string {
return f.GetValue()
}
// Uint64SliceFlag is a flag with type *Uint64Slice
type Uint64SliceFlag struct {
Name string
Category string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value *Uint64Slice
Destination *Uint64Slice
Aliases []string
EnvVars []string
Action func(*Context, []uint64) error
}
// IsSet returns whether or not the flag has been set through env or file
func (f *Uint64SliceFlag) IsSet() bool {
return f.HasBeenSet
}
// Names returns the names of the flag
func (f *Uint64SliceFlag) Names() []string {
return FlagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
func (f *Uint64SliceFlag) IsRequired() bool {
return f.Required
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *Uint64SliceFlag) IsVisible() bool {
return !f.Hidden
}
// UintSliceFlag is a flag with type *UintSlice
type UintSliceFlag struct {
Name string
Category string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value *UintSlice
Destination *UintSlice
Aliases []string
EnvVars []string
Action func(*Context, []uint) error
}
// IsSet returns whether or not the flag has been set through env or file
func (f *UintSliceFlag) IsSet() bool {
return f.HasBeenSet
}
// Names returns the names of the flag
func (f *UintSliceFlag) Names() []string {
return FlagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
func (f *UintSliceFlag) IsRequired() bool {
return f.Required
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *UintSliceFlag) IsVisible() bool {
return !f.Hidden
}
// BoolFlag is a flag with type bool
type BoolFlag struct {
Name string
@ -515,6 +613,10 @@ type BoolFlag struct {
Aliases []string
EnvVars []string
Count *int
Action func(*Context, bool) error
}
// String returns a readable representation of this value (for usage defaults)
@ -580,6 +682,8 @@ type Float64Flag struct {
Aliases []string
EnvVars []string
Action func(*Context, float64) error
}
// String returns a readable representation of this value (for usage defaults)
@ -653,6 +757,10 @@ type IntFlag struct {
Aliases []string
EnvVars []string
Base int
Action func(*Context, int) error
}
// String returns a readable representation of this value (for usage defaults)
@ -726,6 +834,10 @@ type Int64Flag struct {
Aliases []string
EnvVars []string
Base int
Action func(*Context, int64) error
}
// String returns a readable representation of this value (for usage defaults)
@ -801,6 +913,8 @@ type StringFlag struct {
EnvVars []string
TakesFile bool
Action func(*Context, string) error
}
// String returns a readable representation of this value (for usage defaults)
@ -866,6 +980,8 @@ type DurationFlag struct {
Aliases []string
EnvVars []string
Action func(*Context, time.Duration) error
}
// String returns a readable representation of this value (for usage defaults)
@ -939,6 +1055,10 @@ type UintFlag struct {
Aliases []string
EnvVars []string
Base int
Action func(*Context, uint) error
}
// String returns a readable representation of this value (for usage defaults)
@ -1012,6 +1132,10 @@ type Uint64Flag struct {
Aliases []string
EnvVars []string
Base int
Action func(*Context, uint64) error
}
// String returns a readable representation of this value (for usage defaults)

View File

@ -160,6 +160,20 @@ func TestTimestampFlag_SatisfiesFmtStringerInterface(t *testing.T) {
_ = f.String()
}
func TestUint64SliceFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.Uint64SliceFlag{}
_ = f.IsSet()
_ = f.Names()
}
func TestUintSliceFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.UintSliceFlag{}
_ = f.IsSet()
_ = f.Names()
}
func TestBoolFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.BoolFlag{}