Merge remote-tracking branch 'origin' into github-actions-windows
This commit is contained in:
commit
65d28dcc47
1
.github/.codecov.yml
vendored
Normal file
1
.github/.codecov.yml
vendored
Normal file
@ -0,0 +1 @@
|
||||
comment: false
|
28
.github/workflows/cli.yml
vendored
28
.github/workflows/cli.yml
vendored
@ -1,6 +1,10 @@
|
||||
name: Run Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- v1
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
@ -47,40 +51,38 @@ jobs:
|
||||
curl -L -o gfmrun.exe "https://github.com/urfave/gfmrun/releases/download/v1.2.14/gfmrun-windows-amd64-v1.2.14.exe"
|
||||
npm install markdown-toc
|
||||
|
||||
- name: Run Tests (v1) (Linux / MacOS)
|
||||
if: contains(github.base_ref, 'v1') && matrix.os != 'windows-latest'
|
||||
- name: Run Tests
|
||||
run: |
|
||||
go run build.go vet
|
||||
go run build.go test
|
||||
|
||||
- name: Run Tests (v1) (Linux / MacOS)
|
||||
if: contains(github.base_ref, 'v1') && matrix.os != 'windows-latest'
|
||||
run: |
|
||||
go run build.go gfmrun docs/v1/manual.md
|
||||
go run build.go toc docs/v1/manual.md
|
||||
|
||||
- name: Run Tests (v1) (Windows)
|
||||
if: contains(github.base_ref, 'v1') && matrix.os == 'windows-latest'
|
||||
run: |
|
||||
go run build.go vet
|
||||
go run build.go test
|
||||
go run build.go gfmrun docs\v1\manual.md
|
||||
go run build.go toc docs\v1\manual.md
|
||||
|
||||
- name: Run Tests (v2) (Linux / MacOS)
|
||||
if: contains(github.base_ref, 'master') && matrix.os != 'windows-latest'
|
||||
run: |
|
||||
go run build.go vet
|
||||
go run build.go test
|
||||
go run build.go gfmrun docs/v2/manual.md
|
||||
go run build.go toc docs/v2/manual.md
|
||||
|
||||
- name: Run Tests (v2) (Windows)
|
||||
if: contains(github.base_ref, 'master') && matrix.os == 'windows-latest'
|
||||
run: |
|
||||
go run build.go vet
|
||||
go run build.go test
|
||||
go run build.go gfmrun docs/v2/manual.md
|
||||
go run build.go toc docs/v2/manual.md
|
||||
|
||||
- name: Send Coverage Report
|
||||
if: success()
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
run: bash <(curl -s https://codecov.io/bash)
|
||||
- name: Upload coverage to Codecov
|
||||
if: success() && matrix.go == 1.13 && matrix.os == 'ubuntu-latest'
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
token: 0a8cc73b-bb7c-480b-8626-38a461643761
|
||||
fail_ci_if_error: true
|
||||
|
18
README.md
18
README.md
@ -1,7 +1,6 @@
|
||||
cli
|
||||
===
|
||||
|
||||
[![Build Status](https://travis-ci.org/urfave/cli.svg?branch=master)](https://travis-ci.org/urfave/cli)
|
||||
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/rtgk5xufi932pb2v?svg=true)](https://ci.appveyor.com/project/urfave/cli)
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli)
|
||||
@ -20,6 +19,12 @@ Usage documentation exists for each major version. Don't know what version you'r
|
||||
- `v2` - [./docs/v2/manual.md](./docs/v2/manual.md)
|
||||
- `v1` - [./docs/v1/manual.md](./docs/v1/manual.md)
|
||||
|
||||
## Installation
|
||||
|
||||
Make sure you have a working Go environment. Go version 1.11+ is supported. [See the install instructions for Go](http://golang.org/doc/install.html).
|
||||
|
||||
Go Modules are strongly recommended when using this package. [See the go blog guide on using Go Modules](https://blog.golang.org/using-go-modules).
|
||||
|
||||
### Using `v2` releases
|
||||
|
||||
```
|
||||
@ -37,7 +42,7 @@ import (
|
||||
### Using `v1` releases
|
||||
|
||||
```
|
||||
$ go get github.com/urfave/cli
|
||||
$ GO111MODULE=on go get github.com/urfave/cli
|
||||
```
|
||||
|
||||
```go
|
||||
@ -48,11 +53,6 @@ import (
|
||||
...
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
Make sure you have a working Go environment. Go version 1.10+ is supported. [See
|
||||
the install instructions for Go](http://golang.org/doc/install.html).
|
||||
|
||||
### GOPATH
|
||||
|
||||
Make sure your `PATH` includes the `$GOPATH/bin` directory so your commands can
|
||||
@ -64,5 +64,5 @@ export PATH=$PATH:$GOPATH/bin
|
||||
### Supported platforms
|
||||
|
||||
cli is tested against multiple versions of Go on Linux, and against the latest
|
||||
released version of Go on OS X and Windows. For full details, see
|
||||
[`./.travis.yml`](./.travis.yml) and [`./appveyor.yml`](./appveyor.yml).
|
||||
released version of Go on OS X and Windows. This project uses Github Actions for
|
||||
builds. For more build info, please look at the [./.github/workflows/cli.yml](https://github.com/urfave/cli/blob/master/.github/workflows/cli.yml).
|
||||
|
12
app.go
12
app.go
@ -1,6 +1,7 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -12,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md"
|
||||
changeLogURL = "https://github.com/urfave/cli/blob/master/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. "+
|
||||
@ -207,6 +208,13 @@ func (a *App) useShortOptionHandling() bool {
|
||||
// Run is the entry point to the cli app. Parses the arguments slice and routes
|
||||
// to the proper flag/args combination
|
||||
func (a *App) Run(arguments []string) (err error) {
|
||||
return a.RunContext(context.Background(), arguments)
|
||||
}
|
||||
|
||||
// RunContext is like Run except it takes a Context that will be
|
||||
// passed to its commands and sub-commands. Through this, you can
|
||||
// propagate timeouts and cancellation requests
|
||||
func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
|
||||
a.Setup()
|
||||
|
||||
// handle the completion flag separately from the flagset since
|
||||
@ -224,7 +232,7 @@ func (a *App) Run(arguments []string) (err error) {
|
||||
|
||||
err = parseIter(set, a, arguments[1:], shellComplete)
|
||||
nerr := normalizeFlags(a.Flags, set)
|
||||
context := NewContext(a, set, nil)
|
||||
context := NewContext(a, set, &Context{Context: ctx})
|
||||
if nerr != nil {
|
||||
_, _ = fmt.Fprintln(a.Writer, nerr)
|
||||
_ = ShowAppHelp(context)
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
@ -2121,7 +2120,7 @@ func TestWhenExitSubCommandWithCodeThenAppQuitUnexpectedly(t *testing.T) {
|
||||
var exitCodeFromExitErrHandler int
|
||||
app.ExitErrHandler = func(c *Context, err error) {
|
||||
if exitErr, ok := err.(ExitCoder); ok {
|
||||
log.Print(exitErr)
|
||||
t.Log(exitErr)
|
||||
exitCodeFromExitErrHandler = exitErr.ExitCode()
|
||||
}
|
||||
}
|
||||
|
@ -20,10 +20,9 @@ install:
|
||||
- go version
|
||||
- go env
|
||||
- go get github.com/urfave/gfmrun/cmd/gfmrun
|
||||
- go get golang.org/x/tools/cmd/goimports
|
||||
- go mod tidy
|
||||
|
||||
build_script:
|
||||
- go run build.go vet
|
||||
- go run build.go test
|
||||
- go run build.go gfmrun docs/v1/manual.md
|
||||
- go run build.go gfmrun docs/v1/manual.md
|
||||
|
@ -1,5 +1,6 @@
|
||||
package cli
|
||||
|
||||
// CommandCategories interface allows for category manipulation
|
||||
type CommandCategories interface {
|
||||
// AddCommand adds a command to a category, creating a new category if necessary.
|
||||
AddCommand(category string, command *Command)
|
||||
|
15
context.go
15
context.go
@ -5,10 +5,7 @@ import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Context is a type that is passed through to
|
||||
@ -31,19 +28,15 @@ func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
|
||||
if parentCtx != nil {
|
||||
c.Context = parentCtx.Context
|
||||
c.shellComplete = parentCtx.shellComplete
|
||||
if parentCtx.flagSet == nil {
|
||||
parentCtx.flagSet = &flag.FlagSet{}
|
||||
}
|
||||
}
|
||||
|
||||
c.Command = &Command{}
|
||||
|
||||
if c.Context == nil {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
defer cancel()
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigs
|
||||
}()
|
||||
c.Context = ctx
|
||||
c.Context = context.Background()
|
||||
}
|
||||
|
||||
return c
|
||||
|
@ -344,6 +344,81 @@ func TestContextPropagation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextAttributeAccessing(t *testing.T) {
|
||||
tdata := []struct {
|
||||
testCase string
|
||||
setBoolInput string
|
||||
ctxBoolInput string
|
||||
newContextInput *Context
|
||||
}{
|
||||
{
|
||||
testCase: "empty",
|
||||
setBoolInput: "",
|
||||
ctxBoolInput: "",
|
||||
newContextInput: nil,
|
||||
},
|
||||
{
|
||||
testCase: "empty_with_background_context",
|
||||
setBoolInput: "",
|
||||
ctxBoolInput: "",
|
||||
newContextInput: &Context{Context: context.Background()},
|
||||
},
|
||||
{
|
||||
testCase: "empty_set_bool_and_present_ctx_bool",
|
||||
setBoolInput: "",
|
||||
ctxBoolInput: "ctx-bool",
|
||||
newContextInput: nil,
|
||||
},
|
||||
{
|
||||
testCase: "present_set_bool_and_present_ctx_bool_with_background_context",
|
||||
setBoolInput: "",
|
||||
ctxBoolInput: "ctx-bool",
|
||||
newContextInput: &Context{Context: context.Background()},
|
||||
},
|
||||
{
|
||||
testCase: "present_set_bool_and_present_ctx_bool",
|
||||
setBoolInput: "ctx-bool",
|
||||
ctxBoolInput: "ctx-bool",
|
||||
newContextInput: nil,
|
||||
},
|
||||
{
|
||||
testCase: "present_set_bool_and_present_ctx_bool_with_background_context",
|
||||
setBoolInput: "ctx-bool",
|
||||
ctxBoolInput: "ctx-bool",
|
||||
newContextInput: &Context{Context: context.Background()},
|
||||
},
|
||||
{
|
||||
testCase: "present_set_bool_and_different_ctx_bool",
|
||||
setBoolInput: "ctx-bool",
|
||||
ctxBoolInput: "not-ctx-bool",
|
||||
newContextInput: nil,
|
||||
},
|
||||
{
|
||||
testCase: "present_set_bool_and_different_ctx_bool_with_background_context",
|
||||
setBoolInput: "ctx-bool",
|
||||
ctxBoolInput: "not-ctx-bool",
|
||||
newContextInput: &Context{Context: context.Background()},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tdata {
|
||||
t.Run(test.testCase, func(t *testing.T) {
|
||||
// setup
|
||||
set := flag.NewFlagSet("some-flag-set-name", 0)
|
||||
set.Bool(test.setBoolInput, false, "usage documentation")
|
||||
ctx := NewContext(nil, set, test.newContextInput)
|
||||
|
||||
// logic under test
|
||||
value := ctx.Bool(test.ctxBoolInput)
|
||||
|
||||
// assertions
|
||||
if value != false {
|
||||
t.Errorf("expected \"value\" to be false, but it was not")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckRequiredFlags(t *testing.T) {
|
||||
tdata := []struct {
|
||||
testCase string
|
||||
|
4
docs.go
4
docs.go
@ -48,8 +48,8 @@ func (a *App) writeDocTemplate(w io.Writer) error {
|
||||
return t.ExecuteTemplate(w, name, &cliTemplate{
|
||||
App: a,
|
||||
Commands: prepareCommands(a.Commands, 0),
|
||||
GlobalArgs: prepareArgsWithValues(a.Flags),
|
||||
SynopsisArgs: prepareArgsSynopsis(a.Flags),
|
||||
GlobalArgs: prepareArgsWithValues(a.VisibleFlags()),
|
||||
SynopsisArgs: prepareArgsSynopsis(a.VisibleFlags()),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,33 @@
|
||||
|
||||
View [unreleased 2.X] series changes.
|
||||
|
||||
## [2.1.1] - 2019-12-24
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed a `Context` regression introduced in `v2.1.0` in [urfave/cli/pull/1014](https://github.com/urfave/cli/pull/1014) via [@lynncyrin](https://github.com/lynncyrin)
|
||||
|
||||
## [2.1.0] - 2019-12-24
|
||||
|
||||
These release notes were written for the git hash [ae84df4cef4a2a6f1a0cb1d41ea0f3af8755e5a8](https://github.com/urfave/cli/tree/ae84df4cef4a2a6f1a0cb1d41ea0f3af8755e5a8)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed some golint errors in [urfave/cli/pull/988](https://github.com/urfave/cli/pull/988) via [@liamchampton](https://github.com/liamchampton)
|
||||
* Fixed a panic with flag completion [urfave/cli/pull/946](https://github.com/urfave/cli/pull/946) via [@unRob](https://github.com/unRob)
|
||||
|
||||
### Changed
|
||||
|
||||
* Changed docs generation to use visible flags in [urfave/cli/pull/999](https://github.com/urfave/cli/pull/999) via [@subpop](https://github.com/subpop)
|
||||
* Changed `App.Run` to use an optional context for timeouts and cancellation in [urfave/cli/pull/975](https://github.com/urfave/cli/pull/975) via [@marwan-at-work](https://github.com/marwan-at-work)
|
||||
* Changed version info to be hidden if the user has not defined a version in [urfave/cli/pull/955](https://github.com/urfave/cli/pull/955) via [@asahasrabuddhe](https://github.com/asahasrabuddhe)
|
||||
* Changed docs generation to take into account multiple authors in [urfave/cli/pull/900](https://github.com/urfave/cli/pull/900) via [@saschagrunert](https://github.com/saschagrunert)
|
||||
* Changed context to expose a `Value` accessor in [urfave/cli/pull/741](https://github.com/urfave/cli/pull/741) via [@corruptmemory](https://github.com/corruptmemory)
|
||||
|
||||
### Added
|
||||
|
||||
* Added timestamp flag in [urfave/cli/pull/987](https://github.com/urfave/cli/pull/987) via [@drov0](https://github.com/drov0)
|
||||
|
||||
## [2.0.0] - 2019-11-17
|
||||
|
||||
The V2 changes were all shipped in [urfave/cli/pull/892](https://github.com/urfave/cli/pull/892), which was created with the effort of over a dozen participants! They are:
|
||||
@ -520,7 +547,9 @@ signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`.
|
||||
### Added
|
||||
- Initial implementation.
|
||||
|
||||
[unreleased 2.X]: https://github.com/urfave/cli/compare/v2.0.0...HEAD
|
||||
[unreleased 2.X]: https://github.com/urfave/cli/compare/v2.1.1...HEAD
|
||||
[2.1.1]: https://github.com/urfave/cli/compare/v2.1.0...v2.1.1
|
||||
[2.1.0]: https://github.com/urfave/cli/compare/v2.0.0...v2.1.0
|
||||
[2.0.0]: https://github.com/urfave/cli/compare/v1.22.2...v2.0.0
|
||||
|
||||
[unreleased 1.22.X]: https://github.com/urfave/cli/compare/v1.22.2...v1
|
||||
|
@ -13,6 +13,7 @@ cli v2 manual
|
||||
+ [Values from the Environment](#values-from-the-environment)
|
||||
+ [Values from files](#values-from-files)
|
||||
+ [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others)
|
||||
+ [Required Flags](#required-flags)
|
||||
+ [Default Values for help output](#default-values-for-help-output)
|
||||
+ [Precedence](#precedence)
|
||||
* [Subcommands](#subcommands)
|
||||
@ -641,6 +642,63 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
#### Required Flags
|
||||
|
||||
You can make a flag required by setting the `Required` field to `true`. If a user
|
||||
does not provide a required flag, they will be shown an error message.
|
||||
|
||||
Take for example this app that reqiures the `lang` flag:
|
||||
|
||||
<!-- {
|
||||
"error": "Required flag \"lang\" not set"
|
||||
} -->
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
|
||||
app.Flags = []cli.Flag {
|
||||
cli.StringFlag{
|
||||
Name: "lang",
|
||||
Value: "english",
|
||||
Usage: "language for the greeting",
|
||||
Required: true,
|
||||
},
|
||||
}
|
||||
|
||||
app.Action = func(c *cli.Context) error {
|
||||
var output string
|
||||
if c.String("lang") == "spanish" {
|
||||
output = "Hola"
|
||||
} else {
|
||||
output = "Hello"
|
||||
}
|
||||
fmt.Println(output)
|
||||
return nil
|
||||
}
|
||||
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the app is run without the `lang` flag, the user will see the following message
|
||||
|
||||
```
|
||||
Required flag "lang" not set
|
||||
```
|
||||
|
||||
#### Default Values for help output
|
||||
|
||||
Sometimes it's useful to specify a flag's default help-text value within the flag declaration. This can be useful if the default value for a flag is a computed value. The default value can be set via the `DefaultText` struct field.
|
||||
@ -1402,13 +1460,15 @@ func main() {
|
||||
cli.ShowVersion(c)
|
||||
|
||||
fmt.Printf("%#v\n", c.App.Command("doo"))
|
||||
if c.Bool("infinite") {
|
||||
c.App.Run([]string{"app", "doo", "wop"})
|
||||
}
|
||||
// // uncomment when https://github.com/urfave/cli/pull/1014 is released
|
||||
// if c.Bool("infinite") {
|
||||
// c.App.Run([]string{"app", "doo", "wop"})
|
||||
// }
|
||||
|
||||
if c.Bool("forevar") {
|
||||
c.App.RunAsSubcommand(c)
|
||||
}
|
||||
// // uncomment when https://github.com/urfave/cli/pull/1014 is released
|
||||
// if c.Bool("forevar") {
|
||||
// c.App.RunAsSubcommand(c)
|
||||
// }
|
||||
c.App.Setup()
|
||||
fmt.Printf("%#v\n", c.App.VisibleCategories())
|
||||
fmt.Printf("%#v\n", c.App.VisibleCommands())
|
||||
@ -1424,28 +1484,29 @@ func main() {
|
||||
set := flag.NewFlagSet("contrive", 0)
|
||||
nc := cli.NewContext(c.App, set, c)
|
||||
|
||||
fmt.Printf("%#v\n", nc.Args())
|
||||
fmt.Printf("%#v\n", nc.Bool("nope"))
|
||||
fmt.Printf("%#v\n", !nc.Bool("nerp"))
|
||||
fmt.Printf("%#v\n", nc.Duration("howlong"))
|
||||
fmt.Printf("%#v\n", nc.Float64("hay"))
|
||||
fmt.Printf("%#v\n", nc.Generic("bloop"))
|
||||
fmt.Printf("%#v\n", nc.Int64("bonk"))
|
||||
fmt.Printf("%#v\n", nc.Int64Slice("burnks"))
|
||||
fmt.Printf("%#v\n", nc.Int("bips"))
|
||||
fmt.Printf("%#v\n", nc.IntSlice("blups"))
|
||||
fmt.Printf("%#v\n", nc.String("snurt"))
|
||||
fmt.Printf("%#v\n", nc.StringSlice("snurkles"))
|
||||
fmt.Printf("%#v\n", nc.Uint("flub"))
|
||||
fmt.Printf("%#v\n", nc.Uint64("florb"))
|
||||
|
||||
fmt.Printf("%#v\n", nc.FlagNames())
|
||||
fmt.Printf("%#v\n", nc.IsSet("wat"))
|
||||
fmt.Printf("%#v\n", nc.Set("wat", "nope"))
|
||||
fmt.Printf("%#v\n", nc.NArg())
|
||||
fmt.Printf("%#v\n", nc.NumFlags())
|
||||
fmt.Printf("%#v\n", nc.Lineage()[1])
|
||||
// // uncomment when https://github.com/urfave/cli/pull/1014 is released
|
||||
// fmt.Printf("%#v\n", nc.Args())
|
||||
// fmt.Printf("%#v\n", nc.Bool("nope"))
|
||||
// fmt.Printf("%#v\n", !nc.Bool("nerp"))
|
||||
// fmt.Printf("%#v\n", nc.Duration("howlong"))
|
||||
// fmt.Printf("%#v\n", nc.Float64("hay"))
|
||||
// fmt.Printf("%#v\n", nc.Generic("bloop"))
|
||||
// fmt.Printf("%#v\n", nc.Int64("bonk"))
|
||||
// fmt.Printf("%#v\n", nc.Int64Slice("burnks"))
|
||||
// fmt.Printf("%#v\n", nc.Int("bips"))
|
||||
// fmt.Printf("%#v\n", nc.IntSlice("blups"))
|
||||
// fmt.Printf("%#v\n", nc.String("snurt"))
|
||||
// fmt.Printf("%#v\n", nc.StringSlice("snurkles"))
|
||||
// fmt.Printf("%#v\n", nc.Uint("flub"))
|
||||
// fmt.Printf("%#v\n", nc.Uint64("florb"))
|
||||
|
||||
// // uncomment when https://github.com/urfave/cli/pull/1014 is released
|
||||
// fmt.Printf("%#v\n", nc.FlagNames())
|
||||
// fmt.Printf("%#v\n", nc.IsSet("wat"))
|
||||
// fmt.Printf("%#v\n", nc.Set("wat", "nope"))
|
||||
// fmt.Printf("%#v\n", nc.NArg())
|
||||
// fmt.Printf("%#v\n", nc.NumFlags())
|
||||
// fmt.Printf("%#v\n", nc.Lineage()[1])
|
||||
nc.Set("wat", "also-nope")
|
||||
|
||||
ec := cli.Exit("ohwell", 86)
|
||||
|
@ -23,6 +23,10 @@ func testApp() *App {
|
||||
Aliases: []string{"b"},
|
||||
Usage: "another usage text",
|
||||
},
|
||||
&BoolFlag{
|
||||
Name: "hidden-flag",
|
||||
Hidden: true,
|
||||
},
|
||||
}
|
||||
app.Commands = []*Command{{
|
||||
Aliases: []string{"c"},
|
||||
|
@ -48,6 +48,7 @@ func (m *multiError) Errors() []error {
|
||||
return errs
|
||||
}
|
||||
|
||||
// ErrorFormatter is the interface that will suitably format the error output
|
||||
type ErrorFormatter interface {
|
||||
Format(s fmt.State, verb rune)
|
||||
}
|
||||
|
@ -18,11 +18,11 @@ type Float64Flag struct {
|
||||
Value float64
|
||||
DefaultText string
|
||||
Destination *float64
|
||||
HasBeenSet bool
|
||||
HasBeenSet bool
|
||||
}
|
||||
|
||||
// IsSet returns whether or not the flag has been set through env or file
|
||||
func (f *Float64Flag)IsSet() bool {
|
||||
func (f *Float64Flag) IsSet() bool {
|
||||
return f.HasBeenSet
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ func (f *PathFlag) Apply(set *flag.FlagSet) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// String looks up the value of a local PathFlag, returns
|
||||
// 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 {
|
||||
|
55
flag_test.go
55
flag_test.go
@ -1678,3 +1678,58 @@ func TestInt64Slice_Serialized_Set(t *testing.T) {
|
||||
t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimestamp_set(t *testing.T) {
|
||||
ts := Timestamp{
|
||||
timestamp: nil,
|
||||
hasBeenSet: false,
|
||||
layout: "Jan 2, 2006 at 3:04pm (MST)",
|
||||
}
|
||||
|
||||
time1 := "Feb 3, 2013 at 7:54pm (PST)"
|
||||
if err := ts.Set(time1); err != nil {
|
||||
t.Fatalf("Failed to parse time %s with layout %s", time1, ts.layout)
|
||||
}
|
||||
if ts.hasBeenSet == false {
|
||||
t.Fatalf("hasBeenSet is not true after setting a time")
|
||||
}
|
||||
|
||||
ts.hasBeenSet = false
|
||||
ts.SetLayout(time.RFC3339)
|
||||
time2 := "2006-01-02T15:04:05Z"
|
||||
if err := ts.Set(time2); err != nil {
|
||||
t.Fatalf("Failed to parse time %s with layout %s", time2, ts.layout)
|
||||
}
|
||||
if ts.hasBeenSet == false {
|
||||
t.Fatalf("hasBeenSet is not true after setting a time")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimestampFlagApply(t *testing.T) {
|
||||
expectedResult, _ := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
||||
fl := TimestampFlag{Name: "time", Aliases: []string{"t"}, Layout: time.RFC3339}
|
||||
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.Value.timestamp, expectedResult)
|
||||
}
|
||||
|
||||
func TestTimestampFlagApply_Fail_Parse_Wrong_Layout(t *testing.T) {
|
||||
fl := TimestampFlag{Name: "time", Aliases: []string{"t"}, Layout: "randomlayout"}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
_ = fl.Apply(set)
|
||||
|
||||
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 \"randomlayout\": cannot parse \"2006-01-02T15:04:05Z\" as \"randomlayout\""))
|
||||
}
|
||||
|
||||
func TestTimestampFlagApply_Fail_Parse_Wrong_Time(t *testing.T) {
|
||||
fl := TimestampFlag{Name: "time", Aliases: []string{"t"}, Layout: "Jan 2, 2006 at 3:04pm (MST)"}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
_ = fl.Apply(set)
|
||||
|
||||
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\""))
|
||||
}
|
||||
|
152
flag_timestamp.go
Normal file
152
flag_timestamp.go
Normal file
@ -0,0 +1,152 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Timestamp wrap to satisfy golang's flag interface.
|
||||
type Timestamp struct {
|
||||
timestamp *time.Time
|
||||
hasBeenSet bool
|
||||
layout string
|
||||
}
|
||||
|
||||
// Timestamp constructor
|
||||
func NewTimestamp(timestamp time.Time) *Timestamp {
|
||||
return &Timestamp{timestamp: ×tamp}
|
||||
}
|
||||
|
||||
// Set the timestamp value directly
|
||||
func (t *Timestamp) SetTimestamp(value time.Time) {
|
||||
if !t.hasBeenSet {
|
||||
t.timestamp = &value
|
||||
t.hasBeenSet = true
|
||||
}
|
||||
}
|
||||
|
||||
// Set the timestamp string layout for future parsing
|
||||
func (t *Timestamp) SetLayout(layout string) {
|
||||
t.layout = layout
|
||||
}
|
||||
|
||||
// Parses the string value to timestamp
|
||||
func (t *Timestamp) Set(value string) error {
|
||||
timestamp, err := time.Parse(t.layout, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.timestamp = ×tamp
|
||||
t.hasBeenSet = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (t *Timestamp) String() string {
|
||||
return fmt.Sprintf("%#v", t.timestamp)
|
||||
}
|
||||
|
||||
// Value returns the timestamp value stored in the flag
|
||||
func (t *Timestamp) Value() *time.Time {
|
||||
return t.timestamp
|
||||
}
|
||||
|
||||
// Get returns the flag structure
|
||||
func (t *Timestamp) Get() interface{} {
|
||||
return *t
|
||||
}
|
||||
|
||||
// TimestampFlag is a flag with type time
|
||||
type TimestampFlag struct {
|
||||
Name string
|
||||
Aliases []string
|
||||
Usage string
|
||||
EnvVars []string
|
||||
FilePath string
|
||||
Required bool
|
||||
Hidden bool
|
||||
Layout string
|
||||
Value *Timestamp
|
||||
DefaultText string
|
||||
HasBeenSet bool
|
||||
}
|
||||
|
||||
// IsSet returns whether or not the flag has been set through env or file
|
||||
func (f *TimestampFlag) IsSet() bool {
|
||||
return f.HasBeenSet
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value
|
||||
// (for usage defaults)
|
||||
func (f *TimestampFlag) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// Names returns the names of the flag
|
||||
func (f *TimestampFlag) Names() []string {
|
||||
return flagNames(f)
|
||||
}
|
||||
|
||||
// IsRequired returns whether or not the flag is required
|
||||
func (f *TimestampFlag) IsRequired() bool {
|
||||
return f.Required
|
||||
}
|
||||
|
||||
// TakesValue returns true of the flag takes a value, otherwise false
|
||||
func (f *TimestampFlag) TakesValue() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
func (f *TimestampFlag) GetUsage() string {
|
||||
return f.Usage
|
||||
}
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
func (f *TimestampFlag) GetValue() string {
|
||||
if f.Value != nil {
|
||||
return f.Value.timestamp.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
|
||||
if f.Layout == "" {
|
||||
return fmt.Errorf("timestamp Layout is required")
|
||||
}
|
||||
f.Value = &Timestamp{}
|
||||
f.Value.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)
|
||||
}
|
||||
f.HasBeenSet = true
|
||||
}
|
||||
|
||||
for _, name := range f.Names() {
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timestamp gets the timestamp from a flag name
|
||||
func (c *Context) Timestamp(name string) *time.Time {
|
||||
if fs := lookupFlagSet(name, c); fs != nil {
|
||||
return lookupTimestamp(name, fs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fetches the timestamp value from the local timestampWrap
|
||||
func lookupTimestamp(name string, set *flag.FlagSet) *time.Time {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
return (f.Value.(*Timestamp)).Value()
|
||||
}
|
||||
return nil
|
||||
}
|
2
help.go
2
help.go
@ -138,7 +138,7 @@ func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) {
|
||||
if bflag, ok := flag.(*BoolFlag); ok && bflag.Hidden {
|
||||
continue
|
||||
}
|
||||
for _, name := range flag.Names(){
|
||||
for _, name := range flag.Names() {
|
||||
name = strings.TrimSpace(name)
|
||||
// this will get total count utf8 letters in flag name
|
||||
count := utf8.RuneCountInString(name)
|
||||
|
Loading…
Reference in New Issue
Block a user