Merge remote-tracking branch 'origin' into github-actions-windows

This commit is contained in:
Lynn 2019-12-25 09:03:27 -08:00
commit 65d28dcc47
No known key found for this signature in database
GPG Key ID: 9E60BEE0555C367B
19 changed files with 453 additions and 73 deletions

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

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

View File

@ -1,6 +1,10 @@
name: Run Tests name: Run Tests
on: on:
push:
branches:
- master
- v1
pull_request: pull_request:
branches: branches:
- master - 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" 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 npm install markdown-toc
- name: Run Tests (v1) (Linux / MacOS) - name: Run Tests
if: contains(github.base_ref, 'v1') && matrix.os != 'windows-latest'
run: | run: |
go run build.go vet go run build.go vet
go run build.go test 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 gfmrun docs/v1/manual.md
go run build.go toc docs/v1/manual.md go run build.go toc docs/v1/manual.md
- name: Run Tests (v1) (Windows) - name: Run Tests (v1) (Windows)
if: contains(github.base_ref, 'v1') && matrix.os == 'windows-latest' if: contains(github.base_ref, 'v1') && matrix.os == 'windows-latest'
run: | run: |
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
go run build.go toc docs\v1\manual.md go run build.go toc docs\v1\manual.md
- name: Run Tests (v2) (Linux / MacOS) - name: Run Tests (v2) (Linux / MacOS)
if: contains(github.base_ref, 'master') && matrix.os != 'windows-latest' if: contains(github.base_ref, 'master') && matrix.os != 'windows-latest'
run: | run: |
go run build.go vet
go run build.go test
go run build.go gfmrun docs/v2/manual.md go run build.go gfmrun docs/v2/manual.md
go run build.go toc docs/v2/manual.md go run build.go toc docs/v2/manual.md
- name: Run Tests (v2) (Windows) - name: Run Tests (v2) (Windows)
if: contains(github.base_ref, 'master') && matrix.os == 'windows-latest' if: contains(github.base_ref, 'master') && matrix.os == 'windows-latest'
run: | run: |
go run build.go vet
go run build.go test
go run build.go gfmrun docs/v2/manual.md go run build.go gfmrun docs/v2/manual.md
go run build.go toc docs/v2/manual.md go run build.go toc docs/v2/manual.md
- name: Send Coverage Report - name: Upload coverage to Codecov
if: success() if: success() && matrix.go == 1.13 && matrix.os == 'ubuntu-latest'
env: uses: codecov/codecov-action@v1
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with:
run: bash <(curl -s https://codecov.io/bash) token: 0a8cc73b-bb7c-480b-8626-38a461643761
fail_ci_if_error: true

View File

@ -1,7 +1,6 @@
cli 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) [![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) [![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) - `v2` - [./docs/v2/manual.md](./docs/v2/manual.md)
- `v1` - [./docs/v1/manual.md](./docs/v1/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 ### Using `v2` releases
``` ```
@ -37,7 +42,7 @@ import (
### Using `v1` releases ### Using `v1` releases
``` ```
$ go get github.com/urfave/cli $ GO111MODULE=on go get github.com/urfave/cli
``` ```
```go ```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 ### GOPATH
Make sure your `PATH` includes the `$GOPATH/bin` directory so your commands can Make sure your `PATH` includes the `$GOPATH/bin` directory so your commands can
@ -64,5 +64,5 @@ export PATH=$PATH:$GOPATH/bin
### Supported platforms ### Supported platforms
cli is tested against multiple versions of Go on Linux, and against the latest 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 released version of Go on OS X and Windows. This project uses Github Actions for
[`./.travis.yml`](./.travis.yml) and [`./appveyor.yml`](./appveyor.yml). 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
View File

@ -1,6 +1,7 @@
package cli package cli
import ( import (
"context"
"flag" "flag"
"fmt" "fmt"
"io" "io"
@ -12,7 +13,7 @@ import (
) )
var ( 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) 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." 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. "+ 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 // Run is the entry point to the cli app. Parses the arguments slice and routes
// to the proper flag/args combination // to the proper flag/args combination
func (a *App) Run(arguments []string) (err error) { 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() a.Setup()
// handle the completion flag separately from the flagset since // 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) err = parseIter(set, a, arguments[1:], shellComplete)
nerr := normalizeFlags(a.Flags, set) nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, nil) context := NewContext(a, set, &Context{Context: ctx})
if nerr != nil { if nerr != nil {
_, _ = fmt.Fprintln(a.Writer, nerr) _, _ = fmt.Fprintln(a.Writer, nerr)
_ = ShowAppHelp(context) _ = ShowAppHelp(context)

View File

@ -7,7 +7,6 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"reflect" "reflect"
"strings" "strings"
@ -2121,7 +2120,7 @@ func TestWhenExitSubCommandWithCodeThenAppQuitUnexpectedly(t *testing.T) {
var exitCodeFromExitErrHandler int var exitCodeFromExitErrHandler int
app.ExitErrHandler = func(c *Context, err error) { app.ExitErrHandler = func(c *Context, err error) {
if exitErr, ok := err.(ExitCoder); ok { if exitErr, ok := err.(ExitCoder); ok {
log.Print(exitErr) t.Log(exitErr)
exitCodeFromExitErrHandler = exitErr.ExitCode() exitCodeFromExitErrHandler = exitErr.ExitCode()
} }
} }

View File

@ -20,7 +20,6 @@ install:
- go version - go version
- go env - go env
- go get github.com/urfave/gfmrun/cmd/gfmrun - go get github.com/urfave/gfmrun/cmd/gfmrun
- go get golang.org/x/tools/cmd/goimports
- go mod tidy - go mod tidy
build_script: build_script:

View File

@ -1,5 +1,6 @@
package cli package cli
// CommandCategories interface allows for category manipulation
type CommandCategories interface { type CommandCategories interface {
// AddCommand adds a command to a category, creating a new category if necessary. // AddCommand adds a command to a category, creating a new category if necessary.
AddCommand(category string, command *Command) AddCommand(category string, command *Command)

View File

@ -5,10 +5,7 @@ import (
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
"os"
"os/signal"
"strings" "strings"
"syscall"
) )
// Context is a type that is passed through to // 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 { if parentCtx != nil {
c.Context = parentCtx.Context c.Context = parentCtx.Context
c.shellComplete = parentCtx.shellComplete c.shellComplete = parentCtx.shellComplete
if parentCtx.flagSet == nil {
parentCtx.flagSet = &flag.FlagSet{}
}
} }
c.Command = &Command{} c.Command = &Command{}
if c.Context == nil { if c.Context == nil {
ctx, cancel := context.WithCancel(context.Background()) c.Context = context.Background()
go func() {
defer cancel()
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
<-sigs
}()
c.Context = ctx
} }
return c return c

View File

@ -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) { func TestCheckRequiredFlags(t *testing.T) {
tdata := []struct { tdata := []struct {
testCase string testCase string

View File

@ -48,8 +48,8 @@ func (a *App) writeDocTemplate(w io.Writer) error {
return t.ExecuteTemplate(w, name, &cliTemplate{ return t.ExecuteTemplate(w, name, &cliTemplate{
App: a, App: a,
Commands: prepareCommands(a.Commands, 0), Commands: prepareCommands(a.Commands, 0),
GlobalArgs: prepareArgsWithValues(a.Flags), GlobalArgs: prepareArgsWithValues(a.VisibleFlags()),
SynopsisArgs: prepareArgsSynopsis(a.Flags), SynopsisArgs: prepareArgsSynopsis(a.VisibleFlags()),
}) })
} }

View File

@ -6,6 +6,33 @@
View [unreleased 2.X] series changes. 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 ## [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: 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 ### Added
- Initial implementation. - 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 [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 [unreleased 1.22.X]: https://github.com/urfave/cli/compare/v1.22.2...v1

View File

@ -13,6 +13,7 @@ cli v2 manual
+ [Values from the Environment](#values-from-the-environment) + [Values from the Environment](#values-from-the-environment)
+ [Values from files](#values-from-files) + [Values from files](#values-from-files)
+ [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others) + [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) + [Default Values for help output](#default-values-for-help-output)
+ [Precedence](#precedence) + [Precedence](#precedence)
* [Subcommands](#subcommands) * [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 #### 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. 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) cli.ShowVersion(c)
fmt.Printf("%#v\n", c.App.Command("doo")) fmt.Printf("%#v\n", c.App.Command("doo"))
if c.Bool("infinite") { // // uncomment when https://github.com/urfave/cli/pull/1014 is released
c.App.Run([]string{"app", "doo", "wop"}) // if c.Bool("infinite") {
} // c.App.Run([]string{"app", "doo", "wop"})
// }
if c.Bool("forevar") { // // uncomment when https://github.com/urfave/cli/pull/1014 is released
c.App.RunAsSubcommand(c) // if c.Bool("forevar") {
} // c.App.RunAsSubcommand(c)
// }
c.App.Setup() c.App.Setup()
fmt.Printf("%#v\n", c.App.VisibleCategories()) fmt.Printf("%#v\n", c.App.VisibleCategories())
fmt.Printf("%#v\n", c.App.VisibleCommands()) fmt.Printf("%#v\n", c.App.VisibleCommands())
@ -1424,28 +1484,29 @@ func main() {
set := flag.NewFlagSet("contrive", 0) set := flag.NewFlagSet("contrive", 0)
nc := cli.NewContext(c.App, set, c) nc := cli.NewContext(c.App, set, c)
fmt.Printf("%#v\n", nc.Args()) // // uncomment when https://github.com/urfave/cli/pull/1014 is released
fmt.Printf("%#v\n", nc.Bool("nope")) // fmt.Printf("%#v\n", nc.Args())
fmt.Printf("%#v\n", !nc.Bool("nerp")) // fmt.Printf("%#v\n", nc.Bool("nope"))
fmt.Printf("%#v\n", nc.Duration("howlong")) // fmt.Printf("%#v\n", !nc.Bool("nerp"))
fmt.Printf("%#v\n", nc.Float64("hay")) // fmt.Printf("%#v\n", nc.Duration("howlong"))
fmt.Printf("%#v\n", nc.Generic("bloop")) // fmt.Printf("%#v\n", nc.Float64("hay"))
fmt.Printf("%#v\n", nc.Int64("bonk")) // fmt.Printf("%#v\n", nc.Generic("bloop"))
fmt.Printf("%#v\n", nc.Int64Slice("burnks")) // fmt.Printf("%#v\n", nc.Int64("bonk"))
fmt.Printf("%#v\n", nc.Int("bips")) // fmt.Printf("%#v\n", nc.Int64Slice("burnks"))
fmt.Printf("%#v\n", nc.IntSlice("blups")) // fmt.Printf("%#v\n", nc.Int("bips"))
fmt.Printf("%#v\n", nc.String("snurt")) // fmt.Printf("%#v\n", nc.IntSlice("blups"))
fmt.Printf("%#v\n", nc.StringSlice("snurkles")) // fmt.Printf("%#v\n", nc.String("snurt"))
fmt.Printf("%#v\n", nc.Uint("flub")) // fmt.Printf("%#v\n", nc.StringSlice("snurkles"))
fmt.Printf("%#v\n", nc.Uint64("florb")) // 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.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") nc.Set("wat", "also-nope")
ec := cli.Exit("ohwell", 86) ec := cli.Exit("ohwell", 86)

View File

@ -23,6 +23,10 @@ func testApp() *App {
Aliases: []string{"b"}, Aliases: []string{"b"},
Usage: "another usage text", Usage: "another usage text",
}, },
&BoolFlag{
Name: "hidden-flag",
Hidden: true,
},
} }
app.Commands = []*Command{{ app.Commands = []*Command{{
Aliases: []string{"c"}, Aliases: []string{"c"},

View File

@ -48,6 +48,7 @@ func (m *multiError) Errors() []error {
return errs return errs
} }
// ErrorFormatter is the interface that will suitably format the error output
type ErrorFormatter interface { type ErrorFormatter interface {
Format(s fmt.State, verb rune) Format(s fmt.State, verb rune)
} }

View File

@ -22,7 +22,7 @@ type Float64Flag struct {
} }
// IsSet returns whether or not the flag has been set through env or file // 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 return f.HasBeenSet
} }

View File

@ -72,7 +72,7 @@ func (f *PathFlag) Apply(set *flag.FlagSet) error {
return nil 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 // "" if not found
func (c *Context) Path(name string) string { func (c *Context) Path(name string) string {
if fs := lookupFlagSet(name, c); fs != nil { if fs := lookupFlagSet(name, c); fs != nil {

View File

@ -1678,3 +1678,58 @@ func TestInt64Slice_Serialized_Set(t *testing.T) {
t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1) 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
View 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: &timestamp}
}
// 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 = &timestamp
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
}

View File

@ -138,7 +138,7 @@ func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) {
if bflag, ok := flag.(*BoolFlag); ok && bflag.Hidden { if bflag, ok := flag.(*BoolFlag); ok && bflag.Hidden {
continue continue
} }
for _, name := range flag.Names(){ for _, name := range flag.Names() {
name = strings.TrimSpace(name) name = strings.TrimSpace(name)
// this will get total count utf8 letters in flag name // this will get total count utf8 letters in flag name
count := utf8.RuneCountInString(name) count := utf8.RuneCountInString(name)