diff --git a/.gitignore b/.gitignore index 7823778..faf70c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.coverprofile +node_modules/ diff --git a/.travis.yml b/.travis.yml index e7475b1..c879139 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,10 @@ language: go sudo: false +cache: + directories: + - node_modules + go: - 1.2.2 - 1.3.3 @@ -21,14 +25,20 @@ matrix: env: pip_install="sudo pip install" - go: 1.1.2 install: go get -v . - before_script: echo skipping gfmxr on $TRAVIS_GO_VERSION + before_script: echo skipping gfmxr on ${TRAVIS_GO_VERSION} script: - ./runtests vet - ./runtests test before_script: -- go get github.com/urfave/gfmxr/... - $pip_install flake8 +- if [ ! -f node_modules/.bin/markdown-toc ] ; then + npm install markdown-toc ; + fi +- mkdir -p ${GOPATH%%:*}/src/gopkg.in/urfave +- rm -rvf ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 +- ln -sv ${TRAVIS_BUILD_DIR} ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2 +- go get github.com/urfave/gfmxr/... script: - flake8 runtests cli-v1-to-v2 @@ -37,3 +47,4 @@ script: - ./runtests gfmxr - ./cli-v1-to-v2 --selftest - ./runtests migrations +- ./runtests toc diff --git a/CHANGELOG.md b/CHANGELOG.md index e72e968..808a08c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ - `./runtests` test runner with coverage tracking by default - testing on OS X - testing on Windows +- `UintFlag`, `Uint64Flag`, and `Int64Flag` types and supporting code ### Changed - Use spaces for alignment in help/usage output instead of tabs, making the diff --git a/README.md b/README.md index 0bd2a5a..7268140 100644 --- a/README.md +++ b/README.md @@ -1,3 +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) @@ -6,9 +9,6 @@ [![top level coverage](https://gocover.io/_badge/github.com/urfave/cli?0 "top level coverage")](http://gocover.io/github.com/urfave/cli) / [![altsrc coverage](https://gocover.io/_badge/github.com/urfave/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/urfave/cli/altsrc) - -# cli - **Notice:** This is the library formerly known as `github.com/codegangsta/cli` -- Github will automatically redirect requests to this repository, but we recommend updating your references for clarity. @@ -17,6 +17,37 @@ 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 applications in an expressive way. + + +- [Overview](#overview) +- [Installation](#installation) + * [Supported platforms](#supported-platforms) + * [Using the `v2` branch](#using-the-v2-branch) + * [Pinning to the `v1` branch](#pinning-to-the-v1-branch) +- [Getting Started](#getting-started) +- [Examples](#examples) + * [Arguments](#arguments) + * [Flags](#flags) + + [Placeholder Values](#placeholder-values) + + [Alternate Names](#alternate-names) + + [Values from the Environment](#values-from-the-environment) + + [Values from alternate input sources (YAML and others)](#values-from-alternate-input-sources-yaml-and-others) + * [Subcommands](#subcommands) + * [Subcommands categories](#subcommands-categories) + * [Exit code](#exit-code) + * [Bash Completion](#bash-completion) + + [Enabling](#enabling) + + [Distribution](#distribution) + + [Customization](#customization) + * [Generated Help Text](#generated-help-text) + + [Customization](#customization-1) + * [Version Flag](#version-flag) + + [Customization](#customization-2) + * [Full API Example](#full-api-example) +- [Contribution Guidelines](#contribution-guidelines) + + + ## Overview Command line apps are usually so tiny that there is absolutely no reason why @@ -214,7 +245,7 @@ COMMANDS: help, h Shows a list of commands or help for one command GLOBAL OPTIONS - --version Shows version information + --version Shows version information ``` ### Arguments @@ -986,13 +1017,13 @@ func main() { } ``` -#### Full API Example +### Full API Example **NOTE**: This is a contrived (functioning) example meant strictly for API demonstration purposes. Use of one's imagination is encouraged. ``` go package main @@ -1016,7 +1047,7 @@ func init() { cli.HelpFlag = &cli.BoolFlag{Name: "halp"} cli.BashCompletionFlag = &cli.BoolFlag{Name: "compgen", Hidden: true} - cli.VersionFlag = &cli.BoolFlag{Name: "print-version, V"} + cli.VersionFlag = &cli.BoolFlag{Name: "print-version", Aliases: []string{"V"}} cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { fmt.Fprintf(w, "best of luck to you\n") @@ -1044,6 +1075,19 @@ func (w *hexWriter) Write(p []byte) (int, error) { return len(p), nil } +type genericType struct { + s string +} + +func (g *genericType) Set(value string) error { + g.s = value + return nil +} + +func (g *genericType) String() string { + return g.s +} + func main() { app := cli.NewApp() app.Name = "kənˈtrīv" @@ -1070,7 +1114,7 @@ func main() { Description: "no really, there is a lot of dooing to be done", ArgsUsage: "[arrgh]", Flags: []cli.Flag{ - &cli.BoolFlag{Name: "forever, forevvarr"}, + &cli.BoolFlag{Name: "forever", Aliases: []string{"forevvarr"}}, }, Subcommands: []*cli.Command{ &cli.Command{ @@ -1113,7 +1157,17 @@ func main() { app.Flags = []cli.Flag{ &cli.BoolFlag{Name: "fancy"}, &cli.BoolFlag{Value: true, Name: "fancier"}, + &cli.DurationFlag{Name: "howlong", Aliases: []string{"H"}, Value: time.Second * 3}, + &cli.Float64Flag{Name: "howmuch"}, + &cli.GenericFlag{Name: "wat", Value: &genericType{}}, + &cli.Int64Flag{Name: "longdistance"}, + &cli.Int64SliceFlag{Name: "intervals"}, + &cli.IntFlag{Name: "distance"}, + &cli.IntSliceFlag{Name: "times"}, &cli.StringFlag{Name: "dance-move", Aliases: []string{"d"}}, + &cli.StringSliceFlag{Name: "names", Aliases: []string{"N"}}, + &cli.UintFlag{Name: "age"}, + &cli.Uint64Flag{Name: "bigage"}, } app.EnableBashCompletion = true app.HideHelp = false @@ -1190,10 +1244,14 @@ func main() { 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")) diff --git a/appveyor.yml b/appveyor.yml index ae80b2f..90de4d5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,20 +4,24 @@ os: Windows Server 2012 R2 clone_folder: c:\gopath\src\github.com\urfave\cli +cache: +- node_modules + environment: GOPATH: C:\gopath GOVERSION: 1.6 PYTHON: C:\Python27-x64 PYTHON_VERSION: 2.7.x PYTHON_ARCH: 64 - GFMXR_DEBUG: 1 install: - set PATH=%GOPATH%\bin;C:\go\bin;%PATH% - go version - go env +- git clone . c:\gopath\src\gopkg.in\urfave\cli.v2 - go get github.com/urfave/gfmxr/... - go get -v -t ./... +- if not exist node_modules\.bin\markdown-toc npm install markdown-toc build_script: - python runtests vet @@ -25,3 +29,4 @@ build_script: - python runtests gfmxr - python cli-v1-to-v2 --selftest - python runtests migrations +- python runtests toc diff --git a/context.go b/context.go index b43c957..2e07469 100644 --- a/context.go +++ b/context.go @@ -33,6 +33,30 @@ func (c *Context) Int(name string) int { return 0 } +// Int64 looks up the value of a local int flag, returns 0 if no int flag exists +func (c *Context) Int64(name string) int64 { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupInt64(name, fs) + } + return 0 +} + +// Uint looks up the value of a local int flag, returns 0 if no int flag exists +func (c *Context) Uint(name string) uint { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupUint(name, fs) + } + return 0 +} + +// Uint64 looks up the value of a local int flag, returns 0 if no int flag exists +func (c *Context) Uint64(name string) uint64 { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupUint64(name, fs) + } + return 0 +} + // Duration looks up the value of a local time.Duration flag, returns 0 if no // time.Duration flag exists func (c *Context) Duration(name string) time.Duration { @@ -51,6 +75,15 @@ func (c *Context) Float64(name string) float64 { return 0 } +// Int64Slice looks up the value of a local int slice flag, returns nil if no int +// slice flag exists +func (c *Context) Int64Slice(name string) []int64 { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupInt64Slice(name, fs) + } + return nil +} + // Bool looks up the value of a local bool flag, returns false if no bool flag exists func (c *Context) Bool(name string) bool { if fs := lookupFlagSet(name, c); fs != nil { @@ -171,7 +204,46 @@ func lookupFlagSet(name string, ctx *Context) *flag.FlagSet { func lookupInt(name string, set *flag.FlagSet) int { f := set.Lookup(name) if f != nil { - val, err := strconv.Atoi(f.Value.String()) + val, err := strconv.ParseInt(f.Value.String(), 0, 64) + if err != nil { + return 0 + } + return int(val) + } + + return 0 +} + +func lookupInt64(name string, set *flag.FlagSet) int64 { + f := set.Lookup(name) + if f != nil { + val, err := strconv.ParseInt(f.Value.String(), 0, 64) + if err != nil { + return 0 + } + return val + } + + return 0 +} + +func lookupUint(name string, set *flag.FlagSet) uint { + f := set.Lookup(name) + if f != nil { + val, err := strconv.ParseUint(f.Value.String(), 0, 64) + if err != nil { + return 0 + } + return uint(val) + } + + return 0 +} + +func lookupUint64(name string, set *flag.FlagSet) uint64 { + f := set.Lookup(name) + if f != nil { + val, err := strconv.ParseUint(f.Value.String(), 0, 64) if err != nil { return 0 } @@ -235,6 +307,16 @@ func lookupIntSlice(name string, set *flag.FlagSet) []int { return nil } +func lookupInt64Slice(name string, set *flag.FlagSet) []int64 { + f := set.Lookup(name) + if f != nil { + return (f.Value.(*Int64Slice)).Value() + + } + + return nil +} + func lookupGeneric(name string, set *flag.FlagSet) interface{} { f := set.Lookup(name) if f != nil { diff --git a/context_test.go b/context_test.go index 229275c..ac61dba 100644 --- a/context_test.go +++ b/context_test.go @@ -10,15 +10,24 @@ import ( func TestNewContext(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Int("myflag", 12, "doc") + set.Int64("myflagInt64", int64(12), "doc") + set.Uint("myflagUint", uint(93), "doc") + set.Uint64("myflagUint64", uint64(93), "doc") set.Float64("myflag64", float64(17), "doc") globalSet := flag.NewFlagSet("test", 0) globalSet.Int("myflag", 42, "doc") + globalSet.Int64("myflagInt64", int64(42), "doc") + globalSet.Uint("myflagUint", uint(33), "doc") + globalSet.Uint64("myflagUint64", uint64(33), "doc") globalSet.Float64("myflag64", float64(47), "doc") globalCtx := NewContext(nil, globalSet, nil) command := &Command{Name: "mycommand"} c := NewContext(nil, set, globalCtx) c.Command = command expect(t, c.Int("myflag"), 12) + expect(t, c.Int64("myflagInt64"), int64(12)) + expect(t, c.Uint("myflagUint"), uint(93)) + expect(t, c.Uint64("myflagUint64"), uint64(93)) expect(t, c.Float64("myflag64"), float64(17)) expect(t, c.Command.Name, "mycommand") } @@ -30,6 +39,27 @@ func TestContext_Int(t *testing.T) { expect(t, c.Int("myflag"), 12) } +func TestContext_Int64(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Int64("myflagInt64", 12, "doc") + c := NewContext(nil, set, nil) + expect(t, c.Int64("myflagInt64"), int64(12)) +} + +func TestContext_Uint(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Uint("myflagUint", uint(13), "doc") + c := NewContext(nil, set, nil) + expect(t, c.Uint("myflagUint"), uint(13)) +} + +func TestContext_Uint64(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Uint64("myflagUint64", uint64(9), "doc") + c := NewContext(nil, set, nil) + expect(t, c.Uint64("myflagUint64"), uint64(9)) +} + func TestContext_Float64(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Float64("myflag", float64(17), "doc") diff --git a/flag.go b/flag.go index e5f9e0a..3ff53ce 100644 --- a/flag.go +++ b/flag.go @@ -219,6 +219,11 @@ func NewIntSlice(defaults ...int) *IntSlice { return &IntSlice{slice: append([]int{}, defaults...)} } +// NewInt64Slice makes an *Int64Slice with default values +func NewInt64Slice(defaults ...int64) *Int64Slice { + return &Int64Slice{slice: append([]int64{}, defaults...)} +} + // SetInt directly adds an integer to the list of values func (i *IntSlice) SetInt(value int) { if !i.hasBeenSet { @@ -243,18 +248,18 @@ func (i *IntSlice) Set(value string) error { return nil } - tmp, err := strconv.Atoi(value) + tmp, err := strconv.ParseInt(value, 0, 64) if err != nil { return err } - i.slice = append(i.slice, tmp) + i.slice = append(i.slice, int(tmp)) return nil } // String returns a readable representation of this value (for usage defaults) -func (i *IntSlice) String() string { - return fmt.Sprintf("%v", i.slice) +func (f *IntSlice) String() string { + return fmt.Sprintf("%#v", f.slice) } // Serialized allows IntSlice to fulfill Serializeder @@ -317,6 +322,100 @@ func (f *IntSliceFlag) Names() []string { return flagNames(f) } +// Int64Slice is an opaque type for []int to satisfy flag.Value +type Int64Slice struct { + slice []int64 + hasBeenSet bool +} + +// Set parses the value into an integer and appends it to the list of values +func (f *Int64Slice) Set(value string) error { + if !f.hasBeenSet { + f.slice = []int64{} + f.hasBeenSet = true + } + + if strings.HasPrefix(value, slPfx) { + // Deserializing assumes overwrite + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &f.slice) + f.hasBeenSet = true + return nil + } + + tmp, err := strconv.ParseInt(value, 0, 64) + if err != nil { + return err + } + + f.slice = append(f.slice, tmp) + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (f *Int64Slice) String() string { + return fmt.Sprintf("%#v", f.slice) +} + +// Serialized allows Int64Slice to fulfill Serializeder +func (f *Int64Slice) Serialized() string { + jsonBytes, _ := json.Marshal(f.slice) + return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) +} + +// Value returns the slice of ints set by this flag +func (f *Int64Slice) Value() []int64 { + return f.slice +} + +// Int64SliceFlag is an int flag that can be specified multiple times on the +// command-line +type Int64SliceFlag struct { + Name string + Aliases []string + Value *Int64Slice + Usage string + EnvVars []string + Hidden bool +} + +// String returns the usage +func (f *Int64SliceFlag) String() string { + return FlagStringer(f) +} + +// Apply populates the flag given the flag set and environment +func (f *Int64SliceFlag) Apply(set *flag.FlagSet) { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { + if envVal := os.Getenv(envVar); envVal != "" { + newVal := NewInt64Slice() + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + err := newVal.Set(s) + if err != nil { + fmt.Fprintf(ErrWriter, err.Error()) + } + } + f.Value = newVal + break + } + } + } + + if f.Value == nil { + f.Value = NewInt64Slice() + } + + for _, name := range f.Names() { + set.Var(f.Value, name, f.Usage) + } +} + +// Names returns the names of the flag. +func (f *Int64SliceFlag) Names() []string { + return flagNames(f) +} + // BoolFlag is a switch that defaults to false type BoolFlag struct { Name string @@ -403,7 +502,6 @@ func (f *StringFlag) Names() []string { } // IntFlag is a flag that takes an integer -// Errors if the value provided cannot be parsed type IntFlag struct { Name string Aliases []string @@ -447,6 +545,138 @@ func (f *IntFlag) Names() []string { return flagNames(f) } +// Int64Flag is a flag that takes a 64-bit integer +type Int64Flag struct { + Name string + Aliases []string + Value int64 + Usage string + EnvVars []string + Destination *int64 + Hidden bool +} + +// String returns the usage +func (f *Int64Flag) String() string { + return FlagStringer(f) +} + +// Apply populates the flag given the flag set and environment +func (f *Int64Flag) Apply(set *flag.FlagSet) { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { + if envVal := os.Getenv(envVar); envVal != "" { + envValInt, err := strconv.ParseInt(envVal, 0, 64) + if err == nil { + f.Value = envValInt + break + } + } + } + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.Int64Var(f.Destination, name, f.Value, f.Usage) + return + } + set.Int64(name, f.Value, f.Usage) + } +} + +// Names returns the names of the flag. +func (f *Int64Flag) Names() []string { + return flagNames(f) +} + +// UintFlag is a flag that takes an unsigned integer +type UintFlag struct { + Name string + Aliases []string + Value uint + Usage string + EnvVars []string + Destination *uint + Hidden bool +} + +// String returns the usage +func (f *UintFlag) String() string { + return FlagStringer(f) +} + +// Apply populates the flag given the flag set and environment +func (f *UintFlag) Apply(set *flag.FlagSet) { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { + if envVal := os.Getenv(envVar); envVal != "" { + envValInt, err := strconv.ParseUint(envVal, 0, 64) + if err == nil { + f.Value = uint(envValInt) + break + } + } + } + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.UintVar(f.Destination, name, f.Value, f.Usage) + return + } + set.Uint(name, f.Value, f.Usage) + } +} + +// Names returns the names of the flag. +func (f *UintFlag) Names() []string { + return flagNames(f) +} + +// Uint64Flag is a flag that takes an unsigned 64-bit integer +type Uint64Flag struct { + Name string + Aliases []string + Value uint64 + Usage string + EnvVars []string + Destination *uint64 + Hidden bool +} + +// String returns the usage +func (f *Uint64Flag) String() string { + return FlagStringer(f) +} + +// Apply populates the flag given the flag set and environment +func (f *Uint64Flag) Apply(set *flag.FlagSet) { + if f.EnvVars != nil { + for _, envVar := range f.EnvVars { + if envVal := os.Getenv(envVar); envVal != "" { + envValInt, err := strconv.ParseUint(envVal, 0, 64) + if err == nil { + f.Value = uint64(envValInt) + break + } + } + } + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.Uint64Var(f.Destination, name, f.Value, f.Usage) + return + } + set.Uint64(name, f.Value, f.Usage) + } +} + +// Names returns the names of the flag. +func (f *Uint64Flag) Names() []string { + return flagNames(f) +} + // DurationFlag is a flag that takes a duration specified in Go's duration // format: https://golang.org/pkg/time/#ParseDuration type DurationFlag struct { @@ -493,7 +723,6 @@ func (f *DurationFlag) Names() []string { } // Float64Flag is a flag that takes an float value -// Errors if the value provided cannot be parsed type Float64Flag struct { Name string Aliases []string @@ -659,9 +888,14 @@ func stringifyFlag(f Flag) string { switch f.(type) { case *IntSliceFlag: - return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyIntSliceFlag(f.(*IntSliceFlag))) + return withEnvHint(flagStringSliceField(f, "EnvVars"), + stringifyIntSliceFlag(f.(*IntSliceFlag))) + case *Int64SliceFlag: + return withEnvHint(flagStringSliceField(f, "EnvVars"), + stringifyInt64SliceFlag(f.(*Int64SliceFlag))) case *StringSliceFlag: - return withEnvHint(flagStringSliceField(f, "EnvVars"), stringifyStringSliceFlag(f.(*StringSliceFlag))) + return withEnvHint(flagStringSliceField(f, "EnvVars"), + stringifyStringSliceFlag(f.(*StringSliceFlag))) } placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) @@ -701,7 +935,18 @@ func stringifyIntSliceFlag(f *IntSliceFlag) string { } } - return stringifySliceFlag(f.Usage, append([]string{f.Name}, f.Aliases...), defaultVals) + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + +func stringifyInt64SliceFlag(f *Int64SliceFlag) string { + defaultVals := []string{} + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, fmt.Sprintf("%d", i)) + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) } func stringifyStringSliceFlag(f *StringSliceFlag) string { @@ -714,7 +959,7 @@ func stringifyStringSliceFlag(f *StringSliceFlag) string { } } - return stringifySliceFlag(f.Usage, append([]string{f.Name}, f.Aliases...), defaultVals) + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) } func stringifySliceFlag(usage string, names, defaultVals []string) string { diff --git a/flag_test.go b/flag_test.go index b510240..1b69f51 100644 --- a/flag_test.go +++ b/flag_test.go @@ -192,6 +192,114 @@ func TestIntFlagApply_SetsAllNames(t *testing.T) { expect(t, v, 5) } +var int64FlagTests = []struct { + name string + expected string +}{ + {"hats", "--hats value\t(default: 8589934592)"}, + {"H", "-H value\t(default: 8589934592)"}, +} + +func TestInt64FlagHelpOutput(t *testing.T) { + for _, test := range int64FlagTests { + flag := Int64Flag{Name: test.name, Value: 8589934592} + output := flag.String() + + if output != test.expected { + t.Errorf("%s does not match %s", output, test.expected) + } + } +} + +func TestInt64FlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_BAR", "2") + for _, test := range int64FlagTests { + flag := IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} + output := flag.String() + + expectedSuffix := " [$APP_BAR]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_BAR%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with"+expectedSuffix, output) + } + } +} + +var uintFlagTests = []struct { + name string + expected string +}{ + {"nerfs", "--nerfs value\t(default: 41)"}, + {"N", "-N value\t(default: 41)"}, +} + +func TestUintFlagHelpOutput(t *testing.T) { + for _, test := range uintFlagTests { + flag := UintFlag{Name: test.name, Value: 41} + output := flag.String() + + if output != test.expected { + t.Errorf("%s does not match %s", output, test.expected) + } + } +} + +func TestUintFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_BAR", "2") + for _, test := range uintFlagTests { + flag := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} + output := flag.String() + + expectedSuffix := " [$APP_BAR]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_BAR%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with"+expectedSuffix, output) + } + } +} + +var uint64FlagTests = []struct { + name string + expected string +}{ + {"gerfs", "--gerfs value\t(default: 8589934582)"}, + {"G", "-G value\t(default: 8589934582)"}, +} + +func TestUint64FlagHelpOutput(t *testing.T) { + for _, test := range uint64FlagTests { + flag := Uint64Flag{Name: test.name, Value: 8589934582} + output := flag.String() + + if output != test.expected { + t.Errorf("%s does not match %s", output, test.expected) + } + } +} + +func TestUint64FlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_BAR", "2") + for _, test := range uint64FlagTests { + flag := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}} + output := flag.String() + + expectedSuffix := " [$APP_BAR]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_BAR%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with"+expectedSuffix, output) + } + } +} + var durationFlagTests = []struct { name string expected string @@ -287,6 +395,46 @@ func TestIntSliceFlagApply_SetsAllNames(t *testing.T) { expect(t, err, nil) } +var int64SliceFlagTests = []struct { + name string + aliases []string + value *Int64Slice + expected string +}{ + {"heads", nil, NewInt64Slice(), "--heads value\t"}, + {"H", nil, NewInt64Slice(), "-H value\t"}, + {"heads", []string{"H"}, NewInt64Slice(int64(2), int64(17179869184)), + "--heads value, -H value\t(default: 2, 17179869184)"}, +} + +func TestInt64SliceFlagHelpOutput(t *testing.T) { + for _, test := range int64SliceFlagTests { + flag := Int64SliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} + output := flag.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestInt64SliceFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_SMURF", "42,17179869184") + for _, test := range int64SliceFlagTests { + flag := Int64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}} + output := flag.String() + + expectedSuffix := " [$APP_SMURF]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_SMURF%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%q does not end with"+expectedSuffix, output) + } + } +} + var float64FlagTests = []struct { name string expected string @@ -775,6 +923,63 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { }).Run([]string{"run"}) } +func TestParseMultiInt64Slice(t *testing.T) { + (&App{ + Flags: []Flag{ + &Int64SliceFlag{Name: "serve", Aliases: []string{"s"}, Value: NewInt64Slice()}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.Int64Slice("serve"), []int64{10, 17179869184}) { + t.Errorf("main name not set") + } + if !reflect.DeepEqual(ctx.Int64Slice("s"), []int64{10, 17179869184}) { + t.Errorf("short name not set") + } + return nil + }, + }).Run([]string{"run", "-s", "10", "-s", "17179869184"}) +} + +func TestParseMultiInt64SliceFromEnv(t *testing.T) { + os.Clearenv() + os.Setenv("APP_INTERVALS", "20,30,17179869184") + + (&App{ + Flags: []Flag{ + &Int64SliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewInt64Slice(), EnvVars: []string{"APP_INTERVALS"}}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.Int64Slice("intervals"), []int64{20, 30, 17179869184}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.Int64Slice("i"), []int64{20, 30, 17179869184}) { + t.Errorf("short name not set from env") + } + return nil + }, + }).Run([]string{"run"}) +} + +func TestParseMultiInt64SliceFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_INTERVALS", "20,30,17179869184") + + (&App{ + Flags: []Flag{ + &Int64SliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: NewInt64Slice(), EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.Int64Slice("intervals"), []int64{20, 30, 17179869184}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.Int64Slice("i"), []int64{20, 30, 17179869184}) { + t.Errorf("short name not set from env") + } + return nil + }, + }).Run([]string{"run"}) +} + func TestParseMultiFloat64(t *testing.T) { a := App{ Flags: []Flag{ @@ -1131,3 +1336,19 @@ func TestIntSlice_Serialized_Set(t *testing.T) { t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1) } } + +func TestInt64Slice_Serialized_Set(t *testing.T) { + sl0 := NewInt64Slice(int64(1), int64(2)) + ser0 := sl0.Serialized() + + if len(ser0) < len(slPfx) { + t.Fatalf("serialized shorter than expected: %q", ser0) + } + + sl1 := NewInt64Slice(int64(3), int64(4)) + sl1.Set(ser0) + + if sl0.String() != sl1.String() { + t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1) + } +} diff --git a/runtests b/runtests index 4975f96..5b903e6 100755 --- a/runtests +++ b/runtests @@ -4,6 +4,7 @@ from __future__ import print_function import argparse import glob import os +import platform import shutil import sys import tempfile @@ -11,6 +12,7 @@ import tempfile from subprocess import check_call, check_output +_WINDOWS = platform.system().lower() == 'windows' _PACKAGE_NAME = os.environ.get( 'CLI_PACKAGE_NAME', 'github.com/urfave/cli' ) @@ -92,8 +94,19 @@ def _migrations(): shutil.rmtree(tmpdir, ignore_errors=True) +@_target +def _toc(): + exe = ['bash'] if _WINDOWS else [] + _run(exe + [ + os.path.join('node_modules', '.bin', 'markdown-toc'), + '-i', 'README.md' + ]) + _run(['git', 'diff', '--quiet']) + + def _run(command): print('runtests: {}'.format(' '.join(command)), file=sys.stderr) + sys.stderr.flush() check_call(command)