commit
9166808eb5
1
.github/.codecov.yml
vendored
1
.github/.codecov.yml
vendored
@ -1 +0,0 @@
|
||||
comment: false
|
9
.github/codecov.yml
vendored
Normal file
9
.github/codecov.yml
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
comment: false
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
threshold: 5%
|
||||
patch:
|
||||
default:
|
||||
threshold: 5%
|
115
.github/workflows/cli.yml
vendored
115
.github/workflows/cli.yml
vendored
@ -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
18
.gitignore
vendored
@ -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
|
||||
|
22
Makefile
22
Makefile
@ -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
|
||||
|
183
altsrc/flag.go
183
altsrc/flag.go
@ -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
|
||||
|
@ -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"}),
|
||||
|
@ -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)
|
||||
|
@ -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
67
app.go
@ -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
|
||||
|
531
app_test.go
531
app_test.go
@ -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
32
cli.go
@ -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
|
||||
|
21
cmd/urfave-cli-genflags/Makefile
Normal file
21
cmd/urfave-cli-genflags/Makefile
Normal 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
|
15
cmd/urfave-cli-genflags/README.md
Normal file
15
cmd/urfave-cli-genflags/README.md
Normal 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
|
@ -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}}
|
||||
}
|
||||
|
15
cmd/urfave-cli-genflags/go.mod
Normal file
15
cmd/urfave-cli-genflags/go.mod
Normal 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
|
||||
)
|
14
cmd/urfave-cli-genflags/go.sum
Normal file
14
cmd/urfave-cli-genflags/go.sum
Normal 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=
|
@ -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
|
||||
}
|
@ -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",
|
21
command.go
21
command.go
@ -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()
|
||||
}
|
||||
|
33
context.go
33
context.go
@ -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, ",")
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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:
|
||||
|
||||
|
35
docs/v1/examples/arguments.md
Normal file
35
docs/v1/examples/arguments.md
Normal 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)
|
||||
}
|
||||
}
|
||||
```
|
119
docs/v1/examples/bash-completions.md
Normal file
119
docs/v1/examples/bash-completions.md
Normal 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", "--generate-bash-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": ["--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)
|
||||
}
|
||||
}
|
||||
```
|
72
docs/v1/examples/combining-short-options.md
Normal file
72
docs/v1/examples/combining-short-options.md
Normal 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", "-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.
|
43
docs/v1/examples/exit-codes.md
Normal file
43
docs/v1/examples/exit-codes.md
Normal 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
462
docs/v1/examples/flags.md
Normal 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": ["--help"],
|
||||
"output": "--config FILE, -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": ["--help"],
|
||||
"output": "--lang value, -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": ["--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": ["--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": ["--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": ["--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": ["--help"],
|
||||
"output": "--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
|
106
docs/v1/examples/generated-help-text.md
Normal file
106
docs/v1/examples/generated-help-text.md
Normal 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": ["--halp"],
|
||||
"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
74
docs/v1/examples/greet.md
Normal 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
|
||||
```
|
55
docs/v1/examples/subcommands-categories.md
Normal file
55
docs/v1/examples/subcommands-categories.md
Normal 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
|
||||
```
|
75
docs/v1/examples/subcommands.md
Normal file
75
docs/v1/examples/subcommands.md
Normal 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)
|
||||
}
|
||||
}
|
||||
```
|
363
docs/v1/examples/version-flag.md
Normal file
363
docs/v1/examples/version-flag.md
Normal 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": ["--print-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": ["--version"],
|
||||
"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
|
||||
}
|
||||
```
|
65
docs/v1/getting-started.md
Normal file
65
docs/v1/getting-started.md
Normal 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": ["--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.
|
@ -1 +0,0 @@
|
||||
manual.md
|
1455
docs/v1/manual.md
1455
docs/v1/manual.md
File diff suppressed because it is too large
Load Diff
9
docs/v1/migrating-to-v2.md
Normal file
9
docs/v1/migrating-to-v2.md
Normal 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).
|
36
docs/v2/examples/arguments.md
Normal file
36
docs/v2/examples/arguments.md
Normal 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)
|
||||
}
|
||||
}
|
||||
```
|
257
docs/v2/examples/bash-completions.md
Normal file
257
docs/v2/examples/bash-completions.md
Normal 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", "--generate-bash-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": ["--generate-bash-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
|
||||
```
|
74
docs/v2/examples/combining-short-options.md
Normal file
74
docs/v2/examples/combining-short-options.md
Normal 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", "-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.
|
45
docs/v2/examples/exit-codes.md
Normal file
45
docs/v2/examples/exit-codes.md
Normal 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
597
docs/v2/examples/flags.md
Normal 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": ["--foo", "--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": ["--help"],
|
||||
"output": "--config FILE, -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": ["--help"],
|
||||
"output": "--lang value, -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": ["--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": ["--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": ["--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": ["--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": ["--help"],
|
||||
"output": "--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": ["--help"],
|
||||
"output": "--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
|
262
docs/v2/examples/full-api-example.md
Normal file
262
docs/v2/examples/full-api-example.md
Normal 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
|
||||
}
|
||||
```
|
101
docs/v2/examples/generated-help-text.md
Normal file
101
docs/v2/examples/generated-help-text.md
Normal 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": ["--halp"],
|
||||
"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
73
docs/v2/examples/greet.md
Normal 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)
|
||||
```
|
54
docs/v2/examples/subcommands-categories.md
Normal file
54
docs/v2/examples/subcommands-categories.md
Normal 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
|
||||
```
|
76
docs/v2/examples/subcommands.md
Normal file
76
docs/v2/examples/subcommands.md
Normal 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)
|
||||
}
|
||||
}
|
||||
```
|
11
docs/v2/examples/suggestions.md
Normal file
11
docs/v2/examples/suggestions.md
Normal 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.
|
64
docs/v2/examples/timestamp-flag.md
Normal file
64
docs/v2/examples/timestamp-flag.md
Normal 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": ["--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)
|
77
docs/v2/examples/version-flag.md
Normal file
77
docs/v2/examples/version-flag.md
Normal 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": ["--print-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": ["--version"],
|
||||
"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)
|
||||
}
|
||||
```
|
63
docs/v2/getting-started.md
Normal file
63
docs/v2/getting-started.md
Normal 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": ["--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.
|
@ -1 +0,0 @@
|
||||
manual.md
|
1703
docs/v2/manual.md
1703
docs/v2/manual.md
File diff suppressed because it is too large
Load Diff
12
docs/v2/migrating-from-older-releases.md
Normal file
12
docs/v2/migrating-from-older-releases.md
Normal 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.
|
126
flag-spec.yaml
126
flag-spec.yaml
@ -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:
|
||||
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
63
flag.go
@ -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 {
|
||||
|
85
flag_bool.go
85
flag_bool.go
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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{} {
|
||||
|
11
flag_int.go
11
flag_int.go
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
673
flag_test.go
673
flag_test.go
@ -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)
|
||||
|
@ -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 {
|
||||
|
11
flag_uint.go
11
flag_uint.go
@ -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 {
|
||||
|
@ -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
212
flag_uint64_slice.go
Normal 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 flag’s 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
223
flag_uint_slice.go
Normal 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 flag’s 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
|
||||
}
|
3
funcs.go
3
funcs.go
@ -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
6
go.mod
@ -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
|
||||
)
|
||||
|
@ -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 flag’s 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 flag’s 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
113
help.go
@ -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
|
||||
}
|
||||
|
100
help_test.go
100
help_test.go
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
@ -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)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
51
mkdocs.yml
51
mkdocs.yml
@ -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:
|
||||
|
23
sliceflag.go
23
sliceflag.go
@ -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 {
|
||||
|
@ -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"},
|
||||
|
17
template.go
17
template.go
@ -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 }}
|
||||
|
514
testdata/godoc-v2.x.txt
vendored
514
testdata/godoc-v2.x.txt
vendored
@ -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 flag’s 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 flag’s 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 flag’s 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 flag’s 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 flag’s 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 flag’s 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 flag’s 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 flag’s 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 flag’s 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 flag’s 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 flag’s 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 flag’s 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 flag’s 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 flag’s 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 flag’s 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 flag’s 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 flag’s 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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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{}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user