Merge branch 'main' into master

main
Dan Buch 2 years ago committed by GitHub
commit 7a231c5eb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,12 +1,10 @@
---
name: ask a question
about: ask us question - assume stackoverflow's guidelines apply here
title: 'q: ( your question title goes here )'
about: ask a question - assume stackoverflow's guidelines apply here
title: your question title goes here
labels: 'kind/question, status/triage, area/v2'
assignees: ''
---
## my question is...
_**( Put the question text here )**_
my question is...

@ -1,28 +1,32 @@
---
name: v1 bug report
about: Create a report to help us fix v1 bugs
title: 'v1 bug: ( your bug title goes here )'
title: 'your bug title goes here'
labels: 'kind/bug, status/triage, area/v1'
assignees: ''
---
## my urfave/cli version is
## My urfave/cli version is
_**( Put the version of urfave/cli that you are using here )**_
## Checklist
* [ ] Are you running the latest v1 release? The list of releases is [here](https://github.com/urfave/cli/releases).
* [ ] Did you check the manual for your release? The v1 manual is [here](https://github.com/urfave/cli/blob/master/docs/v1/manual.md)
* [ ] Did you perform a search about this problem? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching.
- [ ] Are you running the latest v1 release? The list of releases is [here](https://github.com/urfave/cli/releases).
- [ ] Did you check the manual for your release? The v1 manual is [here](https://github.com/urfave/cli/blob/main/docs/v1/manual.md).
- [ ] Did you perform a search about this problem? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching.
## Dependency Management
- [ ] My project is using go modules.
- [ ] My project is using vendoring.
- [ ] My project is automatically downloading the latest version.
- [ ] I am unsure of what my dependency management setup is.
<!--
Delete any of the following that do not apply:
-->
- My project is using go modules.
- My project is using vendoring.
- My project is automatically downloading the latest version.
- I am unsure of what my dependency management setup is.
## Describe the bug
@ -34,23 +38,30 @@ Describe the steps or code required to reproduce the behavior
## Observed behavior
What did you see happen immediately after the reproduction steps above?
What did you see happen immediately after the reproduction steps
above?
## Expected behavior
What would you have expected to happen immediately after the reproduction steps above?
What would you have expected to happen immediately after the
reproduction steps above?
## Additional context
Add any other context about the problem here.
If the issue relates to a specific open source Github repo, please link that repo here.
If the issue relates to a specific open source Github repo, please
link that repo here.
If you can reproduce this issue with a public CI system, please link a failing build here.
If you can reproduce this issue with a public CI system, please
link a failing build here.
## Want to fix this yourself?
We'd love to have more contributors on this project! If the fix for this bug is easily explained and very small, free free to create a pull request for it. You'll want to base the PR off the `v1` branch, all `v1` bug fix releases will be made from that branch.
We'd love to have more contributors on this project! If the fix for
this bug is easily explained and very small, free free to create a
pull request for it. You'll want to base the PR off the `v1`
branch, all `v1` bug fix releases will be made from that branch.
## Run `go version` and paste its output here

@ -1,28 +1,32 @@
---
name: v2 bug report
about: Create a report to help us fix v2 bugs
title: 'v2 bug: ( your bug title goes here )'
title: 'your bug title goes here'
labels: 'kind/bug, area/v2, status/triage'
assignees: ''
---
## my urfave/cli version is
## My urfave/cli version is
_**( Put the version of urfave/cli that you are using here )**_
## Checklist
* [ ] Are you running the latest v2 release? The list of releases is [here](https://github.com/urfave/cli/releases).
* [ ] Did you check the manual for your release? The v2 manual is [here](https://github.com/urfave/cli/blob/master/docs/v2/manual.md)
* [ ] Did you perform a search about this problem? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching.
- [ ] Are you running the latest v2 release? The list of releases is [here](https://github.com/urfave/cli/releases).
- [ ] Did you check the manual for your release? The v2 manual is [here](https://github.com/urfave/cli/blob/main/docs/v2/manual.md)
- [ ] Did you perform a search about this problem? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching.
## Dependency Management
- [ ] My project is using go modules.
- [ ] My project is using vendoring.
- [ ] My project is automatically downloading the latest version.
- [ ] I am unsure of what my dependency management setup is.
<!--
Delete any of the following that do not apply:
-->
- My project is using go modules.
- My project is using vendoring.
- My project is automatically downloading the latest version.
- I am unsure of what my dependency management setup is.
## Describe the bug
@ -34,23 +38,30 @@ Describe the steps or code required to reproduce the behavior
## Observed behavior
What did you see happen immediately after the reproduction steps above?
What did you see happen immediately after the reproduction steps
above?
## Expected behavior
What would you have expected to happen immediately after the reproduction steps above?
What would you have expected to happen immediately after the
reproduction steps above?
## Additional context
Add any other context about the problem here.
If the issue relates to a specific open source Github repo, please link that repo here.
If the issue relates to a specific open source Github repo, please
link that repo here.
If you can reproduce this issue with a public CI system, please link a failing build here.
If you can reproduce this issue with a public CI system, please
link a failing build here.
## Want to fix this yourself?
We'd love to have more contributors on this project! If the fix for this bug is easily explained and very small, free free to create a pull request for it.
We'd love to have more contributors on this project! If the fix for
this bug is easily explained and very small, free free to create a
pull request for it.
## Run `go version` and paste its output here
```

@ -1,7 +1,7 @@
---
name: v2 feature request
about: Suggest an improvement for v2
title: 'v2 feature: ( your feature title goes here )'
title: 'your feature title goes here'
labels: 'type/feature, area/v2, status/triage'
assignees: ''
@ -10,16 +10,19 @@ assignees: ''
## Checklist
* [ ] Are you running the latest v2 release? The list of releases is [here](https://github.com/urfave/cli/releases).
* [ ] Did you check the manual for your release? The v2 manual is [here](https://github.com/urfave/cli/blob/master/docs/v2/manual.md)
* [ ] Did you check the manual for your release? The v2 manual is [here](https://github.com/urfave/cli/blob/main/docs/v2/manual.md).
* [ ] Did you perform a search about this feature? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching.
## What problem does this solve?
A clear and concise description of what problem this feature would solve. For example:
- needing to type out the full flag name takes a long time, so I would like to suggest adding auto-complete
- I use (osx, windows, linux) and would like support for (some existing feature) to be extended to my platform
- the terminal output for a particular error case is confusing, and I think it could be improved
- needing to type out the full flag name takes a long time, so I
would like to suggest adding auto-complete
- I use (osx, windows, linux) and would like support for (some
existing feature) to be extended to my platform
- the terminal output for a particular error case is confusing, and
I think it could be improved
## Solution description
@ -27,4 +30,5 @@ A detailed description of what you want to happen.
## Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
A clear and concise description of any alternative solutions or
features you've considered.

@ -8,10 +8,14 @@
_(REQUIRED)_
- [ ] bug
- [ ] cleanup
- [ ] documentation
- [ ] feature
<!--
Delete any of the following that do not apply:
-->
- bug
- cleanup
- documentation
- feature
## What this PR does / why we need it:
@ -28,6 +32,7 @@ _(REQUIRED)_
## Which issue(s) this PR fixes:
_(REQUIRED)_
<!--
If this PR fixes one of more issues, list them here.
One line each, like so:

72
.github/stale.yml vendored

@ -1,63 +1,17 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 90
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 30
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
onlyLabels: []
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 365
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 90
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- "kind/maintenance"
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: false
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: false
# Set to true to ignore issues with an assignee (defaults to false)
exemptAssignees: false
# Label to use when marking as stale
staleLabel: "status/stale"
# Comment to post when marking as stale. Set to `false` to disable
# Label to use when marking an issue as stale
staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue or PR has been automatically marked as stale because it has not had
recent activity. Please add a comment bumping this if you're still
interested in it's resolution! Thanks for your help, please let us know
if you need anything else.
# Comment to post when removing the stale label.
unmarkComment: >
This issue or PR has been bumped and is no longer marked as stale! Feel free
to bump it again in the future, if it's still relevant.
# Comment to post when closing a stale Issue or Pull Request.
closeComment: >
Closing this as it has become stale.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
# Limit to only `issues` or `pulls`
# only: issues
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
# pulls:
# daysUntilStale: 30
# markComment: >
# This pull request has been automatically marked as stale because it has not had
# recent activity. It will be closed if no further activity occurs. Thank you
# for your contributions.
# issues:
# exemptLabels:
# - confirmed
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

@ -3,43 +3,35 @@ name: Run Tests
on:
push:
branches:
- master
- v1
- main
tags:
- v2.*
pull_request:
branches:
- master
- v1
- main
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
go: [1.12, 1.13, 1.14]
go: [1.16.x, 1.17.x, 1.18.x]
name: ${{ matrix.os }} @ Go ${{ matrix.go }}
runs-on: ${{ matrix.os }}
steps:
- name: Set up Go ${{ matrix.go }}
uses: actions/setup-go@v1
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go }}
- name: Set GOPATH, PATH and ENV
run: |
echo "::set-env name=GOPATH::$(dirname $GITHUB_WORKSPACE)"
echo "::set-env name=GO111MODULE::on"
echo "::set-env name=GOPROXY::https://proxy.golang.org"
echo "::add-path::$(dirname $GITHUB_WORKSPACE)/bin"
shell: bash
- name: Set PATH
run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}"
- name: Checkout Code
uses: actions/checkout@v1
with:
ref: ${{ github.ref }}
uses: actions/checkout@v3
- name: GOFMT Check
if: matrix.go == 1.14 && matrix.os == 'ubuntu-latest'
if: matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest'
run: test -z $(gofmt -l .)
- name: vet
@ -52,54 +44,40 @@ jobs:
run: go run internal/build/build.go check-binary-size
- name: Upload coverage to Codecov
if: success() && matrix.go == 1.14 && matrix.os == 'ubuntu-latest'
uses: codecov/codecov-action@v1
if: success() && matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest'
uses: codecov/codecov-action@v2
with:
token: 0a8cc73b-bb7c-480b-8626-38a461643761
fail_ci_if_error: true
test-docs:
name: test-docs
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.14
uses: actions/setup-go@v1
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.14
go-version: 1.18.x
- name: Use Node.js 12.x
uses: actions/setup-node@v1
- name: Use Node.js 16
uses: actions/setup-node@v3
with:
node-version: 12.x
node-version: '16'
- name: Set GOPATH, PATH and ENV
run: |
echo "::set-env name=GOPATH::$(dirname $GITHUB_WORKSPACE)"
echo "::set-env name=GO111MODULE::on"
echo "::set-env name=GOPROXY::https://proxy.golang.org"
echo "::add-path::$(dirname $GITHUB_WORKSPACE)/bin"
shell: bash
- name: Set PATH
run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}"
- name: Checkout Code
uses: actions/checkout@v1
with:
ref: ${{ github.ref }}
uses: actions/checkout@v3
- name: Install Dependencies
run: |
mkdir -p $GOPATH/bin
curl -L -o $GOPATH/bin/gfmrun "https://github.com/urfave/gfmrun/releases/download/v1.2.14/gfmrun-$(go env GOOS)-amd64-v1.2.14"
chmod +x $GOPATH/bin/gfmrun
run:
mkdir -p "${GITHUB_WORKSPACE}/.local/bin" &&
curl -fsSL -o "${GITHUB_WORKSPACE}/.local/bin/gfmrun" "https://github.com/urfave/gfmrun/releases/download/v1.3.0/gfmrun-$(go env GOOS)-$(go env GOARCH)-v1.3.0" &&
chmod +x "${GITHUB_WORKSPACE}/.local/bin/gfmrun" &&
npm install -g markdown-toc@1.2.0
- name: Run Tests (v1)
if: contains(github.base_ref, 'v1')
run: |
go run internal/build/build.go gfmrun docs/v1/manual.md
go run internal/build/build.go toc docs/v1/manual.md
- name: Run Tests (v2)
if: contains(github.base_ref, 'master')
run: |
go run internal/build/build.go gfmrun docs/v2/manual.md
go run internal/build/build.go toc docs/v2/manual.md
- name: gfmrun
run: go run internal/build/build.go gfmrun docs/v2/manual.md
- name: toc
run: go run internal/build/build.go toc docs/v2/manual.md

3
.gitignore vendored

@ -5,3 +5,6 @@ vendor
.idea
internal/*/built-example
coverage.txt
/.local/
*.exe

@ -1,10 +1,10 @@
cli
===
[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli)
[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://pkg.go.dev/github.com/urfave/cli/v2)
[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli)
[![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli)
[![codecov](https://codecov.io/gh/urfave/cli/branch/master/graph/badge.svg)](https://codecov.io/gh/urfave/cli)
[![codecov](https://codecov.io/gh/urfave/cli/branch/main/graph/badge.svg)](https://codecov.io/gh/urfave/cli)
cli is a simple, fast, and fun package for building command line apps in Go. The
goal is to enable developers to write fast and distributable command line
@ -12,7 +12,7 @@ applications in an expressive way.
## Usage Documentation
Usage documentation exists for each major version. Don't know what version you're on? You're probably using the version from the `master` branch, which is currently `v2`.
Usage documentation exists for each major version. Don't know what version you're on? You're probably using the version from the `main` branch, which is currently `v2`.
- `v2` - [./docs/v2/manual.md](./docs/v2/manual.md)
- `v1` - [./docs/v1/manual.md](./docs/v1/manual.md)
@ -30,7 +30,7 @@ Go Modules are required when using this package. [See the go blog guide on using
### Using `v2` releases
```
$ GO111MODULE=on go get github.com/urfave/cli/v2
$ go get github.com/urfave/cli/v2
```
```go
@ -44,7 +44,7 @@ import (
### Using `v1` releases
```
$ GO111MODULE=on go get github.com/urfave/cli
$ go get github.com/urfave/cli
```
```go
@ -67,4 +67,4 @@ export PATH=$PATH:$GOPATH/bin
cli is tested against multiple versions of Go on Linux, and against the latest
released version of Go on OS X and Windows. This project uses Github Actions for
builds. To see our currently supported go versions and platforms, look at the [./.github/workflows/cli.yml](https://github.com/urfave/cli/blob/master/.github/workflows/cli.yml).
builds. To see our currently supported go versions and platforms, look at the [./.github/workflows/cli.yml](https://github.com/urfave/cli/blob/main/.github/workflows/cli.yml).

@ -16,6 +16,11 @@ type MapInputSource struct {
valueMap map[interface{}]interface{}
}
// NewMapInputSource creates a new MapInputSource for implementing custom input sources.
func NewMapInputSource(file string, valueMap map[interface{}]interface{}) *MapInputSource {
return &MapInputSource{file: file, valueMap: valueMap}
}
// nestedVal checks if the name has '.' delimiters.
// If so, it tries to traverse the tree by the '.' delimited sections to find
// a nested value for the key.

@ -6,14 +6,13 @@ import (
)
func TestMapDuration(t *testing.T) {
inputSource := &MapInputSource{
file: "test",
valueMap: map[interface{}]interface{}{
inputSource := NewMapInputSource(
"test",
map[interface{}]interface{}{
"duration_of_duration_type": time.Minute,
"duration_of_string_type": "1m",
"duration_of_int_type": 1000,
},
}
})
d, err := inputSource.Duration("duration_of_duration_type")
expect(t, time.Minute, d)
expect(t, nil, err)

@ -12,7 +12,7 @@ import (
)
var (
changeLogURL = "https://github.com/urfave/cli/blob/master/docs/CHANGELOG.md"
changeLogURL = "https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md"
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
errInvalidActionType = NewExitError("ERROR invalid Action type. "+
@ -64,7 +64,7 @@ type App struct {
Action ActionFunc
// Execute this function if the proper command cannot be found
CommandNotFound CommandNotFoundFunc
// Execute this function if an usage error occurs
// Execute this function if a usage error occurs
OnUsageError OnUsageErrorFunc
// Compilation date
Compiled time.Time
@ -72,6 +72,8 @@ type App struct {
Authors []*Author
// Copyright of the binary if any
Copyright string
// Reader reader to write input to (useful for tests)
Reader io.Reader
// Writer writer to write output to
Writer io.Writer
// ErrWriter writes error output
@ -117,6 +119,7 @@ func NewApp() *App {
BashComplete: DefaultAppComplete,
Action: helpCommand.Action,
Compiled: compileTime(),
Reader: os.Stdin,
Writer: os.Stdout,
ErrWriter: os.Stderr,
}
@ -137,7 +140,7 @@ func (a *App) Setup() {
}
if a.HelpName == "" {
a.HelpName = filepath.Base(os.Args[0])
a.HelpName = a.Name
}
if a.Usage == "" {
@ -160,6 +163,10 @@ func (a *App) Setup() {
a.Compiled = compileTime()
}
if a.Reader == nil {
a.Reader = os.Stdin
}
if a.Writer == nil {
a.Writer = os.Stdout
}
@ -271,7 +278,7 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
return nil
}
cerr := checkRequiredFlags(a.Flags, context)
cerr := context.checkRequiredFlags(a.Flags)
if cerr != nil {
_ = ShowAppHelp(context)
return cerr
@ -321,7 +328,7 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
// RunAndExitOnError calls .Run() and exits non-zero if an error was returned
//
// Deprecated: instead you should return an error that fulfills cli.ExitCoder
// to cli.App.Run. This will cause the application to exit with the given eror
// to cli.App.Run. This will cause the application to exit with the given error
// code in the cli.ExitCoder
func (a *App) RunAndExitOnError() {
if err := a.Run(os.Args); err != nil {
@ -390,7 +397,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
}
}
cerr := checkRequiredFlags(a.Flags, context)
cerr := context.checkRequiredFlags(a.Flags)
if cerr != nil {
_ = ShowSubcommandHelp(context)
return cerr

@ -315,7 +315,6 @@ func ExampleApp_Run_bashComplete_withMultipleLongFlag() {
}
func ExampleApp_Run_bashComplete() {
// set args for examples sake
// set args for examples sake
os.Args = []string{"greet", "--generate-bash-completion"}
@ -433,6 +432,12 @@ func TestApp_Command(t *testing.T) {
}
}
func TestApp_Setup_defaultsReader(t *testing.T) {
app := &App{}
app.Setup()
expect(t, app.Reader, os.Stdin)
}
func TestApp_Setup_defaultsWriter(t *testing.T) {
app := &App{}
app.Setup()
@ -471,18 +476,18 @@ func TestApp_RunAsSubCommandIncorrectUsage(t *testing.T) {
a := App{
Name: "cmd",
Flags: []Flag{
&StringFlag{Name: "--foo"},
&StringFlag{Name: "foo"},
},
Writer: bytes.NewBufferString(""),
}
set := flag.NewFlagSet("", flag.ContinueOnError)
_ = set.Parse([]string{"", "---foo"})
_ = set.Parse([]string{"", "-bar"})
c := &Context{flagSet: set}
err := a.RunAsSubcommand(c)
expect(t, err, errors.New("bad flag syntax: ---foo"))
expect(t, err.Error(), "flag provided but not defined: -bar")
}
func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
@ -850,6 +855,15 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) {
}
}
func TestApp_DefaultStdin(t *testing.T) {
app := &App{}
app.Setup()
if app.Reader != os.Stdin {
t.Error("Default input reader not set.")
}
}
func TestApp_DefaultStdout(t *testing.T) {
app := &App{}
app.Setup()
@ -859,6 +873,62 @@ func TestApp_DefaultStdout(t *testing.T) {
}
}
func TestApp_SetStdin(t *testing.T) {
buf := make([]byte, 12)
app := &App{
Name: "test",
Reader: strings.NewReader("Hello World!"),
Action: func(c *Context) error {
_, err := c.App.Reader.Read(buf)
return err
},
}
err := app.Run([]string{"help"})
if err != nil {
t.Fatalf("Run error: %s", err)
}
if string(buf) != "Hello World!" {
t.Error("App did not read input from desired reader.")
}
}
func TestApp_SetStdin_Subcommand(t *testing.T) {
buf := make([]byte, 12)
app := &App{
Name: "test",
Reader: strings.NewReader("Hello World!"),
Commands: []*Command{
{
Name: "command",
Subcommands: []*Command{
{
Name: "subcommand",
Action: func(c *Context) error {
_, err := c.App.Reader.Read(buf)
return err
},
},
},
},
},
}
err := app.Run([]string{"test", "command", "subcommand"})
if err != nil {
t.Fatalf("Run error: %s", err)
}
if string(buf) != "Hello World!" {
t.Error("App did not read input from desired reader.")
}
}
func TestApp_SetStdout(t *testing.T) {
var w bytes.Buffer

@ -0,0 +1,9 @@
$fn = $($MyInvocation.MyCommand.Name)
$name = $fn -replace "(.*)\.ps1$", '$1'
Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock {
param($commandName, $wordToComplete, $cursorPosition)
$other = "$wordToComplete --generate-bash-completion"
Invoke-Expression $other | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
}
}

@ -127,7 +127,7 @@ func (c *Command) Run(ctx *Context) (err error) {
return nil
}
cerr := checkRequiredFlags(c.Flags, context)
cerr := context.checkRequiredFlags(c.Flags)
if cerr != nil {
_ = ShowCommandHelp(context, c.Name)
return cerr
@ -227,6 +227,7 @@ func (c *Command) startApp(ctx *Context) error {
}
app.Usage = c.Usage
app.UsageText = c.UsageText
app.Description = c.Description
app.ArgsUsage = c.ArgsUsage
@ -243,6 +244,7 @@ func (c *Command) startApp(ctx *Context) error {
app.Version = ctx.App.Version
app.HideVersion = true
app.Compiled = ctx.App.Compiled
app.Reader = ctx.App.Reader
app.Writer = ctx.App.Writer
app.ErrWriter = ctx.App.ErrWriter
app.ExitErrHandler = ctx.App.ExitErrHandler

@ -2,9 +2,7 @@ package cli
import (
"context"
"errors"
"flag"
"fmt"
"strings"
)
@ -53,20 +51,18 @@ func (c *Context) Set(name, value string) error {
// IsSet determines if the flag was actually set
func (c *Context) IsSet(name string) bool {
if fs := lookupFlagSet(name, c); fs != nil {
if fs := lookupFlagSet(name, c); fs != nil {
isSet := false
fs.Visit(func(f *flag.Flag) {
if f.Name == name {
isSet = true
}
})
if isSet {
return true
if fs := c.lookupFlagSet(name); fs != nil {
isSet := false
fs.Visit(func(f *flag.Flag) {
if f.Name == name {
isSet = true
}
})
if isSet {
return true
}
f := lookupFlag(name, c)
f := c.lookupFlag(name)
if f == nil {
return false
}
@ -108,7 +104,10 @@ func (c *Context) Lineage() []*Context {
// Value returns the value of the flag corresponding to `name`
func (c *Context) Value(name string) interface{} {
return c.flagSet.Lookup(name).Value.(flag.Getter).Get()
if fs := c.lookupFlagSet(name); fs != nil {
return fs.Lookup(name).Value.(flag.Getter).Get()
}
return nil
}
// Args returns the command line arguments associated with the context.
@ -122,7 +121,7 @@ func (c *Context) NArg() int {
return c.Args().Len()
}
func lookupFlag(name string, ctx *Context) Flag {
func (ctx *Context) lookupFlag(name string) Flag {
for _, c := range ctx.Lineage() {
if c.Command == nil {
continue
@ -150,8 +149,11 @@ func lookupFlag(name string, ctx *Context) Flag {
return nil
}
func lookupFlagSet(name string, ctx *Context) *flag.FlagSet {
func (ctx *Context) lookupFlagSet(name string) *flag.FlagSet {
for _, c := range ctx.Lineage() {
if c.flagSet == nil {
continue
}
if f := c.flagSet.Lookup(name); f != nil {
return c.flagSet
}
@ -160,89 +162,7 @@ func lookupFlagSet(name string, ctx *Context) *flag.FlagSet {
return nil
}
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
switch ff.Value.(type) {
case Serializer:
_ = set.Set(name, ff.Value.(Serializer).Serialize())
default:
_ = set.Set(name, ff.Value.String())
}
}
func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
visited := make(map[string]bool)
set.Visit(func(f *flag.Flag) {
visited[f.Name] = true
})
for _, f := range flags {
parts := f.Names()
if len(parts) == 1 {
continue
}
var ff *flag.Flag
for _, name := range parts {
name = strings.Trim(name, " ")
if visited[name] {
if ff != nil {
return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name)
}
ff = set.Lookup(name)
}
}
if ff == nil {
continue
}
for _, name := range parts {
name = strings.Trim(name, " ")
if !visited[name] {
copyFlag(name, ff, set)
}
}
}
return nil
}
func makeFlagNameVisitor(names *[]string) func(*flag.Flag) {
return func(f *flag.Flag) {
nameParts := strings.Split(f.Name, ",")
name := strings.TrimSpace(nameParts[0])
for _, part := range nameParts {
part = strings.TrimSpace(part)
if len(part) > len(name) {
name = part
}
}
if name != "" {
*names = append(*names, name)
}
}
}
type requiredFlagsErr interface {
error
getMissingFlags() []string
}
type errRequiredFlags struct {
missingFlags []string
}
func (e *errRequiredFlags) Error() string {
numberOfMissingFlags := len(e.missingFlags)
if numberOfMissingFlags == 1 {
return fmt.Sprintf("Required flag %q not set", e.missingFlags[0])
}
joinedMissingFlags := strings.Join(e.missingFlags, ", ")
return fmt.Sprintf("Required flags %q not set", joinedMissingFlags)
}
func (e *errRequiredFlags) getMissingFlags() []string {
return e.missingFlags
}
func checkRequiredFlags(flags []Flag, context *Context) requiredFlagsErr {
func (context *Context) checkRequiredFlags(flags []Flag) requiredFlagsErr {
var missingFlags []string
for _, f := range flags {
if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() {
@ -271,3 +191,21 @@ func checkRequiredFlags(flags []Flag, context *Context) requiredFlagsErr {
return nil
}
func makeFlagNameVisitor(names *[]string) func(*flag.Flag) {
return func(f *flag.Flag) {
nameParts := strings.Split(f.Name, ",")
name := strings.TrimSpace(nameParts[0])
for _, part := range nameParts {
part = strings.TrimSpace(part)
if len(part) > len(name) {
name = part
}
}
if name != "" {
*names = append(*names, name)
}
}
}

@ -112,6 +112,8 @@ func TestContext_String(t *testing.T) {
c := NewContext(nil, set, parentCtx)
expect(t, c.String("myflag"), "hello world")
expect(t, c.String("top-flag"), "hai veld")
c = NewContext(nil, nil, parentCtx)
expect(t, c.String("top-flag"), "hai veld")
}
func TestContext_Path(t *testing.T) {
@ -136,6 +138,18 @@ func TestContext_Bool(t *testing.T) {
expect(t, c.Bool("top-flag"), true)
}
func TestContext_Value(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Int("myflag", 12, "doc")
parentSet := flag.NewFlagSet("test", 0)
parentSet.Int("top-flag", 13, "doc")
parentCtx := NewContext(nil, parentSet, nil)
c := NewContext(nil, set, parentCtx)
expect(t, c.Value("myflag"), 12)
expect(t, c.Value("top-flag"), 13)
expect(t, c.Value("unknown-flag"), nil)
}
func TestContext_Args(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("myflag", false, "doc")
@ -304,13 +318,13 @@ func TestContext_lookupFlagSet(t *testing.T) {
_ = set.Parse([]string{"--local-flag"})
_ = parentSet.Parse([]string{"--top-flag"})
fs := lookupFlagSet("top-flag", ctx)
fs := ctx.lookupFlagSet("top-flag")
expect(t, fs, parentCtx.flagSet)
fs = lookupFlagSet("local-flag", ctx)
fs = ctx.lookupFlagSet("local-flag")
expect(t, fs, ctx.flagSet)
if fs := lookupFlagSet("frob", ctx); fs != nil {
if fs := ctx.lookupFlagSet("frob"); fs != nil {
t.Fail()
}
}
@ -564,7 +578,7 @@ func TestCheckRequiredFlags(t *testing.T) {
ctx.Command.Flags = test.flags
// logic under test
err := checkRequiredFlags(test.flags, ctx)
err := ctx.checkRequiredFlags(test.flags)
// assertions
if test.expectedAnError && err == nil {

@ -15,31 +15,39 @@ import (
// The function errors if either parsing or writing of the string fails.
func (a *App) ToMarkdown() (string, error) {
var w bytes.Buffer
if err := a.writeDocTemplate(&w); err != nil {
if err := a.writeDocTemplate(&w, 0); err != nil {
return "", err
}
return w.String(), nil
}
// ToMan creates a man page string for the `*App`
// ToMan creates a man page string with section number for the `*App`
// The function errors if either parsing or writing of the string fails.
func (a *App) ToMan() (string, error) {
func (a *App) ToManWithSection(sectionNumber int) (string, error) {
var w bytes.Buffer
if err := a.writeDocTemplate(&w); err != nil {
if err := a.writeDocTemplate(&w, sectionNumber); err != nil {
return "", err
}
man := md2man.Render(w.Bytes())
return string(man), nil
}
// ToMan creates a man page string for the `*App`
// The function errors if either parsing or writing of the string fails.
func (a *App) ToMan() (string, error) {
man, err := a.ToManWithSection(8)
return man, err
}
type cliTemplate struct {
App *App
SectionNum int
Commands []string
GlobalArgs []string
SynopsisArgs []string
}
func (a *App) writeDocTemplate(w io.Writer) error {
func (a *App) writeDocTemplate(w io.Writer, sectionNum int) error {
const name = "cli"
t, err := template.New(name).Parse(MarkdownDocTemplate)
if err != nil {
@ -47,6 +55,7 @@ func (a *App) writeDocTemplate(w io.Writer) error {
}
return t.ExecuteTemplate(w, name, &cliTemplate{
App: a,
SectionNum: sectionNum,
Commands: prepareCommands(a.Commands, 0),
GlobalArgs: prepareArgsWithValues(a.VisibleFlags()),
SynopsisArgs: prepareArgsSynopsis(a.VisibleFlags()),
@ -59,15 +68,16 @@ func prepareCommands(commands []*Command, level int) []string {
if command.Hidden {
continue
}
usage := ""
if command.Usage != "" {
usage = command.Usage
}
prepared := fmt.Sprintf("%s %s\n\n%s\n",
usageText := prepareUsageText(command)
usage := prepareUsage(command, usageText)
prepared := fmt.Sprintf("%s %s\n\n%s%s",
strings.Repeat("#", level+2),
strings.Join(command.Names(), ", "),
usage,
usageText,
)
flags := prepareArgsWithValues(command.Flags)
@ -146,3 +156,40 @@ func flagDetails(flag DocGenerationFlag) string {
}
return ": " + description
}
func prepareUsageText(command *Command) string {
if command.UsageText == "" {
return ""
}
// Remove leading and trailing newlines
preparedUsageText := strings.Trim(command.UsageText, "\n")
var usageText string
if strings.Contains(preparedUsageText, "\n") {
// Format multi-line string as a code block using the 4 space schema to allow for embedded markdown such
// that it will not break the continuous code block.
for _, ln := range strings.Split(preparedUsageText, "\n") {
usageText += fmt.Sprintf(" %s\n", ln)
}
} else {
// Style a single line as a note
usageText = fmt.Sprintf(">%s\n", preparedUsageText)
}
return usageText
}
func prepareUsage(command *Command, usageText string) string {
if command.Usage == "" {
return ""
}
usage := command.Usage + "\n"
// Add a newline to the Usage IFF there is a UsageText
if usageText != "" {
usage += "\n"
}
return usage
}

@ -6,7 +6,7 @@ Feel free to put up a pull request to fix a bug or maybe add a feature. We will
give it a code review and make sure that it does not break backwards
compatibility. If collaborators agree that it is in line with
the vision of the project, we will work with you to get the code into
a mergeable state and merge it into the master branch.
a mergeable state and merge it into the main branch.
If you have contributed something significant to the project, we will most
likely add you as a collaborator. As a collaborator you are given the ability

@ -15,15 +15,18 @@ consider sending a PR to help improve this guide.
* [Flags before args](#flags-before-args)
* [Import string changed](#import-string-changed)
* [Flag aliases are done differently.](#flag-aliases-are-done-differently)
* [Flag aliases are done differently](#flag-aliases-are-done-differently)
* [EnvVar is now a list (EnvVars)](#envvar-is-now-a-list-envvars)
* [Actions returns errors](#actions-returns-errors)
* [cli.Flag changed](#cliflag-changed)
* [Commands are now lists of pointers](#commands-are-now-lists-of-pointers)
* [Lists of commands should be pointers](#lists-of-commands-should-be-pointers)
* [cli.Flag changed](#cliflag-changed)
* [Appending Commands](#appending-commands)
* [Actions returns errors](#actions-returns-errors)
* [GlobalString, GlobalBool and its likes are deprecated](#globalstring-globalbool-and-its-likes-are-deprecated)
* [BoolTFlag and BoolT are deprecated](#booltflag-and-boolt-are-deprecated)
* [&cli.StringSlice{""} replaced with cli.NewStringSlice("")](#clistringslice-replaced-with-clinewstringslice)
* [Replace deprecated functions](#replace-deprecated-functions)
* [Everything else](#everything-else)
* [Full API Example](#full-api-example)
<!-- tocstop -->
@ -53,7 +56,7 @@ Check each file for this and make the change.
Shell command to find them all: `fgrep -rl github.com/urfave/cli *`
# Flag aliases are done differently.
# Flag aliases are done differently
Change `Name: "foo, f"` to `Name: "foo", Aliases: []string{"f"}`
@ -181,6 +184,52 @@ Compiler messages you might see:
cannot use c (type *cli.Command) as type cli.Command in append
```
# GlobalString, GlobalBool and its likes are deprecated
Use simply `String` instead of `GlobalString`, `Bool` instead of `GlobalBool`
# BoolTFlag and BoolT are deprecated
BoolTFlag was a Bool Flag with its default value set to true and BoolT was used to find any BoolTFlag used locally, so both are deprecated.
* OLD:
```go
cli.BoolTFlag{
Name: FlagName,
Usage: FlagUsage,
EnvVar: "FLAG_ENV_VAR",
}
```
* NEW:
```go
cli.BoolFlag{
Name: FlagName,
Value: true,
Usage: FlagUsage,
EnvVar: "FLAG_ENV_VAR",
}
```
# &cli.StringSlice{""} replaced with cli.NewStringSlice("")
Example:
* OLD:
```go
Value: &cli.StringSlice{""},
```
* NEW:
```go
Value: cli.NewStringSlice(""),
}
```
# Replace deprecated functions
`cli.NewExitError()` is deprecated. Use `cli.Exit()` instead. ([Staticcheck](https://staticcheck.io/) detects this automatically and recommends replacement code.)
# Everything else
Compile the code and work through any errors. Most should

@ -612,7 +612,7 @@ given sources.
Here is a more complete sample of a command using YAML support:
<!-- {
"args": ["test-cmd", "&#45;&#45;help"],
"args": ["&#45;&#45;help"],
"output": "&#45&#45;test value.*default: 0"
} -->
``` go

@ -30,6 +30,7 @@ cli v2 manual
+ [ZSH Support](#zsh-support)
+ [ZSH default auto-complete example](#zsh-default-auto-complete-example)
+ [ZSH custom auto-complete example](#zsh-custom-auto-complete-example)
+ [PowerShell Support](#powershell-support)
* [Generated Help Text](#generated-help-text)
+ [Customization](#customization-1)
* [Version Flag](#version-flag)
@ -44,7 +45,7 @@ cli v2 manual
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).
[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.
## Getting Started
@ -425,15 +426,17 @@ import (
func main() {
app := &cli.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`",
},
&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{
{
@ -511,7 +514,7 @@ func main() {
```
If `EnvVars` contains more than one string, the first environment variable that
resolves is used as the default.
resolves is used.
<!-- {
"args": ["&#45;&#45;help"],
@ -570,7 +573,8 @@ func main() {
app.Flags = []cli.Flag {
&cli.StringFlag{
Name: "password, p",
Name: "password",
Aliases: []string{"p"},
Usage: "password for the mysql database",
FilePath: "/etc/mysql/password",
},
@ -623,7 +627,7 @@ given sources.
Here is a more complete sample of a command using YAML support:
<!-- {
"args": ["test-cmd", "&#45;&#45;help"],
"args": ["&#45;&#45;help"],
"output": "&#45&#45;test value.*default: 0"
} -->
``` go
@ -645,7 +649,7 @@ func main() {
app := &cli.App{
Action: func(c *cli.Context) error {
fmt.Println("yaml ist rad")
fmt.Println("--test value.*default: 0")
return nil
},
Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")),
@ -670,8 +674,10 @@ Take for example this app that requires the `lang` flag:
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
@ -1222,6 +1228,23 @@ source path/to/autocomplete/zsh_autocomplete
#### 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 `& 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:
```
& path/to/autocomplete/<my program>.ps1
```
### Generated Help Text
The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked
@ -1309,7 +1332,8 @@ import (
func main() {
cli.HelpFlag = &cli.BoolFlag{
Name: "haaaaalp", Aliases: []string{"halp"},
Name: "haaaaalp",
Aliases: []string{"halp"},
Usage: "HALP",
EnvVars: []string{"SHOW_HALP", "HALPPLZ"},
}
@ -1344,7 +1368,8 @@ import (
func main() {
cli.VersionFlag = &cli.BoolFlag{
Name: "print-version", Aliases: []string{"V"},
Name: "print-version",
Aliases: []string{"V"},
Usage: "print only the version",
}

@ -2,6 +2,7 @@ package cli
import (
"bytes"
"errors"
"io/ioutil"
"testing"
)
@ -66,8 +67,50 @@ func testApp() *App {
}, {
Name: "hidden-command",
Hidden: true,
}, {
Aliases: []string{"u"},
Flags: []Flag{
&StringFlag{
Name: "flag",
Aliases: []string{"fl", "f"},
TakesFile: true,
},
&BoolFlag{
Name: "another-flag",
Aliases: []string{"b"},
Usage: "another usage text",
},
},
Name: "usage",
Usage: "standard usage text",
UsageText: `
Usage for the usage text
- formatted: Based on the specified ConfigMap and summon secrets.yml
- list: Inspect the environment for a specific process running on a Pod
- for_effect: Compare 'namespace' environment with 'local'
` + "```" + `
func() { ... }
` + "```" + `
Should be a part of the same code block
`,
Subcommands: []*Command{{
Aliases: []string{"su"},
Flags: []Flag{
&BoolFlag{
Name: "sub-command-flag",
Aliases: []string{"s"},
Usage: "some usage text",
},
},
Name: "sub-usage",
Usage: "standard usage text",
UsageText: "Single line of UsageText",
}},
}}
app.UsageText = "app [first_arg] [second_arg]"
app.Description = `Description of the application.`
app.Usage = "Some app"
app.Authors = []*Author{
{Name: "Harrison", Email: "harrison@lolwut.com"},
@ -76,13 +119,13 @@ func testApp() *App {
return app
}
func expectFileContent(t *testing.T, file, expected string) {
func expectFileContent(t *testing.T, file, got string) {
data, err := ioutil.ReadFile(file)
// Ignore windows line endings
// TODO: Replace with bytes.ReplaceAll when support for Go 1.11 is dropped
data = bytes.Replace(data, []byte("\r\n"), []byte("\n"), -1)
expect(t, err, nil)
expect(t, string(data), expected)
expect(t, got, string(data))
}
func TestToMarkdownFull(t *testing.T) {
@ -136,6 +179,19 @@ func TestToMarkdownNoAuthors(t *testing.T) {
expectFileContent(t, "testdata/expected-doc-no-authors.md", res)
}
func TestToMarkdownNoUsageText(t *testing.T) {
// Given
app := testApp()
app.UsageText = ""
// When
res, err := app.ToMarkdown()
// Then
expect(t, err, nil)
expectFileContent(t, "testdata/expected-doc-no-usagetext.md", res)
}
func TestToMan(t *testing.T) {
// Given
app := testApp()
@ -147,3 +203,137 @@ func TestToMan(t *testing.T) {
expect(t, err, nil)
expectFileContent(t, "testdata/expected-doc-full.man", res)
}
func TestToManParseError(t *testing.T) {
// Given
app := testApp()
// When
// temporarily change the global variable for testing
tmp := MarkdownDocTemplate
MarkdownDocTemplate = `{{ .App.Name`
_, err := app.ToMan()
MarkdownDocTemplate = tmp
// Then
expect(t, err, errors.New(`template: cli:1: unclosed action`))
}
func TestToManWithSection(t *testing.T) {
// Given
app := testApp()
// When
res, err := app.ToManWithSection(8)
// Then
expect(t, err, nil)
expectFileContent(t, "testdata/expected-doc-full.man", res)
}
func Test_prepareUsageText(t *testing.T) {
t.Run("no UsageText provided", func(t *testing.T) {
// Given
cmd := Command{}
// When
res := prepareUsageText(&cmd)
// Then
expect(t, res, "")
})
t.Run("single line UsageText", func(t *testing.T) {
// Given
cmd := Command{UsageText: "Single line usage text"}
// When
res := prepareUsageText(&cmd)
// Then
expect(t, res, ">Single line usage text\n")
})
t.Run("multiline UsageText", func(t *testing.T) {
// Given
cmd := Command{
UsageText: `
Usage for the usage text
- Should be a part of the same code block
`,
}
// When
res := prepareUsageText(&cmd)
// Then
test := ` Usage for the usage text
- Should be a part of the same code block
`
expect(t, res, test)
})
t.Run("multiline UsageText has formatted embedded markdown", func(t *testing.T) {
// Given
cmd := Command{
UsageText: `
Usage for the usage text
` + "```" + `
func() { ... }
` + "```" + `
Should be a part of the same code block
`,
}
// When
res := prepareUsageText(&cmd)
// Then
test := ` Usage for the usage text
` + "```" + `
func() { ... }
` + "```" + `
Should be a part of the same code block
`
expect(t, res, test)
})
}
func Test_prepareUsage(t *testing.T) {
t.Run("no Usage provided", func(t *testing.T) {
// Given
cmd := Command{}
// When
res := prepareUsage(&cmd, "")
// Then
expect(t, res, "")
})
t.Run("simple Usage", func(t *testing.T) {
// Given
cmd := Command{Usage: "simple usage text"}
// When
res := prepareUsage(&cmd, "")
// Then
expect(t, res, cmd.Usage+"\n")
})
t.Run("simple Usage with UsageText", func(t *testing.T) {
// Given
cmd := Command{Usage: "simple usage text"}
// When
res := prepareUsage(&cmd, "a non-empty string")
// Then
expect(t, res, cmd.Usage+"\n\n")
})
}

@ -47,6 +47,28 @@ func (m *multiError) Errors() []error {
return errs
}
type requiredFlagsErr interface {
error
getMissingFlags() []string
}
type errRequiredFlags struct {
missingFlags []string
}
func (e *errRequiredFlags) Error() string {
numberOfMissingFlags := len(e.missingFlags)
if numberOfMissingFlags == 1 {
return fmt.Sprintf("Required flag %q not set", e.missingFlags[0])
}
joinedMissingFlags := strings.Join(e.missingFlags, ", ")
return fmt.Sprintf("Required flags %q not set", joinedMissingFlags)
}
func (e *errRequiredFlags) getMissingFlags() []string {
return e.missingFlags
}
// ErrorFormatter is the interface that will suitably format the error output
type ErrorFormatter interface {
Format(s fmt.State, verb rune)

@ -171,6 +171,10 @@ func fishAddFileFlag(flag Flag, completion *strings.Builder) {
if f.TakesFile {
return
}
case *PathFlag:
if f.TakesFile {
return
}
}
completion.WriteString(" -f")
}

@ -7,6 +7,10 @@ import (
func TestFishCompletion(t *testing.T) {
// Given
app := testApp()
app.Flags = append(app.Flags, &PathFlag{
Name: "logfile",
TakesFile: true,
})
// When
res, err := app.ToFishCompletion()

@ -1,6 +1,7 @@
package cli
import (
"errors"
"flag"
"fmt"
"io/ioutil"
@ -118,6 +119,14 @@ type DocGenerationFlag interface {
GetValue() string
}
// VisibleFlag is an interface that allows to check if a flag is visible
type VisibleFlag interface {
Flag
// IsVisible returns true if the flag is not hidden, otherwise false
IsVisible() bool
}
func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
set := flag.NewFlagSet(name, flag.ContinueOnError)
@ -130,11 +139,52 @@ func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
return set, nil
}
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
switch ff.Value.(type) {
case Serializer:
_ = set.Set(name, ff.Value.(Serializer).Serialize())
default:
_ = set.Set(name, ff.Value.String())
}
}
func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
visited := make(map[string]bool)
set.Visit(func(f *flag.Flag) {
visited[f.Name] = true
})
for _, f := range flags {
parts := f.Names()
if len(parts) == 1 {
continue
}
var ff *flag.Flag
for _, name := range parts {
name = strings.Trim(name, " ")
if visited[name] {
if ff != nil {
return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name)
}
ff = set.Lookup(name)
}
}
if ff == nil {
continue
}
for _, name := range parts {
name = strings.Trim(name, " ")
if !visited[name] {
copyFlag(name, ff, set)
}
}
}
return nil
}
func visibleFlags(fl []Flag) []Flag {
var visible []Flag
for _, f := range fl {
field := flagValue(f).FieldByName("Hidden")
if !field.IsValid() || !field.Bool() {
if vf, ok := f.(VisibleFlag); ok && vf.IsVisible() {
visible = append(visible, f)
}
}
@ -359,7 +409,11 @@ func stringifySliceFlag(usage string, names, defaultVals []string) string {
}
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal))
return fmt.Sprintf("%s\t%s", prefixedNames(names, placeholder), usageWithDefault)
multiInputString := "(accepts multiple inputs)"
if usageWithDefault != "" {
multiInputString = "\t" + multiInputString
}
return fmt.Sprintf("%s\t%s%s", prefixedNames(names, placeholder), usageWithDefault, multiInputString)
}
func hasFlag(flags []Flag, fl Flag) bool {
@ -380,8 +434,10 @@ func flagFromEnvOrFile(envVars []string, filePath string) (val string, ok bool)
}
}
for _, fileVar := range strings.Split(filePath, ",") {
if data, err := ioutil.ReadFile(fileVar); err == nil {
return string(data), true
if fileVar != "" {
if data, err := ioutil.ReadFile(fileVar); err == nil {
return string(data), true
}
}
}
return "", false

@ -58,6 +58,11 @@ func (f *BoolFlag) GetValue() string {
return ""
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *BoolFlag) IsVisible() bool {
return !f.Hidden
}
// Apply populates the flag given the flag set and environment
func (f *BoolFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
@ -87,7 +92,7 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error {
// Bool looks up the value of a local BoolFlag, returns
// false if not found
func (c *Context) Bool(name string) bool {
if fs := lookupFlagSet(name, c); fs != nil {
if fs := c.lookupFlagSet(name); fs != nil {
return lookupBool(name, fs)
}
return false

@ -58,6 +58,11 @@ func (f *DurationFlag) GetValue() string {
return f.Value.String()
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *DurationFlag) IsVisible() bool {
return !f.Hidden
}
// Apply populates the flag given the flag set and environment
func (f *DurationFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
@ -86,7 +91,7 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) error {
// Duration looks up the value of a local DurationFlag, returns
// 0 if not found
func (c *Context) Duration(name string) time.Duration {
if fs := lookupFlagSet(name, c); fs != nil {
if fs := c.lookupFlagSet(name); fs != nil {
return lookupDuration(name, fs)
}
return 0

@ -58,12 +58,16 @@ func (f *Float64Flag) GetValue() string {
return fmt.Sprintf("%f", f.Value)
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *Float64Flag) IsVisible() bool {
return !f.Hidden
}
// Apply populates the flag given the flag set and environment
func (f *Float64Flag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
if val != "" {
valFloat, err := strconv.ParseFloat(val, 10)
valFloat, err := strconv.ParseFloat(val, 64)
if err != nil {
return fmt.Errorf("could not parse %q as float64 value for flag %s: %s", val, f.Name, err)
}
@ -87,7 +91,7 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) error {
// Float64 looks up the value of a local Float64Flag, returns
// 0 if not found
func (c *Context) Float64(name string) float64 {
if fs := lookupFlagSet(name, c); fs != nil {
if fs := c.lookupFlagSet(name); fs != nil {
return lookupFloat64(name, fs)
}
return 0

@ -19,6 +19,16 @@ func NewFloat64Slice(defaults ...float64) *Float64Slice {
return &Float64Slice{slice: append([]float64{}, defaults...)}
}
// clone allocate a copy of self object
func (f *Float64Slice) clone() *Float64Slice {
n := &Float64Slice{
slice: make([]float64, len(f.slice)),
hasBeenSet: f.hasBeenSet,
}
copy(n.slice, f.slice)
return n
}
// Set parses the value into a float64 and appends it to the list of values
func (f *Float64Slice) Set(value string) error {
if !f.hasBeenSet {
@ -117,6 +127,11 @@ func (f *Float64SliceFlag) GetValue() string {
return ""
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *Float64SliceFlag) IsVisible() bool {
return !f.Hidden
}
// Apply populates the flag given the flag set and environment
func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
@ -129,15 +144,19 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error {
}
}
// Set this to false so that we reset the slice if we then set values from
// flags that have already been set by the environment.
f.Value.hasBeenSet = false
f.HasBeenSet = true
}
}
if f.Value == nil {
f.Value = &Float64Slice{}
}
copyValue := f.Value.clone()
for _, name := range f.Names() {
if f.Value == nil {
f.Value = &Float64Slice{}
}
set.Var(f.Value, name, f.Usage)
set.Var(copyValue, name, f.Usage)
}
return nil
@ -146,7 +165,7 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error {
// Float64Slice looks up the value of a local Float64SliceFlag, returns
// nil if not found
func (c *Context) Float64Slice(name string) []float64 {
if fs := lookupFlagSet(name, c); fs != nil {
if fs := c.lookupFlagSet(name); fs != nil {
return lookupFloat64Slice(name, fs)
}
return nil

@ -66,6 +66,11 @@ func (f *GenericFlag) GetValue() string {
return ""
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *GenericFlag) IsVisible() bool {
return !f.Hidden
}
// 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 {
@ -89,7 +94,7 @@ func (f GenericFlag) Apply(set *flag.FlagSet) error {
// Generic looks up the value of a local GenericFlag, returns
// nil if not found
func (c *Context) Generic(name string) interface{} {
if fs := lookupFlagSet(name, c); fs != nil {
if fs := c.lookupFlagSet(name); fs != nil {
return lookupGeneric(name, fs)
}
return nil

@ -58,6 +58,11 @@ func (f *IntFlag) GetValue() string {
return fmt.Sprintf("%d", f.Value)
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *IntFlag) IsVisible() bool {
return !f.Hidden
}
// Apply populates the flag given the flag set and environment
func (f *IntFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
@ -87,7 +92,7 @@ func (f *IntFlag) Apply(set *flag.FlagSet) error {
// Int looks up the value of a local IntFlag, returns
// 0 if not found
func (c *Context) Int(name string) int {
if fs := lookupFlagSet(name, c); fs != nil {
if fs := c.lookupFlagSet(name); fs != nil {
return lookupInt(name, fs)
}
return 0

@ -58,6 +58,11 @@ func (f *Int64Flag) GetValue() string {
return fmt.Sprintf("%d", f.Value)
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *Int64Flag) IsVisible() bool {
return !f.Hidden
}
// Apply populates the flag given the flag set and environment
func (f *Int64Flag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
@ -86,7 +91,7 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) error {
// Int64 looks up the value of a local Int64Flag, returns
// 0 if not found
func (c *Context) Int64(name string) int64 {
if fs := lookupFlagSet(name, c); fs != nil {
if fs := c.lookupFlagSet(name); fs != nil {
return lookupInt64(name, fs)
}
return 0

@ -19,6 +19,16 @@ func NewInt64Slice(defaults ...int64) *Int64Slice {
return &Int64Slice{slice: append([]int64{}, defaults...)}
}
// clone allocate a copy of self object
func (i *Int64Slice) clone() *Int64Slice {
n := &Int64Slice{
slice: make([]int64, 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 *Int64Slice) Set(value string) error {
if !i.hasBeenSet {
@ -118,6 +128,11 @@ func (f *Int64SliceFlag) GetValue() string {
return ""
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *Int64SliceFlag) IsVisible() bool {
return !f.Hidden
}
// Apply populates the flag given the flag set and environment
func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
@ -129,14 +144,18 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error {
}
}
// Set this to false so that we reset the slice if we then set values from
// flags that have already been set by the environment.
f.Value.hasBeenSet = false
f.HasBeenSet = true
}
if f.Value == nil {
f.Value = &Int64Slice{}
}
copyValue := f.Value.clone()
for _, name := range f.Names() {
if f.Value == nil {
f.Value = &Int64Slice{}
}
set.Var(f.Value, name, f.Usage)
set.Var(copyValue, name, f.Usage)
}
return nil
@ -145,7 +164,10 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error {
// Int64Slice looks up the value of a local Int64SliceFlag, returns
// nil if not found
func (c *Context) Int64Slice(name string) []int64 {
return lookupInt64Slice(name, c.flagSet)
if fs := c.lookupFlagSet(name); fs != nil {
return lookupInt64Slice(name, fs)
}
return nil
}
func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {

@ -19,6 +19,16 @@ func NewIntSlice(defaults ...int) *IntSlice {
return &IntSlice{slice: append([]int{}, defaults...)}
}
// clone allocate a copy of self object
func (i *IntSlice) clone() *IntSlice {
n := &IntSlice{
slice: make([]int, 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 *IntSlice) SetInt(value int) {
@ -129,6 +139,11 @@ func (f *IntSliceFlag) GetValue() string {
return ""
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *IntSliceFlag) IsVisible() bool {
return !f.Hidden
}
// Apply populates the flag given the flag set and environment
func (f *IntSliceFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
@ -140,14 +155,18 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error {
}
}
// Set this to false so that we reset the slice if we then set values from
// flags that have already been set by the environment.
f.Value.hasBeenSet = false
f.HasBeenSet = true
}
if f.Value == nil {
f.Value = &IntSlice{}
}
copyValue := f.Value.clone()
for _, name := range f.Names() {
if f.Value == nil {
f.Value = &IntSlice{}
}
set.Var(f.Value, name, f.Usage)
set.Var(copyValue, name, f.Usage)
}
return nil
@ -156,8 +175,8 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error {
// IntSlice looks up the value of a local IntSliceFlag, returns
// nil if not found
func (c *Context) IntSlice(name string) []int {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupIntSlice(name, c.flagSet)
if fs := c.lookupFlagSet(name); fs != nil {
return lookupIntSlice(name, fs)
}
return nil
}

@ -54,6 +54,11 @@ func (f *PathFlag) GetValue() string {
return f.Value
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *PathFlag) IsVisible() bool {
return !f.Hidden
}
// Apply populates the flag given the flag set and environment
func (f *PathFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
@ -75,7 +80,7 @@ func (f *PathFlag) Apply(set *flag.FlagSet) error {
// Path looks up the value of a local PathFlag, returns
// "" if not found
func (c *Context) Path(name string) string {
if fs := lookupFlagSet(name, c); fs != nil {
if fs := c.lookupFlagSet(name); fs != nil {
return lookupPath(name, fs)
}

@ -55,6 +55,11 @@ func (f *StringFlag) GetValue() string {
return f.Value
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *StringFlag) IsVisible() bool {
return !f.Hidden
}
// Apply populates the flag given the flag set and environment
func (f *StringFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
@ -76,7 +81,7 @@ func (f *StringFlag) Apply(set *flag.FlagSet) error {
// String looks up the value of a local StringFlag, returns
// "" if not found
func (c *Context) String(name string) string {
if fs := lookupFlagSet(name, c); fs != nil {
if fs := c.lookupFlagSet(name); fs != nil {
return lookupString(name, fs)
}
return ""
@ -85,10 +90,7 @@ func (c *Context) String(name string) string {
func lookupString(name string, set *flag.FlagSet) string {
f := set.Lookup(name)
if f != nil {
parsed, err := f.Value.String(), error(nil)
if err != nil {
return ""
}
parsed := f.Value.String()
return parsed
}
return ""

@ -18,6 +18,16 @@ func NewStringSlice(defaults ...string) *StringSlice {
return &StringSlice{slice: append([]string{}, defaults...)}
}
// clone allocate a copy of self object
func (s *StringSlice) clone() *StringSlice {
n := &StringSlice{
slice: make([]string, len(s.slice)),
hasBeenSet: s.hasBeenSet,
}
copy(n.slice, s.slice)
return n
}
// Set appends the string value to the list of values
func (s *StringSlice) Set(value string) error {
if !s.hasBeenSet {
@ -114,6 +124,11 @@ func (f *StringSliceFlag) GetValue() string {
return ""
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *StringSliceFlag) IsVisible() bool {
return !f.Hidden
}
// Apply populates the flag given the flag set and environment
func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
@ -124,7 +139,9 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
}
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
f.Value = &StringSlice{}
if f.Value == nil {
f.Value = &StringSlice{}
}
destination := f.Value
if f.Destination != nil {
destination = f.Destination
@ -142,17 +159,15 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
f.HasBeenSet = true
}
if f.Value == nil {
f.Value = &StringSlice{}
}
setValue := f.Destination
if f.Destination == nil {
setValue = f.Value.clone()
}
for _, name := range f.Names() {
if f.Value == nil {
f.Value = &StringSlice{}
}
if f.Destination != nil {
set.Var(f.Destination, name, f.Usage)
continue
}
set.Var(f.Value, name, f.Usage)
set.Var(setValue, name, f.Usage)
}
return nil
@ -161,7 +176,7 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
// StringSlice looks up the value of a local StringSliceFlag, returns
// nil if not found
func (c *Context) StringSlice(name string) []string {
if fs := lookupFlagSet(name, c); fs != nil {
if fs := c.lookupFlagSet(name); fs != nil {
return lookupStringSlice(name, fs)
}
return nil

@ -52,15 +52,21 @@ func TestBoolFlagApply_SetsAllNames(t *testing.T) {
}
func TestFlagsFromEnv(t *testing.T) {
newSetFloat64Slice := func(defaults ...float64) Float64Slice {
s := NewFloat64Slice(defaults...)
s.hasBeenSet = false
return *s
}
newSetIntSlice := func(defaults ...int) IntSlice {
s := NewIntSlice(defaults...)
s.hasBeenSet = true
s.hasBeenSet = false
return *s
}
newSetInt64Slice := func(defaults ...int64) Int64Slice {
s := NewInt64Slice(defaults...)
s.hasBeenSet = true
s.hasBeenSet = false
return *s
}
@ -96,6 +102,9 @@ func TestFlagsFromEnv(t *testing.T) {
{"1.2", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value for flag seconds: .*`},
{"foobar", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value for flag seconds: .*`},
{"1.0,2", newSetFloat64Slice(1, 2), &Float64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
{"foobar", newSetFloat64Slice(), &Float64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "\[\]float64{}" as float64 slice value for flag seconds: .*`},
{"1,2", newSetIntSlice(1, 2), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
{"1.2,2", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int slice value for flag seconds: .*`},
{"foobar", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int slice value for flag seconds: .*`},
@ -340,11 +349,11 @@ var stringSliceFlagTests = []struct {
value *StringSlice
expected string
}{
{"foo", nil, NewStringSlice(""), "--foo value\t"},
{"f", nil, NewStringSlice(""), "-f value\t"},
{"f", nil, NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")"},
{"test", nil, NewStringSlice("Something"), "--test value\t(default: \"Something\")"},
{"dee", []string{"d"}, NewStringSlice("Inka", "Dinka", "dooo"), "--dee value, -d value\t(default: \"Inka\", \"Dinka\", \"dooo\")"},
{"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)"},
}
func TestStringSliceFlagHelpOutput(t *testing.T) {
@ -386,6 +395,20 @@ func TestStringSliceFlagApply_SetsAllNames(t *testing.T) {
expect(t, err, nil)
}
func TestStringSliceFlagApply_UsesEnvValues(t *testing.T) {
defer resetEnv(os.Environ())
os.Clearenv()
_ = os.Setenv("MY_GOAT", "vincent van goat,scape goat")
var val StringSlice
fl := StringSliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: &val}
set := flag.NewFlagSet("test", 0)
_ = fl.Apply(set)
err := set.Parse(nil)
expect(t, err, nil)
expect(t, val.Value(), NewStringSlice("vincent van goat", "scape goat").Value())
}
func TestStringSliceFlagApply_DefaultValueWithDestination(t *testing.T) {
defValue := []string{"UA", "US"}
@ -616,9 +639,9 @@ var intSliceFlagTests = []struct {
value *IntSlice
expected string
}{
{"heads", nil, NewIntSlice(), "--heads value\t"},
{"H", nil, NewIntSlice(), "-H value\t"},
{"H", []string{"heads"}, NewIntSlice(9, 3), "-H value, --heads value\t(default: 9, 3)"},
{"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)"},
}
func TestIntSliceFlagHelpOutput(t *testing.T) {
@ -660,16 +683,55 @@ func TestIntSliceFlagApply_SetsAllNames(t *testing.T) {
expect(t, err, nil)
}
func TestIntSliceFlagApply_ParentContext(t *testing.T) {
_ = (&App{
Flags: []Flag{
&IntSliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewIntSlice(1, 2, 3)},
},
Commands: []*Command{
{
Name: "child",
Action: func(ctx *Context) error {
expected := []int{1, 2, 3}
if !reflect.DeepEqual(ctx.IntSlice("numbers"), expected) {
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.IntSlice("numbers"))
}
if !reflect.DeepEqual(ctx.IntSlice("n"), expected) {
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.IntSlice("n"))
}
return nil
},
},
},
}).Run([]string{"run", "child"})
}
func TestIntSliceFlag_SetFromParentContext(t *testing.T) {
fl := &IntSliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewIntSlice(1, 2, 3, 4)}
set := flag.NewFlagSet("test", 0)
_ = fl.Apply(set)
ctx := &Context{
parentContext: &Context{
flagSet: set,
},
flagSet: flag.NewFlagSet("empty", 0),
}
expected := []int{1, 2, 3, 4}
if !reflect.DeepEqual(ctx.IntSlice("numbers"), expected) {
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.IntSlice("numbers"))
}
}
var int64SliceFlagTests = []struct {
name string
aliases []string
value *Int64Slice
expected string
}{
{"heads", nil, NewInt64Slice(), "--heads value\t"},
{"H", nil, NewInt64Slice(), "-H value\t"},
{"heads", nil, NewInt64Slice(), "--heads value\t(accepts multiple inputs)"},
{"H", nil, NewInt64Slice(), "-H value\t(accepts multiple inputs)"},
{"heads", []string{"H"}, NewInt64Slice(int64(2), int64(17179869184)),
"--heads value, -H value\t(default: 2, 17179869184)"},
"--heads value, -H value\t(default: 2, 17179869184)\t(accepts multiple inputs)"},
}
func TestInt64SliceFlagHelpOutput(t *testing.T) {
@ -702,6 +764,60 @@ func TestInt64SliceFlagWithEnvVarHelpOutput(t *testing.T) {
}
}
func TestInt64SliceFlagApply_ParentContext(t *testing.T) {
_ = (&App{
Flags: []Flag{
&Int64SliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewInt64Slice(1, 2, 3)},
},
Commands: []*Command{
{
Name: "child",
Action: func(ctx *Context) error {
expected := []int64{1, 2, 3}
if !reflect.DeepEqual(ctx.Int64Slice("numbers"), expected) {
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Int64Slice("numbers"))
}
if !reflect.DeepEqual(ctx.Int64Slice("n"), expected) {
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Int64Slice("n"))
}
return nil
},
},
},
}).Run([]string{"run", "child"})
}
func TestInt64SliceFlag_SetFromParentContext(t *testing.T) {
fl := &Int64SliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewInt64Slice(1, 2, 3, 4)}
set := flag.NewFlagSet("test", 0)
_ = fl.Apply(set)
ctx := &Context{
parentContext: &Context{
flagSet: set,
},
flagSet: flag.NewFlagSet("empty", 0),
}
expected := []int64{1, 2, 3, 4}
if !reflect.DeepEqual(ctx.Int64Slice("numbers"), expected) {
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Int64Slice("numbers"))
}
}
func TestInt64SliceFlag_ReturnNil(t *testing.T) {
fl := &Int64SliceFlag{}
set := flag.NewFlagSet("test", 0)
_ = fl.Apply(set)
ctx := &Context{
parentContext: &Context{
flagSet: set,
},
flagSet: flag.NewFlagSet("empty", 0),
}
expected := []int64(nil)
if !reflect.DeepEqual(ctx.Int64Slice("numbers"), expected) {
t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Int64Slice("numbers"))
}
}
var float64FlagTests = []struct {
name string
expected string
@ -757,10 +873,10 @@ var float64SliceFlagTests = []struct {
value *Float64Slice
expected string
}{
{"heads", nil, NewFloat64Slice(), "--heads value\t"},
{"H", nil, NewFloat64Slice(), "-H value\t"},
{"heads", nil, NewFloat64Slice(), "--heads value\t(accepts multiple inputs)"},
{"H", nil, NewFloat64Slice(), "-H value\t(accepts multiple inputs)"},
{"heads", []string{"H"}, NewFloat64Slice(0.1234, -10.5),
"--heads value, -H value\t(default: 0.1234, -10.5)"},
"--heads value, -H value\t(default: 0.1234, -10.5)\t(accepts multiple inputs)"},
}
func TestFloat64SliceFlagHelpOutput(t *testing.T) {
@ -1866,3 +1982,80 @@ func TestTimestampFlagApply_Fail_Parse_Wrong_Time(t *testing.T) {
err := set.Parse([]string{"--time", "2006-01-02T15:04:05Z"})
expect(t, err, fmt.Errorf("invalid value \"2006-01-02T15:04:05Z\" for flag -time: parsing time \"2006-01-02T15:04:05Z\" as \"Jan 2, 2006 at 3:04pm (MST)\": cannot parse \"2006-01-02T15:04:05Z\" as \"Jan\""))
}
type flagDefaultTestCase struct {
name string
flag Flag
toParse []string
expect string
}
func TestFlagDefaultValue(t *testing.T) {
cases := []*flagDefaultTestCase{
&flagDefaultTestCase{
name: "stringSclice",
flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")},
toParse: []string{"--flag", "parsed"},
expect: `--flag value (default: "default1", "default2") (accepts multiple inputs)`,
},
&flagDefaultTestCase{
name: "float64Sclice",
flag: &Float64SliceFlag{Name: "flag", Value: NewFloat64Slice(1.1, 2.2)},
toParse: []string{"--flag", "13.3"},
expect: `--flag value (default: 1.1, 2.2) (accepts multiple inputs)`,
},
&flagDefaultTestCase{
name: "int64Sclice",
flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2)},
toParse: []string{"--flag", "13"},
expect: `--flag value (default: 1, 2) (accepts multiple inputs)`,
},
&flagDefaultTestCase{
name: "intSclice",
flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)},
toParse: []string{"--flag", "13"},
expect: `--flag value (default: 1, 2) (accepts multiple inputs)`,
},
&flagDefaultTestCase{
name: "string",
flag: &StringFlag{Name: "flag", Value: "default"},
toParse: []string{"--flag", "parsed"},
expect: `--flag value (default: "default")`,
},
&flagDefaultTestCase{
name: "bool",
flag: &BoolFlag{Name: "flag", Value: true},
toParse: []string{"--flag", "false"},
expect: `--flag (default: true)`,
},
&flagDefaultTestCase{
name: "uint64",
flag: &Uint64Flag{Name: "flag", Value: 1},
toParse: []string{"--flag", "13"},
expect: `--flag value (default: 1)`,
},
}
for i, v := range cases {
set := flag.NewFlagSet("test", 0)
set.SetOutput(ioutil.Discard)
_ = v.flag.Apply(set)
if err := set.Parse(v.toParse); err != nil {
t.Error(err)
}
if got := v.flag.String(); got != v.expect {
t.Errorf("TestFlagDefaultValue %d %s\nexpect:%s\ngot:%s", i, v.name, v.expect, got)
}
}
}
func TestTimestampFlagApply_WithDestination(t *testing.T) {
var destination Timestamp
expectedResult, _ := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
fl := TimestampFlag{Name: "time", Aliases: []string{"t"}, Layout: time.RFC3339, Destination: &destination}
set := flag.NewFlagSet("test", 0)
_ = fl.Apply(set)
err := set.Parse([]string{"--time", "2006-01-02T15:04:05Z"})
expect(t, err, nil)
expect(t, *fl.Destination.timestamp, expectedResult)
}

@ -71,6 +71,7 @@ type TimestampFlag struct {
Value *Timestamp
DefaultText string
HasBeenSet bool
Destination *Timestamp
}
// IsSet returns whether or not the flag has been set through env or file
@ -113,6 +114,11 @@ func (f *TimestampFlag) GetValue() string {
return ""
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *TimestampFlag) IsVisible() bool {
return !f.Hidden
}
// Apply populates the flag given the flag set and environment
func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
if f.Layout == "" {
@ -123,6 +129,10 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
}
f.Value.SetLayout(f.Layout)
if f.Destination != nil {
f.Destination.SetLayout(f.Layout)
}
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
if err := f.Value.Set(val); err != nil {
return fmt.Errorf("could not parse %q as timestamp value for flag %s: %s", val, f.Name, err)
@ -131,6 +141,11 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
}
for _, name := range f.Names() {
if f.Destination != nil {
set.Var(f.Destination, name, f.Usage)
continue
}
set.Var(f.Value, name, f.Usage)
}
return nil
@ -138,7 +153,7 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
// Timestamp gets the timestamp from a flag name
func (c *Context) Timestamp(name string) *time.Time {
if fs := lookupFlagSet(name, c); fs != nil {
if fs := c.lookupFlagSet(name); fs != nil {
return lookupTimestamp(name, fs)
}
return nil

@ -52,6 +52,11 @@ func (f *UintFlag) GetUsage() string {
return f.Usage
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *UintFlag) IsVisible() bool {
return !f.Hidden
}
// Apply populates the flag given the flag set and environment
func (f *UintFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
@ -86,7 +91,7 @@ func (f *UintFlag) GetValue() string {
// Uint looks up the value of a local UintFlag, returns
// 0 if not found
func (c *Context) Uint(name string) uint {
if fs := lookupFlagSet(name, c); fs != nil {
if fs := c.lookupFlagSet(name); fs != nil {
return lookupUint(name, fs)
}
return 0

@ -52,6 +52,11 @@ func (f *Uint64Flag) GetUsage() string {
return f.Usage
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *Uint64Flag) IsVisible() bool {
return !f.Hidden
}
// Apply populates the flag given the flag set and environment
func (f *Uint64Flag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
@ -86,7 +91,7 @@ func (f *Uint64Flag) GetValue() string {
// Uint64 looks up the value of a local Uint64Flag, returns
// 0 if not found
func (c *Context) Uint64(name string) uint64 {
if fs := lookupFlagSet(name, c); fs != nil {
if fs := c.lookupFlagSet(name); fs != nil {
return lookupUint64(name, fs)
}
return 0

@ -17,7 +17,7 @@ type ActionFunc func(*Context) error
// CommandNotFoundFunc is executed if the proper command cannot be found
type CommandNotFoundFunc func(*Context, string)
// OnUsageErrorFunc is executed if an usage error occurs. This is useful for displaying
// OnUsageErrorFunc is executed if a usage error occurs. This is useful for displaying
// customized usage error messages. This function is able to replace the
// original error messages. If this function is not set, the "Incorrect usage"
// is displayed and the execution is interrupted.

@ -1,9 +1,11 @@
module github.com/urfave/cli/v2
go 1.11
go 1.18
require (
github.com/BurntSushi/toml v0.3.1
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d
gopkg.in/yaml.v2 v2.2.2
github.com/BurntSushi/toml v1.1.0
github.com/cpuguy83/go-md2man/v2 v2.0.1
gopkg.in/yaml.v2 v2.4.0
)
require github.com/russross/blackfriday/v2 v2.1.0 // indirect

@ -1,14 +1,12 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

@ -72,13 +72,13 @@ func ShowAppHelpAndExit(c *Context, exitCode int) {
// ShowAppHelp is an action that displays the help.
func ShowAppHelp(c *Context) error {
template := c.App.CustomAppHelpTemplate
if template == "" {
template = AppHelpTemplate
tpl := c.App.CustomAppHelpTemplate
if tpl == "" {
tpl = AppHelpTemplate
}
if c.App.ExtraInfo == nil {
HelpPrinter(c.App.Writer, template, c.App)
HelpPrinter(c.App.Writer, tpl, c.App)
return nil
}
@ -87,7 +87,7 @@ func ShowAppHelp(c *Context) error {
"ExtraInfo": c.App.ExtraInfo,
}
}
HelpPrinterCustom(c.App.Writer, template, c.App, customAppData())
HelpPrinterCustom(c.App.Writer, tpl, c.App, customAppData())
return nil
}
@ -215,6 +215,12 @@ func ShowCommandHelp(ctx *Context, command string) error {
return nil
}
// ShowSubcommandHelpAndExit - Prints help for the given subcommand and exits with exit code.
func ShowSubcommandHelpAndExit(c *Context, exitCode int) {
_ = ShowSubcommandHelp(c)
os.Exit(exitCode)
}
// ShowSubcommandHelp prints help for the given subcommand
func ShowSubcommandHelp(c *Context) error {
if c == nil {
@ -264,7 +270,10 @@ func ShowCommandCompletions(ctx *Context, command string) {
// allow using arbitrary functions in template rendering.
func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs map[string]interface{}) {
funcMap := template.FuncMap{
"join": strings.Join,
"join": strings.Join,
"indent": indent,
"nindent": nindent,
"trim": strings.TrimSpace,
}
for key, value := range customFuncs {
funcMap[key] = value
@ -367,3 +376,12 @@ func checkCommandCompletions(c *Context, name string) bool {
ShowCommandCompletions(c, name)
return true
}
func indent(spaces int, v string) string {
pad := strings.Repeat(" ", spaces)
return pad + strings.Replace(v, "\n", "\n"+pad, -1)
}
func nindent(spaces int, v string) string {
return "\n" + indent(spaces, v)
}

@ -54,6 +54,22 @@ func Test_ShowAppHelp_HideVersion(t *testing.T) {
}
}
func Test_ShowAppHelp_MultiLineDescription(t *testing.T) {
output := new(bytes.Buffer)
app := &App{Writer: output}
app.HideVersion = true
app.Description = "multi\n line"
c := NewContext(app, nil, nil)
_ = ShowAppHelp(c)
if !bytes.Contains(output.Bytes(), []byte("DESCRIPTION:\n multi\n line")) {
t.Errorf("expected\n%s\nto include\n%s", output.String(), "DESCRIPTION:\n multi\n line")
}
}
func Test_Help_Custom_Flags(t *testing.T) {
oldFlag := HelpFlag
defer func() {
@ -495,6 +511,36 @@ func TestShowSubcommandHelp_CommandUsageText(t *testing.T) {
}
}
func TestShowSubcommandHelp_MultiLine_CommandUsageText(t *testing.T) {
app := &App{
Commands: []*Command{
{
Name: "frobbly",
UsageText: `This is a
multi
line
UsageText`,
},
},
}
output := &bytes.Buffer{}
app.Writer = output
_ = app.Run([]string{"foo", "frobbly", "--help"})
expected := `USAGE:
This is a
multi
line
UsageText
`
if !strings.Contains(output.String(), expected) {
t.Errorf("expected output to include usage text; got: %q", output.String())
}
}
func TestShowSubcommandHelp_SubcommandUsageText(t *testing.T) {
app := &App{
Commands: []*Command{
@ -519,6 +565,40 @@ func TestShowSubcommandHelp_SubcommandUsageText(t *testing.T) {
}
}
func TestShowSubcommandHelp_MultiLine_SubcommandUsageText(t *testing.T) {
app := &App{
Commands: []*Command{
{
Name: "frobbly",
Subcommands: []*Command{
{
Name: "bobbly",
UsageText: `This is a
multi
line
UsageText`,
},
},
},
},
}
output := &bytes.Buffer{}
app.Writer = output
_ = app.Run([]string{"foo", "frobbly", "bobbly", "--help"})
expected := `USAGE:
This is a
multi
line
UsageText
`
if !strings.Contains(output.String(), expected) {
t.Errorf("expected output to include usage text; got: %q", output.String())
}
}
func TestShowAppHelp_HiddenCommand(t *testing.T) {
app := &App{
Commands: []*Command{
@ -764,6 +844,56 @@ VERSION:
}
}
func TestShowAppHelp_UsageText(t *testing.T) {
app := &App{
UsageText: "This is a sinlge line of UsageText",
Commands: []*Command{
{
Name: "frobbly",
},
},
}
output := &bytes.Buffer{}
app.Writer = output
_ = app.Run([]string{"foo"})
if !strings.Contains(output.String(), "This is a sinlge line of UsageText") {
t.Errorf("expected output to include usage text; got: %q", output.String())
}
}
func TestShowAppHelp_MultiLine_UsageText(t *testing.T) {
app := &App{
UsageText: `This is a
multi
line
App UsageText`,
Commands: []*Command{
{
Name: "frobbly",
},
},
}
output := &bytes.Buffer{}
app.Writer = output
_ = app.Run([]string{"foo"})
expected := `USAGE:
This is a
multi
line
App UsageText
`
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,

@ -3,24 +3,17 @@ package cli
import (
"os"
"reflect"
"runtime"
"strings"
"testing"
)
var (
wd, _ = os.Getwd()
)
func init() {
_ = os.Setenv("CLI_TEMPLATE_REPANIC", "1")
}
func expect(t *testing.T, a interface{}, b interface{}) {
_, fn, line, _ := runtime.Caller(1)
fn = strings.Replace(fn, wd+"/", "", -1)
t.Helper()
if !reflect.DeepEqual(a, b) {
t.Errorf("(%s:%d) Expected %v (type %v) - Got %v (type %v)", fn, line, b, reflect.TypeOf(b), a, reflect.TypeOf(a))
t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
}
}

@ -193,8 +193,8 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) {
cliBuiltFilePath = "./internal/example-cli/built-example"
helloSourceFilePath = "./internal/example-hello-world/example-hello-world.go"
helloBuiltFilePath = "./internal/example-hello-world/built-example"
desiredMinBinarySize = 2.0
desiredMaxBinarySize = 2.1
desiredMinBinarySize = 1.9
desiredMaxBinarySize = 2.2
badNewsEmoji = "🚨"
goodNewsEmoji = "✨"
checksPassedEmoji = "✅"

@ -7,13 +7,13 @@ var AppHelpTemplate = `NAME:
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
{{if .UsageText}}{{.UsageText | nindent 3 | trim}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
VERSION:
{{.Version}}{{end}}{{end}}{{if .Description}}
DESCRIPTION:
{{.Description}}{{end}}{{if len .Authors}}
{{.Description | nindent 3 | trim}}{{end}}{{if len .Authors}}
AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
{{range $index, $author := .Authors}}{{if $index}}
@ -39,13 +39,13 @@ var CommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
{{if .UsageText}}{{.UsageText | nindent 3 | trim}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
CATEGORY:
{{.Category}}{{end}}{{if .Description}}
DESCRIPTION:
{{.Description}}{{end}}{{if .VisibleFlags}}
{{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlags}}
OPTIONS:
{{range .VisibleFlags}}{{.}}
@ -59,10 +59,10 @@ var SubcommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}}
{{if .UsageText}}{{.UsageText | nindent 3 | trim}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}}
DESCRIPTION:
{{.Description}}{{end}}
{{.Description | nindent 3 | trim}}{{end}}
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{range .VisibleCommands}}
@ -74,9 +74,9 @@ OPTIONS:
{{end}}{{end}}
`
var MarkdownDocTemplate = `% {{ .App.Name }} 8
var MarkdownDocTemplate = `{{if gt .SectionNum 0}}% {{ .App.Name }} {{ .SectionNum }}
# NAME
{{end}}# NAME
{{ .App.Name }}{{ if .App.Usage }} - {{ .App.Usage }}{{ end }}
@ -86,16 +86,18 @@ var MarkdownDocTemplate = `% {{ .App.Name }} 8
{{ if .SynopsisArgs }}
` + "```" + `
{{ range $v := .SynopsisArgs }}{{ $v }}{{ end }}` + "```" + `
{{ end }}{{ if .App.UsageText }}
{{ end }}{{ if .App.Description }}
# DESCRIPTION
{{ .App.UsageText }}
{{ .App.Description }}
{{ end }}
**Usage**:
` + "```" + `
` + "```" + `{{ if .App.UsageText }}
{{ .App.UsageText }}
{{ else }}
{{ .App.Name }} [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
` + "```" + `
{{ end }}` + "```" + `
{{ if .GlobalArgs }}
# GLOBAL OPTIONS
{{ range $v := .GlobalArgs }}

@ -3,7 +3,7 @@
.SH NAME
.PP
greet \- Some app
greet - Some app
.SH SYNOPSIS
@ -14,9 +14,9 @@ greet
.RS
.nf
[\-\-another\-flag|\-b]
[\-\-flag|\-\-fl|\-f]=[value]
[\-\-socket|\-s]=[value]
[--another-flag|-b]
[--flag|--fl|-f]=[value]
[--socket|-s]=[value]
.fi
.RE
@ -24,7 +24,7 @@ greet
.SH DESCRIPTION
.PP
app [first\_arg] [second\_arg]
Description of the application.
.PP
\fBUsage\fP:
@ -33,7 +33,7 @@ app [first\_arg] [second\_arg]
.RS
.nf
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
app [first_arg] [second_arg]
.fi
.RE
@ -41,13 +41,13 @@ greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
.SH GLOBAL OPTIONS
.PP
\fB\-\-another\-flag, \-b\fP: another usage text
\fB--another-flag, -b\fP: another usage text
.PP
\fB\-\-flag, \-\-fl, \-f\fP="":
\fB--flag, --fl, -f\fP="":
.PP
\fB\-\-socket, \-s\fP="": some 'usage' text (default: value)
\fB--socket, -s\fP="": some 'usage' text (default: value)
.SH COMMANDS
@ -56,23 +56,65 @@ greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
another usage test
.PP
\fB\-\-another\-flag, \-b\fP: another usage text
\fB--another-flag, -b\fP: another usage text
.PP
\fB\-\-flag, \-\-fl, \-f\fP="":
\fB--flag, --fl, -f\fP="":
.SS sub\-config, s, ss
.SS sub-config, s, ss
.PP
another usage test
.PP
\fB\-\-sub\-command\-flag, \-s\fP: some usage text
\fB--sub-command-flag, -s\fP: some usage text
.PP
\fB\-\-sub\-flag, \-\-sub\-fl, \-s\fP="":
\fB--sub-flag, --sub-fl, -s\fP="":
.SH info, i, in
.PP
retrieve generic information
.SH some\-command
.SH some-command
.SH usage, u
.PP
standard usage text
.PP
.RS
.nf
Usage for the usage text
- formatted: Based on the specified ConfigMap and summon secrets.yml
- list: Inspect the environment for a specific process running on a Pod
- for_effect: Compare 'namespace' environment with 'local'
```
func() { ... }
```
Should be a part of the same code block
.fi
.RE
.PP
\fB--another-flag, -b\fP: another usage text
.PP
\fB--flag, --fl, -f\fP="":
.SS sub-usage, su
.PP
standard usage text
.PP
.RS
.PP
Single line of UsageText
.RE
.PP
\fB--sub-command-flag, -s\fP: some usage text

@ -1,5 +1,3 @@
% greet 8
# NAME
greet - Some app
@ -16,12 +14,12 @@ greet
# DESCRIPTION
app [first_arg] [second_arg]
Description of the application.
**Usage**:
```
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
app [first_arg] [second_arg]
```
# GLOBAL OPTIONS
@ -58,3 +56,29 @@ retrieve generic information
## some-command
## usage, u
standard usage text
Usage for the usage text
- formatted: Based on the specified ConfigMap and summon secrets.yml
- list: Inspect the environment for a specific process running on a Pod
- for_effect: Compare 'namespace' environment with 'local'
```
func() { ... }
```
Should be a part of the same code block
**--another-flag, -b**: another usage text
**--flag, --fl, -f**="":
### sub-usage, su
standard usage text
>Single line of UsageText
**--sub-command-flag, -s**: some usage text

@ -1,5 +1,3 @@
% greet 8
# NAME
greet - Some app
@ -16,12 +14,12 @@ greet
# DESCRIPTION
app [first_arg] [second_arg]
Description of the application.
**Usage**:
```
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
app [first_arg] [second_arg]
```
# GLOBAL OPTIONS
@ -58,3 +56,29 @@ retrieve generic information
## some-command
## usage, u
standard usage text
Usage for the usage text
- formatted: Based on the specified ConfigMap and summon secrets.yml
- list: Inspect the environment for a specific process running on a Pod
- for_effect: Compare 'namespace' environment with 'local'
```
func() { ... }
```
Should be a part of the same code block
**--another-flag, -b**: another usage text
**--flag, --fl, -f**="":
### sub-usage, su
standard usage text
>Single line of UsageText
**--sub-command-flag, -s**: some usage text

@ -1,5 +1,3 @@
% greet 8
# NAME
greet - Some app
@ -16,12 +14,12 @@ greet
# DESCRIPTION
app [first_arg] [second_arg]
Description of the application.
**Usage**:
```
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
app [first_arg] [second_arg]
```
# GLOBAL OPTIONS

@ -1,5 +1,3 @@
% greet 8
# NAME
greet - Some app
@ -10,12 +8,12 @@ greet
# DESCRIPTION
app [first_arg] [second_arg]
Description of the application.
**Usage**:
```
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
app [first_arg] [second_arg]
```
# COMMANDS
@ -43,3 +41,29 @@ retrieve generic information
## some-command
## usage, u
standard usage text
Usage for the usage text
- formatted: Based on the specified ConfigMap and summon secrets.yml
- list: Inspect the environment for a specific process running on a Pod
- for_effect: Compare 'namespace' environment with 'local'
```
func() { ... }
```
Should be a part of the same code block
**--another-flag, -b**: another usage text
**--flag, --fl, -f**="":
### sub-usage, su
standard usage text
>Single line of UsageText
**--sub-command-flag, -s**: some usage text

@ -0,0 +1,84 @@
# NAME
greet - Some app
# SYNOPSIS
greet
```
[--another-flag|-b]
[--flag|--fl|-f]=[value]
[--socket|-s]=[value]
```
# DESCRIPTION
Description of the application.
**Usage**:
```
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
```
# GLOBAL OPTIONS
**--another-flag, -b**: another usage text
**--flag, --fl, -f**="":
**--socket, -s**="": some 'usage' text (default: value)
# COMMANDS
## config, c
another usage test
**--another-flag, -b**: another usage text
**--flag, --fl, -f**="":
### sub-config, s, ss
another usage test
**--sub-command-flag, -s**: some usage text
**--sub-flag, --sub-fl, -s**="":
## info, i, in
retrieve generic information
## some-command
## usage, u
standard usage text
Usage for the usage text
- formatted: Based on the specified ConfigMap and summon secrets.yml
- list: Inspect the environment for a specific process running on a Pod
- for_effect: Compare 'namespace' environment with 'local'
```
func() { ... }
```
Should be a part of the same code block
**--another-flag, -b**: another usage text
**--flag, --fl, -f**="":
### sub-usage, su
standard usage text
>Single line of UsageText
**--sub-command-flag, -s**: some usage text

@ -2,7 +2,7 @@
function __fish_greet_no_subcommand --description 'Test if there has been any subcommand yet'
for i in (commandline -opc)
if contains -- $i config c sub-config s ss info i in some-command
if contains -- $i config c sub-config s ss info i in some-command usage u sub-usage su
return 1
end
end
@ -12,6 +12,7 @@ end
complete -c greet -n '__fish_greet_no_subcommand' -l socket -s s -r -d 'some \'usage\' text'
complete -c greet -n '__fish_greet_no_subcommand' -f -l flag -s fl -s f -r
complete -c greet -n '__fish_greet_no_subcommand' -f -l another-flag -s b -d 'another usage text'
complete -c greet -n '__fish_greet_no_subcommand' -l logfile -r
complete -c greet -n '__fish_greet_no_subcommand' -f -l help -s h -d 'show help'
complete -c greet -n '__fish_greet_no_subcommand' -f -l version -s v -d 'print the version'
complete -c greet -n '__fish_seen_subcommand_from config c' -f -l help -s h -d 'show help'
@ -26,3 +27,10 @@ complete -c greet -n '__fish_seen_subcommand_from info i in' -f -l help -s h -d
complete -r -c greet -n '__fish_greet_no_subcommand' -a 'info i in' -d 'retrieve generic information'
complete -c greet -n '__fish_seen_subcommand_from some-command' -f -l help -s h -d 'show help'
complete -r -c greet -n '__fish_greet_no_subcommand' -a 'some-command'
complete -c greet -n '__fish_seen_subcommand_from usage u' -f -l help -s h -d 'show help'
complete -r -c greet -n '__fish_greet_no_subcommand' -a 'usage u' -d 'standard usage text'
complete -c greet -n '__fish_seen_subcommand_from usage u' -l flag -s fl -s f -r
complete -c greet -n '__fish_seen_subcommand_from usage u' -f -l another-flag -s b -d 'another usage text'
complete -c greet -n '__fish_seen_subcommand_from sub-usage su' -f -l help -s h -d 'show help'
complete -r -c greet -n '__fish_seen_subcommand_from usage u' -a 'sub-usage su' -d 'standard usage text'
complete -c greet -n '__fish_seen_subcommand_from sub-usage su' -f -l sub-command-flag -s s -d 'some usage text'

Loading…
Cancel
Save