From ff1c0b58dd975249d2e49a4893438a872d85ceed Mon Sep 17 00:00:00 2001 From: Michael Schuett Date: Sun, 27 Jan 2019 01:41:06 -0500 Subject: [PATCH 001/125] Start of category flag support This adds what I think needs to be done to support categories for flags but we will see if that works. It also forces the scripts to use python2 since they blow up under python3 which is becoming the default python on many linux systems. Small fix to app_test as well so it conforms to the new Flag interface. --- app_test.go | 4 +++ category.go | 43 +++++++++++++++++++++++++ flag.go | 3 ++ flag_generated.go | 78 +++++++++++++++++++++++++++++++++++++++++++++ generate-flag-types | 8 ++++- runtests | 2 +- 6 files changed, 136 insertions(+), 2 deletions(-) diff --git a/app_test.go b/app_test.go index 629681e..95ff66b 100644 --- a/app_test.go +++ b/app_test.go @@ -1603,6 +1603,10 @@ func (c *customBoolFlag) GetName() string { return c.Nombre } +func (c *customBoolFlag) GetHidden() bool { + return false +} + func (c *customBoolFlag) Apply(set *flag.FlagSet) { set.String(c.Nombre, c.Nombre, "") } diff --git a/category.go b/category.go index bf3c73c..af478a5 100644 --- a/category.go +++ b/category.go @@ -42,3 +42,46 @@ func (c *CommandCategory) VisibleCommands() []Command { } return ret } + +// FlagCategories is a slice of *FlagCategory. +type FlagCategories []*FlagCategory + +// FlagCategory is a category containing commands. +type FlagCategory struct { + Name string + Flags Flags +} + +func (f FlagCategories) Less(i, j int) bool { + return lexicographicLess(f[i].Name, f[j].Name) +} + +func (f FlagCategories) Len() int { + return len(f) +} + +func (f FlagCategories) Swap(i, j int) { + f[i], f[j] = f[j], f[i] +} + +// AddFlags adds a command to a category. +func (f FlagCategories) AddFlag(category string, flag Flag) FlagCategories { + for _, flagCategory := range f { + if flagCategory.Name == category { + flagCategory.Flags = append(flagCategory.Flags, flag) + return f + } + } + return append(f, &FlagCategory{Name: category, Flags: []Flag{flag}}) +} + +// VisibleFlags returns a slice of the Flags with Hidden=false +func (c *FlagCategory) VisibleFlags() []Flag { + ret := []Flag{} + for _, flag := range c.Flags { + if !flag.GetHidden() { + ret = append(ret, flag) + } + } + return ret +} diff --git a/flag.go b/flag.go index b0cffc0..f3274cd 100644 --- a/flag.go +++ b/flag.go @@ -73,8 +73,11 @@ type Flag interface { // Apply Flag settings to the given flag set Apply(*flag.FlagSet) GetName() string + GetHidden() bool } +type Flags []Flag + // errorableFlag is an interface that allows us to return errors during apply // it allows flags defined in this library to return errors in a fashion backwards compatible // TODO remove in v2 and modify the existing Flag interface to return errors diff --git a/flag_generated.go b/flag_generated.go index 001576c..b699b91 100644 --- a/flag_generated.go +++ b/flag_generated.go @@ -15,6 +15,7 @@ type BoolFlag struct { EnvVar string FilePath string Hidden bool + Category string Destination *bool } @@ -29,6 +30,11 @@ func (f BoolFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f BoolFlag) GetHidden() bool { + return f.Hidden +} + // Bool looks up the value of a local BoolFlag, returns // false if not found func (c *Context) Bool(name string) bool { @@ -63,6 +69,7 @@ type BoolTFlag struct { EnvVar string FilePath string Hidden bool + Category string Destination *bool } @@ -77,6 +84,11 @@ func (f BoolTFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f BoolTFlag) GetHidden() bool { + return f.Hidden +} + // BoolT looks up the value of a local BoolTFlag, returns // false if not found func (c *Context) BoolT(name string) bool { @@ -111,6 +123,7 @@ type DurationFlag struct { EnvVar string FilePath string Hidden bool + Category string Value time.Duration Destination *time.Duration } @@ -126,6 +139,11 @@ func (f DurationFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f DurationFlag) GetHidden() bool { + return f.Hidden +} + // Duration looks up the value of a local DurationFlag, returns // 0 if not found func (c *Context) Duration(name string) time.Duration { @@ -160,6 +178,7 @@ type Float64Flag struct { EnvVar string FilePath string Hidden bool + Category string Value float64 Destination *float64 } @@ -175,6 +194,11 @@ func (f Float64Flag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f Float64Flag) GetHidden() bool { + return f.Hidden +} + // Float64 looks up the value of a local Float64Flag, returns // 0 if not found func (c *Context) Float64(name string) float64 { @@ -209,6 +233,7 @@ type GenericFlag struct { EnvVar string FilePath string Hidden bool + Category string Value Generic } @@ -223,6 +248,11 @@ func (f GenericFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f GenericFlag) GetHidden() bool { + return f.Hidden +} + // Generic looks up the value of a local GenericFlag, returns // nil if not found func (c *Context) Generic(name string) interface{} { @@ -257,6 +287,7 @@ type Int64Flag struct { EnvVar string FilePath string Hidden bool + Category string Value int64 Destination *int64 } @@ -272,6 +303,11 @@ func (f Int64Flag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f Int64Flag) GetHidden() bool { + return f.Hidden +} + // Int64 looks up the value of a local Int64Flag, returns // 0 if not found func (c *Context) Int64(name string) int64 { @@ -306,6 +342,7 @@ type IntFlag struct { EnvVar string FilePath string Hidden bool + Category string Value int Destination *int } @@ -321,6 +358,11 @@ func (f IntFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f IntFlag) GetHidden() bool { + return f.Hidden +} + // Int looks up the value of a local IntFlag, returns // 0 if not found func (c *Context) Int(name string) int { @@ -355,6 +397,7 @@ type IntSliceFlag struct { EnvVar string FilePath string Hidden bool + Category string Value *IntSlice } @@ -369,6 +412,11 @@ func (f IntSliceFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f IntSliceFlag) GetHidden() bool { + return f.Hidden +} + // IntSlice looks up the value of a local IntSliceFlag, returns // nil if not found func (c *Context) IntSlice(name string) []int { @@ -403,6 +451,7 @@ type Int64SliceFlag struct { EnvVar string FilePath string Hidden bool + Category string Value *Int64Slice } @@ -417,6 +466,11 @@ func (f Int64SliceFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f Int64SliceFlag) GetHidden() bool { + return f.Hidden +} + // Int64Slice looks up the value of a local Int64SliceFlag, returns // nil if not found func (c *Context) Int64Slice(name string) []int64 { @@ -451,6 +505,7 @@ type StringFlag struct { EnvVar string FilePath string Hidden bool + Category string Value string Destination *string } @@ -466,6 +521,11 @@ func (f StringFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f StringFlag) GetHidden() bool { + return f.Hidden +} + // String looks up the value of a local StringFlag, returns // "" if not found func (c *Context) String(name string) string { @@ -500,6 +560,7 @@ type StringSliceFlag struct { EnvVar string FilePath string Hidden bool + Category string Value *StringSlice } @@ -514,6 +575,11 @@ func (f StringSliceFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f StringSliceFlag) GetHidden() bool { + return f.Hidden +} + // StringSlice looks up the value of a local StringSliceFlag, returns // nil if not found func (c *Context) StringSlice(name string) []string { @@ -548,6 +614,7 @@ type Uint64Flag struct { EnvVar string FilePath string Hidden bool + Category string Value uint64 Destination *uint64 } @@ -563,6 +630,11 @@ func (f Uint64Flag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f Uint64Flag) GetHidden() bool { + return f.Hidden +} + // Uint64 looks up the value of a local Uint64Flag, returns // 0 if not found func (c *Context) Uint64(name string) uint64 { @@ -597,6 +669,7 @@ type UintFlag struct { EnvVar string FilePath string Hidden bool + Category string Value uint Destination *uint } @@ -612,6 +685,11 @@ func (f UintFlag) GetName() string { return f.Name } +// GetHidden lets us know if the flag is hidden or not +func (f UintFlag) GetHidden() bool { + return f.Hidden +} + // Uint looks up the value of a local UintFlag, returns // 0 if not found func (c *Context) Uint(name string) uint { diff --git a/generate-flag-types b/generate-flag-types index 1358857..324b3c1 100755 --- a/generate-flag-types +++ b/generate-flag-types @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python2 """ The flag types that ship with the cli library have many things in common, and so we can take advantage of the `go generate` command to create much of the @@ -144,6 +144,7 @@ def _write_cli_flag_types(outfile, types): EnvVar string FilePath string Hidden bool + Category string """.format(**typedef)) if typedef['value']: @@ -170,6 +171,11 @@ def _write_cli_flag_types(outfile, types): return f.Name }} + // GetHidden lets us know if the flag is hidden or not + func (f {name}Flag) GetHidden() bool {{ + return f.Hidden + }} + // {name} looks up the value of a local {name}Flag, returns // {context_default} if not found func (c *Context) {name}(name string) {context_type} {{ diff --git a/runtests b/runtests index ee22bde..92d2081 100755 --- a/runtests +++ b/runtests @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python2 from __future__ import print_function import argparse From 7c383b0d1628d83e3fd938e95de545e6cb87fc73 Mon Sep 17 00:00:00 2001 From: Michael Schuett Date: Sun, 27 Jan 2019 01:44:36 -0500 Subject: [PATCH 002/125] Go Fmt --- command.go | 2 +- command_test.go | 22 +++++++++++----------- flag_test.go | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/command.go b/command.go index 3d44404..5bb2ada 100644 --- a/command.go +++ b/command.go @@ -266,7 +266,7 @@ func reorderArgs(args []string) []string { } func translateShortOptions(set *flag.FlagSet, flagArgs Args) []string { - allCharsFlags := func (s string) bool { + allCharsFlags := func(s string) bool { for i := range s { f := set.Lookup(string(s[i])) if f == nil { diff --git a/command_test.go b/command_test.go index 8c2650e..6ef9202 100644 --- a/command_test.go +++ b/command_test.go @@ -59,9 +59,9 @@ func TestCommandFlagParsing(t *testing.T) { func TestParseAndRunShortOpts(t *testing.T) { cases := []struct { - testArgs []string - expectedErr error - expectedArgs []string + testArgs []string + expectedErr error + expectedArgs []string }{ {[]string{"foo", "test", "-a"}, nil, []string{}}, {[]string{"foo", "test", "-c", "arg1", "arg2"}, nil, []string{"arg1", "arg2"}}, @@ -71,18 +71,18 @@ func TestParseAndRunShortOpts(t *testing.T) { {[]string{"foo", "test", "-cf"}, nil, []string{}}, {[]string{"foo", "test", "-acf"}, nil, []string{}}, {[]string{"foo", "test", "-invalid"}, errors.New("flag provided but not defined: -invalid"), []string{}}, - {[]string{"foo", "test", "-acf", "arg1", "-invalid"}, nil, []string{"arg1" ,"-invalid"}}, + {[]string{"foo", "test", "-acf", "arg1", "-invalid"}, nil, []string{"arg1", "-invalid"}}, } var args []string cmd := Command{ - Name: "test", - Usage: "this is for testing", - Description: "testing", - Action: func(c *Context) error { - args = c.Args() - return nil - }, + Name: "test", + Usage: "this is for testing", + Description: "testing", + Action: func(c *Context) error { + args = c.Args() + return nil + }, SkipArgReorder: true, UseShortOptionHandling: true, Flags: []Flag{ diff --git a/flag_test.go b/flag_test.go index da9fd73..ac3e70c 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1052,7 +1052,7 @@ func TestParseBoolShortOptionHandle(t *testing.T) { a := App{ Commands: []Command{ { - Name: "foobar", + Name: "foobar", UseShortOptionHandling: true, Action: func(ctx *Context) error { if ctx.Bool("serve") != true { From 9dd96e9e90d7ff68de22a954ebbd977bbb900d79 Mon Sep 17 00:00:00 2001 From: Michael Schuett Date: Sun, 27 Jan 2019 11:41:54 -0500 Subject: [PATCH 003/125] Add in Category to Flag interface --- app.go | 14 +++++++++- app_test.go | 4 +++ flag.go | 1 + flag_generated.go | 65 +++++++++++++++++++++++++++++++++++++++++++++ generate-flag-types | 5 ++++ 5 files changed, 88 insertions(+), 1 deletion(-) diff --git a/app.go b/app.go index 9add067..677ba02 100644 --- a/app.go +++ b/app.go @@ -51,6 +51,8 @@ type App struct { HideVersion bool // Populate on app startup, only gettable through method Categories() categories CommandCategories + // Populate on app startup, only gettable through method Categories() + flagCategories FlagCategories // An action to execute when the bash-completion flag is set BashComplete BashCompleteFunc // An action to execute before any subcommands are run, but after the context is ready @@ -164,7 +166,7 @@ func (a *App) Setup() { } sort.Sort(a.categories) - if a.Metadata == nil { +if a.Metadata == nil { a.Metadata = make(map[string]interface{}) } @@ -192,6 +194,11 @@ func (a *App) Run(arguments []string) (err error) { return err } + a.flagCategories = FlagCategories{} + for _, flag := range a.Flags { + a.flagCategories = a.flagCategories.AddFlag(flag.GetCategory(), flag) + } + set.SetOutput(ioutil.Discard) err = set.Parse(arguments[1:]) nerr := normalizeFlags(a.Flags, set) @@ -437,6 +444,11 @@ func (a *App) VisibleCommands() []Command { return ret } +// Categories returns a slice containing all the categories with the commands they contain +func (a *App) FlagCategories() FlagCategories { + return a.flagCategories +} + // VisibleFlags returns a slice of the Flags with Hidden=false func (a *App) VisibleFlags() []Flag { return visibleFlags(a.Flags) diff --git a/app_test.go b/app_test.go index 95ff66b..ceb0ee9 100644 --- a/app_test.go +++ b/app_test.go @@ -1607,6 +1607,10 @@ func (c *customBoolFlag) GetHidden() bool { return false } +func (c *customBoolFlag) GetCategory() string { + return "" +} + func (c *customBoolFlag) Apply(set *flag.FlagSet) { set.String(c.Nombre, c.Nombre, "") } diff --git a/flag.go b/flag.go index f3274cd..8d01ca9 100644 --- a/flag.go +++ b/flag.go @@ -73,6 +73,7 @@ type Flag interface { // Apply Flag settings to the given flag set Apply(*flag.FlagSet) GetName() string + GetCategory() string GetHidden() bool } diff --git a/flag_generated.go b/flag_generated.go index b699b91..b3ebfbc 100644 --- a/flag_generated.go +++ b/flag_generated.go @@ -35,6 +35,11 @@ func (f BoolFlag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f BoolFlag) GetCategory() string { + return f.Category +} + // Bool looks up the value of a local BoolFlag, returns // false if not found func (c *Context) Bool(name string) bool { @@ -89,6 +94,11 @@ func (f BoolTFlag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f BoolTFlag) GetCategory() string { + return f.Category +} + // BoolT looks up the value of a local BoolTFlag, returns // false if not found func (c *Context) BoolT(name string) bool { @@ -144,6 +154,11 @@ func (f DurationFlag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f DurationFlag) GetCategory() string { + return f.Category +} + // Duration looks up the value of a local DurationFlag, returns // 0 if not found func (c *Context) Duration(name string) time.Duration { @@ -199,6 +214,11 @@ func (f Float64Flag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f Float64Flag) GetCategory() string { + return f.Category +} + // Float64 looks up the value of a local Float64Flag, returns // 0 if not found func (c *Context) Float64(name string) float64 { @@ -253,6 +273,11 @@ func (f GenericFlag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f GenericFlag) GetCategory() string { + return f.Category +} + // Generic looks up the value of a local GenericFlag, returns // nil if not found func (c *Context) Generic(name string) interface{} { @@ -308,6 +333,11 @@ func (f Int64Flag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f Int64Flag) GetCategory() string { + return f.Category +} + // Int64 looks up the value of a local Int64Flag, returns // 0 if not found func (c *Context) Int64(name string) int64 { @@ -363,6 +393,11 @@ func (f IntFlag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f IntFlag) GetCategory() string { + return f.Category +} + // Int looks up the value of a local IntFlag, returns // 0 if not found func (c *Context) Int(name string) int { @@ -417,6 +452,11 @@ func (f IntSliceFlag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f IntSliceFlag) GetCategory() string { + return f.Category +} + // IntSlice looks up the value of a local IntSliceFlag, returns // nil if not found func (c *Context) IntSlice(name string) []int { @@ -471,6 +511,11 @@ func (f Int64SliceFlag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f Int64SliceFlag) GetCategory() string { + return f.Category +} + // Int64Slice looks up the value of a local Int64SliceFlag, returns // nil if not found func (c *Context) Int64Slice(name string) []int64 { @@ -526,6 +571,11 @@ func (f StringFlag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f StringFlag) GetCategory() string { + return f.Category +} + // String looks up the value of a local StringFlag, returns // "" if not found func (c *Context) String(name string) string { @@ -580,6 +630,11 @@ func (f StringSliceFlag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f StringSliceFlag) GetCategory() string { + return f.Category +} + // StringSlice looks up the value of a local StringSliceFlag, returns // nil if not found func (c *Context) StringSlice(name string) []string { @@ -635,6 +690,11 @@ func (f Uint64Flag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f Uint64Flag) GetCategory() string { + return f.Category +} + // Uint64 looks up the value of a local Uint64Flag, returns // 0 if not found func (c *Context) Uint64(name string) uint64 { @@ -690,6 +750,11 @@ func (f UintFlag) GetHidden() bool { return f.Hidden } +// GetCategory lets us access the flag category +func (f UintFlag) GetCategory() string { + return f.Category +} + // Uint looks up the value of a local UintFlag, returns // 0 if not found func (c *Context) Uint(name string) uint { diff --git a/generate-flag-types b/generate-flag-types index 324b3c1..9cea29e 100755 --- a/generate-flag-types +++ b/generate-flag-types @@ -176,6 +176,11 @@ def _write_cli_flag_types(outfile, types): return f.Hidden }} + // GetCategory lets us access the flag category + func (f {name}Flag) GetCategory() string {{ + return f.Category + }} + // {name} looks up the value of a local {name}Flag, returns // {context_default} if not found func (c *Context) {name}(name string) {context_type} {{ From 51aebb5a03a97ecc2783b585cf742035b7bdfe57 Mon Sep 17 00:00:00 2001 From: Michael Schuett Date: Sun, 27 Jan 2019 12:00:16 -0500 Subject: [PATCH 004/125] Add mod files Let us use this repo via replace locally. --- go.mod | 7 +++++++ go.sum | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8899222 --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module github.com/urfave/cli + +require ( + github.com/BurntSushi/toml v0.3.1 + gopkg.in/urfave/cli.v1 v1.20.0 + gopkg.in/yaml.v2 v2.2.2 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..46f9ae2 --- /dev/null +++ b/go.sum @@ -0,0 +1,7 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= +gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 9720ac029c97c01ec70333b4a7f2aa6dc04536b9 Mon Sep 17 00:00:00 2001 From: Michael Schuett Date: Sun, 27 Jan 2019 15:48:00 -0500 Subject: [PATCH 005/125] Basic working flag category support --- app.go | 17 ++++++++++------- command.go | 7 +++++++ help.go | 10 +++++++--- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/app.go b/app.go index 677ba02..9483482 100644 --- a/app.go +++ b/app.go @@ -145,6 +145,14 @@ func (a *App) Setup() { if c.HelpName == "" { c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) } + + fc := FlagCategories{} + for _, flag := range c.Flags { + fc = fc.AddFlag(flag.GetCategory(), flag) + } + + sort.Sort(fc) + c.FlagCategories = fc newCmds = append(newCmds, c) } a.Commands = newCmds @@ -166,7 +174,7 @@ func (a *App) Setup() { } sort.Sort(a.categories) -if a.Metadata == nil { + if a.Metadata == nil { a.Metadata = make(map[string]interface{}) } @@ -194,11 +202,6 @@ func (a *App) Run(arguments []string) (err error) { return err } - a.flagCategories = FlagCategories{} - for _, flag := range a.Flags { - a.flagCategories = a.flagCategories.AddFlag(flag.GetCategory(), flag) - } - set.SetOutput(ioutil.Discard) err = set.Parse(arguments[1:]) nerr := normalizeFlags(a.Flags, set) @@ -445,7 +448,7 @@ func (a *App) VisibleCommands() []Command { } // Categories returns a slice containing all the categories with the commands they contain -func (a *App) FlagCategories() FlagCategories { +func (a *App) VisibleFlagCategories() FlagCategories { return a.flagCategories } diff --git a/command.go b/command.go index 5bb2ada..1e6ee9f 100644 --- a/command.go +++ b/command.go @@ -45,6 +45,8 @@ type Command struct { Subcommands Commands // List of flags to parse Flags []Flag + // List of all flag categories + FlagCategories FlagCategories // Treat all flags as normal arguments if true SkipFlagParsing bool // Skip argument reordering which attempts to move flags before arguments, @@ -377,6 +379,11 @@ func (c Command) startApp(ctx *Context) error { return app.RunAsSubcommand(ctx) } +// Categories returns a slice containing all the categories with the commands they contain +func (c Command) VisibleFlagCategories() FlagCategories { + return c.FlagCategories +} + // VisibleFlags returns a slice of the Flags with Hidden=false func (c Command) VisibleFlags() []Flag { return visibleFlags(c.Flags) diff --git a/help.go b/help.go index 65874fa..18e185e 100644 --- a/help.go +++ b/help.go @@ -54,9 +54,10 @@ CATEGORY: {{.Category}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description}}{{end}}{{if .VisibleFlags}} + {{.Description}}{{end}} -OPTIONS: +OPTIONS:{{range .VisibleFlagCategories}} + {{.Name}} {{range .VisibleFlags}}{{.}} {{end}}{{end}} ` @@ -250,7 +251,10 @@ func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc m // If the writer is closed, t.Execute will fail, and there's nothing // we can do to recover. if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" { - fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err) + // Generic error message + fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR DEBUG: %#v\n", err) + // Helpful error message + fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR DEBUG: %#v\n", err.Error()) } return } From 50b52ca9d52b24529cdfeb6dcbff3aa80b69922f Mon Sep 17 00:00:00 2001 From: Michael Schuett Date: Sun, 27 Jan 2019 17:25:59 -0500 Subject: [PATCH 006/125] Fix unit tests --- app_test.go | 75 ++++++++++++++++++++++++++++++----------------------- help.go | 5 ++-- 2 files changed, 44 insertions(+), 36 deletions(-) diff --git a/app_test.go b/app_test.go index ceb0ee9..f779d69 100644 --- a/app_test.go +++ b/app_test.go @@ -1381,20 +1381,23 @@ func TestApp_VisibleCategories(t *testing.T) { app.HideHelp = true app.Commands = []Command{ { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + FlagCategories: FlagCategories{}, }, { - Name: "command2", - Category: "2", - HelpName: "foo command2", + Name: "command2", + Category: "2", + HelpName: "foo command2", + FlagCategories: FlagCategories{}, }, { - Name: "command3", - Category: "3", - HelpName: "foo command3", + Name: "command3", + Category: "3", + HelpName: "foo command3", + FlagCategories: FlagCategories{}, }, } @@ -1421,21 +1424,24 @@ func TestApp_VisibleCategories(t *testing.T) { app.HideHelp = true app.Commands = []Command{ { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + FlagCategories: FlagCategories{}, }, { - Name: "command2", - Category: "2", - HelpName: "foo command2", - Hidden: true, + Name: "command2", + Category: "2", + HelpName: "foo command2", + Hidden: true, + FlagCategories: FlagCategories{}, }, { - Name: "command3", - Category: "3", - HelpName: "foo command3", + Name: "command3", + Category: "3", + HelpName: "foo command3", + FlagCategories: FlagCategories{}, }, } @@ -1456,22 +1462,25 @@ func TestApp_VisibleCategories(t *testing.T) { app.HideHelp = true app.Commands = []Command{ { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + FlagCategories: FlagCategories{}, }, { - Name: "command2", - Category: "2", - HelpName: "foo command2", - Hidden: true, + Name: "command2", + Category: "2", + HelpName: "foo command2", + Hidden: true, + FlagCategories: FlagCategories{}, }, { - Name: "command3", - Category: "3", - HelpName: "foo command3", - Hidden: true, + Name: "command3", + Category: "3", + HelpName: "foo command3", + Hidden: true, + FlagCategories: FlagCategories{}, }, } diff --git a/help.go b/help.go index 18e185e..b80bac3 100644 --- a/help.go +++ b/help.go @@ -54,12 +54,11 @@ CATEGORY: {{.Category}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description}}{{end}} - + {{.Description}}{{end}}{{if .VisibleFlagCategories}} OPTIONS:{{range .VisibleFlagCategories}} {{.Name}} {{range .VisibleFlags}}{{.}} - {{end}}{{end}} + {{end}}{{end}}{{end}} ` // SubcommandHelpTemplate is the text template for the subcommand help topic. From 002bde2233182425d5f6d7f3d2456db482d2bb1f Mon Sep 17 00:00:00 2001 From: Sascha Grunert Date: Fri, 29 Nov 2019 14:09:46 +0100 Subject: [PATCH 007/125] Add suggestions support The new option `app.Suggest` enables command and flag suggestions via the jaro-winkler distance algorithm. Flags are scoped to their appropriate commands whereas command suggestions are scoped to the current command level. Signed-off-by: Sascha Grunert --- app.go | 12 +++++ command.go | 6 +++ docs/v2/manual.md | 8 +++ go.mod | 1 + go.sum | 2 + help.go | 21 ++++++-- parse.go | 18 +++++-- suggestions.go | 75 +++++++++++++++++++++++++++ suggestions_test.go | 122 ++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 257 insertions(+), 8 deletions(-) create mode 100644 suggestions.go create mode 100644 suggestions_test.go diff --git a/app.go b/app.go index dd8f1de..f3d24d0 100644 --- a/app.go +++ b/app.go @@ -88,6 +88,8 @@ type App struct { // single-character bool arguments into one // i.e. foobar -o -v -> foobar -ov UseShortOptionHandling bool + // Enable suggestions for commands and flags + Suggest bool didSetup bool } @@ -250,6 +252,11 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { return err } _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) + if a.Suggest { + if suggestion, err := a.suggestFlagFromError(err, ""); err == nil { + fmt.Fprintf(a.Writer, suggestion) + } + } _ = ShowAppHelp(context) return err } @@ -381,6 +388,11 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { return err } _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) + if a.Suggest { + if suggestion, err := a.suggestFlagFromError(err, context.Command.Name); err == nil { + fmt.Fprintf(a.Writer, suggestion) + } + } _ = ShowSubcommandHelp(context) return err } diff --git a/command.go b/command.go index db6c802..5300ee8 100644 --- a/command.go +++ b/command.go @@ -116,6 +116,11 @@ func (c *Command) Run(ctx *Context) (err error) { } _, _ = fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error()) _, _ = fmt.Fprintln(context.App.Writer) + if ctx.App.Suggest { + if suggestion, err := ctx.App.suggestFlagFromError(err, c.Name); err == nil { + fmt.Fprintf(context.App.Writer, suggestion) + } + } _ = ShowCommandHelp(context, c.Name) return err } @@ -244,6 +249,7 @@ func (c *Command) startApp(ctx *Context) error { app.ErrWriter = ctx.App.ErrWriter app.ExitErrHandler = ctx.App.ExitErrHandler app.UseShortOptionHandling = ctx.App.UseShortOptionHandling + app.Suggest = ctx.App.Suggest app.categories = newCommandCategories() for _, command := range c.Subcommands { diff --git a/docs/v2/manual.md b/docs/v2/manual.md index ce61075..29c2638 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -34,6 +34,7 @@ cli v2 manual * [Version Flag](#version-flag) + [Customization](#customization-2) * [Timestamp Flag](#timestamp-flag) + * [Suggestions](#suggestions) * [Full API Example](#full-api-example) @@ -1426,6 +1427,13 @@ In this example the flag could be used like this : Side note: quotes may be necessary around the date depending on your layout (if you have spaces for instance) +### Suggestions + +To enable flag and command suggestions, set `app.Suggest = true`. If the suggest +feature is enabled, then the help output of the corresponding command will +provide an appropriate suggestion for the provided flag or subcommand if +available. + ### Full API Example **Notice**: This is a contrived (functioning) example meant strictly for API diff --git a/go.mod b/go.mod index c38d41c..1c280a6 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.11 require ( github.com/BurntSushi/toml v0.3.1 + github.com/antzucaro/matchr v0.0.0-20180616170659-cbc221335f3c github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index ef121ff..6165e94 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/antzucaro/matchr v0.0.0-20180616170659-cbc221335f3c h1:CucViv7orgFBMkehuFFdkCVF5ERovbkRRyhvaYaHu/k= +github.com/antzucaro/matchr v0.0.0-20180616170659-cbc221335f3c/go.mod h1:bV/CkX4+ANGDaBwbHkt9kK287al/i9BsB18PRBvyqYo= 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= diff --git a/help.go b/help.go index c1e974a..9162661 100644 --- a/help.go +++ b/help.go @@ -10,9 +10,14 @@ import ( "unicode/utf8" ) +const ( + helpName = "help" + helpAlias = "h" +) + var helpCommand = &Command{ - Name: "help", - Aliases: []string{"h"}, + Name: helpName, + Aliases: []string{helpAlias}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", Action: func(c *Context) error { @@ -27,8 +32,8 @@ var helpCommand = &Command{ } var helpSubcommand = &Command{ - Name: "help", - Aliases: []string{"h"}, + Name: helpName, + Aliases: []string{helpAlias}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", Action: func(c *Context) error { @@ -207,7 +212,13 @@ func ShowCommandHelp(ctx *Context, command string) error { } if ctx.App.CommandNotFound == nil { - return Exit(fmt.Sprintf("No help topic for '%v'", command), 3) + errMsg := fmt.Sprintf("No help topic for '%v'", command) + if ctx.App.Suggest { + if suggestion := suggestCommand(ctx.App.Commands, command); suggestion != "" { + errMsg += ". " + suggestion + } + } + return Exit(errMsg, 3) } ctx.App.CommandNotFound(ctx, command) diff --git a/parse.go b/parse.go index 7df1729..a2db306 100644 --- a/parse.go +++ b/parse.go @@ -26,9 +26,8 @@ func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComple return err } - errStr := err.Error() - trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: -") - if errStr == trimmed { + trimmed, trimErr := flagFromError(err) + if trimErr != nil { return err } @@ -67,6 +66,19 @@ func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComple } } +const providedButNotDefinedErrMsg = "flag provided but not defined: -" + +// flagFromError tries to parse a provided flag from an error message. If the +// parsing fials, it returns the input error and an empty string +func flagFromError(err error) (string, error) { + errStr := err.Error() + trimmed := strings.TrimPrefix(errStr, providedButNotDefinedErrMsg) + if errStr == trimmed { + return "", err + } + return trimmed, nil +} + func splitShortOptions(set *flag.FlagSet, arg string) []string { shortFlagsExist := func(s string) bool { for _, c := range s[1:] { diff --git a/suggestions.go b/suggestions.go new file mode 100644 index 0000000..476af4d --- /dev/null +++ b/suggestions.go @@ -0,0 +1,75 @@ +package cli + +import ( + "fmt" + + "github.com/antzucaro/matchr" +) + +const didYouMeanTemplate = "Did you mean '%s'?" + +func (a *App) suggestFlagFromError(err error, command string) (string, error) { + flag, parseErr := flagFromError(err) + if parseErr != nil { + return "", err + } + + flags := a.Flags + if command != "" { + cmd := a.Command(command) + if cmd == nil { + return "", err + } + flags = cmd.Flags + } + + suggestion := a.suggestFlag(flags, flag) + if len(suggestion) == 0 { + return "", err + } + + return fmt.Sprintf(didYouMeanTemplate+"\n\n", suggestion), nil +} + +func (a *App) suggestFlag(flags []Flag, provided string) (suggestion string) { + distance := 0.0 + + for _, flag := range flags { + flagNames := flag.Names() + if !a.HideHelp { + flagNames = append(flagNames, HelpFlag.Names()...) + } + for _, name := range flagNames { + newDistance := matchr.JaroWinkler(name, provided, true) + if newDistance > distance { + distance = newDistance + suggestion = name + } + } + } + + if len(suggestion) == 1 { + suggestion = "-" + suggestion + } else if len(suggestion) > 1 { + suggestion = "--" + suggestion + } + + return suggestion +} + +// suggestCommand takes a list of commands and a provided string to suggest a +// command name +func suggestCommand(commands []*Command, provided string) (suggestion string) { + distance := 0.0 + for _, command := range commands { + for _, name := range append(command.Names(), helpName, helpAlias) { + newDistance := matchr.JaroWinkler(name, provided, true) + if newDistance > distance { + distance = newDistance + suggestion = name + } + } + } + + return fmt.Sprintf(didYouMeanTemplate, suggestion) +} diff --git a/suggestions_test.go b/suggestions_test.go new file mode 100644 index 0000000..3b31250 --- /dev/null +++ b/suggestions_test.go @@ -0,0 +1,122 @@ +package cli + +import ( + "errors" + "fmt" + "testing" +) + +func TestSuggestFlag(t *testing.T) { + // Given + app := testApp() + + for _, testCase := range []struct { + provided, expected string + }{ + {"", ""}, + {"a", "--another-flag"}, + {"hlp", "--help"}, + {"k", ""}, + {"s", "-s"}, + } { + // When + res := app.suggestFlag(app.Flags, testCase.provided) + + // Then + expect(t, res, testCase.expected) + } +} + +func TestSuggestFlagHideHelp(t *testing.T) { + // Given + app := testApp() + app.HideHelp = true + + // When + res := app.suggestFlag(app.Flags, "hlp") + + // Then + expect(t, res, "--fl") +} + +func TestSuggestFlagFromError(t *testing.T) { + // Given + app := testApp() + + for _, testCase := range []struct { + command, provided, expected string + }{ + {"", "hel", "--help"}, + {"", "soccer", "--socket"}, + {"config", "anot", "--another-flag"}, + } { + // When + res, _ := app.suggestFlagFromError( + errors.New(providedButNotDefinedErrMsg+testCase.provided), + testCase.command, + ) + + // Then + expect(t, res, fmt.Sprintf(didYouMeanTemplate+"\n\n", testCase.expected)) + } +} + +func TestSuggestFlagFromErrorWrongError(t *testing.T) { + // Given + app := testApp() + + // When + _, err := app.suggestFlagFromError(errors.New("invalid"), "") + + // Then + expect(t, true, err != nil) +} + +func TestSuggestFlagFromErrorWrongCommand(t *testing.T) { + // Given + app := testApp() + + // When + _, err := app.suggestFlagFromError( + errors.New(providedButNotDefinedErrMsg+"flag"), + "invalid", + ) + + // Then + expect(t, true, err != nil) +} + +func TestSuggestFlagFromErrorNoSuggestion(t *testing.T) { + // Given + app := testApp() + + // When + _, err := app.suggestFlagFromError( + errors.New(providedButNotDefinedErrMsg+""), + "", + ) + + // Then + expect(t, true, err != nil) +} + +func TestSuggestCommand(t *testing.T) { + // Given + app := testApp() + + for _, testCase := range []struct { + provided, expected string + }{ + {"", ""}, + {"conf", "config"}, + {"i", "i"}, + {"information", "info"}, + {"not-existing", "info"}, + } { + // When + res := suggestCommand(app.Commands, testCase.provided) + + // Then + expect(t, res, fmt.Sprintf(didYouMeanTemplate, testCase.expected)) + } +} From a8e44a8b5b1a75b1bfd95a46f809f9aba3ddb214 Mon Sep 17 00:00:00 2001 From: Zack Scholl Date: Thu, 17 Sep 2020 12:46:05 -0700 Subject: [PATCH 008/125] show only subcommand flags with bash completion --- help.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/help.go b/help.go index c1e974a..95ba188 100644 --- a/help.go +++ b/help.go @@ -164,9 +164,10 @@ func DefaultCompleteWithFlags(cmd *Command) func(c *Context) { if len(os.Args) > 2 { lastArg := os.Args[len(os.Args)-2] if strings.HasPrefix(lastArg, "-") { - printFlagSuggestions(lastArg, c.App.Flags, c.App.Writer) if cmd != nil { printFlagSuggestions(lastArg, cmd.Flags, c.App.Writer) + } else { + printFlagSuggestions(lastArg, c.App.Flags, c.App.Writer) } return } From 500d6b04e6dc2b9882c0e555a80df7523a97e05f Mon Sep 17 00:00:00 2001 From: Mostyn Bramley-Moore Date: Thu, 30 Jul 2020 12:05:11 +0200 Subject: [PATCH 009/125] Report the source of a value when we cannot parse it If you allow a flag to be set from environment variables or files and a parse error occurs from one of them, it is very useful for the error message to mention where the value came from. Without this, it can be difficult to notice an error caused by an unexpected environment variable being set. Implements #1167. --- flag.go | 8 ++++---- flag_bool.go | 4 ++-- flag_duration.go | 4 ++-- flag_float64.go | 4 ++-- flag_float64_slice.go | 4 ++-- flag_generic.go | 4 ++-- flag_int.go | 4 ++-- flag_int64.go | 4 ++-- flag_int64_slice.go | 4 ++-- flag_int_slice.go | 4 ++-- flag_path.go | 3 ++- flag_string.go | 3 ++- flag_string_slice.go | 4 ++-- flag_test.go | 32 ++++++++++++++++---------------- flag_timestamp.go | 4 ++-- flag_uint.go | 4 ++-- flag_uint64.go | 4 ++-- 17 files changed, 50 insertions(+), 48 deletions(-) diff --git a/flag.go b/flag.go index ad97c2d..a8edc4f 100644 --- a/flag.go +++ b/flag.go @@ -372,17 +372,17 @@ func hasFlag(flags []Flag, fl Flag) bool { return false } -func flagFromEnvOrFile(envVars []string, filePath string) (val string, ok bool) { +func flagFromEnvOrFile(envVars []string, filePath string) (val string, ok bool, source string) { for _, envVar := range envVars { envVar = strings.TrimSpace(envVar) if val, ok := syscall.Getenv(envVar); ok { - return val, true + return val, true, fmt.Sprintf("from environment variable %q", envVar) } } for _, fileVar := range strings.Split(filePath, ",") { if data, err := ioutil.ReadFile(fileVar); err == nil { - return string(data), true + return string(data), true, fmt.Sprintf("from file %q", filePath) } } - return "", false + return "", false, "" } diff --git a/flag_bool.go b/flag_bool.go index bc9ea35..903fd08 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -60,12 +60,12 @@ func (f *BoolFlag) GetValue() string { // 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 { + if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { if val != "" { valBool, err := strconv.ParseBool(val) if err != nil { - return fmt.Errorf("could not parse %q as bool value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as bool value %s for flag %s: %s", val, source, f.Name, err) } f.Value = valBool diff --git a/flag_duration.go b/flag_duration.go index 22a2e67..0717135 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -60,12 +60,12 @@ func (f *DurationFlag) GetValue() string { // 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 { + if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { if val != "" { valDuration, err := time.ParseDuration(val) if err != nil { - return fmt.Errorf("could not parse %q as duration value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as duration value %s for flag %s: %s", val, source, f.Name, err) } f.Value = valDuration diff --git a/flag_float64.go b/flag_float64.go index 91c778c..a152945 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -60,12 +60,12 @@ func (f *Float64Flag) GetValue() string { // 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, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { if val != "" { valFloat, err := strconv.ParseFloat(val, 10) if err != nil { - return fmt.Errorf("could not parse %q as float64 value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as float64 value %s for flag %s: %s", val, source, f.Name, err) } f.Value = valFloat diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 706ee6c..dfd68fd 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -119,13 +119,13 @@ func (f *Float64SliceFlag) GetValue() string { // 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 { + if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { if val != "" { f.Value = &Float64Slice{} for _, s := range strings.Split(val, ",") { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as float64 slice value for flag %s: %s", f.Value, f.Name, err) + return fmt.Errorf("could not parse %q as float64 slice value %s for flag %s: %s", f.Value, source, f.Name, err) } } diff --git a/flag_generic.go b/flag_generic.go index b0c8ff4..fab517c 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -69,10 +69,10 @@ func (f *GenericFlag) GetValue() string { // Apply takes the flagset and calls Set on the generic flag with the value // provided by the user for parsing by the flag func (f GenericFlag) Apply(set *flag.FlagSet) error { - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { if val != "" { if err := f.Value.Set(val); err != nil { - return fmt.Errorf("could not parse %q as value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q %s as value for flag %s: %s", val, source, f.Name, err) } f.HasBeenSet = true diff --git a/flag_int.go b/flag_int.go index ac39d4a..7d0a889 100644 --- a/flag_int.go +++ b/flag_int.go @@ -60,12 +60,12 @@ func (f *IntFlag) GetValue() string { // 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 { + if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { if val != "" { valInt, err := strconv.ParseInt(val, 0, 64) if err != nil { - return fmt.Errorf("could not parse %q as int value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as int value %s for flag %s: %s", val, source, f.Name, err) } f.Value = int(valInt) diff --git a/flag_int64.go b/flag_int64.go index e099912..401d0b4 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -60,12 +60,12 @@ func (f *Int64Flag) GetValue() string { // 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 { + if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { if val != "" { valInt, err := strconv.ParseInt(val, 0, 64) if err != nil { - return fmt.Errorf("could not parse %q as int value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as int value %s for flag %s: %s", val, source, f.Name, err) } f.Value = valInt diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 6c7fd93..15ee9fd 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -120,12 +120,12 @@ func (f *Int64SliceFlag) GetValue() string { // 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 { + if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { f.Value = &Int64Slice{} for _, s := range strings.Split(val, ",") { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as int64 slice value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as int64 slice value %s for flag %s: %s", val, source, f.Name, err) } } diff --git a/flag_int_slice.go b/flag_int_slice.go index 4e0afc0..7807e35 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -131,12 +131,12 @@ func (f *IntSliceFlag) GetValue() string { // 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 { + if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { f.Value = &IntSlice{} for _, s := range strings.Split(val, ",") { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as int slice value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as int slice value %s for flag %s: %s", val, source, f.Name, err) } } diff --git a/flag_path.go b/flag_path.go index 8070dc4..5d33ed8 100644 --- a/flag_path.go +++ b/flag_path.go @@ -56,7 +56,8 @@ func (f *PathFlag) GetValue() string { // 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 { + // TODO: how to report the source of parse errors? + if val, ok, _ := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { f.Value = val f.HasBeenSet = true } diff --git a/flag_string.go b/flag_string.go index 400bb53..5fdea3a 100644 --- a/flag_string.go +++ b/flag_string.go @@ -57,7 +57,8 @@ func (f *StringFlag) GetValue() string { // 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 { + // TODO: how to report source? + if val, ok, _ := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { f.Value = val f.HasBeenSet = true } diff --git a/flag_string_slice.go b/flag_string_slice.go index 3549703..2fb62aa 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -123,7 +123,7 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { } - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { if f.Value == nil { f.Value = &StringSlice{} } @@ -134,7 +134,7 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { for _, s := range strings.Split(val, ",") { if err := destination.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as string value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as string value %s for flag %s: %s", val, source, f.Name, err) } } diff --git a/flag_test.go b/flag_test.go index b1fe70a..8600ba4 100644 --- a/flag_test.go +++ b/flag_test.go @@ -79,30 +79,30 @@ func TestFlagsFromEnv(t *testing.T) { {"", false, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""}, {"1", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""}, {"false", false, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""}, - {"foobar", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, `could not parse "foobar" as bool value for flag debug: .*`}, + {"foobar", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, `could not parse "foobar" as bool value from environment variable "DEBUG" for flag debug: .*`}, {"1s", 1 * time.Second, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, ""}, - {"foobar", false, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, `could not parse "foobar" as duration value for flag time: .*`}, + {"foobar", false, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, `could not parse "foobar" as duration value from environment variable "TIME" for flag time: .*`}, {"1.2", 1.2, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, {"1", 1.0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"foobar", 0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as float64 value for flag seconds: .*`}, + {"foobar", 0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as float64 value from environment variable "SECONDS" for flag seconds: .*`}, {"1", int64(1), &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value for flag seconds: .*`}, - {"foobar", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value for flag seconds: .*`}, + {"1.2", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value from environment variable "SECONDS" for flag seconds: .*`}, + {"foobar", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value from environment variable "SECONDS" for flag seconds: .*`}, {"1", 1, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"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.2", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value from environment variable "SECONDS" for flag seconds: .*`}, + {"foobar", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value from environment variable "SECONDS" for flag seconds: .*`}, {"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: .*`}, + {"1.2,2", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int slice value from environment variable "SECONDS" for flag seconds: .*`}, + {"foobar", newSetIntSlice(), &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int slice value from environment variable "SECONDS" for flag seconds: .*`}, {"1,2", newSetInt64Slice(1, 2), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2,2", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int64 slice value for flag seconds: .*`}, - {"foobar", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int64 slice value for flag seconds: .*`}, + {"1.2,2", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as int64 slice value from environment variable "SECONDS" for flag seconds: .*`}, + {"foobar", newSetInt64Slice(), &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int64 slice value from environment variable "SECONDS" for flag seconds: .*`}, {"foo", "foo", &StringFlag{Name: "name", EnvVars: []string{"NAME"}}, ""}, {"path", "path", &PathFlag{Name: "path", EnvVars: []string{"PATH"}}, ""}, @@ -110,12 +110,12 @@ func TestFlagsFromEnv(t *testing.T) { {"foo,bar", newSetStringSlice("foo", "bar"), &StringSliceFlag{Name: "names", EnvVars: []string{"NAMES"}}, ""}, {"1", uint(1), &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as uint value for flag seconds: .*`}, - {"foobar", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint value for flag seconds: .*`}, + {"1.2", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as uint value from environment variable "SECONDS" for flag seconds: .*`}, + {"foobar", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint value from environment variable "SECONDS" for flag seconds: .*`}, {"1", uint64(1), &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, - {"1.2", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as uint64 value for flag seconds: .*`}, - {"foobar", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint64 value for flag seconds: .*`}, + {"1.2", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as uint64 value from environment variable "SECONDS" for flag seconds: .*`}, + {"foobar", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint64 value from environment variable "SECONDS" for flag seconds: .*`}, {"foo,bar", &Parser{"foo", "bar"}, &GenericFlag{Name: "names", Value: &Parser{}, EnvVars: []string{"NAMES"}}, ""}, } @@ -1758,7 +1758,7 @@ func TestFlagFromFile(t *testing.T) { } for _, filePathTest := range filePathTests { - got, _ := flagFromEnvOrFile(filePathTest.name, filePathTest.path) + got, _, _ := flagFromEnvOrFile(filePathTest.name, filePathTest.path) if want := filePathTest.expected; got != want { t.Errorf("Did not expect %v - Want %v", got, want) } diff --git a/flag_timestamp.go b/flag_timestamp.go index 0382a6b..8980835 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -123,9 +123,9 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error { } f.Value.SetLayout(f.Layout) - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, ok, source := 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) + return fmt.Errorf("could not parse %q as timestamp value %s for flag %s: %s", val, source, f.Name, err) } f.HasBeenSet = true } diff --git a/flag_uint.go b/flag_uint.go index 2e5e76b..127f90b 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -54,11 +54,11 @@ func (f *UintFlag) GetUsage() string { // 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 { + if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { if val != "" { valInt, err := strconv.ParseUint(val, 0, 64) if err != nil { - return fmt.Errorf("could not parse %q as uint value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as uint value %s for flag %s: %s", val, source, f.Name, err) } f.Value = uint(valInt) diff --git a/flag_uint64.go b/flag_uint64.go index 8fc3289..162d447 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -54,11 +54,11 @@ func (f *Uint64Flag) GetUsage() string { // 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 { + if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { if val != "" { valInt, err := strconv.ParseUint(val, 0, 64) if err != nil { - return fmt.Errorf("could not parse %q as uint64 value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as uint64 value %s for flag %s: %s", val, source, f.Name, err) } f.Value = valInt From cdc1f6e07c17b57ef6e907bc505fddcae87abebe Mon Sep 17 00:00:00 2001 From: Mostyn Bramley-Moore Date: Sat, 7 Nov 2020 14:05:37 +0100 Subject: [PATCH 010/125] fixup! Report the source of a value when we cannot parse it move bool to the end of the return arguments remove "from " prefix in the source/fromWhere description remove TODO notes from functions that don't currently perform error checking --- flag.go | 13 ++++++++----- flag_bool.go | 4 ++-- flag_duration.go | 4 ++-- flag_float64.go | 4 ++-- flag_float64_slice.go | 4 ++-- flag_generic.go | 4 ++-- flag_int.go | 4 ++-- flag_int64.go | 4 ++-- flag_int64_slice.go | 4 ++-- flag_int_slice.go | 4 ++-- flag_path.go | 3 +-- flag_string.go | 3 +-- flag_string_slice.go | 4 ++-- flag_timestamp.go | 4 ++-- flag_uint.go | 4 ++-- flag_uint64.go | 4 ++-- 16 files changed, 36 insertions(+), 35 deletions(-) diff --git a/flag.go b/flag.go index a8edc4f..35ec217 100644 --- a/flag.go +++ b/flag.go @@ -372,17 +372,20 @@ func hasFlag(flags []Flag, fl Flag) bool { return false } -func flagFromEnvOrFile(envVars []string, filePath string) (val string, ok bool, source string) { +// Return the first value from a list of environment variables and files +// (which may or may not exist), a description of where the value was found, +// and a boolean which is true if a value was found. +func flagFromEnvOrFile(envVars []string, filePath string) (value string, fromWhere string, found bool) { for _, envVar := range envVars { envVar = strings.TrimSpace(envVar) - if val, ok := syscall.Getenv(envVar); ok { - return val, true, fmt.Sprintf("from environment variable %q", envVar) + if value, found := syscall.Getenv(envVar); found { + return value, fmt.Sprintf("environment variable %q", envVar), true } } for _, fileVar := range strings.Split(filePath, ",") { if data, err := ioutil.ReadFile(fileVar); err == nil { - return string(data), true, fmt.Sprintf("from file %q", filePath) + return string(data), fmt.Sprintf("file %q", filePath), true } } - return "", false, "" + return "", "", false } diff --git a/flag_bool.go b/flag_bool.go index 903fd08..9462e06 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -60,12 +60,12 @@ func (f *BoolFlag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *BoolFlag) Apply(set *flag.FlagSet) error { - if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valBool, err := strconv.ParseBool(val) if err != nil { - return fmt.Errorf("could not parse %q as bool value %s for flag %s: %s", val, source, f.Name, err) + return fmt.Errorf("could not parse %q as bool value from %s for flag %s: %s", val, source, f.Name, err) } f.Value = valBool diff --git a/flag_duration.go b/flag_duration.go index 0717135..040b5d6 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -60,12 +60,12 @@ func (f *DurationFlag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *DurationFlag) Apply(set *flag.FlagSet) error { - if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valDuration, err := time.ParseDuration(val) if err != nil { - return fmt.Errorf("could not parse %q as duration value %s for flag %s: %s", val, source, f.Name, err) + return fmt.Errorf("could not parse %q as duration value from %s for flag %s: %s", val, source, f.Name, err) } f.Value = valDuration diff --git a/flag_float64.go b/flag_float64.go index a152945..14df60d 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -60,12 +60,12 @@ func (f *Float64Flag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *Float64Flag) Apply(set *flag.FlagSet) error { - if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valFloat, err := strconv.ParseFloat(val, 10) if err != nil { - return fmt.Errorf("could not parse %q as float64 value %s for flag %s: %s", val, source, f.Name, err) + return fmt.Errorf("could not parse %q as float64 value from %s for flag %s: %s", val, source, f.Name, err) } f.Value = valFloat diff --git a/flag_float64_slice.go b/flag_float64_slice.go index dfd68fd..0382229 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -119,13 +119,13 @@ func (f *Float64SliceFlag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { - if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { f.Value = &Float64Slice{} for _, s := range strings.Split(val, ",") { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as float64 slice value %s for flag %s: %s", f.Value, source, f.Name, err) + return fmt.Errorf("could not parse %q as float64 slice value from %s for flag %s: %s", f.Value, source, f.Name, err) } } diff --git a/flag_generic.go b/flag_generic.go index fab517c..b92c4de 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -69,10 +69,10 @@ func (f *GenericFlag) GetValue() string { // Apply takes the flagset and calls Set on the generic flag with the value // provided by the user for parsing by the flag func (f GenericFlag) Apply(set *flag.FlagSet) error { - if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { if err := f.Value.Set(val); err != nil { - return fmt.Errorf("could not parse %q %s as value for flag %s: %s", val, source, f.Name, err) + return fmt.Errorf("could not parse %q from %s as value for flag %s: %s", val, source, f.Name, err) } f.HasBeenSet = true diff --git a/flag_int.go b/flag_int.go index 7d0a889..8e0ee59 100644 --- a/flag_int.go +++ b/flag_int.go @@ -60,12 +60,12 @@ func (f *IntFlag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *IntFlag) Apply(set *flag.FlagSet) error { - if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valInt, err := strconv.ParseInt(val, 0, 64) if err != nil { - return fmt.Errorf("could not parse %q as int value %s for flag %s: %s", val, source, f.Name, err) + return fmt.Errorf("could not parse %q as int value from %s for flag %s: %s", val, source, f.Name, err) } f.Value = int(valInt) diff --git a/flag_int64.go b/flag_int64.go index 401d0b4..0dda82e 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -60,12 +60,12 @@ func (f *Int64Flag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *Int64Flag) Apply(set *flag.FlagSet) error { - if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valInt, err := strconv.ParseInt(val, 0, 64) if err != nil { - return fmt.Errorf("could not parse %q as int value %s for flag %s: %s", val, source, f.Name, err) + return fmt.Errorf("could not parse %q as int value from %s for flag %s: %s", val, source, f.Name, err) } f.Value = valInt diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 15ee9fd..cfb29eb 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -120,12 +120,12 @@ func (f *Int64SliceFlag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { - if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { f.Value = &Int64Slice{} for _, s := range strings.Split(val, ",") { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as int64 slice value %s for flag %s: %s", val, source, f.Name, err) + return fmt.Errorf("could not parse %q as int64 slice value from %s for flag %s: %s", val, source, f.Name, err) } } diff --git a/flag_int_slice.go b/flag_int_slice.go index 7807e35..f379039 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -131,12 +131,12 @@ func (f *IntSliceFlag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { - if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { f.Value = &IntSlice{} for _, s := range strings.Split(val, ",") { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as int slice value %s for flag %s: %s", val, source, f.Name, err) + return fmt.Errorf("could not parse %q as int slice value from %s for flag %s: %s", val, source, f.Name, err) } } diff --git a/flag_path.go b/flag_path.go index 5d33ed8..bc8d5a2 100644 --- a/flag_path.go +++ b/flag_path.go @@ -56,8 +56,7 @@ func (f *PathFlag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *PathFlag) Apply(set *flag.FlagSet) error { - // TODO: how to report the source of parse errors? - if val, ok, _ := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, _, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { f.Value = val f.HasBeenSet = true } diff --git a/flag_string.go b/flag_string.go index 5fdea3a..5d79ce8 100644 --- a/flag_string.go +++ b/flag_string.go @@ -57,8 +57,7 @@ func (f *StringFlag) GetValue() string { // Apply populates the flag given the flag set and environment func (f *StringFlag) Apply(set *flag.FlagSet) error { - // TODO: how to report source? - if val, ok, _ := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, _, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { f.Value = val f.HasBeenSet = true } diff --git a/flag_string_slice.go b/flag_string_slice.go index 2fb62aa..e081c4f 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -123,7 +123,7 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { } - if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if f.Value == nil { f.Value = &StringSlice{} } @@ -134,7 +134,7 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { for _, s := range strings.Split(val, ",") { if err := destination.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as string value %s for flag %s: %s", val, source, f.Name, err) + return fmt.Errorf("could not parse %q as string value from %s for flag %s: %s", val, source, f.Name, err) } } diff --git a/flag_timestamp.go b/flag_timestamp.go index 8980835..fb174ee 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -123,9 +123,9 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error { } f.Value.SetLayout(f.Layout) - if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if err := f.Value.Set(val); err != nil { - return fmt.Errorf("could not parse %q as timestamp value %s for flag %s: %s", val, source, f.Name, err) + return fmt.Errorf("could not parse %q as timestamp value from %s for flag %s: %s", val, source, f.Name, err) } f.HasBeenSet = true } diff --git a/flag_uint.go b/flag_uint.go index 127f90b..f64cbe4 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -54,11 +54,11 @@ func (f *UintFlag) GetUsage() string { // Apply populates the flag given the flag set and environment func (f *UintFlag) Apply(set *flag.FlagSet) error { - if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valInt, err := strconv.ParseUint(val, 0, 64) if err != nil { - return fmt.Errorf("could not parse %q as uint value %s for flag %s: %s", val, source, f.Name, err) + return fmt.Errorf("could not parse %q as uint value from %s for flag %s: %s", val, source, f.Name, err) } f.Value = uint(valInt) diff --git a/flag_uint64.go b/flag_uint64.go index 162d447..a6bf63d 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -54,11 +54,11 @@ func (f *Uint64Flag) GetUsage() string { // Apply populates the flag given the flag set and environment func (f *Uint64Flag) Apply(set *flag.FlagSet) error { - if val, ok, source := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valInt, err := strconv.ParseUint(val, 0, 64) if err != nil { - return fmt.Errorf("could not parse %q as uint64 value %s for flag %s: %s", val, source, f.Name, err) + return fmt.Errorf("could not parse %q as uint64 value from %s for flag %s: %s", val, source, f.Name, err) } f.Value = valInt From ef9430e77eb1e7e05daecdfaa9ec75fa43b79e7e Mon Sep 17 00:00:00 2001 From: Ally Dale Date: Mon, 8 Feb 2021 20:10:28 +0800 Subject: [PATCH 011/125] fix #1238: accept multi-value input on sclice flags --- app_test.go | 34 ++++++++++++++++++++++++++++++ docs/v2/manual.md | 46 +++++++++++++++++++++++++++++++++++++++++ flag.go | 4 ++++ flag_float64_slice.go | 12 ++++++----- flag_int64_slice.go | 14 +++++++------ flag_int_slice.go | 14 +++++++------ flag_string_slice.go | 6 ++++-- flag_test.go | 48 +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 159 insertions(+), 19 deletions(-) diff --git a/app_test.go b/app_test.go index 7c38f60..0b8b4bf 100644 --- a/app_test.go +++ b/app_test.go @@ -390,6 +390,40 @@ func ExampleApp_Run_zshComplete() { // h:Shows a list of commands or help for one command } +func ExampleApp_Run_sliceValues() { + // set args for examples sake + os.Args = []string{"multi_values", + "--stringSclice", "parsed1,parsed2", "--stringSclice", "parsed3,parsed4", + "--float64Sclice", "13.3,14.4", "--float64Sclice", "15.5,16.6", + "--int64Sclice", "13,14", "--int64Sclice", "15,16", + "--intSclice", "13,14", "--intSclice", "15,16", + } + app := NewApp() + app.Name = "multi_values" + app.Flags = []Flag{ + &StringSliceFlag{Name: "stringSclice"}, + &Float64SliceFlag{Name: "float64Sclice"}, + &Int64SliceFlag{Name: "int64Sclice"}, + &IntSliceFlag{Name: "intSclice"}, + } + app.Action = func(ctx *Context) error { + for i, v := range ctx.FlagNames() { + fmt.Printf("%d-%s %#v\n", i, v, ctx.Value(v)) + } + err := ctx.Err() + fmt.Println("error:", err) + return err + } + + _ = app.Run(os.Args) + // Output: + // 0-float64Sclice cli.Float64Slice{slice:[]float64{13.3, 14.4, 15.5, 16.6}, hasBeenSet:true} + // 1-int64Sclice cli.Int64Slice{slice:[]int64{13, 14, 15, 16}, hasBeenSet:true} + // 2-intSclice cli.IntSlice{slice:[]int{13, 14, 15, 16}, hasBeenSet:true} + // 3-stringSclice cli.StringSlice{slice:[]string{"parsed1", "parsed2", "parsed3", "parsed4"}, hasBeenSet:true} + // error: +} + func TestApp_Run(t *testing.T) { s := "" diff --git a/docs/v2/manual.md b/docs/v2/manual.md index af09010..8f7cbbb 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -14,6 +14,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) + + [Muti values](#multi-values) + [Required Flags](#required-flags) + [Default Values for help output](#default-values-for-help-output) + [Precedence](#precedence) @@ -660,6 +661,51 @@ func main() { } ``` +#### Multi values + +Slice flags(Float64SliceFlag, Int64SliceFlag, IntSliceFlag, StringSliceFlag) are designed to accept multi values. + +Here is a sample of setting multi values for slice flags: + + +``` +package main + +import ( + "fmt" + "os" + "github.com/urfave/cli/v2" +) + +func main() { + os.Args = []string{"multi_values", + "--stringSclice", "parsed1,parsed2", "--stringSclice", "parsed3,parsed4", + "--float64Sclice", "13.3,14.4", "--float64Sclice", "15.5,16.6", + "--int64Sclice", "13,14", "--int64Sclice", "15,16", + "--intSclice", "13,14", "--intSclice", "15,16", + } + app := cli.NewApp() + app.Name = "multi_values" + app.Flags = []cli.Flag{ + &cli.StringSliceFlag{Name: "stringSclice"}, + &cli.Float64SliceFlag{Name: "float64Sclice"}, + &cli.Int64SliceFlag{Name: "int64Sclice"}, + &cli.IntSliceFlag{Name: "intSclice"}, + } + app.Action = func(ctx *cli.Context) error { + for i, v := range ctx.FlagNames() { + fmt.Printf("%d-%s %#v\n", i, v, ctx.Value(v)) + } + return ctx.Err() + } + + _ = app.Run(os.Args) +} + +``` + #### Required Flags You can make a flag required by setting the `Required` field to `true`. If a user diff --git a/flag.go b/flag.go index aff8d5b..bca41cc 100644 --- a/flag.go +++ b/flag.go @@ -390,3 +390,7 @@ func flagFromEnvOrFile(envVars []string, filePath string) (val string, ok bool) } return "", false } + +func flagSplitMultiValues(val string) []string { + return strings.Split(val, ",") +} diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 706ee6c..8ee1204 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -33,12 +33,14 @@ func (f *Float64Slice) Set(value string) error { return nil } - tmp, err := strconv.ParseFloat(value, 64) - if err != nil { - return err - } + for _, s := range flagSplitMultiValues(value) { + tmp, err := strconv.ParseFloat(strings.TrimSpace(s), 64) + if err != nil { + return err + } - f.slice = append(f.slice, tmp) + f.slice = append(f.slice, tmp) + } return nil } diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 2c9a15a..2edc3c6 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -33,12 +33,14 @@ func (i *Int64Slice) Set(value string) error { return nil } - tmp, err := strconv.ParseInt(value, 0, 64) - if err != nil { - return err - } + for _, s := range flagSplitMultiValues(value) { + tmp, err := strconv.ParseInt(strings.TrimSpace(s), 0, 64) + if err != nil { + return err + } - i.slice = append(i.slice, tmp) + i.slice = append(i.slice, tmp) + } return nil } @@ -123,7 +125,7 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { f.Value = &Int64Slice{} - for _, s := range strings.Split(val, ",") { + for _, s := range flagSplitMultiValues(val) { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { return fmt.Errorf("could not parse %q as int64 slice value for flag %s: %s", val, f.Name, err) } diff --git a/flag_int_slice.go b/flag_int_slice.go index a73ca6b..8861649 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -44,12 +44,14 @@ func (i *IntSlice) Set(value string) error { return nil } - tmp, err := strconv.ParseInt(value, 0, 64) - if err != nil { - return err - } + for _, s := range flagSplitMultiValues(value) { + tmp, err := strconv.ParseInt(strings.TrimSpace(s), 0, 64) + if err != nil { + return err + } - i.slice = append(i.slice, int(tmp)) + i.slice = append(i.slice, int(tmp)) + } return nil } @@ -134,7 +136,7 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { f.Value = &IntSlice{} - for _, s := range strings.Split(val, ",") { + for _, s := range flagSplitMultiValues(val) { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { return fmt.Errorf("could not parse %q as int slice value for flag %s: %s", val, f.Name, err) } diff --git a/flag_string_slice.go b/flag_string_slice.go index 3549703..92ded57 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -32,7 +32,9 @@ func (s *StringSlice) Set(value string) error { return nil } - s.slice = append(s.slice, value) + for _, t := range flagSplitMultiValues(value) { + s.slice = append(s.slice, strings.TrimSpace(t)) + } return nil } @@ -132,7 +134,7 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { destination = f.Destination } - for _, s := range strings.Split(val, ",") { + for _, s := range flagSplitMultiValues(val) { if err := destination.Set(strings.TrimSpace(s)); err != nil { return fmt.Errorf("could not parse %q as string value for flag %s: %s", val, f.Name, err) } diff --git a/flag_test.go b/flag_test.go index b3b0d7c..1afc631 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1973,3 +1973,51 @@ 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 flagValueTestCase struct { + name string + flag Flag + toParse []string + expect string +} + +func TestFlagValue(t *testing.T) { + cases := []*flagValueTestCase{ + &flagValueTestCase{ + name: "stringSclice", + flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")}, + toParse: []string{"--flag", "parsed,parsed2", "--flag", "parsed3,parsed4"}, + expect: `[parsed parsed2 parsed3 parsed4]`, + }, + &flagValueTestCase{ + name: "float64Sclice", + flag: &Float64SliceFlag{Name: "flag", Value: NewFloat64Slice(1.1, 2.2)}, + toParse: []string{"--flag", "13.3,14.4", "--flag", "15.5,16.6"}, + expect: `[]float64{13.3, 14.4, 15.5, 16.6}`, + }, + &flagValueTestCase{ + name: "int64Sclice", + flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2)}, + toParse: []string{"--flag", "13,14", "--flag", "15,16"}, + expect: `[]int64{13, 14, 15, 16}`, + }, + &flagValueTestCase{ + name: "intSclice", + flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)}, + toParse: []string{"--flag", "13,14", "--flag", "15,16"}, + expect: `[]int{13, 14, 15, 16}`, + }, + } + 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) + } + f := set.Lookup("flag") + if got := f.Value.String(); got != v.expect { + t.Errorf("TestFlagValue %d-%s\nexpect:%s\ngot:%s", i, v.name, v.expect, got) + } + } +} From 06f6815b8d881483b4ce75de2471fa7d2b724903 Mon Sep 17 00:00:00 2001 From: Ally Dale Date: Mon, 8 Feb 2021 22:41:34 +0800 Subject: [PATCH 012/125] revert docs/v2/manual.md --- docs/v2/manual.md | 46 ------------------------------------------- flag_float64_slice.go | 2 +- 2 files changed, 1 insertion(+), 47 deletions(-) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 8f7cbbb..af09010 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -14,7 +14,6 @@ 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) - + [Muti values](#multi-values) + [Required Flags](#required-flags) + [Default Values for help output](#default-values-for-help-output) + [Precedence](#precedence) @@ -661,51 +660,6 @@ func main() { } ``` -#### Multi values - -Slice flags(Float64SliceFlag, Int64SliceFlag, IntSliceFlag, StringSliceFlag) are designed to accept multi values. - -Here is a sample of setting multi values for slice flags: - - -``` -package main - -import ( - "fmt" - "os" - "github.com/urfave/cli/v2" -) - -func main() { - os.Args = []string{"multi_values", - "--stringSclice", "parsed1,parsed2", "--stringSclice", "parsed3,parsed4", - "--float64Sclice", "13.3,14.4", "--float64Sclice", "15.5,16.6", - "--int64Sclice", "13,14", "--int64Sclice", "15,16", - "--intSclice", "13,14", "--intSclice", "15,16", - } - app := cli.NewApp() - app.Name = "multi_values" - app.Flags = []cli.Flag{ - &cli.StringSliceFlag{Name: "stringSclice"}, - &cli.Float64SliceFlag{Name: "float64Sclice"}, - &cli.Int64SliceFlag{Name: "int64Sclice"}, - &cli.IntSliceFlag{Name: "intSclice"}, - } - app.Action = func(ctx *cli.Context) error { - for i, v := range ctx.FlagNames() { - fmt.Printf("%d-%s %#v\n", i, v, ctx.Value(v)) - } - return ctx.Err() - } - - _ = app.Run(os.Args) -} - -``` - #### Required Flags You can make a flag required by setting the `Required` field to `true`. If a user diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 8ee1204..343c9b0 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -125,7 +125,7 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { if val != "" { f.Value = &Float64Slice{} - for _, s := range strings.Split(val, ",") { + for _, s := range flagSplitMultiValues(val) { if err := f.Value.Set(strings.TrimSpace(s)); err != nil { return fmt.Errorf("could not parse %q as float64 slice value for flag %s: %s", f.Value, f.Name, err) } From 50c71ed517bd28c3ead9b70ad8f13e668957f7b6 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sat, 27 Mar 2021 13:07:49 -0400 Subject: [PATCH 013/125] Remove reflect calls for doc generation --- flag.go | 60 +++++++++++++++---------------------------- flag_bool.go | 13 ++++++++++ flag_duration.go | 13 ++++++++++ flag_float64.go | 15 ++++++++++- flag_float64_slice.go | 15 ++++++++++- flag_generic.go | 13 ++++++++++ flag_int.go | 13 ++++++++++ flag_int64.go | 13 ++++++++++ flag_int64_slice.go | 15 ++++++++++- flag_int_slice.go | 15 ++++++++++- flag_path.go | 21 ++++++++++++++- flag_string.go | 21 ++++++++++++++- flag_string_slice.go | 15 ++++++++++- flag_timestamp.go | 13 ++++++++++ flag_uint.go | 13 ++++++++++ flag_uint64.go | 13 ++++++++++ 16 files changed, 234 insertions(+), 47 deletions(-) diff --git a/flag.go b/flag.go index 125c6ce..0d72662 100644 --- a/flag.go +++ b/flag.go @@ -117,6 +117,12 @@ type DocGenerationFlag interface { // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. GetValue() string + + // GetDefaultText returns the default text for this flag + GetDefaultText() string + + // GetEnvVars returns the env vars for this flag + GetEnvVars() []string } // VisibleFlag is an interface that allows to check if a flag is visible @@ -299,55 +305,29 @@ func formatDefault(format string) string { } func stringifyFlag(f Flag) string { - fv := flagValue(f) - - switch f := f.(type) { - case *IntSliceFlag: - return withEnvHint(flagStringSliceField(f, "EnvVars"), - stringifyIntSliceFlag(f)) - case *Int64SliceFlag: - return withEnvHint(flagStringSliceField(f, "EnvVars"), - stringifyInt64SliceFlag(f)) - case *Float64SliceFlag: - return withEnvHint(flagStringSliceField(f, "EnvVars"), - stringifyFloat64SliceFlag(f)) - case *StringSliceFlag: - return withEnvHint(flagStringSliceField(f, "EnvVars"), - stringifyStringSliceFlag(f)) + // enforce DocGeneration interface on flags to avoid reflection + df, ok := f.(DocGenerationFlag) + if !ok { + return "" } - placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) - - needsPlaceholder := false - defaultValueString := "" - val := fv.FieldByName("Value") - if val.IsValid() { - needsPlaceholder = val.Kind() != reflect.Bool - defaultValueString = fmt.Sprintf(formatDefault("%v"), val.Interface()) - - if val.Kind() == reflect.String && val.String() != "" { - defaultValueString = fmt.Sprintf(formatDefault("%q"), val.String()) - } - } - - helpText := fv.FieldByName("DefaultText") - if helpText.IsValid() && helpText.String() != "" { - needsPlaceholder = val.Kind() != reflect.Bool - defaultValueString = fmt.Sprintf(formatDefault("%s"), helpText.String()) - } - - if defaultValueString == formatDefault("") { - defaultValueString = "" - } + placeholder, usage := unquoteUsage(df.GetUsage()) + needsPlaceholder := df.TakesValue() if needsPlaceholder && placeholder == "" { placeholder = defaultPlaceholder } + defaultValueString := "" + + if s := df.GetDefaultText(); s != "" { + defaultValueString = fmt.Sprintf(formatDefault("%s"), s) + } + usageWithDefault := strings.TrimSpace(usage + defaultValueString) - return withEnvHint(flagStringSliceField(f, "EnvVars"), - fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault)) + return withEnvHint(df.GetEnvVars(), + fmt.Sprintf("%s\t%s", prefixedNames(df.Names(), placeholder), usageWithDefault)) } func stringifyIntSliceFlag(f *IntSliceFlag) string { diff --git a/flag_bool.go b/flag_bool.go index 8bd5820..b8e625a 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -63,6 +63,19 @@ func (f *BoolFlag) IsVisible() bool { return !f.Hidden } +// GetDefaultText returns the default text for this flag +func (f *BoolFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return fmt.Sprintf("%v", f.Value) +} + +// GetEnvVars returns the env vars for this flag +func (f *BoolFlag) GetEnvVars() []string { + return f.EnvVars +} + // 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 { diff --git a/flag_duration.go b/flag_duration.go index 28f3978..e8ca15e 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -63,6 +63,19 @@ func (f *DurationFlag) IsVisible() bool { return !f.Hidden } +// GetDefaultText returns the default text for this flag +func (f *DurationFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *DurationFlag) GetEnvVars() []string { + return f.EnvVars +} + // 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 { diff --git a/flag_float64.go b/flag_float64.go index 10fb8df..e08e97f 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -55,7 +55,20 @@ func (f *Float64Flag) GetUsage() string { // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *Float64Flag) GetValue() string { - return fmt.Sprintf("%f", f.Value) + return fmt.Sprintf("%v", f.Value) +} + +// GetDefaultText returns the default text for this flag +func (f *Float64Flag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *Float64Flag) GetEnvVars() []string { + return f.EnvVars } // IsVisible returns true if the flag is not hidden, otherwise false diff --git a/flag_float64_slice.go b/flag_float64_slice.go index f752ad7..ecef6ee 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -95,7 +95,7 @@ func (f *Float64SliceFlag) IsSet() bool { // String returns a readable representation of this value // (for usage defaults) func (f *Float64SliceFlag) String() string { - return FlagStringer(f) + return withEnvHint(f.GetEnvVars(), stringifyFloat64SliceFlag(f)) } // Names returns the names of the flag @@ -132,6 +132,19 @@ func (f *Float64SliceFlag) IsVisible() bool { return !f.Hidden } +// GetDefaultText returns the default text for this flag +func (f *Float64SliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *Float64SliceFlag) GetEnvVars() []string { + return f.EnvVars +} + // 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 { diff --git a/flag_generic.go b/flag_generic.go index fdf586d..d159507 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -71,6 +71,19 @@ func (f *GenericFlag) IsVisible() bool { return !f.Hidden } +// GetDefaultText returns the default text for this flag +func (f *GenericFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *GenericFlag) GetEnvVars() []string { + return f.EnvVars +} + // 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 { diff --git a/flag_int.go b/flag_int.go index 1ebe356..62c0848 100644 --- a/flag_int.go +++ b/flag_int.go @@ -63,6 +63,19 @@ func (f *IntFlag) IsVisible() bool { return !f.Hidden } +// GetDefaultText returns the default text for this flag +func (f *IntFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *IntFlag) GetEnvVars() []string { + return f.EnvVars +} + // 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 { diff --git a/flag_int64.go b/flag_int64.go index ecf0e9e..2f0be7a 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -63,6 +63,19 @@ func (f *Int64Flag) IsVisible() bool { return !f.Hidden } +// GetDefaultText returns the default text for this flag +func (f *Int64Flag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *Int64Flag) GetEnvVars() []string { + return f.EnvVars +} + // 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 { diff --git a/flag_int64_slice.go b/flag_int64_slice.go index b4c8bc1..7995f44 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -96,7 +96,7 @@ func (f *Int64SliceFlag) IsSet() bool { // String returns a readable representation of this value // (for usage defaults) func (f *Int64SliceFlag) String() string { - return FlagStringer(f) + return withEnvHint(f.GetEnvVars(), stringifyInt64SliceFlag(f)) } // Names returns the names of the flag @@ -133,6 +133,19 @@ func (f *Int64SliceFlag) IsVisible() bool { return !f.Hidden } +// GetDefaultText returns the default text for this flag +func (f *Int64SliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *Int64SliceFlag) GetEnvVars() []string { + return f.EnvVars +} + // 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 { diff --git a/flag_int_slice.go b/flag_int_slice.go index d4889b3..8f5f1d5 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -107,7 +107,7 @@ func (f *IntSliceFlag) IsSet() bool { // String returns a readable representation of this value // (for usage defaults) func (f *IntSliceFlag) String() string { - return FlagStringer(f) + return withEnvHint(f.GetEnvVars(), stringifyIntSliceFlag(f)) } // Names returns the names of the flag @@ -144,6 +144,19 @@ func (f *IntSliceFlag) IsVisible() bool { return !f.Hidden } +// GetDefaultText returns the default text for this flag +func (f *IntSliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *IntSliceFlag) GetEnvVars() []string { + return f.EnvVars +} + // 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 { diff --git a/flag_path.go b/flag_path.go index b3aa919..4010e84 100644 --- a/flag_path.go +++ b/flag_path.go @@ -1,6 +1,9 @@ package cli -import "flag" +import ( + "flag" + "fmt" +) type PathFlag struct { Name string @@ -59,6 +62,22 @@ func (f *PathFlag) IsVisible() bool { return !f.Hidden } +// GetDefaultText returns the default text for this flag +func (f *PathFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + if f.Value == "" { + return f.Value + } + return fmt.Sprintf("%q", f.Value) +} + +// GetEnvVars returns the env vars for this flag +func (f *PathFlag) GetEnvVars() []string { + return f.EnvVars +} + // 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 { diff --git a/flag_string.go b/flag_string.go index e7bd412..7e81499 100644 --- a/flag_string.go +++ b/flag_string.go @@ -1,6 +1,9 @@ package cli -import "flag" +import ( + "flag" + "fmt" +) // StringFlag is a flag with type string type StringFlag struct { @@ -60,6 +63,22 @@ func (f *StringFlag) IsVisible() bool { return !f.Hidden } +// GetDefaultText returns the default text for this flag +func (f *StringFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + if f.Value == "" { + return f.Value + } + return fmt.Sprintf("%q", f.Value) +} + +// GetEnvVars returns the env vars for this flag +func (f *StringFlag) GetEnvVars() []string { + return f.EnvVars +} + // 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 { diff --git a/flag_string_slice.go b/flag_string_slice.go index 5269643..1664247 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -92,7 +92,7 @@ func (f *StringSliceFlag) IsSet() bool { // String returns a readable representation of this value // (for usage defaults) func (f *StringSliceFlag) String() string { - return FlagStringer(f) + return withEnvHint(f.GetEnvVars(), stringifyStringSliceFlag(f)) } // Names returns the names of the flag @@ -129,6 +129,19 @@ func (f *StringSliceFlag) IsVisible() bool { return !f.Hidden } +// GetDefaultText returns the default text for this flag +func (f *StringSliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *StringSliceFlag) GetEnvVars() []string { + return f.EnvVars +} + // Apply populates the flag given the flag set and environment func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { diff --git a/flag_timestamp.go b/flag_timestamp.go index 7458a79..ed06418 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -119,6 +119,19 @@ func (f *TimestampFlag) IsVisible() bool { return !f.Hidden } +// GetDefaultText returns the default text for this flag +func (f *TimestampFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *TimestampFlag) GetEnvVars() []string { + return f.EnvVars +} + // Apply populates the flag given the flag set and environment func (f *TimestampFlag) Apply(set *flag.FlagSet) error { if f.Layout == "" { diff --git a/flag_uint.go b/flag_uint.go index 23a70a7..dd10e1c 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -88,6 +88,19 @@ func (f *UintFlag) GetValue() string { return fmt.Sprintf("%d", f.Value) } +// GetDefaultText returns the default text for this flag +func (f *UintFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *UintFlag) GetEnvVars() []string { + return f.EnvVars +} + // Uint looks up the value of a local UintFlag, returns // 0 if not found func (c *Context) Uint(name string) uint { diff --git a/flag_uint64.go b/flag_uint64.go index a2df024..017db53 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -88,6 +88,19 @@ func (f *Uint64Flag) GetValue() string { return fmt.Sprintf("%d", f.Value) } +// GetDefaultText returns the default text for this flag +func (f *Uint64Flag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *Uint64Flag) GetEnvVars() []string { + return f.EnvVars +} + // Uint64 looks up the value of a local Uint64Flag, returns // 0 if not found func (c *Context) Uint64(name string) uint64 { From 7cd7ff7dd55db47593c1a5e45a4c8b8457851b87 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sun, 28 Mar 2021 18:34:30 -0400 Subject: [PATCH 014/125] Remove reflect from flag_test --- flag_test.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/flag_test.go b/flag_test.go index e46270d..d44214c 100644 --- a/flag_test.go +++ b/flag_test.go @@ -123,8 +123,13 @@ func TestFlagsFromEnv(t *testing.T) { for i, test := range flagTests { defer resetEnv(os.Environ()) os.Clearenv() - envVarSlice := reflect.Indirect(reflect.ValueOf(test.flag)).FieldByName("EnvVars").Slice(0, 1) - _ = os.Setenv(envVarSlice.Index(0).String(), test.input) + + f, ok := test.flag.(DocGenerationFlag) + if !ok { + t.Errorf("flag %v needs to implement DocGenerationFlag to retrieve env vars", test.flag) + } + envVarSlice := f.GetEnvVars() + _ = os.Setenv(envVarSlice[0], test.input) a := App{ Flags: []Flag{test.flag}, From 69366976476213a8da7b774588fe78167b75e15c Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Wed, 28 Apr 2021 21:00:51 -0400 Subject: [PATCH 015/125] Change min binary size --- internal/build/build.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/build/build.go b/internal/build/build.go index d0be855..a7bf0dd 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -193,7 +193,7 @@ 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 = 1.9 + desiredMinBinarySize = 1.8 desiredMaxBinarySize = 2.1 badNewsEmoji = "🚨" goodNewsEmoji = "✨" From e7157a87d96483a94e51dc53f6db7c400a85f74d Mon Sep 17 00:00:00 2001 From: jolheiser Date: Mon, 18 Apr 2022 20:58:38 -0500 Subject: [PATCH 016/125] Exclude hidden sub-command flags from docs Signed-off-by: jolheiser --- docs.go | 4 ++-- docs_test.go | 5 +++++ fish.go | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs.go b/docs.go index 9f82fc6..e6f0265 100644 --- a/docs.go +++ b/docs.go @@ -80,14 +80,14 @@ func prepareCommands(commands []*Command, level int) []string { usageText, ) - flags := prepareArgsWithValues(command.Flags) + flags := prepareArgsWithValues(command.VisibleFlags()) if len(flags) > 0 { prepared += fmt.Sprintf("\n%s", strings.Join(flags, "\n")) } coms = append(coms, prepared) - // recursevly iterate subcommands + // recursively iterate subcommands if len(command.Subcommands) > 0 { coms = append( coms, diff --git a/docs_test.go b/docs_test.go index adccbbb..ad714e5 100644 --- a/docs_test.go +++ b/docs_test.go @@ -103,6 +103,11 @@ Should be a part of the same code block Aliases: []string{"s"}, Usage: "some usage text", }, + &StringFlag{ + Name: "sub-command-hidden-flag", + Usage: "some hidden usage text", + Hidden: true, + }, }, Name: "sub-usage", Usage: "standard usage text", diff --git a/fish.go b/fish.go index 588e070..eec3253 100644 --- a/fish.go +++ b/fish.go @@ -95,7 +95,7 @@ func (a *App) prepareFishCommands(commands []*Command, allCommands *[]string, pr completions = append(completions, completion.String()) completions = append( completions, - a.prepareFishFlags(command.Flags, command.Names())..., + a.prepareFishFlags(command.VisibleFlags(), command.Names())..., ) // recursevly iterate subcommands From f89647bd19dac132bbf382f3377bcdee022ae791 Mon Sep 17 00:00:00 2001 From: Ihor Urazov Date: Thu, 14 Jan 2021 21:14:32 +0200 Subject: [PATCH 017/125] Simplify zsh completion Completion file shouldn't be sourced. It should provide only completion code (source of _command) for command. It's task for package manager or user to put under $fpath. --- autocomplete/zsh_autocomplete | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/autocomplete/zsh_autocomplete b/autocomplete/zsh_autocomplete index cf39c88..b3872bf 100644 --- a/autocomplete/zsh_autocomplete +++ b/autocomplete/zsh_autocomplete @@ -1,23 +1,16 @@ #compdef $PROG -_cli_zsh_autocomplete() { +local -a opts +local cur +cur=${words[-1]} +if [[ "$cur" == "-"* ]]; then + opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") +else + opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}") +fi - local -a opts - local cur - cur=${words[-1]} - if [[ "$cur" == "-"* ]]; then - opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") - else - opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}") - fi - - if [[ "${opts[1]}" != "" ]]; then - _describe 'values' opts - else - _files - fi - - return -} - -compdef _cli_zsh_autocomplete $PROG +if [[ "${opts[1]}" != "" ]]; then + _describe 'values' opts +else + _files +fi From 1150c2e180e571fc3460db4c67bac76bf67cbd80 Mon Sep 17 00:00:00 2001 From: Ihor Urazov Date: Fri, 29 Jan 2021 17:04:54 +0200 Subject: [PATCH 018/125] Properly detect Zsh shell There is no need to define custom shell var, when Zsh can be detected by checking SHELL env var. --- app_test.go | 2 +- autocomplete/zsh_autocomplete | 4 ++-- docs/v2/manual.md | 9 ++++----- help.go | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app_test.go b/app_test.go index 76e211d..6afc681 100644 --- a/app_test.go +++ b/app_test.go @@ -355,7 +355,7 @@ func ExampleApp_Run_bashComplete() { func ExampleApp_Run_zshComplete() { // set args for examples sake os.Args = []string{"greet", "--generate-bash-completion"} - _ = os.Setenv("_CLI_ZSH_AUTOCOMPLETE_HACK", "1") + _ = os.Setenv("SHELL", "/usr/bin/zsh") app := NewApp() app.Name = "greet" diff --git a/autocomplete/zsh_autocomplete b/autocomplete/zsh_autocomplete index b3872bf..ee1b562 100644 --- a/autocomplete/zsh_autocomplete +++ b/autocomplete/zsh_autocomplete @@ -4,9 +4,9 @@ local -a opts local cur cur=${words[-1]} if [[ "$cur" == "-"* ]]; then - opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") + opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") else - opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}") + opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}") fi if [[ "${opts[1]}" != "" ]]; then diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 56be65b..27b5c62 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -1211,14 +1211,13 @@ func main() { #### ZSH Support Auto-completion for ZSH is also supported using the `autocomplete/zsh_autocomplete` -file included in this repo. Two environment variables are used, `PROG` and `_CLI_ZSH_AUTOCOMPLETE_HACK`. -Set `PROG` to the program name as before, set `_CLI_ZSH_AUTOCOMPLETE_HACK` to `1`, and -then `source path/to/autocomplete/zsh_autocomplete`. Adding the following lines to your ZSH -configuration file (usually `.zshrc`) will allow the auto-completion to persist across new shells: +file included in this repo. One environment variable is used, `PROG`. Set +`PROG` to the program name as before, and then `source path/to/autocomplete/zsh_autocomplete`. +Adding the following lines to your ZSH configuration file (usually `.zshrc`) +will allow the auto-completion to persist across new shells: ``` PROG= -_CLI_ZSH_AUTOCOMPLETE_HACK=1 source path/to/autocomplete/zsh_autocomplete ``` #### ZSH default auto-complete example diff --git a/help.go b/help.go index 0a421ee..a7b3a94 100644 --- a/help.go +++ b/help.go @@ -102,7 +102,7 @@ func printCommandSuggestions(commands []*Command, writer io.Writer) { if command.Hidden { continue } - if os.Getenv("_CLI_ZSH_AUTOCOMPLETE_HACK") == "1" { + if strings.Contains(os.Getenv("SHELL"), "zsh") { for _, name := range command.Names() { _, _ = fmt.Fprintf(writer, "%s:%s\n", name, command.Usage) } From 6effd9fba83bf06aed486eb8527badecb7c07e10 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 21 Apr 2022 19:11:01 -0400 Subject: [PATCH 019/125] Update dependencies and go.mod spec Closes #1322 --- .github/workflows/cli.yml | 4 ++-- go.mod | 8 +++++--- go.sum | 6 ++++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index e9a45c2..0ab3baf 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -31,7 +31,7 @@ jobs: uses: actions/checkout@v3 - name: GOFMT Check - if: matrix.go == '1.17.x' && matrix.os == 'ubuntu-latest' + if: matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest' run: test -z $(gofmt -l .) - name: vet @@ -44,7 +44,7 @@ jobs: run: go run internal/build/build.go check-binary-size - name: Upload coverage to Codecov - if: success() && matrix.go == '1.17.x' && matrix.os == 'ubuntu-latest' + if: success() && matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@v2 with: fail_ci_if_error: true diff --git a/go.mod b/go.mod index 71c38a6..7a4b88b 100644 --- a/go.mod +++ b/go.mod @@ -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/BurntSushi/toml v1.1.0 github.com/cpuguy83/go-md2man/v2 v2.0.1 - gopkg.in/yaml.v2 v2.2.8 + gopkg.in/yaml.v2 v2.4.0 ) + +require github.com/russross/blackfriday/v2 v2.1.0 // indirect diff --git a/go.sum b/go.sum index ad7e1ce..bde502b 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,12 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +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.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/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= From c4c15e14538e59cee39aed139ce75bcf375ff34c Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 21 Apr 2022 20:07:06 -0400 Subject: [PATCH 020/125] Writing tests around changes from #1186 --- help.go | 14 ++++++--- help_test.go | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 4 deletions(-) diff --git a/help.go b/help.go index 03cead4..fad990a 100644 --- a/help.go +++ b/help.go @@ -163,20 +163,26 @@ func DefaultCompleteWithFlags(cmd *Command) func(c *Context) { return func(c *Context) { if len(os.Args) > 2 { lastArg := os.Args[len(os.Args)-2] + if strings.HasPrefix(lastArg, "-") { if cmd != nil { printFlagSuggestions(lastArg, cmd.Flags, c.App.Writer) - } else { - printFlagSuggestions(lastArg, c.App.Flags, c.App.Writer) + + return } + + printFlagSuggestions(lastArg, c.App.Flags, c.App.Writer) + return } } + if cmd != nil { printCommandSuggestions(cmd.Subcommands, c.App.Writer) - } else { - printCommandSuggestions(c.App.Commands, c.App.Writer) + return } + + printCommandSuggestions(c.App.Commands, c.App.Writer) } } diff --git a/help_test.go b/help_test.go index 8dd262d..98530dd 100644 --- a/help_test.go +++ b/help_test.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "io/ioutil" + "os" "runtime" "strings" "testing" @@ -1037,3 +1038,85 @@ func TestHideHelpCommand_WithSubcommands(t *testing.T) { t.Errorf("Run returned unexpected error: %v", err) } } + +func TestDefaultCompleteWithFlags(t *testing.T) { + origArgv := os.Args + + t.Cleanup(func() { + os.Args = origArgv + }) + + for _, tc := range []struct { + name string + c *Context + cmd *Command + argv []string + expected string + }{ + { + name: "empty", + c: &Context{App: &App{}}, + cmd: &Command{}, + argv: []string{"prog", "cmd"}, + expected: "", + }, + { + name: "typical-flag-suggestion", + c: &Context{App: &App{ + Name: "cmd", + Flags: []Flag{ + &BoolFlag{Name: "happiness"}, + &Int64Flag{Name: "everybody-jump-on"}, + }, + Commands: []*Command{ + {Name: "putz"}, + }, + }}, + cmd: &Command{ + Flags: []Flag{ + &BoolFlag{Name: "excitement"}, + &StringFlag{Name: "hat-shape"}, + }, + }, + argv: []string{"cmd", "--e", "--generate-bash-completion"}, + expected: "--excitement\n", + }, + { + name: "typical-command-suggestion", + c: &Context{App: &App{ + Name: "cmd", + Flags: []Flag{ + &BoolFlag{Name: "happiness"}, + &Int64Flag{Name: "everybody-jump-on"}, + }, + }}, + cmd: &Command{ + Name: "putz", + Subcommands: []*Command{ + {Name: "futz"}, + }, + Flags: []Flag{ + &BoolFlag{Name: "excitement"}, + &StringFlag{Name: "hat-shape"}, + }, + }, + argv: []string{"cmd", "--generate-bash-completion"}, + expected: "futz\n", + }, + } { + t.Run(tc.name, func(ct *testing.T) { + writer := &bytes.Buffer{} + tc.c.App.Writer = writer + + os.Args = tc.argv + f := DefaultCompleteWithFlags(tc.cmd) + f(tc.c) + + written := writer.String() + + if written != tc.expected { + ct.Errorf("written help does not match expected %q != %q", written, tc.expected) + } + }) + } +} From 79ed8b5263d6e079639e515a5efcd2079642898b Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 21 Apr 2022 20:35:22 -0400 Subject: [PATCH 021/125] Drop desired min binary size accordingly --- internal/build/build.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/build/build.go b/internal/build/build.go index 9fd2cf9..4cbaa68 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -193,7 +193,7 @@ 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 = 1.9 + desiredMinBinarySize = 1.675 desiredMaxBinarySize = 2.2 badNewsEmoji = "🚨" goodNewsEmoji = "✨" From 9fd3cc92ad483b1680332797ccc1fcaaa9f1caa5 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 21 Apr 2022 23:02:44 -0400 Subject: [PATCH 022/125] Add tests around flag stringifying for all modified types --- flag_test.go | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) diff --git a/flag_test.go b/flag_test.go index 6ce7367..44c3500 100644 --- a/flag_test.go +++ b/flag_test.go @@ -168,6 +168,183 @@ func TestFlagsFromEnv(t *testing.T) { } } +type nodocFlag struct { + Flag + + Name string +} + +func TestFlagStringifying(t *testing.T) { + for _, tc := range []struct { + name string + fl Flag + expected string + }{ + { + name: "bool-flag", + fl: &BoolFlag{Name: "vividly"}, + expected: "--vividly\t(default: false)", + }, + { + name: "bool-flag-with-default-text", + fl: &BoolFlag{Name: "wildly", DefaultText: "scrambled"}, + expected: "--wildly\t(default: scrambled)", + }, + { + name: "duration-flag", + fl: &DurationFlag{Name: "scream-for"}, + expected: "--scream-for value\t(default: 0s)", + }, + { + name: "duration-flag-with-default-text", + fl: &DurationFlag{Name: "feels-about", DefaultText: "whimsically"}, + expected: "--feels-about value\t(default: whimsically)", + }, + { + name: "float64-flag", + fl: &Float64Flag{Name: "arduous"}, + expected: "--arduous value\t(default: 0)", + }, + { + name: "float64-flag-with-default-text", + fl: &Float64Flag{Name: "filibuster", DefaultText: "42"}, + expected: "--filibuster value\t(default: 42)", + }, + { + name: "float64-slice-flag", + fl: &Float64SliceFlag{Name: "pizzas"}, + expected: "--pizzas value\t", + }, + { + name: "float64-slice-flag-with-default-text", + fl: &Float64SliceFlag{Name: "pepperonis", DefaultText: "shaved"}, + expected: "--pepperonis value\t(default: shaved)", + }, + { + name: "generic-flag", + fl: &GenericFlag{Name: "yogurt"}, + expected: "--yogurt value\t", + }, + { + name: "generic-flag-with-default-text", + fl: &GenericFlag{Name: "ricotta", DefaultText: "plops"}, + expected: "--ricotta value\t(default: plops)", + }, + { + name: "int-flag", + fl: &IntFlag{Name: "grubs"}, + expected: "--grubs value\t(default: 0)", + }, + { + name: "int-flag-with-default-text", + fl: &IntFlag{Name: "poisons", DefaultText: "11ty"}, + expected: "--poisons value\t(default: 11ty)", + }, + { + name: "int-slice-flag", + fl: &IntSliceFlag{Name: "pencils"}, + expected: "--pencils value\t", + }, + { + name: "int-slice-flag-with-default-text", + fl: &IntFlag{Name: "pens", DefaultText: "-19"}, + expected: "--pens value\t(default: -19)", + }, + { + name: "int64-flag", + fl: &Int64Flag{Name: "flume"}, + expected: "--flume value\t(default: 0)", + }, + { + name: "int64-flag-with-default-text", + fl: &Int64Flag{Name: "shattering", DefaultText: "22"}, + expected: "--shattering value\t(default: 22)", + }, + { + name: "int64-slice-flag", + fl: &Int64SliceFlag{Name: "drawers"}, + expected: "--drawers value\t", + }, + { + name: "int64-slice-flag-with-default-text", + fl: &Int64SliceFlag{Name: "handles", DefaultText: "-2"}, + expected: "--handles value\t(default: -2)", + }, + { + name: "path-flag", + fl: &PathFlag{Name: "soup"}, + expected: "--soup value\t", + }, + { + name: "path-flag-with-default-text", + fl: &PathFlag{Name: "stew", DefaultText: "charred/beans"}, + expected: "--stew value\t(default: charred/beans)", + }, + { + name: "string-flag", + fl: &StringFlag{Name: "arf-sound"}, + expected: "--arf-sound value\t", + }, + { + name: "string-flag-with-default-text", + fl: &StringFlag{Name: "woof-sound", DefaultText: "urp"}, + expected: "--woof-sound value\t(default: urp)", + }, + { + name: "string-slice-flag", + fl: &StringSliceFlag{Name: "meow-sounds"}, + expected: "--meow-sounds value\t", + }, + { + name: "string-slice-flag-with-default-text", + fl: &StringSliceFlag{Name: "moo-sounds", DefaultText: "awoo"}, + expected: "--moo-sounds value\t(default: awoo)", + }, + { + name: "timestamp-flag", + fl: &TimestampFlag{Name: "eating"}, + expected: "--eating value\t", + }, + { + name: "timestamp-flag-with-default-text", + fl: &TimestampFlag{Name: "sleeping", DefaultText: "earlier"}, + expected: "--sleeping value\t(default: earlier)", + }, + { + name: "uint-flag", + fl: &UintFlag{Name: "jars"}, + expected: "--jars value\t(default: 0)", + }, + { + name: "uint-flag-with-default-text", + fl: &UintFlag{Name: "bottles", DefaultText: "99"}, + expected: "--bottles value\t(default: 99)", + }, + { + name: "uint64-flag", + fl: &Uint64Flag{Name: "cans"}, + expected: "--cans value\t(default: 0)", + }, + { + name: "uint64-flag-with-default-text", + fl: &UintFlag{Name: "tubes", DefaultText: "13"}, + expected: "--tubes value\t(default: 13)", + }, + { + name: "nodoc-flag", + fl: &nodocFlag{Name: "scarecrow"}, + expected: "", + }, + } { + t.Run(tc.name, func(ct *testing.T) { + s := stringifyFlag(tc.fl) + if s != tc.expected { + ct.Errorf("stringified flag %q does not match expected %q", s, tc.expected) + } + }) + } +} + var stringFlagTests = []struct { name string aliases []string From 3b6436c36d7fdf927c5f53c916dbd9dded6cb0ed Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 22 Apr 2022 06:35:07 -0400 Subject: [PATCH 023/125] Pass non-empty string slice to example app `Run` (#1364) so that it does not panic when run. --- internal/example-cli/example-cli.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/example-cli/example-cli.go b/internal/example-cli/example-cli.go index 6fb84e2..06cbbff 100644 --- a/internal/example-cli/example-cli.go +++ b/internal/example-cli/example-cli.go @@ -7,5 +7,5 @@ import ( ) func main() { - (&cli.App{}).Run([]string{}) + (&cli.App{}).Run([]string{""}) } From f1d0b0ef43a2f4c816eddcccfa6cb4e8fbe014b6 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 22 Apr 2022 08:19:29 -0400 Subject: [PATCH 024/125] Add a security policy document Closes #1342 --- docs/SECURITY.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 docs/SECURITY.md diff --git a/docs/SECURITY.md b/docs/SECURITY.md new file mode 100644 index 0000000..a6fe0cc --- /dev/null +++ b/docs/SECURITY.md @@ -0,0 +1,24 @@ +# Security Policy + +Hello and thank you for your interest in the `urfave/cli` security +policy! :tada: :lock: + +## Supported Versions + +| Version | Supported | +| --------- | ------------------ | +| >= 2.3.x | :white_check_mark: | +| < 2.3 | :x: | +| >= 1.22.x | :white_check_mark: | +| < 1.22 | :x: | + +## Reporting a Vulnerability + +Please disclose any vulnerabilities by sending an email to: + +[dan+urfave-cli-security@meatballhat.com](mailto:dan+urfave-cli-security@meatballhat.com) + +You should expect a response within 48 hours and further +communications to be decided via email. The `urfave/cli` maintainer +team comprises volunteers who contribute when possible, so please +have patience :bow: From b7ee8fff3118da8fb07c391577edc94ebef7bee6 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 22 Apr 2022 10:49:09 -0400 Subject: [PATCH 025/125] Revert "Add Stale bot configuration per docs" --- .github/stale.yml | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index f050e9b..0000000 --- a/.github/stale.yml +++ /dev/null @@ -1,17 +0,0 @@ -# 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 -# 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 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 From baf8ae98deb3bae30567f621d5167694da2a1402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Fri, 22 Apr 2022 15:48:26 +0200 Subject: [PATCH 026/125] BoolFlag.ValueFromContext() as convenient accessor --- flag_bool.go | 5 +++++ flag_test.go | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/flag_bool.go b/flag_bool.go index b8e625a..bdca2b5 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -102,6 +102,11 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error { return nil } +// ValueFromContext returns the flag’s value in the given Context. +func (f *BoolFlag) ValueFromContext(ctx *Context) bool { + return ctx.Bool(f.Name) +} + // Bool looks up the value of a local BoolFlag, returns // false if not found func (c *Context) Bool(name string) bool { diff --git a/flag_test.go b/flag_test.go index 44c3500..53cb60b 100644 --- a/flag_test.go +++ b/flag_test.go @@ -51,6 +51,17 @@ func TestBoolFlagApply_SetsAllNames(t *testing.T) { expect(t, v, true) } +func TestBoolFlagValueFromContext(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("trueflag", true, "doc") + set.Bool("falseflag", false, "doc") + ctx := NewContext(nil, set, nil) + tf := &BoolFlag{Name: "trueflag"} + ff := &BoolFlag{Name: "falseflag"} + expect(t, tf.ValueFromContext(ctx), true) + expect(t, ff.ValueFromContext(ctx), false) +} + func TestFlagsFromEnv(t *testing.T) { newSetFloat64Slice := func(defaults ...float64) Float64Slice { s := NewFloat64Slice(defaults...) From 9eae255aac5411488cdebaf71efe73440fcdf929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Fri, 22 Apr 2022 15:50:13 +0200 Subject: [PATCH 027/125] DurationFlag.ValueFromContext() as convenient accessor --- flag_duration.go | 5 +++++ flag_test.go | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/flag_duration.go b/flag_duration.go index e8ca15e..1de10b1 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -101,6 +101,11 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) error { return nil } +// ValueFromContext returns the flag’s value in the given Context. +func (f *DurationFlag) ValueFromContext(ctx *Context) time.Duration { + return ctx.Duration(f.Name) +} + // Duration looks up the value of a local DurationFlag, returns // 0 if not found func (c *Context) Duration(name string) time.Duration { diff --git a/flag_test.go b/flag_test.go index 53cb60b..2f41f42 100644 --- a/flag_test.go +++ b/flag_test.go @@ -826,6 +826,14 @@ func TestDurationFlagApply_SetsAllNames(t *testing.T) { expect(t, v, time.Hour*30) } +func TestDurationFlagValueFromContext(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Duration("myflag", 42*time.Second, "doc") + ctx := NewContext(nil, set, nil) + f := &DurationFlag{Name: "myflag"} + expect(t, f.ValueFromContext(ctx), 42*time.Second) +} + var intSliceFlagTests = []struct { name string aliases []string From 5047beb00185b71caa446147381f89a3178f575b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Fri, 22 Apr 2022 15:55:37 +0200 Subject: [PATCH 028/125] Float64Flag.ValueFromContext() as convenient accessor --- flag_float64.go | 5 +++++ flag_test.go | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/flag_float64.go b/flag_float64.go index 0ac5b43..f2eb040 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -101,6 +101,11 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) error { return nil } +// ValueFromContext returns the flag’s value in the given Context. +func (f *Float64Flag) ValueFromContext(ctx *Context) float64 { + return ctx.Float64(f.Name) +} + // Float64 looks up the value of a local Float64Flag, returns // 0 if not found func (c *Context) Float64(name string) float64 { diff --git a/flag_test.go b/flag_test.go index 2f41f42..edf9e8e 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1068,6 +1068,14 @@ func TestFloat64FlagApply_SetsAllNames(t *testing.T) { expect(t, v, float64(43.33333)) } +func TestFloat64FlagValueFromContext(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Float64("myflag", 1.23, "doc") + ctx := NewContext(nil, set, nil) + f := &Float64Flag{Name: "myflag"} + expect(t, f.ValueFromContext(ctx), 1.23) +} + var float64SliceFlagTests = []struct { name string aliases []string From 2f92fc644ca176d529d751f5e19675bdebb85d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Fri, 22 Apr 2022 16:04:42 +0200 Subject: [PATCH 029/125] Float64SliceFlag.ValueFromContext() as convenient accessor --- flag_float64_slice.go | 5 +++++ flag_test.go | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 984f77f..c987688 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -175,6 +175,11 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { return nil } +// ValueFromContext returns the flag’s value in the given Context. +func (f *Float64SliceFlag) ValueFromContext(ctx *Context) []float64 { + return ctx.Float64Slice(f.Name) +} + // Float64Slice looks up the value of a local Float64SliceFlag, returns // nil if not found func (c *Context) Float64Slice(name string) []float64 { diff --git a/flag_test.go b/flag_test.go index edf9e8e..ce9b5a3 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1117,6 +1117,14 @@ func TestFloat64SliceFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestFloat64SliceFlagValueFromContext(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Var(NewFloat64Slice(1.23, 4.56), "myflag", "doc") + ctx := NewContext(nil, set, nil) + f := &Float64SliceFlag{Name: "myflag"} + expect(t, f.ValueFromContext(ctx), []float64{1.23, 4.56}) +} + var genericFlagTests = []struct { name string value Generic From 8bd5fb2390f6b8a4f2cba2376302cce8a545d5e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Fri, 22 Apr 2022 16:10:09 +0200 Subject: [PATCH 030/125] GenericFlag.ValueFromContext() as convenient accessor --- flag_generic.go | 5 +++++ flag_test.go | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/flag_generic.go b/flag_generic.go index d159507..483c7d1 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -104,6 +104,11 @@ func (f GenericFlag) Apply(set *flag.FlagSet) error { return nil } +// ValueFromContext returns the flag’s value in the given Context. +func (f *GenericFlag) ValueFromContext(ctx *Context) interface{} { + return ctx.Generic(f.Name) +} + // Generic looks up the value of a local GenericFlag, returns // nil if not found func (c *Context) Generic(name string) interface{} { diff --git a/flag_test.go b/flag_test.go index ce9b5a3..7fa6673 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1173,6 +1173,14 @@ func TestGenericFlagApply_SetsAllNames(t *testing.T) { expect(t, err, nil) } +func TestGenericFlagValueFromContext(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Var(&Parser{"abc", "def"}, "myflag", "doc") + ctx := NewContext(nil, set, nil) + f := &GenericFlag{Name: "myflag"} + expect(t, f.ValueFromContext(ctx), &Parser{"abc", "def"}) +} + func TestParseMultiString(t *testing.T) { _ = (&App{ Flags: []Flag{ From bf18c00347867c9f8be0e872a82b4e1baed24d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Fri, 22 Apr 2022 16:12:11 +0200 Subject: [PATCH 031/125] IntFlag.ValueFromContext() as convenient accessor --- flag_int.go | 5 +++++ flag_test.go | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/flag_int.go b/flag_int.go index 62c0848..642a68b 100644 --- a/flag_int.go +++ b/flag_int.go @@ -102,6 +102,11 @@ func (f *IntFlag) Apply(set *flag.FlagSet) error { return nil } +// ValueFromContext returns the flag’s value in the given Context. +func (f *IntFlag) ValueFromContext(ctx *Context) int { + return ctx.Int(f.Name) +} + // Int looks up the value of a local IntFlag, returns // 0 if not found func (c *Context) Int(name string) int { diff --git a/flag_test.go b/flag_test.go index 7fa6673..86d281e 100644 --- a/flag_test.go +++ b/flag_test.go @@ -663,6 +663,14 @@ func TestIntFlagApply_SetsAllNames(t *testing.T) { expect(t, v, 5) } +func TestIntFlagValueFromContext(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Int("myflag", 42, "doc") + ctx := NewContext(nil, set, nil) + f := &IntFlag{Name: "myflag"} + expect(t, f.ValueFromContext(ctx), 42) +} + var int64FlagTests = []struct { name string expected string From 18b44dfb291d23ff4db2f388e746d88e6f7a310e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Fri, 22 Apr 2022 16:45:10 +0200 Subject: [PATCH 032/125] Int64Flag.ValueFromContext() as convenient accessor --- flag_int64.go | 5 +++++ flag_test.go | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/flag_int64.go b/flag_int64.go index 2f0be7a..6b40374 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -101,6 +101,11 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) error { return nil } +// ValueFromContext returns the flag’s value in the given Context. +func (f *Int64Flag) ValueFromContext(ctx *Context) int64 { + return ctx.Int64(f.Name) +} + // Int64 looks up the value of a local Int64Flag, returns // 0 if not found func (c *Context) Int64(name string) int64 { diff --git a/flag_test.go b/flag_test.go index 86d281e..0b343e9 100644 --- a/flag_test.go +++ b/flag_test.go @@ -709,6 +709,14 @@ func TestInt64FlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestInt64FlagValueFromContext(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Int64("myflag", 42, "doc") + ctx := NewContext(nil, set, nil) + f := &Int64Flag{Name: "myflag"} + expect(t, f.ValueFromContext(ctx), int64(42)) +} + var uintFlagTests = []struct { name string expected string From dcc47855b8d7685e47644ee6966df6dc7de8dfa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Fri, 22 Apr 2022 16:45:10 +0200 Subject: [PATCH 033/125] Int64SliceFlag.ValueFromContext() as convenient accessor --- flag_int64_slice.go | 5 +++++ flag_test.go | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/flag_int64_slice.go b/flag_int64_slice.go index a53b185..29e8dc4 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -174,6 +174,11 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { return nil } +// ValueFromContext returns the flag’s value in the given Context. +func (f *Int64SliceFlag) ValueFromContext(ctx *Context) []int64 { + return ctx.Int64Slice(f.Name) +} + // Int64Slice looks up the value of a local Int64SliceFlag, returns // nil if not found func (c *Context) Int64Slice(name string) []int64 { diff --git a/flag_test.go b/flag_test.go index 0b343e9..d510235 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1035,6 +1035,14 @@ func TestInt64SliceFlag_ReturnNil(t *testing.T) { } } +func TestInt64SliceFlagValueFromContext(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Var(NewInt64Slice(1, 2, 3), "myflag", "doc") + ctx := NewContext(nil, set, nil) + f := &Int64SliceFlag{Name: "myflag"} + expect(t, f.ValueFromContext(ctx), []int64{1, 2, 3}) +} + var float64FlagTests = []struct { name string expected string From 6d7f8590089276c1f11e91f93830f6ada78a035c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Fri, 22 Apr 2022 16:49:38 +0200 Subject: [PATCH 034/125] IntSliceFlag.ValueFromContext() as convenient accessor --- flag_int_slice.go | 5 +++++ flag_test.go | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/flag_int_slice.go b/flag_int_slice.go index 5f3bd88..4d1741c 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -185,6 +185,11 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { return nil } +// ValueFromContext returns the flag’s value in the given Context. +func (f *IntSliceFlag) ValueFromContext(ctx *Context) []int { + return ctx.IntSlice(f.Name) +} + // IntSlice looks up the value of a local IntSliceFlag, returns // nil if not found func (c *Context) IntSlice(name string) []int { diff --git a/flag_test.go b/flag_test.go index d510235..e80231d 100644 --- a/flag_test.go +++ b/flag_test.go @@ -939,6 +939,14 @@ func TestIntSliceFlag_SetFromParentContext(t *testing.T) { } } +func TestIntSliceFlagValueFromContext(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Var(NewIntSlice(1, 2, 3), "myflag", "doc") + ctx := NewContext(nil, set, nil) + f := &IntSliceFlag{Name: "myflag"} + expect(t, f.ValueFromContext(ctx), []int{1, 2, 3}) +} + var int64SliceFlagTests = []struct { name string aliases []string From 660184dd92d1f13f1156dfceb1f22df327dfbd3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Fri, 22 Apr 2022 16:59:19 +0200 Subject: [PATCH 035/125] PathFlag.ValueFromContext() as convenient accessor --- flag_path.go | 5 +++++ flag_test.go | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/flag_path.go b/flag_path.go index 4010e84..40c9009 100644 --- a/flag_path.go +++ b/flag_path.go @@ -96,6 +96,11 @@ func (f *PathFlag) Apply(set *flag.FlagSet) error { return nil } +// ValueFromContext returns the flag’s value in the given Context. +func (f *PathFlag) ValueFromContext(ctx *Context) string { + return ctx.Path(f.Name) +} + // Path looks up the value of a local PathFlag, returns // "" if not found func (c *Context) Path(name string) string { diff --git a/flag_test.go b/flag_test.go index e80231d..1ff56f8 100644 --- a/flag_test.go +++ b/flag_test.go @@ -501,6 +501,14 @@ func TestPathFlagApply_SetsAllNames(t *testing.T) { expect(t, v, "/path/to/file/PATH") } +func TestPathFlagValueFromContext(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.String("myflag", "/my/path", "doc") + ctx := NewContext(nil, set, nil) + f := &PathFlag{Name: "myflag"} + expect(t, f.ValueFromContext(ctx), "/my/path") +} + var envHintFlagTests = []struct { name string env string From ce4d9279c42ef762256900e52a58f8cc3ad48b08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Fri, 22 Apr 2022 17:02:18 +0200 Subject: [PATCH 036/125] StringFlag.ValueFromContext() as convenient accessor --- flag_string.go | 5 +++++ flag_test.go | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/flag_string.go b/flag_string.go index cd3c7df..7464a28 100644 --- a/flag_string.go +++ b/flag_string.go @@ -97,6 +97,11 @@ func (f *StringFlag) Apply(set *flag.FlagSet) error { return nil } +// ValueFromContext returns the flag’s value in the given Context. +func (f *StringFlag) ValueFromContext(ctx *Context) string { + return ctx.String(f.Name) +} + // String looks up the value of a local StringFlag, returns // "" if not found func (c *Context) String(name string) string { diff --git a/flag_test.go b/flag_test.go index 1ff56f8..aa9fbec 100644 --- a/flag_test.go +++ b/flag_test.go @@ -450,6 +450,14 @@ func TestStringFlagApply_SetsAllNames(t *testing.T) { expect(t, v, "YUUUU") } +func TestStringFlagValueFromContext(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.String("myflag", "foobar", "doc") + ctx := NewContext(nil, set, nil) + f := &StringFlag{Name: "myflag"} + expect(t, f.ValueFromContext(ctx), "foobar") +} + var pathFlagTests = []struct { name string aliases []string From 6b336c478f198630e67e5adeb53fe8da24640165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Fri, 22 Apr 2022 17:04:13 +0200 Subject: [PATCH 037/125] StringSliceFlag.ValueFromContext() as convenient accessor --- flag_string_slice.go | 5 +++++ flag_test.go | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/flag_string_slice.go b/flag_string_slice.go index 1664247..90e6ebd 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -186,6 +186,11 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { return nil } +// ValueFromContext returns the flag’s value in the given Context. +func (f *StringSliceFlag) ValueFromContext(ctx *Context) []string { + return ctx.StringSlice(f.Name) +} + // StringSlice looks up the value of a local StringSliceFlag, returns // nil if not found func (c *Context) StringSlice(name string) []string { diff --git a/flag_test.go b/flag_test.go index aa9fbec..cda34be 100644 --- a/flag_test.go +++ b/flag_test.go @@ -630,6 +630,14 @@ func TestStringSliceFlagApply_DefaultValueWithDestination(t *testing.T) { expect(t, defValue, fl.Destination.Value()) } +func TestStringSliceFlagValueFromContext(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Var(NewStringSlice("a", "b", "c"), "myflag", "doc") + ctx := NewContext(nil, set, nil) + f := &StringSliceFlag{Name: "myflag"} + expect(t, f.ValueFromContext(ctx), []string{"a", "b", "c"}) +} + var intFlagTests = []struct { name string expected string From 889c7b5d7a4deb4f9aaaeed3a113933910253d08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Fri, 22 Apr 2022 17:07:12 +0200 Subject: [PATCH 038/125] TimestampFlag.ValueFromContext() as convenient accessor --- flag_test.go | 9 +++++++++ flag_timestamp.go | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/flag_test.go b/flag_test.go index cda34be..11b3265 100644 --- a/flag_test.go +++ b/flag_test.go @@ -2264,6 +2264,15 @@ func TestTimestampFlagApply_Fail_Parse_Wrong_Time(t *testing.T) { 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\"")) } +func TestTimestampFlagValueFromContext(t *testing.T) { + set := flag.NewFlagSet("test", 0) + now := time.Now() + set.Var(NewTimestamp(now), "myflag", "doc") + ctx := NewContext(nil, set, nil) + f := &TimestampFlag{Name: "myflag"} + expect(t, f.ValueFromContext(ctx), &now) +} + type flagDefaultTestCase struct { name string flag Flag diff --git a/flag_timestamp.go b/flag_timestamp.go index ed06418..a3b230e 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -164,6 +164,11 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error { return nil } +// ValueFromContext returns the flag’s value in the given Context. +func (f *TimestampFlag) ValueFromContext(ctx *Context) *time.Time { + return ctx.Timestamp(f.Name) +} + // Timestamp gets the timestamp from a flag name func (c *Context) Timestamp(name string) *time.Time { if fs := c.lookupFlagSet(name); fs != nil { From 1f621059d32f9f1b4f66b8c719016eb119f0d816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Fri, 22 Apr 2022 17:09:09 +0200 Subject: [PATCH 039/125] UintFlag.ValueFromContext() as convenient accessor --- flag_test.go | 8 ++++++++ flag_uint.go | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/flag_test.go b/flag_test.go index 11b3265..9e6b335 100644 --- a/flag_test.go +++ b/flag_test.go @@ -779,6 +779,14 @@ func TestUintFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestUintFlagValueFromContext(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Uint("myflag", 42, "doc") + ctx := NewContext(nil, set, nil) + f := &UintFlag{Name: "myflag"} + expect(t, f.ValueFromContext(ctx), uint(42)) +} + var uint64FlagTests = []struct { name string expected string diff --git a/flag_uint.go b/flag_uint.go index dd10e1c..b074c4e 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -101,6 +101,11 @@ func (f *UintFlag) GetEnvVars() []string { return f.EnvVars } +// ValueFromContext returns the flag’s value in the given Context. +func (f *UintFlag) ValueFromContext(ctx *Context) uint { + return ctx.Uint(f.Name) +} + // Uint looks up the value of a local UintFlag, returns // 0 if not found func (c *Context) Uint(name string) uint { From ca7f26ecb04f8092897b05cdc2ccb03827a15da9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Fri, 22 Apr 2022 17:11:41 +0200 Subject: [PATCH 040/125] Uint64Flag.ValueFromContext() as convenient accessor --- flag_test.go | 8 ++++++++ flag_uint64.go | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/flag_test.go b/flag_test.go index 9e6b335..4862c03 100644 --- a/flag_test.go +++ b/flag_test.go @@ -825,6 +825,14 @@ func TestUint64FlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestUint64FlagValueFromContext(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Uint64("myflag", 42, "doc") + ctx := NewContext(nil, set, nil) + f := &Uint64Flag{Name: "myflag"} + expect(t, f.ValueFromContext(ctx), uint64(42)) +} + var durationFlagTests = []struct { name string expected string diff --git a/flag_uint64.go b/flag_uint64.go index 017db53..e79b1a7 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -101,6 +101,11 @@ func (f *Uint64Flag) GetEnvVars() []string { return f.EnvVars } +// ValueFromContext returns the flag’s value in the given Context. +func (f *Uint64Flag) ValueFromContext(ctx *Context) uint64 { + return ctx.Uint64(f.Name) +} + // Uint64 looks up the value of a local Uint64Flag, returns // 0 if not found func (c *Context) Uint64(name string) uint64 { From 75e4ee69e98bcd59c97c16cdc276d0352afa5ee0 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 22 Apr 2022 15:00:43 -0400 Subject: [PATCH 041/125] Porting remainder of #796 --- app.go | 6 ++++-- category.go | 14 ++++++++------ flag.go | 10 ++++++++-- flag_bool.go | 6 ++++++ flag_duration.go | 6 ++++++ flag_float64.go | 6 ++++++ flag_float64_slice.go | 6 ++++++ flag_generic.go | 6 ++++++ flag_int.go | 6 ++++++ flag_int64.go | 6 ++++++ flag_int64_slice.go | 8 +++++++- flag_int_slice.go | 8 +++++++- flag_path.go | 6 ++++++ flag_string.go | 6 ++++++ flag_string_slice.go | 6 ++++++ flag_timestamp.go | 6 ++++++ flag_uint.go | 6 ++++++ flag_uint64.go | 6 ++++++ 18 files changed, 112 insertions(+), 12 deletions(-) diff --git a/app.go b/app.go index dd7f026..65813c6 100644 --- a/app.go +++ b/app.go @@ -185,8 +185,10 @@ func (a *App) Setup() { } fc := FlagCategories{} - for _, flag := range c.Flags { - fc = fc.AddFlag(flag.GetCategory(), flag) + for _, fl := range c.Flags { + if cf, ok := fl.(CategorizableFlag); ok { + fc = fc.AddFlag(cf.GetCategory(), cf) + } } sort.Sort(fc) diff --git a/category.go b/category.go index f9ba86a..7580e90 100644 --- a/category.go +++ b/category.go @@ -84,7 +84,7 @@ type FlagCategories []*FlagCategory // FlagCategory is a category containing commands. type FlagCategory struct { Name string - Flags Flags + Flags []Flag } func (f FlagCategories) Less(i, j int) bool { @@ -111,11 +111,13 @@ func (f FlagCategories) AddFlag(category string, flag Flag) FlagCategories { } // VisibleFlags returns a slice of the Flags with Hidden=false -func (c *FlagCategory) VisibleFlags() []Flag { - ret := []Flag{} - for _, flag := range c.Flags { - if !flag.GetHidden() { - ret = append(ret, flag) +func (c *FlagCategory) VisibleFlags() []VisibleFlag { + ret := []VisibleFlag{} + for _, fl := range c.Flags { + if vf, ok := fl.(VisibleFlag); ok { + if vf.IsVisible() { + ret = append(ret, vf) + } } } return ret diff --git a/flag.go b/flag.go index 60d592e..16778d7 100644 --- a/flag.go +++ b/flag.go @@ -94,8 +94,6 @@ type Flag interface { Apply(*flag.FlagSet) error Names() []string IsSet() bool - GetCategory() string - GetHidden() bool } // RequiredFlag is an interface that allows us to mark flags as required @@ -135,6 +133,14 @@ type VisibleFlag interface { IsVisible() bool } +// CategorizableFlag is an interface that allows us to potentially +// use a flag in a categorized representation. +type CategorizableFlag interface { + VisibleFlag + + GetCategory() string +} + func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { set := flag.NewFlagSet(name, flag.ContinueOnError) diff --git a/flag_bool.go b/flag_bool.go index b8e625a..ef5f634 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -19,6 +19,7 @@ type BoolFlag struct { DefaultText string Destination *bool HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -52,6 +53,11 @@ func (f *BoolFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *BoolFlag) GetCategory() string { + return f.Category +} + // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *BoolFlag) GetValue() string { diff --git a/flag_duration.go b/flag_duration.go index e8ca15e..7592180 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -19,6 +19,7 @@ type DurationFlag struct { DefaultText string Destination *time.Duration HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -52,6 +53,11 @@ func (f *DurationFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *DurationFlag) GetCategory() string { + return f.Category +} + // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *DurationFlag) GetValue() string { diff --git a/flag_float64.go b/flag_float64.go index 0ac5b43..1cda961 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -19,6 +19,7 @@ type Float64Flag struct { DefaultText string Destination *float64 HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -52,6 +53,11 @@ func (f *Float64Flag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *Float64Flag) GetCategory() string { + return f.Category +} + // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *Float64Flag) GetValue() string { diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 984f77f..d2328b6 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -85,6 +85,7 @@ type Float64SliceFlag struct { Value *Float64Slice DefaultText string HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -118,6 +119,11 @@ func (f *Float64SliceFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *Float64SliceFlag) GetCategory() string { + return f.Category +} + // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *Float64SliceFlag) GetValue() string { diff --git a/flag_generic.go b/flag_generic.go index d159507..6c973f9 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -24,6 +24,7 @@ type GenericFlag struct { Value Generic DefaultText string HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -57,6 +58,11 @@ func (f *GenericFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *GenericFlag) GetCategory() string { + return f.Category +} + // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *GenericFlag) GetValue() string { diff --git a/flag_int.go b/flag_int.go index 62c0848..7979c64 100644 --- a/flag_int.go +++ b/flag_int.go @@ -19,6 +19,7 @@ type IntFlag struct { DefaultText string Destination *int HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -52,6 +53,11 @@ func (f *IntFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *IntFlag) GetCategory() string { + return f.Category +} + // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *IntFlag) GetValue() string { diff --git a/flag_int64.go b/flag_int64.go index 2f0be7a..7083c60 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -19,6 +19,7 @@ type Int64Flag struct { DefaultText string Destination *int64 HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -52,6 +53,11 @@ func (f *Int64Flag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *Int64Flag) GetCategory() string { + return f.Category +} + // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *Int64Flag) GetValue() string { diff --git a/flag_int64_slice.go b/flag_int64_slice.go index a53b185..5f883bf 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -86,6 +86,7 @@ type Int64SliceFlag struct { Value *Int64Slice DefaultText string HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -115,10 +116,15 @@ func (f *Int64SliceFlag) TakesValue() bool { } // GetUsage returns the usage string for the flag -func (f Int64SliceFlag) GetUsage() string { +func (f *Int64SliceFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *Int64SliceFlag) GetCategory() string { + return f.Category +} + // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *Int64SliceFlag) GetValue() string { diff --git a/flag_int_slice.go b/flag_int_slice.go index 5f3bd88..1a2c679 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -97,6 +97,7 @@ type IntSliceFlag struct { Value *IntSlice DefaultText string HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -126,10 +127,15 @@ func (f *IntSliceFlag) TakesValue() bool { } // GetUsage returns the usage string for the flag -func (f IntSliceFlag) GetUsage() string { +func (f *IntSliceFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *IntSliceFlag) GetCategory() string { + return f.Category +} + // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *IntSliceFlag) GetValue() string { diff --git a/flag_path.go b/flag_path.go index 4010e84..8a90685 100644 --- a/flag_path.go +++ b/flag_path.go @@ -18,6 +18,7 @@ type PathFlag struct { DefaultText string Destination *string HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -51,6 +52,11 @@ func (f *PathFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *PathFlag) GetCategory() string { + return f.Category +} + // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *PathFlag) GetValue() string { diff --git a/flag_string.go b/flag_string.go index cd3c7df..7d904a0 100644 --- a/flag_string.go +++ b/flag_string.go @@ -19,6 +19,7 @@ type StringFlag struct { DefaultText string Destination *string HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -52,6 +53,11 @@ func (f *StringFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *StringFlag) GetCategory() string { + return f.Category +} + // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *StringFlag) GetValue() string { diff --git a/flag_string_slice.go b/flag_string_slice.go index 1664247..a280505 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -82,6 +82,7 @@ type StringSliceFlag struct { DefaultText string HasBeenSet bool Destination *StringSlice + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -115,6 +116,11 @@ func (f *StringSliceFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *StringSliceFlag) GetCategory() string { + return f.Category +} + // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *StringSliceFlag) GetValue() string { diff --git a/flag_timestamp.go b/flag_timestamp.go index ed06418..14be5f8 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -72,6 +72,7 @@ type TimestampFlag struct { DefaultText string HasBeenSet bool Destination *Timestamp + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -105,6 +106,11 @@ func (f *TimestampFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *TimestampFlag) GetCategory() string { + return f.Category +} + // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *TimestampFlag) GetValue() string { diff --git a/flag_uint.go b/flag_uint.go index dd10e1c..f7efed1 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -19,6 +19,7 @@ type UintFlag struct { DefaultText string Destination *uint HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -52,6 +53,11 @@ func (f *UintFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *UintFlag) GetCategory() string { + return f.Category +} + // IsVisible returns true if the flag is not hidden, otherwise false func (f *UintFlag) IsVisible() bool { return !f.Hidden diff --git a/flag_uint64.go b/flag_uint64.go index 017db53..0b3edfe 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -19,6 +19,7 @@ type Uint64Flag struct { DefaultText string Destination *uint64 HasBeenSet bool + Category string } // IsSet returns whether or not the flag has been set through env or file @@ -52,6 +53,11 @@ func (f *Uint64Flag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *Uint64Flag) GetCategory() string { + return f.Category +} + // IsVisible returns true if the flag is not hidden, otherwise false func (f *Uint64Flag) IsVisible() bool { return !f.Hidden From e4580f0c50b5921206bb0cd474a9ba450466f3e2 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 22 Apr 2022 15:44:59 -0400 Subject: [PATCH 042/125] Extend flag categorization to top-level (global) flags --- app.go | 10 ++++++- app_test.go | 83 ++++++++++++++++++++++++++++++++--------------------- template.go | 18 +++++++++--- 3 files changed, 73 insertions(+), 38 deletions(-) diff --git a/app.go b/app.go index 65813c6..fdc4ea6 100644 --- a/app.go +++ b/app.go @@ -52,7 +52,7 @@ type App struct { HideVersion bool // categories contains the categorized commands and is populated on app startup categories CommandCategories - // Populate on app startup, only gettable through method Categories() + // flagCategories contains the categorized flags and is populated on app startup flagCategories FlagCategories // An action to execute when the shell completion flag is set BashComplete BashCompleteFunc @@ -217,6 +217,14 @@ func (a *App) Setup() { } sort.Sort(a.categories.(*commandCategories)) + a.flagCategories = FlagCategories{} + for _, fl := range a.Flags { + if cf, ok := fl.(CategorizableFlag); ok { + a.flagCategories.AddFlag(cf.GetCategory(), cf) + } + } + sort.Sort(a.flagCategories) + if a.Metadata == nil { a.Metadata = make(map[string]interface{}) } diff --git a/app_test.go b/app_test.go index 76e211d..3ba1945 100644 --- a/app_test.go +++ b/app_test.go @@ -1789,20 +1789,23 @@ func TestApp_VisibleCategories(t *testing.T) { HideHelp: true, Commands: []*Command{ { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + FlagCategories: FlagCategories{}, }, { - Name: "command2", - Category: "2", - HelpName: "foo command2", + Name: "command2", + Category: "2", + HelpName: "foo command2", + FlagCategories: FlagCategories{}, }, { - Name: "command3", - Category: "3", - HelpName: "foo command3", + Name: "command3", + Category: "3", + HelpName: "foo command3", + FlagCategories: FlagCategories{}, }, }, } @@ -1830,21 +1833,24 @@ func TestApp_VisibleCategories(t *testing.T) { HideHelp: true, Commands: []*Command{ { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + FlagCategories: FlagCategories{}, }, { - Name: "command2", - Category: "2", - HelpName: "foo command2", - Hidden: true, + Name: "command2", + Category: "2", + HelpName: "foo command2", + Hidden: true, + FlagCategories: FlagCategories{}, }, { - Name: "command3", - Category: "3", - HelpName: "foo command3", + Name: "command3", + Category: "3", + HelpName: "foo command3", + FlagCategories: FlagCategories{}, }, }, } @@ -1866,22 +1872,25 @@ func TestApp_VisibleCategories(t *testing.T) { HideHelp: true, Commands: []*Command{ { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + FlagCategories: FlagCategories{}, }, { - Name: "command2", - Category: "2", - HelpName: "foo command2", - Hidden: true, + Name: "command2", + Category: "2", + HelpName: "foo command2", + Hidden: true, + FlagCategories: FlagCategories{}, }, { - Name: "command3", - Category: "3", - HelpName: "foo command3", - Hidden: true, + Name: "command3", + Category: "3", + HelpName: "foo command3", + Hidden: true, + FlagCategories: FlagCategories{}, }, }, } @@ -1890,6 +1899,14 @@ func TestApp_VisibleCategories(t *testing.T) { expect(t, []CommandCategory{}, app.VisibleCategories()) } +func TestApp_VisibleFlagCategories(t *testing.T) { + app := &App{} + vfc := app.VisibleFlagCategories() + if len(vfc) != 0 { + t.Errorf("unexpected visible flag categories %+v", vfc) + } +} + func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { app := &App{ Action: func(c *Context) error { return nil }, diff --git a/template.go b/template.go index 39fa4db..8b7ea63 100644 --- a/template.go +++ b/template.go @@ -22,11 +22,16 @@ AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{range .VisibleCommands}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} + +GLOBAL OPTIONS:{{range .VisibleFlagCategories}} + {{.Name}} + {{range .VisibleFlags}}{{.}} + {{end}}{{end}}{{else}}{{if .VisibleFlags}} GLOBAL OPTIONS: {{range $index, $option := .VisibleFlags}}{{if $index}} - {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}} + {{end}}{{$option}}{{end}}{{end}}{{end}}{{if .Copyright}} COPYRIGHT: {{.Copyright}}{{end}} @@ -45,11 +50,16 @@ CATEGORY: {{.Category}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlags}} + {{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlagCategories}} + +OPTIONS:{{range .VisibleFlagCategories}} + {{.Name}} + {{range .VisibleFlags}}{{.}} + {{end}}{{end}}{{else}}{{if .VisibleFlags}} OPTIONS: {{range .VisibleFlags}}{{.}} - {{end}}{{end}} + {{end}}{{end}}{{end}} ` // SubcommandHelpTemplate is the text template for the subcommand help topic. From 156d47e6964c769561edf7946aa168be6d4d4661 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sun, 28 Mar 2021 08:18:46 -0400 Subject: [PATCH 043/125] Fix: (issue#1254) short options handling needs to proceed from last terminated error --- flag_test.go | 31 +++++++++++++++++++++++++++++++ parse.go | 5 +++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/flag_test.go b/flag_test.go index c563d6f..f662fee 100644 --- a/flag_test.go +++ b/flag_test.go @@ -2059,3 +2059,34 @@ func TestTimestampFlagApply_WithDestination(t *testing.T) { expect(t, err, nil) expect(t, *fl.Destination.timestamp, expectedResult) } + +// Test issue #1254 +// StringSlice() with UseShortOptionHandling causes duplicated entries, depending on the ordering of the flags +func TestSliceShortOptionHandle(t *testing.T) { + _ = (&App{ + Commands: []*Command{ + { + Name: "foobar", + UseShortOptionHandling: true, + Action: func(ctx *Context) error { + if ctx.Bool("i") != true { + t.Errorf("bool i not set") + } + if ctx.Bool("t") != true { + t.Errorf("bool i not set") + } + ss := ctx.StringSlice("net") + if !reflect.DeepEqual(ss, []string{"foo"}) { + t.Errorf("Got different slice(%v) than expected", ss) + } + return nil + }, + Flags: []Flag{ + &StringSliceFlag{Name: "net"}, + &BoolFlag{Name: "i"}, + &BoolFlag{Name: "t"}, + }, + }, + }, + }).Run([]string{"run", "foobar", "--net=foo", "-it"}) +} diff --git a/parse.go b/parse.go index 7df1729..57df48b 100644 --- a/parse.go +++ b/parse.go @@ -46,8 +46,9 @@ func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComple return err } - // swap current argument with the split version - args = append(args[:i], append(shortOpts, args[i+1:]...)...) + // Start processing only from failed argument and not + // from beginning + args = append(shortOpts, args[i+1:]...) argsWereSplit = true break } From dd7065671f833817d58c8f4b9a4c0f46cc4e51c7 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Wed, 28 Apr 2021 20:44:02 -0400 Subject: [PATCH 044/125] Rebase from master --- parse.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/parse.go b/parse.go index 57df48b..7df1729 100644 --- a/parse.go +++ b/parse.go @@ -46,9 +46,8 @@ func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComple return err } - // Start processing only from failed argument and not - // from beginning - args = append(shortOpts, args[i+1:]...) + // swap current argument with the split version + args = append(args[:i], append(shortOpts, args[i+1:]...)...) argsWereSplit = true break } From 8c5f1fb359a8f7c2995e823b7248a0ba0b46149f Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sat, 23 Apr 2022 19:30:34 -0400 Subject: [PATCH 045/125] Changes from code review --- flag_test.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/flag_test.go b/flag_test.go index f662fee..ecb7847 100644 --- a/flag_test.go +++ b/flag_test.go @@ -2063,17 +2063,19 @@ func TestTimestampFlagApply_WithDestination(t *testing.T) { // Test issue #1254 // StringSlice() with UseShortOptionHandling causes duplicated entries, depending on the ordering of the flags func TestSliceShortOptionHandle(t *testing.T) { - _ = (&App{ + wasCalled := false + err := (&App{ Commands: []*Command{ { Name: "foobar", UseShortOptionHandling: true, Action: func(ctx *Context) error { + wasCalled = true if ctx.Bool("i") != true { - t.Errorf("bool i not set") + t.Error("bool i not set") } if ctx.Bool("t") != true { - t.Errorf("bool i not set") + t.Error("bool i not set") } ss := ctx.StringSlice("net") if !reflect.DeepEqual(ss, []string{"foo"}) { @@ -2089,4 +2091,10 @@ func TestSliceShortOptionHandle(t *testing.T) { }, }, }).Run([]string{"run", "foobar", "--net=foo", "-it"}) + if err != nil { + t.Fatal(err) + } + if !wasCalled { + t.Fatal("Action callback was never called") + } } From 9ce946162f52dff23413c1cb98d817962cf0eb9d Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 23 Apr 2022 22:07:16 -0400 Subject: [PATCH 046/125] Unshadow `context` package and consistently name `*cli.Context` vars and method receivers `cCtx` --- altsrc/flag.go | 54 ++++++++++---------- altsrc/json_source_context.go | 6 +-- altsrc/toml_file_loader.go | 8 +-- altsrc/yaml_file_loader.go | 8 +-- app.go | 90 +++++++++++++++++----------------- app_test.go | 8 +-- command.go | 36 +++++++------- command_test.go | 6 +-- context.go | 56 ++++++++++----------- flag_bool.go | 4 +- flag_duration.go | 4 +- flag_float64.go | 4 +- flag_float64_slice.go | 4 +- flag_generic.go | 4 +- flag_int.go | 4 +- flag_int64.go | 4 +- flag_int64_slice.go | 4 +- flag_int_slice.go | 4 +- flag_path.go | 4 +- flag_string.go | 4 +- flag_string_slice.go | 4 +- flag_timestamp.go | 4 +- flag_uint.go | 4 +- flag_uint64.go | 4 +- funcs.go | 4 +- help.go | 92 +++++++++++++++++------------------ 26 files changed, 214 insertions(+), 214 deletions(-) diff --git a/altsrc/flag.go b/altsrc/flag.go index 31b8a04..4b5588a 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -13,18 +13,18 @@ import ( // allows a value to be set on the existing parsed flags. type FlagInputSourceExtension interface { cli.Flag - ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error } // ApplyInputSourceValues iterates over all provided flags and // executes ApplyInputSourceValue on flags implementing the // FlagInputSourceExtension interface to initialize these flags // to an alternate input source. -func ApplyInputSourceValues(context *cli.Context, inputSourceContext InputSourceContext, flags []cli.Flag) error { +func ApplyInputSourceValues(cCtx *cli.Context, inputSourceContext InputSourceContext, flags []cli.Flag) error { for _, f := range flags { inputSourceExtendedFlag, isType := f.(FlagInputSourceExtension) if isType { - err := inputSourceExtendedFlag.ApplyInputSourceValue(context, inputSourceContext) + err := inputSourceExtendedFlag.ApplyInputSourceValue(cCtx, inputSourceContext) if err != nil { return err } @@ -38,34 +38,34 @@ func ApplyInputSourceValues(context *cli.Context, inputSourceContext InputSource // input source based on the func provided. If there is no error it will then apply the new input source to any flags // that are supported by the input source func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceContext, error)) cli.BeforeFunc { - return func(context *cli.Context) error { + return func(cCtx *cli.Context) error { inputSource, err := createInputSource() if err != nil { return fmt.Errorf("Unable to create input source: inner error: \n'%v'", err.Error()) } - return ApplyInputSourceValues(context, inputSource, flags) + return ApplyInputSourceValues(cCtx, inputSource, flags) } } // InitInputSourceWithContext is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new // input source based on the func provided with potentially using existing cli.Context values to initialize itself. If there is // no error it will then apply the new input source to any flags that are supported by the input source -func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context *cli.Context) (InputSourceContext, error)) cli.BeforeFunc { - return func(context *cli.Context) error { - inputSource, err := createInputSource(context) +func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(cCtx *cli.Context) (InputSourceContext, error)) cli.BeforeFunc { + return func(cCtx *cli.Context) error { + inputSource, err := createInputSource(cCtx) if err != nil { return fmt.Errorf("Unable to create input source with context: inner error: \n'%v'", err.Error()) } - return ApplyInputSourceValues(context, inputSource, flags) + return ApplyInputSourceValues(cCtx, inputSource, flags) } } // ApplyInputSourceValue applies a generic value to the flagSet if required -func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { +func (f *GenericFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { + if !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { value, err := isc.Generic(f.GenericFlag.Name) if err != nil { return err @@ -82,9 +82,9 @@ func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourc } // ApplyInputSourceValue applies a StringSlice value to the flagSet if required -func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { +func (f *StringSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { + if !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { value, err := isc.StringSlice(f.StringSliceFlag.Name) if err != nil { return err @@ -104,9 +104,9 @@ func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputS } // ApplyInputSourceValue applies a IntSlice value if required -func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { +func (f *IntSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { + if !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { value, err := isc.IntSlice(f.IntSliceFlag.Name) if err != nil { return err @@ -126,9 +126,9 @@ func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSour } // ApplyInputSourceValue applies a Bool value to the flagSet if required -func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { +func (f *BoolFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { + if !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { value, err := isc.Bool(f.BoolFlag.Name) if err != nil { return err @@ -144,9 +144,9 @@ func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceCo } // ApplyInputSourceValue applies a String value to the flagSet if required -func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { +func (f *StringFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { + if !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { value, err := isc.String(f.StringFlag.Name) if err != nil { return err @@ -162,9 +162,9 @@ func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSource } // ApplyInputSourceValue applies a Path value to the flagSet if required -func (f *PathFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { +func (f *PathFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { + if !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { value, err := isc.String(f.PathFlag.Name) if err != nil { return err @@ -190,9 +190,9 @@ func (f *PathFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceCo } // ApplyInputSourceValue applies a int value to the flagSet if required -func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { +func (f *IntFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { + if !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { value, err := isc.Int(f.IntFlag.Name) if err != nil { return err @@ -208,9 +208,9 @@ func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceCon } // ApplyInputSourceValue applies a Duration value to the flagSet if required -func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { +func (f *DurationFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { + if !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { value, err := isc.Duration(f.DurationFlag.Name) if err != nil { return err @@ -226,9 +226,9 @@ func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSour } // ApplyInputSourceValue applies a Float64 value to the flagSet if required -func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { +func (f *Float64Flag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { if f.set != nil { - if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { + if !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { value, err := isc.Float64(f.Float64Flag.Name) if err != nil { return err diff --git a/altsrc/json_source_context.go b/altsrc/json_source_context.go index 6e7bf11..9307a35 100644 --- a/altsrc/json_source_context.go +++ b/altsrc/json_source_context.go @@ -16,9 +16,9 @@ import ( // variables from a file containing JSON data with the file name defined // by the given flag. func NewJSONSourceFromFlagFunc(flag string) func(c *cli.Context) (InputSourceContext, error) { - return func(context *cli.Context) (InputSourceContext, error) { - if context.IsSet(flag) { - return NewJSONSourceFromFile(context.String(flag)) + return func(cCtx *cli.Context) (InputSourceContext, error) { + if cCtx.IsSet(flag) { + return NewJSONSourceFromFile(cCtx.String(flag)) } return defaultInputSource() diff --git a/altsrc/toml_file_loader.go b/altsrc/toml_file_loader.go index 9b86ee1..dfc9b7b 100644 --- a/altsrc/toml_file_loader.go +++ b/altsrc/toml_file_loader.go @@ -85,10 +85,10 @@ func NewTomlSourceFromFile(file string) (InputSourceContext, error) { } // NewTomlSourceFromFlagFunc creates a new TOML InputSourceContext from a provided flag name and source context. -func NewTomlSourceFromFlagFunc(flagFileName string) func(context *cli.Context) (InputSourceContext, error) { - return func(context *cli.Context) (InputSourceContext, error) { - if context.IsSet(flagFileName) { - filePath := context.String(flagFileName) +func NewTomlSourceFromFlagFunc(flagFileName string) func(cCtx *cli.Context) (InputSourceContext, error) { + return func(cCtx *cli.Context) (InputSourceContext, error) { + if cCtx.IsSet(flagFileName) { + filePath := cCtx.String(flagFileName) return NewTomlSourceFromFile(filePath) } diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index a49df56..4ace1f2 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -31,10 +31,10 @@ func NewYamlSourceFromFile(file string) (InputSourceContext, error) { } // NewYamlSourceFromFlagFunc creates a new Yaml InputSourceContext from a provided flag name and source context. -func NewYamlSourceFromFlagFunc(flagFileName string) func(context *cli.Context) (InputSourceContext, error) { - return func(context *cli.Context) (InputSourceContext, error) { - if context.IsSet(flagFileName) { - filePath := context.String(flagFileName) +func NewYamlSourceFromFlagFunc(flagFileName string) func(cCtx *cli.Context) (InputSourceContext, error) { + return func(cCtx *cli.Context) (InputSourceContext, error) { + if cCtx.IsSet(flagFileName) { + filePath := cCtx.String(flagFileName) return NewYamlSourceFromFile(filePath) } diff --git a/app.go b/app.go index 6abcc08..52ef1b0 100644 --- a/app.go +++ b/app.go @@ -245,48 +245,48 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { err = parseIter(set, a, arguments[1:], shellComplete) nerr := normalizeFlags(a.Flags, set) - context := NewContext(a, set, &Context{Context: ctx}) + cCtx := NewContext(a, set, &Context{Context: ctx}) if nerr != nil { _, _ = fmt.Fprintln(a.Writer, nerr) - _ = ShowAppHelp(context) + _ = ShowAppHelp(cCtx) return nerr } - context.shellComplete = shellComplete + cCtx.shellComplete = shellComplete - if checkCompletions(context) { + if checkCompletions(cCtx) { return nil } if err != nil { if a.OnUsageError != nil { - err := a.OnUsageError(context, err, false) - a.handleExitCoder(context, err) + err := a.OnUsageError(cCtx, err, false) + a.handleExitCoder(cCtx, err) return err } _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) - _ = ShowAppHelp(context) + _ = ShowAppHelp(cCtx) return err } - if !a.HideHelp && checkHelp(context) { - _ = ShowAppHelp(context) + if !a.HideHelp && checkHelp(cCtx) { + _ = ShowAppHelp(cCtx) return nil } - if !a.HideVersion && checkVersion(context) { - ShowVersion(context) + if !a.HideVersion && checkVersion(cCtx) { + ShowVersion(cCtx) return nil } - cerr := context.checkRequiredFlags(a.Flags) + cerr := cCtx.checkRequiredFlags(a.Flags) if cerr != nil { - _ = ShowAppHelp(context) + _ = ShowAppHelp(cCtx) return cerr } if a.After != nil { defer func() { - if afterErr := a.After(context); afterErr != nil { + if afterErr := a.After(cCtx); afterErr != nil { if err != nil { err = newMultiError(err, afterErr) } else { @@ -297,20 +297,20 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { } if a.Before != nil { - beforeErr := a.Before(context) + beforeErr := a.Before(cCtx) if beforeErr != nil { - a.handleExitCoder(context, beforeErr) + a.handleExitCoder(cCtx, beforeErr) err = beforeErr return err } } - args := context.Args() + args := cCtx.Args() if args.Present() { name := args.First() c := a.Command(name) if c != nil { - return c.Run(context) + return c.Run(cCtx) } } @@ -319,9 +319,9 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { } // Run default Action - err = a.Action(context) + err = a.Action(cCtx) - a.handleExitCoder(context, err) + a.handleExitCoder(cCtx, err) return err } @@ -359,55 +359,55 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { err = parseIter(set, a, ctx.Args().Tail(), ctx.shellComplete) nerr := normalizeFlags(a.Flags, set) - context := NewContext(a, set, ctx) + cCtx := NewContext(a, set, ctx) if nerr != nil { _, _ = fmt.Fprintln(a.Writer, nerr) _, _ = fmt.Fprintln(a.Writer) if len(a.Commands) > 0 { - _ = ShowSubcommandHelp(context) + _ = ShowSubcommandHelp(cCtx) } else { - _ = ShowCommandHelp(ctx, context.Args().First()) + _ = ShowCommandHelp(ctx, cCtx.Args().First()) } return nerr } - if checkCompletions(context) { + if checkCompletions(cCtx) { return nil } if err != nil { if a.OnUsageError != nil { - err = a.OnUsageError(context, err, true) - a.handleExitCoder(context, err) + err = a.OnUsageError(cCtx, err, true) + a.handleExitCoder(cCtx, err) return err } _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) - _ = ShowSubcommandHelp(context) + _ = ShowSubcommandHelp(cCtx) return err } if len(a.Commands) > 0 { - if checkSubcommandHelp(context) { + if checkSubcommandHelp(cCtx) { return nil } } else { - if checkCommandHelp(ctx, context.Args().First()) { + if checkCommandHelp(ctx, cCtx.Args().First()) { return nil } } - cerr := context.checkRequiredFlags(a.Flags) + cerr := cCtx.checkRequiredFlags(a.Flags) if cerr != nil { - _ = ShowSubcommandHelp(context) + _ = ShowSubcommandHelp(cCtx) return cerr } if a.After != nil { defer func() { - afterErr := a.After(context) + afterErr := a.After(cCtx) if afterErr != nil { - a.handleExitCoder(context, err) + a.handleExitCoder(cCtx, err) if err != nil { err = newMultiError(err, afterErr) } else { @@ -418,27 +418,27 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } if a.Before != nil { - beforeErr := a.Before(context) + beforeErr := a.Before(cCtx) if beforeErr != nil { - a.handleExitCoder(context, beforeErr) + a.handleExitCoder(cCtx, beforeErr) err = beforeErr return err } } - args := context.Args() + args := cCtx.Args() if args.Present() { name := args.First() c := a.Command(name) if c != nil { - return c.Run(context) + return c.Run(cCtx) } } // Run default Action - err = a.Action(context) + err = a.Action(cCtx) - a.handleExitCoder(context, err) + a.handleExitCoder(cCtx, err) return err } @@ -498,9 +498,9 @@ func (a *App) appendCommand(c *Command) { } } -func (a *App) handleExitCoder(context *Context, err error) { +func (a *App) handleExitCoder(cCtx *Context, err error) { if a.ExitErrHandler != nil { - a.ExitErrHandler(context, err) + a.ExitErrHandler(cCtx, err) } else { HandleExitCoder(err) } @@ -525,14 +525,14 @@ func (a *Author) String() string { // HandleAction attempts to figure out which Action signature was used. If // it's an ActionFunc or a func with the legacy signature for Action, the func // is run! -func HandleAction(action interface{}, context *Context) (err error) { +func HandleAction(action interface{}, cCtx *Context) (err error) { switch a := action.(type) { case ActionFunc: - return a(context) + return a(cCtx) case func(*Context) error: - return a(context) + return a(cCtx) case func(*Context): // deprecated function signature - a(context) + a(cCtx) return nil } diff --git a/app_test.go b/app_test.go index 76e211d..0829ac6 100644 --- a/app_test.go +++ b/app_test.go @@ -445,14 +445,14 @@ func TestApp_Setup_defaultsWriter(t *testing.T) { } func TestApp_RunAsSubcommandParseFlags(t *testing.T) { - var context *Context + var cCtx *Context a := &App{ Commands: []*Command{ { Name: "foo", Action: func(c *Context) error { - context = c + cCtx = c return nil }, Flags: []Flag{ @@ -468,8 +468,8 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) { } _ = a.Run([]string{"", "foo", "--lang", "spanish", "abcd"}) - expect(t, context.Args().Get(0), "abcd") - expect(t, context.String("lang"), "spanish") + expect(t, cCtx.Args().Get(0), "abcd") + expect(t, cCtx.String("lang"), "spanish") } func TestApp_RunAsSubCommandIncorrectUsage(t *testing.T) { diff --git a/command.go b/command.go index 3477686..ba0169b 100644 --- a/command.go +++ b/command.go @@ -105,39 +105,39 @@ func (c *Command) Run(ctx *Context) (err error) { set, err := c.parseFlags(ctx.Args(), ctx.shellComplete) - context := NewContext(ctx.App, set, ctx) - context.Command = c - if checkCommandCompletions(context, c.Name) { + cCtx := NewContext(ctx.App, set, ctx) + cCtx.Command = c + if checkCommandCompletions(cCtx, c.Name) { return nil } if err != nil { if c.OnUsageError != nil { - err = c.OnUsageError(context, err, false) - context.App.handleExitCoder(context, err) + err = c.OnUsageError(cCtx, err, false) + cCtx.App.handleExitCoder(cCtx, err) return err } - _, _ = fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error()) - _, _ = fmt.Fprintln(context.App.Writer) - _ = ShowCommandHelp(context, c.Name) + _, _ = fmt.Fprintln(cCtx.App.Writer, "Incorrect Usage:", err.Error()) + _, _ = fmt.Fprintln(cCtx.App.Writer) + _ = ShowCommandHelp(cCtx, c.Name) return err } - if checkCommandHelp(context, c.Name) { + if checkCommandHelp(cCtx, c.Name) { return nil } - cerr := context.checkRequiredFlags(c.Flags) + cerr := cCtx.checkRequiredFlags(c.Flags) if cerr != nil { - _ = ShowCommandHelp(context, c.Name) + _ = ShowCommandHelp(cCtx, c.Name) return cerr } if c.After != nil { defer func() { - afterErr := c.After(context) + afterErr := c.After(cCtx) if afterErr != nil { - context.App.handleExitCoder(context, err) + cCtx.App.handleExitCoder(cCtx, err) if err != nil { err = newMultiError(err, afterErr) } else { @@ -148,9 +148,9 @@ func (c *Command) Run(ctx *Context) (err error) { } if c.Before != nil { - err = c.Before(context) + err = c.Before(cCtx) if err != nil { - context.App.handleExitCoder(context, err) + cCtx.App.handleExitCoder(cCtx, err) return err } } @@ -159,11 +159,11 @@ func (c *Command) Run(ctx *Context) (err error) { c.Action = helpSubcommand.Action } - context.Command = c - err = c.Action(context) + cCtx.Command = c + err = c.Action(cCtx) if err != nil { - context.App.handleExitCoder(context, err) + cCtx.App.handleExitCoder(cCtx, err) } return err } diff --git a/command_test.go b/command_test.go index 6add442..9dfd46f 100644 --- a/command_test.go +++ b/command_test.go @@ -30,7 +30,7 @@ func TestCommandFlagParsing(t *testing.T) { set := flag.NewFlagSet("test", 0) _ = set.Parse(c.testArgs) - context := NewContext(app, set, nil) + cCtx := NewContext(app, set, nil) command := Command{ Name: "test-cmd", @@ -41,10 +41,10 @@ func TestCommandFlagParsing(t *testing.T) { SkipFlagParsing: c.skipFlagParsing, } - err := command.Run(context) + err := command.Run(cCtx) expect(t, err, c.expectedErr) - expect(t, context.Args().Slice(), c.testArgs) + expect(t, cCtx.Args().Slice(), c.testArgs) } } diff --git a/context.go b/context.go index da090e8..6b497ed 100644 --- a/context.go +++ b/context.go @@ -40,18 +40,18 @@ func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { } // NumFlags returns the number of flags set -func (c *Context) NumFlags() int { - return c.flagSet.NFlag() +func (cCtx *Context) NumFlags() int { + return cCtx.flagSet.NFlag() } // Set sets a context flag to a value. -func (c *Context) Set(name, value string) error { - return c.flagSet.Set(name, value) +func (cCtx *Context) Set(name, value string) error { + return cCtx.flagSet.Set(name, value) } // IsSet determines if the flag was actually set -func (c *Context) IsSet(name string) bool { - if fs := c.lookupFlagSet(name); fs != nil { +func (cCtx *Context) IsSet(name string) bool { + if fs := cCtx.lookupFlagSet(name); fs != nil { isSet := false fs.Visit(func(f *flag.Flag) { if f.Name == name { @@ -62,7 +62,7 @@ func (c *Context) IsSet(name string) bool { return true } - f := c.lookupFlag(name) + f := cCtx.lookupFlag(name) if f == nil { return false } @@ -74,28 +74,28 @@ func (c *Context) IsSet(name string) bool { } // LocalFlagNames returns a slice of flag names used in this context. -func (c *Context) LocalFlagNames() []string { +func (cCtx *Context) LocalFlagNames() []string { var names []string - c.flagSet.Visit(makeFlagNameVisitor(&names)) + cCtx.flagSet.Visit(makeFlagNameVisitor(&names)) return names } // FlagNames returns a slice of flag names used by the this context and all of // its parent contexts. -func (c *Context) FlagNames() []string { +func (cCtx *Context) FlagNames() []string { var names []string - for _, ctx := range c.Lineage() { - ctx.flagSet.Visit(makeFlagNameVisitor(&names)) + for _, pCtx := range cCtx.Lineage() { + pCtx.flagSet.Visit(makeFlagNameVisitor(&names)) } return names } // Lineage returns *this* context and all of its ancestor contexts in order from // child to parent -func (c *Context) Lineage() []*Context { +func (cCtx *Context) Lineage() []*Context { var lineage []*Context - for cur := c; cur != nil; cur = cur.parentContext { + for cur := cCtx; cur != nil; cur = cur.parentContext { lineage = append(lineage, cur) } @@ -103,26 +103,26 @@ func (c *Context) Lineage() []*Context { } // Value returns the value of the flag corresponding to `name` -func (c *Context) Value(name string) interface{} { - if fs := c.lookupFlagSet(name); fs != nil { +func (cCtx *Context) Value(name string) interface{} { + if fs := cCtx.lookupFlagSet(name); fs != nil { return fs.Lookup(name).Value.(flag.Getter).Get() } return nil } // Args returns the command line arguments associated with the context. -func (c *Context) Args() Args { - ret := args(c.flagSet.Args()) +func (cCtx *Context) Args() Args { + ret := args(cCtx.flagSet.Args()) return &ret } // NArg returns the number of the command line arguments. -func (c *Context) NArg() int { - return c.Args().Len() +func (cCtx *Context) NArg() int { + return cCtx.Args().Len() } -func (ctx *Context) lookupFlag(name string) Flag { - for _, c := range ctx.Lineage() { +func (cCtx *Context) lookupFlag(name string) Flag { + for _, c := range cCtx.Lineage() { if c.Command == nil { continue } @@ -136,8 +136,8 @@ func (ctx *Context) lookupFlag(name string) Flag { } } - if ctx.App != nil { - for _, f := range ctx.App.Flags { + if cCtx.App != nil { + for _, f := range cCtx.App.Flags { for _, n := range f.Names() { if n == name { return f @@ -149,8 +149,8 @@ func (ctx *Context) lookupFlag(name string) Flag { return nil } -func (ctx *Context) lookupFlagSet(name string) *flag.FlagSet { - for _, c := range ctx.Lineage() { +func (cCtx *Context) lookupFlagSet(name string) *flag.FlagSet { + for _, c := range cCtx.Lineage() { if c.flagSet == nil { continue } @@ -162,7 +162,7 @@ func (ctx *Context) lookupFlagSet(name string) *flag.FlagSet { return nil } -func (context *Context) checkRequiredFlags(flags []Flag) requiredFlagsErr { +func (cCtx *Context) checkRequiredFlags(flags []Flag) requiredFlagsErr { var missingFlags []string for _, f := range flags { if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() { @@ -174,7 +174,7 @@ func (context *Context) checkRequiredFlags(flags []Flag) requiredFlagsErr { flagName = key } - if context.IsSet(strings.TrimSpace(key)) { + if cCtx.IsSet(strings.TrimSpace(key)) { flagPresent = true } } diff --git a/flag_bool.go b/flag_bool.go index b8e625a..6e832f9 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -104,8 +104,8 @@ 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 := c.lookupFlagSet(name); fs != nil { +func (cCtx *Context) Bool(name string) bool { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupBool(name, fs) } return false diff --git a/flag_duration.go b/flag_duration.go index e8ca15e..d30bd1d 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -103,8 +103,8 @@ 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 := c.lookupFlagSet(name); fs != nil { +func (cCtx *Context) Duration(name string) time.Duration { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupDuration(name, fs) } return 0 diff --git a/flag_float64.go b/flag_float64.go index 0ac5b43..f11bef3 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -103,8 +103,8 @@ 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 := c.lookupFlagSet(name); fs != nil { +func (cCtx *Context) Float64(name string) float64 { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupFloat64(name, fs) } return 0 diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 984f77f..d573ae5 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -177,8 +177,8 @@ 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 := c.lookupFlagSet(name); fs != nil { +func (cCtx *Context) Float64Slice(name string) []float64 { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupFloat64Slice(name, fs) } return nil diff --git a/flag_generic.go b/flag_generic.go index d159507..e44e7d7 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -106,8 +106,8 @@ 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 := c.lookupFlagSet(name); fs != nil { +func (cCtx *Context) Generic(name string) interface{} { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupGeneric(name, fs) } return nil diff --git a/flag_int.go b/flag_int.go index 62c0848..2c4e602 100644 --- a/flag_int.go +++ b/flag_int.go @@ -104,8 +104,8 @@ 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 := c.lookupFlagSet(name); fs != nil { +func (cCtx *Context) Int(name string) int { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupInt(name, fs) } return 0 diff --git a/flag_int64.go b/flag_int64.go index 2f0be7a..f21e7e4 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -103,8 +103,8 @@ 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 := c.lookupFlagSet(name); fs != nil { +func (cCtx *Context) Int64(name string) int64 { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupInt64(name, fs) } return 0 diff --git a/flag_int64_slice.go b/flag_int64_slice.go index a53b185..16f5665 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -176,8 +176,8 @@ 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 { - if fs := c.lookupFlagSet(name); fs != nil { +func (cCtx *Context) Int64Slice(name string) []int64 { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupInt64Slice(name, fs) } return nil diff --git a/flag_int_slice.go b/flag_int_slice.go index 5f3bd88..b003229 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -187,8 +187,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 := c.lookupFlagSet(name); fs != nil { +func (cCtx *Context) IntSlice(name string) []int { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupIntSlice(name, fs) } return nil diff --git a/flag_path.go b/flag_path.go index 4010e84..de19eea 100644 --- a/flag_path.go +++ b/flag_path.go @@ -98,8 +98,8 @@ 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 := c.lookupFlagSet(name); fs != nil { +func (cCtx *Context) Path(name string) string { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupPath(name, fs) } diff --git a/flag_string.go b/flag_string.go index cd3c7df..936ce72 100644 --- a/flag_string.go +++ b/flag_string.go @@ -99,8 +99,8 @@ 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 := c.lookupFlagSet(name); fs != nil { +func (cCtx *Context) String(name string) string { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupString(name, fs) } return "" diff --git a/flag_string_slice.go b/flag_string_slice.go index 1664247..3879235 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -188,8 +188,8 @@ 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 := c.lookupFlagSet(name); fs != nil { +func (cCtx *Context) StringSlice(name string) []string { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupStringSlice(name, fs) } return nil diff --git a/flag_timestamp.go b/flag_timestamp.go index ed06418..dcb9d84 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -165,8 +165,8 @@ 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 := c.lookupFlagSet(name); fs != nil { +func (cCtx *Context) Timestamp(name string) *time.Time { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupTimestamp(name, fs) } return nil diff --git a/flag_uint.go b/flag_uint.go index dd10e1c..e9ce9fe 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -103,8 +103,8 @@ func (f *UintFlag) GetEnvVars() []string { // Uint looks up the value of a local UintFlag, returns // 0 if not found -func (c *Context) Uint(name string) uint { - if fs := c.lookupFlagSet(name); fs != nil { +func (cCtx *Context) Uint(name string) uint { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupUint(name, fs) } return 0 diff --git a/flag_uint64.go b/flag_uint64.go index 017db53..2a2669f 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -103,8 +103,8 @@ func (f *Uint64Flag) GetEnvVars() []string { // Uint64 looks up the value of a local Uint64Flag, returns // 0 if not found -func (c *Context) Uint64(name string) uint64 { - if fs := c.lookupFlagSet(name); fs != nil { +func (cCtx *Context) Uint64(name string) uint64 { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupUint64(name, fs) } return 0 diff --git a/funcs.go b/funcs.go index 842b4aa..0a9b22c 100644 --- a/funcs.go +++ b/funcs.go @@ -21,11 +21,11 @@ type CommandNotFoundFunc func(*Context, string) // 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. -type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error +type OnUsageErrorFunc func(cCtx *Context, err error, isSubcommand bool) error // ExitErrHandlerFunc is executed if provided in order to handle exitError values // returned by Actions and Before/After functions. -type ExitErrHandlerFunc func(context *Context, err error) +type ExitErrHandlerFunc func(cCtx *Context, err error) // FlagStringFunc is used by the help generation to display a flag, which is // expected to be a single line. diff --git a/help.go b/help.go index fad990a..2f8156f 100644 --- a/help.go +++ b/help.go @@ -15,13 +15,13 @@ var helpCommand = &Command{ Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", - Action: func(c *Context) error { - args := c.Args() + Action: func(cCtx *Context) error { + args := cCtx.Args() if args.Present() { - return ShowCommandHelp(c, args.First()) + return ShowCommandHelp(cCtx, args.First()) } - _ = ShowAppHelp(c) + _ = ShowAppHelp(cCtx) return nil }, } @@ -31,13 +31,13 @@ var helpSubcommand = &Command{ Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", - Action: func(c *Context) error { - args := c.Args() + Action: func(cCtx *Context) error { + args := cCtx.Args() if args.Present() { - return ShowCommandHelp(c, args.First()) + return ShowCommandHelp(cCtx, args.First()) } - return ShowSubcommandHelp(c) + return ShowSubcommandHelp(cCtx) }, } @@ -71,30 +71,30 @@ func ShowAppHelpAndExit(c *Context, exitCode int) { } // ShowAppHelp is an action that displays the help. -func ShowAppHelp(c *Context) error { - tpl := c.App.CustomAppHelpTemplate +func ShowAppHelp(cCtx *Context) error { + tpl := cCtx.App.CustomAppHelpTemplate if tpl == "" { tpl = AppHelpTemplate } - if c.App.ExtraInfo == nil { - HelpPrinter(c.App.Writer, tpl, c.App) + if cCtx.App.ExtraInfo == nil { + HelpPrinter(cCtx.App.Writer, tpl, cCtx.App) return nil } customAppData := func() map[string]interface{} { return map[string]interface{}{ - "ExtraInfo": c.App.ExtraInfo, + "ExtraInfo": cCtx.App.ExtraInfo, } } - HelpPrinterCustom(c.App.Writer, tpl, c.App, customAppData()) + HelpPrinterCustom(cCtx.App.Writer, tpl, cCtx.App, customAppData()) return nil } // DefaultAppComplete prints the list of subcommands as the default app completion method -func DefaultAppComplete(c *Context) { - DefaultCompleteWithFlags(nil)(c) +func DefaultAppComplete(cCtx *Context) { + DefaultCompleteWithFlags(nil)(cCtx) } func printCommandSuggestions(commands []*Command, writer io.Writer) { @@ -159,30 +159,30 @@ func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) { } } -func DefaultCompleteWithFlags(cmd *Command) func(c *Context) { - return func(c *Context) { +func DefaultCompleteWithFlags(cmd *Command) func(cCtx *Context) { + return func(cCtx *Context) { if len(os.Args) > 2 { lastArg := os.Args[len(os.Args)-2] if strings.HasPrefix(lastArg, "-") { if cmd != nil { - printFlagSuggestions(lastArg, cmd.Flags, c.App.Writer) + printFlagSuggestions(lastArg, cmd.Flags, cCtx.App.Writer) return } - printFlagSuggestions(lastArg, c.App.Flags, c.App.Writer) + printFlagSuggestions(lastArg, cCtx.App.Flags, cCtx.App.Writer) return } } if cmd != nil { - printCommandSuggestions(cmd.Subcommands, c.App.Writer) + printCommandSuggestions(cmd.Subcommands, cCtx.App.Writer) return } - printCommandSuggestions(c.App.Commands, c.App.Writer) + printCommandSuggestions(cCtx.App.Commands, cCtx.App.Writer) } } @@ -228,32 +228,32 @@ func ShowSubcommandHelpAndExit(c *Context, exitCode int) { } // ShowSubcommandHelp prints help for the given subcommand -func ShowSubcommandHelp(c *Context) error { - if c == nil { +func ShowSubcommandHelp(cCtx *Context) error { + if cCtx == nil { return nil } - if c.Command != nil { - return ShowCommandHelp(c, c.Command.Name) + if cCtx.Command != nil { + return ShowCommandHelp(cCtx, cCtx.Command.Name) } - return ShowCommandHelp(c, "") + return ShowCommandHelp(cCtx, "") } // ShowVersion prints the version number of the App -func ShowVersion(c *Context) { - VersionPrinter(c) +func ShowVersion(cCtx *Context) { + VersionPrinter(cCtx) } -func printVersion(c *Context) { - _, _ = fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version) +func printVersion(cCtx *Context) { + _, _ = fmt.Fprintf(cCtx.App.Writer, "%v version %v\n", cCtx.App.Name, cCtx.App.Version) } // ShowCompletions prints the lists of commands within a given context -func ShowCompletions(c *Context) { - a := c.App +func ShowCompletions(cCtx *Context) { + a := cCtx.App if a != nil && a.BashComplete != nil { - a.BashComplete(c) + a.BashComplete(cCtx) } } @@ -304,20 +304,20 @@ func printHelp(out io.Writer, templ string, data interface{}) { HelpPrinterCustom(out, templ, data, nil) } -func checkVersion(c *Context) bool { +func checkVersion(cCtx *Context) bool { found := false for _, name := range VersionFlag.Names() { - if c.Bool(name) { + if cCtx.Bool(name) { found = true } } return found } -func checkHelp(c *Context) bool { +func checkHelp(cCtx *Context) bool { found := false for _, name := range HelpFlag.Names() { - if c.Bool(name) { + if cCtx.Bool(name) { found = true } } @@ -333,9 +333,9 @@ func checkCommandHelp(c *Context, name string) bool { return false } -func checkSubcommandHelp(c *Context) bool { - if c.Bool("h") || c.Bool("help") { - _ = ShowSubcommandHelp(c) +func checkSubcommandHelp(cCtx *Context) bool { + if cCtx.Bool("h") || cCtx.Bool("help") { + _ = ShowSubcommandHelp(cCtx) return true } @@ -357,20 +357,20 @@ func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) { return true, arguments[:pos] } -func checkCompletions(c *Context) bool { - if !c.shellComplete { +func checkCompletions(cCtx *Context) bool { + if !cCtx.shellComplete { return false } - if args := c.Args(); args.Present() { + if args := cCtx.Args(); args.Present() { name := args.First() - if cmd := c.App.Command(name); cmd != nil { + if cmd := cCtx.App.Command(name); cmd != nil { // let the command handle the completion return false } } - ShowCompletions(c) + ShowCompletions(cCtx) return true } From f1ce5c74b0c8646817acc6df26190a7ada66dfce Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 24 Apr 2022 08:10:33 -0400 Subject: [PATCH 047/125] Cleaning up some release-related metadata --- LICENSE | 2 +- README.md | 4 ++++ docs/CHANGELOG.md | 6 ++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 42a597e..2c84c78 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016 Jeremy Saenz & Contributors +Copyright (c) 2022 urfave/cli maintainers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 5b8d2b6..79791ad 100644 --- a/README.md +++ b/README.md @@ -68,3 +68,7 @@ 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/main/.github/workflows/cli.yml). + +## License + +See [`LICENSE`](./LICENSE) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index e957d94..6dfff88 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,9 @@ +> This document is no longer being actively maintained. Please see the +> [releases page](https://github.com/urfave/cli/releases) for all release notes +> and related hypermedia for releases > 1.22.5+, 2.3.0+ and more. + +--- + # Change Log **ATTN**: This project uses [semantic versioning](http://semver.org/). From de589511d80635bab63a0b822fc598aa7b56b961 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 24 Apr 2022 08:14:05 -0400 Subject: [PATCH 048/125] Nit updates to changelog admonition --- docs/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 6dfff88..6aec50b 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,6 @@ -> This document is no longer being actively maintained. Please see the +> :warning: This document is no longer being actively maintained. Please see the > [releases page](https://github.com/urfave/cli/releases) for all release notes -> and related hypermedia for releases > 1.22.5+, 2.3.0+ and more. +> and related hypermedia for releases `>= 1.22.5`, `>= 2.3.0`. --- From 11b3a30b4ac662cd3c490f11117460b6fe34e7f6 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 24 Apr 2022 09:17:59 -0400 Subject: [PATCH 049/125] Add a new document specifically for releasing --- docs/RELEASING.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 docs/RELEASING.md diff --git a/docs/RELEASING.md b/docs/RELEASING.md new file mode 100644 index 0000000..57d75a0 --- /dev/null +++ b/docs/RELEASING.md @@ -0,0 +1,61 @@ +# Releasing urfave/cli + +Releasing small batches often is [backed by +research](https://itrevolution.com/accelerate-book/) as part of the +virtuous cycles that keep teams and products healthy. + +To that end, the overall goal of the release process is to send +changes out into the world as close to the time the commits were +merged to the `main` branch as possible. In this way, the community +of humans depending on this library are able to make use of the +changes they need **quickly**, which means they should'nt have to +maintain long-lived forks of the project, which means they can get +back to focusing on the work on which they want to focus. This also +means that the @urfave/cli team should be ablo to focus on +delivering a steadily improving product with significantly eased +ability to associate bugs and regressions with specific releases. + +## Process + +- Release versions follow [semantic versioning](https://semver.org/) +- Releases are associated with **signed, annotated git tags**[^1]. +- Release notes are **automatically generated**[^2]. + +In the `main` or `v1` branch, the current version is always +available via: + +```sh +git describe --always --dirty --tags +``` + +**NOTE**: if the version reported contains `-dirty`, this is +indicative of a "dirty" work tree, which is not a great state for +creating a new release tag. Seek help from @urfave/cli teammates. + +For example, given a described version of `v2.4.7-3-g68da1cd` and a +diff of `v2.4.7...` that contains only bug fixes, the next version +should be `v2.4.8`: + +```sh +git tag -a -s -m 'Release 2.4.8' v2.4.8 +git push origin v2.4.8 +``` + +The tag push will trigger a GitHub Actions workflow. The remaining +steps require human intervention through the GitHub web view +although [automated solutions +exist](https://github.com/softprops/action-gh-release) that may be +adopted in the future. + +- Open the [the new release page](https://github.com/urfave/cli/releases/new) +- At the top of the form, click on the `Choose a tag` select control and select `v2.4.8` +- In the `Write` tab below, click the `Auto-generate release notes` button +- At the bottom of the form, click the `Publish release` button +- :white_check_mark: you're done! + +[^1]: This was not always true. There are many **lightweight git + tags** present in the repository history. + +[^2]: This was not always true. The + [`docs/CHANGELOG.md`](./CHANGELOG.md) document used to be + manually maintained. From 7f37d1d13c1652b28b1074aa944d37a8af17da71 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Fri, 2 Apr 2021 20:19:13 -0400 Subject: [PATCH 050/125] Fix(1199). Allow -ve values for int, float & duration --- altsrc/flag.go | 20 +++++++------------- altsrc/flag_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/altsrc/flag.go b/altsrc/flag.go index 4b5588a..53382b2 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -197,10 +197,8 @@ func (f *IntFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContex if err != nil { return err } - if value > 0 { - for _, name := range f.Names() { - _ = f.set.Set(name, strconv.FormatInt(int64(value), 10)) - } + for _, name := range f.Names() { + _ = f.set.Set(name, strconv.FormatInt(int64(value), 10)) } } } @@ -215,10 +213,8 @@ func (f *DurationFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceC if err != nil { return err } - if value > 0 { - for _, name := range f.Names() { - _ = f.set.Set(name, value.String()) - } + for _, name := range f.Names() { + _ = f.set.Set(name, value.String()) } } } @@ -233,11 +229,9 @@ func (f *Float64Flag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceCo if err != nil { return err } - if value > 0 { - floatStr := float64ToString(value) - for _, name := range f.Names() { - _ = f.set.Set(name, floatStr) - } + floatStr := float64ToString(value) + for _, name := range f.Names() { + _ = f.set.Set(name, floatStr) } } } diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index 2048331..e20ab02 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -234,6 +234,15 @@ func TestIntApplyInputSourceMethodSet(t *testing.T) { expect(t, 15, c.Int("test")) } +func TestIntApplyInputSourceMethodSetNegativeValue(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewIntFlag(&cli.IntFlag{Name: "test"}), + FlagName: "test", + MapValue: -1, + }) + expect(t, -1, c.Int("test")) +} + func TestIntApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ Flag: NewIntFlag(&cli.IntFlag{Name: "test"}), @@ -264,6 +273,15 @@ func TestDurationApplyInputSourceMethodSet(t *testing.T) { expect(t, 30*time.Second, c.Duration("test")) } +func TestDurationApplyInputSourceMethodSetNegativeValue(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}), + FlagName: "test", + MapValue: -30 * time.Second, + }) + expect(t, -30*time.Second, c.Duration("test")) +} + func TestDurationApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}), @@ -294,6 +312,15 @@ func TestFloat64ApplyInputSourceMethodSet(t *testing.T) { expect(t, 1.3, c.Float64("test")) } +func TestFloat64ApplyInputSourceMethodSetNegativeValue(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}), + FlagName: "test", + MapValue: -1.3, + }) + expect(t, -1.3, c.Float64("test")) +} + func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}), From d198aed170178d0d875914288926e300bdf116c1 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sun, 24 Apr 2022 12:06:28 -0400 Subject: [PATCH 051/125] Add test when flag is not set --- altsrc/flag_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index e20ab02..dcb8263 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -321,6 +321,15 @@ func TestFloat64ApplyInputSourceMethodSetNegativeValue(t *testing.T) { expect(t, -1.3, c.Float64("test")) } +func TestFloat64ApplyInputSourceMethodSetNegativeValueNotSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test1"}), + FlagName: "test1", + // dont set map value + }) + expect(t, 0.0, c.Float64("test1")) +} + func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) { c := runTest(t, testApplyInputSource{ Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}), From f6c020fa0907f08f6e44c9b2c60398f7e599e136 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 24 Apr 2022 12:59:12 -0400 Subject: [PATCH 052/125] Spelling fixes thanks to @asmaloney :bow: --- docs/RELEASING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 57d75a0..b10e4e0 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -8,10 +8,10 @@ To that end, the overall goal of the release process is to send changes out into the world as close to the time the commits were merged to the `main` branch as possible. In this way, the community of humans depending on this library are able to make use of the -changes they need **quickly**, which means they should'nt have to +changes they need **quickly**, which means they shouldn't have to maintain long-lived forks of the project, which means they can get back to focusing on the work on which they want to focus. This also -means that the @urfave/cli team should be ablo to focus on +means that the @urfave/cli team should be able to focus on delivering a steadily improving product with significantly eased ability to associate bugs and regressions with specific releases. From 835bd32714ec77ac75a7dd66f616ce94dcfc8ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Mon, 25 Apr 2022 07:59:10 +0200 Subject: [PATCH 053/125] =?UTF-8?q?rename=20flags=E2=80=99=20ValueFromCont?= =?UTF-8?q?ext()=20to=20Get()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flag_bool.go | 4 ++-- flag_duration.go | 4 ++-- flag_float64.go | 4 ++-- flag_float64_slice.go | 4 ++-- flag_generic.go | 4 ++-- flag_int.go | 4 ++-- flag_int64.go | 4 ++-- flag_int64_slice.go | 4 ++-- flag_int_slice.go | 4 ++-- flag_path.go | 4 ++-- flag_string.go | 4 ++-- flag_string_slice.go | 4 ++-- flag_test.go | 32 ++++++++++++++++---------------- flag_timestamp.go | 4 ++-- flag_uint.go | 4 ++-- flag_uint64.go | 4 ++-- 16 files changed, 46 insertions(+), 46 deletions(-) diff --git a/flag_bool.go b/flag_bool.go index bdca2b5..3caeb08 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -102,8 +102,8 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error { return nil } -// ValueFromContext returns the flag’s value in the given Context. -func (f *BoolFlag) ValueFromContext(ctx *Context) bool { +// Get returns the flag’s value in the given Context. +func (f *BoolFlag) Get(ctx *Context) bool { return ctx.Bool(f.Name) } diff --git a/flag_duration.go b/flag_duration.go index 1de10b1..3452aac 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -101,8 +101,8 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) error { return nil } -// ValueFromContext returns the flag’s value in the given Context. -func (f *DurationFlag) ValueFromContext(ctx *Context) time.Duration { +// Get returns the flag’s value in the given Context. +func (f *DurationFlag) Get(ctx *Context) time.Duration { return ctx.Duration(f.Name) } diff --git a/flag_float64.go b/flag_float64.go index f2eb040..fc1b3b9 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -101,8 +101,8 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) error { return nil } -// ValueFromContext returns the flag’s value in the given Context. -func (f *Float64Flag) ValueFromContext(ctx *Context) float64 { +// Get returns the flag’s value in the given Context. +func (f *Float64Flag) Get(ctx *Context) float64 { return ctx.Float64(f.Name) } diff --git a/flag_float64_slice.go b/flag_float64_slice.go index c987688..35cc535 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -175,8 +175,8 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { return nil } -// ValueFromContext returns the flag’s value in the given Context. -func (f *Float64SliceFlag) ValueFromContext(ctx *Context) []float64 { +// Get returns the flag’s value in the given Context. +func (f *Float64SliceFlag) Get(ctx *Context) []float64 { return ctx.Float64Slice(f.Name) } diff --git a/flag_generic.go b/flag_generic.go index 483c7d1..74c896e 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -104,8 +104,8 @@ func (f GenericFlag) Apply(set *flag.FlagSet) error { return nil } -// ValueFromContext returns the flag’s value in the given Context. -func (f *GenericFlag) ValueFromContext(ctx *Context) interface{} { +// Get returns the flag’s value in the given Context. +func (f *GenericFlag) Get(ctx *Context) interface{} { return ctx.Generic(f.Name) } diff --git a/flag_int.go b/flag_int.go index 642a68b..6929543 100644 --- a/flag_int.go +++ b/flag_int.go @@ -102,8 +102,8 @@ func (f *IntFlag) Apply(set *flag.FlagSet) error { return nil } -// ValueFromContext returns the flag’s value in the given Context. -func (f *IntFlag) ValueFromContext(ctx *Context) int { +// Get returns the flag’s value in the given Context. +func (f *IntFlag) Get(ctx *Context) int { return ctx.Int(f.Name) } diff --git a/flag_int64.go b/flag_int64.go index 6b40374..55d1214 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -101,8 +101,8 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) error { return nil } -// ValueFromContext returns the flag’s value in the given Context. -func (f *Int64Flag) ValueFromContext(ctx *Context) int64 { +// Get returns the flag’s value in the given Context. +func (f *Int64Flag) Get(ctx *Context) int64 { return ctx.Int64(f.Name) } diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 29e8dc4..212b47b 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -174,8 +174,8 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { return nil } -// ValueFromContext returns the flag’s value in the given Context. -func (f *Int64SliceFlag) ValueFromContext(ctx *Context) []int64 { +// Get returns the flag’s value in the given Context. +func (f *Int64SliceFlag) Get(ctx *Context) []int64 { return ctx.Int64Slice(f.Name) } diff --git a/flag_int_slice.go b/flag_int_slice.go index 4d1741c..82c045d 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -185,8 +185,8 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { return nil } -// ValueFromContext returns the flag’s value in the given Context. -func (f *IntSliceFlag) ValueFromContext(ctx *Context) []int { +// Get returns the flag’s value in the given Context. +func (f *IntSliceFlag) Get(ctx *Context) []int { return ctx.IntSlice(f.Name) } diff --git a/flag_path.go b/flag_path.go index 40c9009..82e540b 100644 --- a/flag_path.go +++ b/flag_path.go @@ -96,8 +96,8 @@ func (f *PathFlag) Apply(set *flag.FlagSet) error { return nil } -// ValueFromContext returns the flag’s value in the given Context. -func (f *PathFlag) ValueFromContext(ctx *Context) string { +// Get returns the flag’s value in the given Context. +func (f *PathFlag) Get(ctx *Context) string { return ctx.Path(f.Name) } diff --git a/flag_string.go b/flag_string.go index 7464a28..258ca92 100644 --- a/flag_string.go +++ b/flag_string.go @@ -97,8 +97,8 @@ func (f *StringFlag) Apply(set *flag.FlagSet) error { return nil } -// ValueFromContext returns the flag’s value in the given Context. -func (f *StringFlag) ValueFromContext(ctx *Context) string { +// Get returns the flag’s value in the given Context. +func (f *StringFlag) Get(ctx *Context) string { return ctx.String(f.Name) } diff --git a/flag_string_slice.go b/flag_string_slice.go index 90e6ebd..79257b6 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -186,8 +186,8 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { return nil } -// ValueFromContext returns the flag’s value in the given Context. -func (f *StringSliceFlag) ValueFromContext(ctx *Context) []string { +// Get returns the flag’s value in the given Context. +func (f *StringSliceFlag) Get(ctx *Context) []string { return ctx.StringSlice(f.Name) } diff --git a/flag_test.go b/flag_test.go index 4862c03..07f6f89 100644 --- a/flag_test.go +++ b/flag_test.go @@ -58,8 +58,8 @@ func TestBoolFlagValueFromContext(t *testing.T) { ctx := NewContext(nil, set, nil) tf := &BoolFlag{Name: "trueflag"} ff := &BoolFlag{Name: "falseflag"} - expect(t, tf.ValueFromContext(ctx), true) - expect(t, ff.ValueFromContext(ctx), false) + expect(t, tf.Get(ctx), true) + expect(t, ff.Get(ctx), false) } func TestFlagsFromEnv(t *testing.T) { @@ -455,7 +455,7 @@ func TestStringFlagValueFromContext(t *testing.T) { set.String("myflag", "foobar", "doc") ctx := NewContext(nil, set, nil) f := &StringFlag{Name: "myflag"} - expect(t, f.ValueFromContext(ctx), "foobar") + expect(t, f.Get(ctx), "foobar") } var pathFlagTests = []struct { @@ -514,7 +514,7 @@ func TestPathFlagValueFromContext(t *testing.T) { set.String("myflag", "/my/path", "doc") ctx := NewContext(nil, set, nil) f := &PathFlag{Name: "myflag"} - expect(t, f.ValueFromContext(ctx), "/my/path") + expect(t, f.Get(ctx), "/my/path") } var envHintFlagTests = []struct { @@ -635,7 +635,7 @@ func TestStringSliceFlagValueFromContext(t *testing.T) { set.Var(NewStringSlice("a", "b", "c"), "myflag", "doc") ctx := NewContext(nil, set, nil) f := &StringSliceFlag{Name: "myflag"} - expect(t, f.ValueFromContext(ctx), []string{"a", "b", "c"}) + expect(t, f.Get(ctx), []string{"a", "b", "c"}) } var intFlagTests = []struct { @@ -692,7 +692,7 @@ func TestIntFlagValueFromContext(t *testing.T) { set.Int("myflag", 42, "doc") ctx := NewContext(nil, set, nil) f := &IntFlag{Name: "myflag"} - expect(t, f.ValueFromContext(ctx), 42) + expect(t, f.Get(ctx), 42) } var int64FlagTests = []struct { @@ -738,7 +738,7 @@ func TestInt64FlagValueFromContext(t *testing.T) { set.Int64("myflag", 42, "doc") ctx := NewContext(nil, set, nil) f := &Int64Flag{Name: "myflag"} - expect(t, f.ValueFromContext(ctx), int64(42)) + expect(t, f.Get(ctx), int64(42)) } var uintFlagTests = []struct { @@ -784,7 +784,7 @@ func TestUintFlagValueFromContext(t *testing.T) { set.Uint("myflag", 42, "doc") ctx := NewContext(nil, set, nil) f := &UintFlag{Name: "myflag"} - expect(t, f.ValueFromContext(ctx), uint(42)) + expect(t, f.Get(ctx), uint(42)) } var uint64FlagTests = []struct { @@ -830,7 +830,7 @@ func TestUint64FlagValueFromContext(t *testing.T) { set.Uint64("myflag", 42, "doc") ctx := NewContext(nil, set, nil) f := &Uint64Flag{Name: "myflag"} - expect(t, f.ValueFromContext(ctx), uint64(42)) + expect(t, f.Get(ctx), uint64(42)) } var durationFlagTests = []struct { @@ -887,7 +887,7 @@ func TestDurationFlagValueFromContext(t *testing.T) { set.Duration("myflag", 42*time.Second, "doc") ctx := NewContext(nil, set, nil) f := &DurationFlag{Name: "myflag"} - expect(t, f.ValueFromContext(ctx), 42*time.Second) + expect(t, f.Get(ctx), 42*time.Second) } var intSliceFlagTests = []struct { @@ -984,7 +984,7 @@ func TestIntSliceFlagValueFromContext(t *testing.T) { set.Var(NewIntSlice(1, 2, 3), "myflag", "doc") ctx := NewContext(nil, set, nil) f := &IntSliceFlag{Name: "myflag"} - expect(t, f.ValueFromContext(ctx), []int{1, 2, 3}) + expect(t, f.Get(ctx), []int{1, 2, 3}) } var int64SliceFlagTests = []struct { @@ -1088,7 +1088,7 @@ func TestInt64SliceFlagValueFromContext(t *testing.T) { set.Var(NewInt64Slice(1, 2, 3), "myflag", "doc") ctx := NewContext(nil, set, nil) f := &Int64SliceFlag{Name: "myflag"} - expect(t, f.ValueFromContext(ctx), []int64{1, 2, 3}) + expect(t, f.Get(ctx), []int64{1, 2, 3}) } var float64FlagTests = []struct { @@ -1145,7 +1145,7 @@ func TestFloat64FlagValueFromContext(t *testing.T) { set.Float64("myflag", 1.23, "doc") ctx := NewContext(nil, set, nil) f := &Float64Flag{Name: "myflag"} - expect(t, f.ValueFromContext(ctx), 1.23) + expect(t, f.Get(ctx), 1.23) } var float64SliceFlagTests = []struct { @@ -1194,7 +1194,7 @@ func TestFloat64SliceFlagValueFromContext(t *testing.T) { set.Var(NewFloat64Slice(1.23, 4.56), "myflag", "doc") ctx := NewContext(nil, set, nil) f := &Float64SliceFlag{Name: "myflag"} - expect(t, f.ValueFromContext(ctx), []float64{1.23, 4.56}) + expect(t, f.Get(ctx), []float64{1.23, 4.56}) } var genericFlagTests = []struct { @@ -1250,7 +1250,7 @@ func TestGenericFlagValueFromContext(t *testing.T) { set.Var(&Parser{"abc", "def"}, "myflag", "doc") ctx := NewContext(nil, set, nil) f := &GenericFlag{Name: "myflag"} - expect(t, f.ValueFromContext(ctx), &Parser{"abc", "def"}) + expect(t, f.Get(ctx), &Parser{"abc", "def"}) } func TestParseMultiString(t *testing.T) { @@ -2286,7 +2286,7 @@ func TestTimestampFlagValueFromContext(t *testing.T) { set.Var(NewTimestamp(now), "myflag", "doc") ctx := NewContext(nil, set, nil) f := &TimestampFlag{Name: "myflag"} - expect(t, f.ValueFromContext(ctx), &now) + expect(t, f.Get(ctx), &now) } type flagDefaultTestCase struct { diff --git a/flag_timestamp.go b/flag_timestamp.go index a3b230e..872f855 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -164,8 +164,8 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error { return nil } -// ValueFromContext returns the flag’s value in the given Context. -func (f *TimestampFlag) ValueFromContext(ctx *Context) *time.Time { +// Get returns the flag’s value in the given Context. +func (f *TimestampFlag) Get(ctx *Context) *time.Time { return ctx.Timestamp(f.Name) } diff --git a/flag_uint.go b/flag_uint.go index b074c4e..358e22f 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -101,8 +101,8 @@ func (f *UintFlag) GetEnvVars() []string { return f.EnvVars } -// ValueFromContext returns the flag’s value in the given Context. -func (f *UintFlag) ValueFromContext(ctx *Context) uint { +// Get returns the flag’s value in the given Context. +func (f *UintFlag) Get(ctx *Context) uint { return ctx.Uint(f.Name) } diff --git a/flag_uint64.go b/flag_uint64.go index e79b1a7..844dfc9 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -101,8 +101,8 @@ func (f *Uint64Flag) GetEnvVars() []string { return f.EnvVars } -// ValueFromContext returns the flag’s value in the given Context. -func (f *Uint64Flag) ValueFromContext(ctx *Context) uint64 { +// Get returns the flag’s value in the given Context. +func (f *Uint64Flag) Get(ctx *Context) uint64 { return ctx.Uint64(f.Name) } From aabfea87c811de4c6bf6dcd01f64309a8864fead Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Mon, 25 Apr 2022 10:04:40 -0700 Subject: [PATCH 054/125] Move some test helpers from docs_test to fish_test This is in preparation to make docs optional. Signed-off-by: Kir Kolyshkin --- docs_test.go | 123 --------------------------------------------------- fish_test.go | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 123 deletions(-) diff --git a/docs_test.go b/docs_test.go index adccbbb..b925cb9 100644 --- a/docs_test.go +++ b/docs_test.go @@ -1,133 +1,10 @@ package cli import ( - "bytes" "errors" - "io/ioutil" "testing" ) -func testApp() *App { - app := newTestApp() - app.Name = "greet" - app.Flags = []Flag{ - &StringFlag{ - Name: "socket", - Aliases: []string{"s"}, - Usage: "some 'usage' text", - Value: "value", - TakesFile: true, - }, - &StringFlag{Name: "flag", Aliases: []string{"fl", "f"}}, - &BoolFlag{ - Name: "another-flag", - Aliases: []string{"b"}, - Usage: "another usage text", - }, - &BoolFlag{ - Name: "hidden-flag", - Hidden: true, - }, - } - app.Commands = []*Command{{ - Aliases: []string{"c"}, - Flags: []Flag{ - &StringFlag{ - Name: "flag", - Aliases: []string{"fl", "f"}, - TakesFile: true, - }, - &BoolFlag{ - Name: "another-flag", - Aliases: []string{"b"}, - Usage: "another usage text", - }, - }, - Name: "config", - Usage: "another usage test", - Subcommands: []*Command{{ - Aliases: []string{"s", "ss"}, - Flags: []Flag{ - &StringFlag{Name: "sub-flag", Aliases: []string{"sub-fl", "s"}}, - &BoolFlag{ - Name: "sub-command-flag", - Aliases: []string{"s"}, - Usage: "some usage text", - }, - }, - Name: "sub-config", - Usage: "another usage test", - }}, - }, { - Aliases: []string{"i", "in"}, - Name: "info", - Usage: "retrieve generic information", - }, { - Name: "some-command", - }, { - 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"}, - {Name: "Oliver Allen", Email: "oliver@toyshop.com"}, - } - return app -} - -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, got, string(data)) -} - func TestToMarkdownFull(t *testing.T) { // Given app := testApp() diff --git a/fish_test.go b/fish_test.go index 4ca8c47..af1a14c 100644 --- a/fish_test.go +++ b/fish_test.go @@ -1,6 +1,8 @@ package cli import ( + "bytes" + "io/ioutil" "testing" ) @@ -19,3 +21,124 @@ func TestFishCompletion(t *testing.T) { expect(t, err, nil) expectFileContent(t, "testdata/expected-fish-full.fish", res) } + +func testApp() *App { + app := newTestApp() + app.Name = "greet" + app.Flags = []Flag{ + &StringFlag{ + Name: "socket", + Aliases: []string{"s"}, + Usage: "some 'usage' text", + Value: "value", + TakesFile: true, + }, + &StringFlag{Name: "flag", Aliases: []string{"fl", "f"}}, + &BoolFlag{ + Name: "another-flag", + Aliases: []string{"b"}, + Usage: "another usage text", + }, + &BoolFlag{ + Name: "hidden-flag", + Hidden: true, + }, + } + app.Commands = []*Command{{ + Aliases: []string{"c"}, + Flags: []Flag{ + &StringFlag{ + Name: "flag", + Aliases: []string{"fl", "f"}, + TakesFile: true, + }, + &BoolFlag{ + Name: "another-flag", + Aliases: []string{"b"}, + Usage: "another usage text", + }, + }, + Name: "config", + Usage: "another usage test", + Subcommands: []*Command{{ + Aliases: []string{"s", "ss"}, + Flags: []Flag{ + &StringFlag{Name: "sub-flag", Aliases: []string{"sub-fl", "s"}}, + &BoolFlag{ + Name: "sub-command-flag", + Aliases: []string{"s"}, + Usage: "some usage text", + }, + }, + Name: "sub-config", + Usage: "another usage test", + }}, + }, { + Aliases: []string{"i", "in"}, + Name: "info", + Usage: "retrieve generic information", + }, { + Name: "some-command", + }, { + 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"}, + {Name: "Oliver Allen", Email: "oliver@toyshop.com"}, + } + return app +} + +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, got, string(data)) +} From 4c7b46cb24c9af9b5d93bcc2316b2a877008b723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20B=C4=85k?= Date: Mon, 25 Apr 2022 19:11:11 +0200 Subject: [PATCH 055/125] fix: clean code --- flag.go | 22 +--------------------- flag_test.go | 18 +++++++++--------- 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/flag.go b/flag.go index 1907622..b85bafd 100644 --- a/flag.go +++ b/flag.go @@ -5,7 +5,6 @@ import ( "flag" "fmt" "io/ioutil" - "reflect" "regexp" "runtime" "strconv" @@ -244,7 +243,7 @@ func prefixedNames(names []string, placeholder string) string { func withEnvHint(envVars []string, str string) string { envText := "" - if envVars != nil && len(envVars) > 0 { + if len(envVars) > 0 { prefix := "$" suffix := "" sep := ", $" @@ -273,17 +272,6 @@ func flagNames(name string, aliases []string) []string { return ret } -func flagStringSliceField(f Flag, name string) []string { - fv := flagValue(f) - field := fv.FieldByName(name) - - if field.IsValid() { - return field.Interface().([]string) - } - - return []string{} -} - func withFileHint(filePath, str string) string { fileText := "" if filePath != "" { @@ -292,14 +280,6 @@ func withFileHint(filePath, str string) string { return str + fileText } -func flagValue(f Flag) reflect.Value { - fv := reflect.ValueOf(f) - for fv.Kind() == reflect.Ptr { - fv = reflect.Indirect(fv) - } - return fv -} - func formatDefault(format string) string { return " (default: " + format + ")" } diff --git a/flag_test.go b/flag_test.go index b9cbfc3..843f646 100644 --- a/flag_test.go +++ b/flag_test.go @@ -400,7 +400,7 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { } } -var prefixStringFlagTests = []struct { +var _ = []struct { name string aliases []string usage string @@ -490,7 +490,7 @@ func TestPathFlagApply_SetsAllNames(t *testing.T) { expect(t, v, "/path/to/file/PATH") } -var envHintFlagTests = []struct { +var _ = []struct { name string env string hinter FlagEnvHintFunc @@ -2174,43 +2174,43 @@ type flagDefaultTestCase struct { 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"}, From 49e43beba32f7fb2007f557bb77129bf8a0e4300 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Mon, 25 Apr 2022 10:16:31 -0700 Subject: [PATCH 056/125] Add urfave_cli_no_docs build tag This removes the resulting binary dependency on cpuguy83/md2man and russross/blackfriday, which saves more than 400 KB (more than 300 KB once stripped) from the resulting binary. Document this in README. --- README.md | 10 ++++++++++ docs.go | 3 +++ docs_test.go | 3 +++ 3 files changed, 16 insertions(+) diff --git a/README.md b/README.md index 5b8d2b6..134e72e 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,16 @@ import ( ... ``` +### Build tags + +You can use the following build tags: + +#### `urfave_cli_no_docs` + +When set, this removes `ToMarkdown` and `ToMan` methods, so your application +won't be able to call those. This reduces the resulting binary size by about +300-400 KB (measured using Go 1.18.1 on Linux/amd64), due to less dependencies. + ### GOPATH Make sure your `PATH` includes the `$GOPATH/bin` directory so your commands can diff --git a/docs.go b/docs.go index 9f82fc6..ace6a6b 100644 --- a/docs.go +++ b/docs.go @@ -1,3 +1,6 @@ +//go:build !urfave_cli_no_docs +// +build !urfave_cli_no_docs + package cli import ( diff --git a/docs_test.go b/docs_test.go index b925cb9..12d5d3c 100644 --- a/docs_test.go +++ b/docs_test.go @@ -1,3 +1,6 @@ +//go:build !urfave_cli_no_docs +// +build !urfave_cli_no_docs + package cli import ( From b8cb475418d24c63ac616018ac4bdb5f61353f1f Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Mon, 25 Apr 2022 10:58:31 -0700 Subject: [PATCH 057/125] ci: test newly added tag We run test with the tag set (to make sure nothing is broken), and also the check-binary-size target (for informational purposes only). Signed-off-by: Kir Kolyshkin --- .github/workflows/cli.yml | 6 ++++++ internal/build/build.go | 20 +++++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 0ab3baf..c60ed4c 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -37,12 +37,18 @@ jobs: - name: vet run: go run internal/build/build.go vet + - name: test with tags + run: go run internal/build/build.go -tags urfave_cli_no_docs test + - name: test run: go run internal/build/build.go test - name: check-binary-size run: go run internal/build/build.go check-binary-size + - name: check-binary-size with tags (informational only) + run: go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size || true + - name: Upload coverage to Codecov if: success() && matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@v2 diff --git a/internal/build/build.go b/internal/build/build.go index 4cbaa68..dec0dfb 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -46,6 +46,12 @@ func main() { Action: checkBinarySizeActionFunc, }, } + app.Flags = []cli.Flag{ + &cli.StringFlag{ + Name: "tags", + Usage: "set build tags", + }, + } err := app.Run(os.Args) if err != nil { @@ -68,6 +74,8 @@ func VetActionFunc(_ *cli.Context) error { } func TestActionFunc(c *cli.Context) error { + tags := c.String("tags") + for _, pkg := range packages { var packageName string @@ -79,7 +87,7 @@ func TestActionFunc(c *cli.Context) error { coverProfile := fmt.Sprintf("--coverprofile=%s.coverprofile", pkg) - err := runCmd("go", "test", "-v", coverProfile, packageName) + err := runCmd("go", "test", "-tags", tags, "-v", coverProfile, packageName) if err != nil { return err } @@ -201,14 +209,16 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) { mbStringFormatter = "%.1fMB" ) + tags := c.String("tags") + // get cli example size - cliSize, err := getSize(cliSourceFilePath, cliBuiltFilePath) + cliSize, err := getSize(cliSourceFilePath, cliBuiltFilePath, tags) if err != nil { return err } // get hello world size - helloSize, err := getSize(helloSourceFilePath, helloBuiltFilePath) + helloSize, err := getSize(helloSourceFilePath, helloBuiltFilePath, tags) if err != nil { return err } @@ -270,9 +280,9 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) { return nil } -func getSize(sourcePath string, builtPath string) (size int64, err error) { +func getSize(sourcePath string, builtPath string, tags string) (size int64, err error) { // build example binary - err = runCmd("go", "build", "-o", builtPath, "-ldflags", "-s -w", sourcePath) + err = runCmd("go", "build", "-tags", tags, "-o", builtPath, "-ldflags", "-s -w", sourcePath) if err != nil { fmt.Println("issue getting size for example binary") return 0, err From e3aa8d3246f4e5d1e26e78c98c6cb5ebb6c3b668 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 25 Apr 2022 20:16:01 -0400 Subject: [PATCH 058/125] Add failing test for #1373 --- altsrc/yaml_file_loader_test.go | 87 +++++++++++++++++++++++++++++++++ testdata/empty.yml | 1 + 2 files changed, 88 insertions(+) create mode 100644 altsrc/yaml_file_loader_test.go create mode 100644 testdata/empty.yml diff --git a/altsrc/yaml_file_loader_test.go b/altsrc/yaml_file_loader_test.go new file mode 100644 index 0000000..814586b --- /dev/null +++ b/altsrc/yaml_file_loader_test.go @@ -0,0 +1,87 @@ +package altsrc_test + +import ( + "fmt" + "log" + "os" + "time" + + "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2/altsrc" +) + +func ExampleApp_Run_yamlFileLoaderDuration() { + execServe := func(c *cli.Context) error { + keepaliveInterval := c.Duration("keepalive-interval") + fmt.Printf("keepalive %s\n", keepaliveInterval) + return nil + } + + fileExists := func(filename string) bool { + stat, _ := os.Stat(filename) + return stat != nil + } + + // initConfigFileInputSource is like altsrc.InitInputSourceWithContext and altsrc.NewYamlSourceFromFlagFunc, but checks + // if the config flag is exists and only loads it if it does. If the flag is set and the file exists, it fails. + initConfigFileInputSource := func(configFlag string, flags []cli.Flag) cli.BeforeFunc { + return func(context *cli.Context) error { + configFile := context.String(configFlag) + if context.IsSet(configFlag) && !fileExists(configFile) { + return fmt.Errorf("config file %s does not exist", configFile) + } else if !context.IsSet(configFlag) && !fileExists(configFile) { + return nil + } + inputSource, err := altsrc.NewYamlSourceFromFile(configFile) + if err != nil { + return err + } + return altsrc.ApplyInputSourceValues(context, inputSource, flags) + } + } + + flagsServe := []cli.Flag{ + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + EnvVars: []string{"CONFIG_FILE"}, + Value: "../testdata/empty.yml", + DefaultText: "../testdata/empty.yml", + Usage: "config file", + }, + altsrc.NewDurationFlag( + &cli.DurationFlag{ + Name: "keepalive-interval", + Aliases: []string{"k"}, + EnvVars: []string{"KEEPALIVE_INTERVAL"}, + Value: 45 * time.Second, + Usage: "interval of keepalive messages", + }, + ), + } + + cmdServe := &cli.Command{ + Name: "serve", + Usage: "Run the server", + UsageText: "serve [OPTIONS..]", + Action: execServe, + Flags: flagsServe, + Before: initConfigFileInputSource("config", flagsServe), + } + + c := &cli.App{ + Name: "cmd", + HideVersion: true, + UseShortOptionHandling: true, + Commands: []*cli.Command{ + cmdServe, + }, + } + + if err := c.Run([]string{"cmd", "serve", "--config", "../testdata/empty.yml"}); err != nil { + log.Fatal(err) + } + + // Output: + // keepalive 45s +} diff --git a/testdata/empty.yml b/testdata/empty.yml new file mode 100644 index 0000000..ab2fc5d --- /dev/null +++ b/testdata/empty.yml @@ -0,0 +1 @@ +# empty file \ No newline at end of file From b42bf698a162b6cc6ed90a25f87b814cead70afb Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 25 Apr 2022 20:24:10 -0400 Subject: [PATCH 059/125] Add note about looming EOL of `v1` --- docs/SECURITY.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/SECURITY.md b/docs/SECURITY.md index a6fe0cc..699538f 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -5,12 +5,12 @@ policy! :tada: :lock: ## Supported Versions -| Version | Supported | -| --------- | ------------------ | -| >= 2.3.x | :white_check_mark: | -| < 2.3 | :x: | -| >= 1.22.x | :white_check_mark: | -| < 1.22 | :x: | +| Version | Supported | +| ------------ | ------------------------------------- | +| `>= v2.3.x` | :white_check_mark: | +| `< v2.3` | :x: | +| `>= v1.22.x` | :white_check_mark: :lady_beetle: [^1] | +| `< v1.22` | :x: | ## Reporting a Vulnerability @@ -22,3 +22,6 @@ You should expect a response within 48 hours and further communications to be decided via email. The `urfave/cli` maintainer team comprises volunteers who contribute when possible, so please have patience :bow: + +[^1]: The `v1.22.x` series will only receive bug fixes until `v1` + is deemed entirely unsupported From efe04493fc5a4013f26ad0b1c37df4d6a769f7a8 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 25 Apr 2022 20:43:43 -0400 Subject: [PATCH 060/125] Only apply altsrc input source values when set Closes #1373 --- altsrc/flag.go | 182 +++++++++++++++------------------ altsrc/input_source_context.go | 1 + altsrc/json_source_context.go | 5 + altsrc/map_input_source.go | 13 +++ 4 files changed, 101 insertions(+), 100 deletions(-) diff --git a/altsrc/flag.go b/altsrc/flag.go index 53382b2..a90a52a 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -64,16 +64,14 @@ func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(cCtx *c // ApplyInputSourceValue applies a generic value to the flagSet if required func (f *GenericFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { - if f.set != nil { - if !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { - value, err := isc.Generic(f.GenericFlag.Name) - if err != nil { - return err - } - if value != nil { - for _, name := range f.Names() { - _ = f.set.Set(name, value.String()) - } + if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.IsSet(f.GenericFlag.Name) { + value, err := isc.Generic(f.GenericFlag.Name) + if err != nil { + return err + } + if value != nil { + for _, name := range f.Names() { + _ = f.set.Set(name, value.String()) } } } @@ -83,19 +81,17 @@ func (f *GenericFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceCo // ApplyInputSourceValue applies a StringSlice value to the flagSet if required func (f *StringSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { - if f.set != nil { - if !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { - value, err := isc.StringSlice(f.StringSliceFlag.Name) - if err != nil { - return err - } - if value != nil { - var sliceValue cli.StringSlice = *(cli.NewStringSlice(value...)) - for _, name := range f.Names() { - underlyingFlag := f.set.Lookup(name) - if underlyingFlag != nil { - underlyingFlag.Value = &sliceValue - } + if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.IsSet(f.StringSliceFlag.Name) { + value, err := isc.StringSlice(f.StringSliceFlag.Name) + if err != nil { + return err + } + if value != nil { + var sliceValue cli.StringSlice = *(cli.NewStringSlice(value...)) + for _, name := range f.Names() { + underlyingFlag := f.set.Lookup(name) + if underlyingFlag != nil { + underlyingFlag.Value = &sliceValue } } } @@ -105,19 +101,17 @@ func (f *StringSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSour // ApplyInputSourceValue applies a IntSlice value if required func (f *IntSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { - if f.set != nil { - if !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { - value, err := isc.IntSlice(f.IntSliceFlag.Name) - if err != nil { - return err - } - if value != nil { - var sliceValue cli.IntSlice = *(cli.NewIntSlice(value...)) - for _, name := range f.Names() { - underlyingFlag := f.set.Lookup(name) - if underlyingFlag != nil { - underlyingFlag.Value = &sliceValue - } + if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.IsSet(f.IntSliceFlag.Name) { + value, err := isc.IntSlice(f.IntSliceFlag.Name) + if err != nil { + return err + } + if value != nil { + var sliceValue cli.IntSlice = *(cli.NewIntSlice(value...)) + for _, name := range f.Names() { + underlyingFlag := f.set.Lookup(name) + if underlyingFlag != nil { + underlyingFlag.Value = &sliceValue } } } @@ -127,16 +121,14 @@ func (f *IntSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceC // ApplyInputSourceValue applies a Bool value to the flagSet if required func (f *BoolFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { - if f.set != nil { - if !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) { - value, err := isc.Bool(f.BoolFlag.Name) - if err != nil { - return err - } - if value { - for _, name := range f.Names() { - _ = f.set.Set(name, strconv.FormatBool(value)) - } + if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.IsSet(f.BoolFlag.Name) { + value, err := isc.Bool(f.BoolFlag.Name) + if err != nil { + return err + } + if value { + for _, name := range f.Names() { + _ = f.set.Set(name, strconv.FormatBool(value)) } } } @@ -145,16 +137,14 @@ func (f *BoolFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceConte // ApplyInputSourceValue applies a String value to the flagSet if required func (f *StringFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { - if f.set != nil { - if !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { - value, err := isc.String(f.StringFlag.Name) - if err != nil { - return err - } - if value != "" { - for _, name := range f.Names() { - _ = f.set.Set(name, value) - } + if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.IsSet(f.StringFlag.Name) { + value, err := isc.String(f.StringFlag.Name) + if err != nil { + return err + } + if value != "" { + for _, name := range f.Names() { + _ = f.set.Set(name, value) } } } @@ -163,26 +153,24 @@ func (f *StringFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceCon // ApplyInputSourceValue applies a Path value to the flagSet if required func (f *PathFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { - if f.set != nil { - if !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { - value, err := isc.String(f.PathFlag.Name) - if err != nil { - return err - } - if value != "" { - for _, name := range f.Names() { + if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.IsSet(f.PathFlag.Name) { + value, err := isc.String(f.PathFlag.Name) + if err != nil { + return err + } + if value != "" { + for _, name := range f.Names() { - if !filepath.IsAbs(value) && isc.Source() != "" { - basePathAbs, err := filepath.Abs(isc.Source()) - if err != nil { - return err - } - - value = filepath.Join(filepath.Dir(basePathAbs), value) + if !filepath.IsAbs(value) && isc.Source() != "" { + basePathAbs, err := filepath.Abs(isc.Source()) + if err != nil { + return err } - _ = f.set.Set(name, value) + value = filepath.Join(filepath.Dir(basePathAbs), value) } + + _ = f.set.Set(name, value) } } } @@ -191,15 +179,13 @@ func (f *PathFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceConte // ApplyInputSourceValue applies a int value to the flagSet if required func (f *IntFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { - if f.set != nil { - if !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { - value, err := isc.Int(f.IntFlag.Name) - if err != nil { - return err - } - for _, name := range f.Names() { - _ = f.set.Set(name, strconv.FormatInt(int64(value), 10)) - } + if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.IsSet(f.IntFlag.Name) { + value, err := isc.Int(f.IntFlag.Name) + if err != nil { + return err + } + for _, name := range f.Names() { + _ = f.set.Set(name, strconv.FormatInt(int64(value), 10)) } } return nil @@ -207,15 +193,13 @@ func (f *IntFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContex // ApplyInputSourceValue applies a Duration value to the flagSet if required func (f *DurationFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { - if f.set != nil { - if !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { - value, err := isc.Duration(f.DurationFlag.Name) - if err != nil { - return err - } - for _, name := range f.Names() { - _ = f.set.Set(name, value.String()) - } + if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.IsSet(f.DurationFlag.Name) { + value, err := isc.Duration(f.DurationFlag.Name) + if err != nil { + return err + } + for _, name := range f.Names() { + _ = f.set.Set(name, value.String()) } } return nil @@ -223,16 +207,14 @@ func (f *DurationFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceC // ApplyInputSourceValue applies a Float64 value to the flagSet if required func (f *Float64Flag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { - if f.set != nil { - if !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) { - value, err := isc.Float64(f.Float64Flag.Name) - if err != nil { - return err - } - floatStr := float64ToString(value) - for _, name := range f.Names() { - _ = f.set.Set(name, floatStr) - } + if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.IsSet(f.Float64Flag.Name) { + value, err := isc.Float64(f.Float64Flag.Name) + if err != nil { + return err + } + floatStr := float64ToString(value) + for _, name := range f.Names() { + _ = f.set.Set(name, floatStr) } } return nil diff --git a/altsrc/input_source_context.go b/altsrc/input_source_context.go index a639d8b..467360c 100644 --- a/altsrc/input_source_context.go +++ b/altsrc/input_source_context.go @@ -14,6 +14,7 @@ import ( type InputSourceContext interface { Source() string + IsSet(name string) bool Int(name string) (int, error) Duration(name string) (time.Duration, error) Float64(name string) (float64, error) diff --git a/altsrc/json_source_context.go b/altsrc/json_source_context.go index 9307a35..d5f8cc1 100644 --- a/altsrc/json_source_context.go +++ b/altsrc/json_source_context.go @@ -184,6 +184,11 @@ func (x *jsonSource) Bool(name string) (bool, error) { return v, nil } +func (x *jsonSource) IsSet(name string) bool { + _, err := x.getValue(name) + return err == nil +} + func (x *jsonSource) getValue(key string) (interface{}, error) { return jsonGetValue(key, x.deserialized) } diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index 117461f..be94906 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -244,6 +244,19 @@ func (fsm *MapInputSource) Bool(name string) (bool, error) { return false, nil } +// IsSet returns the truth of the key's existence in the map +func (fsm *MapInputSource) IsSet(name string) bool { + if _, exists := fsm.valueMap[name]; exists { + return exists + } + + if _, exists := nestedVal(name, fsm.valueMap); exists { + return exists + } + + return false +} + func incorrectTypeForFlagError(name, expectedTypeName string, value interface{}) error { valueType := reflect.TypeOf(value) valueTypeName := "" From fe1468cc86b1cab489b2038a7f83090198b72af8 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 25 Apr 2022 20:49:31 -0400 Subject: [PATCH 061/125] Make the altsrc input source context `isSet` method private so that it doesn't extend the public surface area, instead focusing on being a bugfix _only_. --- altsrc/flag.go | 18 +++++++++--------- altsrc/input_source_context.go | 3 ++- altsrc/json_source_context.go | 2 +- altsrc/map_input_source.go | 3 +-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/altsrc/flag.go b/altsrc/flag.go index a90a52a..db95949 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -64,7 +64,7 @@ func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(cCtx *c // ApplyInputSourceValue applies a generic value to the flagSet if required func (f *GenericFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { - if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.IsSet(f.GenericFlag.Name) { + if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.isSet(f.GenericFlag.Name) { value, err := isc.Generic(f.GenericFlag.Name) if err != nil { return err @@ -81,7 +81,7 @@ func (f *GenericFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceCo // ApplyInputSourceValue applies a StringSlice value to the flagSet if required func (f *StringSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { - if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.IsSet(f.StringSliceFlag.Name) { + if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.isSet(f.StringSliceFlag.Name) { value, err := isc.StringSlice(f.StringSliceFlag.Name) if err != nil { return err @@ -101,7 +101,7 @@ func (f *StringSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSour // ApplyInputSourceValue applies a IntSlice value if required func (f *IntSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { - if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.IsSet(f.IntSliceFlag.Name) { + if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.isSet(f.IntSliceFlag.Name) { value, err := isc.IntSlice(f.IntSliceFlag.Name) if err != nil { return err @@ -121,7 +121,7 @@ func (f *IntSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceC // ApplyInputSourceValue applies a Bool value to the flagSet if required func (f *BoolFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { - if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.IsSet(f.BoolFlag.Name) { + if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.isSet(f.BoolFlag.Name) { value, err := isc.Bool(f.BoolFlag.Name) if err != nil { return err @@ -137,7 +137,7 @@ func (f *BoolFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceConte // ApplyInputSourceValue applies a String value to the flagSet if required func (f *StringFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { - if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.IsSet(f.StringFlag.Name) { + if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.isSet(f.StringFlag.Name) { value, err := isc.String(f.StringFlag.Name) if err != nil { return err @@ -153,7 +153,7 @@ func (f *StringFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceCon // ApplyInputSourceValue applies a Path value to the flagSet if required func (f *PathFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { - if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.IsSet(f.PathFlag.Name) { + if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.isSet(f.PathFlag.Name) { value, err := isc.String(f.PathFlag.Name) if err != nil { return err @@ -179,7 +179,7 @@ func (f *PathFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceConte // ApplyInputSourceValue applies a int value to the flagSet if required func (f *IntFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { - if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.IsSet(f.IntFlag.Name) { + if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.isSet(f.IntFlag.Name) { value, err := isc.Int(f.IntFlag.Name) if err != nil { return err @@ -193,7 +193,7 @@ func (f *IntFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContex // ApplyInputSourceValue applies a Duration value to the flagSet if required func (f *DurationFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { - if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.IsSet(f.DurationFlag.Name) { + if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.isSet(f.DurationFlag.Name) { value, err := isc.Duration(f.DurationFlag.Name) if err != nil { return err @@ -207,7 +207,7 @@ func (f *DurationFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceC // ApplyInputSourceValue applies a Float64 value to the flagSet if required func (f *Float64Flag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error { - if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.IsSet(f.Float64Flag.Name) { + if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.isSet(f.Float64Flag.Name) { value, err := isc.Float64(f.Float64Flag.Name) if err != nil { return err diff --git a/altsrc/input_source_context.go b/altsrc/input_source_context.go index 467360c..d743253 100644 --- a/altsrc/input_source_context.go +++ b/altsrc/input_source_context.go @@ -14,7 +14,6 @@ import ( type InputSourceContext interface { Source() string - IsSet(name string) bool Int(name string) (int, error) Duration(name string) (time.Duration, error) Float64(name string) (float64, error) @@ -23,4 +22,6 @@ type InputSourceContext interface { IntSlice(name string) ([]int, error) Generic(name string) (cli.Generic, error) Bool(name string) (bool, error) + + isSet(name string) bool } diff --git a/altsrc/json_source_context.go b/altsrc/json_source_context.go index d5f8cc1..168b6da 100644 --- a/altsrc/json_source_context.go +++ b/altsrc/json_source_context.go @@ -184,7 +184,7 @@ func (x *jsonSource) Bool(name string) (bool, error) { return v, nil } -func (x *jsonSource) IsSet(name string) bool { +func (x *jsonSource) isSet(name string) bool { _, err := x.getValue(name) return err == nil } diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index be94906..cc11fc5 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -244,8 +244,7 @@ func (fsm *MapInputSource) Bool(name string) (bool, error) { return false, nil } -// IsSet returns the truth of the key's existence in the map -func (fsm *MapInputSource) IsSet(name string) bool { +func (fsm *MapInputSource) isSet(name string) bool { if _, exists := fsm.valueMap[name]; exists { return exists } From ac641ffda54727c6af36b80c0941f05886aed217 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 25 Apr 2022 22:15:39 -0400 Subject: [PATCH 062/125] Add more test coverage around unset input source applying --- altsrc/flag_test.go | 259 ++++++++++++++++++++++++++++++++--------- altsrc/helpers_test.go | 7 +- 2 files changed, 206 insertions(+), 60 deletions(-) diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index dcb8263..e3725f7 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -26,29 +26,48 @@ type testApplyInputSource struct { MapValue interface{} } +type racyInputSource struct { + *MapInputSource +} + +func (ris *racyInputSource) isSet(name string) bool { + if _, ok := ris.MapInputSource.valueMap[name]; ok { + ris.MapInputSource.valueMap[name] = bogus{0} + } + return true +} + func TestGenericApplyInputSourceValue(t *testing.T) { v := &Parser{"abc", "def"} - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Value: &Parser{}}), FlagName: "test", MapValue: v, - }) + } + c := runTest(t, tis) expect(t, v, c.Generic("test")) + + c = runRacyTest(t, tis) + refute(t, v, c.Generic("test")) } func TestGenericApplyInputSourceMethodContextSet(t *testing.T) { p := &Parser{"abc", "def"} - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Value: &Parser{}}), FlagName: "test", MapValue: &Parser{"efg", "hig"}, ContextValueString: p.String(), - }) + } + c := runTest(t, tis) expect(t, p, c.Generic("test")) + + c = runRacyTest(t, tis) + refute(t, p, c.Generic("test")) } func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewGenericFlag(&cli.GenericFlag{ Name: "test", Value: &Parser{}, @@ -58,17 +77,25 @@ func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) { MapValue: &Parser{"efg", "hij"}, EnvVarName: "TEST", EnvVarValue: "abc,def", - }) + } + c := runTest(t, tis) expect(t, &Parser{"abc", "def"}, c.Generic("test")) + + c = runRacyTest(t, tis) + refute(t, &Parser{"abc", "def"}, c.Generic("test")) } func TestStringSliceApplyInputSourceValue(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}), FlagName: "test", MapValue: []interface{}{"hello", "world"}, - }) + } + c := runTest(t, tis) expect(t, c.StringSlice("test"), []string{"hello", "world"}) + + c = runRacyTest(t, tis) + refute(t, c.StringSlice("test"), []string{"hello", "world"}) } func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) { @@ -82,112 +109,154 @@ func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) { } func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: []interface{}{"hello", "world"}, EnvVarName: "TEST", EnvVarValue: "oh,no", - }) + } + c := runTest(t, tis) expect(t, c.StringSlice("test"), []string{"oh", "no"}) + + c = runRacyTest(t, tis) + refute(t, c.StringSlice("test"), []string{"oh", "no"}) } func TestIntSliceApplyInputSourceValue(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}), FlagName: "test", MapValue: []interface{}{1, 2}, - }) + } + c := runTest(t, tis) expect(t, c.IntSlice("test"), []int{1, 2}) + + c = runRacyTest(t, tis) + refute(t, c.IntSlice("test"), []int{1, 2}) } func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}), FlagName: "test", MapValue: []interface{}{1, 2}, ContextValueString: "3", - }) + } + c := runTest(t, tis) expect(t, c.IntSlice("test"), []int{3}) + + c = runRacyTest(t, tis) + refute(t, c.IntSlice("test"), []int{3}) } func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: []interface{}{1, 2}, EnvVarName: "TEST", EnvVarValue: "3,4", - }) + } + c := runTest(t, tis) expect(t, c.IntSlice("test"), []int{3, 4}) + + c = runRacyTest(t, tis) + refute(t, c.IntSlice("test"), []int{3, 4}) } func TestBoolApplyInputSourceMethodSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewBoolFlag(&cli.BoolFlag{Name: "test"}), FlagName: "test", MapValue: true, - }) + } + c := runTest(t, tis) expect(t, true, c.Bool("test")) + + c = runRacyTest(t, tis) + refute(t, true, c.Bool("test")) } func TestBoolApplyInputSourceMethodContextSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewBoolFlag(&cli.BoolFlag{Name: "test"}), FlagName: "test", MapValue: false, ContextValueString: "true", - }) + } + c := runTest(t, tis) expect(t, true, c.Bool("test")) + + c = runRacyTest(t, tis) + refute(t, true, c.Bool("test")) } func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewBoolFlag(&cli.BoolFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: false, EnvVarName: "TEST", EnvVarValue: "true", - }) + } + c := runTest(t, tis) expect(t, true, c.Bool("test")) + + c = runRacyTest(t, tis) + refute(t, true, c.Bool("test")) } func TestStringApplyInputSourceMethodSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewStringFlag(&cli.StringFlag{Name: "test"}), FlagName: "test", MapValue: "hello", - }) + } + c := runTest(t, tis) expect(t, "hello", c.String("test")) + + c = runRacyTest(t, tis) + refute(t, "hello", c.String("test")) } func TestStringApplyInputSourceMethodContextSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewStringFlag(&cli.StringFlag{Name: "test"}), FlagName: "test", MapValue: "hello", ContextValueString: "goodbye", - }) + } + c := runTest(t, tis) expect(t, "goodbye", c.String("test")) + + c = runRacyTest(t, tis) + refute(t, "goodbye", c.String("test")) } func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewStringFlag(&cli.StringFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: "hello", EnvVarName: "TEST", EnvVarValue: "goodbye", - }) + } + c := runTest(t, tis) expect(t, "goodbye", c.String("test")) + + c = runRacyTest(t, tis) + refute(t, "goodbye", c.String("test")) } + func TestPathApplyInputSourceMethodSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewPathFlag(&cli.PathFlag{Name: "test"}), FlagName: "test", MapValue: "hello", SourcePath: "/path/to/source/file", - }) + } + c := runTest(t, tis) expected := "/path/to/source/hello" if runtime.GOOS == "windows" { @@ -200,125 +269,176 @@ func TestPathApplyInputSourceMethodSet(t *testing.T) { } } expect(t, expected, c.String("test")) + + c = runRacyTest(t, tis) + refute(t, expected, c.String("test")) } func TestPathApplyInputSourceMethodContextSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewPathFlag(&cli.PathFlag{Name: "test"}), FlagName: "test", MapValue: "hello", ContextValueString: "goodbye", SourcePath: "/path/to/source/file", - }) + } + c := runTest(t, tis) expect(t, "goodbye", c.String("test")) + + c = runRacyTest(t, tis) + refute(t, "goodbye", c.String("test")) } func TestPathApplyInputSourceMethodEnvVarSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewPathFlag(&cli.PathFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: "hello", EnvVarName: "TEST", EnvVarValue: "goodbye", SourcePath: "/path/to/source/file", - }) + } + c := runTest(t, tis) expect(t, "goodbye", c.String("test")) + + c = runRacyTest(t, tis) + refute(t, "goodbye", c.String("test")) } func TestIntApplyInputSourceMethodSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewIntFlag(&cli.IntFlag{Name: "test"}), FlagName: "test", MapValue: 15, - }) + } + c := runTest(t, tis) expect(t, 15, c.Int("test")) + + c = runRacyTest(t, tis) + refute(t, 15, c.Int("test")) } func TestIntApplyInputSourceMethodSetNegativeValue(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewIntFlag(&cli.IntFlag{Name: "test"}), FlagName: "test", MapValue: -1, - }) + } + c := runTest(t, tis) expect(t, -1, c.Int("test")) + + c = runRacyTest(t, tis) + refute(t, -1, c.Int("test")) } func TestIntApplyInputSourceMethodContextSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewIntFlag(&cli.IntFlag{Name: "test"}), FlagName: "test", MapValue: 15, ContextValueString: "7", - }) + } + c := runTest(t, tis) expect(t, 7, c.Int("test")) + + c = runRacyTest(t, tis) + refute(t, 7, c.Int("test")) } func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewIntFlag(&cli.IntFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: 15, EnvVarName: "TEST", EnvVarValue: "12", - }) + } + c := runTest(t, tis) expect(t, 12, c.Int("test")) + + c = runRacyTest(t, tis) + refute(t, 12, c.Int("test")) } func TestDurationApplyInputSourceMethodSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}), FlagName: "test", MapValue: 30 * time.Second, - }) + } + c := runTest(t, tis) expect(t, 30*time.Second, c.Duration("test")) + + c = runRacyTest(t, tis) + refute(t, 30*time.Second, c.Duration("test")) } func TestDurationApplyInputSourceMethodSetNegativeValue(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}), FlagName: "test", MapValue: -30 * time.Second, - }) + } + c := runTest(t, tis) expect(t, -30*time.Second, c.Duration("test")) + + c = runRacyTest(t, tis) + refute(t, -30*time.Second, c.Duration("test")) } func TestDurationApplyInputSourceMethodContextSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}), FlagName: "test", MapValue: 30 * time.Second, ContextValueString: (15 * time.Second).String(), - }) + } + c := runTest(t, tis) expect(t, 15*time.Second, c.Duration("test")) + + c = runRacyTest(t, tis) + refute(t, 15*time.Second, c.Duration("test")) } func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewDurationFlag(&cli.DurationFlag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: 30 * time.Second, EnvVarName: "TEST", EnvVarValue: (15 * time.Second).String(), - }) + } + c := runTest(t, tis) expect(t, 15*time.Second, c.Duration("test")) + + c = runRacyTest(t, tis) + refute(t, 15*time.Second, c.Duration("test")) } func TestFloat64ApplyInputSourceMethodSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}), FlagName: "test", MapValue: 1.3, - }) + } + c := runTest(t, tis) expect(t, 1.3, c.Float64("test")) + + c = runRacyTest(t, tis) + refute(t, 1.3, c.Float64("test")) } func TestFloat64ApplyInputSourceMethodSetNegativeValue(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}), FlagName: "test", MapValue: -1.3, - }) + } + c := runTest(t, tis) expect(t, -1.3, c.Float64("test")) + + c = runRacyTest(t, tis) + refute(t, -1.3, c.Float64("test")) } func TestFloat64ApplyInputSourceMethodSetNegativeValueNotSet(t *testing.T) { @@ -331,24 +451,32 @@ func TestFloat64ApplyInputSourceMethodSetNegativeValueNotSet(t *testing.T) { } func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test"}), FlagName: "test", MapValue: 1.3, ContextValueString: fmt.Sprintf("%v", 1.4), - }) + } + c := runTest(t, tis) expect(t, 1.4, c.Float64("test")) + + c = runRacyTest(t, tis) + refute(t, 1.4, c.Float64("test")) } func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) { - c := runTest(t, testApplyInputSource{ + tis := testApplyInputSource{ Flag: NewFloat64Flag(&cli.Float64Flag{Name: "test", EnvVars: []string{"TEST"}}), FlagName: "test", MapValue: 1.3, EnvVarName: "TEST", EnvVarValue: fmt.Sprintf("%v", 1.4), - }) + } + c := runTest(t, tis) expect(t, 1.4, c.Float64("test")) + + c = runRacyTest(t, tis) + refute(t, 1.4, c.Float64("test")) } func runTest(t *testing.T, test testApplyInputSource) *cli.Context { @@ -376,6 +504,19 @@ func runTest(t *testing.T, test testApplyInputSource) *cli.Context { return c } +func runRacyTest(t *testing.T, test testApplyInputSource) *cli.Context { + set := flag.NewFlagSet(test.FlagSetName, flag.ContinueOnError) + c := cli.NewContext(nil, set, nil) + _ = test.Flag.ApplyInputSourceValue(c, &racyInputSource{ + MapInputSource: &MapInputSource{ + file: test.SourcePath, + valueMap: map[interface{}]interface{}{test.FlagName: test.MapValue}, + }, + }) + + return c +} + type Parser [2]string func (p *Parser) Set(value string) error { @@ -393,3 +534,5 @@ func (p *Parser) Set(value string) error { func (p *Parser) String() string { return fmt.Sprintf("%s,%s", p[0], p[1]) } + +type bogus [1]uint diff --git a/altsrc/helpers_test.go b/altsrc/helpers_test.go index 33e8a4b..1f8d5c2 100644 --- a/altsrc/helpers_test.go +++ b/altsrc/helpers_test.go @@ -22,7 +22,10 @@ func expect(t *testing.T, a interface{}, b interface{}) { } func refute(t *testing.T, a interface{}, b interface{}) { - if a == b { - t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) + _, fn, line, _ := runtime.Caller(1) + fn = strings.Replace(fn, wd+"/", "", -1) + + if reflect.DeepEqual(a, b) { + t.Errorf("(%s:%d) Did not expect %v (type %v) - Got %v (type %v)", fn, line, b, reflect.TypeOf(b), a, reflect.TypeOf(a)) } } From cdb1730b7da03b1d52af0a5cf20d3cf4422c191d Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 25 Apr 2022 22:21:46 -0400 Subject: [PATCH 063/125] Drop extra if condition --- altsrc/map_input_source.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index cc11fc5..e065c7c 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -249,11 +249,8 @@ func (fsm *MapInputSource) isSet(name string) bool { return exists } - if _, exists := nestedVal(name, fsm.valueMap); exists { - return exists - } - - return false + _, exists := nestedVal(name, fsm.valueMap) + return exists } func incorrectTypeForFlagError(name, expectedTypeName string, value interface{}) error { From 1bef0318f9995cb1fecf7055b9a0ee7341767200 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 26 Apr 2022 07:52:30 -0400 Subject: [PATCH 064/125] Footnote update per feedback in #1365 --- docs/SECURITY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 699538f..eae6a22 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -23,5 +23,5 @@ communications to be decided via email. The `urfave/cli` maintainer team comprises volunteers who contribute when possible, so please have patience :bow: -[^1]: The `v1.22.x` series will only receive bug fixes until `v1` - is deemed entirely unsupported +[^1]: The `v1.22.x` series will receive bug fixes and security + patches only. From ddac788d8515c4354497becb29feafd055c3d9ec Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 28 Apr 2022 20:42:24 -0400 Subject: [PATCH 065/125] Correct doc comment per feedback --- category.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/category.go b/category.go index 7580e90..60e4e9b 100644 --- a/category.go +++ b/category.go @@ -81,7 +81,7 @@ func (c *commandCategory) VisibleCommands() []*Command { // FlagCategories is a slice of *FlagCategory. type FlagCategories []*FlagCategory -// FlagCategory is a category containing commands. +// FlagCategory is a category containing flags. type FlagCategory struct { Name string Flags []Flag From 156eaafb22ac81600ca4bec47d3408f1a6dfb3f7 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 30 Apr 2022 08:59:50 -0400 Subject: [PATCH 066/125] Rework flag categories a bit with internal maps instead of slices and slightly less public API surface area --- app.go | 19 ++++++----- app_test.go | 77 +++++++++++++++++++---------------------- category.go | 98 ++++++++++++++++++++++++++++++++++++----------------- command.go | 14 ++++---- template.go | 8 ++--- 5 files changed, 123 insertions(+), 93 deletions(-) diff --git a/app.go b/app.go index 83ebe30..7379388 100644 --- a/app.go +++ b/app.go @@ -184,15 +184,14 @@ func (a *App) Setup() { c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) } - fc := FlagCategories{} + fc := newFlagCategories() for _, fl := range c.Flags { if cf, ok := fl.(CategorizableFlag); ok { - fc = fc.AddFlag(cf.GetCategory(), cf) + fc.AddFlag(cf.GetCategory(), cf) } } - sort.Sort(fc) - c.FlagCategories = fc + c.flagCategories = fc newCommands = append(newCommands, c) } a.Commands = newCommands @@ -217,13 +216,12 @@ func (a *App) Setup() { } sort.Sort(a.categories.(*commandCategories)) - a.flagCategories = FlagCategories{} + a.flagCategories = newFlagCategories() for _, fl := range a.Flags { if cf, ok := fl.(CategorizableFlag); ok { a.flagCategories.AddFlag(cf.GetCategory(), cf) } } - sort.Sort(a.flagCategories) if a.Metadata == nil { a.Metadata = make(map[string]interface{}) @@ -501,9 +499,12 @@ func (a *App) VisibleCommands() []*Command { return ret } -// Categories returns a slice containing all the categories with the commands they contain -func (a *App) VisibleFlagCategories() FlagCategories { - return a.flagCategories +// VisibleFlagCategories returns a slice containing all the categories with the flags they contain +func (a *App) VisibleFlagCategories() []VisibleFlagCategory { + if a.flagCategories == nil { + a.flagCategories = newFlagCategories() + } + return a.flagCategories.VisibleCategories() } // VisibleFlags returns a slice of the Flags with Hidden=false diff --git a/app_test.go b/app_test.go index 38c7ce8..52b7209 100644 --- a/app_test.go +++ b/app_test.go @@ -142,8 +142,8 @@ func ExampleApp_Run_appHelp() { // help, h Shows a list of commands or help for one command // // GLOBAL OPTIONS: - // --name value a name to say (default: "bob") // --help, -h show help (default: false) + // --name value a name to say (default: "bob") // --version, -v print the version (default: false) } @@ -1823,23 +1823,20 @@ func TestApp_VisibleCategories(t *testing.T) { HideHelp: true, Commands: []*Command{ { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, - FlagCategories: FlagCategories{}, + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, }, { - Name: "command2", - Category: "2", - HelpName: "foo command2", - FlagCategories: FlagCategories{}, + Name: "command2", + Category: "2", + HelpName: "foo command2", }, { - Name: "command3", - Category: "3", - HelpName: "foo command3", - FlagCategories: FlagCategories{}, + Name: "command3", + Category: "3", + HelpName: "foo command3", }, }, } @@ -1867,24 +1864,21 @@ func TestApp_VisibleCategories(t *testing.T) { HideHelp: true, Commands: []*Command{ { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, - FlagCategories: FlagCategories{}, + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, }, { - Name: "command2", - Category: "2", - HelpName: "foo command2", - Hidden: true, - FlagCategories: FlagCategories{}, + Name: "command2", + Category: "2", + HelpName: "foo command2", + Hidden: true, }, { - Name: "command3", - Category: "3", - HelpName: "foo command3", - FlagCategories: FlagCategories{}, + Name: "command3", + Category: "3", + HelpName: "foo command3", }, }, } @@ -1906,25 +1900,22 @@ func TestApp_VisibleCategories(t *testing.T) { HideHelp: true, Commands: []*Command{ { - Name: "command1", - Category: "1", - HelpName: "foo command1", - Hidden: true, - FlagCategories: FlagCategories{}, + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, }, { - Name: "command2", - Category: "2", - HelpName: "foo command2", - Hidden: true, - FlagCategories: FlagCategories{}, + Name: "command2", + Category: "2", + HelpName: "foo command2", + Hidden: true, }, { - Name: "command3", - Category: "3", - HelpName: "foo command3", - Hidden: true, - FlagCategories: FlagCategories{}, + Name: "command3", + Category: "3", + HelpName: "foo command3", + Hidden: true, }, }, } diff --git a/category.go b/category.go index 60e4e9b..7bbe4a5 100644 --- a/category.go +++ b/category.go @@ -1,10 +1,12 @@ package cli +import "sort" + // 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) - // categories returns a copy of the category slice + // Categories returns a slice of categories sorted by name Categories() []CommandCategory } @@ -78,47 +80,81 @@ func (c *commandCategory) VisibleCommands() []*Command { return ret } -// FlagCategories is a slice of *FlagCategory. -type FlagCategories []*FlagCategory - -// FlagCategory is a category containing flags. -type FlagCategory struct { - Name string - Flags []Flag +// FlagCategories interface allows for category manipulation +type FlagCategories interface { + // AddFlags adds a flag to a category, creating a new category if necessary. + AddFlag(category string, fl Flag) + // VisibleCategories returns a slice of visible flag categories sorted by name + VisibleCategories() []VisibleFlagCategory } -func (f FlagCategories) Less(i, j int) bool { - return lexicographicLess(f[i].Name, f[j].Name) +type defaultFlagCategories struct { + m map[string]*defaultVisibleFlagCategory } -func (f FlagCategories) Len() int { - return len(f) -} - -func (f FlagCategories) Swap(i, j int) { - f[i], f[j] = f[j], f[i] -} - -// AddFlags adds a command to a category. -func (f FlagCategories) AddFlag(category string, flag Flag) FlagCategories { - for _, flagCategory := range f { - if flagCategory.Name == category { - flagCategory.Flags = append(flagCategory.Flags, flag) - return f - } +func newFlagCategories() FlagCategories { + return &defaultFlagCategories{ + m: map[string]*defaultVisibleFlagCategory{}, } - return append(f, &FlagCategory{Name: category, Flags: []Flag{flag}}) } -// VisibleFlags returns a slice of the Flags with Hidden=false -func (c *FlagCategory) VisibleFlags() []VisibleFlag { - ret := []VisibleFlag{} - for _, fl := range c.Flags { +func (f *defaultFlagCategories) AddFlag(category string, fl Flag) { + if _, ok := f.m[category]; !ok { + f.m[category] = &defaultVisibleFlagCategory{name: category, m: map[string]Flag{}} + } + + f.m[category].m[fl.String()] = fl +} + +func (f *defaultFlagCategories) VisibleCategories() []VisibleFlagCategory { + catNames := []string{} + for name := range f.m { + catNames = append(catNames, name) + } + + sort.Strings(catNames) + + ret := make([]VisibleFlagCategory, len(catNames)) + for i, name := range catNames { + ret[i] = f.m[name] + } + + return ret +} + +// VisibleFlagCategory is a category containing flags. +type VisibleFlagCategory interface { + // Name returns the category name string + Name() string + // Flags returns a slice of VisibleFlag sorted by name + Flags() []VisibleFlag +} + +type defaultVisibleFlagCategory struct { + name string + m map[string]Flag +} + +func (fc *defaultVisibleFlagCategory) Name() string { + return fc.name +} + +func (fc *defaultVisibleFlagCategory) Flags() []VisibleFlag { + vfNames := []string{} + for flName, fl := range fc.m { if vf, ok := fl.(VisibleFlag); ok { if vf.IsVisible() { - ret = append(ret, vf) + vfNames = append(vfNames, flName) } } } + + sort.Strings(vfNames) + + ret := make([]VisibleFlag, len(vfNames)) + for i, flName := range vfNames { + ret[i] = fc.m[flName].(VisibleFlag) + } + return ret } diff --git a/command.go b/command.go index 1f24dc7..02ea5ff 100644 --- a/command.go +++ b/command.go @@ -38,9 +38,8 @@ type Command struct { // List of child commands Subcommands []*Command // List of flags to parse - Flags []Flag - // List of all flag categories - FlagCategories FlagCategories + Flags []Flag + flagCategories FlagCategories // Treat all flags as normal arguments if true SkipFlagParsing bool // Boolean to hide built-in help command and help flag @@ -282,9 +281,12 @@ func (c *Command) startApp(ctx *Context) error { return app.RunAsSubcommand(ctx) } -// Categories returns a slice containing all the categories with the commands they contain -func (c Command) VisibleFlagCategories() FlagCategories { - return c.FlagCategories +// VisibleFlagCategories returns a slice containing all the visible flag categories with the flags they contain +func (c *Command) VisibleFlagCategories() []VisibleFlagCategory { + if c.flagCategories == nil { + c.flagCategories = newFlagCategories() + } + return c.flagCategories.VisibleCategories() } // VisibleFlags returns a slice of the Flags with Hidden=false diff --git a/template.go b/template.go index 8b7ea63..264eb85 100644 --- a/template.go +++ b/template.go @@ -25,8 +25,8 @@ COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} GLOBAL OPTIONS:{{range .VisibleFlagCategories}} - {{.Name}} - {{range .VisibleFlags}}{{.}} + {{if .Name}}{{.Name}} + {{end}}{{range .Flags}}{{.}} {{end}}{{end}}{{else}}{{if .VisibleFlags}} GLOBAL OPTIONS: @@ -53,8 +53,8 @@ DESCRIPTION: {{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlagCategories}} OPTIONS:{{range .VisibleFlagCategories}} - {{.Name}} - {{range .VisibleFlags}}{{.}} + {{if .Name}}{{.Name}} + {{end}}{{range .Flags}}{{.}} {{end}}{{end}}{{else}}{{if .VisibleFlags}} OPTIONS: From f3ef95f8ccf8a8d98d87a61009ff8690b0a7bf4a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 30 Apr 2022 14:16:54 -0400 Subject: [PATCH 067/125] Tighten up restriction on SHELL match --- help.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/help.go b/help.go index 1455689..4b03abe 100644 --- a/help.go +++ b/help.go @@ -102,7 +102,7 @@ func printCommandSuggestions(commands []*Command, writer io.Writer) { if command.Hidden { continue } - if strings.Contains(os.Getenv("SHELL"), "zsh") { + if strings.HasSuffix(os.Getenv("SHELL"), "zsh") { for _, name := range command.Names() { _, _ = fmt.Fprintf(writer, "%s:%s\n", name, command.Usage) } From ed0033984bc6dbfc4a864d454f3e606978c5cc2b Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 1 May 2022 23:02:05 -0400 Subject: [PATCH 068/125] Generate flag types (again?) Closes #1381 --- .github/workflows/cli.yml | 5 + Makefile | 40 ++ cli.go | 2 +- docs/CONTRIBUTING.md | 110 ++++- flag-spec.yaml | 50 +++ flag.go | 2 +- flag_bool.go | 31 -- flag_duration.go | 31 -- flag_float64.go | 31 -- flag_float64_slice.go | 24 -- flag_generic.go | 31 -- flag_int.go | 31 -- flag_int64.go | 31 -- flag_int64_slice.go | 24 -- flag_int_slice.go | 24 -- flag_path.go | 31 +- flag_string.go | 32 -- flag_string_slice.go | 26 -- flag_timestamp.go | 32 -- flag_uint.go | 31 -- flag_uint64.go | 31 -- go.mod | 1 + go.sum | 4 +- internal/build/build.go | 131 +++--- internal/genflags/cmd/genflags/main.go | 161 ++++++++ internal/genflags/generated.gotmpl | 53 +++ internal/genflags/generated_test.gotmpl | 22 + internal/genflags/package.go | 34 ++ internal/genflags/package_test.go | 41 ++ internal/genflags/spec.go | 100 +++++ zz_generated.flags.go | 507 ++++++++++++++++++++++++ zz_generated.flags_test.go | 116 ++++++ 32 files changed, 1304 insertions(+), 516 deletions(-) create mode 100644 Makefile create mode 100644 flag-spec.yaml create mode 100644 internal/genflags/cmd/genflags/main.go create mode 100644 internal/genflags/generated.gotmpl create mode 100644 internal/genflags/generated_test.gotmpl create mode 100644 internal/genflags/package.go create mode 100644 internal/genflags/package_test.go create mode 100644 internal/genflags/spec.go create mode 100644 zz_generated.flags.go create mode 100644 zz_generated.flags_test.go diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index c60ed4c..84f9cae 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -87,3 +87,8 @@ jobs: - name: toc run: go run internal/build/build.go toc docs/v2/manual.md + + - name: diff check + run: | + git diff --exit-code + git diff --cached --exit-code diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dd6893a --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ +# NOTE: this Makefile is meant to provide a simplified entry point for humans to +# run all of the critical steps to verify one's changes are harmonious in +# nature. Keeping target bodies to one line each and abstaining from make magic +# are very important so that maintainers and contributors can focus their +# attention on files that are primarily Go. + +.PHONY: all +all: generate lint tag-test test check-bin tag-check-bin gfmrun toc + +.PHONY: generate +generate: + go run internal/build/build.go generate + +.PHONY: lint +lint: + go run internal/build/build.go vet + +.PHONY: tag-test +tag-test: + go run internal/build/build.go -tags urfave_cli_no_docs test + +.PHONY: test +test: + go run internal/build/build.go test + +.PHONY: check-bin +check-bin: + go run internal/build/build.go check-binary-size + +.PHONY: tag-check-bin +tag-check-bin: + go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size + +.PHONY: gfmrun +gfmrun: + go run internal/build/build.go gfmrun docs/v2/manual.md + +.PHONY: toc +toc: + go run internal/build/build.go toc docs/v2/manual.md diff --git a/cli.go b/cli.go index 62a5bc2..2a11c5a 100644 --- a/cli.go +++ b/cli.go @@ -20,4 +20,4 @@ // } package cli -//go:generate go run flag-gen/main.go flag-gen/assets_vfsdata.go +//go:generate go run internal/genflags/cmd/genflags/main.go diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 41671a9..88c6b95 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,18 +1,102 @@ ## Contributing -Use @urfave/cli to ping the maintainers. +Welcome to the `urfave/cli` contributor docs! This goal of this document is to help those +interested in joining the 200+ humans who have contributed to this project over the years. -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 main branch. +> As a general guiding principle, the current maintainers may be notified via the +> @urfave/cli GitHub team. -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 -to merge others pull requests. It is very important that new code does not -break existing code, so be careful about what code you do choose to merge. +All of the current maintainers are *volunteers* who live in various timezones with +different scheduling needs, so please understand that your contribution or question may +not get a response for many days. -If you feel like you have contributed to the project but have not yet been added -as a collaborator, we probably forgot to add you :sweat_smile:. Please open an -issue! +### semantic versioning adherence + +The `urfave/cli` project strives to strictly adhere to semantic versioning. The active +development branches and the milestones and import paths to which they correspond are: + +#### `main` branch + + + +The majority of active development and issue management is targeting the `main` branch, +which **MUST** *only* receive bug fixes and feature *additions*. + +- :arrow_right: [`v2.x`](https://github.com/urfave/cli/milestone/16) +- :arrow_right: `github.com/urfave/cli/v2` + +#### `v1` branch + + + +The `v1` branch **MUST** only receive bug fixes in the `v1.22.x` series. There is no +strict rule regarding bug fixes to the `v2.x` series being backported to the `v1.22.x` +series. + +- :arrow_right: [`v1.22.x`](https://github.com/urfave/cli/milestone/11) +- :arrow_right: `github.com/urfave/cli` + +#### `v3-dev-main` branch + + + +The `v3-dev-branch` **MUST** receive all bug fixes and features added to the `main` branch +and **MAY** receive feature *removals* and other changes that are otherwise +*backward-incompatible* with the `v2.x` series. + +- :arrow_right: [`v3.x`](https://github.com/urfave/cli/milestone/5) +- unreleased / unsupported + +### development workflow + +Most of the tooling around the development workflow strives for effective +[dogfooding](https://en.wikipedia.org/wiki/Eating_your_own_dog_food). There is a top-level +`Makefile` that is maintained strictly for the purpose of easing verification of one's +development environment and any changes one may have introduced: + +```sh +make +``` + +Running the default `make` target (`all`) will ensure all of the critical steps are run to +verify one's changes are harmonious in nature. The same steps are also run during the +[continuous integration +phase](https://github.com/urfave/cli/blob/main/.github/workflows/cli.yml). + +#### generated code + +A significant portion of the project's source code is generated, with the goal being to +eliminate repetetive maintenance where other type-safe abstraction is impractical or +impossible with Go versions `< 1.18`. In a future where the eldest Go version supported is +`1.18.x`, there will likely be efforts to take advantage of +[generics](https://go.dev/doc/tutorial/generics). + +The built-in `go generate` command is used to run the commands specified in +`//go:generate` directives. Each such command runs a file that also supports a command +line help system which may be consulted for further information, e.g.: + +```sh +go run internal/genflags/cmd/genflags/main.go --help +``` + +### pull requests + +Please feel free to open a pull request to fix a bug or add a feature. The @urfave/cli +team will review it as soon as possible, giving special attention to maintaining backward +compatibility. If the @urfave/cli team agrees that your contribution is in line with the +vision of the project, they will work with you to get the code into a mergeable state, +merged, and then released. + +### granting of commit bit / admin mode + +Those with a history of contributing to this project will likely be invited to join the +@urfave/cli team. As a member of the @urfave/cli team, you will have the ability to fully +administer pull requests, issues, and other repository bits. + +If you feel that you should be a member of the @urfave/cli team but have not yet been +added, the most likely explanation is that this is an accidental oversight! :sweat_smile:. +Please open an issue! + + diff --git a/flag-spec.yaml b/flag-spec.yaml new file mode 100644 index 0000000..d85fa30 --- /dev/null +++ b/flag-spec.yaml @@ -0,0 +1,50 @@ +# NOTE: this file is used by the tool defined in +# ./internal/genflags/cmd/genflags/main.go which uses the +# `genflags.Spec` type that maps to this file structure. + +flag_types: + bool: {} + float64: {} + int64: {} + int: {} + time.Duration: {} + uint64: {} + uint: {} + + string: + struct_fields: + - { name: TakesFile, type: bool } + Generic: + struct_fields: + - { name: TakesFile, type: bool } + Path: + struct_fields: + - { name: TakesFile, type: bool } + + Float64Slice: + value_pointer: true + skip_interfaces: + - fmt.Stringer + Int64Slice: + value_pointer: true + skip_interfaces: + - fmt.Stringer + IntSlice: + value_pointer: true + skip_interfaces: + - fmt.Stringer + StringSlice: + value_pointer: true + skip_interfaces: + - fmt.Stringer + struct_fields: + - { name: TakesFile, type: bool } + Timestamp: + value_pointer: true + struct_fields: + - { name: Layout, type: string } + + # TODO: enable UintSlice + # UintSlice: {} + # TODO: enable Uint64Slice once #1334 lands + # Uint64Slice: {} diff --git a/flag.go b/flag.go index d2a33f0..0c61d00 100644 --- a/flag.go +++ b/flag.go @@ -258,7 +258,7 @@ func withEnvHint(envVars []string, str string) string { return str + envText } -func flagNames(name string, aliases []string) []string { +func FlagNames(name string, aliases []string) []string { var ret []string for _, part := range append([]string{name}, aliases...) { diff --git a/flag_bool.go b/flag_bool.go index 018f9af..f984acf 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -6,37 +6,6 @@ import ( "strconv" ) -// BoolFlag is a flag with type bool -type BoolFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - FilePath string - Required bool - Hidden bool - Value bool - DefaultText string - Destination *bool - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *BoolFlag) IsSet() bool { - return f.HasBeenSet -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *BoolFlag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *BoolFlag) Names() []string { - return flagNames(f.Name, f.Aliases) -} - // IsRequired returns whether or not the flag is required func (f *BoolFlag) IsRequired() bool { return f.Required diff --git a/flag_duration.go b/flag_duration.go index 9c6dde7..a6677ad 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -6,37 +6,6 @@ import ( "time" ) -// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration) -type DurationFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - FilePath string - Required bool - Hidden bool - Value time.Duration - DefaultText string - Destination *time.Duration - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *DurationFlag) IsSet() bool { - return f.HasBeenSet -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *DurationFlag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *DurationFlag) Names() []string { - return flagNames(f.Name, f.Aliases) -} - // IsRequired returns whether or not the flag is required func (f *DurationFlag) IsRequired() bool { return f.Required diff --git a/flag_float64.go b/flag_float64.go index 62fdb13..62a1973 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -6,37 +6,6 @@ import ( "strconv" ) -// Float64Flag is a flag with type float64 -type Float64Flag struct { - Name string - Aliases []string - Usage string - EnvVars []string - FilePath string - Required bool - Hidden bool - Value float64 - DefaultText string - Destination *float64 - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *Float64Flag) IsSet() bool { - return f.HasBeenSet -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *Float64Flag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *Float64Flag) Names() []string { - return flagNames(f.Name, f.Aliases) -} - // IsRequired returns whether or not the flag is required func (f *Float64Flag) IsRequired() bool { return f.Required diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 8be586c..0f09c8c 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -75,36 +75,12 @@ func (f *Float64Slice) Get() interface{} { return *f } -// Float64SliceFlag is a flag with type *Float64Slice -type Float64SliceFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - FilePath string - Required bool - Hidden bool - Value *Float64Slice - DefaultText string - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *Float64SliceFlag) IsSet() bool { - return f.HasBeenSet -} - // String returns a readable representation of this value // (for usage defaults) func (f *Float64SliceFlag) String() string { return withEnvHint(f.GetEnvVars(), stringifyFloat64SliceFlag(f)) } -// Names returns the names of the flag -func (f *Float64SliceFlag) Names() []string { - return flagNames(f.Name, f.Aliases) -} - // IsRequired returns whether or not the flag is required func (f *Float64SliceFlag) IsRequired() bool { return f.Required diff --git a/flag_generic.go b/flag_generic.go index ae594d4..71e7173 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -11,37 +11,6 @@ type Generic interface { String() string } -// GenericFlag is a flag with type Generic -type GenericFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - FilePath string - Required bool - Hidden bool - TakesFile bool - Value Generic - DefaultText string - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *GenericFlag) IsSet() bool { - return f.HasBeenSet -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *GenericFlag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *GenericFlag) Names() []string { - return flagNames(f.Name, f.Aliases) -} - // IsRequired returns whether or not the flag is required func (f *GenericFlag) IsRequired() bool { return f.Required diff --git a/flag_int.go b/flag_int.go index 63ec9a4..b68c3e8 100644 --- a/flag_int.go +++ b/flag_int.go @@ -6,37 +6,6 @@ import ( "strconv" ) -// IntFlag is a flag with type int -type IntFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - FilePath string - Required bool - Hidden bool - Value int - DefaultText string - Destination *int - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *IntFlag) IsSet() bool { - return f.HasBeenSet -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *IntFlag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *IntFlag) Names() []string { - return flagNames(f.Name, f.Aliases) -} - // IsRequired returns whether or not the flag is required func (f *IntFlag) IsRequired() bool { return f.Required diff --git a/flag_int64.go b/flag_int64.go index 929e2d8..3f71134 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -6,37 +6,6 @@ import ( "strconv" ) -// Int64Flag is a flag with type int64 -type Int64Flag struct { - Name string - Aliases []string - Usage string - EnvVars []string - FilePath string - Required bool - Hidden bool - Value int64 - DefaultText string - Destination *int64 - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *Int64Flag) IsSet() bool { - return f.HasBeenSet -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *Int64Flag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *Int64Flag) Names() []string { - return flagNames(f.Name, f.Aliases) -} - // IsRequired returns whether or not the flag is required func (f *Int64Flag) IsRequired() bool { return f.Required diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 08f03dd..14d8c41 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -76,36 +76,12 @@ func (i *Int64Slice) Get() interface{} { return *i } -// Int64SliceFlag is a flag with type *Int64Slice -type Int64SliceFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - FilePath string - Required bool - Hidden bool - Value *Int64Slice - DefaultText string - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *Int64SliceFlag) IsSet() bool { - return f.HasBeenSet -} - // String returns a readable representation of this value // (for usage defaults) func (f *Int64SliceFlag) String() string { return withEnvHint(f.GetEnvVars(), stringifyInt64SliceFlag(f)) } -// Names returns the names of the flag -func (f *Int64SliceFlag) Names() []string { - return flagNames(f.Name, f.Aliases) -} - // IsRequired returns whether or not the flag is required func (f *Int64SliceFlag) IsRequired() bool { return f.Required diff --git a/flag_int_slice.go b/flag_int_slice.go index e5b9e21..4ed7f2c 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -87,36 +87,12 @@ func (i *IntSlice) Get() interface{} { return *i } -// IntSliceFlag is a flag with type *IntSlice -type IntSliceFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - FilePath string - Required bool - Hidden bool - Value *IntSlice - DefaultText string - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *IntSliceFlag) IsSet() bool { - return f.HasBeenSet -} - // String returns a readable representation of this value // (for usage defaults) func (f *IntSliceFlag) String() string { return withEnvHint(f.GetEnvVars(), stringifyIntSliceFlag(f)) } -// Names returns the names of the flag -func (f *IntSliceFlag) Names() []string { - return flagNames(f.Name, f.Aliases) -} - // IsRequired returns whether or not the flag is required func (f *IntSliceFlag) IsRequired() bool { return f.Required diff --git a/flag_path.go b/flag_path.go index a7a213e..095a596 100644 --- a/flag_path.go +++ b/flag_path.go @@ -5,36 +5,7 @@ import ( "fmt" ) -type PathFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - FilePath string - Required bool - Hidden bool - TakesFile bool - Value string - DefaultText string - Destination *string - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *PathFlag) IsSet() bool { - return f.HasBeenSet -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *PathFlag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *PathFlag) Names() []string { - return flagNames(f.Name, f.Aliases) -} +type Path = string // IsRequired returns whether or not the flag is required func (f *PathFlag) IsRequired() bool { diff --git a/flag_string.go b/flag_string.go index 0365762..4831c17 100644 --- a/flag_string.go +++ b/flag_string.go @@ -5,38 +5,6 @@ import ( "fmt" ) -// StringFlag is a flag with type string -type StringFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - FilePath string - Required bool - Hidden bool - TakesFile bool - Value string - DefaultText string - Destination *string - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *StringFlag) IsSet() bool { - return f.HasBeenSet -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *StringFlag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *StringFlag) Names() []string { - return flagNames(f.Name, f.Aliases) -} - // IsRequired returns whether or not the flag is required func (f *StringFlag) IsRequired() bool { return f.Required diff --git a/flag_string_slice.go b/flag_string_slice.go index 82aa1a4..9e69d00 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -70,38 +70,12 @@ func (s *StringSlice) Get() interface{} { return *s } -// StringSliceFlag is a flag with type *StringSlice -type StringSliceFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - FilePath string - Required bool - Hidden bool - TakesFile bool - Value *StringSlice - DefaultText string - HasBeenSet bool - Destination *StringSlice -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *StringSliceFlag) IsSet() bool { - return f.HasBeenSet -} - // String returns a readable representation of this value // (for usage defaults) func (f *StringSliceFlag) String() string { return withEnvHint(f.GetEnvVars(), stringifyStringSliceFlag(f)) } -// Names returns the names of the flag -func (f *StringSliceFlag) Names() []string { - return flagNames(f.Name, f.Aliases) -} - // IsRequired returns whether or not the flag is required func (f *StringSliceFlag) IsRequired() bool { return f.Required diff --git a/flag_timestamp.go b/flag_timestamp.go index 7bda8ee..32899f5 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -58,38 +58,6 @@ 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 - Destination *Timestamp -} - -// 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.Name, f.Aliases) -} - // IsRequired returns whether or not the flag is required func (f *TimestampFlag) IsRequired() bool { return f.Required diff --git a/flag_uint.go b/flag_uint.go index 0fd0777..625f11c 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -6,37 +6,6 @@ import ( "strconv" ) -// UintFlag is a flag with type uint -type UintFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - FilePath string - Required bool - Hidden bool - Value uint - DefaultText string - Destination *uint - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *UintFlag) IsSet() bool { - return f.HasBeenSet -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *UintFlag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *UintFlag) Names() []string { - return flagNames(f.Name, f.Aliases) -} - // IsRequired returns whether or not the flag is required func (f *UintFlag) IsRequired() bool { return f.Required diff --git a/flag_uint64.go b/flag_uint64.go index c8314ac..58969a5 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -6,37 +6,6 @@ import ( "strconv" ) -// Uint64Flag is a flag with type uint64 -type Uint64Flag struct { - Name string - Aliases []string - Usage string - EnvVars []string - FilePath string - Required bool - Hidden bool - Value uint64 - DefaultText string - Destination *uint64 - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *Uint64Flag) IsSet() bool { - return f.HasBeenSet -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *Uint64Flag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *Uint64Flag) Names() []string { - return flagNames(f.Name, f.Aliases) -} - // IsRequired returns whether or not the flag is required func (f *Uint64Flag) IsRequired() bool { return f.Required diff --git a/go.mod b/go.mod index 7a4b88b..3455d26 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.18 require ( github.com/BurntSushi/toml v1.1.0 github.com/cpuguy83/go-md2man/v2 v2.0.1 + golang.org/x/text v0.3.7 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index bde502b..a96d8e7 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 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= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/internal/build/build.go b/internal/build/build.go index dec0dfb..0e9391b 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -6,7 +6,6 @@ import ( "bufio" "bytes" "fmt" - "io/ioutil" "log" "math" "os" @@ -16,9 +15,18 @@ import ( "github.com/urfave/cli/v2" ) -var packages = []string{"cli", "altsrc"} - func main() { + top, err := func() (string, error) { + if v, err := sh("git", "rev-parse", "--show-toplevel"); err == nil { + return strings.TrimSpace(v), nil + } + + return os.Getwd() + }() + if err != nil { + log.Fatal(err) + } + app := cli.NewApp() app.Name = "builder" @@ -45,20 +53,41 @@ func main() { Name: "check-binary-size", Action: checkBinarySizeActionFunc, }, + { + Name: "generate", + Action: GenerateActionFunc, + }, } app.Flags = []cli.Flag{ &cli.StringFlag{ Name: "tags", Usage: "set build tags", }, + &cli.PathFlag{ + Name: "top", + Value: top, + }, + &cli.StringSliceFlag{ + Name: "packages", + Value: cli.NewStringSlice("cli", "altsrc", "internal/build", "internal/genflags"), + }, } - err := app.Run(os.Args) - if err != nil { + if err := app.Run(os.Args); err != nil { log.Fatal(err) } } +func sh(exe string, args ...string) (string, error) { + cmd := exec.Command(exe, args...) + cmd.Stdin = os.Stdin + cmd.Stderr = os.Stderr + + fmt.Fprintf(os.Stderr, "# ---> %s\n", cmd) + outBytes, err := cmd.Output() + return string(outBytes), err +} + func runCmd(arg string, args ...string) error { cmd := exec.Command(arg, args...) @@ -66,78 +95,63 @@ func runCmd(arg string, args ...string) error { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr + fmt.Fprintf(os.Stderr, "# ---> %s\n", cmd) return cmd.Run() } -func VetActionFunc(_ *cli.Context) error { - return runCmd("go", "vet") +func VetActionFunc(cCtx *cli.Context) error { + return runCmd("go", "vet", cCtx.Path("top")+"/...") } func TestActionFunc(c *cli.Context) error { tags := c.String("tags") - for _, pkg := range packages { - var packageName string + for _, pkg := range c.StringSlice("packages") { + packageName := "github.com/urfave/cli/v2" - if pkg == "cli" { - packageName = "github.com/urfave/cli/v2" - } else { + if pkg != "cli" { packageName = fmt.Sprintf("github.com/urfave/cli/v2/%s", pkg) } - coverProfile := fmt.Sprintf("--coverprofile=%s.coverprofile", pkg) - - err := runCmd("go", "test", "-tags", tags, "-v", coverProfile, packageName) - if err != nil { + if err := runCmd( + "go", "test", + "-tags", tags, + "-v", + "--coverprofile", pkg+".coverprofile", + "--covermode", "count", + "--cover", packageName, + packageName, + ); err != nil { return err } } - return testCleanup() + return testCleanup(c.StringSlice("packages")) } -func testCleanup() error { - var out bytes.Buffer +func testCleanup(packages []string) error { + out := &bytes.Buffer{} + + fmt.Fprintf(out, "mode: count\n") for _, pkg := range packages { - file, err := os.Open(fmt.Sprintf("%s.coverprofile", pkg)) + filename := pkg + ".coverprofile" + + lineBytes, err := os.ReadFile(filename) if err != nil { return err } - b, err := ioutil.ReadAll(file) - if err != nil { - return err - } + lines := strings.Split(string(lineBytes), "\n") - out.Write(b) - err = file.Close() - if err != nil { - return err - } + fmt.Fprintf(out, strings.Join(lines[1:], "\n")) - err = os.Remove(fmt.Sprintf("%s.coverprofile", pkg)) - if err != nil { + if err := os.Remove(filename); err != nil { return err } } - outFile, err := os.Create("coverage.txt") - if err != nil { - return err - } - - _, err = out.WriteTo(outFile) - if err != nil { - return err - } - - err = outFile.Close() - if err != nil { - return err - } - - return nil + return os.WriteFile("coverage.txt", out.Bytes(), 0644) } func GfmrunActionFunc(c *cli.Context) error { @@ -179,17 +193,7 @@ func TocActionFunc(c *cli.Context) error { filename = "README.md" } - err := runCmd("markdown-toc", "-i", filename) - if err != nil { - return err - } - - err = runCmd("git", "diff", "--exit-code") - if err != nil { - return err - } - - return nil + return runCmd("markdown-toc", "-i", filename) } // checkBinarySizeActionFunc checks the size of an example binary to ensure that we are keeping size down @@ -201,7 +205,6 @@ 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 = 1.675 desiredMaxBinarySize = 2.2 badNewsEmoji = "🚨" goodNewsEmoji = "✨" @@ -209,8 +212,14 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) { mbStringFormatter = "%.1fMB" ) + desiredMinBinarySize := 1.675 + tags := c.String("tags") + if strings.Contains(tags, "urfave_cli_no_docs") { + desiredMinBinarySize = 1.39 + } + // get cli example size cliSize, err := getSize(cliSourceFilePath, cliBuiltFilePath, tags) if err != nil { @@ -280,6 +289,10 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) { return nil } +func GenerateActionFunc(cCtx *cli.Context) error { + return runCmd("go", "generate", cCtx.Path("top")+"/...") +} + func getSize(sourcePath string, builtPath string, tags string) (size int64, err error) { // build example binary err = runCmd("go", "build", "-tags", tags, "-o", builtPath, "-ldflags", "-s -w", sourcePath) diff --git a/internal/genflags/cmd/genflags/main.go b/internal/genflags/cmd/genflags/main.go new file mode 100644 index 0000000..f4aaeba --- /dev/null +++ b/internal/genflags/cmd/genflags/main.go @@ -0,0 +1,161 @@ +package main + +import ( + "bytes" + "context" + _ "embed" + "log" + "os" + "os/exec" + "os/signal" + "path/filepath" + "strings" + "syscall" + "text/template" + + "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2/internal/genflags" + "gopkg.in/yaml.v2" +) + +const ( + defaultPackageName = "cli" +) + +func sh(ctx context.Context, exe string, args ...string) (string, error) { + cmd := exec.CommandContext(ctx, exe, args...) + cmd.Stderr = os.Stderr + outBytes, err := cmd.Output() + return string(outBytes), err +} + +func main() { + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer stop() + + top := "../../" + if v, err := sh(ctx, "git", "rev-parse", "--show-toplevel"); err == nil { + top = strings.TrimSpace(v) + } + + app := &cli.App{ + Flags: []cli.Flag{ + &cli.PathFlag{ + Name: "flag-spec-yaml", + Aliases: []string{"f"}, + Value: filepath.Join(top, "flag-spec.yaml"), + }, + &cli.PathFlag{ + Name: "generated-output", + Aliases: []string{"o"}, + Value: filepath.Join(top, "zz_generated.flags.go"), + }, + &cli.PathFlag{ + Name: "generated-test-output", + Aliases: []string{"t"}, + Value: filepath.Join(top, "zz_generated.flags_test.go"), + }, + &cli.StringFlag{ + Name: "generated-package-name", + Aliases: []string{"p"}, + Value: defaultPackageName, + }, + &cli.StringFlag{ + Name: "generated-test-package-name", + Aliases: []string{"T"}, + Value: defaultPackageName + "_test", + }, + &cli.StringFlag{ + Name: "urfave-cli-namespace", + Aliases: []string{"n"}, + Value: "", + }, + &cli.StringFlag{ + Name: "urfave-cli-test-namespace", + Aliases: []string{"N"}, + Value: "cli.", + }, + }, + Action: runGenFlags, + } + + if err := app.RunContext(ctx, os.Args); err != nil { + log.Fatal(err) + } +} + +func runGenFlags(cCtx *cli.Context) error { + specBytes, err := os.ReadFile(cCtx.Path("flag-spec-yaml")) + if err != nil { + return err + } + + spec := &genflags.Spec{} + if err := yaml.Unmarshal(specBytes, spec); err != nil { + return err + } + + if cCtx.IsSet("generated-package-name") { + spec.PackageName = strings.TrimSpace(cCtx.String("generated-package-name")) + } + + if strings.TrimSpace(spec.PackageName) == "" { + spec.PackageName = defaultPackageName + } + + if cCtx.IsSet("generated-test-package-name") { + spec.TestPackageName = strings.TrimSpace(cCtx.String("generated-test-package-name")) + } + + if strings.TrimSpace(spec.TestPackageName) == "" { + spec.TestPackageName = defaultPackageName + "_test" + } + + if cCtx.IsSet("urfave-cli-namespace") { + spec.UrfaveCLINamespace = strings.TrimSpace(cCtx.String("urfave-cli-namespace")) + } + + if cCtx.IsSet("urfave-cli-test-namespace") { + spec.UrfaveCLITestNamespace = strings.TrimSpace(cCtx.String("urfave-cli-test-namespace")) + } else { + spec.UrfaveCLITestNamespace = "cli." + } + + genTmpl, err := template.New("gen").Parse(genflags.TemplateString) + if err != nil { + return err + } + + genTestTmpl, err := template.New("gen_test").Parse(genflags.TestTemplateString) + if err != nil { + return err + } + + genBuf := &bytes.Buffer{} + if err := genTmpl.Execute(genBuf, spec); err != nil { + return err + } + + genTestBuf := &bytes.Buffer{} + if err := genTestTmpl.Execute(genTestBuf, spec); err != nil { + return err + } + + if err := os.WriteFile(cCtx.Path("generated-output"), genBuf.Bytes(), 0644); err != nil { + return err + } + + if err := os.WriteFile(cCtx.Path("generated-test-output"), genTestBuf.Bytes(), 0644); err != nil { + return err + } + + if _, err := sh(cCtx.Context, "goimports", "-w", cCtx.Path("generated-output")); err != nil { + return err + } + + if _, err := sh(cCtx.Context, "goimports", "-w", cCtx.Path("generated-test-output")); err != nil { + return err + } + + return nil +} diff --git a/internal/genflags/generated.gotmpl b/internal/genflags/generated.gotmpl new file mode 100644 index 0000000..681a101 --- /dev/null +++ b/internal/genflags/generated.gotmpl @@ -0,0 +1,53 @@ +// WARNING: this file is generated. DO NOT EDIT + +package {{.PackageName}} + +{{range .SortedFlagTypes}} +// {{.TypeName}} is a flag with type {{.GoType}} +type {{.TypeName}} struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value {{if .ValuePointer}}*{{end}}{{.GoType}} + Destination *{{.GoType}} + + Aliases []string + EnvVars []string + + {{range .StructFields}} + {{.Name}} {{.Type}} + {{end}} +} + +{{if .GenerateFmtStringerInterface}} +// String returns a readable representation of this value (for usage defaults) +func (f *{{.TypeName}}) String() string { + return {{$.UrfaveCLINamespace}}FlagStringer(f) +} +{{end}} + +{{if .GenerateFlagInterface}} +// IsSet returns whether or not the flag has been set through env or file +func (f *{{.TypeName}}) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *{{.TypeName}}) Names() []string { + return {{$.UrfaveCLINamespace}}FlagNames(f.Name, f.Aliases) +} + +{{end}}{{/* /if .GenerateFlagInterface */}} +{{end}}{{/* /range .SortedFlagTypes */}} + +// vim{{/* 👻 */}}:ro +{{/* +vim:filetype=gotexttmpl +*/}} diff --git a/internal/genflags/generated_test.gotmpl b/internal/genflags/generated_test.gotmpl new file mode 100644 index 0000000..84806f2 --- /dev/null +++ b/internal/genflags/generated_test.gotmpl @@ -0,0 +1,22 @@ +// WARNING: this file is generated. DO NOT EDIT + +package {{.TestPackageName}} + +{{range .SortedFlagTypes}} +{{if .GenerateFlagInterface}} +func Test{{.TypeName}}_SatisfiesFlagInterface(t *testing.T) { + var _ {{$.UrfaveCLITestNamespace}}Flag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} +} +{{end}} + +{{if .GenerateFmtStringerInterface}} +func Test{{.TypeName}}_SatisfiesFmtStringerInterface(t *testing.T) { + var _ fmt.Stringer = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} +} +{{end}} +{{end}} + +// vim{{/* 👻 */}}:ro +{{/* +vim:filetype=gotexttmpl +*/}} diff --git a/internal/genflags/package.go b/internal/genflags/package.go new file mode 100644 index 0000000..4e5de41 --- /dev/null +++ b/internal/genflags/package.go @@ -0,0 +1,34 @@ +package genflags + +import ( + _ "embed" + "strings" + + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +var ( + //go:embed generated.gotmpl + TemplateString string + + //go:embed generated_test.gotmpl + TestTemplateString string + + titler = cases.Title(language.Und, cases.NoLower) +) + +func TypeName(goType string, fc *FlagTypeConfig) string { + if fc != nil && strings.TrimSpace(fc.TypeName) != "" { + return strings.TrimSpace(fc.TypeName) + } + + dotSplit := strings.Split(goType, ".") + goType = dotSplit[len(dotSplit)-1] + + if strings.HasPrefix(goType, "[]") { + return titler.String(strings.TrimPrefix(goType, "[]")) + "SliceFlag" + } + + return titler.String(goType) + "Flag" +} diff --git a/internal/genflags/package_test.go b/internal/genflags/package_test.go new file mode 100644 index 0000000..3920540 --- /dev/null +++ b/internal/genflags/package_test.go @@ -0,0 +1,41 @@ +package genflags_test + +import ( + "fmt" + "testing" + + "github.com/urfave/cli/v2/internal/genflags" +) + +func TestTypeName(t *testing.T) { + for _, tc := range []struct { + gt string + fc *genflags.FlagTypeConfig + expected string + }{ + {gt: "int", fc: nil, expected: "IntFlag"}, + {gt: "int", fc: &genflags.FlagTypeConfig{}, expected: "IntFlag"}, + {gt: "int", fc: &genflags.FlagTypeConfig{TypeName: "VeryIntyFlag"}, expected: "VeryIntyFlag"}, + {gt: "[]bool", fc: nil, expected: "BoolSliceFlag"}, + {gt: "[]bool", fc: &genflags.FlagTypeConfig{}, expected: "BoolSliceFlag"}, + {gt: "[]bool", fc: &genflags.FlagTypeConfig{TypeName: "ManyTruthsFlag"}, expected: "ManyTruthsFlag"}, + {gt: "time.Rumination", fc: nil, expected: "RuminationFlag"}, + {gt: "time.Rumination", fc: &genflags.FlagTypeConfig{}, expected: "RuminationFlag"}, + {gt: "time.Rumination", fc: &genflags.FlagTypeConfig{TypeName: "PonderFlag"}, expected: "PonderFlag"}, + } { + t.Run( + fmt.Sprintf("type=%s,cfg=%v", tc.gt, func() string { + if tc.fc != nil { + return tc.fc.TypeName + } + return "nil" + }()), + func(ct *testing.T) { + actual := genflags.TypeName(tc.gt, tc.fc) + if tc.expected != actual { + ct.Errorf("expected %q, got %q", tc.expected, actual) + } + }, + ) + } +} diff --git a/internal/genflags/spec.go b/internal/genflags/spec.go new file mode 100644 index 0000000..d9f18db --- /dev/null +++ b/internal/genflags/spec.go @@ -0,0 +1,100 @@ +package genflags + +import ( + "sort" + "strings" +) + +type Spec struct { + FlagTypes map[string]*FlagTypeConfig `yaml:"flag_types"` + PackageName string `yaml:"package_name"` + TestPackageName string `yaml:"test_package_name"` + UrfaveCLINamespace string `yaml:"urfave_cli_namespace"` + UrfaveCLITestNamespace string `yaml:"urfave_cli_test_namespace"` +} + +func (gfs *Spec) SortedFlagTypes() []*FlagType { + typeNames := []string{} + + for name := range gfs.FlagTypes { + if strings.HasPrefix(name, "[]") { + name = strings.TrimPrefix(name, "[]") + "Slice" + } + + typeNames = append(typeNames, name) + } + + sort.Strings(typeNames) + + ret := make([]*FlagType, len(typeNames)) + + for i, typeName := range typeNames { + ret[i] = &FlagType{ + GoType: typeName, + Config: gfs.FlagTypes[typeName], + } + } + + return ret +} + +type FlagTypeConfig struct { + SkipInterfaces []string `yaml:"skip_interfaces"` + StructFields []*FlagStructField `yaml:"struct_fields"` + TypeName string `yaml:"type_name"` + ValuePointer bool `yaml:"value_pointer"` +} + +type FlagStructField struct { + Name string + Type string +} + +type FlagType struct { + GoType string + Config *FlagTypeConfig +} + +func (ft *FlagType) StructFields() []*FlagStructField { + if ft.Config == nil || ft.Config.StructFields == nil { + return []*FlagStructField{} + } + + return ft.Config.StructFields +} + +func (ft *FlagType) ValuePointer() bool { + if ft.Config == nil { + return false + } + + return ft.Config.ValuePointer +} + +func (ft *FlagType) TypeName() string { + return TypeName(ft.GoType, ft.Config) +} + +func (ft *FlagType) GenerateFmtStringerInterface() bool { + return ft.skipInterfaceNamed("fmt.Stringer") +} + +func (ft *FlagType) GenerateFlagInterface() bool { + return ft.skipInterfaceNamed("Flag") +} + +func (ft *FlagType) skipInterfaceNamed(name string) bool { + if ft.Config == nil { + return true + } + + lowName := strings.ToLower(name) + + for _, interfaceName := range ft.Config.SkipInterfaces { + if strings.ToLower(interfaceName) == lowName { + return false + } + } + + return true +} diff --git a/zz_generated.flags.go b/zz_generated.flags.go new file mode 100644 index 0000000..eb980bc --- /dev/null +++ b/zz_generated.flags.go @@ -0,0 +1,507 @@ +// WARNING: this file is generated. DO NOT EDIT + +package cli + +import "time" + +// Float64SliceFlag is a flag with type Float64Slice +type Float64SliceFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *Float64Slice + Destination *Float64Slice + + Aliases []string + EnvVars []string +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Float64SliceFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *Float64SliceFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// GenericFlag is a flag with type Generic +type GenericFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value Generic + Destination *Generic + + Aliases []string + EnvVars []string + + TakesFile bool +} + +// String returns a readable representation of this value (for usage defaults) +func (f *GenericFlag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *GenericFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *GenericFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// Int64SliceFlag is a flag with type Int64Slice +type Int64SliceFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *Int64Slice + Destination *Int64Slice + + Aliases []string + EnvVars []string +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Int64SliceFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *Int64SliceFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// IntSliceFlag is a flag with type IntSlice +type IntSliceFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *IntSlice + Destination *IntSlice + + Aliases []string + EnvVars []string +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *IntSliceFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *IntSliceFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// PathFlag is a flag with type Path +type PathFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value Path + Destination *Path + + Aliases []string + EnvVars []string + + TakesFile bool +} + +// String returns a readable representation of this value (for usage defaults) +func (f *PathFlag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *PathFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *PathFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// StringSliceFlag is a flag with type StringSlice +type StringSliceFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *StringSlice + Destination *StringSlice + + Aliases []string + EnvVars []string + + TakesFile bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *StringSliceFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *StringSliceFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// TimestampFlag is a flag with type Timestamp +type TimestampFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *Timestamp + Destination *Timestamp + + Aliases []string + EnvVars []string + + Layout string +} + +// String returns a readable representation of this value (for usage defaults) +func (f *TimestampFlag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *TimestampFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *TimestampFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// BoolFlag is a flag with type bool +type BoolFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value bool + Destination *bool + + Aliases []string + EnvVars []string +} + +// String returns a readable representation of this value (for usage defaults) +func (f *BoolFlag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *BoolFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *BoolFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// Float64Flag is a flag with type float64 +type Float64Flag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value float64 + Destination *float64 + + Aliases []string + EnvVars []string +} + +// String returns a readable representation of this value (for usage defaults) +func (f *Float64Flag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Float64Flag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *Float64Flag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// IntFlag is a flag with type int +type IntFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value int + Destination *int + + Aliases []string + EnvVars []string +} + +// String returns a readable representation of this value (for usage defaults) +func (f *IntFlag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *IntFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *IntFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// Int64Flag is a flag with type int64 +type Int64Flag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value int64 + Destination *int64 + + Aliases []string + EnvVars []string +} + +// String returns a readable representation of this value (for usage defaults) +func (f *Int64Flag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Int64Flag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *Int64Flag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// StringFlag is a flag with type string +type StringFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value string + Destination *string + + Aliases []string + EnvVars []string + + TakesFile bool +} + +// String returns a readable representation of this value (for usage defaults) +func (f *StringFlag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *StringFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *StringFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// DurationFlag is a flag with type time.Duration +type DurationFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value time.Duration + Destination *time.Duration + + Aliases []string + EnvVars []string +} + +// String returns a readable representation of this value (for usage defaults) +func (f *DurationFlag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *DurationFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *DurationFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// UintFlag is a flag with type uint +type UintFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value uint + Destination *uint + + Aliases []string + EnvVars []string +} + +// String returns a readable representation of this value (for usage defaults) +func (f *UintFlag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *UintFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *UintFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// Uint64Flag is a flag with type uint64 +type Uint64Flag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value uint64 + Destination *uint64 + + Aliases []string + EnvVars []string +} + +// String returns a readable representation of this value (for usage defaults) +func (f *Uint64Flag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Uint64Flag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *Uint64Flag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// vim:ro diff --git a/zz_generated.flags_test.go b/zz_generated.flags_test.go new file mode 100644 index 0000000..791c61f --- /dev/null +++ b/zz_generated.flags_test.go @@ -0,0 +1,116 @@ +// WARNING: this file is generated. DO NOT EDIT + +package cli_test + +import ( + "fmt" + "testing" + + "github.com/urfave/cli/v2" +) + +func TestFloat64SliceFlag_SatisfiesFlagInterface(t *testing.T) { + var _ cli.Flag = &cli.Float64SliceFlag{} +} + +func TestGenericFlag_SatisfiesFlagInterface(t *testing.T) { + var _ cli.Flag = &cli.GenericFlag{} +} + +func TestGenericFlag_SatisfiesFmtStringerInterface(t *testing.T) { + var _ fmt.Stringer = &cli.GenericFlag{} +} + +func TestInt64SliceFlag_SatisfiesFlagInterface(t *testing.T) { + var _ cli.Flag = &cli.Int64SliceFlag{} +} + +func TestIntSliceFlag_SatisfiesFlagInterface(t *testing.T) { + var _ cli.Flag = &cli.IntSliceFlag{} +} + +func TestPathFlag_SatisfiesFlagInterface(t *testing.T) { + var _ cli.Flag = &cli.PathFlag{} +} + +func TestPathFlag_SatisfiesFmtStringerInterface(t *testing.T) { + var _ fmt.Stringer = &cli.PathFlag{} +} + +func TestStringSliceFlag_SatisfiesFlagInterface(t *testing.T) { + var _ cli.Flag = &cli.StringSliceFlag{} +} + +func TestTimestampFlag_SatisfiesFlagInterface(t *testing.T) { + var _ cli.Flag = &cli.TimestampFlag{} +} + +func TestTimestampFlag_SatisfiesFmtStringerInterface(t *testing.T) { + var _ fmt.Stringer = &cli.TimestampFlag{} +} + +func TestBoolFlag_SatisfiesFlagInterface(t *testing.T) { + var _ cli.Flag = &cli.BoolFlag{} +} + +func TestBoolFlag_SatisfiesFmtStringerInterface(t *testing.T) { + var _ fmt.Stringer = &cli.BoolFlag{} +} + +func TestFloat64Flag_SatisfiesFlagInterface(t *testing.T) { + var _ cli.Flag = &cli.Float64Flag{} +} + +func TestFloat64Flag_SatisfiesFmtStringerInterface(t *testing.T) { + var _ fmt.Stringer = &cli.Float64Flag{} +} + +func TestIntFlag_SatisfiesFlagInterface(t *testing.T) { + var _ cli.Flag = &cli.IntFlag{} +} + +func TestIntFlag_SatisfiesFmtStringerInterface(t *testing.T) { + var _ fmt.Stringer = &cli.IntFlag{} +} + +func TestInt64Flag_SatisfiesFlagInterface(t *testing.T) { + var _ cli.Flag = &cli.Int64Flag{} +} + +func TestInt64Flag_SatisfiesFmtStringerInterface(t *testing.T) { + var _ fmt.Stringer = &cli.Int64Flag{} +} + +func TestStringFlag_SatisfiesFlagInterface(t *testing.T) { + var _ cli.Flag = &cli.StringFlag{} +} + +func TestStringFlag_SatisfiesFmtStringerInterface(t *testing.T) { + var _ fmt.Stringer = &cli.StringFlag{} +} + +func TestDurationFlag_SatisfiesFlagInterface(t *testing.T) { + var _ cli.Flag = &cli.DurationFlag{} +} + +func TestDurationFlag_SatisfiesFmtStringerInterface(t *testing.T) { + var _ fmt.Stringer = &cli.DurationFlag{} +} + +func TestUintFlag_SatisfiesFlagInterface(t *testing.T) { + var _ cli.Flag = &cli.UintFlag{} +} + +func TestUintFlag_SatisfiesFmtStringerInterface(t *testing.T) { + var _ fmt.Stringer = &cli.UintFlag{} +} + +func TestUint64Flag_SatisfiesFlagInterface(t *testing.T) { + var _ cli.Flag = &cli.Uint64Flag{} +} + +func TestUint64Flag_SatisfiesFmtStringerInterface(t *testing.T) { + var _ fmt.Stringer = &cli.Uint64Flag{} +} + +// vim:ro From 2630f2642a16f987ece3fd7a2faf688945b5fda5 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 1 May 2022 23:16:59 -0400 Subject: [PATCH 069/125] Add missing go.sum entry plus minor touchups to genflags app --- go.sum | 1 + internal/genflags/cmd/genflags/main.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/go.sum b/go.sum index a96d8e7..e6383eb 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,7 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= 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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/internal/genflags/cmd/genflags/main.go b/internal/genflags/cmd/genflags/main.go index f4aaeba..cad2508 100644 --- a/internal/genflags/cmd/genflags/main.go +++ b/internal/genflags/cmd/genflags/main.go @@ -39,6 +39,8 @@ func main() { } app := &cli.App{ + Name: "genflags", + Usage: "Generate flag types for urfave/cli", Flags: []cli.Flag{ &cli.PathFlag{ Name: "flag-spec-yaml", From adc61ca06b8b8c4537776b5db1052842fa5e3fac Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 1 May 2022 23:18:16 -0400 Subject: [PATCH 070/125] Add another missing go.sum entry (?) --- go.sum | 1 + 1 file changed, 1 insertion(+) diff --git a/go.sum b/go.sum index e6383eb..2c20c28 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,7 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= From 71cd131794231860b15fb029002cd309db40c300 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 1 May 2022 23:36:59 -0400 Subject: [PATCH 071/125] Add more tests to flag generation code --- internal/genflags/generated_test.gotmpl | 9 +- internal/genflags/spec_test.go | 112 ++++++++++++++++++++++ zz_generated.flags_test.go | 119 ++++++++++++++++++------ 3 files changed, 212 insertions(+), 28 deletions(-) create mode 100644 internal/genflags/spec_test.go diff --git a/internal/genflags/generated_test.gotmpl b/internal/genflags/generated_test.gotmpl index 84806f2..44e9ad4 100644 --- a/internal/genflags/generated_test.gotmpl +++ b/internal/genflags/generated_test.gotmpl @@ -5,13 +5,18 @@ package {{.TestPackageName}} {{range .SortedFlagTypes}} {{if .GenerateFlagInterface}} func Test{{.TypeName}}_SatisfiesFlagInterface(t *testing.T) { - var _ {{$.UrfaveCLITestNamespace}}Flag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} + var f {{$.UrfaveCLITestNamespace}}Flag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} + + _ = f.IsSet() + _ = f.Names() } {{end}} {{if .GenerateFmtStringerInterface}} func Test{{.TypeName}}_SatisfiesFmtStringerInterface(t *testing.T) { - var _ fmt.Stringer = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} + var f fmt.Stringer = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} + + _ = f.String() } {{end}} {{end}} diff --git a/internal/genflags/spec_test.go b/internal/genflags/spec_test.go new file mode 100644 index 0000000..25a9c8b --- /dev/null +++ b/internal/genflags/spec_test.go @@ -0,0 +1,112 @@ +package genflags_test + +import ( + "reflect" + "testing" + + "github.com/urfave/cli/v2/internal/genflags" +) + +func TestSpec_SortedFlagTypes(t *testing.T) { + spec := &genflags.Spec{ + FlagTypes: map[string]*genflags.FlagTypeConfig{ + "nerf": &genflags.FlagTypeConfig{}, + "gerf": nil, + }, + } + + actual := spec.SortedFlagTypes() + expected := []*genflags.FlagType{ + { + GoType: "gerf", + Config: nil, + }, + { + GoType: "nerf", + Config: &genflags.FlagTypeConfig{}, + }, + } + if !reflect.DeepEqual(expected, actual) { + t.Errorf("expected %#v, got %#v", expected, actual) + } +} + +func genFlagType() *genflags.FlagType { + return &genflags.FlagType{ + GoType: "blerf", + Config: &genflags.FlagTypeConfig{ + SkipInterfaces: []string{"fmt.Stringer"}, + StructFields: []*genflags.FlagStructField{ + { + Name: "Foibles", + Type: "int", + }, + { + Name: "Hoopled", + Type: "bool", + }, + }, + TypeName: "YeOldeBlerfFlag", + ValuePointer: true, + }, + } +} + +func TestFlagType_StructFields(t *testing.T) { + ft := genFlagType() + + sf := ft.StructFields() + if 2 != len(sf) { + t.Errorf("expected 2 struct fields, got %v", len(sf)) + return + } + + if "Foibles" != sf[0].Name { + t.Errorf("expected struct field order to be retained") + } +} + +func TestFlagType_ValuePointer(t *testing.T) { + ft := genFlagType() + + if !ft.ValuePointer() { + t.Errorf("expected ValuePointer to be true") + return + } + + ft.Config = nil + + if ft.ValuePointer() { + t.Errorf("expected ValuePointer to be false") + } +} + +func TestFlagType_GenerateFmtStringerInterface(t *testing.T) { + ft := genFlagType() + + if ft.GenerateFmtStringerInterface() { + t.Errorf("expected GenerateFmtStringerInterface to be false") + return + } + + ft.Config = nil + + if !ft.GenerateFmtStringerInterface() { + t.Errorf("expected GenerateFmtStringerInterface to be true") + } +} + +func TestFlagType_GenerateFlagInterface(t *testing.T) { + ft := genFlagType() + + if !ft.GenerateFlagInterface() { + t.Errorf("expected GenerateFlagInterface to be true") + return + } + + ft.Config = nil + + if !ft.GenerateFlagInterface() { + t.Errorf("expected GenerateFlagInterface to be true") + } +} diff --git a/zz_generated.flags_test.go b/zz_generated.flags_test.go index 791c61f..b8363d4 100644 --- a/zz_generated.flags_test.go +++ b/zz_generated.flags_test.go @@ -10,107 +10,174 @@ import ( ) func TestFloat64SliceFlag_SatisfiesFlagInterface(t *testing.T) { - var _ cli.Flag = &cli.Float64SliceFlag{} + var f cli.Flag = &cli.Float64SliceFlag{} + + _ = f.IsSet() + _ = f.Names() } func TestGenericFlag_SatisfiesFlagInterface(t *testing.T) { - var _ cli.Flag = &cli.GenericFlag{} + var f cli.Flag = &cli.GenericFlag{} + + _ = f.IsSet() + _ = f.Names() } func TestGenericFlag_SatisfiesFmtStringerInterface(t *testing.T) { - var _ fmt.Stringer = &cli.GenericFlag{} + var f fmt.Stringer = &cli.GenericFlag{} + + _ = f.String() } func TestInt64SliceFlag_SatisfiesFlagInterface(t *testing.T) { - var _ cli.Flag = &cli.Int64SliceFlag{} + var f cli.Flag = &cli.Int64SliceFlag{} + + _ = f.IsSet() + _ = f.Names() } func TestIntSliceFlag_SatisfiesFlagInterface(t *testing.T) { - var _ cli.Flag = &cli.IntSliceFlag{} + var f cli.Flag = &cli.IntSliceFlag{} + + _ = f.IsSet() + _ = f.Names() } func TestPathFlag_SatisfiesFlagInterface(t *testing.T) { - var _ cli.Flag = &cli.PathFlag{} + var f cli.Flag = &cli.PathFlag{} + + _ = f.IsSet() + _ = f.Names() } func TestPathFlag_SatisfiesFmtStringerInterface(t *testing.T) { - var _ fmt.Stringer = &cli.PathFlag{} + var f fmt.Stringer = &cli.PathFlag{} + + _ = f.String() } func TestStringSliceFlag_SatisfiesFlagInterface(t *testing.T) { - var _ cli.Flag = &cli.StringSliceFlag{} + var f cli.Flag = &cli.StringSliceFlag{} + + _ = f.IsSet() + _ = f.Names() } func TestTimestampFlag_SatisfiesFlagInterface(t *testing.T) { - var _ cli.Flag = &cli.TimestampFlag{} + var f cli.Flag = &cli.TimestampFlag{} + + _ = f.IsSet() + _ = f.Names() } func TestTimestampFlag_SatisfiesFmtStringerInterface(t *testing.T) { - var _ fmt.Stringer = &cli.TimestampFlag{} + var f fmt.Stringer = &cli.TimestampFlag{} + + _ = f.String() } func TestBoolFlag_SatisfiesFlagInterface(t *testing.T) { - var _ cli.Flag = &cli.BoolFlag{} + var f cli.Flag = &cli.BoolFlag{} + + _ = f.IsSet() + _ = f.Names() } func TestBoolFlag_SatisfiesFmtStringerInterface(t *testing.T) { - var _ fmt.Stringer = &cli.BoolFlag{} + var f fmt.Stringer = &cli.BoolFlag{} + + _ = f.String() } func TestFloat64Flag_SatisfiesFlagInterface(t *testing.T) { - var _ cli.Flag = &cli.Float64Flag{} + var f cli.Flag = &cli.Float64Flag{} + + _ = f.IsSet() + _ = f.Names() } func TestFloat64Flag_SatisfiesFmtStringerInterface(t *testing.T) { - var _ fmt.Stringer = &cli.Float64Flag{} + var f fmt.Stringer = &cli.Float64Flag{} + + _ = f.String() } func TestIntFlag_SatisfiesFlagInterface(t *testing.T) { - var _ cli.Flag = &cli.IntFlag{} + var f cli.Flag = &cli.IntFlag{} + + _ = f.IsSet() + _ = f.Names() } func TestIntFlag_SatisfiesFmtStringerInterface(t *testing.T) { - var _ fmt.Stringer = &cli.IntFlag{} + var f fmt.Stringer = &cli.IntFlag{} + + _ = f.String() } func TestInt64Flag_SatisfiesFlagInterface(t *testing.T) { - var _ cli.Flag = &cli.Int64Flag{} + var f cli.Flag = &cli.Int64Flag{} + + _ = f.IsSet() + _ = f.Names() } func TestInt64Flag_SatisfiesFmtStringerInterface(t *testing.T) { - var _ fmt.Stringer = &cli.Int64Flag{} + var f fmt.Stringer = &cli.Int64Flag{} + + _ = f.String() } func TestStringFlag_SatisfiesFlagInterface(t *testing.T) { - var _ cli.Flag = &cli.StringFlag{} + var f cli.Flag = &cli.StringFlag{} + + _ = f.IsSet() + _ = f.Names() } func TestStringFlag_SatisfiesFmtStringerInterface(t *testing.T) { - var _ fmt.Stringer = &cli.StringFlag{} + var f fmt.Stringer = &cli.StringFlag{} + + _ = f.String() } func TestDurationFlag_SatisfiesFlagInterface(t *testing.T) { - var _ cli.Flag = &cli.DurationFlag{} + var f cli.Flag = &cli.DurationFlag{} + + _ = f.IsSet() + _ = f.Names() } func TestDurationFlag_SatisfiesFmtStringerInterface(t *testing.T) { - var _ fmt.Stringer = &cli.DurationFlag{} + var f fmt.Stringer = &cli.DurationFlag{} + + _ = f.String() } func TestUintFlag_SatisfiesFlagInterface(t *testing.T) { - var _ cli.Flag = &cli.UintFlag{} + var f cli.Flag = &cli.UintFlag{} + + _ = f.IsSet() + _ = f.Names() } func TestUintFlag_SatisfiesFmtStringerInterface(t *testing.T) { - var _ fmt.Stringer = &cli.UintFlag{} + var f fmt.Stringer = &cli.UintFlag{} + + _ = f.String() } func TestUint64Flag_SatisfiesFlagInterface(t *testing.T) { - var _ cli.Flag = &cli.Uint64Flag{} + var f cli.Flag = &cli.Uint64Flag{} + + _ = f.IsSet() + _ = f.Names() } func TestUint64Flag_SatisfiesFmtStringerInterface(t *testing.T) { - var _ fmt.Stringer = &cli.Uint64Flag{} + var f fmt.Stringer = &cli.Uint64Flag{} + + _ = f.String() } // vim:ro From 3288bec5be9f32485e1e4876d0c7b05be8ccd67e Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 4 May 2022 10:32:29 -0400 Subject: [PATCH 072/125] Introduce a v2.x semver approval gate --- Makefile | 28 +- docs/CONTRIBUTING.md | 23 +- godoc-current.txt | 2090 +++++++++++++++++++++++++++++++++++++++ internal/build/build.go | 91 +- testdata/godoc-v2.x.txt | 2090 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 4299 insertions(+), 23 deletions(-) create mode 100644 godoc-current.txt create mode 100644 testdata/godoc-v2.x.txt diff --git a/Makefile b/Makefile index dd6893a..52e9204 100644 --- a/Makefile +++ b/Makefile @@ -5,30 +5,22 @@ # attention on files that are primarily Go. .PHONY: all -all: generate lint tag-test test check-bin tag-check-bin gfmrun toc +all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun toc v2diff -.PHONY: generate -generate: - go run internal/build/build.go generate - -.PHONY: lint -lint: - go run internal/build/build.go vet +# NOTE: this is a special catch-all rule to run any of the commands +# defined in internal/build/build.go with optional arguments passed +# via GFLAGS (global flags) and FLAGS (command-specific flags), e.g.: +# +# $ make test GFLAGS='--packages cli' +%: + go run internal/build/build.go $(GFLAGS) $* $(FLAGS) .PHONY: tag-test tag-test: go run internal/build/build.go -tags urfave_cli_no_docs test -.PHONY: test -test: - go run internal/build/build.go test - -.PHONY: check-bin -check-bin: - go run internal/build/build.go check-binary-size - -.PHONY: tag-check-bin -tag-check-bin: +.PHONY: tag-check-binary-size +tag-check-binary-size: go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size .PHONY: gfmrun diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 88c6b95..37055ff 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -12,8 +12,9 @@ not get a response for many days. ### semantic versioning adherence -The `urfave/cli` project strives to strictly adhere to semantic versioning. The active -development branches and the milestones and import paths to which they correspond are: +The `urfave/cli` project strives to strictly adhere to [semantic +versioning](https://semver.org/spec/v2.0.0.html). The active development branches and the +milestones and import paths to which they correspond are: #### `main` branch @@ -25,6 +26,10 @@ which **MUST** *only* receive bug fixes and feature *additions*. - :arrow_right: [`v2.x`](https://github.com/urfave/cli/milestone/16) - :arrow_right: `github.com/urfave/cli/v2` +The `main` branch in particular includes tooling to help with keeping the `v2.x` series +backward compatible. More details on this process are in the development workflow section +below. + #### `v1` branch @@ -63,6 +68,20 @@ verify one's changes are harmonious in nature. The same steps are also run durin [continuous integration phase](https://github.com/urfave/cli/blob/main/.github/workflows/cli.yml). +In the event that the `v2diff` target exits non-zero, this is a signal that the public API +surface area has changed. If the changes adhere to semantic versioning, meaning they are +*additions* or *bug fixes*, then manually running the approval step will "promote" the +current `go doc` output: + +```sh +make v2approve +``` + +Because the `generate` step includes updating `godoc-current.txt` and +`testdata/godoc-v2.x.txt`, these changes *MUST* be part of any proposed pull request so +that reviewers have an opportunity to also make an informed decision about the "promotion" +step. + #### generated code A significant portion of the project's source code is generated, with the goal being to diff --git a/godoc-current.txt b/godoc-current.txt new file mode 100644 index 0000000..1a9ab23 --- /dev/null +++ b/godoc-current.txt @@ -0,0 +1,2090 @@ +package cli // import "github.com/urfave/cli/v2" + +Package cli provides a minimal framework for creating and organizing command +line Go applications. cli is designed to be easy to understand and write, +the most simple cli application can be written as follows: + + func main() { + (&cli.App{}).Run(os.Args) + } + +Of course this application does not do much, so let's make this an actual +application: + + func main() { + app := &cli.App{ + Name: "greet", + Usage: "say a greeting", + Action: func(c *cli.Context) error { + fmt.Println("Greetings") + return nil + }, + } + + app.Run(os.Args) + } + +VARIABLES + +var AppHelpTemplate = `NAME: + {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} + +USAGE: + {{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 | 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}} + {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} + +COMMANDS:{{range .VisibleCategories}}{{if .Name}} + {{.Name}}:{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + +GLOBAL OPTIONS: + {{range $index, $option := .VisibleFlags}}{{if $index}} + {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}} + +COPYRIGHT: + {{.Copyright}}{{end}} +` + AppHelpTemplate is the text template for the Default help topic. cli.go uses + text/template to render templates. You can render custom help text by + setting this variable. + +var CommandHelpTemplate = `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{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 | nindent 3 | trim}}{{end}}{{if .VisibleFlags}} + +OPTIONS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}} +` + CommandHelpTemplate is the text template for the command help topic. cli.go + uses text/template to render templates. You can render custom help text by + setting this variable. + +var ErrWriter io.Writer = os.Stderr + ErrWriter is used to write errors to the user. This can be anything + implementing the io.Writer interface and defaults to os.Stderr. + +var FishCompletionTemplate = `# {{ .App.Name }} fish shell completion + +function __fish_{{ .App.Name }}_no_subcommand --description 'Test if there has been any subcommand yet' + for i in (commandline -opc) + if contains -- $i{{ range $v := .AllCommands }} {{ $v }}{{ end }} + return 1 + end + end + return 0 +end + +{{ range $v := .Completions }}{{ $v }} +{{ end }}` +var MarkdownDocTemplate = `{{if gt .SectionNum 0}}% {{ .App.Name }} {{ .SectionNum }} + +{{end}}# NAME + +{{ .App.Name }}{{ if .App.Usage }} - {{ .App.Usage }}{{ end }} + +# SYNOPSIS + +{{ .App.Name }} +{{ if .SynopsisArgs }} +` + "```" + ` +{{ range $v := .SynopsisArgs }}{{ $v }}{{ end }}` + "```" + ` +{{ end }}{{ if .App.Description }} +# DESCRIPTION + +{{ .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 }} +{{ $v }}{{ end }} +{{ end }}{{ if .Commands }} +# COMMANDS +{{ range $v := .Commands }} +{{ $v }}{{ end }}{{ end }}` +var OsExiter = os.Exit + OsExiter is the function used when the app exits. If not set defaults to + os.Exit. + +var SubcommandHelpTemplate = `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{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 | nindent 3 | trim}}{{end}} + +COMMANDS:{{range .VisibleCategories}}{{if .Name}} + {{.Name}}:{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + +OPTIONS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}} +` + SubcommandHelpTemplate is the text template for the subcommand help topic. + cli.go uses text/template to render templates. You can render custom help + text by setting this variable. + +var VersionPrinter = printVersion + VersionPrinter prints the version for the App + +var HelpPrinter helpPrinter = printHelp + HelpPrinter is a function that writes the help output. If not set + explicitly, this calls HelpPrinterCustom using only the default template + functions. + + If custom logic for printing help is required, this function can be + overridden. If the ExtraInfo field is defined on an App, this function + should not be modified, as HelpPrinterCustom will be used directly in order + to capture the extra information. + +var HelpPrinterCustom helpPrinterCustom = printHelpCustom + HelpPrinterCustom is a function that writes the help output. It is used as + the default implementation of HelpPrinter, and may be called directly if the + ExtraInfo field is set on an App. + + +FUNCTIONS + +func DefaultAppComplete(cCtx *Context) + DefaultAppComplete prints the list of subcommands as the default app + completion method + +func DefaultCompleteWithFlags(cmd *Command) func(cCtx *Context) +func FlagNames(name string, aliases []string) []string +func HandleAction(action interface{}, cCtx *Context) (err error) + HandleAction attempts to figure out which Action signature was used. If it's + an ActionFunc or a func with the legacy signature for Action, the func is + run! + +func HandleExitCoder(err error) + HandleExitCoder handles errors implementing ExitCoder by printing their + message and calling OsExiter with the given exit code. + + If the given error instead implements MultiError, each error will be checked + for the ExitCoder interface, and OsExiter will be called with the last exit + code found, or exit code 1 if no ExitCoder is found. + + This function is the default error-handling behavior for an App. + +func ShowAppHelp(cCtx *Context) error + ShowAppHelp is an action that displays the help. + +func ShowAppHelpAndExit(c *Context, exitCode int) + ShowAppHelpAndExit - Prints the list of subcommands for the app and exits + with exit code. + +func ShowCommandCompletions(ctx *Context, command string) + ShowCommandCompletions prints the custom completions for a given command + +func ShowCommandHelp(ctx *Context, command string) error + ShowCommandHelp prints help for the given command + +func ShowCommandHelpAndExit(c *Context, command string, code int) + ShowCommandHelpAndExit - exits with code after showing help + +func ShowCompletions(cCtx *Context) + ShowCompletions prints the lists of commands within a given context + +func ShowSubcommandHelp(cCtx *Context) error + ShowSubcommandHelp prints help for the given subcommand + +func ShowSubcommandHelpAndExit(c *Context, exitCode int) + ShowSubcommandHelpAndExit - Prints help for the given subcommand and exits + with exit code. + +func ShowVersion(cCtx *Context) + ShowVersion prints the version number of the App + + +TYPES + +type ActionFunc func(*Context) error + ActionFunc is the action to execute when no subcommands are specified + +type AfterFunc func(*Context) error + AfterFunc is an action to execute after any subcommands are run, but after + the subcommand has finished it is run even if Action() panics + +type App struct { + // The name of the program. Defaults to path.Base(os.Args[0]) + Name string + // Full name of command for help, defaults to Name + HelpName string + // Description of the program. + Usage string + // Text to override the USAGE section of help + UsageText string + // Description of the program argument format. + ArgsUsage string + // Version of the program + Version string + // Description of the program + Description string + // List of commands to execute + Commands []*Command + // List of flags to parse + Flags []Flag + // Boolean to enable bash completion commands + EnableBashCompletion bool + // Boolean to hide built-in help command and help flag + HideHelp bool + // Boolean to hide built-in help command but keep help flag. + // Ignored if HideHelp is true. + HideHelpCommand bool + // Boolean to hide built-in version flag and the VERSION section of help + HideVersion bool + + // An action to execute when the shell completion flag is set + BashComplete BashCompleteFunc + // An action to execute before any subcommands are run, but after the context is ready + // If a non-nil error is returned, no subcommands are run + Before BeforeFunc + // An action to execute after any subcommands are run, but after the subcommand has finished + // It is run even if Action() panics + After AfterFunc + // The action to execute when no subcommands are specified + Action ActionFunc + // Execute this function if the proper command cannot be found + CommandNotFound CommandNotFoundFunc + // Execute this function if a usage error occurs + OnUsageError OnUsageErrorFunc + // Compilation date + Compiled time.Time + // List of all authors who contributed + 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 + ErrWriter io.Writer + // ExitErrHandler processes any error encountered while running an App before + // it is returned to the caller. If no function is provided, HandleExitCoder + // is used as the default behavior. + ExitErrHandler ExitErrHandlerFunc + // Other custom info + Metadata map[string]interface{} + // Carries a function which returns app specific info. + ExtraInfo func() map[string]string + // CustomAppHelpTemplate the text template for app help topic. + // cli.go uses text/template to render templates. You can + // render custom help text by setting this variable. + CustomAppHelpTemplate string + // Boolean to enable short-option handling so user can combine several + // single-character bool arguments into one + // i.e. foobar -o -v -> foobar -ov + UseShortOptionHandling bool + + // Has unexported fields. +} + App is the main structure of a cli application. It is recommended that an + app be created with the cli.NewApp() function + +func NewApp() *App + NewApp creates a new cli Application with some reasonable defaults for Name, + Usage, Version and Action. + +func (a *App) Command(name string) *Command + Command returns the named command on App. Returns nil if the command does + not exist + +func (a *App) Run(arguments []string) (err error) + Run is the entry point to the cli app. Parses the arguments slice and routes + to the proper flag/args combination + +func (a *App) RunAndExitOnError() + 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 error + code in the cli.ExitCoder + +func (a *App) RunAsSubcommand(ctx *Context) (err error) + RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() + to generate command-specific flags + +func (a *App) RunContext(ctx context.Context, arguments []string) (err error) + RunContext is like Run except it takes a Context that will be passed to its + commands and sub-commands. Through this, you can propagate timeouts and + cancellation requests + +func (a *App) Setup() + Setup runs initialization code to ensure all data structures are ready for + `Run` or inspection prior to `Run`. It is internally called by `Run`, but + will return early if setup has already happened. + +func (a *App) ToFishCompletion() (string, error) + ToFishCompletion creates a fish completion string for the `*App` The + function errors if either parsing or writing of the string fails. + +func (a *App) ToMan() (string, error) + ToMan creates a man page string for the `*App` The function errors if either + parsing or writing of the string fails. + +func (a *App) ToManWithSection(sectionNumber int) (string, error) + 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) ToMarkdown() (string, error) + ToMarkdown creates a markdown string for the `*App` The function errors if + either parsing or writing of the string fails. + +func (a *App) VisibleCategories() []CommandCategory + VisibleCategories returns a slice of categories and commands that are + Hidden=false + +func (a *App) VisibleCommands() []*Command + VisibleCommands returns a slice of the Commands with Hidden=false + +func (a *App) VisibleFlags() []Flag + VisibleFlags returns a slice of the Flags with Hidden=false + +type Args interface { + // Get returns the nth argument, or else a blank string + Get(n int) string + // First returns the first argument, or else a blank string + First() string + // Tail returns the rest of the arguments (not the first one) + // or else an empty string slice + Tail() []string + // Len returns the length of the wrapped slice + Len() int + // Present checks if there are any arguments present + Present() bool + // Slice returns a copy of the internal slice + Slice() []string +} + +type Author struct { + Name string // The Authors name + Email string // The Authors email +} + Author represents someone who has contributed to a cli project. + +func (a *Author) String() string + String makes Author comply to the Stringer interface, to allow an easy print + in the templating process + +type BashCompleteFunc func(*Context) + BashCompleteFunc is an action to execute when the shell completion flag is + set + +type BeforeFunc func(*Context) error + BeforeFunc is an action to execute before any subcommands are run, but after + the context is ready if a non-nil error is returned, no subcommands are run + +type BoolFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value bool + Destination *bool + + Aliases []string + EnvVars []string +} + BoolFlag is a flag with type bool + +func (f *BoolFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *BoolFlag) Get(ctx *Context) bool + Get returns the flag’s value in the given Context. + +func (f *BoolFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *BoolFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *BoolFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *BoolFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *BoolFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *BoolFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *BoolFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *BoolFlag) Names() []string + Names returns the names of the flag + +func (f *BoolFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *BoolFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type Command struct { + // The name of the command + Name string + // A list of aliases for the command + Aliases []string + // A short description of the usage of this command + Usage string + // Custom text to show on USAGE section of help + UsageText string + // A longer explanation of how the command works + Description string + // A short description of the arguments of this command + ArgsUsage string + // The category the command is part of + Category string + // The function to call when checking for bash command completions + BashComplete BashCompleteFunc + // An action to execute before any sub-subcommands are run, but after the context is ready + // If a non-nil error is returned, no sub-subcommands are run + Before BeforeFunc + // An action to execute after any subcommands are run, but after the subcommand has finished + // It is run even if Action() panics + After AfterFunc + // The function to call when this command is invoked + Action ActionFunc + // Execute this function if a usage error occurs. + OnUsageError OnUsageErrorFunc + // List of child commands + Subcommands []*Command + // List of flags to parse + Flags []Flag + // Treat all flags as normal arguments if true + SkipFlagParsing bool + // Boolean to hide built-in help command and help flag + HideHelp bool + // Boolean to hide built-in help command but keep help flag + // Ignored if HideHelp is true. + HideHelpCommand bool + // Boolean to hide this command from help or completion + Hidden bool + // Boolean to enable short-option handling so user can combine several + // single-character bool arguments into one + // i.e. foobar -o -v -> foobar -ov + UseShortOptionHandling bool + + // Full name of command for help, defaults to full command name, including parent commands. + HelpName string + + // CustomHelpTemplate the text template for the command help topic. + // cli.go uses text/template to render templates. You can + // render custom help text by setting this variable. + CustomHelpTemplate string + // Has unexported fields. +} + Command is a subcommand for a cli.App. + +func (c *Command) FullName() string + FullName returns the full name of the command. For subcommands this ensures + that parent commands are part of the command path + +func (c *Command) HasName(name string) bool + HasName returns true if Command.Name matches given name + +func (c *Command) Names() []string + Names returns the names including short names and aliases. + +func (c *Command) Run(ctx *Context) (err error) + Run invokes the command given the context, parses ctx.Args() to generate + command-specific flags + +func (c *Command) VisibleFlags() []Flag + VisibleFlags returns a slice of the Flags with Hidden=false + +type CommandCategories interface { + // AddCommand adds a command to a category, creating a new category if necessary. + AddCommand(category string, command *Command) + // categories returns a copy of the category slice + Categories() []CommandCategory +} + CommandCategories interface allows for category manipulation + +type CommandCategory interface { + // Name returns the category name string + Name() string + // VisibleCommands returns a slice of the Commands with Hidden=false + VisibleCommands() []*Command +} + CommandCategory is a category containing commands. + +type CommandNotFoundFunc func(*Context, string) + CommandNotFoundFunc is executed if the proper command cannot be found + +type Commands []*Command + +type CommandsByName []*Command + +func (c CommandsByName) Len() int + +func (c CommandsByName) Less(i, j int) bool + +func (c CommandsByName) Swap(i, j int) + +type Context struct { + context.Context + App *App + Command *Command + + // Has unexported fields. +} + Context is a type that is passed through to each Handler action in a cli + application. Context can be used to retrieve context-specific args and + parsed command-line options. + +func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context + NewContext creates a new context. For use in when invoking an App or Command + action. + +func (cCtx *Context) Args() Args + Args returns the command line arguments associated with the context. + +func (cCtx *Context) Bool(name string) bool + Bool looks up the value of a local BoolFlag, returns false if not found + +func (cCtx *Context) Duration(name string) time.Duration + Duration looks up the value of a local DurationFlag, returns 0 if not found + +func (cCtx *Context) FlagNames() []string + FlagNames returns a slice of flag names used by the this context and all of + its parent contexts. + +func (cCtx *Context) Float64(name string) float64 + Float64 looks up the value of a local Float64Flag, returns 0 if not found + +func (cCtx *Context) Float64Slice(name string) []float64 + Float64Slice looks up the value of a local Float64SliceFlag, returns nil if + not found + +func (cCtx *Context) Generic(name string) interface{} + Generic looks up the value of a local GenericFlag, returns nil if not found + +func (cCtx *Context) Int(name string) int + Int looks up the value of a local IntFlag, returns 0 if not found + +func (cCtx *Context) Int64(name string) int64 + Int64 looks up the value of a local Int64Flag, returns 0 if not found + +func (cCtx *Context) Int64Slice(name string) []int64 + Int64Slice looks up the value of a local Int64SliceFlag, returns nil if not + found + +func (cCtx *Context) IntSlice(name string) []int + IntSlice looks up the value of a local IntSliceFlag, returns nil if not + found + +func (cCtx *Context) IsSet(name string) bool + IsSet determines if the flag was actually set + +func (cCtx *Context) Lineage() []*Context + Lineage returns *this* context and all of its ancestor contexts in order + from child to parent + +func (cCtx *Context) LocalFlagNames() []string + LocalFlagNames returns a slice of flag names used in this context. + +func (cCtx *Context) NArg() int + NArg returns the number of the command line arguments. + +func (cCtx *Context) NumFlags() int + NumFlags returns the number of flags set + +func (cCtx *Context) Path(name string) string + Path looks up the value of a local PathFlag, returns "" if not found + +func (cCtx *Context) Set(name, value string) error + Set sets a context flag to a value. + +func (cCtx *Context) String(name string) string + String looks up the value of a local StringFlag, returns "" if not found + +func (cCtx *Context) StringSlice(name string) []string + StringSlice looks up the value of a local StringSliceFlag, returns nil if + not found + +func (cCtx *Context) Timestamp(name string) *time.Time + Timestamp gets the timestamp from a flag name + +func (cCtx *Context) Uint(name string) uint + Uint looks up the value of a local UintFlag, returns 0 if not found + +func (cCtx *Context) Uint64(name string) uint64 + Uint64 looks up the value of a local Uint64Flag, returns 0 if not found + +func (cCtx *Context) Value(name string) interface{} + Value returns the value of the flag corresponding to `name` + +type DocGenerationFlag interface { + Flag + + // TakesValue returns true if the flag takes a value, otherwise false + TakesValue() bool + + // GetUsage returns the usage string for the flag + GetUsage() string + + // GetValue returns the flags value as string representation and an empty + // string if the flag takes no value at all. + GetValue() string + + // GetDefaultText returns the default text for this flag + GetDefaultText() string + + // GetEnvVars returns the env vars for this flag + GetEnvVars() []string +} + DocGenerationFlag is an interface that allows documentation generation for + the flag + +type DurationFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value time.Duration + Destination *time.Duration + + Aliases []string + EnvVars []string +} + DurationFlag is a flag with type time.Duration + +func (f *DurationFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *DurationFlag) Get(ctx *Context) time.Duration + Get returns the flag’s value in the given Context. + +func (f *DurationFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *DurationFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *DurationFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *DurationFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *DurationFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *DurationFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *DurationFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *DurationFlag) Names() []string + Names returns the names of the flag + +func (f *DurationFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *DurationFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type ErrorFormatter interface { + Format(s fmt.State, verb rune) +} + ErrorFormatter is the interface that will suitably format the error output + +type ExitCoder interface { + error + ExitCode() int +} + ExitCoder is the interface checked by `App` and `Command` for a custom exit + code + +func Exit(message interface{}, exitCode int) ExitCoder + Exit wraps a message and exit code into an error, which by default is + handled with a call to os.Exit during default error handling. + + This is the simplest way to trigger a non-zero exit code for an App without + having to call os.Exit manually. During testing, this behavior can be + avoided by overiding the ExitErrHandler function on an App or the + package-global OsExiter function. + +func NewExitError(message interface{}, exitCode int) ExitCoder + NewExitError calls Exit to create a new ExitCoder. + + Deprecated: This function is a duplicate of Exit and will eventually be + removed. + +type ExitErrHandlerFunc func(cCtx *Context, err error) + ExitErrHandlerFunc is executed if provided in order to handle exitError + values returned by Actions and Before/After functions. + +type Flag interface { + fmt.Stringer + // Apply Flag settings to the given flag set + Apply(*flag.FlagSet) error + Names() []string + IsSet() bool +} + Flag is a common interface related to parsing flags in cli. For more + advanced flag parsing techniques, it is recommended that this interface be + implemented. + +var BashCompletionFlag Flag = &BoolFlag{ + Name: "generate-bash-completion", + Hidden: true, +} + BashCompletionFlag enables bash-completion for all commands and subcommands + +var HelpFlag Flag = &BoolFlag{ + Name: "help", + Aliases: []string{"h"}, + Usage: "show help", +} + HelpFlag prints the help for all commands and subcommands. Set to nil to + disable the flag. The subcommand will still be added unless HideHelp or + HideHelpCommand is set to true. + +var VersionFlag Flag = &BoolFlag{ + Name: "version", + Aliases: []string{"v"}, + Usage: "print the version", +} + VersionFlag prints the version for the application + +type FlagEnvHintFunc func(envVars []string, str string) string + FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help + with the environment variable details. + +var FlagEnvHinter FlagEnvHintFunc = withEnvHint + FlagEnvHinter annotates flag help message with the environment variable + details. This is used by the default FlagStringer. + +type FlagFileHintFunc func(filePath, str string) string + FlagFileHintFunc is used by the default FlagStringFunc to annotate flag help + with the file path details. + +var FlagFileHinter FlagFileHintFunc = withFileHint + FlagFileHinter annotates flag help message with the environment variable + details. This is used by the default FlagStringer. + +type FlagNamePrefixFunc func(fullName []string, placeholder string) string + FlagNamePrefixFunc is used by the default FlagStringFunc to create prefix + text for a flag's full name. + +var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames + FlagNamePrefixer converts a full flag name and its placeholder into the help + message flag prefix. This is used by the default FlagStringer. + +type FlagStringFunc func(Flag) string + FlagStringFunc is used by the help generation to display a flag, which is + expected to be a single line. + +var FlagStringer FlagStringFunc = stringifyFlag + FlagStringer converts a flag definition to a string. This is used by help to + display a flag. + +type FlagsByName []Flag + FlagsByName is a slice of Flag. + +func (f FlagsByName) Len() int + +func (f FlagsByName) Less(i, j int) bool + +func (f FlagsByName) Swap(i, j int) + +type Float64Flag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value float64 + Destination *float64 + + Aliases []string + EnvVars []string +} + Float64Flag is a flag with type float64 + +func (f *Float64Flag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *Float64Flag) Get(ctx *Context) float64 + Get returns the flag’s value in the given Context. + +func (f *Float64Flag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *Float64Flag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *Float64Flag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *Float64Flag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *Float64Flag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *Float64Flag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *Float64Flag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *Float64Flag) Names() []string + Names returns the names of the flag + +func (f *Float64Flag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Float64Flag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type Float64Slice struct { + // Has unexported fields. +} + Float64Slice wraps []float64 to satisfy flag.Value + +func NewFloat64Slice(defaults ...float64) *Float64Slice + NewFloat64Slice makes a *Float64Slice with default values + +func (f *Float64Slice) Get() interface{} + Get returns the slice of float64s set by this flag + +func (f *Float64Slice) Serialize() string + Serialize allows Float64Slice to fulfill Serializer + +func (f *Float64Slice) Set(value string) error + Set parses the value into a float64 and appends it to the list of values + +func (f *Float64Slice) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Float64Slice) Value() []float64 + Value returns the slice of float64s set by this flag + +type Float64SliceFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *Float64Slice + Destination *Float64Slice + + Aliases []string + EnvVars []string +} + Float64SliceFlag is a flag with type Float64Slice + +func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *Float64SliceFlag) Get(ctx *Context) []float64 + Get returns the flag’s value in the given Context. + +func (f *Float64SliceFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *Float64SliceFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *Float64SliceFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *Float64SliceFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *Float64SliceFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *Float64SliceFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *Float64SliceFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *Float64SliceFlag) Names() []string + Names returns the names of the flag + +func (f *Float64SliceFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Float64SliceFlag) TakesValue() bool + TakesValue returns true if the flag takes a value, otherwise false + +type Generic interface { + Set(value string) error + String() string +} + Generic is a generic parseable type identified by a specific flag + +type GenericFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value Generic + Destination *Generic + + Aliases []string + EnvVars []string + + TakesFile bool +} + GenericFlag is a flag with type Generic + +func (f GenericFlag) Apply(set *flag.FlagSet) error + Apply takes the flagset and calls Set on the generic flag with the value + provided by the user for parsing by the flag + +func (f *GenericFlag) Get(ctx *Context) interface{} + Get returns the flag’s value in the given Context. + +func (f *GenericFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *GenericFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *GenericFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *GenericFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *GenericFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *GenericFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *GenericFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *GenericFlag) Names() []string + Names returns the names of the flag + +func (f *GenericFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *GenericFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type Int64Flag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value int64 + Destination *int64 + + Aliases []string + EnvVars []string +} + Int64Flag is a flag with type int64 + +func (f *Int64Flag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *Int64Flag) Get(ctx *Context) int64 + Get returns the flag’s value in the given Context. + +func (f *Int64Flag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *Int64Flag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *Int64Flag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *Int64Flag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *Int64Flag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *Int64Flag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *Int64Flag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *Int64Flag) Names() []string + Names returns the names of the flag + +func (f *Int64Flag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Int64Flag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type Int64Slice struct { + // Has unexported fields. +} + Int64Slice wraps []int64 to satisfy flag.Value + +func NewInt64Slice(defaults ...int64) *Int64Slice + NewInt64Slice makes an *Int64Slice with default values + +func (i *Int64Slice) Get() interface{} + Get returns the slice of ints set by this flag + +func (i *Int64Slice) Serialize() string + Serialize allows Int64Slice to fulfill Serializer + +func (i *Int64Slice) Set(value string) error + Set parses the value into an integer and appends it to the list of values + +func (i *Int64Slice) String() string + String returns a readable representation of this value (for usage defaults) + +func (i *Int64Slice) Value() []int64 + Value returns the slice of ints set by this flag + +type Int64SliceFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *Int64Slice + Destination *Int64Slice + + Aliases []string + EnvVars []string +} + Int64SliceFlag is a flag with type Int64Slice + +func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *Int64SliceFlag) Get(ctx *Context) []int64 + Get returns the flag’s value in the given Context. + +func (f *Int64SliceFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *Int64SliceFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f Int64SliceFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *Int64SliceFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *Int64SliceFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *Int64SliceFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *Int64SliceFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *Int64SliceFlag) Names() []string + Names returns the names of the flag + +func (f *Int64SliceFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Int64SliceFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type IntFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value int + Destination *int + + Aliases []string + EnvVars []string +} + IntFlag is a flag with type int + +func (f *IntFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *IntFlag) Get(ctx *Context) int + Get returns the flag’s value in the given Context. + +func (f *IntFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *IntFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *IntFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *IntFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *IntFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *IntFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *IntFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *IntFlag) Names() []string + Names returns the names of the flag + +func (f *IntFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *IntFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type IntSlice struct { + // Has unexported fields. +} + IntSlice wraps []int to satisfy flag.Value + +func NewIntSlice(defaults ...int) *IntSlice + NewIntSlice makes an *IntSlice with default values + +func (i *IntSlice) Get() interface{} + Get returns the slice of ints set by this flag + +func (i *IntSlice) Serialize() string + Serialize allows IntSlice to fulfill Serializer + +func (i *IntSlice) Set(value string) error + Set parses the value into an integer and appends it to the list of values + +func (i *IntSlice) SetInt(value int) + TODO: Consistently have specific Set function for Int64 and Float64 ? SetInt + directly adds an integer to the list of values + +func (i *IntSlice) String() string + String returns a readable representation of this value (for usage defaults) + +func (i *IntSlice) Value() []int + Value returns the slice of ints set by this flag + +type IntSliceFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *IntSlice + Destination *IntSlice + + Aliases []string + EnvVars []string +} + IntSliceFlag is a flag with type IntSlice + +func (f *IntSliceFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *IntSliceFlag) Get(ctx *Context) []int + Get returns the flag’s value in the given Context. + +func (f *IntSliceFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *IntSliceFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f IntSliceFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *IntSliceFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *IntSliceFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *IntSliceFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *IntSliceFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *IntSliceFlag) Names() []string + Names returns the names of the flag + +func (f *IntSliceFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *IntSliceFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type MultiError interface { + error + Errors() []error +} + MultiError is an error that wraps multiple errors. + +type OnUsageErrorFunc func(cCtx *Context, err error, isSubcommand bool) error + 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. + +type Path = string + +type PathFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value Path + Destination *Path + + Aliases []string + EnvVars []string + + TakesFile bool +} + PathFlag is a flag with type Path + +func (f *PathFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *PathFlag) Get(ctx *Context) string + Get returns the flag’s value in the given Context. + +func (f *PathFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *PathFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *PathFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *PathFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *PathFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *PathFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *PathFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *PathFlag) Names() []string + Names returns the names of the flag + +func (f *PathFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *PathFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type RequiredFlag interface { + Flag + + IsRequired() bool +} + RequiredFlag is an interface that allows us to mark flags as required it + allows flags required flags to be backwards compatible with the Flag + interface + +type Serializer interface { + Serialize() string +} + Serializer is used to circumvent the limitations of flag.FlagSet.Set + +type StringFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value string + Destination *string + + Aliases []string + EnvVars []string + + TakesFile bool +} + StringFlag is a flag with type string + +func (f *StringFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *StringFlag) Get(ctx *Context) string + Get returns the flag’s value in the given Context. + +func (f *StringFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *StringFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *StringFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *StringFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *StringFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *StringFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *StringFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *StringFlag) Names() []string + Names returns the names of the flag + +func (f *StringFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *StringFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type StringSlice struct { + // Has unexported fields. +} + StringSlice wraps a []string to satisfy flag.Value + +func NewStringSlice(defaults ...string) *StringSlice + NewStringSlice creates a *StringSlice with default values + +func (s *StringSlice) Get() interface{} + Get returns the slice of strings set by this flag + +func (s *StringSlice) Serialize() string + Serialize allows StringSlice to fulfill Serializer + +func (s *StringSlice) Set(value string) error + Set appends the string value to the list of values + +func (s *StringSlice) String() string + String returns a readable representation of this value (for usage defaults) + +func (s *StringSlice) Value() []string + Value returns the slice of strings set by this flag + +type StringSliceFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *StringSlice + Destination *StringSlice + + Aliases []string + EnvVars []string + + TakesFile bool +} + StringSliceFlag is a flag with type StringSlice + +func (f *StringSliceFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *StringSliceFlag) Get(ctx *Context) []string + Get returns the flag’s value in the given Context. + +func (f *StringSliceFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *StringSliceFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *StringSliceFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *StringSliceFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *StringSliceFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *StringSliceFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *StringSliceFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *StringSliceFlag) Names() []string + Names returns the names of the flag + +func (f *StringSliceFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *StringSliceFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type Timestamp struct { + // Has unexported fields. +} + Timestamp wrap to satisfy golang's flag interface. + +func NewTimestamp(timestamp time.Time) *Timestamp + Timestamp constructor + +func (t *Timestamp) Get() interface{} + Get returns the flag structure + +func (t *Timestamp) Set(value string) error + Parses the string value to timestamp + +func (t *Timestamp) SetLayout(layout string) + Set the timestamp string layout for future parsing + +func (t *Timestamp) SetTimestamp(value time.Time) + Set the timestamp value directly + +func (t *Timestamp) String() string + String returns a readable representation of this value (for usage defaults) + +func (t *Timestamp) Value() *time.Time + Value returns the timestamp value stored in the flag + +type TimestampFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *Timestamp + Destination *Timestamp + + Aliases []string + EnvVars []string + + Layout string +} + TimestampFlag is a flag with type Timestamp + +func (f *TimestampFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *TimestampFlag) Get(ctx *Context) *time.Time + Get returns the flag’s value in the given Context. + +func (f *TimestampFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *TimestampFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *TimestampFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *TimestampFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *TimestampFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *TimestampFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *TimestampFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *TimestampFlag) Names() []string + Names returns the names of the flag + +func (f *TimestampFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *TimestampFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type Uint64Flag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value uint64 + Destination *uint64 + + Aliases []string + EnvVars []string +} + Uint64Flag is a flag with type uint64 + +func (f *Uint64Flag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *Uint64Flag) Get(ctx *Context) uint64 + Get returns the flag’s value in the given Context. + +func (f *Uint64Flag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *Uint64Flag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *Uint64Flag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *Uint64Flag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *Uint64Flag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *Uint64Flag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *Uint64Flag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *Uint64Flag) Names() []string + Names returns the names of the flag + +func (f *Uint64Flag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Uint64Flag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type UintFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value uint + Destination *uint + + Aliases []string + EnvVars []string +} + UintFlag is a flag with type uint + +func (f *UintFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *UintFlag) Get(ctx *Context) uint + Get returns the flag’s value in the given Context. + +func (f *UintFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *UintFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *UintFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *UintFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *UintFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *UintFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *UintFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *UintFlag) Names() []string + Names returns the names of the flag + +func (f *UintFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *UintFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type VisibleFlag interface { + Flag + + // IsVisible returns true if the flag is not hidden, otherwise false + IsVisible() bool +} + VisibleFlag is an interface that allows to check if a flag is visible + +package altsrc // import "github.com/urfave/cli/v2/altsrc" + + +FUNCTIONS + +func ApplyInputSourceValues(cCtx *cli.Context, inputSourceContext InputSourceContext, flags []cli.Flag) error + ApplyInputSourceValues iterates over all provided flags and executes + ApplyInputSourceValue on flags implementing the FlagInputSourceExtension + interface to initialize these flags to an alternate input source. + +func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceContext, error)) cli.BeforeFunc + InitInputSource is used to to setup an InputSourceContext on a cli.Command + Before method. It will create a new input source based on the func provided. + If there is no error it will then apply the new input source to any flags + that are supported by the input source + +func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(cCtx *cli.Context) (InputSourceContext, error)) cli.BeforeFunc + InitInputSourceWithContext is used to to setup an InputSourceContext on a + cli.Command Before method. It will create a new input source based on the + func provided with potentially using existing cli.Context values to + initialize itself. If there is no error it will then apply the new input + source to any flags that are supported by the input source + +func NewJSONSourceFromFlagFunc(flag string) func(c *cli.Context) (InputSourceContext, error) + NewJSONSourceFromFlagFunc returns a func that takes a cli.Context and + returns an InputSourceContext suitable for retrieving config variables from + a file containing JSON data with the file name defined by the given flag. + +func NewTomlSourceFromFlagFunc(flagFileName string) func(cCtx *cli.Context) (InputSourceContext, error) + NewTomlSourceFromFlagFunc creates a new TOML InputSourceContext from a + provided flag name and source context. + +func NewYamlSourceFromFlagFunc(flagFileName string) func(cCtx *cli.Context) (InputSourceContext, error) + NewYamlSourceFromFlagFunc creates a new Yaml InputSourceContext from a + provided flag name and source context. + + +TYPES + +type BoolFlag struct { + *cli.BoolFlag + // Has unexported fields. +} + BoolFlag is the flag type that wraps cli.BoolFlag to allow for other values + to be specified + +func NewBoolFlag(fl *cli.BoolFlag) *BoolFlag + NewBoolFlag creates a new BoolFlag + +func (f *BoolFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + BoolFlag.Apply + +func (f *BoolFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a Bool value to the flagSet if required + +type DurationFlag struct { + *cli.DurationFlag + // Has unexported fields. +} + DurationFlag is the flag type that wraps cli.DurationFlag to allow for other + values to be specified + +func NewDurationFlag(fl *cli.DurationFlag) *DurationFlag + NewDurationFlag creates a new DurationFlag + +func (f *DurationFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + DurationFlag.Apply + +func (f *DurationFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a Duration value to the flagSet if required + +type FlagInputSourceExtension interface { + cli.Flag + ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error +} + FlagInputSourceExtension is an extension interface of cli.Flag that allows a + value to be set on the existing parsed flags. + +type Float64Flag struct { + *cli.Float64Flag + // Has unexported fields. +} + Float64Flag is the flag type that wraps cli.Float64Flag to allow for other + values to be specified + +func NewFloat64Flag(fl *cli.Float64Flag) *Float64Flag + NewFloat64Flag creates a new Float64Flag + +func (f *Float64Flag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + Float64Flag.Apply + +func (f *Float64Flag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a Float64 value to the flagSet if required + +type Float64SliceFlag struct { + *cli.Float64SliceFlag + // Has unexported fields. +} + Float64SliceFlag is the flag type that wraps cli.Float64SliceFlag to allow + for other values to be specified + +func NewFloat64SliceFlag(fl *cli.Float64SliceFlag) *Float64SliceFlag + NewFloat64SliceFlag creates a new Float64SliceFlag + +func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + Float64SliceFlag.Apply + +type GenericFlag struct { + *cli.GenericFlag + // Has unexported fields. +} + GenericFlag is the flag type that wraps cli.GenericFlag to allow for other + values to be specified + +func NewGenericFlag(fl *cli.GenericFlag) *GenericFlag + NewGenericFlag creates a new GenericFlag + +func (f *GenericFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + GenericFlag.Apply + +func (f *GenericFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a generic value to the flagSet if required + +type InputSourceContext interface { + Source() string + + Int(name string) (int, error) + Duration(name string) (time.Duration, error) + Float64(name string) (float64, error) + String(name string) (string, error) + StringSlice(name string) ([]string, error) + IntSlice(name string) ([]int, error) + Generic(name string) (cli.Generic, error) + Bool(name string) (bool, error) + + // Has unexported methods. +} + InputSourceContext is an interface used to allow other input sources to be + implemented as needed. + + Source returns an identifier for the input source. In case of file source it + should return path to the file. + +func NewJSONSource(data []byte) (InputSourceContext, error) + NewJSONSource returns an InputSourceContext suitable for retrieving config + variables from raw JSON data. + +func NewJSONSourceFromFile(f string) (InputSourceContext, error) + NewJSONSourceFromFile returns an InputSourceContext suitable for retrieving + config variables from a file (or url) containing JSON data. + +func NewJSONSourceFromReader(r io.Reader) (InputSourceContext, error) + NewJSONSourceFromReader returns an InputSourceContext suitable for + retrieving config variables from an io.Reader that returns JSON data. + +func NewTomlSourceFromFile(file string) (InputSourceContext, error) + NewTomlSourceFromFile creates a new TOML InputSourceContext from a filepath. + +func NewYamlSourceFromFile(file string) (InputSourceContext, error) + NewYamlSourceFromFile creates a new Yaml InputSourceContext from a filepath. + +type Int64Flag struct { + *cli.Int64Flag + // Has unexported fields. +} + Int64Flag is the flag type that wraps cli.Int64Flag to allow for other + values to be specified + +func NewInt64Flag(fl *cli.Int64Flag) *Int64Flag + NewInt64Flag creates a new Int64Flag + +func (f *Int64Flag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + Int64Flag.Apply + +type Int64SliceFlag struct { + *cli.Int64SliceFlag + // Has unexported fields. +} + Int64SliceFlag is the flag type that wraps cli.Int64SliceFlag to allow for + other values to be specified + +func NewInt64SliceFlag(fl *cli.Int64SliceFlag) *Int64SliceFlag + NewInt64SliceFlag creates a new Int64SliceFlag + +func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + Int64SliceFlag.Apply + +type IntFlag struct { + *cli.IntFlag + // Has unexported fields. +} + IntFlag is the flag type that wraps cli.IntFlag to allow for other values to + be specified + +func NewIntFlag(fl *cli.IntFlag) *IntFlag + NewIntFlag creates a new IntFlag + +func (f *IntFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + IntFlag.Apply + +func (f *IntFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a int value to the flagSet if required + +type IntSliceFlag struct { + *cli.IntSliceFlag + // Has unexported fields. +} + IntSliceFlag is the flag type that wraps cli.IntSliceFlag to allow for other + values to be specified + +func NewIntSliceFlag(fl *cli.IntSliceFlag) *IntSliceFlag + NewIntSliceFlag creates a new IntSliceFlag + +func (f *IntSliceFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + IntSliceFlag.Apply + +func (f *IntSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a IntSlice value if required + +type MapInputSource struct { + // Has unexported fields. +} + MapInputSource implements InputSourceContext to return data from the map + that is loaded. + +func NewMapInputSource(file string, valueMap map[interface{}]interface{}) *MapInputSource + NewMapInputSource creates a new MapInputSource for implementing custom input + sources. + +func (fsm *MapInputSource) Bool(name string) (bool, error) + Bool returns an bool from the map otherwise returns false + +func (fsm *MapInputSource) Duration(name string) (time.Duration, error) + Duration returns a duration from the map if it exists otherwise returns 0 + +func (fsm *MapInputSource) Float64(name string) (float64, error) + Float64 returns an float64 from the map if it exists otherwise returns 0 + +func (fsm *MapInputSource) Generic(name string) (cli.Generic, error) + Generic returns an cli.Generic from the map if it exists otherwise returns + nil + +func (fsm *MapInputSource) Int(name string) (int, error) + Int returns an int from the map if it exists otherwise returns 0 + +func (fsm *MapInputSource) IntSlice(name string) ([]int, error) + IntSlice returns an []int from the map if it exists otherwise returns nil + +func (fsm *MapInputSource) Source() string + Source returns the path of the source file + +func (fsm *MapInputSource) String(name string) (string, error) + String returns a string from the map if it exists otherwise returns an empty + string + +func (fsm *MapInputSource) StringSlice(name string) ([]string, error) + StringSlice returns an []string from the map if it exists otherwise returns + nil + +type PathFlag struct { + *cli.PathFlag + // Has unexported fields. +} + PathFlag is the flag type that wraps cli.PathFlag to allow for other values + to be specified + +func NewPathFlag(fl *cli.PathFlag) *PathFlag + NewPathFlag creates a new PathFlag + +func (f *PathFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + PathFlag.Apply + +func (f *PathFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a Path value to the flagSet if required + +type StringFlag struct { + *cli.StringFlag + // Has unexported fields. +} + StringFlag is the flag type that wraps cli.StringFlag to allow for other + values to be specified + +func NewStringFlag(fl *cli.StringFlag) *StringFlag + NewStringFlag creates a new StringFlag + +func (f *StringFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + StringFlag.Apply + +func (f *StringFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a String value to the flagSet if required + +type StringSliceFlag struct { + *cli.StringSliceFlag + // Has unexported fields. +} + StringSliceFlag is the flag type that wraps cli.StringSliceFlag to allow for + other values to be specified + +func NewStringSliceFlag(fl *cli.StringSliceFlag) *StringSliceFlag + NewStringSliceFlag creates a new StringSliceFlag + +func (f *StringSliceFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + StringSliceFlag.Apply + +func (f *StringSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a StringSlice value to the flagSet if required + +type Uint64Flag struct { + *cli.Uint64Flag + // Has unexported fields. +} + Uint64Flag is the flag type that wraps cli.Uint64Flag to allow for other + values to be specified + +func NewUint64Flag(fl *cli.Uint64Flag) *Uint64Flag + NewUint64Flag creates a new Uint64Flag + +func (f *Uint64Flag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + Uint64Flag.Apply + +type UintFlag struct { + *cli.UintFlag + // Has unexported fields. +} + UintFlag is the flag type that wraps cli.UintFlag to allow for other values + to be specified + +func NewUintFlag(fl *cli.UintFlag) *UintFlag + NewUintFlag creates a new UintFlag + +func (f *UintFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + UintFlag.Apply + diff --git a/internal/build/build.go b/internal/build/build.go index 0e9391b..929ab0f 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -10,11 +10,29 @@ import ( "math" "os" "os/exec" + "path/filepath" "strings" "github.com/urfave/cli/v2" ) +const ( + badNewsEmoji = "🚨" + goodNewsEmoji = "✨" + checksPassedEmoji = "✅" + + v2diffWarning = ` +# The unified diff above indicates that the public API surface area +# has changed. If you feel that the changes are acceptable and adhere +# to the semantic versioning promise of the v2.x series described in +# docs/CONTRIBUTING.md, please run the following command to promote +# the current go docs: +# +# make v2approve +# +` +) + func main() { top, err := func() (string, error) { if v, err := sh("git", "rev-parse", "--show-toplevel"); err == nil { @@ -57,6 +75,17 @@ func main() { Name: "generate", Action: GenerateActionFunc, }, + { + Name: "v2diff", + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "color", Value: false}, + }, + Action: V2Diff, + }, + { + Name: "v2approve", + Action: V2Approve, + }, } app.Flags = []cli.Flag{ &cli.StringFlag{ @@ -206,9 +235,6 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) { helloSourceFilePath = "./internal/example-hello-world/example-hello-world.go" helloBuiltFilePath = "./internal/example-hello-world/built-example" desiredMaxBinarySize = 2.2 - badNewsEmoji = "🚨" - goodNewsEmoji = "✨" - checksPassedEmoji = "✅" mbStringFormatter = "%.1fMB" ) @@ -290,9 +316,68 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) { } func GenerateActionFunc(cCtx *cli.Context) error { + top := cCtx.Path("top") + + cliDocs, err := sh("go", "doc", "-all", top) + if err != nil { + return err + } + + altsrcDocs, err := sh("go", "doc", "-all", filepath.Join(top, "altsrc")) + if err != nil { + return err + } + + if err := os.WriteFile( + filepath.Join(top, "godoc-current.txt"), + []byte(cliDocs+altsrcDocs), + 0644, + ); err != nil { + return err + } + return runCmd("go", "generate", cCtx.Path("top")+"/...") } +func V2Diff(cCtx *cli.Context) error { + os.Chdir(cCtx.Path("top")) + + err := runCmd( + "diff", + "--ignore-all-space", + "--minimal", + "--color="+func() string { + if cCtx.Bool("color") { + return "always" + } + return "auto" + }(), + "--unified", + "--label=a/godoc", + filepath.Join("testdata", "godoc-v2.x.txt"), + "--label=b/godoc", + "godoc-current.txt", + ) + + if err != nil { + fmt.Printf("# %v ---> Hey! <---\n", badNewsEmoji) + fmt.Println(strings.TrimSpace(v2diffWarning)) + } + + return err +} + +func V2Approve(cCtx *cli.Context) error { + top := cCtx.Path("top") + + return runCmd( + "cp", + "-v", + filepath.Join(top, "godoc-current.txt"), + filepath.Join(top, "testdata", "godoc-v2.x.txt"), + ) +} + func getSize(sourcePath string, builtPath string, tags string) (size int64, err error) { // build example binary err = runCmd("go", "build", "-tags", tags, "-o", builtPath, "-ldflags", "-s -w", sourcePath) diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt new file mode 100644 index 0000000..1a9ab23 --- /dev/null +++ b/testdata/godoc-v2.x.txt @@ -0,0 +1,2090 @@ +package cli // import "github.com/urfave/cli/v2" + +Package cli provides a minimal framework for creating and organizing command +line Go applications. cli is designed to be easy to understand and write, +the most simple cli application can be written as follows: + + func main() { + (&cli.App{}).Run(os.Args) + } + +Of course this application does not do much, so let's make this an actual +application: + + func main() { + app := &cli.App{ + Name: "greet", + Usage: "say a greeting", + Action: func(c *cli.Context) error { + fmt.Println("Greetings") + return nil + }, + } + + app.Run(os.Args) + } + +VARIABLES + +var AppHelpTemplate = `NAME: + {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} + +USAGE: + {{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 | 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}} + {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} + +COMMANDS:{{range .VisibleCategories}}{{if .Name}} + {{.Name}}:{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + +GLOBAL OPTIONS: + {{range $index, $option := .VisibleFlags}}{{if $index}} + {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}} + +COPYRIGHT: + {{.Copyright}}{{end}} +` + AppHelpTemplate is the text template for the Default help topic. cli.go uses + text/template to render templates. You can render custom help text by + setting this variable. + +var CommandHelpTemplate = `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{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 | nindent 3 | trim}}{{end}}{{if .VisibleFlags}} + +OPTIONS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}} +` + CommandHelpTemplate is the text template for the command help topic. cli.go + uses text/template to render templates. You can render custom help text by + setting this variable. + +var ErrWriter io.Writer = os.Stderr + ErrWriter is used to write errors to the user. This can be anything + implementing the io.Writer interface and defaults to os.Stderr. + +var FishCompletionTemplate = `# {{ .App.Name }} fish shell completion + +function __fish_{{ .App.Name }}_no_subcommand --description 'Test if there has been any subcommand yet' + for i in (commandline -opc) + if contains -- $i{{ range $v := .AllCommands }} {{ $v }}{{ end }} + return 1 + end + end + return 0 +end + +{{ range $v := .Completions }}{{ $v }} +{{ end }}` +var MarkdownDocTemplate = `{{if gt .SectionNum 0}}% {{ .App.Name }} {{ .SectionNum }} + +{{end}}# NAME + +{{ .App.Name }}{{ if .App.Usage }} - {{ .App.Usage }}{{ end }} + +# SYNOPSIS + +{{ .App.Name }} +{{ if .SynopsisArgs }} +` + "```" + ` +{{ range $v := .SynopsisArgs }}{{ $v }}{{ end }}` + "```" + ` +{{ end }}{{ if .App.Description }} +# DESCRIPTION + +{{ .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 }} +{{ $v }}{{ end }} +{{ end }}{{ if .Commands }} +# COMMANDS +{{ range $v := .Commands }} +{{ $v }}{{ end }}{{ end }}` +var OsExiter = os.Exit + OsExiter is the function used when the app exits. If not set defaults to + os.Exit. + +var SubcommandHelpTemplate = `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{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 | nindent 3 | trim}}{{end}} + +COMMANDS:{{range .VisibleCategories}}{{if .Name}} + {{.Name}}:{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + +OPTIONS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}} +` + SubcommandHelpTemplate is the text template for the subcommand help topic. + cli.go uses text/template to render templates. You can render custom help + text by setting this variable. + +var VersionPrinter = printVersion + VersionPrinter prints the version for the App + +var HelpPrinter helpPrinter = printHelp + HelpPrinter is a function that writes the help output. If not set + explicitly, this calls HelpPrinterCustom using only the default template + functions. + + If custom logic for printing help is required, this function can be + overridden. If the ExtraInfo field is defined on an App, this function + should not be modified, as HelpPrinterCustom will be used directly in order + to capture the extra information. + +var HelpPrinterCustom helpPrinterCustom = printHelpCustom + HelpPrinterCustom is a function that writes the help output. It is used as + the default implementation of HelpPrinter, and may be called directly if the + ExtraInfo field is set on an App. + + +FUNCTIONS + +func DefaultAppComplete(cCtx *Context) + DefaultAppComplete prints the list of subcommands as the default app + completion method + +func DefaultCompleteWithFlags(cmd *Command) func(cCtx *Context) +func FlagNames(name string, aliases []string) []string +func HandleAction(action interface{}, cCtx *Context) (err error) + HandleAction attempts to figure out which Action signature was used. If it's + an ActionFunc or a func with the legacy signature for Action, the func is + run! + +func HandleExitCoder(err error) + HandleExitCoder handles errors implementing ExitCoder by printing their + message and calling OsExiter with the given exit code. + + If the given error instead implements MultiError, each error will be checked + for the ExitCoder interface, and OsExiter will be called with the last exit + code found, or exit code 1 if no ExitCoder is found. + + This function is the default error-handling behavior for an App. + +func ShowAppHelp(cCtx *Context) error + ShowAppHelp is an action that displays the help. + +func ShowAppHelpAndExit(c *Context, exitCode int) + ShowAppHelpAndExit - Prints the list of subcommands for the app and exits + with exit code. + +func ShowCommandCompletions(ctx *Context, command string) + ShowCommandCompletions prints the custom completions for a given command + +func ShowCommandHelp(ctx *Context, command string) error + ShowCommandHelp prints help for the given command + +func ShowCommandHelpAndExit(c *Context, command string, code int) + ShowCommandHelpAndExit - exits with code after showing help + +func ShowCompletions(cCtx *Context) + ShowCompletions prints the lists of commands within a given context + +func ShowSubcommandHelp(cCtx *Context) error + ShowSubcommandHelp prints help for the given subcommand + +func ShowSubcommandHelpAndExit(c *Context, exitCode int) + ShowSubcommandHelpAndExit - Prints help for the given subcommand and exits + with exit code. + +func ShowVersion(cCtx *Context) + ShowVersion prints the version number of the App + + +TYPES + +type ActionFunc func(*Context) error + ActionFunc is the action to execute when no subcommands are specified + +type AfterFunc func(*Context) error + AfterFunc is an action to execute after any subcommands are run, but after + the subcommand has finished it is run even if Action() panics + +type App struct { + // The name of the program. Defaults to path.Base(os.Args[0]) + Name string + // Full name of command for help, defaults to Name + HelpName string + // Description of the program. + Usage string + // Text to override the USAGE section of help + UsageText string + // Description of the program argument format. + ArgsUsage string + // Version of the program + Version string + // Description of the program + Description string + // List of commands to execute + Commands []*Command + // List of flags to parse + Flags []Flag + // Boolean to enable bash completion commands + EnableBashCompletion bool + // Boolean to hide built-in help command and help flag + HideHelp bool + // Boolean to hide built-in help command but keep help flag. + // Ignored if HideHelp is true. + HideHelpCommand bool + // Boolean to hide built-in version flag and the VERSION section of help + HideVersion bool + + // An action to execute when the shell completion flag is set + BashComplete BashCompleteFunc + // An action to execute before any subcommands are run, but after the context is ready + // If a non-nil error is returned, no subcommands are run + Before BeforeFunc + // An action to execute after any subcommands are run, but after the subcommand has finished + // It is run even if Action() panics + After AfterFunc + // The action to execute when no subcommands are specified + Action ActionFunc + // Execute this function if the proper command cannot be found + CommandNotFound CommandNotFoundFunc + // Execute this function if a usage error occurs + OnUsageError OnUsageErrorFunc + // Compilation date + Compiled time.Time + // List of all authors who contributed + 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 + ErrWriter io.Writer + // ExitErrHandler processes any error encountered while running an App before + // it is returned to the caller. If no function is provided, HandleExitCoder + // is used as the default behavior. + ExitErrHandler ExitErrHandlerFunc + // Other custom info + Metadata map[string]interface{} + // Carries a function which returns app specific info. + ExtraInfo func() map[string]string + // CustomAppHelpTemplate the text template for app help topic. + // cli.go uses text/template to render templates. You can + // render custom help text by setting this variable. + CustomAppHelpTemplate string + // Boolean to enable short-option handling so user can combine several + // single-character bool arguments into one + // i.e. foobar -o -v -> foobar -ov + UseShortOptionHandling bool + + // Has unexported fields. +} + App is the main structure of a cli application. It is recommended that an + app be created with the cli.NewApp() function + +func NewApp() *App + NewApp creates a new cli Application with some reasonable defaults for Name, + Usage, Version and Action. + +func (a *App) Command(name string) *Command + Command returns the named command on App. Returns nil if the command does + not exist + +func (a *App) Run(arguments []string) (err error) + Run is the entry point to the cli app. Parses the arguments slice and routes + to the proper flag/args combination + +func (a *App) RunAndExitOnError() + 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 error + code in the cli.ExitCoder + +func (a *App) RunAsSubcommand(ctx *Context) (err error) + RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() + to generate command-specific flags + +func (a *App) RunContext(ctx context.Context, arguments []string) (err error) + RunContext is like Run except it takes a Context that will be passed to its + commands and sub-commands. Through this, you can propagate timeouts and + cancellation requests + +func (a *App) Setup() + Setup runs initialization code to ensure all data structures are ready for + `Run` or inspection prior to `Run`. It is internally called by `Run`, but + will return early if setup has already happened. + +func (a *App) ToFishCompletion() (string, error) + ToFishCompletion creates a fish completion string for the `*App` The + function errors if either parsing or writing of the string fails. + +func (a *App) ToMan() (string, error) + ToMan creates a man page string for the `*App` The function errors if either + parsing or writing of the string fails. + +func (a *App) ToManWithSection(sectionNumber int) (string, error) + 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) ToMarkdown() (string, error) + ToMarkdown creates a markdown string for the `*App` The function errors if + either parsing or writing of the string fails. + +func (a *App) VisibleCategories() []CommandCategory + VisibleCategories returns a slice of categories and commands that are + Hidden=false + +func (a *App) VisibleCommands() []*Command + VisibleCommands returns a slice of the Commands with Hidden=false + +func (a *App) VisibleFlags() []Flag + VisibleFlags returns a slice of the Flags with Hidden=false + +type Args interface { + // Get returns the nth argument, or else a blank string + Get(n int) string + // First returns the first argument, or else a blank string + First() string + // Tail returns the rest of the arguments (not the first one) + // or else an empty string slice + Tail() []string + // Len returns the length of the wrapped slice + Len() int + // Present checks if there are any arguments present + Present() bool + // Slice returns a copy of the internal slice + Slice() []string +} + +type Author struct { + Name string // The Authors name + Email string // The Authors email +} + Author represents someone who has contributed to a cli project. + +func (a *Author) String() string + String makes Author comply to the Stringer interface, to allow an easy print + in the templating process + +type BashCompleteFunc func(*Context) + BashCompleteFunc is an action to execute when the shell completion flag is + set + +type BeforeFunc func(*Context) error + BeforeFunc is an action to execute before any subcommands are run, but after + the context is ready if a non-nil error is returned, no subcommands are run + +type BoolFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value bool + Destination *bool + + Aliases []string + EnvVars []string +} + BoolFlag is a flag with type bool + +func (f *BoolFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *BoolFlag) Get(ctx *Context) bool + Get returns the flag’s value in the given Context. + +func (f *BoolFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *BoolFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *BoolFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *BoolFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *BoolFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *BoolFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *BoolFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *BoolFlag) Names() []string + Names returns the names of the flag + +func (f *BoolFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *BoolFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type Command struct { + // The name of the command + Name string + // A list of aliases for the command + Aliases []string + // A short description of the usage of this command + Usage string + // Custom text to show on USAGE section of help + UsageText string + // A longer explanation of how the command works + Description string + // A short description of the arguments of this command + ArgsUsage string + // The category the command is part of + Category string + // The function to call when checking for bash command completions + BashComplete BashCompleteFunc + // An action to execute before any sub-subcommands are run, but after the context is ready + // If a non-nil error is returned, no sub-subcommands are run + Before BeforeFunc + // An action to execute after any subcommands are run, but after the subcommand has finished + // It is run even if Action() panics + After AfterFunc + // The function to call when this command is invoked + Action ActionFunc + // Execute this function if a usage error occurs. + OnUsageError OnUsageErrorFunc + // List of child commands + Subcommands []*Command + // List of flags to parse + Flags []Flag + // Treat all flags as normal arguments if true + SkipFlagParsing bool + // Boolean to hide built-in help command and help flag + HideHelp bool + // Boolean to hide built-in help command but keep help flag + // Ignored if HideHelp is true. + HideHelpCommand bool + // Boolean to hide this command from help or completion + Hidden bool + // Boolean to enable short-option handling so user can combine several + // single-character bool arguments into one + // i.e. foobar -o -v -> foobar -ov + UseShortOptionHandling bool + + // Full name of command for help, defaults to full command name, including parent commands. + HelpName string + + // CustomHelpTemplate the text template for the command help topic. + // cli.go uses text/template to render templates. You can + // render custom help text by setting this variable. + CustomHelpTemplate string + // Has unexported fields. +} + Command is a subcommand for a cli.App. + +func (c *Command) FullName() string + FullName returns the full name of the command. For subcommands this ensures + that parent commands are part of the command path + +func (c *Command) HasName(name string) bool + HasName returns true if Command.Name matches given name + +func (c *Command) Names() []string + Names returns the names including short names and aliases. + +func (c *Command) Run(ctx *Context) (err error) + Run invokes the command given the context, parses ctx.Args() to generate + command-specific flags + +func (c *Command) VisibleFlags() []Flag + VisibleFlags returns a slice of the Flags with Hidden=false + +type CommandCategories interface { + // AddCommand adds a command to a category, creating a new category if necessary. + AddCommand(category string, command *Command) + // categories returns a copy of the category slice + Categories() []CommandCategory +} + CommandCategories interface allows for category manipulation + +type CommandCategory interface { + // Name returns the category name string + Name() string + // VisibleCommands returns a slice of the Commands with Hidden=false + VisibleCommands() []*Command +} + CommandCategory is a category containing commands. + +type CommandNotFoundFunc func(*Context, string) + CommandNotFoundFunc is executed if the proper command cannot be found + +type Commands []*Command + +type CommandsByName []*Command + +func (c CommandsByName) Len() int + +func (c CommandsByName) Less(i, j int) bool + +func (c CommandsByName) Swap(i, j int) + +type Context struct { + context.Context + App *App + Command *Command + + // Has unexported fields. +} + Context is a type that is passed through to each Handler action in a cli + application. Context can be used to retrieve context-specific args and + parsed command-line options. + +func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context + NewContext creates a new context. For use in when invoking an App or Command + action. + +func (cCtx *Context) Args() Args + Args returns the command line arguments associated with the context. + +func (cCtx *Context) Bool(name string) bool + Bool looks up the value of a local BoolFlag, returns false if not found + +func (cCtx *Context) Duration(name string) time.Duration + Duration looks up the value of a local DurationFlag, returns 0 if not found + +func (cCtx *Context) FlagNames() []string + FlagNames returns a slice of flag names used by the this context and all of + its parent contexts. + +func (cCtx *Context) Float64(name string) float64 + Float64 looks up the value of a local Float64Flag, returns 0 if not found + +func (cCtx *Context) Float64Slice(name string) []float64 + Float64Slice looks up the value of a local Float64SliceFlag, returns nil if + not found + +func (cCtx *Context) Generic(name string) interface{} + Generic looks up the value of a local GenericFlag, returns nil if not found + +func (cCtx *Context) Int(name string) int + Int looks up the value of a local IntFlag, returns 0 if not found + +func (cCtx *Context) Int64(name string) int64 + Int64 looks up the value of a local Int64Flag, returns 0 if not found + +func (cCtx *Context) Int64Slice(name string) []int64 + Int64Slice looks up the value of a local Int64SliceFlag, returns nil if not + found + +func (cCtx *Context) IntSlice(name string) []int + IntSlice looks up the value of a local IntSliceFlag, returns nil if not + found + +func (cCtx *Context) IsSet(name string) bool + IsSet determines if the flag was actually set + +func (cCtx *Context) Lineage() []*Context + Lineage returns *this* context and all of its ancestor contexts in order + from child to parent + +func (cCtx *Context) LocalFlagNames() []string + LocalFlagNames returns a slice of flag names used in this context. + +func (cCtx *Context) NArg() int + NArg returns the number of the command line arguments. + +func (cCtx *Context) NumFlags() int + NumFlags returns the number of flags set + +func (cCtx *Context) Path(name string) string + Path looks up the value of a local PathFlag, returns "" if not found + +func (cCtx *Context) Set(name, value string) error + Set sets a context flag to a value. + +func (cCtx *Context) String(name string) string + String looks up the value of a local StringFlag, returns "" if not found + +func (cCtx *Context) StringSlice(name string) []string + StringSlice looks up the value of a local StringSliceFlag, returns nil if + not found + +func (cCtx *Context) Timestamp(name string) *time.Time + Timestamp gets the timestamp from a flag name + +func (cCtx *Context) Uint(name string) uint + Uint looks up the value of a local UintFlag, returns 0 if not found + +func (cCtx *Context) Uint64(name string) uint64 + Uint64 looks up the value of a local Uint64Flag, returns 0 if not found + +func (cCtx *Context) Value(name string) interface{} + Value returns the value of the flag corresponding to `name` + +type DocGenerationFlag interface { + Flag + + // TakesValue returns true if the flag takes a value, otherwise false + TakesValue() bool + + // GetUsage returns the usage string for the flag + GetUsage() string + + // GetValue returns the flags value as string representation and an empty + // string if the flag takes no value at all. + GetValue() string + + // GetDefaultText returns the default text for this flag + GetDefaultText() string + + // GetEnvVars returns the env vars for this flag + GetEnvVars() []string +} + DocGenerationFlag is an interface that allows documentation generation for + the flag + +type DurationFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value time.Duration + Destination *time.Duration + + Aliases []string + EnvVars []string +} + DurationFlag is a flag with type time.Duration + +func (f *DurationFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *DurationFlag) Get(ctx *Context) time.Duration + Get returns the flag’s value in the given Context. + +func (f *DurationFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *DurationFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *DurationFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *DurationFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *DurationFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *DurationFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *DurationFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *DurationFlag) Names() []string + Names returns the names of the flag + +func (f *DurationFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *DurationFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type ErrorFormatter interface { + Format(s fmt.State, verb rune) +} + ErrorFormatter is the interface that will suitably format the error output + +type ExitCoder interface { + error + ExitCode() int +} + ExitCoder is the interface checked by `App` and `Command` for a custom exit + code + +func Exit(message interface{}, exitCode int) ExitCoder + Exit wraps a message and exit code into an error, which by default is + handled with a call to os.Exit during default error handling. + + This is the simplest way to trigger a non-zero exit code for an App without + having to call os.Exit manually. During testing, this behavior can be + avoided by overiding the ExitErrHandler function on an App or the + package-global OsExiter function. + +func NewExitError(message interface{}, exitCode int) ExitCoder + NewExitError calls Exit to create a new ExitCoder. + + Deprecated: This function is a duplicate of Exit and will eventually be + removed. + +type ExitErrHandlerFunc func(cCtx *Context, err error) + ExitErrHandlerFunc is executed if provided in order to handle exitError + values returned by Actions and Before/After functions. + +type Flag interface { + fmt.Stringer + // Apply Flag settings to the given flag set + Apply(*flag.FlagSet) error + Names() []string + IsSet() bool +} + Flag is a common interface related to parsing flags in cli. For more + advanced flag parsing techniques, it is recommended that this interface be + implemented. + +var BashCompletionFlag Flag = &BoolFlag{ + Name: "generate-bash-completion", + Hidden: true, +} + BashCompletionFlag enables bash-completion for all commands and subcommands + +var HelpFlag Flag = &BoolFlag{ + Name: "help", + Aliases: []string{"h"}, + Usage: "show help", +} + HelpFlag prints the help for all commands and subcommands. Set to nil to + disable the flag. The subcommand will still be added unless HideHelp or + HideHelpCommand is set to true. + +var VersionFlag Flag = &BoolFlag{ + Name: "version", + Aliases: []string{"v"}, + Usage: "print the version", +} + VersionFlag prints the version for the application + +type FlagEnvHintFunc func(envVars []string, str string) string + FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help + with the environment variable details. + +var FlagEnvHinter FlagEnvHintFunc = withEnvHint + FlagEnvHinter annotates flag help message with the environment variable + details. This is used by the default FlagStringer. + +type FlagFileHintFunc func(filePath, str string) string + FlagFileHintFunc is used by the default FlagStringFunc to annotate flag help + with the file path details. + +var FlagFileHinter FlagFileHintFunc = withFileHint + FlagFileHinter annotates flag help message with the environment variable + details. This is used by the default FlagStringer. + +type FlagNamePrefixFunc func(fullName []string, placeholder string) string + FlagNamePrefixFunc is used by the default FlagStringFunc to create prefix + text for a flag's full name. + +var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames + FlagNamePrefixer converts a full flag name and its placeholder into the help + message flag prefix. This is used by the default FlagStringer. + +type FlagStringFunc func(Flag) string + FlagStringFunc is used by the help generation to display a flag, which is + expected to be a single line. + +var FlagStringer FlagStringFunc = stringifyFlag + FlagStringer converts a flag definition to a string. This is used by help to + display a flag. + +type FlagsByName []Flag + FlagsByName is a slice of Flag. + +func (f FlagsByName) Len() int + +func (f FlagsByName) Less(i, j int) bool + +func (f FlagsByName) Swap(i, j int) + +type Float64Flag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value float64 + Destination *float64 + + Aliases []string + EnvVars []string +} + Float64Flag is a flag with type float64 + +func (f *Float64Flag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *Float64Flag) Get(ctx *Context) float64 + Get returns the flag’s value in the given Context. + +func (f *Float64Flag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *Float64Flag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *Float64Flag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *Float64Flag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *Float64Flag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *Float64Flag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *Float64Flag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *Float64Flag) Names() []string + Names returns the names of the flag + +func (f *Float64Flag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Float64Flag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type Float64Slice struct { + // Has unexported fields. +} + Float64Slice wraps []float64 to satisfy flag.Value + +func NewFloat64Slice(defaults ...float64) *Float64Slice + NewFloat64Slice makes a *Float64Slice with default values + +func (f *Float64Slice) Get() interface{} + Get returns the slice of float64s set by this flag + +func (f *Float64Slice) Serialize() string + Serialize allows Float64Slice to fulfill Serializer + +func (f *Float64Slice) Set(value string) error + Set parses the value into a float64 and appends it to the list of values + +func (f *Float64Slice) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Float64Slice) Value() []float64 + Value returns the slice of float64s set by this flag + +type Float64SliceFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *Float64Slice + Destination *Float64Slice + + Aliases []string + EnvVars []string +} + Float64SliceFlag is a flag with type Float64Slice + +func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *Float64SliceFlag) Get(ctx *Context) []float64 + Get returns the flag’s value in the given Context. + +func (f *Float64SliceFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *Float64SliceFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *Float64SliceFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *Float64SliceFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *Float64SliceFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *Float64SliceFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *Float64SliceFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *Float64SliceFlag) Names() []string + Names returns the names of the flag + +func (f *Float64SliceFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Float64SliceFlag) TakesValue() bool + TakesValue returns true if the flag takes a value, otherwise false + +type Generic interface { + Set(value string) error + String() string +} + Generic is a generic parseable type identified by a specific flag + +type GenericFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value Generic + Destination *Generic + + Aliases []string + EnvVars []string + + TakesFile bool +} + GenericFlag is a flag with type Generic + +func (f GenericFlag) Apply(set *flag.FlagSet) error + Apply takes the flagset and calls Set on the generic flag with the value + provided by the user for parsing by the flag + +func (f *GenericFlag) Get(ctx *Context) interface{} + Get returns the flag’s value in the given Context. + +func (f *GenericFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *GenericFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *GenericFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *GenericFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *GenericFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *GenericFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *GenericFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *GenericFlag) Names() []string + Names returns the names of the flag + +func (f *GenericFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *GenericFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type Int64Flag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value int64 + Destination *int64 + + Aliases []string + EnvVars []string +} + Int64Flag is a flag with type int64 + +func (f *Int64Flag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *Int64Flag) Get(ctx *Context) int64 + Get returns the flag’s value in the given Context. + +func (f *Int64Flag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *Int64Flag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *Int64Flag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *Int64Flag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *Int64Flag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *Int64Flag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *Int64Flag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *Int64Flag) Names() []string + Names returns the names of the flag + +func (f *Int64Flag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Int64Flag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type Int64Slice struct { + // Has unexported fields. +} + Int64Slice wraps []int64 to satisfy flag.Value + +func NewInt64Slice(defaults ...int64) *Int64Slice + NewInt64Slice makes an *Int64Slice with default values + +func (i *Int64Slice) Get() interface{} + Get returns the slice of ints set by this flag + +func (i *Int64Slice) Serialize() string + Serialize allows Int64Slice to fulfill Serializer + +func (i *Int64Slice) Set(value string) error + Set parses the value into an integer and appends it to the list of values + +func (i *Int64Slice) String() string + String returns a readable representation of this value (for usage defaults) + +func (i *Int64Slice) Value() []int64 + Value returns the slice of ints set by this flag + +type Int64SliceFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *Int64Slice + Destination *Int64Slice + + Aliases []string + EnvVars []string +} + Int64SliceFlag is a flag with type Int64Slice + +func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *Int64SliceFlag) Get(ctx *Context) []int64 + Get returns the flag’s value in the given Context. + +func (f *Int64SliceFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *Int64SliceFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f Int64SliceFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *Int64SliceFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *Int64SliceFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *Int64SliceFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *Int64SliceFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *Int64SliceFlag) Names() []string + Names returns the names of the flag + +func (f *Int64SliceFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Int64SliceFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type IntFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value int + Destination *int + + Aliases []string + EnvVars []string +} + IntFlag is a flag with type int + +func (f *IntFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *IntFlag) Get(ctx *Context) int + Get returns the flag’s value in the given Context. + +func (f *IntFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *IntFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *IntFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *IntFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *IntFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *IntFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *IntFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *IntFlag) Names() []string + Names returns the names of the flag + +func (f *IntFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *IntFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type IntSlice struct { + // Has unexported fields. +} + IntSlice wraps []int to satisfy flag.Value + +func NewIntSlice(defaults ...int) *IntSlice + NewIntSlice makes an *IntSlice with default values + +func (i *IntSlice) Get() interface{} + Get returns the slice of ints set by this flag + +func (i *IntSlice) Serialize() string + Serialize allows IntSlice to fulfill Serializer + +func (i *IntSlice) Set(value string) error + Set parses the value into an integer and appends it to the list of values + +func (i *IntSlice) SetInt(value int) + TODO: Consistently have specific Set function for Int64 and Float64 ? SetInt + directly adds an integer to the list of values + +func (i *IntSlice) String() string + String returns a readable representation of this value (for usage defaults) + +func (i *IntSlice) Value() []int + Value returns the slice of ints set by this flag + +type IntSliceFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *IntSlice + Destination *IntSlice + + Aliases []string + EnvVars []string +} + IntSliceFlag is a flag with type IntSlice + +func (f *IntSliceFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *IntSliceFlag) Get(ctx *Context) []int + Get returns the flag’s value in the given Context. + +func (f *IntSliceFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *IntSliceFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f IntSliceFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *IntSliceFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *IntSliceFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *IntSliceFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *IntSliceFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *IntSliceFlag) Names() []string + Names returns the names of the flag + +func (f *IntSliceFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *IntSliceFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type MultiError interface { + error + Errors() []error +} + MultiError is an error that wraps multiple errors. + +type OnUsageErrorFunc func(cCtx *Context, err error, isSubcommand bool) error + 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. + +type Path = string + +type PathFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value Path + Destination *Path + + Aliases []string + EnvVars []string + + TakesFile bool +} + PathFlag is a flag with type Path + +func (f *PathFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *PathFlag) Get(ctx *Context) string + Get returns the flag’s value in the given Context. + +func (f *PathFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *PathFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *PathFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *PathFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *PathFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *PathFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *PathFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *PathFlag) Names() []string + Names returns the names of the flag + +func (f *PathFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *PathFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type RequiredFlag interface { + Flag + + IsRequired() bool +} + RequiredFlag is an interface that allows us to mark flags as required it + allows flags required flags to be backwards compatible with the Flag + interface + +type Serializer interface { + Serialize() string +} + Serializer is used to circumvent the limitations of flag.FlagSet.Set + +type StringFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value string + Destination *string + + Aliases []string + EnvVars []string + + TakesFile bool +} + StringFlag is a flag with type string + +func (f *StringFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *StringFlag) Get(ctx *Context) string + Get returns the flag’s value in the given Context. + +func (f *StringFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *StringFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *StringFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *StringFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *StringFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *StringFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *StringFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *StringFlag) Names() []string + Names returns the names of the flag + +func (f *StringFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *StringFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type StringSlice struct { + // Has unexported fields. +} + StringSlice wraps a []string to satisfy flag.Value + +func NewStringSlice(defaults ...string) *StringSlice + NewStringSlice creates a *StringSlice with default values + +func (s *StringSlice) Get() interface{} + Get returns the slice of strings set by this flag + +func (s *StringSlice) Serialize() string + Serialize allows StringSlice to fulfill Serializer + +func (s *StringSlice) Set(value string) error + Set appends the string value to the list of values + +func (s *StringSlice) String() string + String returns a readable representation of this value (for usage defaults) + +func (s *StringSlice) Value() []string + Value returns the slice of strings set by this flag + +type StringSliceFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *StringSlice + Destination *StringSlice + + Aliases []string + EnvVars []string + + TakesFile bool +} + StringSliceFlag is a flag with type StringSlice + +func (f *StringSliceFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *StringSliceFlag) Get(ctx *Context) []string + Get returns the flag’s value in the given Context. + +func (f *StringSliceFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *StringSliceFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *StringSliceFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *StringSliceFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *StringSliceFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *StringSliceFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *StringSliceFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *StringSliceFlag) Names() []string + Names returns the names of the flag + +func (f *StringSliceFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *StringSliceFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type Timestamp struct { + // Has unexported fields. +} + Timestamp wrap to satisfy golang's flag interface. + +func NewTimestamp(timestamp time.Time) *Timestamp + Timestamp constructor + +func (t *Timestamp) Get() interface{} + Get returns the flag structure + +func (t *Timestamp) Set(value string) error + Parses the string value to timestamp + +func (t *Timestamp) SetLayout(layout string) + Set the timestamp string layout for future parsing + +func (t *Timestamp) SetTimestamp(value time.Time) + Set the timestamp value directly + +func (t *Timestamp) String() string + String returns a readable representation of this value (for usage defaults) + +func (t *Timestamp) Value() *time.Time + Value returns the timestamp value stored in the flag + +type TimestampFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *Timestamp + Destination *Timestamp + + Aliases []string + EnvVars []string + + Layout string +} + TimestampFlag is a flag with type Timestamp + +func (f *TimestampFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *TimestampFlag) Get(ctx *Context) *time.Time + Get returns the flag’s value in the given Context. + +func (f *TimestampFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *TimestampFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *TimestampFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *TimestampFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *TimestampFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *TimestampFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *TimestampFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *TimestampFlag) Names() []string + Names returns the names of the flag + +func (f *TimestampFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *TimestampFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type Uint64Flag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value uint64 + Destination *uint64 + + Aliases []string + EnvVars []string +} + Uint64Flag is a flag with type uint64 + +func (f *Uint64Flag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *Uint64Flag) Get(ctx *Context) uint64 + Get returns the flag’s value in the given Context. + +func (f *Uint64Flag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *Uint64Flag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *Uint64Flag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *Uint64Flag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *Uint64Flag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *Uint64Flag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *Uint64Flag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *Uint64Flag) Names() []string + Names returns the names of the flag + +func (f *Uint64Flag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Uint64Flag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type UintFlag struct { + Name string + + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value uint + Destination *uint + + Aliases []string + EnvVars []string +} + UintFlag is a flag with type uint + +func (f *UintFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *UintFlag) Get(ctx *Context) uint + Get returns the flag’s value in the given Context. + +func (f *UintFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *UintFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *UintFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *UintFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *UintFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *UintFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *UintFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *UintFlag) Names() []string + Names returns the names of the flag + +func (f *UintFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *UintFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type VisibleFlag interface { + Flag + + // IsVisible returns true if the flag is not hidden, otherwise false + IsVisible() bool +} + VisibleFlag is an interface that allows to check if a flag is visible + +package altsrc // import "github.com/urfave/cli/v2/altsrc" + + +FUNCTIONS + +func ApplyInputSourceValues(cCtx *cli.Context, inputSourceContext InputSourceContext, flags []cli.Flag) error + ApplyInputSourceValues iterates over all provided flags and executes + ApplyInputSourceValue on flags implementing the FlagInputSourceExtension + interface to initialize these flags to an alternate input source. + +func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceContext, error)) cli.BeforeFunc + InitInputSource is used to to setup an InputSourceContext on a cli.Command + Before method. It will create a new input source based on the func provided. + If there is no error it will then apply the new input source to any flags + that are supported by the input source + +func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(cCtx *cli.Context) (InputSourceContext, error)) cli.BeforeFunc + InitInputSourceWithContext is used to to setup an InputSourceContext on a + cli.Command Before method. It will create a new input source based on the + func provided with potentially using existing cli.Context values to + initialize itself. If there is no error it will then apply the new input + source to any flags that are supported by the input source + +func NewJSONSourceFromFlagFunc(flag string) func(c *cli.Context) (InputSourceContext, error) + NewJSONSourceFromFlagFunc returns a func that takes a cli.Context and + returns an InputSourceContext suitable for retrieving config variables from + a file containing JSON data with the file name defined by the given flag. + +func NewTomlSourceFromFlagFunc(flagFileName string) func(cCtx *cli.Context) (InputSourceContext, error) + NewTomlSourceFromFlagFunc creates a new TOML InputSourceContext from a + provided flag name and source context. + +func NewYamlSourceFromFlagFunc(flagFileName string) func(cCtx *cli.Context) (InputSourceContext, error) + NewYamlSourceFromFlagFunc creates a new Yaml InputSourceContext from a + provided flag name and source context. + + +TYPES + +type BoolFlag struct { + *cli.BoolFlag + // Has unexported fields. +} + BoolFlag is the flag type that wraps cli.BoolFlag to allow for other values + to be specified + +func NewBoolFlag(fl *cli.BoolFlag) *BoolFlag + NewBoolFlag creates a new BoolFlag + +func (f *BoolFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + BoolFlag.Apply + +func (f *BoolFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a Bool value to the flagSet if required + +type DurationFlag struct { + *cli.DurationFlag + // Has unexported fields. +} + DurationFlag is the flag type that wraps cli.DurationFlag to allow for other + values to be specified + +func NewDurationFlag(fl *cli.DurationFlag) *DurationFlag + NewDurationFlag creates a new DurationFlag + +func (f *DurationFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + DurationFlag.Apply + +func (f *DurationFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a Duration value to the flagSet if required + +type FlagInputSourceExtension interface { + cli.Flag + ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error +} + FlagInputSourceExtension is an extension interface of cli.Flag that allows a + value to be set on the existing parsed flags. + +type Float64Flag struct { + *cli.Float64Flag + // Has unexported fields. +} + Float64Flag is the flag type that wraps cli.Float64Flag to allow for other + values to be specified + +func NewFloat64Flag(fl *cli.Float64Flag) *Float64Flag + NewFloat64Flag creates a new Float64Flag + +func (f *Float64Flag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + Float64Flag.Apply + +func (f *Float64Flag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a Float64 value to the flagSet if required + +type Float64SliceFlag struct { + *cli.Float64SliceFlag + // Has unexported fields. +} + Float64SliceFlag is the flag type that wraps cli.Float64SliceFlag to allow + for other values to be specified + +func NewFloat64SliceFlag(fl *cli.Float64SliceFlag) *Float64SliceFlag + NewFloat64SliceFlag creates a new Float64SliceFlag + +func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + Float64SliceFlag.Apply + +type GenericFlag struct { + *cli.GenericFlag + // Has unexported fields. +} + GenericFlag is the flag type that wraps cli.GenericFlag to allow for other + values to be specified + +func NewGenericFlag(fl *cli.GenericFlag) *GenericFlag + NewGenericFlag creates a new GenericFlag + +func (f *GenericFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + GenericFlag.Apply + +func (f *GenericFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a generic value to the flagSet if required + +type InputSourceContext interface { + Source() string + + Int(name string) (int, error) + Duration(name string) (time.Duration, error) + Float64(name string) (float64, error) + String(name string) (string, error) + StringSlice(name string) ([]string, error) + IntSlice(name string) ([]int, error) + Generic(name string) (cli.Generic, error) + Bool(name string) (bool, error) + + // Has unexported methods. +} + InputSourceContext is an interface used to allow other input sources to be + implemented as needed. + + Source returns an identifier for the input source. In case of file source it + should return path to the file. + +func NewJSONSource(data []byte) (InputSourceContext, error) + NewJSONSource returns an InputSourceContext suitable for retrieving config + variables from raw JSON data. + +func NewJSONSourceFromFile(f string) (InputSourceContext, error) + NewJSONSourceFromFile returns an InputSourceContext suitable for retrieving + config variables from a file (or url) containing JSON data. + +func NewJSONSourceFromReader(r io.Reader) (InputSourceContext, error) + NewJSONSourceFromReader returns an InputSourceContext suitable for + retrieving config variables from an io.Reader that returns JSON data. + +func NewTomlSourceFromFile(file string) (InputSourceContext, error) + NewTomlSourceFromFile creates a new TOML InputSourceContext from a filepath. + +func NewYamlSourceFromFile(file string) (InputSourceContext, error) + NewYamlSourceFromFile creates a new Yaml InputSourceContext from a filepath. + +type Int64Flag struct { + *cli.Int64Flag + // Has unexported fields. +} + Int64Flag is the flag type that wraps cli.Int64Flag to allow for other + values to be specified + +func NewInt64Flag(fl *cli.Int64Flag) *Int64Flag + NewInt64Flag creates a new Int64Flag + +func (f *Int64Flag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + Int64Flag.Apply + +type Int64SliceFlag struct { + *cli.Int64SliceFlag + // Has unexported fields. +} + Int64SliceFlag is the flag type that wraps cli.Int64SliceFlag to allow for + other values to be specified + +func NewInt64SliceFlag(fl *cli.Int64SliceFlag) *Int64SliceFlag + NewInt64SliceFlag creates a new Int64SliceFlag + +func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + Int64SliceFlag.Apply + +type IntFlag struct { + *cli.IntFlag + // Has unexported fields. +} + IntFlag is the flag type that wraps cli.IntFlag to allow for other values to + be specified + +func NewIntFlag(fl *cli.IntFlag) *IntFlag + NewIntFlag creates a new IntFlag + +func (f *IntFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + IntFlag.Apply + +func (f *IntFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a int value to the flagSet if required + +type IntSliceFlag struct { + *cli.IntSliceFlag + // Has unexported fields. +} + IntSliceFlag is the flag type that wraps cli.IntSliceFlag to allow for other + values to be specified + +func NewIntSliceFlag(fl *cli.IntSliceFlag) *IntSliceFlag + NewIntSliceFlag creates a new IntSliceFlag + +func (f *IntSliceFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + IntSliceFlag.Apply + +func (f *IntSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a IntSlice value if required + +type MapInputSource struct { + // Has unexported fields. +} + MapInputSource implements InputSourceContext to return data from the map + that is loaded. + +func NewMapInputSource(file string, valueMap map[interface{}]interface{}) *MapInputSource + NewMapInputSource creates a new MapInputSource for implementing custom input + sources. + +func (fsm *MapInputSource) Bool(name string) (bool, error) + Bool returns an bool from the map otherwise returns false + +func (fsm *MapInputSource) Duration(name string) (time.Duration, error) + Duration returns a duration from the map if it exists otherwise returns 0 + +func (fsm *MapInputSource) Float64(name string) (float64, error) + Float64 returns an float64 from the map if it exists otherwise returns 0 + +func (fsm *MapInputSource) Generic(name string) (cli.Generic, error) + Generic returns an cli.Generic from the map if it exists otherwise returns + nil + +func (fsm *MapInputSource) Int(name string) (int, error) + Int returns an int from the map if it exists otherwise returns 0 + +func (fsm *MapInputSource) IntSlice(name string) ([]int, error) + IntSlice returns an []int from the map if it exists otherwise returns nil + +func (fsm *MapInputSource) Source() string + Source returns the path of the source file + +func (fsm *MapInputSource) String(name string) (string, error) + String returns a string from the map if it exists otherwise returns an empty + string + +func (fsm *MapInputSource) StringSlice(name string) ([]string, error) + StringSlice returns an []string from the map if it exists otherwise returns + nil + +type PathFlag struct { + *cli.PathFlag + // Has unexported fields. +} + PathFlag is the flag type that wraps cli.PathFlag to allow for other values + to be specified + +func NewPathFlag(fl *cli.PathFlag) *PathFlag + NewPathFlag creates a new PathFlag + +func (f *PathFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + PathFlag.Apply + +func (f *PathFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a Path value to the flagSet if required + +type StringFlag struct { + *cli.StringFlag + // Has unexported fields. +} + StringFlag is the flag type that wraps cli.StringFlag to allow for other + values to be specified + +func NewStringFlag(fl *cli.StringFlag) *StringFlag + NewStringFlag creates a new StringFlag + +func (f *StringFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + StringFlag.Apply + +func (f *StringFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a String value to the flagSet if required + +type StringSliceFlag struct { + *cli.StringSliceFlag + // Has unexported fields. +} + StringSliceFlag is the flag type that wraps cli.StringSliceFlag to allow for + other values to be specified + +func NewStringSliceFlag(fl *cli.StringSliceFlag) *StringSliceFlag + NewStringSliceFlag creates a new StringSliceFlag + +func (f *StringSliceFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + StringSliceFlag.Apply + +func (f *StringSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a StringSlice value to the flagSet if required + +type Uint64Flag struct { + *cli.Uint64Flag + // Has unexported fields. +} + Uint64Flag is the flag type that wraps cli.Uint64Flag to allow for other + values to be specified + +func NewUint64Flag(fl *cli.Uint64Flag) *Uint64Flag + NewUint64Flag creates a new Uint64Flag + +func (f *Uint64Flag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + Uint64Flag.Apply + +type UintFlag struct { + *cli.UintFlag + // Has unexported fields. +} + UintFlag is the flag type that wraps cli.UintFlag to allow for other values + to be specified + +func NewUintFlag(fl *cli.UintFlag) *UintFlag + NewUintFlag creates a new UintFlag + +func (f *UintFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + UintFlag.Apply + From 2ac3904d122959569d8fb8b8a2136853237c7169 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 4 May 2022 23:11:05 -0400 Subject: [PATCH 073/125] Include `*` in documented flag value type when applicable --- internal/genflags/generated.gotmpl | 2 +- zz_generated.flags.go | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/genflags/generated.gotmpl b/internal/genflags/generated.gotmpl index 681a101..5d6f0dd 100644 --- a/internal/genflags/generated.gotmpl +++ b/internal/genflags/generated.gotmpl @@ -3,7 +3,7 @@ package {{.PackageName}} {{range .SortedFlagTypes}} -// {{.TypeName}} is a flag with type {{.GoType}} +// {{.TypeName}} is a flag with type {{if .ValuePointer}}*{{end}}{{.GoType}} type {{.TypeName}} struct { Name string diff --git a/zz_generated.flags.go b/zz_generated.flags.go index eb980bc..703e66a 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -4,7 +4,7 @@ package cli import "time" -// Float64SliceFlag is a flag with type Float64Slice +// Float64SliceFlag is a flag with type *Float64Slice type Float64SliceFlag struct { Name string @@ -69,7 +69,7 @@ func (f *GenericFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } -// Int64SliceFlag is a flag with type Int64Slice +// Int64SliceFlag is a flag with type *Int64Slice type Int64SliceFlag struct { Name string @@ -98,7 +98,7 @@ func (f *Int64SliceFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } -// IntSliceFlag is a flag with type IntSlice +// IntSliceFlag is a flag with type *IntSlice type IntSliceFlag struct { Name string @@ -163,7 +163,7 @@ func (f *PathFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } -// StringSliceFlag is a flag with type StringSlice +// StringSliceFlag is a flag with type *StringSlice type StringSliceFlag struct { Name string @@ -194,7 +194,7 @@ func (f *StringSliceFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } -// TimestampFlag is a flag with type Timestamp +// TimestampFlag is a flag with type *Timestamp type TimestampFlag struct { Name string From f1834d176f97c38419b7b249bca96ec5c12e5aaf Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 4 May 2022 23:12:27 -0400 Subject: [PATCH 074/125] Update docs with corrected flag value --- godoc-current.txt | 10 +++++----- testdata/godoc-v2.x.txt | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/godoc-current.txt b/godoc-current.txt index 1a9ab23..1bddc54 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -934,7 +934,7 @@ type Float64SliceFlag struct { Aliases []string EnvVars []string } - Float64SliceFlag is a flag with type Float64Slice + Float64SliceFlag is a flag with type *Float64Slice func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error Apply populates the flag given the flag set and environment @@ -1134,7 +1134,7 @@ type Int64SliceFlag struct { Aliases []string EnvVars []string } - Int64SliceFlag is a flag with type Int64Slice + Int64SliceFlag is a flag with type *Int64Slice func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error Apply populates the flag given the flag set and environment @@ -1273,7 +1273,7 @@ type IntSliceFlag struct { Aliases []string EnvVars []string } - IntSliceFlag is a flag with type IntSlice + IntSliceFlag is a flag with type *IntSlice func (f *IntSliceFlag) Apply(set *flag.FlagSet) error Apply populates the flag given the flag set and environment @@ -1498,7 +1498,7 @@ type StringSliceFlag struct { TakesFile bool } - StringSliceFlag is a flag with type StringSlice + StringSliceFlag is a flag with type *StringSlice func (f *StringSliceFlag) Apply(set *flag.FlagSet) error Apply populates the flag given the flag set and environment @@ -1582,7 +1582,7 @@ type TimestampFlag struct { Layout string } - TimestampFlag is a flag with type Timestamp + TimestampFlag is a flag with type *Timestamp func (f *TimestampFlag) Apply(set *flag.FlagSet) error Apply populates the flag given the flag set and environment diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt index 1a9ab23..1bddc54 100644 --- a/testdata/godoc-v2.x.txt +++ b/testdata/godoc-v2.x.txt @@ -934,7 +934,7 @@ type Float64SliceFlag struct { Aliases []string EnvVars []string } - Float64SliceFlag is a flag with type Float64Slice + Float64SliceFlag is a flag with type *Float64Slice func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error Apply populates the flag given the flag set and environment @@ -1134,7 +1134,7 @@ type Int64SliceFlag struct { Aliases []string EnvVars []string } - Int64SliceFlag is a flag with type Int64Slice + Int64SliceFlag is a flag with type *Int64Slice func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error Apply populates the flag given the flag set and environment @@ -1273,7 +1273,7 @@ type IntSliceFlag struct { Aliases []string EnvVars []string } - IntSliceFlag is a flag with type IntSlice + IntSliceFlag is a flag with type *IntSlice func (f *IntSliceFlag) Apply(set *flag.FlagSet) error Apply populates the flag given the flag set and environment @@ -1498,7 +1498,7 @@ type StringSliceFlag struct { TakesFile bool } - StringSliceFlag is a flag with type StringSlice + StringSliceFlag is a flag with type *StringSlice func (f *StringSliceFlag) Apply(set *flag.FlagSet) error Apply populates the flag given the flag set and environment @@ -1582,7 +1582,7 @@ type TimestampFlag struct { Layout string } - TimestampFlag is a flag with type Timestamp + TimestampFlag is a flag with type *Timestamp func (f *TimestampFlag) Apply(set *flag.FlagSet) error Apply populates the flag given the flag set and environment From 16d5d5a3df85096946ba70a48bf465224a1f7367 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 6 May 2022 22:23:17 -0400 Subject: [PATCH 075/125] Some changes per feedback in #1368 --- app.go | 11 ++--------- category.go | 11 +++++++++++ command.go | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app.go b/app.go index 7379388..ebbf192 100644 --- a/app.go +++ b/app.go @@ -184,14 +184,7 @@ func (a *App) Setup() { c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) } - fc := newFlagCategories() - for _, fl := range c.Flags { - if cf, ok := fl.(CategorizableFlag); ok { - fc.AddFlag(cf.GetCategory(), cf) - } - } - - c.flagCategories = fc + c.flagCategories = newFlagCategoriesFromFlags(c.Flags) newCommands = append(newCommands, c) } a.Commands = newCommands @@ -502,7 +495,7 @@ func (a *App) VisibleCommands() []*Command { // VisibleFlagCategories returns a slice containing all the categories with the flags they contain func (a *App) VisibleFlagCategories() []VisibleFlagCategory { if a.flagCategories == nil { - a.flagCategories = newFlagCategories() + return []VisibleFlagCategory{} } return a.flagCategories.VisibleCategories() } diff --git a/category.go b/category.go index 7bbe4a5..8bf325e 100644 --- a/category.go +++ b/category.go @@ -98,6 +98,17 @@ func newFlagCategories() FlagCategories { } } +func newFlagCategoriesFromFlags(fs []Flag) FlagCategories { + fc := newFlagCategories() + for _, fl := range fs { + if cf, ok := fl.(CategorizableFlag); ok { + fc.AddFlag(cf.GetCategory(), cf) + } + } + + return fc +} + func (f *defaultFlagCategories) AddFlag(category string, fl Flag) { if _, ok := f.m[category]; !ok { f.m[category] = &defaultVisibleFlagCategory{name: category, m: map[string]Flag{}} diff --git a/command.go b/command.go index 02ea5ff..1e39c9e 100644 --- a/command.go +++ b/command.go @@ -284,7 +284,7 @@ func (c *Command) startApp(ctx *Context) error { // VisibleFlagCategories returns a slice containing all the visible flag categories with the flags they contain func (c *Command) VisibleFlagCategories() []VisibleFlagCategory { if c.flagCategories == nil { - c.flagCategories = newFlagCategories() + return []VisibleFlagCategory{} } return c.flagCategories.VisibleCategories() } From 21d435d4d19d206426351afb509ea520bb42ff25 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 7 May 2022 08:50:09 -0400 Subject: [PATCH 076/125] Adapt flag generation for flag categories --- godoc-current.txt | 117 +++++++++++++++++++++++++++-- internal/genflags/generated.gotmpl | 1 + testdata/godoc-v2.x.txt | 117 +++++++++++++++++++++++++++-- zz_generated.flags.go | 15 ++++ 4 files changed, 236 insertions(+), 14 deletions(-) diff --git a/godoc-current.txt b/godoc-current.txt index 1bddc54..62642bb 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -45,11 +45,16 @@ AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{range .VisibleCommands}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} + +GLOBAL OPTIONS:{{range .VisibleFlagCategories}} + {{if .Name}}{{.Name}} + {{end}}{{range .Flags}}{{.}} + {{end}}{{end}}{{else}}{{if .VisibleFlags}} GLOBAL OPTIONS: {{range $index, $option := .VisibleFlags}}{{if $index}} - {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}} + {{end}}{{$option}}{{end}}{{end}}{{end}}{{if .Copyright}} COPYRIGHT: {{.Copyright}}{{end}} @@ -68,11 +73,16 @@ CATEGORY: {{.Category}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlags}} + {{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlagCategories}} + +OPTIONS:{{range .VisibleFlagCategories}} + {{if .Name}}{{.Name}} + {{end}}{{range .Flags}}{{.}} + {{end}}{{end}}{{else}}{{if .VisibleFlags}} OPTIONS: {{range .VisibleFlags}}{{.}} - {{end}}{{end}} + {{end}}{{end}}{{end}} ` CommandHelpTemplate is the text template for the command help topic. cli.go uses text/template to render templates. You can render custom help text by @@ -367,6 +377,10 @@ func (a *App) VisibleCategories() []CommandCategory func (a *App) VisibleCommands() []*Command VisibleCommands returns a slice of the Commands with Hidden=false +func (a *App) VisibleFlagCategories() []VisibleFlagCategory + VisibleFlagCategories returns a slice containing all the categories with the + flags they contain + func (a *App) VisibleFlags() []Flag VisibleFlags returns a slice of the Flags with Hidden=false @@ -407,6 +421,7 @@ type BeforeFunc func(*Context) error type BoolFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -429,6 +444,9 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error func (f *BoolFlag) Get(ctx *Context) bool Get returns the flag’s value in the given Context. +func (f *BoolFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *BoolFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -460,6 +478,14 @@ func (f *BoolFlag) String() string func (f *BoolFlag) TakesValue() bool TakesValue returns true of the flag takes a value, otherwise false +type CategorizableFlag interface { + VisibleFlag + + GetCategory() string +} + CategorizableFlag is an interface that allows us to potentially use a flag + in a categorized representation. + type Command struct { // The name of the command Name string @@ -491,6 +517,7 @@ type Command struct { Subcommands []*Command // List of flags to parse Flags []Flag + // Treat all flags as normal arguments if true SkipFlagParsing bool // Boolean to hide built-in help command and help flag @@ -530,13 +557,17 @@ func (c *Command) Run(ctx *Context) (err error) Run invokes the command given the context, parses ctx.Args() to generate command-specific flags +func (c *Command) VisibleFlagCategories() []VisibleFlagCategory + VisibleFlagCategories returns a slice containing all the visible flag + categories with the flags they contain + func (c *Command) VisibleFlags() []Flag VisibleFlags returns a slice of the Flags with Hidden=false type CommandCategories interface { // AddCommand adds a command to a category, creating a new category if necessary. AddCommand(category string, command *Command) - // categories returns a copy of the category slice + // Categories returns a slice of categories sorted by name Categories() []CommandCategory } CommandCategories interface allows for category manipulation @@ -680,6 +711,7 @@ type DocGenerationFlag interface { type DurationFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -702,6 +734,9 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) error func (f *DurationFlag) Get(ctx *Context) time.Duration Get returns the flag’s value in the given Context. +func (f *DurationFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *DurationFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -797,6 +832,14 @@ var VersionFlag Flag = &BoolFlag{ } VersionFlag prints the version for the application +type FlagCategories interface { + // AddFlags adds a flag to a category, creating a new category if necessary. + AddFlag(category string, fl Flag) + // VisibleCategories returns a slice of visible flag categories sorted by name + VisibleCategories() []VisibleFlagCategory +} + FlagCategories interface allows for category manipulation + type FlagEnvHintFunc func(envVars []string, str string) string FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help with the environment variable details. @@ -841,6 +884,7 @@ func (f FlagsByName) Swap(i, j int) type Float64Flag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -863,6 +907,9 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) error func (f *Float64Flag) Get(ctx *Context) float64 Get returns the flag’s value in the given Context. +func (f *Float64Flag) GetCategory() string + GetCategory returns the category for the flag + func (f *Float64Flag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -920,6 +967,7 @@ func (f *Float64Slice) Value() []float64 type Float64SliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -942,6 +990,9 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error func (f *Float64SliceFlag) Get(ctx *Context) []float64 Get returns the flag’s value in the given Context. +func (f *Float64SliceFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *Float64SliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -982,6 +1033,7 @@ type Generic interface { type GenericFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1007,6 +1059,9 @@ func (f GenericFlag) Apply(set *flag.FlagSet) error func (f *GenericFlag) Get(ctx *Context) interface{} Get returns the flag’s value in the given Context. +func (f *GenericFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *GenericFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1041,6 +1096,7 @@ func (f *GenericFlag) TakesValue() bool type Int64Flag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1063,6 +1119,9 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) error func (f *Int64Flag) Get(ctx *Context) int64 Get returns the flag’s value in the given Context. +func (f *Int64Flag) GetCategory() string + GetCategory returns the category for the flag + func (f *Int64Flag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1120,6 +1179,7 @@ func (i *Int64Slice) Value() []int64 type Int64SliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1142,13 +1202,16 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error func (f *Int64SliceFlag) Get(ctx *Context) []int64 Get returns the flag’s value in the given Context. +func (f *Int64SliceFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *Int64SliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag func (f *Int64SliceFlag) GetEnvVars() []string GetEnvVars returns the env vars for this flag -func (f Int64SliceFlag) GetUsage() string +func (f *Int64SliceFlag) GetUsage() string GetUsage returns the usage string for the flag func (f *Int64SliceFlag) GetValue() string @@ -1176,6 +1239,7 @@ func (f *Int64SliceFlag) TakesValue() bool type IntFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1198,6 +1262,9 @@ func (f *IntFlag) Apply(set *flag.FlagSet) error func (f *IntFlag) Get(ctx *Context) int Get returns the flag’s value in the given Context. +func (f *IntFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *IntFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1259,6 +1326,7 @@ func (i *IntSlice) Value() []int type IntSliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1281,13 +1349,16 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error func (f *IntSliceFlag) Get(ctx *Context) []int Get returns the flag’s value in the given Context. +func (f *IntSliceFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *IntSliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag func (f *IntSliceFlag) GetEnvVars() []string GetEnvVars returns the env vars for this flag -func (f IntSliceFlag) GetUsage() string +func (f *IntSliceFlag) GetUsage() string GetUsage returns the usage string for the flag func (f *IntSliceFlag) GetValue() string @@ -1329,6 +1400,7 @@ type Path = string type PathFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1353,6 +1425,9 @@ func (f *PathFlag) Apply(set *flag.FlagSet) error func (f *PathFlag) Get(ctx *Context) string Get returns the flag’s value in the given Context. +func (f *PathFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *PathFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1401,6 +1476,7 @@ type Serializer interface { type StringFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1425,6 +1501,9 @@ func (f *StringFlag) Apply(set *flag.FlagSet) error func (f *StringFlag) Get(ctx *Context) string Get returns the flag’s value in the given Context. +func (f *StringFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *StringFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1482,6 +1561,7 @@ func (s *StringSlice) Value() []string type StringSliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1506,6 +1586,9 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error func (f *StringSliceFlag) Get(ctx *Context) []string Get returns the flag’s value in the given Context. +func (f *StringSliceFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *StringSliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1566,6 +1649,7 @@ func (t *Timestamp) Value() *time.Time type TimestampFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1590,6 +1674,9 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error func (f *TimestampFlag) Get(ctx *Context) *time.Time Get returns the flag’s value in the given Context. +func (f *TimestampFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *TimestampFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1624,6 +1711,7 @@ func (f *TimestampFlag) TakesValue() bool type Uint64Flag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1646,6 +1734,9 @@ func (f *Uint64Flag) Apply(set *flag.FlagSet) error func (f *Uint64Flag) Get(ctx *Context) uint64 Get returns the flag’s value in the given Context. +func (f *Uint64Flag) GetCategory() string + GetCategory returns the category for the flag + func (f *Uint64Flag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1680,6 +1771,7 @@ func (f *Uint64Flag) TakesValue() bool type UintFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1702,6 +1794,9 @@ func (f *UintFlag) Apply(set *flag.FlagSet) error func (f *UintFlag) Get(ctx *Context) uint Get returns the flag’s value in the given Context. +func (f *UintFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *UintFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1741,6 +1836,14 @@ type VisibleFlag interface { } VisibleFlag is an interface that allows to check if a flag is visible +type VisibleFlagCategory interface { + // Name returns the category name string + Name() string + // Flags returns a slice of VisibleFlag sorted by name + Flags() []VisibleFlag +} + VisibleFlagCategory is a category containing flags. + package altsrc // import "github.com/urfave/cli/v2/altsrc" diff --git a/internal/genflags/generated.gotmpl b/internal/genflags/generated.gotmpl index 5d6f0dd..13d006a 100644 --- a/internal/genflags/generated.gotmpl +++ b/internal/genflags/generated.gotmpl @@ -7,6 +7,7 @@ package {{.PackageName}} type {{.TypeName}} struct { Name string + Category string DefaultText string FilePath string Usage string diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt index 1bddc54..62642bb 100644 --- a/testdata/godoc-v2.x.txt +++ b/testdata/godoc-v2.x.txt @@ -45,11 +45,16 @@ AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{range .VisibleCommands}} {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} + +GLOBAL OPTIONS:{{range .VisibleFlagCategories}} + {{if .Name}}{{.Name}} + {{end}}{{range .Flags}}{{.}} + {{end}}{{end}}{{else}}{{if .VisibleFlags}} GLOBAL OPTIONS: {{range $index, $option := .VisibleFlags}}{{if $index}} - {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}} + {{end}}{{$option}}{{end}}{{end}}{{end}}{{if .Copyright}} COPYRIGHT: {{.Copyright}}{{end}} @@ -68,11 +73,16 @@ CATEGORY: {{.Category}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlags}} + {{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlagCategories}} + +OPTIONS:{{range .VisibleFlagCategories}} + {{if .Name}}{{.Name}} + {{end}}{{range .Flags}}{{.}} + {{end}}{{end}}{{else}}{{if .VisibleFlags}} OPTIONS: {{range .VisibleFlags}}{{.}} - {{end}}{{end}} + {{end}}{{end}}{{end}} ` CommandHelpTemplate is the text template for the command help topic. cli.go uses text/template to render templates. You can render custom help text by @@ -367,6 +377,10 @@ func (a *App) VisibleCategories() []CommandCategory func (a *App) VisibleCommands() []*Command VisibleCommands returns a slice of the Commands with Hidden=false +func (a *App) VisibleFlagCategories() []VisibleFlagCategory + VisibleFlagCategories returns a slice containing all the categories with the + flags they contain + func (a *App) VisibleFlags() []Flag VisibleFlags returns a slice of the Flags with Hidden=false @@ -407,6 +421,7 @@ type BeforeFunc func(*Context) error type BoolFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -429,6 +444,9 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error func (f *BoolFlag) Get(ctx *Context) bool Get returns the flag’s value in the given Context. +func (f *BoolFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *BoolFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -460,6 +478,14 @@ func (f *BoolFlag) String() string func (f *BoolFlag) TakesValue() bool TakesValue returns true of the flag takes a value, otherwise false +type CategorizableFlag interface { + VisibleFlag + + GetCategory() string +} + CategorizableFlag is an interface that allows us to potentially use a flag + in a categorized representation. + type Command struct { // The name of the command Name string @@ -491,6 +517,7 @@ type Command struct { Subcommands []*Command // List of flags to parse Flags []Flag + // Treat all flags as normal arguments if true SkipFlagParsing bool // Boolean to hide built-in help command and help flag @@ -530,13 +557,17 @@ func (c *Command) Run(ctx *Context) (err error) Run invokes the command given the context, parses ctx.Args() to generate command-specific flags +func (c *Command) VisibleFlagCategories() []VisibleFlagCategory + VisibleFlagCategories returns a slice containing all the visible flag + categories with the flags they contain + func (c *Command) VisibleFlags() []Flag VisibleFlags returns a slice of the Flags with Hidden=false type CommandCategories interface { // AddCommand adds a command to a category, creating a new category if necessary. AddCommand(category string, command *Command) - // categories returns a copy of the category slice + // Categories returns a slice of categories sorted by name Categories() []CommandCategory } CommandCategories interface allows for category manipulation @@ -680,6 +711,7 @@ type DocGenerationFlag interface { type DurationFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -702,6 +734,9 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) error func (f *DurationFlag) Get(ctx *Context) time.Duration Get returns the flag’s value in the given Context. +func (f *DurationFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *DurationFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -797,6 +832,14 @@ var VersionFlag Flag = &BoolFlag{ } VersionFlag prints the version for the application +type FlagCategories interface { + // AddFlags adds a flag to a category, creating a new category if necessary. + AddFlag(category string, fl Flag) + // VisibleCategories returns a slice of visible flag categories sorted by name + VisibleCategories() []VisibleFlagCategory +} + FlagCategories interface allows for category manipulation + type FlagEnvHintFunc func(envVars []string, str string) string FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help with the environment variable details. @@ -841,6 +884,7 @@ func (f FlagsByName) Swap(i, j int) type Float64Flag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -863,6 +907,9 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) error func (f *Float64Flag) Get(ctx *Context) float64 Get returns the flag’s value in the given Context. +func (f *Float64Flag) GetCategory() string + GetCategory returns the category for the flag + func (f *Float64Flag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -920,6 +967,7 @@ func (f *Float64Slice) Value() []float64 type Float64SliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -942,6 +990,9 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error func (f *Float64SliceFlag) Get(ctx *Context) []float64 Get returns the flag’s value in the given Context. +func (f *Float64SliceFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *Float64SliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -982,6 +1033,7 @@ type Generic interface { type GenericFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1007,6 +1059,9 @@ func (f GenericFlag) Apply(set *flag.FlagSet) error func (f *GenericFlag) Get(ctx *Context) interface{} Get returns the flag’s value in the given Context. +func (f *GenericFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *GenericFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1041,6 +1096,7 @@ func (f *GenericFlag) TakesValue() bool type Int64Flag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1063,6 +1119,9 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) error func (f *Int64Flag) Get(ctx *Context) int64 Get returns the flag’s value in the given Context. +func (f *Int64Flag) GetCategory() string + GetCategory returns the category for the flag + func (f *Int64Flag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1120,6 +1179,7 @@ func (i *Int64Slice) Value() []int64 type Int64SliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1142,13 +1202,16 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error func (f *Int64SliceFlag) Get(ctx *Context) []int64 Get returns the flag’s value in the given Context. +func (f *Int64SliceFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *Int64SliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag func (f *Int64SliceFlag) GetEnvVars() []string GetEnvVars returns the env vars for this flag -func (f Int64SliceFlag) GetUsage() string +func (f *Int64SliceFlag) GetUsage() string GetUsage returns the usage string for the flag func (f *Int64SliceFlag) GetValue() string @@ -1176,6 +1239,7 @@ func (f *Int64SliceFlag) TakesValue() bool type IntFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1198,6 +1262,9 @@ func (f *IntFlag) Apply(set *flag.FlagSet) error func (f *IntFlag) Get(ctx *Context) int Get returns the flag’s value in the given Context. +func (f *IntFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *IntFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1259,6 +1326,7 @@ func (i *IntSlice) Value() []int type IntSliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1281,13 +1349,16 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error func (f *IntSliceFlag) Get(ctx *Context) []int Get returns the flag’s value in the given Context. +func (f *IntSliceFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *IntSliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag func (f *IntSliceFlag) GetEnvVars() []string GetEnvVars returns the env vars for this flag -func (f IntSliceFlag) GetUsage() string +func (f *IntSliceFlag) GetUsage() string GetUsage returns the usage string for the flag func (f *IntSliceFlag) GetValue() string @@ -1329,6 +1400,7 @@ type Path = string type PathFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1353,6 +1425,9 @@ func (f *PathFlag) Apply(set *flag.FlagSet) error func (f *PathFlag) Get(ctx *Context) string Get returns the flag’s value in the given Context. +func (f *PathFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *PathFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1401,6 +1476,7 @@ type Serializer interface { type StringFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1425,6 +1501,9 @@ func (f *StringFlag) Apply(set *flag.FlagSet) error func (f *StringFlag) Get(ctx *Context) string Get returns the flag’s value in the given Context. +func (f *StringFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *StringFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1482,6 +1561,7 @@ func (s *StringSlice) Value() []string type StringSliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1506,6 +1586,9 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error func (f *StringSliceFlag) Get(ctx *Context) []string Get returns the flag’s value in the given Context. +func (f *StringSliceFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *StringSliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1566,6 +1649,7 @@ func (t *Timestamp) Value() *time.Time type TimestampFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1590,6 +1674,9 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error func (f *TimestampFlag) Get(ctx *Context) *time.Time Get returns the flag’s value in the given Context. +func (f *TimestampFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *TimestampFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1624,6 +1711,7 @@ func (f *TimestampFlag) TakesValue() bool type Uint64Flag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1646,6 +1734,9 @@ func (f *Uint64Flag) Apply(set *flag.FlagSet) error func (f *Uint64Flag) Get(ctx *Context) uint64 Get returns the flag’s value in the given Context. +func (f *Uint64Flag) GetCategory() string + GetCategory returns the category for the flag + func (f *Uint64Flag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1680,6 +1771,7 @@ func (f *Uint64Flag) TakesValue() bool type UintFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -1702,6 +1794,9 @@ func (f *UintFlag) Apply(set *flag.FlagSet) error func (f *UintFlag) Get(ctx *Context) uint Get returns the flag’s value in the given Context. +func (f *UintFlag) GetCategory() string + GetCategory returns the category for the flag + func (f *UintFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1741,6 +1836,14 @@ type VisibleFlag interface { } VisibleFlag is an interface that allows to check if a flag is visible +type VisibleFlagCategory interface { + // Name returns the category name string + Name() string + // Flags returns a slice of VisibleFlag sorted by name + Flags() []VisibleFlag +} + VisibleFlagCategory is a category containing flags. + package altsrc // import "github.com/urfave/cli/v2/altsrc" diff --git a/zz_generated.flags.go b/zz_generated.flags.go index 703e66a..6f39539 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -8,6 +8,7 @@ import "time" type Float64SliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -37,6 +38,7 @@ func (f *Float64SliceFlag) Names() []string { type GenericFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -73,6 +75,7 @@ func (f *GenericFlag) Names() []string { type Int64SliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -102,6 +105,7 @@ func (f *Int64SliceFlag) Names() []string { type IntSliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -131,6 +135,7 @@ func (f *IntSliceFlag) Names() []string { type PathFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -167,6 +172,7 @@ func (f *PathFlag) Names() []string { type StringSliceFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -198,6 +204,7 @@ func (f *StringSliceFlag) Names() []string { type TimestampFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -234,6 +241,7 @@ func (f *TimestampFlag) Names() []string { type BoolFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -268,6 +276,7 @@ func (f *BoolFlag) Names() []string { type Float64Flag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -302,6 +311,7 @@ func (f *Float64Flag) Names() []string { type IntFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -336,6 +346,7 @@ func (f *IntFlag) Names() []string { type Int64Flag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -370,6 +381,7 @@ func (f *Int64Flag) Names() []string { type StringFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -406,6 +418,7 @@ func (f *StringFlag) Names() []string { type DurationFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -440,6 +453,7 @@ func (f *DurationFlag) Names() []string { type UintFlag struct { Name string + Category string DefaultText string FilePath string Usage string @@ -474,6 +488,7 @@ func (f *UintFlag) Names() []string { type Uint64Flag struct { Name string + Category string DefaultText string FilePath string Usage string From 32be625ece1fcab837c2984283784e07aca70add Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 7 May 2022 09:30:42 -0400 Subject: [PATCH 077/125] Use google group email addresses --- CODE_OF_CONDUCT.md | 11 ++++++----- docs/SECURITY.md | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 41ba294..9fee148 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -55,11 +55,12 @@ further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting Dan Buch at dan@meatballhat.com. All complaints will be -reviewed and investigated and will result in a response that is deemed necessary -and appropriate to the circumstances. The project team is obligated to maintain -confidentiality with regard to the reporter of an incident. Further details of -specific enforcement policies may be posted separately. +reported by contacting urfave-governance@googlegroups.com, a members-only group +that is world-postable. All complaints will be reviewed and investigated and +will result in a response that is deemed necessary and appropriate to the +circumstances. The project team is obligated to maintain confidentiality with +regard to the reporter of an incident. Further details of specific enforcement +policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other diff --git a/docs/SECURITY.md b/docs/SECURITY.md index eae6a22..8af4ce4 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -16,7 +16,7 @@ policy! :tada: :lock: Please disclose any vulnerabilities by sending an email to: -[dan+urfave-cli-security@meatballhat.com](mailto:dan+urfave-cli-security@meatballhat.com) +[urfave-security@googlegroups.com](mailto:urfave-security@googlegroups.com) You should expect a response within 48 hours and further communications to be decided via email. The `urfave/cli` maintainer From 63b1a7deee832fd3b2d5e235fe22b0699c5c9163 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 7 May 2022 14:43:15 -0400 Subject: [PATCH 078/125] A few follow-up conflict resolutions --- app.go | 2 +- command.go | 2 +- godoc-current.txt | 2 ++ testdata/godoc-v2.x.txt | 2 ++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app.go b/app.go index 5edfa46..463437a 100644 --- a/app.go +++ b/app.go @@ -391,7 +391,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) if a.Suggest { - if suggestion, err := a.suggestFlagFromError(err, context.Command.Name); err == nil { + if suggestion, err := a.suggestFlagFromError(err, cCtx.Command.Name); err == nil { fmt.Fprintf(a.Writer, suggestion) } } diff --git a/command.go b/command.go index 16ac7c3..3b9b837 100644 --- a/command.go +++ b/command.go @@ -121,7 +121,7 @@ func (c *Command) Run(ctx *Context) (err error) { _, _ = fmt.Fprintln(cCtx.App.Writer) if ctx.App.Suggest { if suggestion, err := ctx.App.suggestFlagFromError(err, c.Name); err == nil { - fmt.Fprintf(context.App.Writer, suggestion) + fmt.Fprintf(cCtx.App.Writer, suggestion) } } _ = ShowCommandHelp(cCtx, c.Name) diff --git a/godoc-current.txt b/godoc-current.txt index 1bddc54..90700eb 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -305,6 +305,8 @@ type App struct { // single-character bool arguments into one // i.e. foobar -o -v -> foobar -ov UseShortOptionHandling bool + // Enable suggestions for commands and flags + Suggest bool // Has unexported fields. } diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt index 1bddc54..90700eb 100644 --- a/testdata/godoc-v2.x.txt +++ b/testdata/godoc-v2.x.txt @@ -305,6 +305,8 @@ type App struct { // single-character bool arguments into one // i.e. foobar -o -v -> foobar -ov UseShortOptionHandling bool + // Enable suggestions for commands and flags + Suggest bool // Has unexported fields. } From 974f2d410de5e80cdf8b51850ec15934332620d5 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 7 May 2022 15:21:00 -0400 Subject: [PATCH 079/125] Guard suggestion capability (+ dependency) with build tag --- .github/workflows/cli.yml | 10 ++++++++-- Makefile | 4 ++-- README.md | 12 +++++++++++- docs.go | 4 ++-- docs_test.go | 4 ++-- go.mod | 2 +- go.sum | 10 ++++++---- internal/build/build.go | 2 +- suggestions.go | 3 +++ suggestions_stubs.go | 12 ++++++++++++ suggestions_test.go | 3 +++ 11 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 suggestions_stubs.go diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 84f9cae..cad0389 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -37,9 +37,15 @@ jobs: - name: vet run: go run internal/build/build.go vet - - name: test with tags + - name: test with urfave_cli_core tag + run: go run internal/build/build.go -tags urfave_cli_core test + + - name: test with urfave_cli_no_docs tag run: go run internal/build/build.go -tags urfave_cli_no_docs test + - name: test with urfave_cli_no_suggest tag + run: go run internal/build/build.go -tags urfave_cli_no_suggest test + - name: test run: go run internal/build/build.go test @@ -47,7 +53,7 @@ jobs: run: go run internal/build/build.go check-binary-size - name: check-binary-size with tags (informational only) - run: go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size || true + run: go run internal/build/build.go -tags urfave_cli_core check-binary-size - name: Upload coverage to Codecov if: success() && matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest' diff --git a/Makefile b/Makefile index 52e9204..9ca3c35 100644 --- a/Makefile +++ b/Makefile @@ -17,11 +17,11 @@ all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun t .PHONY: tag-test tag-test: - go run internal/build/build.go -tags urfave_cli_no_docs test + go run internal/build/build.go -tags urfave_cli_core test .PHONY: tag-check-binary-size tag-check-binary-size: - go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size + go run internal/build/build.go -tags urfave_cli_core check-binary-size .PHONY: gfmrun gfmrun: diff --git a/README.md b/README.md index 6e4d698..35694d2 100644 --- a/README.md +++ b/README.md @@ -59,11 +59,21 @@ import ( You can use the following build tags: +#### `urfave_cli_core` + +When set, applies all `urfave_cli_no.+` build tags to minimize resulting binary +size. + #### `urfave_cli_no_docs` When set, this removes `ToMarkdown` and `ToMan` methods, so your application won't be able to call those. This reduces the resulting binary size by about -300-400 KB (measured using Go 1.18.1 on Linux/amd64), due to less dependencies. +300-400 KB (measured using Go 1.18.1 on Linux/amd64), due to fewer dependencies. + +#### `urfave_cli_no_suggest` + +When set, the capability enabled by setting `App.Suggest` will be a no-op. This +reduces the resulting binary size due to fewer dependencies. ### GOPATH diff --git a/docs.go b/docs.go index 8b1c9c8..4aba31b 100644 --- a/docs.go +++ b/docs.go @@ -1,5 +1,5 @@ -//go:build !urfave_cli_no_docs -// +build !urfave_cli_no_docs +//go:build !urfave_cli_no_docs && !urfave_cli_core +// +build !urfave_cli_no_docs,!urfave_cli_core package cli diff --git a/docs_test.go b/docs_test.go index 12d5d3c..bc3703f 100644 --- a/docs_test.go +++ b/docs_test.go @@ -1,5 +1,5 @@ -//go:build !urfave_cli_no_docs -// +build !urfave_cli_no_docs +//go:build !urfave_cli_no_docs && !urfave_cli_core +// +build !urfave_cli_no_docs,!urfave_cli_core package cli diff --git a/go.mod b/go.mod index 7042c4e..f9111b0 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/BurntSushi/toml v1.1.0 - github.com/antzucaro/matchr v0.0.0-20180616170659-cbc221335f3c + github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0 github.com/cpuguy83/go-md2man/v2 v2.0.1 golang.org/x/text v0.3.7 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index 3cb2b0b..afac06b 100644 --- a/go.sum +++ b/go.sum @@ -2,14 +2,16 @@ github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/antzucaro/matchr v0.0.0-20180616170659-cbc221335f3c h1:CucViv7orgFBMkehuFFdkCVF5ERovbkRRyhvaYaHu/k= github.com/antzucaro/matchr v0.0.0-20180616170659-cbc221335f3c/go.mod h1:bV/CkX4+ANGDaBwbHkt9kK287al/i9BsB18PRBvyqYo= -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/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/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0 h1:R/qAiUxFT3mNgQaNqJe0IVznjKRNm23ohAIh9lgtlzc= +github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0/go.mod h1:v3ZDlfVAL1OrkKHbGSFFK60k0/7hruHPDq2XMs9Gu6U= 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/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.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/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= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= diff --git a/internal/build/build.go b/internal/build/build.go index 929ab0f..e74acfb 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -242,7 +242,7 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) { tags := c.String("tags") - if strings.Contains(tags, "urfave_cli_no_docs") { + if strings.Contains(tags, "urfave_cli_core") || strings.Contains(tags, "urfave_cli_no_docs") { desiredMinBinarySize = 1.39 } diff --git a/suggestions.go b/suggestions.go index 476af4d..301c598 100644 --- a/suggestions.go +++ b/suggestions.go @@ -1,3 +1,6 @@ +//go:build !urfave_cli_no_suggest && !urfave_cli_core +// +build !urfave_cli_no_suggest,!urfave_cli_core + package cli import ( diff --git a/suggestions_stubs.go b/suggestions_stubs.go new file mode 100644 index 0000000..48643bb --- /dev/null +++ b/suggestions_stubs.go @@ -0,0 +1,12 @@ +//go:build urfave_cli_no_suggest || urfave_cli_core +// +build urfave_cli_no_suggest urfave_cli_core + +package cli + +func (a *App) suggestFlagFromError(err error, _ string) (string, error) { + return "", err +} + +func suggestCommand([]*Command, string) string { + return "" +} diff --git a/suggestions_test.go b/suggestions_test.go index 3b31250..f87e702 100644 --- a/suggestions_test.go +++ b/suggestions_test.go @@ -1,3 +1,6 @@ +//go:build !urfave_cli_no_suggest && !urfave_cli_core +// +build !urfave_cli_no_suggest,!urfave_cli_core + package cli import ( From 9da2c564f8b1563222e3e60a1761de1d0a3c43c4 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 7 May 2022 20:57:44 -0400 Subject: [PATCH 080/125] Add example app with `Suggest` support --- suggestions_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/suggestions_test.go b/suggestions_test.go index f87e702..55ef29e 100644 --- a/suggestions_test.go +++ b/suggestions_test.go @@ -123,3 +123,36 @@ func TestSuggestCommand(t *testing.T) { expect(t, res, fmt.Sprintf(didYouMeanTemplate, testCase.expected)) } } + +func ExampleApp_Suggest() { + app := &App{ + Name: "greet", + Suggest: true, + Flags: []Flag{ + &StringFlag{Name: "name", Value: "squirrel", Usage: "a name to say"}, + }, + Action: func(c *Context) error { + fmt.Printf("Hello %v\n", c.String("name")) + return nil + }, + } + + app.Run([]string{"greet", "--nema", "chipmunk"}) + // Output: + // Incorrect Usage. flag provided but not defined: -nema + // + // Did you mean '--name'? + // + // NAME: + // greet - A new cli application + // + // USAGE: + // greet [global options] command [command options] [arguments...] + // + // COMMANDS: + // help, h Shows a list of commands or help for one command + // + // GLOBAL OPTIONS: + // --name value a name to say (default: "squirrel") + // --help, -h show help (default: false) +} From 34eed95a8ee897b19e8abe77ddffcc7f7e39bdfb Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 7 May 2022 22:06:39 -0400 Subject: [PATCH 081/125] Reduce amount of text compared in suggest example app --- suggestions_test.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/suggestions_test.go b/suggestions_test.go index 55ef29e..359c793 100644 --- a/suggestions_test.go +++ b/suggestions_test.go @@ -126,8 +126,10 @@ func TestSuggestCommand(t *testing.T) { func ExampleApp_Suggest() { app := &App{ - Name: "greet", - Suggest: true, + Name: "greet", + Suggest: true, + HideHelp: true, + HideHelpCommand: true, Flags: []Flag{ &StringFlag{Name: "name", Value: "squirrel", Usage: "a name to say"}, }, @@ -147,12 +149,8 @@ func ExampleApp_Suggest() { // greet - A new cli application // // USAGE: - // greet [global options] command [command options] [arguments...] - // - // COMMANDS: - // help, h Shows a list of commands or help for one command + // greet [global options] [arguments...] // // GLOBAL OPTIONS: // --name value a name to say (default: "squirrel") - // --help, -h show help (default: false) } From 9bd6349ed21eb7338d28f2c58c3a17eef0895741 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 7 May 2022 23:06:43 -0400 Subject: [PATCH 082/125] Add example app for suggestion in command and further reduce example output diff potential --- suggestions_test.go | 63 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/suggestions_test.go b/suggestions_test.go index 359c793..fa7b0c9 100644 --- a/suggestions_test.go +++ b/suggestions_test.go @@ -126,15 +126,16 @@ func TestSuggestCommand(t *testing.T) { func ExampleApp_Suggest() { app := &App{ - Name: "greet", - Suggest: true, - HideHelp: true, - HideHelpCommand: true, + Name: "greet", + Suggest: true, + HideHelp: true, + HideHelpCommand: true, + CustomAppHelpTemplate: "(this space intentionally left blank)\n", Flags: []Flag{ &StringFlag{Name: "name", Value: "squirrel", Usage: "a name to say"}, }, - Action: func(c *Context) error { - fmt.Printf("Hello %v\n", c.String("name")) + Action: func(cCtx *Context) error { + fmt.Printf("Hello %v\n", cCtx.String("name")) return nil }, } @@ -145,12 +146,46 @@ func ExampleApp_Suggest() { // // Did you mean '--name'? // - // NAME: - // greet - A new cli application - // - // USAGE: - // greet [global options] [arguments...] - // - // GLOBAL OPTIONS: - // --name value a name to say (default: "squirrel") + // (this space intentionally left blank) +} + +func ExampleApp_Suggest_command() { + app := &App{ + Name: "greet", + Suggest: true, + HideHelp: true, + HideHelpCommand: true, + CustomAppHelpTemplate: "(this space intentionally left blank)\n", + Flags: []Flag{ + &StringFlag{Name: "name", Value: "squirrel", Usage: "a name to say"}, + }, + Action: func(cCtx *Context) error { + fmt.Printf("Hello %v\n", cCtx.String("name")) + return nil + }, + Commands: []*Command{ + { + Name: "neighbors", + CustomHelpTemplate: "(this space intentionally left blank)\n", + Flags: []Flag{ + &BoolFlag{Name: "smiling"}, + }, + Action: func(cCtx *Context) error { + if cCtx.Bool("smiling") { + fmt.Println("😀") + } + fmt.Println("Hello, neighbors") + return nil + }, + }, + }, + } + + app.Run([]string{"greet", "neighbors", "--sliming"}) + // Output: + // Incorrect Usage: flag provided but not defined: -sliming + // + // Did you mean '--smiling'? + // + // (this space intentionally left blank) } From 858ce7ee6dde4e030be5cb1733eecb4d33c9b794 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 8 May 2022 11:36:03 -0400 Subject: [PATCH 083/125] Initial mkdocs setup Connected to #1343 --- docs/index.md | 20 +++++++++++ docs/migrate-v1-to-v2.md | 23 +----------- docs/v1/{manual.md => index.md} | 33 +----------------- docs/v2/{manual.md => index.md} | 57 +++++------------------------- mkdocs-requirements.txt | 4 +++ mkdocs.yml | 62 +++++++++++++++++++++++++++++++++ 6 files changed, 97 insertions(+), 102 deletions(-) create mode 100644 docs/index.md rename docs/v1/{manual.md => index.md} (96%) rename docs/v2/{manual.md => index.md} (95%) create mode 100644 mkdocs-requirements.txt create mode 100644 mkdocs.yml diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..d9d5d46 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,20 @@ +# Welcome to 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/main/graph/badge.svg)](https://codecov.io/gh/urfave/cli) + +`urfave/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. + +These are the guides for each major supported version: + +- [`v2`](./v2/) +- [`v1`](./v1/) + +In addition to the version-specific guides, these other documents are available: + +- [`CONTRIBUTING`](./CONTRIBUTING/) +- [`RELEASING`](./RELEASING/) diff --git a/docs/migrate-v1-to-v2.md b/docs/migrate-v1-to-v2.md index c555468..7c703c8 100644 --- a/docs/migrate-v1-to-v2.md +++ b/docs/migrate-v1-to-v2.md @@ -1,6 +1,4 @@ -Migration Guide: v1 to v2 -=== - +# Migration Guide: v1 to v2 v2 has a number of breaking changes but converting is relatively straightforward: make the changes documented below then resolve any @@ -11,25 +9,6 @@ If you find any issues not covered by this document, please post a comment on [Issue 921](https://github.com/urfave/cli/issues/921) or 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) - * [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) - * [Appending Commands](#appending-commands) - * [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) - - - # Flags before args In v2 flags must come before args. This is more POSIX-compliant. You diff --git a/docs/v1/manual.md b/docs/v1/index.md similarity index 96% rename from docs/v1/manual.md rename to docs/v1/index.md index dd22bdb..6f568b7 100644 --- a/docs/v1/manual.md +++ b/docs/v1/index.md @@ -1,35 +1,4 @@ -cli v1 manual -=== - - - -- [Getting Started](#getting-started) -- [Examples](#examples) - * [Arguments](#arguments) - * [Flags](#flags) - + [Placeholder Values](#placeholder-values) - + [Alternate Names](#alternate-names) - + [Ordering](#ordering) - + [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) - + [Precedence](#precedence) - * [Subcommands](#subcommands) - * [Subcommands categories](#subcommands-categories) - * [Exit code](#exit-code) - * [Combining short options](#combining-short-options) - * [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) - * [Migrating to V2](#migrating-to-v2) - - +# v1 guide ## Getting Started diff --git a/docs/v2/manual.md b/docs/v2/index.md similarity index 95% rename from docs/v2/manual.md rename to docs/v2/index.md index b480dd6..9c26100 100644 --- a/docs/v2/manual.md +++ b/docs/v2/index.md @@ -1,51 +1,4 @@ -cli v2 manual -=== - - - -- [Migrating From Older Releases](#migrating-from-older-releases) -- [Getting Started](#getting-started) -- [Examples](#examples) - * [Arguments](#arguments) - * [Flags](#flags) - + [Placeholder Values](#placeholder-values) - + [Alternate Names](#alternate-names) - + [Ordering](#ordering) - + [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) - * [Subcommands categories](#subcommands-categories) - * [Exit code](#exit-code) - * [Combining short options](#combining-short-options) - * [Bash Completion](#bash-completion) - + [Default auto-completion](#default-auto-completion) - + [Custom auto-completion](#custom-auto-completion) - + [Enabling](#enabling) - + [Distribution and Persistent Autocompletion](#distribution-and-persistent-autocompletion) - + [Customization](#customization) - + [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) - + [Customization](#customization-2) - * [Timestamp Flag](#timestamp-flag) - * [Full API Example](#full-api-example) - - - -## Migrating From Older Releases - -There are a small set of breaking changes between v1 and v2. -Converting is relatively straightforward and typically takes less than -an hour. Specific steps are included in -[Migration Guide: v1 to v2](../migrate-v1-to-v2.md). Also see the [pkg.go.dev docs](https://pkg.go.dev/github.com/urfave/cli/v2) for v2 API documentation. +# v2 guide ## Getting Started @@ -1714,3 +1667,11 @@ func wopAction(c *cli.Context) error { return nil } ``` + +## Migrating From Older Releases + +There are a small set of breaking changes between v1 and v2. +Converting is relatively straightforward and typically takes less than +an hour. Specific steps are included in +[Migration Guide: v1 to v2](../migrate-v1-to-v2.md). Also see the [pkg.go.dev docs](https://pkg.go.dev/github.com/urfave/cli/v2) for v2 API documentation. + diff --git a/mkdocs-requirements.txt b/mkdocs-requirements.txt new file mode 100644 index 0000000..94dae76 --- /dev/null +++ b/mkdocs-requirements.txt @@ -0,0 +1,4 @@ +mkdocs +mkdocs-material +mkdocs-git-revision-date-localized-plugin +pygments diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..c963d37 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,62 @@ +# NOTE: the mkdocs dependencies will need to be installed out of +# band until this whole thing gets more automated: +# +# pip install -r mkdocs-requirements.txt +# + +site_name: urfave/cli +site_url: https://cli.urfave.org/ +repo_url: https://github.com/urvafe/cli +edit_uri: edit/main/docs/ +nav: + - Home: index.md + - v2 Manual: v2/index.md + - v1 Manual: v1/index.md +theme: + name: material + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + toggle: + icon: material/brightness-4 + name: dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/brightness-7 + name: light mode +plugins: + - git-revision-date-localized + - search +# NOTE: this is the recommended configuration from +# https://squidfunk.github.io/mkdocs-material/setup/extensions/#recommended-configuration +markdown_extensions: + - abbr + - admonition + - attr_list + - def_list + - footnotes + - meta + - md_in_html + - toc: + permalink: true + - pymdownx.arithmatex: + generic: true + - pymdownx.betterem: + smart_enable: all + - pymdownx.caret + - pymdownx.details + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg + - pymdownx.highlight + - pymdownx.inlinehilite + - pymdownx.keys + - pymdownx.mark + - pymdownx.smartsymbols + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.tilde From 8da1afc62be381a58a0d029a7e11bf51ec634970 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 8 May 2022 11:38:41 -0400 Subject: [PATCH 084/125] Un-rename manual docs + symlink instead --- docs/v1/index.md | 1456 +-------------------------------------- docs/v1/manual.md | 1455 +++++++++++++++++++++++++++++++++++++++ docs/v2/index.md | 1678 +-------------------------------------------- docs/v2/manual.md | 1677 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 3134 insertions(+), 3132 deletions(-) mode change 100644 => 120000 docs/v1/index.md create mode 100644 docs/v1/manual.md mode change 100644 => 120000 docs/v2/index.md create mode 100644 docs/v2/manual.md diff --git a/docs/v1/index.md b/docs/v1/index.md deleted file mode 100644 index 6f568b7..0000000 --- a/docs/v1/index.md +++ /dev/null @@ -1,1455 +0,0 @@ -# v1 guide - -## Getting Started - -One of the philosophies behind cli is that an API should be playful and full of -discovery. So a cli app can be as little as one line of code in `main()`. - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - err := cli.NewApp().Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -This app will run and show help text, but is not very useful. Let's give an -action to execute and some help documentation: - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - app.Name = "boom" - app.Usage = "make an explosive entrance" - app.Action = func(c *cli.Context) error { - fmt.Println("boom! I say!") - return nil - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Running this already gives you a ton of functionality, plus support for things -like subcommands and flags, which are covered below. - -## Examples - -Being a programmer can be a lonely job. Thankfully by the power of automation -that is not the case! Let's create a greeter app to fend off our demons of -loneliness! - -Start by creating a directory named `greet`, and within it, add a file, -`greet.go` with the following code in it: - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - app.Name = "greet" - app.Usage = "fight the loneliness!" - app.Action = func(c *cli.Context) error { - fmt.Println("Hello friend!") - return nil - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Install our command to the `$GOPATH/bin` directory: - -``` -$ go install -``` - -Finally run our new command: - -``` -$ greet -Hello friend! -``` - -cli also generates neat help text: - -``` -$ greet help -NAME: - greet - fight the loneliness! - -USAGE: - greet [global options] command [command options] [arguments...] - -VERSION: - 0.0.0 - -COMMANDS: - help, h Shows a list of commands or help for one command - -GLOBAL OPTIONS - --version Shows version information -``` - -### Arguments - -You can lookup arguments by calling the `Args` function on `cli.Context`, e.g.: - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Action = func(c *cli.Context) error { - fmt.Printf("Hello %q", c.Args().Get(0)) - return nil - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -### Flags - -Setting and querying flags is simple. - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", - }, - } - - app.Action = func(c *cli.Context) error { - name := "Nefertiti" - if c.NArg() > 0 { - name = c.Args().Get(0) - } - if c.String("lang") == "spanish" { - fmt.Println("Hola", name) - } else { - fmt.Println("Hello", name) - } - return nil - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -You can also set a destination variable for a flag, to which the content will be -scanned. - - -``` go -package main - -import ( - "log" - "os" - "fmt" - - "github.com/urfave/cli" -) - -func main() { - var language string - - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", - Destination: &language, - }, - } - - app.Action = func(c *cli.Context) error { - name := "someone" - if c.NArg() > 0 { - name = c.Args()[0] - } - if language == "spanish" { - fmt.Println("Hola", name) - } else { - fmt.Println("Hello", name) - } - return nil - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -See full list of flags at http://godoc.org/github.com/urfave/cli - -#### Placeholder Values - -Sometimes it's useful to specify a flag's value within the usage string itself. -Such placeholders are indicated with back quotes. - -For example this: - - -```go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag{ - cli.StringFlag{ - Name: "config, c", - Usage: "Load configuration from `FILE`", - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Will result in help output like: - -``` ---config FILE, -c FILE Load configuration from FILE -``` - -Note that only the first placeholder is used. Subsequent back-quoted words will -be left as-is. - -#### Alternate Names - -You can set alternate (or short) names for flags by providing a comma-delimited -list for the `Name`. e.g. - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -That flag can then be set with `--lang spanish` or `-l spanish`. Note that -giving two different forms of the same flag in the same command invocation is an -error. - -#### Ordering - -Flags for the application and commands are shown in the order they are defined. -However, it's possible to sort them from outside this library by using `FlagsByName` -or `CommandsByName` with `sort`. - -For example this: - - -``` go -package main - -import ( - "log" - "os" - "sort" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "Language for the greeting", - }, - cli.StringFlag{ - Name: "config, c", - Usage: "Load configuration from `FILE`", - }, - } - - app.Commands = []cli.Command{ - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - return nil - }, - }, - { - Name: "add", - Aliases: []string{"a"}, - Usage: "add a task to the list", - Action: func(c *cli.Context) error { - return nil - }, - }, - } - - sort.Sort(cli.FlagsByName(app.Flags)) - sort.Sort(cli.CommandsByName(app.Commands)) - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Will result in help output like: - -``` ---config FILE, -c FILE Load configuration from FILE ---lang value, -l value Language for the greeting (default: "english") -``` - -#### Values from the Environment - -You can also have the default value set from the environment via `EnvVar`. e.g. - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", - EnvVar: "APP_LANG", - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -The `EnvVar` may also be given as a comma-delimited "cascade", where the first -environment variable that resolves is used as the default. - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", - EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG", - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -#### Values from files - -You can also have the default value set from file via `FilePath`. e.g. - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "password, p", - Usage: "password for the mysql database", - FilePath: "/etc/mysql/password", - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Note that default values set from file (e.g. `FilePath`) take precedence over -default values set from the environment (e.g. `EnvVar`). - -#### Values from alternate input sources (YAML, TOML, and others) - -There is a separate package altsrc that adds support for getting flag values -from other file input sources. - -Currently supported input source formats: -* YAML -* JSON -* TOML - -In order to get values for a flag from an alternate input source the following -code would be added to wrap an existing cli.Flag like below: - -``` go - altsrc.NewIntFlag(cli.IntFlag{Name: "test"}) -``` - -Initialization must also occur for these flags. Below is an example initializing -getting data from a yaml file below. - -``` go - command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) -``` - -The code above will use the "load" string as a flag name to get the file name of -a yaml file from the cli.Context. It will then use that file name to initialize -the yaml input source for any flags that are defined on that command. As a note -the "load" flag used would also have to be defined on the command flags in order -for this code snippet to work. - -Currently only YAML, JSON, and TOML files are supported but developers can add support -for other input sources by implementing the altsrc.InputSourceContext for their -given sources. - -Here is a more complete sample of a command using YAML support: - - -``` go -package notmain - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" - "github.com/urfave/cli/altsrc" -) - -func main() { - app := cli.NewApp() - - flags := []cli.Flag{ - altsrc.NewIntFlag(cli.IntFlag{Name: "test"}), - cli.StringFlag{Name: "load"}, - } - - app.Action = func(c *cli.Context) error { - fmt.Println("yaml ist rad") - return nil - } - - app.Before = altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")) - app.Flags = flags - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -#### Precedence - -The precedence for flag value sources is as follows (highest to lowest): - -0. Command line flag value from user -0. Environment variable (if specified) -0. Configuration file (if specified) -0. Default defined on the flag - -### Subcommands - -Subcommands can be defined for a more git-like command line app. - - -```go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Commands = []cli.Command{ - { - Name: "add", - Aliases: []string{"a"}, - Usage: "add a task to the list", - Action: func(c *cli.Context) error { - fmt.Println("added task: ", c.Args().First()) - return nil - }, - }, - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - fmt.Println("completed task: ", c.Args().First()) - return nil - }, - }, - { - Name: "template", - Aliases: []string{"t"}, - Usage: "options for task templates", - Subcommands: []cli.Command{ - { - Name: "add", - Usage: "add a new template", - Action: func(c *cli.Context) error { - fmt.Println("new task template: ", c.Args().First()) - return nil - }, - }, - { - Name: "remove", - Usage: "remove an existing template", - Action: func(c *cli.Context) error { - fmt.Println("removed task template: ", c.Args().First()) - return nil - }, - }, - }, - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -### Subcommands categories - -For additional organization in apps that have many subcommands, you can -associate a category for each command to group them together in the help -output. - -E.g. - -```go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Commands = []cli.Command{ - { - Name: "noop", - }, - { - Name: "add", - Category: "Template actions", - }, - { - Name: "remove", - Category: "Template actions", - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Will include: - -``` -COMMANDS: - noop - - Template actions: - add - remove -``` - -### Exit code - -Calling `App.Run` will not automatically call `os.Exit`, which means that by -default the exit code will "fall through" to being `0`. An explicit exit code -may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a -`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.: - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - app.Flags = []cli.Flag{ - cli.BoolFlag{ - Name: "ginger-crouton", - Usage: "Add ginger croutons to the soup", - }, - } - app.Action = func(ctx *cli.Context) error { - if !ctx.Bool("ginger-crouton") { - return cli.NewExitError("Ginger croutons are not in the soup", 86) - } - return nil - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -### Combining short options - -Traditional use of options using their shortnames look like this: - -``` -$ cmd -s -o -m "Some message" -``` - -Suppose you want users to be able to combine options with their shortnames. This -can be done using the `UseShortOptionHandling` bool in your app configuration, -or for individual commands by attaching it to the command configuration. For -example: - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - app.UseShortOptionHandling = true - app.Commands = []cli.Command{ - { - Name: "short", - Usage: "complete a task on the list", - Flags: []cli.Flag{ - cli.BoolFlag{Name: "serve, s"}, - cli.BoolFlag{Name: "option, o"}, - cli.StringFlag{Name: "message, m"}, - }, - Action: func(c *cli.Context) error { - fmt.Println("serve:", c.Bool("serve")) - fmt.Println("option:", c.Bool("option")) - fmt.Println("message:", c.String("message")) - return nil - }, - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -If your program has any number of bool flags such as `serve` and `option`, and -optionally one non-bool flag `message`, with the short options of `-s`, `-o`, -and `-m` respectively, setting `UseShortOptionHandling` will also support the -following syntax: - -``` -$ cmd -som "Some message" -``` - -If you enable `UseShortOptionHandling`, then you must not use any flags that -have a single leading `-` or this will result in failures. For example, -`-option` can no longer be used. Flags with two leading dashes (such as -`--options`) are still valid. - -### Bash Completion - -You can enable completion commands by setting the `EnableBashCompletion` -flag on the `App` object. By default, this setting will only auto-complete to -show an app's subcommands, but you can write your own completion methods for -the App or its subcommands. - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} - - app := cli.NewApp() - app.EnableBashCompletion = true - app.Commands = []cli.Command{ - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - fmt.Println("completed task: ", c.Args().First()) - return nil - }, - BashComplete: func(c *cli.Context) { - // This will complete if no args are passed - if c.NArg() > 0 { - return - } - for _, t := range tasks { - fmt.Println(t) - } - }, - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -#### Enabling - -Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while -setting the `PROG` variable to the name of your program: - -`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` - -#### Distribution - -Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename -it to the name of the program you wish to add autocomplete support for (or -automatically install it there if you are distributing a package). Don't forget -to source the file to make it active in the current shell. - -``` -sudo cp src/bash_autocomplete /etc/bash_completion.d/ -source /etc/bash_completion.d/ -``` - -Alternatively, you can just document that users should source the generic -`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set -to the name of their program (as above). - -#### Customization - -The default bash completion flag (`--generate-bash-completion`) is defined as -`cli.BashCompletionFlag`, and may be redefined if desired, e.g.: - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - cli.BashCompletionFlag = cli.BoolFlag{ - Name: "compgen", - Hidden: true, - } - - app := cli.NewApp() - app.EnableBashCompletion = true - app.Commands = []cli.Command{ - { - Name: "wat", - }, - } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -### Generated Help Text - -The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked -by the cli internals in order to print generated help text for the app, command, -or subcommand, and break execution. - -#### Customization - -All of the help text generation may be customized, and at multiple levels. The -templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and -`SubcommandHelpTemplate` which may be reassigned or augmented, and full override -is possible by assigning a compatible func to the `cli.HelpPrinter` variable, -e.g.: - - -``` go -package main - -import ( - "fmt" - "log" - "io" - "os" - - "github.com/urfave/cli" -) - -func main() { - // EXAMPLE: Append to an existing template - cli.AppHelpTemplate = fmt.Sprintf(`%s - -WEBSITE: http://awesometown.example.com - -SUPPORT: support@awesometown.example.com - -`, cli.AppHelpTemplate) - - // EXAMPLE: Override a template - cli.AppHelpTemplate = `NAME: - {{.Name}} - {{.Usage}} -USAGE: - {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} - {{if len .Authors}} -AUTHOR: - {{range .Authors}}{{ . }}{{end}} - {{end}}{{if .Commands}} -COMMANDS: -{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} -GLOBAL OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}}{{if .Copyright }} -COPYRIGHT: - {{.Copyright}} - {{end}}{{if .Version}} -VERSION: - {{.Version}} - {{end}} -` - - // EXAMPLE: Replace the `HelpPrinter` func - cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { - fmt.Println("Ha HA. I pwnd the help!!1") - } - - err := cli.NewApp().Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -The default flag may be customized to something other than `-h/--help` by -setting `cli.HelpFlag`, e.g.: - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - cli.HelpFlag = cli.BoolFlag{ - Name: "halp, haaaaalp", - Usage: "HALP", - EnvVar: "SHOW_HALP,HALPPLZ", - } - - err := cli.NewApp().Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -### Version Flag - -The default version flag (`-v/--version`) is defined as `cli.VersionFlag`, which -is checked by the cli internals in order to print the `App.Version` via -`cli.VersionPrinter` and break execution. - -#### Customization - -The default flag may be customized to something other than `-v/--version` by -setting `cli.VersionFlag`, e.g.: - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli" -) - -func main() { - cli.VersionFlag = cli.BoolFlag{ - Name: "print-version, V", - Usage: "print only the version", - } - - app := cli.NewApp() - app.Name = "partay" - app.Version = "19.99.0" - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Alternatively, the version printer at `cli.VersionPrinter` may be overridden, e.g.: - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli" -) - -var ( - Revision = "fafafaf" -) - -func main() { - cli.VersionPrinter = func(c *cli.Context) { - fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision) - } - - app := cli.NewApp() - app.Name = "partay" - app.Version = "19.99.0" - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -#### Full API Example - -**Notice**: This is a contrived (functioning) example meant strictly for API -demonstration purposes. Use of one's imagination is encouraged. - - -``` go -package main - -import ( - "errors" - "flag" - "fmt" - "io" - "io/ioutil" - "os" - "time" - - "github.com/urfave/cli" -) - -func init() { - cli.AppHelpTemplate += "\nCUSTOMIZED: you bet ur muffins\n" - cli.CommandHelpTemplate += "\nYMMV\n" - cli.SubcommandHelpTemplate += "\nor something\n" - - cli.HelpFlag = cli.BoolFlag{Name: "halp"} - cli.BashCompletionFlag = cli.BoolFlag{Name: "compgen", Hidden: true} - cli.VersionFlag = cli.BoolFlag{Name: "print-version, V"} - - cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { - fmt.Fprintf(w, "best of luck to you\n") - } - cli.VersionPrinter = func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "version=%s\n", c.App.Version) - } - cli.OsExiter = func(c int) { - fmt.Fprintf(cli.ErrWriter, "refusing to exit %d\n", c) - } - cli.ErrWriter = ioutil.Discard - cli.FlagStringer = func(fl cli.Flag) string { - return fmt.Sprintf("\t\t%s", fl.GetName()) - } -} - -type hexWriter struct{} - -func (w *hexWriter) Write(p []byte) (int, error) { - for _, b := range p { - fmt.Printf("%x", b) - } - fmt.Printf("\n") - - return len(p), nil -} - -type genericType struct{ - s string -} - -func (g *genericType) Set(value string) error { - g.s = value - return nil -} - -func (g *genericType) String() string { - return g.s -} - -func main() { - app := cli.NewApp() - app.Name = "kənˈtrīv" - app.Version = "19.99.0" - app.Compiled = time.Now() - app.Authors = []cli.Author{ - cli.Author{ - Name: "Example Human", - Email: "human@example.com", - }, - } - app.Copyright = "(c) 1999 Serious Enterprise" - app.HelpName = "contrive" - app.Usage = "demonstrate available API" - app.UsageText = "contrive - demonstrating the available API" - app.ArgsUsage = "[args and such]" - app.Commands = []cli.Command{ - cli.Command{ - Name: "doo", - Aliases: []string{"do"}, - Category: "motion", - Usage: "do the doo", - UsageText: "doo - does the dooing", - Description: "no really, there is a lot of dooing to be done", - ArgsUsage: "[arrgh]", - Flags: []cli.Flag{ - cli.BoolFlag{Name: "forever, forevvarr"}, - }, - Subcommands: cli.Commands{ - cli.Command{ - Name: "wop", - Action: wopAction, - }, - }, - SkipFlagParsing: false, - HideHelp: false, - Hidden: false, - HelpName: "doo!", - BashComplete: func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "--better\n") - }, - Before: func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "brace for impact\n") - return nil - }, - After: func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "did we lose anyone?\n") - return nil - }, - Action: func(c *cli.Context) error { - c.Command.FullName() - c.Command.HasName("wop") - c.Command.Names() - c.Command.VisibleFlags() - fmt.Fprintf(c.App.Writer, "dodododododoodododddooooododododooo\n") - if c.Bool("forever") { - c.Command.Run(c) - } - return nil - }, - OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { - fmt.Fprintf(c.App.Writer, "for shame\n") - return err - }, - }, - } - app.Flags = []cli.Flag{ - cli.BoolFlag{Name: "fancy"}, - cli.BoolTFlag{Name: "fancier"}, - cli.DurationFlag{Name: "howlong, H", Value: time.Second * 3}, - cli.Float64Flag{Name: "howmuch"}, - cli.GenericFlag{Name: "wat", Value: &genericType{}}, - cli.Int64Flag{Name: "longdistance"}, - cli.Int64SliceFlag{Name: "intervals"}, - cli.IntFlag{Name: "distance"}, - cli.IntSliceFlag{Name: "times"}, - cli.StringFlag{Name: "dance-move, d"}, - cli.StringSliceFlag{Name: "names, N"}, - cli.UintFlag{Name: "age"}, - cli.Uint64Flag{Name: "bigage"}, - } - app.EnableBashCompletion = true - app.UseShortOptionHandling = true - app.HideHelp = false - app.HideVersion = false - app.BashComplete = func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") - } - app.Before = func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "HEEEERE GOES\n") - return nil - } - app.After = func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "Phew!\n") - return nil - } - app.CommandNotFound = func(c *cli.Context, command string) { - fmt.Fprintf(c.App.Writer, "Thar be no %q here.\n", command) - } - app.OnUsageError = func(c *cli.Context, err error, isSubcommand bool) error { - if isSubcommand { - return err - } - - fmt.Fprintf(c.App.Writer, "WRONG: %#v\n", err) - return nil - } - app.Action = func(c *cli.Context) error { - cli.DefaultAppComplete(c) - cli.HandleExitCoder(errors.New("not an exit coder, though")) - cli.ShowAppHelp(c) - cli.ShowCommandCompletions(c, "nope") - cli.ShowCommandHelp(c, "also-nope") - cli.ShowCompletions(c) - cli.ShowSubcommandHelp(c) - cli.ShowVersion(c) - - categories := c.App.Categories() - categories.AddCommand("sounds", cli.Command{ - Name: "bloop", - }) - - for _, category := range c.App.Categories() { - fmt.Fprintf(c.App.Writer, "%s\n", category.Name) - fmt.Fprintf(c.App.Writer, "%#v\n", category.Commands) - fmt.Fprintf(c.App.Writer, "%#v\n", category.VisibleCommands()) - } - - fmt.Printf("%#v\n", c.App.Command("doo")) - if c.Bool("infinite") { - c.App.Run([]string{"app", "doo", "wop"}) - } - - if c.Bool("forevar") { - c.App.RunAsSubcommand(c) - } - c.App.Setup() - fmt.Printf("%#v\n", c.App.VisibleCategories()) - fmt.Printf("%#v\n", c.App.VisibleCommands()) - fmt.Printf("%#v\n", c.App.VisibleFlags()) - - fmt.Printf("%#v\n", c.Args().First()) - if len(c.Args()) > 0 { - fmt.Printf("%#v\n", c.Args()[1]) - } - fmt.Printf("%#v\n", c.Args().Present()) - fmt.Printf("%#v\n", c.Args().Tail()) - - set := flag.NewFlagSet("contrive", 0) - nc := cli.NewContext(c.App, set, c) - - fmt.Printf("%#v\n", nc.Args()) - fmt.Printf("%#v\n", nc.Bool("nope")) - fmt.Printf("%#v\n", nc.BoolT("nerp")) - fmt.Printf("%#v\n", nc.Duration("howlong")) - fmt.Printf("%#v\n", nc.Float64("hay")) - fmt.Printf("%#v\n", nc.Generic("bloop")) - fmt.Printf("%#v\n", nc.Int64("bonk")) - fmt.Printf("%#v\n", nc.Int64Slice("burnks")) - fmt.Printf("%#v\n", nc.Int("bips")) - fmt.Printf("%#v\n", nc.IntSlice("blups")) - fmt.Printf("%#v\n", nc.String("snurt")) - fmt.Printf("%#v\n", nc.StringSlice("snurkles")) - fmt.Printf("%#v\n", nc.Uint("flub")) - fmt.Printf("%#v\n", nc.Uint64("florb")) - fmt.Printf("%#v\n", nc.GlobalBool("global-nope")) - fmt.Printf("%#v\n", nc.GlobalBoolT("global-nerp")) - fmt.Printf("%#v\n", nc.GlobalDuration("global-howlong")) - fmt.Printf("%#v\n", nc.GlobalFloat64("global-hay")) - fmt.Printf("%#v\n", nc.GlobalGeneric("global-bloop")) - fmt.Printf("%#v\n", nc.GlobalInt("global-bips")) - fmt.Printf("%#v\n", nc.GlobalIntSlice("global-blups")) - fmt.Printf("%#v\n", nc.GlobalString("global-snurt")) - fmt.Printf("%#v\n", nc.GlobalStringSlice("global-snurkles")) - - fmt.Printf("%#v\n", nc.FlagNames()) - fmt.Printf("%#v\n", nc.GlobalFlagNames()) - fmt.Printf("%#v\n", nc.GlobalIsSet("wat")) - fmt.Printf("%#v\n", nc.GlobalSet("wat", "nope")) - fmt.Printf("%#v\n", nc.NArg()) - fmt.Printf("%#v\n", nc.NumFlags()) - fmt.Printf("%#v\n", nc.Parent()) - - nc.Set("wat", "also-nope") - - ec := cli.NewExitError("ohwell", 86) - fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) - fmt.Printf("made it!\n") - return nil - } - - if os.Getenv("HEXY") != "" { - app.Writer = &hexWriter{} - app.ErrWriter = &hexWriter{} - } - - app.Metadata = map[string]interface{}{ - "layers": "many", - "explicable": false, - "whatever-values": 19.99, - } - - - // ignore error so we don't exit non-zero and break gfmrun README example tests - _ = app.Run(os.Args) -} - -func wopAction(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, ":wave: over here, eh\n") - return nil -} -``` - -## Migrating to V2 - -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). diff --git a/docs/v1/index.md b/docs/v1/index.md new file mode 120000 index 0000000..9d0493a --- /dev/null +++ b/docs/v1/index.md @@ -0,0 +1 @@ +manual.md \ No newline at end of file diff --git a/docs/v1/manual.md b/docs/v1/manual.md new file mode 100644 index 0000000..6f568b7 --- /dev/null +++ b/docs/v1/manual.md @@ -0,0 +1,1455 @@ +# v1 guide + +## Getting Started + +One of the philosophies behind cli is that an API should be playful and full of +discovery. So a cli app can be as little as one line of code in `main()`. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + err := cli.NewApp().Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +This app will run and show help text, but is not very useful. Let's give an +action to execute and some help documentation: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "boom" + app.Usage = "make an explosive entrance" + app.Action = func(c *cli.Context) error { + fmt.Println("boom! I say!") + return nil + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Running this already gives you a ton of functionality, plus support for things +like subcommands and flags, which are covered below. + +## Examples + +Being a programmer can be a lonely job. Thankfully by the power of automation +that is not the case! Let's create a greeter app to fend off our demons of +loneliness! + +Start by creating a directory named `greet`, and within it, add a file, +`greet.go` with the following code in it: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "greet" + app.Usage = "fight the loneliness!" + app.Action = func(c *cli.Context) error { + fmt.Println("Hello friend!") + return nil + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Install our command to the `$GOPATH/bin` directory: + +``` +$ go install +``` + +Finally run our new command: + +``` +$ greet +Hello friend! +``` + +cli also generates neat help text: + +``` +$ greet help +NAME: + greet - fight the loneliness! + +USAGE: + greet [global options] command [command options] [arguments...] + +VERSION: + 0.0.0 + +COMMANDS: + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS + --version Shows version information +``` + +### Arguments + +You can lookup arguments by calling the `Args` function on `cli.Context`, e.g.: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Action = func(c *cli.Context) error { + fmt.Printf("Hello %q", c.Args().Get(0)) + return nil + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +### Flags + +Setting and querying flags is simple. + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + }, + } + + app.Action = func(c *cli.Context) error { + name := "Nefertiti" + if c.NArg() > 0 { + name = c.Args().Get(0) + } + if c.String("lang") == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +You can also set a destination variable for a flag, to which the content will be +scanned. + + +``` go +package main + +import ( + "log" + "os" + "fmt" + + "github.com/urfave/cli" +) + +func main() { + var language string + + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + Destination: &language, + }, + } + + app.Action = func(c *cli.Context) error { + name := "someone" + if c.NArg() > 0 { + name = c.Args()[0] + } + if language == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +See full list of flags at http://godoc.org/github.com/urfave/cli + +#### Placeholder Values + +Sometimes it's useful to specify a flag's value within the usage string itself. +Such placeholders are indicated with back quotes. + +For example this: + + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "config, c", + Usage: "Load configuration from `FILE`", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Will result in help output like: + +``` +--config FILE, -c FILE Load configuration from FILE +``` + +Note that only the first placeholder is used. Subsequent back-quoted words will +be left as-is. + +#### Alternate Names + +You can set alternate (or short) names for flags by providing a comma-delimited +list for the `Name`. e.g. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +That flag can then be set with `--lang spanish` or `-l spanish`. Note that +giving two different forms of the same flag in the same command invocation is an +error. + +#### Ordering + +Flags for the application and commands are shown in the order they are defined. +However, it's possible to sort them from outside this library by using `FlagsByName` +or `CommandsByName` with `sort`. + +For example this: + + +``` go +package main + +import ( + "log" + "os" + "sort" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "Language for the greeting", + }, + cli.StringFlag{ + Name: "config, c", + Usage: "Load configuration from `FILE`", + }, + } + + app.Commands = []cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + return nil + }, + }, + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(c *cli.Context) error { + return nil + }, + }, + } + + sort.Sort(cli.FlagsByName(app.Flags)) + sort.Sort(cli.CommandsByName(app.Commands)) + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Will result in help output like: + +``` +--config FILE, -c FILE Load configuration from FILE +--lang value, -l value Language for the greeting (default: "english") +``` + +#### Values from the Environment + +You can also have the default value set from the environment via `EnvVar`. e.g. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + EnvVar: "APP_LANG", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +The `EnvVar` may also be given as a comma-delimited "cascade", where the first +environment variable that resolves is used as the default. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +#### Values from files + +You can also have the default value set from file via `FilePath`. e.g. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "password, p", + Usage: "password for the mysql database", + FilePath: "/etc/mysql/password", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Note that default values set from file (e.g. `FilePath`) take precedence over +default values set from the environment (e.g. `EnvVar`). + +#### Values from alternate input sources (YAML, TOML, and others) + +There is a separate package altsrc that adds support for getting flag values +from other file input sources. + +Currently supported input source formats: +* YAML +* JSON +* TOML + +In order to get values for a flag from an alternate input source the following +code would be added to wrap an existing cli.Flag like below: + +``` go + altsrc.NewIntFlag(cli.IntFlag{Name: "test"}) +``` + +Initialization must also occur for these flags. Below is an example initializing +getting data from a yaml file below. + +``` go + command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) +``` + +The code above will use the "load" string as a flag name to get the file name of +a yaml file from the cli.Context. It will then use that file name to initialize +the yaml input source for any flags that are defined on that command. As a note +the "load" flag used would also have to be defined on the command flags in order +for this code snippet to work. + +Currently only YAML, JSON, and TOML files are supported but developers can add support +for other input sources by implementing the altsrc.InputSourceContext for their +given sources. + +Here is a more complete sample of a command using YAML support: + + +``` go +package notmain + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" + "github.com/urfave/cli/altsrc" +) + +func main() { + app := cli.NewApp() + + flags := []cli.Flag{ + altsrc.NewIntFlag(cli.IntFlag{Name: "test"}), + cli.StringFlag{Name: "load"}, + } + + app.Action = func(c *cli.Context) error { + fmt.Println("yaml ist rad") + return nil + } + + app.Before = altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")) + app.Flags = flags + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +#### Precedence + +The precedence for flag value sources is as follows (highest to lowest): + +0. Command line flag value from user +0. Environment variable (if specified) +0. Configuration file (if specified) +0. Default defined on the flag + +### Subcommands + +Subcommands can be defined for a more git-like command line app. + + +```go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Commands = []cli.Command{ + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(c *cli.Context) error { + fmt.Println("added task: ", c.Args().First()) + return nil + }, + }, + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + fmt.Println("completed task: ", c.Args().First()) + return nil + }, + }, + { + Name: "template", + Aliases: []string{"t"}, + Usage: "options for task templates", + Subcommands: []cli.Command{ + { + Name: "add", + Usage: "add a new template", + Action: func(c *cli.Context) error { + fmt.Println("new task template: ", c.Args().First()) + return nil + }, + }, + { + Name: "remove", + Usage: "remove an existing template", + Action: func(c *cli.Context) error { + fmt.Println("removed task template: ", c.Args().First()) + return nil + }, + }, + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +### Subcommands categories + +For additional organization in apps that have many subcommands, you can +associate a category for each command to group them together in the help +output. + +E.g. + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Commands = []cli.Command{ + { + Name: "noop", + }, + { + Name: "add", + Category: "Template actions", + }, + { + Name: "remove", + Category: "Template actions", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Will include: + +``` +COMMANDS: + noop + + Template actions: + add + remove +``` + +### Exit code + +Calling `App.Run` will not automatically call `os.Exit`, which means that by +default the exit code will "fall through" to being `0`. An explicit exit code +may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a +`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.: + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Flags = []cli.Flag{ + cli.BoolFlag{ + Name: "ginger-crouton", + Usage: "Add ginger croutons to the soup", + }, + } + app.Action = func(ctx *cli.Context) error { + if !ctx.Bool("ginger-crouton") { + return cli.NewExitError("Ginger croutons are not in the soup", 86) + } + return nil + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +### Combining short options + +Traditional use of options using their shortnames look like this: + +``` +$ cmd -s -o -m "Some message" +``` + +Suppose you want users to be able to combine options with their shortnames. This +can be done using the `UseShortOptionHandling` bool in your app configuration, +or for individual commands by attaching it to the command configuration. For +example: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.UseShortOptionHandling = true + app.Commands = []cli.Command{ + { + Name: "short", + Usage: "complete a task on the list", + Flags: []cli.Flag{ + cli.BoolFlag{Name: "serve, s"}, + cli.BoolFlag{Name: "option, o"}, + cli.StringFlag{Name: "message, m"}, + }, + Action: func(c *cli.Context) error { + fmt.Println("serve:", c.Bool("serve")) + fmt.Println("option:", c.Bool("option")) + fmt.Println("message:", c.String("message")) + return nil + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +If your program has any number of bool flags such as `serve` and `option`, and +optionally one non-bool flag `message`, with the short options of `-s`, `-o`, +and `-m` respectively, setting `UseShortOptionHandling` will also support the +following syntax: + +``` +$ cmd -som "Some message" +``` + +If you enable `UseShortOptionHandling`, then you must not use any flags that +have a single leading `-` or this will result in failures. For example, +`-option` can no longer be used. Flags with two leading dashes (such as +`--options`) are still valid. + +### Bash Completion + +You can enable completion commands by setting the `EnableBashCompletion` +flag on the `App` object. By default, this setting will only auto-complete to +show an app's subcommands, but you can write your own completion methods for +the App or its subcommands. + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} + + app := cli.NewApp() + app.EnableBashCompletion = true + app.Commands = []cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + fmt.Println("completed task: ", c.Args().First()) + return nil + }, + BashComplete: func(c *cli.Context) { + // This will complete if no args are passed + if c.NArg() > 0 { + return + } + for _, t := range tasks { + fmt.Println(t) + } + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +#### Enabling + +Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while +setting the `PROG` variable to the name of your program: + +`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` + +#### Distribution + +Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename +it to the name of the program you wish to add autocomplete support for (or +automatically install it there if you are distributing a package). Don't forget +to source the file to make it active in the current shell. + +``` +sudo cp src/bash_autocomplete /etc/bash_completion.d/ +source /etc/bash_completion.d/ +``` + +Alternatively, you can just document that users should source the generic +`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set +to the name of their program (as above). + +#### Customization + +The default bash completion flag (`--generate-bash-completion`) is defined as +`cli.BashCompletionFlag`, and may be redefined if desired, e.g.: + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + cli.BashCompletionFlag = cli.BoolFlag{ + Name: "compgen", + Hidden: true, + } + + app := cli.NewApp() + app.EnableBashCompletion = true + app.Commands = []cli.Command{ + { + Name: "wat", + }, + } + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +### Generated Help Text + +The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked +by the cli internals in order to print generated help text for the app, command, +or subcommand, and break execution. + +#### Customization + +All of the help text generation may be customized, and at multiple levels. The +templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and +`SubcommandHelpTemplate` which may be reassigned or augmented, and full override +is possible by assigning a compatible func to the `cli.HelpPrinter` variable, +e.g.: + + +``` go +package main + +import ( + "fmt" + "log" + "io" + "os" + + "github.com/urfave/cli" +) + +func main() { + // EXAMPLE: Append to an existing template + cli.AppHelpTemplate = fmt.Sprintf(`%s + +WEBSITE: http://awesometown.example.com + +SUPPORT: support@awesometown.example.com + +`, cli.AppHelpTemplate) + + // EXAMPLE: Override a template + cli.AppHelpTemplate = `NAME: + {{.Name}} - {{.Usage}} +USAGE: + {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} + {{if len .Authors}} +AUTHOR: + {{range .Authors}}{{ . }}{{end}} + {{end}}{{if .Commands}} +COMMANDS: +{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} +GLOBAL OPTIONS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}}{{if .Copyright }} +COPYRIGHT: + {{.Copyright}} + {{end}}{{if .Version}} +VERSION: + {{.Version}} + {{end}} +` + + // EXAMPLE: Replace the `HelpPrinter` func + cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { + fmt.Println("Ha HA. I pwnd the help!!1") + } + + err := cli.NewApp().Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +The default flag may be customized to something other than `-h/--help` by +setting `cli.HelpFlag`, e.g.: + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + cli.HelpFlag = cli.BoolFlag{ + Name: "halp, haaaaalp", + Usage: "HALP", + EnvVar: "SHOW_HALP,HALPPLZ", + } + + err := cli.NewApp().Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +### Version Flag + +The default version flag (`-v/--version`) is defined as `cli.VersionFlag`, which +is checked by the cli internals in order to print the `App.Version` via +`cli.VersionPrinter` and break execution. + +#### Customization + +The default flag may be customized to something other than `-v/--version` by +setting `cli.VersionFlag`, e.g.: + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli" +) + +func main() { + cli.VersionFlag = cli.BoolFlag{ + Name: "print-version, V", + Usage: "print only the version", + } + + app := cli.NewApp() + app.Name = "partay" + app.Version = "19.99.0" + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Alternatively, the version printer at `cli.VersionPrinter` may be overridden, e.g.: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli" +) + +var ( + Revision = "fafafaf" +) + +func main() { + cli.VersionPrinter = func(c *cli.Context) { + fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision) + } + + app := cli.NewApp() + app.Name = "partay" + app.Version = "19.99.0" + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +#### Full API Example + +**Notice**: This is a contrived (functioning) example meant strictly for API +demonstration purposes. Use of one's imagination is encouraged. + + +``` go +package main + +import ( + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "time" + + "github.com/urfave/cli" +) + +func init() { + cli.AppHelpTemplate += "\nCUSTOMIZED: you bet ur muffins\n" + cli.CommandHelpTemplate += "\nYMMV\n" + cli.SubcommandHelpTemplate += "\nor something\n" + + cli.HelpFlag = cli.BoolFlag{Name: "halp"} + cli.BashCompletionFlag = cli.BoolFlag{Name: "compgen", Hidden: true} + cli.VersionFlag = cli.BoolFlag{Name: "print-version, V"} + + cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { + fmt.Fprintf(w, "best of luck to you\n") + } + cli.VersionPrinter = func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "version=%s\n", c.App.Version) + } + cli.OsExiter = func(c int) { + fmt.Fprintf(cli.ErrWriter, "refusing to exit %d\n", c) + } + cli.ErrWriter = ioutil.Discard + cli.FlagStringer = func(fl cli.Flag) string { + return fmt.Sprintf("\t\t%s", fl.GetName()) + } +} + +type hexWriter struct{} + +func (w *hexWriter) Write(p []byte) (int, error) { + for _, b := range p { + fmt.Printf("%x", b) + } + fmt.Printf("\n") + + return len(p), nil +} + +type genericType struct{ + s string +} + +func (g *genericType) Set(value string) error { + g.s = value + return nil +} + +func (g *genericType) String() string { + return g.s +} + +func main() { + app := cli.NewApp() + app.Name = "kənˈtrīv" + app.Version = "19.99.0" + app.Compiled = time.Now() + app.Authors = []cli.Author{ + cli.Author{ + Name: "Example Human", + Email: "human@example.com", + }, + } + app.Copyright = "(c) 1999 Serious Enterprise" + app.HelpName = "contrive" + app.Usage = "demonstrate available API" + app.UsageText = "contrive - demonstrating the available API" + app.ArgsUsage = "[args and such]" + app.Commands = []cli.Command{ + cli.Command{ + Name: "doo", + Aliases: []string{"do"}, + Category: "motion", + Usage: "do the doo", + UsageText: "doo - does the dooing", + Description: "no really, there is a lot of dooing to be done", + ArgsUsage: "[arrgh]", + Flags: []cli.Flag{ + cli.BoolFlag{Name: "forever, forevvarr"}, + }, + Subcommands: cli.Commands{ + cli.Command{ + Name: "wop", + Action: wopAction, + }, + }, + SkipFlagParsing: false, + HideHelp: false, + Hidden: false, + HelpName: "doo!", + BashComplete: func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "--better\n") + }, + Before: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "brace for impact\n") + return nil + }, + After: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "did we lose anyone?\n") + return nil + }, + Action: func(c *cli.Context) error { + c.Command.FullName() + c.Command.HasName("wop") + c.Command.Names() + c.Command.VisibleFlags() + fmt.Fprintf(c.App.Writer, "dodododododoodododddooooododododooo\n") + if c.Bool("forever") { + c.Command.Run(c) + } + return nil + }, + OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { + fmt.Fprintf(c.App.Writer, "for shame\n") + return err + }, + }, + } + app.Flags = []cli.Flag{ + cli.BoolFlag{Name: "fancy"}, + cli.BoolTFlag{Name: "fancier"}, + cli.DurationFlag{Name: "howlong, H", Value: time.Second * 3}, + cli.Float64Flag{Name: "howmuch"}, + cli.GenericFlag{Name: "wat", Value: &genericType{}}, + cli.Int64Flag{Name: "longdistance"}, + cli.Int64SliceFlag{Name: "intervals"}, + cli.IntFlag{Name: "distance"}, + cli.IntSliceFlag{Name: "times"}, + cli.StringFlag{Name: "dance-move, d"}, + cli.StringSliceFlag{Name: "names, N"}, + cli.UintFlag{Name: "age"}, + cli.Uint64Flag{Name: "bigage"}, + } + app.EnableBashCompletion = true + app.UseShortOptionHandling = true + app.HideHelp = false + app.HideVersion = false + app.BashComplete = func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") + } + app.Before = func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "HEEEERE GOES\n") + return nil + } + app.After = func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "Phew!\n") + return nil + } + app.CommandNotFound = func(c *cli.Context, command string) { + fmt.Fprintf(c.App.Writer, "Thar be no %q here.\n", command) + } + app.OnUsageError = func(c *cli.Context, err error, isSubcommand bool) error { + if isSubcommand { + return err + } + + fmt.Fprintf(c.App.Writer, "WRONG: %#v\n", err) + return nil + } + app.Action = func(c *cli.Context) error { + cli.DefaultAppComplete(c) + cli.HandleExitCoder(errors.New("not an exit coder, though")) + cli.ShowAppHelp(c) + cli.ShowCommandCompletions(c, "nope") + cli.ShowCommandHelp(c, "also-nope") + cli.ShowCompletions(c) + cli.ShowSubcommandHelp(c) + cli.ShowVersion(c) + + categories := c.App.Categories() + categories.AddCommand("sounds", cli.Command{ + Name: "bloop", + }) + + for _, category := range c.App.Categories() { + fmt.Fprintf(c.App.Writer, "%s\n", category.Name) + fmt.Fprintf(c.App.Writer, "%#v\n", category.Commands) + fmt.Fprintf(c.App.Writer, "%#v\n", category.VisibleCommands()) + } + + fmt.Printf("%#v\n", c.App.Command("doo")) + if c.Bool("infinite") { + c.App.Run([]string{"app", "doo", "wop"}) + } + + if c.Bool("forevar") { + c.App.RunAsSubcommand(c) + } + c.App.Setup() + fmt.Printf("%#v\n", c.App.VisibleCategories()) + fmt.Printf("%#v\n", c.App.VisibleCommands()) + fmt.Printf("%#v\n", c.App.VisibleFlags()) + + fmt.Printf("%#v\n", c.Args().First()) + if len(c.Args()) > 0 { + fmt.Printf("%#v\n", c.Args()[1]) + } + fmt.Printf("%#v\n", c.Args().Present()) + fmt.Printf("%#v\n", c.Args().Tail()) + + set := flag.NewFlagSet("contrive", 0) + nc := cli.NewContext(c.App, set, c) + + fmt.Printf("%#v\n", nc.Args()) + fmt.Printf("%#v\n", nc.Bool("nope")) + fmt.Printf("%#v\n", nc.BoolT("nerp")) + fmt.Printf("%#v\n", nc.Duration("howlong")) + fmt.Printf("%#v\n", nc.Float64("hay")) + fmt.Printf("%#v\n", nc.Generic("bloop")) + fmt.Printf("%#v\n", nc.Int64("bonk")) + fmt.Printf("%#v\n", nc.Int64Slice("burnks")) + fmt.Printf("%#v\n", nc.Int("bips")) + fmt.Printf("%#v\n", nc.IntSlice("blups")) + fmt.Printf("%#v\n", nc.String("snurt")) + fmt.Printf("%#v\n", nc.StringSlice("snurkles")) + fmt.Printf("%#v\n", nc.Uint("flub")) + fmt.Printf("%#v\n", nc.Uint64("florb")) + fmt.Printf("%#v\n", nc.GlobalBool("global-nope")) + fmt.Printf("%#v\n", nc.GlobalBoolT("global-nerp")) + fmt.Printf("%#v\n", nc.GlobalDuration("global-howlong")) + fmt.Printf("%#v\n", nc.GlobalFloat64("global-hay")) + fmt.Printf("%#v\n", nc.GlobalGeneric("global-bloop")) + fmt.Printf("%#v\n", nc.GlobalInt("global-bips")) + fmt.Printf("%#v\n", nc.GlobalIntSlice("global-blups")) + fmt.Printf("%#v\n", nc.GlobalString("global-snurt")) + fmt.Printf("%#v\n", nc.GlobalStringSlice("global-snurkles")) + + fmt.Printf("%#v\n", nc.FlagNames()) + fmt.Printf("%#v\n", nc.GlobalFlagNames()) + fmt.Printf("%#v\n", nc.GlobalIsSet("wat")) + fmt.Printf("%#v\n", nc.GlobalSet("wat", "nope")) + fmt.Printf("%#v\n", nc.NArg()) + fmt.Printf("%#v\n", nc.NumFlags()) + fmt.Printf("%#v\n", nc.Parent()) + + nc.Set("wat", "also-nope") + + ec := cli.NewExitError("ohwell", 86) + fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) + fmt.Printf("made it!\n") + return nil + } + + if os.Getenv("HEXY") != "" { + app.Writer = &hexWriter{} + app.ErrWriter = &hexWriter{} + } + + app.Metadata = map[string]interface{}{ + "layers": "many", + "explicable": false, + "whatever-values": 19.99, + } + + + // ignore error so we don't exit non-zero and break gfmrun README example tests + _ = app.Run(os.Args) +} + +func wopAction(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, ":wave: over here, eh\n") + return nil +} +``` + +## Migrating to V2 + +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). diff --git a/docs/v2/index.md b/docs/v2/index.md deleted file mode 100644 index 9c26100..0000000 --- a/docs/v2/index.md +++ /dev/null @@ -1,1677 +0,0 @@ -# v2 guide - -## Getting Started - -One of the philosophies behind cli is that an API should be playful and full of -discovery. So a cli app can be as little as one line of code in `main()`. - - -``` go -package main - -import ( - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - (&cli.App{}).Run(os.Args) -} -``` - -This app will run and show help text, but is not very useful. Let's give an -action to execute and some help documentation: - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Name: "boom", - Usage: "make an explosive entrance", - Action: func(c *cli.Context) error { - fmt.Println("boom! I say!") - return nil - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Running this already gives you a ton of functionality, plus support for things -like subcommands and flags, which are covered below. - -## Examples - -Being a programmer can be a lonely job. Thankfully by the power of automation -that is not the case! Let's create a greeter app to fend off our demons of -loneliness! - -Start by creating a directory named `greet`, and within it, add a file, -`greet.go` with the following code in it: - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Name: "greet", - Usage: "fight the loneliness!", - Action: func(c *cli.Context) error { - fmt.Println("Hello friend!") - return nil - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Install our command to the `$GOPATH/bin` directory: - -``` -$ go install -``` - -Finally run our new command: - -``` -$ greet -Hello friend! -``` - -cli also generates neat help text: - -``` -$ greet help -NAME: - greet - fight the loneliness! - -USAGE: - greet [global options] command [command options] [arguments...] - -COMMANDS: - help, h Shows a list of commands or help for one command - -GLOBAL OPTIONS - --help, -h show help (default: false) -``` - -### Arguments - -You can lookup arguments by calling the `Args` function on `cli.Context`, e.g.: - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Action: func(c *cli.Context) error { - fmt.Printf("Hello %q", c.Args().Get(0)) - return nil - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -### Flags - -Setting and querying flags is simple. - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag { - &cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", - }, - }, - Action: func(c *cli.Context) error { - name := "Nefertiti" - if c.NArg() > 0 { - name = c.Args().Get(0) - } - if c.String("lang") == "spanish" { - fmt.Println("Hola", name) - } else { - fmt.Println("Hello", name) - } - return nil - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -You can also set a destination variable for a flag, to which the content will be -scanned. - - -``` go -package main - -import ( - "log" - "os" - "fmt" - - "github.com/urfave/cli/v2" -) - -func main() { - var language string - - app := &cli.App{ - Flags: []cli.Flag { - &cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", - Destination: &language, - }, - }, - Action: func(c *cli.Context) error { - name := "someone" - if c.NArg() > 0 { - name = c.Args().Get(0) - } - if language == "spanish" { - fmt.Println("Hola", name) - } else { - fmt.Println("Hello", name) - } - return nil - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -See full list of flags at https://pkg.go.dev/github.com/urfave/cli/v2 - -#### Placeholder Values - -Sometimes it's useful to specify a flag's value within the usage string itself. -Such placeholders are indicated with back quotes. - -For example this: - - -```go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "config", - Aliases: []string{"c"}, - Usage: "Load configuration from `FILE`", - }, - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Will result in help output like: - -``` ---config FILE, -c FILE Load configuration from FILE -``` - -Note that only the first placeholder is used. Subsequent back-quoted words will -be left as-is. - -#### Alternate Names - -You can set alternate (or short) names for flags by providing a comma-delimited -list for the `Name`. e.g. - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag { - &cli.StringFlag{ - Name: "lang", - Aliases: []string{"l"}, - Value: "english", - Usage: "language for the greeting", - }, - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -That flag can then be set with `--lang spanish` or `-l spanish`. Note that -giving two different forms of the same flag in the same command invocation is an -error. - -#### Ordering - -Flags for the application and commands are shown in the order they are defined. -However, it's possible to sort them from outside this library by using `FlagsByName` -or `CommandsByName` with `sort`. - -For example this: - - -``` go -package main - -import ( - "log" - "os" - "sort" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "lang", - Aliases: []string{"l"}, - Value: "english", - Usage: "Language for the greeting", - }, - &cli.StringFlag{ - Name: "config", - Aliases: []string{"c"}, - Usage: "Load configuration from `FILE`", - }, - }, - Commands: []*cli.Command{ - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - return nil - }, - }, - { - Name: "add", - Aliases: []string{"a"}, - Usage: "add a task to the list", - Action: func(c *cli.Context) error { - return nil - }, - }, - }, - } - - sort.Sort(cli.FlagsByName(app.Flags)) - sort.Sort(cli.CommandsByName(app.Commands)) - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Will result in help output like: - -``` ---config FILE, -c FILE Load configuration from FILE ---lang value, -l value Language for the greeting (default: "english") -``` - -#### Values from the Environment - -You can also have the default value set from the environment via `EnvVars`. e.g. - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag { - &cli.StringFlag{ - Name: "lang", - Aliases: []string{"l"}, - Value: "english", - Usage: "language for the greeting", - EnvVars: []string{"APP_LANG"}, - }, - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -If `EnvVars` contains more than one string, the first environment variable that -resolves is used. - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "lang", - Aliases: []string{"l"}, - Value: "english", - Usage: "language for the greeting", - EnvVars: []string{"LEGACY_COMPAT_LANG", "APP_LANG", "LANG"}, - }, - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -#### Values from files - -You can also have the default value set from file via `FilePath`. e.g. - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - &cli.StringFlag{ - Name: "password", - Aliases: []string{"p"}, - Usage: "password for the mysql database", - FilePath: "/etc/mysql/password", - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Note that default values set from file (e.g. `FilePath`) take precedence over -default values set from the environment (e.g. `EnvVar`). - -#### Values from alternate input sources (YAML, TOML, and others) - -There is a separate package altsrc that adds support for getting flag values -from other file input sources. - -Currently supported input source formats: -* YAML -* JSON -* TOML - -In order to get values for a flag from an alternate input source the following -code would be added to wrap an existing cli.Flag like below: - -``` go - altsrc.NewIntFlag(&cli.IntFlag{Name: "test"}) -``` - -Initialization must also occur for these flags. Below is an example initializing -getting data from a yaml file below. - -``` go - command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) -``` - -The code above will use the "load" string as a flag name to get the file name of -a yaml file from the cli.Context. It will then use that file name to initialize -the yaml input source for any flags that are defined on that command. As a note -the "load" flag used would also have to be defined on the command flags in order -for this code snippet to work. - -Currently only YAML, JSON, and TOML files are supported but developers can add support -for other input sources by implementing the altsrc.InputSourceContext for their -given sources. - -Here is a more complete sample of a command using YAML support: - - -``` go -package main - -import ( - "fmt" - "os" - - "github.com/urfave/cli/v2" - "github.com/urfave/cli/v2/altsrc" -) - -func main() { - flags := []cli.Flag{ - altsrc.NewIntFlag(&cli.IntFlag{Name: "test"}), - &cli.StringFlag{Name: "load"}, - } - - app := &cli.App{ - Action: func(c *cli.Context) error { - fmt.Println("--test value.*default: 0") - return nil - }, - Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")), - Flags: flags, - } - - app.Run(os.Args) -} -``` - -#### Required Flags - -You can make a flag required by setting the `Required` field to `true`. If a user -does not provide a required flag, they will be shown an error message. - -Take for example this app that requires the `lang` flag: - - -```go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -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. - -For example this: - - -```go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.IntFlag{ - Name: "port", - Usage: "Use a randomized port", - Value: 0, - DefaultText: "random", - }, - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Will result in help output like: - -``` ---port value Use a randomized port (default: random) -``` - -#### Precedence - -The precedence for flag value sources is as follows (highest to lowest): - -0. Command line flag value from user -0. Environment variable (if specified) -0. Configuration file (if specified) -0. Default defined on the flag - -### Subcommands - -Subcommands can be defined for a more git-like command line app. - - -```go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Commands: []*cli.Command{ - { - Name: "add", - Aliases: []string{"a"}, - Usage: "add a task to the list", - Action: func(c *cli.Context) error { - fmt.Println("added task: ", c.Args().First()) - return nil - }, - }, - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - fmt.Println("completed task: ", c.Args().First()) - return nil - }, - }, - { - Name: "template", - Aliases: []string{"t"}, - Usage: "options for task templates", - Subcommands: []*cli.Command{ - { - Name: "add", - Usage: "add a new template", - Action: func(c *cli.Context) error { - fmt.Println("new task template: ", c.Args().First()) - return nil - }, - }, - { - Name: "remove", - Usage: "remove an existing template", - Action: func(c *cli.Context) error { - fmt.Println("removed task template: ", c.Args().First()) - return nil - }, - }, - }, - }, - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -### Subcommands categories - -For additional organization in apps that have many subcommands, you can -associate a category for each command to group them together in the help -output. - -E.g. - -```go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Commands: []*cli.Command{ - { - Name: "noop", - }, - { - Name: "add", - Category: "template", - }, - { - Name: "remove", - Category: "template", - }, - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -Will include: - -``` -COMMANDS: - noop - - Template actions: - add - remove -``` - -### Exit code - -Calling `App.Run` will not automatically call `os.Exit`, which means that by -default the exit code will "fall through" to being `0`. An explicit exit code -may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a -`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.: - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "ginger-crouton", - Usage: "is it in the soup?", - }, - }, - Action: func(ctx *cli.Context) error { - if !ctx.Bool("ginger-crouton") { - return cli.Exit("Ginger croutons are not in the soup", 86) - } - return nil - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -### Combining short options - -Traditional use of options using their shortnames look like this: - -``` -$ cmd -s -o -m "Some message" -``` - -Suppose you want users to be able to combine options with their shortnames. This -can be done using the `UseShortOptionHandling` bool in your app configuration, -or for individual commands by attaching it to the command configuration. For -example: - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{} - app.UseShortOptionHandling = true - app.Commands = []*cli.Command{ - { - Name: "short", - Usage: "complete a task on the list", - Flags: []cli.Flag{ - &cli.BoolFlag{Name: "serve", Aliases: []string{"s"}}, - &cli.BoolFlag{Name: "option", Aliases: []string{"o"}}, - &cli.StringFlag{Name: "message", Aliases: []string{"m"}}, - }, - Action: func(c *cli.Context) error { - fmt.Println("serve:", c.Bool("serve")) - fmt.Println("option:", c.Bool("option")) - fmt.Println("message:", c.String("message")) - return nil - }, - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -If your program has any number of bool flags such as `serve` and `option`, and -optionally one non-bool flag `message`, with the short options of `-s`, `-o`, -and `-m` respectively, setting `UseShortOptionHandling` will also support the -following syntax: - -``` -$ cmd -som "Some message" -``` - -If you enable `UseShortOptionHandling`, then you must not use any flags that -have a single leading `-` or this will result in failures. For example, -`-option` can no longer be used. Flags with two leading dashes (such as -`--options`) are still valid. - -### Bash Completion - -You can enable completion commands by setting the `EnableBashCompletion` -flag on the `App` object to `true`. By default, this setting will allow auto-completion -for an app's subcommands, but you can write your own completion methods for -the App or its subcommands as well. - -#### Default auto-completion - -```go -package main -import ( - "fmt" - "log" - "os" - "github.com/urfave/cli/v2" -) -func main() { - app := cli.NewApp() - app.EnableBashCompletion = true - app.Commands = []*cli.Command{ - { - Name: "add", - Aliases: []string{"a"}, - Usage: "add a task to the list", - Action: func(c *cli.Context) error { - fmt.Println("added task: ", c.Args().First()) - return nil - }, - }, - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - fmt.Println("completed task: ", c.Args().First()) - return nil - }, - }, - { - Name: "template", - Aliases: []string{"t"}, - Usage: "options for task templates", - Subcommands: []*cli.Command{ - { - Name: "add", - Usage: "add a new template", - Action: func(c *cli.Context) error { - fmt.Println("new task template: ", c.Args().First()) - return nil - }, - }, - { - Name: "remove", - Usage: "remove an existing template", - Action: func(c *cli.Context) error { - fmt.Println("removed task template: ", c.Args().First()) - return nil - }, - }, - }, - }, - } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` -![](/docs/v2/images/default-bash-autocomplete.gif) - -#### Custom auto-completion - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} - - app := &cli.App{ - EnableBashCompletion: true, - Commands: []*cli.Command{ - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - fmt.Println("completed task: ", c.Args().First()) - return nil - }, - BashComplete: func(c *cli.Context) { - // This will complete if no args are passed - if c.NArg() > 0 { - return - } - for _, t := range tasks { - fmt.Println(t) - } - }, - }, - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` -![](/docs/v2/images/custom-bash-autocomplete.gif) - -#### Enabling - -To enable auto-completion for the current shell session, a bash script, -`autocomplete/bash_autocomplete` is included in this repo. - -To use `autocomplete/bash_autocomplete` set an environment variable named `PROG` to -the name of your program and then `source` the `autocomplete/bash_autocomplete` file. - -For example, if your cli program is called `myprogram`: - -`PROG=myprogram source path/to/cli/autocomplete/bash_autocomplete` - -Auto-completion is now enabled for the current shell, but will not persist into a new shell. - -#### Distribution and Persistent Autocompletion - -Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename -it to the name of the program you wish to add autocomplete support for (or -automatically install it there if you are distributing a package). Don't forget -to source the file or restart your shell to activate the auto-completion. - -``` -sudo cp path/to/autocomplete/bash_autocomplete /etc/bash_completion.d/ -source /etc/bash_completion.d/ -``` - -Alternatively, you can just document that users should `source` the generic -`autocomplete/bash_autocomplete` and set `$PROG` within their bash configuration -file, adding these lines: - -``` -PROG= -source path/to/cli/autocomplete/bash_autocomplete -``` -Keep in mind that if they are enabling auto-completion for more than one program, -they will need to set `PROG` and source `autocomplete/bash_autocomplete` for each -program, like so: - -``` -PROG= -source path/to/cli/autocomplete/bash_autocomplete -PROG= -source path/to/cli/autocomplete/bash_autocomplete -``` - -#### Customization - -The default shell completion flag (`--generate-bash-completion`) is defined as -`cli.EnableBashCompletion`, and may be redefined if desired, e.g.: - - -``` go -package main - -import ( - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - EnableBashCompletion: true, - Commands: []*cli.Command{ - { - Name: "wat", - }, - }, - } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -#### ZSH Support -Auto-completion for ZSH is also supported using the `autocomplete/zsh_autocomplete` -file included in this repo. Two environment variables are used, `PROG` and `_CLI_ZSH_AUTOCOMPLETE_HACK`. -Set `PROG` to the program name as before, set `_CLI_ZSH_AUTOCOMPLETE_HACK` to `1`, and -then `source path/to/autocomplete/zsh_autocomplete`. Adding the following lines to your ZSH -configuration file (usually `.zshrc`) will allow the auto-completion to persist across new shells: - -``` -PROG= -_CLI_ZSH_AUTOCOMPLETE_HACK=1 -source path/to/autocomplete/zsh_autocomplete -``` -#### ZSH default auto-complete example -![](/docs/v2/images/default-zsh-autocomplete.gif) -#### ZSH custom auto-complete example -![](/docs/v2/images/custom-zsh-autocomplete.gif) - -#### PowerShell Support -Auto-completion for PowerShell is also supported using the `autocomplete/powershell_autocomplete.ps1` -file included in this repo. - -Rename the script to `.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/.ps1` - -To persist across new shells, open the PowerShell profile (with `code $profile` or `notepad $profile`) -and add the line: -``` -& path/to/autocomplete/.ps1 -``` - - -### Generated Help Text - -The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked -by the cli internals in order to print generated help text for the app, command, -or subcommand, and break execution. - -#### Customization - -All of the help text generation may be customized, and at multiple levels. The -templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and -`SubcommandHelpTemplate` which may be reassigned or augmented, and full override -is possible by assigning a compatible func to the `cli.HelpPrinter` variable, -e.g.: - - -``` go -package main - -import ( - "fmt" - "io" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - // EXAMPLE: Append to an existing template - cli.AppHelpTemplate = fmt.Sprintf(`%s - -WEBSITE: http://awesometown.example.com - -SUPPORT: support@awesometown.example.com - -`, cli.AppHelpTemplate) - - // EXAMPLE: Override a template - cli.AppHelpTemplate = `NAME: - {{.Name}} - {{.Usage}} -USAGE: - {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} - {{if len .Authors}} -AUTHOR: - {{range .Authors}}{{ . }}{{end}} - {{end}}{{if .Commands}} -COMMANDS: -{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} -GLOBAL OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}}{{if .Copyright }} -COPYRIGHT: - {{.Copyright}} - {{end}}{{if .Version}} -VERSION: - {{.Version}} - {{end}} -` - - // EXAMPLE: Replace the `HelpPrinter` func - cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { - fmt.Println("Ha HA. I pwnd the help!!1") - } - - (&cli.App{}).Run(os.Args) -} -``` - -The default flag may be customized to something other than `-h/--help` by -setting `cli.HelpFlag`, e.g.: - - -``` go -package main - -import ( - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - cli.HelpFlag = &cli.BoolFlag{ - Name: "haaaaalp", - Aliases: []string{"halp"}, - Usage: "HALP", - EnvVars: []string{"SHOW_HALP", "HALPPLZ"}, - } - - (&cli.App{}).Run(os.Args) -} -``` - -### Version Flag - -The default version flag (`-v/--version`) is defined as `cli.VersionFlag`, which -is checked by the cli internals in order to print the `App.Version` via -`cli.VersionPrinter` and break execution. - -#### Customization - -The default flag may be customized to something other than `-v/--version` by -setting `cli.VersionFlag`, e.g.: - - -``` go -package main - -import ( - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - cli.VersionFlag = &cli.BoolFlag{ - Name: "print-version", - Aliases: []string{"V"}, - Usage: "print only the version", - } - - app := &cli.App{ - Name: "partay", - Version: "v19.99.0", - } - app.Run(os.Args) -} -``` - -Alternatively, the version printer at `cli.VersionPrinter` may be overridden, e.g.: - - -``` go -package main - -import ( - "fmt" - "os" - - "github.com/urfave/cli/v2" -) - -var ( - Revision = "fafafaf" -) - -func main() { - cli.VersionPrinter = func(c *cli.Context) { - fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision) - } - - app := &cli.App{ - Name: "partay", - Version: "v19.99.0", - } - app.Run(os.Args) -} -``` - -### Timestamp Flag - -Using the timestamp flag is simple. Please refer to [`time.Parse`](https://golang.org/pkg/time/#example_Parse) to get possible formats. - - -``` go -package main - -import ( - "fmt" - "log" - "os" - - "github.com/urfave/cli/v2" -) - -func main() { - app := &cli.App{ - Flags: []cli.Flag { - &cli.TimestampFlag{Name: "meeting", Layout: "2006-01-02T15:04:05"}, - }, - Action: func(c *cli.Context) error { - fmt.Printf("%s", c.Timestamp("meeting").String()) - return nil - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} -``` - -In this example the flag could be used like this : - -`myapp --meeting 2019-08-12T15:04:05` - -Side note: quotes may be necessary around the date depending on your layout (if you have spaces for instance) - -### Full API Example - -**Notice**: This is a contrived (functioning) example meant strictly for API -demonstration purposes. Use of one's imagination is encouraged. - - -``` go -package main - -import ( - "errors" - "flag" - "fmt" - "io" - "io/ioutil" - "os" - "time" - - "github.com/urfave/cli/v2" -) - -func init() { - cli.AppHelpTemplate += "\nCUSTOMIZED: you bet ur muffins\n" - cli.CommandHelpTemplate += "\nYMMV\n" - cli.SubcommandHelpTemplate += "\nor something\n" - - cli.HelpFlag = &cli.BoolFlag{Name: "halp"} - cli.VersionFlag = &cli.BoolFlag{Name: "print-version", Aliases: []string{"V"}} - - cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { - fmt.Fprintf(w, "best of luck to you\n") - } - cli.VersionPrinter = func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "version=%s\n", c.App.Version) - } - cli.OsExiter = func(c int) { - fmt.Fprintf(cli.ErrWriter, "refusing to exit %d\n", c) - } - cli.ErrWriter = ioutil.Discard - cli.FlagStringer = func(fl cli.Flag) string { - return fmt.Sprintf("\t\t%s", fl.Names()[0]) - } -} - -type hexWriter struct{} - -func (w *hexWriter) Write(p []byte) (int, error) { - for _, b := range p { - fmt.Printf("%x", b) - } - fmt.Printf("\n") - - return len(p), nil -} - -type genericType struct { - s string -} - -func (g *genericType) Set(value string) error { - g.s = value - return nil -} - -func (g *genericType) String() string { - return g.s -} - -func main() { - app := &cli.App{ - Name: "kənˈtrīv", - Version: "v19.99.0", - Compiled: time.Now(), - Authors: []*cli.Author{ - &cli.Author{ - Name: "Example Human", - Email: "human@example.com", - }, - }, - Copyright: "(c) 1999 Serious Enterprise", - HelpName: "contrive", - Usage: "demonstrate available API", - UsageText: "contrive - demonstrating the available API", - ArgsUsage: "[args and such]", - Commands: []*cli.Command{ - &cli.Command{ - Name: "doo", - Aliases: []string{"do"}, - Category: "motion", - Usage: "do the doo", - UsageText: "doo - does the dooing", - Description: "no really, there is a lot of dooing to be done", - ArgsUsage: "[arrgh]", - Flags: []cli.Flag{ - &cli.BoolFlag{Name: "forever", Aliases: []string{"forevvarr"}}, - }, - Subcommands: []*cli.Command{ - &cli.Command{ - Name: "wop", - Action: wopAction, - }, - }, - SkipFlagParsing: false, - HideHelp: false, - Hidden: false, - HelpName: "doo!", - BashComplete: func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "--better\n") - }, - Before: func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "brace for impact\n") - return nil - }, - After: func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "did we lose anyone?\n") - return nil - }, - Action: func(c *cli.Context) error { - c.Command.FullName() - c.Command.HasName("wop") - c.Command.Names() - c.Command.VisibleFlags() - fmt.Fprintf(c.App.Writer, "dodododododoodododddooooododododooo\n") - if c.Bool("forever") { - c.Command.Run(c) - } - return nil - }, - OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { - fmt.Fprintf(c.App.Writer, "for shame\n") - return err - }, - }, - }, - Flags: []cli.Flag{ - &cli.BoolFlag{Name: "fancy"}, - &cli.BoolFlag{Value: true, Name: "fancier"}, - &cli.DurationFlag{Name: "howlong", Aliases: []string{"H"}, Value: time.Second * 3}, - &cli.Float64Flag{Name: "howmuch"}, - &cli.GenericFlag{Name: "wat", Value: &genericType{}}, - &cli.Int64Flag{Name: "longdistance"}, - &cli.Int64SliceFlag{Name: "intervals"}, - &cli.IntFlag{Name: "distance"}, - &cli.IntSliceFlag{Name: "times"}, - &cli.StringFlag{Name: "dance-move", Aliases: []string{"d"}}, - &cli.StringSliceFlag{Name: "names", Aliases: []string{"N"}}, - &cli.UintFlag{Name: "age"}, - &cli.Uint64Flag{Name: "bigage"}, - }, - EnableBashCompletion: true, - HideHelp: false, - HideVersion: false, - BashComplete: func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") - }, - Before: func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "HEEEERE GOES\n") - return nil - }, - After: func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "Phew!\n") - return nil - }, - CommandNotFound: func(c *cli.Context, command string) { - fmt.Fprintf(c.App.Writer, "Thar be no %q here.\n", command) - }, - OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { - if isSubcommand { - return err - } - - fmt.Fprintf(c.App.Writer, "WRONG: %#v\n", err) - return nil - }, - Action: func(c *cli.Context) error { - cli.DefaultAppComplete(c) - cli.HandleExitCoder(errors.New("not an exit coder, though")) - cli.ShowAppHelp(c) - cli.ShowCommandCompletions(c, "nope") - cli.ShowCommandHelp(c, "also-nope") - cli.ShowCompletions(c) - cli.ShowSubcommandHelp(c) - cli.ShowVersion(c) - - fmt.Printf("%#v\n", c.App.Command("doo")) - if c.Bool("infinite") { - c.App.Run([]string{"app", "doo", "wop"}) - } - - if c.Bool("forevar") { - c.App.RunAsSubcommand(c) - } - c.App.Setup() - fmt.Printf("%#v\n", c.App.VisibleCategories()) - fmt.Printf("%#v\n", c.App.VisibleCommands()) - fmt.Printf("%#v\n", c.App.VisibleFlags()) - - fmt.Printf("%#v\n", c.Args().First()) - if c.Args().Len() > 0 { - fmt.Printf("%#v\n", c.Args().Get(1)) - } - fmt.Printf("%#v\n", c.Args().Present()) - fmt.Printf("%#v\n", c.Args().Tail()) - - set := flag.NewFlagSet("contrive", 0) - nc := cli.NewContext(c.App, set, c) - - fmt.Printf("%#v\n", nc.Args()) - fmt.Printf("%#v\n", nc.Bool("nope")) - fmt.Printf("%#v\n", !nc.Bool("nerp")) - fmt.Printf("%#v\n", nc.Duration("howlong")) - fmt.Printf("%#v\n", nc.Float64("hay")) - fmt.Printf("%#v\n", nc.Generic("bloop")) - fmt.Printf("%#v\n", nc.Int64("bonk")) - fmt.Printf("%#v\n", nc.Int64Slice("burnks")) - fmt.Printf("%#v\n", nc.Int("bips")) - fmt.Printf("%#v\n", nc.IntSlice("blups")) - fmt.Printf("%#v\n", nc.String("snurt")) - fmt.Printf("%#v\n", nc.StringSlice("snurkles")) - fmt.Printf("%#v\n", nc.Uint("flub")) - fmt.Printf("%#v\n", nc.Uint64("florb")) - - fmt.Printf("%#v\n", nc.FlagNames()) - fmt.Printf("%#v\n", nc.IsSet("wat")) - fmt.Printf("%#v\n", nc.Set("wat", "nope")) - fmt.Printf("%#v\n", nc.NArg()) - fmt.Printf("%#v\n", nc.NumFlags()) - fmt.Printf("%#v\n", nc.Lineage()[1]) - nc.Set("wat", "also-nope") - - ec := cli.Exit("ohwell", 86) - fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) - fmt.Printf("made it!\n") - return ec - }, - Metadata: map[string]interface{}{ - "layers": "many", - "explicable": false, - "whatever-values": 19.99, - }, - } - - if os.Getenv("HEXY") != "" { - app.Writer = &hexWriter{} - app.ErrWriter = &hexWriter{} - } - - app.Run(os.Args) -} - -func wopAction(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, ":wave: over here, eh\n") - return nil -} -``` - -## Migrating From Older Releases - -There are a small set of breaking changes between v1 and v2. -Converting is relatively straightforward and typically takes less than -an hour. Specific steps are included in -[Migration Guide: v1 to v2](../migrate-v1-to-v2.md). Also see the [pkg.go.dev docs](https://pkg.go.dev/github.com/urfave/cli/v2) for v2 API documentation. - diff --git a/docs/v2/index.md b/docs/v2/index.md new file mode 120000 index 0000000..9d0493a --- /dev/null +++ b/docs/v2/index.md @@ -0,0 +1 @@ +manual.md \ No newline at end of file diff --git a/docs/v2/manual.md b/docs/v2/manual.md new file mode 100644 index 0000000..9c26100 --- /dev/null +++ b/docs/v2/manual.md @@ -0,0 +1,1677 @@ +# v2 guide + +## Getting Started + +One of the philosophies behind cli is that an API should be playful and full of +discovery. So a cli app can be as little as one line of code in `main()`. + + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + (&cli.App{}).Run(os.Args) +} +``` + +This app will run and show help text, but is not very useful. Let's give an +action to execute and some help documentation: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Name: "boom", + Usage: "make an explosive entrance", + Action: func(c *cli.Context) error { + fmt.Println("boom! I say!") + return nil + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Running this already gives you a ton of functionality, plus support for things +like subcommands and flags, which are covered below. + +## Examples + +Being a programmer can be a lonely job. Thankfully by the power of automation +that is not the case! Let's create a greeter app to fend off our demons of +loneliness! + +Start by creating a directory named `greet`, and within it, add a file, +`greet.go` with the following code in it: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Name: "greet", + Usage: "fight the loneliness!", + Action: func(c *cli.Context) error { + fmt.Println("Hello friend!") + return nil + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Install our command to the `$GOPATH/bin` directory: + +``` +$ go install +``` + +Finally run our new command: + +``` +$ greet +Hello friend! +``` + +cli also generates neat help text: + +``` +$ greet help +NAME: + greet - fight the loneliness! + +USAGE: + greet [global options] command [command options] [arguments...] + +COMMANDS: + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS + --help, -h show help (default: false) +``` + +### Arguments + +You can lookup arguments by calling the `Args` function on `cli.Context`, e.g.: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Action: func(c *cli.Context) error { + fmt.Printf("Hello %q", c.Args().Get(0)) + return nil + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +### Flags + +Setting and querying flags is simple. + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag { + &cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + }, + }, + Action: func(c *cli.Context) error { + name := "Nefertiti" + if c.NArg() > 0 { + name = c.Args().Get(0) + } + if c.String("lang") == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +You can also set a destination variable for a flag, to which the content will be +scanned. + + +``` go +package main + +import ( + "log" + "os" + "fmt" + + "github.com/urfave/cli/v2" +) + +func main() { + var language string + + app := &cli.App{ + Flags: []cli.Flag { + &cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + Destination: &language, + }, + }, + Action: func(c *cli.Context) error { + name := "someone" + if c.NArg() > 0 { + name = c.Args().Get(0) + } + if language == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +See full list of flags at https://pkg.go.dev/github.com/urfave/cli/v2 + +#### Placeholder Values + +Sometimes it's useful to specify a flag's value within the usage string itself. +Such placeholders are indicated with back quotes. + +For example this: + + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: "Load configuration from `FILE`", + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Will result in help output like: + +``` +--config FILE, -c FILE Load configuration from FILE +``` + +Note that only the first placeholder is used. Subsequent back-quoted words will +be left as-is. + +#### Alternate Names + +You can set alternate (or short) names for flags by providing a comma-delimited +list for the `Name`. e.g. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag { + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +That flag can then be set with `--lang spanish` or `-l spanish`. Note that +giving two different forms of the same flag in the same command invocation is an +error. + +#### Ordering + +Flags for the application and commands are shown in the order they are defined. +However, it's possible to sort them from outside this library by using `FlagsByName` +or `CommandsByName` with `sort`. + +For example this: + + +``` go +package main + +import ( + "log" + "os" + "sort" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "Language for the greeting", + }, + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: "Load configuration from `FILE`", + }, + }, + Commands: []*cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + return nil + }, + }, + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(c *cli.Context) error { + return nil + }, + }, + }, + } + + sort.Sort(cli.FlagsByName(app.Flags)) + sort.Sort(cli.CommandsByName(app.Commands)) + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Will result in help output like: + +``` +--config FILE, -c FILE Load configuration from FILE +--lang value, -l value Language for the greeting (default: "english") +``` + +#### Values from the Environment + +You can also have the default value set from the environment via `EnvVars`. e.g. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag { + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + EnvVars: []string{"APP_LANG"}, + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +If `EnvVars` contains more than one string, the first environment variable that +resolves is used. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + EnvVars: []string{"LEGACY_COMPAT_LANG", "APP_LANG", "LANG"}, + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +#### Values from files + +You can also have the default value set from file via `FilePath`. e.g. + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + &cli.StringFlag{ + Name: "password", + Aliases: []string{"p"}, + Usage: "password for the mysql database", + FilePath: "/etc/mysql/password", + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Note that default values set from file (e.g. `FilePath`) take precedence over +default values set from the environment (e.g. `EnvVar`). + +#### Values from alternate input sources (YAML, TOML, and others) + +There is a separate package altsrc that adds support for getting flag values +from other file input sources. + +Currently supported input source formats: +* YAML +* JSON +* TOML + +In order to get values for a flag from an alternate input source the following +code would be added to wrap an existing cli.Flag like below: + +``` go + altsrc.NewIntFlag(&cli.IntFlag{Name: "test"}) +``` + +Initialization must also occur for these flags. Below is an example initializing +getting data from a yaml file below. + +``` go + command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) +``` + +The code above will use the "load" string as a flag name to get the file name of +a yaml file from the cli.Context. It will then use that file name to initialize +the yaml input source for any flags that are defined on that command. As a note +the "load" flag used would also have to be defined on the command flags in order +for this code snippet to work. + +Currently only YAML, JSON, and TOML files are supported but developers can add support +for other input sources by implementing the altsrc.InputSourceContext for their +given sources. + +Here is a more complete sample of a command using YAML support: + + +``` go +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2/altsrc" +) + +func main() { + flags := []cli.Flag{ + altsrc.NewIntFlag(&cli.IntFlag{Name: "test"}), + &cli.StringFlag{Name: "load"}, + } + + app := &cli.App{ + Action: func(c *cli.Context) error { + fmt.Println("--test value.*default: 0") + return nil + }, + Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")), + Flags: flags, + } + + app.Run(os.Args) +} +``` + +#### Required Flags + +You can make a flag required by setting the `Required` field to `true`. If a user +does not provide a required flag, they will be shown an error message. + +Take for example this app that requires the `lang` flag: + + +```go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +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. + +For example this: + + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "port", + Usage: "Use a randomized port", + Value: 0, + DefaultText: "random", + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Will result in help output like: + +``` +--port value Use a randomized port (default: random) +``` + +#### Precedence + +The precedence for flag value sources is as follows (highest to lowest): + +0. Command line flag value from user +0. Environment variable (if specified) +0. Configuration file (if specified) +0. Default defined on the flag + +### Subcommands + +Subcommands can be defined for a more git-like command line app. + + +```go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Commands: []*cli.Command{ + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(c *cli.Context) error { + fmt.Println("added task: ", c.Args().First()) + return nil + }, + }, + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + fmt.Println("completed task: ", c.Args().First()) + return nil + }, + }, + { + Name: "template", + Aliases: []string{"t"}, + Usage: "options for task templates", + Subcommands: []*cli.Command{ + { + Name: "add", + Usage: "add a new template", + Action: func(c *cli.Context) error { + fmt.Println("new task template: ", c.Args().First()) + return nil + }, + }, + { + Name: "remove", + Usage: "remove an existing template", + Action: func(c *cli.Context) error { + fmt.Println("removed task template: ", c.Args().First()) + return nil + }, + }, + }, + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +### Subcommands categories + +For additional organization in apps that have many subcommands, you can +associate a category for each command to group them together in the help +output. + +E.g. + +```go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Commands: []*cli.Command{ + { + Name: "noop", + }, + { + Name: "add", + Category: "template", + }, + { + Name: "remove", + Category: "template", + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +Will include: + +``` +COMMANDS: + noop + + Template actions: + add + remove +``` + +### Exit code + +Calling `App.Run` will not automatically call `os.Exit`, which means that by +default the exit code will "fall through" to being `0`. An explicit exit code +may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a +`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.: + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "ginger-crouton", + Usage: "is it in the soup?", + }, + }, + Action: func(ctx *cli.Context) error { + if !ctx.Bool("ginger-crouton") { + return cli.Exit("Ginger croutons are not in the soup", 86) + } + return nil + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +### Combining short options + +Traditional use of options using their shortnames look like this: + +``` +$ cmd -s -o -m "Some message" +``` + +Suppose you want users to be able to combine options with their shortnames. This +can be done using the `UseShortOptionHandling` bool in your app configuration, +or for individual commands by attaching it to the command configuration. For +example: + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{} + app.UseShortOptionHandling = true + app.Commands = []*cli.Command{ + { + Name: "short", + Usage: "complete a task on the list", + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "serve", Aliases: []string{"s"}}, + &cli.BoolFlag{Name: "option", Aliases: []string{"o"}}, + &cli.StringFlag{Name: "message", Aliases: []string{"m"}}, + }, + Action: func(c *cli.Context) error { + fmt.Println("serve:", c.Bool("serve")) + fmt.Println("option:", c.Bool("option")) + fmt.Println("message:", c.String("message")) + return nil + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +If your program has any number of bool flags such as `serve` and `option`, and +optionally one non-bool flag `message`, with the short options of `-s`, `-o`, +and `-m` respectively, setting `UseShortOptionHandling` will also support the +following syntax: + +``` +$ cmd -som "Some message" +``` + +If you enable `UseShortOptionHandling`, then you must not use any flags that +have a single leading `-` or this will result in failures. For example, +`-option` can no longer be used. Flags with two leading dashes (such as +`--options`) are still valid. + +### Bash Completion + +You can enable completion commands by setting the `EnableBashCompletion` +flag on the `App` object to `true`. By default, this setting will allow auto-completion +for an app's subcommands, but you can write your own completion methods for +the App or its subcommands as well. + +#### Default auto-completion + +```go +package main +import ( + "fmt" + "log" + "os" + "github.com/urfave/cli/v2" +) +func main() { + app := cli.NewApp() + app.EnableBashCompletion = true + app.Commands = []*cli.Command{ + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(c *cli.Context) error { + fmt.Println("added task: ", c.Args().First()) + return nil + }, + }, + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + fmt.Println("completed task: ", c.Args().First()) + return nil + }, + }, + { + Name: "template", + Aliases: []string{"t"}, + Usage: "options for task templates", + Subcommands: []*cli.Command{ + { + Name: "add", + Usage: "add a new template", + Action: func(c *cli.Context) error { + fmt.Println("new task template: ", c.Args().First()) + return nil + }, + }, + { + Name: "remove", + Usage: "remove an existing template", + Action: func(c *cli.Context) error { + fmt.Println("removed task template: ", c.Args().First()) + return nil + }, + }, + }, + }, + } + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` +![](/docs/v2/images/default-bash-autocomplete.gif) + +#### Custom auto-completion + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} + + app := &cli.App{ + EnableBashCompletion: true, + Commands: []*cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + fmt.Println("completed task: ", c.Args().First()) + return nil + }, + BashComplete: func(c *cli.Context) { + // This will complete if no args are passed + if c.NArg() > 0 { + return + } + for _, t := range tasks { + fmt.Println(t) + } + }, + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` +![](/docs/v2/images/custom-bash-autocomplete.gif) + +#### Enabling + +To enable auto-completion for the current shell session, a bash script, +`autocomplete/bash_autocomplete` is included in this repo. + +To use `autocomplete/bash_autocomplete` set an environment variable named `PROG` to +the name of your program and then `source` the `autocomplete/bash_autocomplete` file. + +For example, if your cli program is called `myprogram`: + +`PROG=myprogram source path/to/cli/autocomplete/bash_autocomplete` + +Auto-completion is now enabled for the current shell, but will not persist into a new shell. + +#### Distribution and Persistent Autocompletion + +Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename +it to the name of the program you wish to add autocomplete support for (or +automatically install it there if you are distributing a package). Don't forget +to source the file or restart your shell to activate the auto-completion. + +``` +sudo cp path/to/autocomplete/bash_autocomplete /etc/bash_completion.d/ +source /etc/bash_completion.d/ +``` + +Alternatively, you can just document that users should `source` the generic +`autocomplete/bash_autocomplete` and set `$PROG` within their bash configuration +file, adding these lines: + +``` +PROG= +source path/to/cli/autocomplete/bash_autocomplete +``` +Keep in mind that if they are enabling auto-completion for more than one program, +they will need to set `PROG` and source `autocomplete/bash_autocomplete` for each +program, like so: + +``` +PROG= +source path/to/cli/autocomplete/bash_autocomplete +PROG= +source path/to/cli/autocomplete/bash_autocomplete +``` + +#### Customization + +The default shell completion flag (`--generate-bash-completion`) is defined as +`cli.EnableBashCompletion`, and may be redefined if desired, e.g.: + + +``` go +package main + +import ( + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + EnableBashCompletion: true, + Commands: []*cli.Command{ + { + Name: "wat", + }, + }, + } + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +#### ZSH Support +Auto-completion for ZSH is also supported using the `autocomplete/zsh_autocomplete` +file included in this repo. Two environment variables are used, `PROG` and `_CLI_ZSH_AUTOCOMPLETE_HACK`. +Set `PROG` to the program name as before, set `_CLI_ZSH_AUTOCOMPLETE_HACK` to `1`, and +then `source path/to/autocomplete/zsh_autocomplete`. Adding the following lines to your ZSH +configuration file (usually `.zshrc`) will allow the auto-completion to persist across new shells: + +``` +PROG= +_CLI_ZSH_AUTOCOMPLETE_HACK=1 +source path/to/autocomplete/zsh_autocomplete +``` +#### ZSH default auto-complete example +![](/docs/v2/images/default-zsh-autocomplete.gif) +#### ZSH custom auto-complete example +![](/docs/v2/images/custom-zsh-autocomplete.gif) + +#### PowerShell Support +Auto-completion for PowerShell is also supported using the `autocomplete/powershell_autocomplete.ps1` +file included in this repo. + +Rename the script to `.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/.ps1` + +To persist across new shells, open the PowerShell profile (with `code $profile` or `notepad $profile`) +and add the line: +``` +& path/to/autocomplete/.ps1 +``` + + +### Generated Help Text + +The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked +by the cli internals in order to print generated help text for the app, command, +or subcommand, and break execution. + +#### Customization + +All of the help text generation may be customized, and at multiple levels. The +templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and +`SubcommandHelpTemplate` which may be reassigned or augmented, and full override +is possible by assigning a compatible func to the `cli.HelpPrinter` variable, +e.g.: + + +``` go +package main + +import ( + "fmt" + "io" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + // EXAMPLE: Append to an existing template + cli.AppHelpTemplate = fmt.Sprintf(`%s + +WEBSITE: http://awesometown.example.com + +SUPPORT: support@awesometown.example.com + +`, cli.AppHelpTemplate) + + // EXAMPLE: Override a template + cli.AppHelpTemplate = `NAME: + {{.Name}} - {{.Usage}} +USAGE: + {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} + {{if len .Authors}} +AUTHOR: + {{range .Authors}}{{ . }}{{end}} + {{end}}{{if .Commands}} +COMMANDS: +{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} +GLOBAL OPTIONS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}}{{if .Copyright }} +COPYRIGHT: + {{.Copyright}} + {{end}}{{if .Version}} +VERSION: + {{.Version}} + {{end}} +` + + // EXAMPLE: Replace the `HelpPrinter` func + cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { + fmt.Println("Ha HA. I pwnd the help!!1") + } + + (&cli.App{}).Run(os.Args) +} +``` + +The default flag may be customized to something other than `-h/--help` by +setting `cli.HelpFlag`, e.g.: + + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + cli.HelpFlag = &cli.BoolFlag{ + Name: "haaaaalp", + Aliases: []string{"halp"}, + Usage: "HALP", + EnvVars: []string{"SHOW_HALP", "HALPPLZ"}, + } + + (&cli.App{}).Run(os.Args) +} +``` + +### Version Flag + +The default version flag (`-v/--version`) is defined as `cli.VersionFlag`, which +is checked by the cli internals in order to print the `App.Version` via +`cli.VersionPrinter` and break execution. + +#### Customization + +The default flag may be customized to something other than `-v/--version` by +setting `cli.VersionFlag`, e.g.: + + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + cli.VersionFlag = &cli.BoolFlag{ + Name: "print-version", + Aliases: []string{"V"}, + Usage: "print only the version", + } + + app := &cli.App{ + Name: "partay", + Version: "v19.99.0", + } + app.Run(os.Args) +} +``` + +Alternatively, the version printer at `cli.VersionPrinter` may be overridden, e.g.: + + +``` go +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli/v2" +) + +var ( + Revision = "fafafaf" +) + +func main() { + cli.VersionPrinter = func(c *cli.Context) { + fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision) + } + + app := &cli.App{ + Name: "partay", + Version: "v19.99.0", + } + app.Run(os.Args) +} +``` + +### Timestamp Flag + +Using the timestamp flag is simple. Please refer to [`time.Parse`](https://golang.org/pkg/time/#example_Parse) to get possible formats. + + +``` go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Flags: []cli.Flag { + &cli.TimestampFlag{Name: "meeting", Layout: "2006-01-02T15:04:05"}, + }, + Action: func(c *cli.Context) error { + fmt.Printf("%s", c.Timestamp("meeting").String()) + return nil + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} +``` + +In this example the flag could be used like this : + +`myapp --meeting 2019-08-12T15:04:05` + +Side note: quotes may be necessary around the date depending on your layout (if you have spaces for instance) + +### Full API Example + +**Notice**: This is a contrived (functioning) example meant strictly for API +demonstration purposes. Use of one's imagination is encouraged. + + +``` go +package main + +import ( + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "time" + + "github.com/urfave/cli/v2" +) + +func init() { + cli.AppHelpTemplate += "\nCUSTOMIZED: you bet ur muffins\n" + cli.CommandHelpTemplate += "\nYMMV\n" + cli.SubcommandHelpTemplate += "\nor something\n" + + cli.HelpFlag = &cli.BoolFlag{Name: "halp"} + cli.VersionFlag = &cli.BoolFlag{Name: "print-version", Aliases: []string{"V"}} + + cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { + fmt.Fprintf(w, "best of luck to you\n") + } + cli.VersionPrinter = func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "version=%s\n", c.App.Version) + } + cli.OsExiter = func(c int) { + fmt.Fprintf(cli.ErrWriter, "refusing to exit %d\n", c) + } + cli.ErrWriter = ioutil.Discard + cli.FlagStringer = func(fl cli.Flag) string { + return fmt.Sprintf("\t\t%s", fl.Names()[0]) + } +} + +type hexWriter struct{} + +func (w *hexWriter) Write(p []byte) (int, error) { + for _, b := range p { + fmt.Printf("%x", b) + } + fmt.Printf("\n") + + return len(p), nil +} + +type genericType struct { + s string +} + +func (g *genericType) Set(value string) error { + g.s = value + return nil +} + +func (g *genericType) String() string { + return g.s +} + +func main() { + app := &cli.App{ + Name: "kənˈtrīv", + Version: "v19.99.0", + Compiled: time.Now(), + Authors: []*cli.Author{ + &cli.Author{ + Name: "Example Human", + Email: "human@example.com", + }, + }, + Copyright: "(c) 1999 Serious Enterprise", + HelpName: "contrive", + Usage: "demonstrate available API", + UsageText: "contrive - demonstrating the available API", + ArgsUsage: "[args and such]", + Commands: []*cli.Command{ + &cli.Command{ + Name: "doo", + Aliases: []string{"do"}, + Category: "motion", + Usage: "do the doo", + UsageText: "doo - does the dooing", + Description: "no really, there is a lot of dooing to be done", + ArgsUsage: "[arrgh]", + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "forever", Aliases: []string{"forevvarr"}}, + }, + Subcommands: []*cli.Command{ + &cli.Command{ + Name: "wop", + Action: wopAction, + }, + }, + SkipFlagParsing: false, + HideHelp: false, + Hidden: false, + HelpName: "doo!", + BashComplete: func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "--better\n") + }, + Before: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "brace for impact\n") + return nil + }, + After: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "did we lose anyone?\n") + return nil + }, + Action: func(c *cli.Context) error { + c.Command.FullName() + c.Command.HasName("wop") + c.Command.Names() + c.Command.VisibleFlags() + fmt.Fprintf(c.App.Writer, "dodododododoodododddooooododododooo\n") + if c.Bool("forever") { + c.Command.Run(c) + } + return nil + }, + OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { + fmt.Fprintf(c.App.Writer, "for shame\n") + return err + }, + }, + }, + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "fancy"}, + &cli.BoolFlag{Value: true, Name: "fancier"}, + &cli.DurationFlag{Name: "howlong", Aliases: []string{"H"}, Value: time.Second * 3}, + &cli.Float64Flag{Name: "howmuch"}, + &cli.GenericFlag{Name: "wat", Value: &genericType{}}, + &cli.Int64Flag{Name: "longdistance"}, + &cli.Int64SliceFlag{Name: "intervals"}, + &cli.IntFlag{Name: "distance"}, + &cli.IntSliceFlag{Name: "times"}, + &cli.StringFlag{Name: "dance-move", Aliases: []string{"d"}}, + &cli.StringSliceFlag{Name: "names", Aliases: []string{"N"}}, + &cli.UintFlag{Name: "age"}, + &cli.Uint64Flag{Name: "bigage"}, + }, + EnableBashCompletion: true, + HideHelp: false, + HideVersion: false, + BashComplete: func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") + }, + Before: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "HEEEERE GOES\n") + return nil + }, + After: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "Phew!\n") + return nil + }, + CommandNotFound: func(c *cli.Context, command string) { + fmt.Fprintf(c.App.Writer, "Thar be no %q here.\n", command) + }, + OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { + if isSubcommand { + return err + } + + fmt.Fprintf(c.App.Writer, "WRONG: %#v\n", err) + return nil + }, + Action: func(c *cli.Context) error { + cli.DefaultAppComplete(c) + cli.HandleExitCoder(errors.New("not an exit coder, though")) + cli.ShowAppHelp(c) + cli.ShowCommandCompletions(c, "nope") + cli.ShowCommandHelp(c, "also-nope") + cli.ShowCompletions(c) + cli.ShowSubcommandHelp(c) + cli.ShowVersion(c) + + fmt.Printf("%#v\n", c.App.Command("doo")) + if c.Bool("infinite") { + c.App.Run([]string{"app", "doo", "wop"}) + } + + if c.Bool("forevar") { + c.App.RunAsSubcommand(c) + } + c.App.Setup() + fmt.Printf("%#v\n", c.App.VisibleCategories()) + fmt.Printf("%#v\n", c.App.VisibleCommands()) + fmt.Printf("%#v\n", c.App.VisibleFlags()) + + fmt.Printf("%#v\n", c.Args().First()) + if c.Args().Len() > 0 { + fmt.Printf("%#v\n", c.Args().Get(1)) + } + fmt.Printf("%#v\n", c.Args().Present()) + fmt.Printf("%#v\n", c.Args().Tail()) + + set := flag.NewFlagSet("contrive", 0) + nc := cli.NewContext(c.App, set, c) + + fmt.Printf("%#v\n", nc.Args()) + fmt.Printf("%#v\n", nc.Bool("nope")) + fmt.Printf("%#v\n", !nc.Bool("nerp")) + fmt.Printf("%#v\n", nc.Duration("howlong")) + fmt.Printf("%#v\n", nc.Float64("hay")) + fmt.Printf("%#v\n", nc.Generic("bloop")) + fmt.Printf("%#v\n", nc.Int64("bonk")) + fmt.Printf("%#v\n", nc.Int64Slice("burnks")) + fmt.Printf("%#v\n", nc.Int("bips")) + fmt.Printf("%#v\n", nc.IntSlice("blups")) + fmt.Printf("%#v\n", nc.String("snurt")) + fmt.Printf("%#v\n", nc.StringSlice("snurkles")) + fmt.Printf("%#v\n", nc.Uint("flub")) + fmt.Printf("%#v\n", nc.Uint64("florb")) + + fmt.Printf("%#v\n", nc.FlagNames()) + fmt.Printf("%#v\n", nc.IsSet("wat")) + fmt.Printf("%#v\n", nc.Set("wat", "nope")) + fmt.Printf("%#v\n", nc.NArg()) + fmt.Printf("%#v\n", nc.NumFlags()) + fmt.Printf("%#v\n", nc.Lineage()[1]) + nc.Set("wat", "also-nope") + + ec := cli.Exit("ohwell", 86) + fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) + fmt.Printf("made it!\n") + return ec + }, + Metadata: map[string]interface{}{ + "layers": "many", + "explicable": false, + "whatever-values": 19.99, + }, + } + + if os.Getenv("HEXY") != "" { + app.Writer = &hexWriter{} + app.ErrWriter = &hexWriter{} + } + + app.Run(os.Args) +} + +func wopAction(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, ":wave: over here, eh\n") + return nil +} +``` + +## Migrating From Older Releases + +There are a small set of breaking changes between v1 and v2. +Converting is relatively straightforward and typically takes less than +an hour. Specific steps are included in +[Migration Guide: v1 to v2](../migrate-v1-to-v2.md). Also see the [pkg.go.dev docs](https://pkg.go.dev/github.com/urfave/cli/v2) for v2 API documentation. + From bc9ad9fede4d530aaec478c0dc21edcdfc8b1b6a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 8 May 2022 13:52:32 -0400 Subject: [PATCH 085/125] Generate RequiredFlag and VisibleFlag implementations --- flag_bool.go | 10 -- flag_duration.go | 10 -- flag_float64.go | 10 -- flag_float64_slice.go | 10 -- flag_generic.go | 10 -- flag_int.go | 10 -- flag_int64.go | 10 -- flag_int64_slice.go | 10 -- flag_int_slice.go | 10 -- flag_path.go | 10 -- flag_string.go | 10 -- flag_string_slice.go | 10 -- flag_timestamp.go | 10 -- flag_uint.go | 10 -- flag_uint64.go | 10 -- internal/genflags/generated.gotmpl | 16 ++- internal/genflags/generated_test.gotmpl | 16 +++ internal/genflags/spec.go | 8 ++ zz_generated.flags.go | 150 ++++++++++++++++++++ zz_generated.flags_test.go | 180 ++++++++++++++++++++++++ 20 files changed, 369 insertions(+), 151 deletions(-) diff --git a/flag_bool.go b/flag_bool.go index f984acf..1277504 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -6,11 +6,6 @@ import ( "strconv" ) -// IsRequired returns whether or not the flag is required -func (f *BoolFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *BoolFlag) TakesValue() bool { return false @@ -27,11 +22,6 @@ 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 -} - // GetDefaultText returns the default text for this flag func (f *BoolFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_duration.go b/flag_duration.go index a6677ad..236056c 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -6,11 +6,6 @@ import ( "time" ) -// IsRequired returns whether or not the flag is required -func (f *DurationFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *DurationFlag) TakesValue() bool { return true @@ -27,11 +22,6 @@ 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 -} - // GetDefaultText returns the default text for this flag func (f *DurationFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_float64.go b/flag_float64.go index 62a1973..aa2f359 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -6,11 +6,6 @@ import ( "strconv" ) -// IsRequired returns whether or not the flag is required -func (f *Float64Flag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *Float64Flag) TakesValue() bool { return true @@ -40,11 +35,6 @@ func (f *Float64Flag) GetEnvVars() []string { return f.EnvVars } -// 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 { diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 0f09c8c..e2bfc4c 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -81,11 +81,6 @@ func (f *Float64SliceFlag) String() string { return withEnvHint(f.GetEnvVars(), stringifyFloat64SliceFlag(f)) } -// IsRequired returns whether or not the flag is required -func (f *Float64SliceFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true if the flag takes a value, otherwise false func (f *Float64SliceFlag) TakesValue() bool { return true @@ -105,11 +100,6 @@ 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 -} - // GetDefaultText returns the default text for this flag func (f *Float64SliceFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_generic.go b/flag_generic.go index 71e7173..8be32b8 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -11,11 +11,6 @@ type Generic interface { String() string } -// IsRequired returns whether or not the flag is required -func (f *GenericFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *GenericFlag) TakesValue() bool { return true @@ -35,11 +30,6 @@ 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 -} - // GetDefaultText returns the default text for this flag func (f *GenericFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_int.go b/flag_int.go index b68c3e8..3f5dec5 100644 --- a/flag_int.go +++ b/flag_int.go @@ -6,11 +6,6 @@ import ( "strconv" ) -// IsRequired returns whether or not the flag is required -func (f *IntFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *IntFlag) TakesValue() bool { return true @@ -27,11 +22,6 @@ 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 -} - // GetDefaultText returns the default text for this flag func (f *IntFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_int64.go b/flag_int64.go index 3f71134..d005866 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -6,11 +6,6 @@ import ( "strconv" ) -// IsRequired returns whether or not the flag is required -func (f *Int64Flag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *Int64Flag) TakesValue() bool { return true @@ -27,11 +22,6 @@ 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 -} - // GetDefaultText returns the default text for this flag func (f *Int64Flag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 14d8c41..b61bd7f 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -82,11 +82,6 @@ func (f *Int64SliceFlag) String() string { return withEnvHint(f.GetEnvVars(), stringifyInt64SliceFlag(f)) } -// IsRequired returns whether or not the flag is required -func (f *Int64SliceFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *Int64SliceFlag) TakesValue() bool { return true @@ -106,11 +101,6 @@ 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 -} - // GetDefaultText returns the default text for this flag func (f *Int64SliceFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_int_slice.go b/flag_int_slice.go index 4ed7f2c..f9713cc 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -93,11 +93,6 @@ func (f *IntSliceFlag) String() string { return withEnvHint(f.GetEnvVars(), stringifyIntSliceFlag(f)) } -// IsRequired returns whether or not the flag is required -func (f *IntSliceFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *IntSliceFlag) TakesValue() bool { return true @@ -117,11 +112,6 @@ 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 -} - // GetDefaultText returns the default text for this flag func (f *IntSliceFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_path.go b/flag_path.go index 095a596..b0c2215 100644 --- a/flag_path.go +++ b/flag_path.go @@ -7,11 +7,6 @@ import ( type Path = string -// IsRequired returns whether or not the flag is required -func (f *PathFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *PathFlag) TakesValue() bool { return true @@ -28,11 +23,6 @@ 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 -} - // GetDefaultText returns the default text for this flag func (f *PathFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_string.go b/flag_string.go index 4831c17..24adbe9 100644 --- a/flag_string.go +++ b/flag_string.go @@ -5,11 +5,6 @@ import ( "fmt" ) -// IsRequired returns whether or not the flag is required -func (f *StringFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *StringFlag) TakesValue() bool { return true @@ -26,11 +21,6 @@ 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 -} - // GetDefaultText returns the default text for this flag func (f *StringFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_string_slice.go b/flag_string_slice.go index 9e69d00..d0195d5 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -76,11 +76,6 @@ func (f *StringSliceFlag) String() string { return withEnvHint(f.GetEnvVars(), stringifyStringSliceFlag(f)) } -// IsRequired returns whether or not the flag is required -func (f *StringSliceFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *StringSliceFlag) TakesValue() bool { return true @@ -100,11 +95,6 @@ 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 -} - // GetDefaultText returns the default text for this flag func (f *StringSliceFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_timestamp.go b/flag_timestamp.go index 32899f5..ed480cf 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -58,11 +58,6 @@ func (t *Timestamp) Get() interface{} { return *t } -// 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 @@ -82,11 +77,6 @@ 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 -} - // GetDefaultText returns the default text for this flag func (f *TimestampFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/flag_uint.go b/flag_uint.go index 625f11c..1ec9713 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -6,11 +6,6 @@ import ( "strconv" ) -// IsRequired returns whether or not the flag is required -func (f *UintFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *UintFlag) TakesValue() bool { return true @@ -21,11 +16,6 @@ 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 { diff --git a/flag_uint64.go b/flag_uint64.go index 58969a5..55ba08a 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -6,11 +6,6 @@ import ( "strconv" ) -// IsRequired returns whether or not the flag is required -func (f *Uint64Flag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *Uint64Flag) TakesValue() bool { return true @@ -21,11 +16,6 @@ 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 { diff --git a/internal/genflags/generated.gotmpl b/internal/genflags/generated.gotmpl index 5d6f0dd..99c292a 100644 --- a/internal/genflags/generated.gotmpl +++ b/internal/genflags/generated.gotmpl @@ -31,7 +31,7 @@ type {{.TypeName}} struct { func (f *{{.TypeName}}) String() string { return {{$.UrfaveCLINamespace}}FlagStringer(f) } -{{end}} +{{end}}{{/* /if .GenerateFmtStringerInterface */}} {{if .GenerateFlagInterface}} // IsSet returns whether or not the flag has been set through env or file @@ -45,6 +45,20 @@ func (f *{{.TypeName}}) Names() []string { } {{end}}{{/* /if .GenerateFlagInterface */}} + +{{if .GenerateRequiredFlagInterface}} +// IsRequired returns whether or not the flag is required +func (f *{{.TypeName}}) IsRequired() bool { + return f.Required +} +{{end}}{{/* /if .GenerateRequiredFlagInterface */}} + +{{if .GenerateVisibleFlagInterface}} +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *{{.TypeName}}) IsVisible() bool { + return !f.Hidden +} +{{end}}{{/* /if .GenerateVisibleFlagInterface */}} {{end}}{{/* /range .SortedFlagTypes */}} // vim{{/* 👻 */}}:ro diff --git a/internal/genflags/generated_test.gotmpl b/internal/genflags/generated_test.gotmpl index 44e9ad4..52de4e4 100644 --- a/internal/genflags/generated_test.gotmpl +++ b/internal/genflags/generated_test.gotmpl @@ -19,6 +19,22 @@ func Test{{.TypeName}}_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } {{end}} + +{{if .GenerateRequiredFlagInterface}} +func Test{{.TypeName}}_SatisfiesRequiredFlagInterface(t *testing.T) { + var f {{$.UrfaveCLITestNamespace}}RequiredFlag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} + + _ = f.IsRequired() +} +{{end}} + +{{if .GenerateVisibleFlagInterface}} +func Test{{.TypeName}}_SatisfiesVisibleFlagInterface(t *testing.T) { + var f {{$.UrfaveCLITestNamespace}}VisibleFlag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{} + + _ = f.IsVisible() +} +{{end}} {{end}} // vim{{/* 👻 */}}:ro diff --git a/internal/genflags/spec.go b/internal/genflags/spec.go index d9f18db..a7afb22 100644 --- a/internal/genflags/spec.go +++ b/internal/genflags/spec.go @@ -83,6 +83,14 @@ func (ft *FlagType) GenerateFlagInterface() bool { return ft.skipInterfaceNamed("Flag") } +func (ft *FlagType) GenerateRequiredFlagInterface() bool { + return ft.skipInterfaceNamed("RequiredFlag") +} + +func (ft *FlagType) GenerateVisibleFlagInterface() bool { + return ft.skipInterfaceNamed("VisibleFlag") +} + func (ft *FlagType) skipInterfaceNamed(name string) bool { if ft.Config == nil { return true diff --git a/zz_generated.flags.go b/zz_generated.flags.go index 703e66a..6c16c3e 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -33,6 +33,16 @@ func (f *Float64SliceFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *Float64SliceFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Float64SliceFlag) IsVisible() bool { + return !f.Hidden +} + // GenericFlag is a flag with type Generic type GenericFlag struct { Name string @@ -69,6 +79,16 @@ func (f *GenericFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *GenericFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *GenericFlag) IsVisible() bool { + return !f.Hidden +} + // Int64SliceFlag is a flag with type *Int64Slice type Int64SliceFlag struct { Name string @@ -98,6 +118,16 @@ func (f *Int64SliceFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *Int64SliceFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Int64SliceFlag) IsVisible() bool { + return !f.Hidden +} + // IntSliceFlag is a flag with type *IntSlice type IntSliceFlag struct { Name string @@ -127,6 +157,16 @@ func (f *IntSliceFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *IntSliceFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *IntSliceFlag) IsVisible() bool { + return !f.Hidden +} + // PathFlag is a flag with type Path type PathFlag struct { Name string @@ -163,6 +203,16 @@ func (f *PathFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *PathFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *PathFlag) IsVisible() bool { + return !f.Hidden +} + // StringSliceFlag is a flag with type *StringSlice type StringSliceFlag struct { Name string @@ -194,6 +244,16 @@ func (f *StringSliceFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *StringSliceFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *StringSliceFlag) IsVisible() bool { + return !f.Hidden +} + // TimestampFlag is a flag with type *Timestamp type TimestampFlag struct { Name string @@ -230,6 +290,16 @@ func (f *TimestampFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *TimestampFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *TimestampFlag) IsVisible() bool { + return !f.Hidden +} + // BoolFlag is a flag with type bool type BoolFlag struct { Name string @@ -264,6 +334,16 @@ func (f *BoolFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *BoolFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *BoolFlag) IsVisible() bool { + return !f.Hidden +} + // Float64Flag is a flag with type float64 type Float64Flag struct { Name string @@ -298,6 +378,16 @@ func (f *Float64Flag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *Float64Flag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Float64Flag) IsVisible() bool { + return !f.Hidden +} + // IntFlag is a flag with type int type IntFlag struct { Name string @@ -332,6 +422,16 @@ func (f *IntFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *IntFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *IntFlag) IsVisible() bool { + return !f.Hidden +} + // Int64Flag is a flag with type int64 type Int64Flag struct { Name string @@ -366,6 +466,16 @@ func (f *Int64Flag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *Int64Flag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Int64Flag) IsVisible() bool { + return !f.Hidden +} + // StringFlag is a flag with type string type StringFlag struct { Name string @@ -402,6 +512,16 @@ func (f *StringFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *StringFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *StringFlag) IsVisible() bool { + return !f.Hidden +} + // DurationFlag is a flag with type time.Duration type DurationFlag struct { Name string @@ -436,6 +556,16 @@ func (f *DurationFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *DurationFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *DurationFlag) IsVisible() bool { + return !f.Hidden +} + // UintFlag is a flag with type uint type UintFlag struct { Name string @@ -470,6 +600,16 @@ func (f *UintFlag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *UintFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *UintFlag) IsVisible() bool { + return !f.Hidden +} + // Uint64Flag is a flag with type uint64 type Uint64Flag struct { Name string @@ -504,4 +644,14 @@ func (f *Uint64Flag) Names() []string { return FlagNames(f.Name, f.Aliases) } +// IsRequired returns whether or not the flag is required +func (f *Uint64Flag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Uint64Flag) IsVisible() bool { + return !f.Hidden +} + // vim:ro diff --git a/zz_generated.flags_test.go b/zz_generated.flags_test.go index b8363d4..1d9afda 100644 --- a/zz_generated.flags_test.go +++ b/zz_generated.flags_test.go @@ -16,6 +16,18 @@ func TestFloat64SliceFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } +func TestFloat64SliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.Float64SliceFlag{} + + _ = f.IsRequired() +} + +func TestFloat64SliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.Float64SliceFlag{} + + _ = f.IsVisible() +} + func TestGenericFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.GenericFlag{} @@ -29,6 +41,18 @@ func TestGenericFlag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestGenericFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.GenericFlag{} + + _ = f.IsRequired() +} + +func TestGenericFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.GenericFlag{} + + _ = f.IsVisible() +} + func TestInt64SliceFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.Int64SliceFlag{} @@ -36,6 +60,18 @@ func TestInt64SliceFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } +func TestInt64SliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.Int64SliceFlag{} + + _ = f.IsRequired() +} + +func TestInt64SliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.Int64SliceFlag{} + + _ = f.IsVisible() +} + func TestIntSliceFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.IntSliceFlag{} @@ -43,6 +79,18 @@ func TestIntSliceFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } +func TestIntSliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.IntSliceFlag{} + + _ = f.IsRequired() +} + +func TestIntSliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.IntSliceFlag{} + + _ = f.IsVisible() +} + func TestPathFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.PathFlag{} @@ -56,6 +104,18 @@ func TestPathFlag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestPathFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.PathFlag{} + + _ = f.IsRequired() +} + +func TestPathFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.PathFlag{} + + _ = f.IsVisible() +} + func TestStringSliceFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.StringSliceFlag{} @@ -63,6 +123,18 @@ func TestStringSliceFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } +func TestStringSliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.StringSliceFlag{} + + _ = f.IsRequired() +} + +func TestStringSliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.StringSliceFlag{} + + _ = f.IsVisible() +} + func TestTimestampFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.TimestampFlag{} @@ -76,6 +148,18 @@ func TestTimestampFlag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestTimestampFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.TimestampFlag{} + + _ = f.IsRequired() +} + +func TestTimestampFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.TimestampFlag{} + + _ = f.IsVisible() +} + func TestBoolFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.BoolFlag{} @@ -89,6 +173,18 @@ func TestBoolFlag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestBoolFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.BoolFlag{} + + _ = f.IsRequired() +} + +func TestBoolFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.BoolFlag{} + + _ = f.IsVisible() +} + func TestFloat64Flag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.Float64Flag{} @@ -102,6 +198,18 @@ func TestFloat64Flag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestFloat64Flag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.Float64Flag{} + + _ = f.IsRequired() +} + +func TestFloat64Flag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.Float64Flag{} + + _ = f.IsVisible() +} + func TestIntFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.IntFlag{} @@ -115,6 +223,18 @@ func TestIntFlag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestIntFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.IntFlag{} + + _ = f.IsRequired() +} + +func TestIntFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.IntFlag{} + + _ = f.IsVisible() +} + func TestInt64Flag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.Int64Flag{} @@ -128,6 +248,18 @@ func TestInt64Flag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestInt64Flag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.Int64Flag{} + + _ = f.IsRequired() +} + +func TestInt64Flag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.Int64Flag{} + + _ = f.IsVisible() +} + func TestStringFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.StringFlag{} @@ -141,6 +273,18 @@ func TestStringFlag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestStringFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.StringFlag{} + + _ = f.IsRequired() +} + +func TestStringFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.StringFlag{} + + _ = f.IsVisible() +} + func TestDurationFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.DurationFlag{} @@ -154,6 +298,18 @@ func TestDurationFlag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestDurationFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.DurationFlag{} + + _ = f.IsRequired() +} + +func TestDurationFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.DurationFlag{} + + _ = f.IsVisible() +} + func TestUintFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.UintFlag{} @@ -167,6 +323,18 @@ func TestUintFlag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestUintFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.UintFlag{} + + _ = f.IsRequired() +} + +func TestUintFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.UintFlag{} + + _ = f.IsVisible() +} + func TestUint64Flag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.Uint64Flag{} @@ -180,4 +348,16 @@ func TestUint64Flag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestUint64Flag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.Uint64Flag{} + + _ = f.IsRequired() +} + +func TestUint64Flag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.Uint64Flag{} + + _ = f.IsVisible() +} + // vim:ro From c46856627277addb4291b2e7371119de440ff835 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 8 May 2022 20:49:08 -0400 Subject: [PATCH 086/125] Touching up more mkdocs details --- .github/workflows/cli.yml | 14 ++++++++++++++ .gitignore | 2 +- Makefile | 12 ++++++++++++ docs/CNAME | 1 + docs/CODE_OF_CONDUCT.md | 1 + docs/CONTRIBUTING.md | 22 ++++++++++++++++++++++ docs/index.md | 5 +++-- mkdocs-requirements.txt | 8 ++++---- 8 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 docs/CNAME create mode 120000 docs/CODE_OF_CONDUCT.md diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 84f9cae..6983404 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -92,3 +92,17 @@ jobs: run: | git diff --exit-code git diff --cached --exit-code + + publish: + if: startswith(github.ref, 'refs/tags/') + name: publish + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Publish Docs + uses: mhausenblas/mkdocs-deploy-gh-pages@master + env: + GITHUB_TOKEN: ${{ secrets.MKDOCS_PUBLISH_GITHUB_TOKEN }} + REQUIREMENTS: mkdocs-requirements.txt diff --git a/.gitignore b/.gitignore index e0c50ab..c04fcc5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,10 @@ *.coverprofile *.orig -node_modules/ vendor .idea internal/*/built-example coverage.txt /.local/ +/site/ *.exe diff --git a/Makefile b/Makefile index 52e9204..ba783cb 100644 --- a/Makefile +++ b/Makefile @@ -30,3 +30,15 @@ gfmrun: .PHONY: toc toc: go run internal/build/build.go toc docs/v2/manual.md + +.PHONY: docs +docs: + mkdocs build + +.PHONY: docs-deps +docs-deps: + pip install -r mkdocs-requirements.txt + +.PHONY: serve-docs +serve-docs: + mkdocs serve diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 0000000..8654f8f --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +cli.urfave.org diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md new file mode 120000 index 0000000..0400d57 --- /dev/null +++ b/docs/CODE_OF_CONDUCT.md @@ -0,0 +1 @@ +../CODE_OF_CONDUCT.md \ No newline at end of file diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 37055ff..7e80bdb 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -98,6 +98,28 @@ line help system which may be consulted for further information, e.g.: go run internal/genflags/cmd/genflags/main.go --help ``` +#### docs output + +The documentation in the `docs` directory is automatically built via `mkdocs` into a +static site and published when releases are pushed (see [RELEASING](./RELEASING/)). There +is no strict requirement to build the documentation when developing locally, but the +following `make` targets may be used if desired: + +```sh +# install documentation dependencies with `pip` +make docs-deps +``` + +```sh +# build the static site in `./site` +make docs +``` + +```sh +# start an mkdocs development server +make serve-docs +``` + ### pull requests Please feel free to open a pull request to fix a bug or add a feature. The @urfave/cli diff --git a/docs/index.md b/docs/index.md index d9d5d46..7927044 100644 --- a/docs/index.md +++ b/docs/index.md @@ -16,5 +16,6 @@ These are the guides for each major supported version: In addition to the version-specific guides, these other documents are available: -- [`CONTRIBUTING`](./CONTRIBUTING/) -- [`RELEASING`](./RELEASING/) +- [CONTRIBUTING](./CONTRIBUTING/) +- [CODE OF CONDUCT](./CODE_OF_CONDUCT/) +- [RELEASING](./RELEASING/) diff --git a/mkdocs-requirements.txt b/mkdocs-requirements.txt index 94dae76..1c2f874 100644 --- a/mkdocs-requirements.txt +++ b/mkdocs-requirements.txt @@ -1,4 +1,4 @@ -mkdocs -mkdocs-material -mkdocs-git-revision-date-localized-plugin -pygments +mkdocs~=1.3 +mkdocs-material~=8.2 +mkdocs-git-revision-date-localized-plugin~=1.0 +pygments~=2.12 From f3cf7640c7b846daeb4bedfb4b1e70334ffe8039 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 17 May 2022 08:49:15 -0400 Subject: [PATCH 087/125] Backing out build tag for suggestions per recommendation from @kolyshkin :bow: --- .github/workflows/cli.yml | 8 +------- Makefile | 4 ++-- README.md | 10 ---------- docs.go | 4 ++-- docs_test.go | 4 ++-- internal/build/build.go | 2 +- suggestions.go | 3 --- suggestions_stubs.go | 12 ------------ suggestions_test.go | 3 --- 9 files changed, 8 insertions(+), 42 deletions(-) delete mode 100644 suggestions_stubs.go diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index caab03d..a7c395a 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -37,15 +37,9 @@ jobs: - name: vet run: go run internal/build/build.go vet - - name: test with urfave_cli_core tag - run: go run internal/build/build.go -tags urfave_cli_core test - - name: test with urfave_cli_no_docs tag run: go run internal/build/build.go -tags urfave_cli_no_docs test - - name: test with urfave_cli_no_suggest tag - run: go run internal/build/build.go -tags urfave_cli_no_suggest test - - name: test run: go run internal/build/build.go test @@ -53,7 +47,7 @@ jobs: run: go run internal/build/build.go check-binary-size - name: check-binary-size with tags (informational only) - run: go run internal/build/build.go -tags urfave_cli_core check-binary-size + run: go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size - name: Upload coverage to Codecov if: success() && matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest' diff --git a/Makefile b/Makefile index 6ab812a..ba783cb 100644 --- a/Makefile +++ b/Makefile @@ -17,11 +17,11 @@ all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun t .PHONY: tag-test tag-test: - go run internal/build/build.go -tags urfave_cli_core test + go run internal/build/build.go -tags urfave_cli_no_docs test .PHONY: tag-check-binary-size tag-check-binary-size: - go run internal/build/build.go -tags urfave_cli_core check-binary-size + go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size .PHONY: gfmrun gfmrun: diff --git a/README.md b/README.md index 35694d2..beb5963 100644 --- a/README.md +++ b/README.md @@ -59,22 +59,12 @@ import ( You can use the following build tags: -#### `urfave_cli_core` - -When set, applies all `urfave_cli_no.+` build tags to minimize resulting binary -size. - #### `urfave_cli_no_docs` When set, this removes `ToMarkdown` and `ToMan` methods, so your application won't be able to call those. This reduces the resulting binary size by about 300-400 KB (measured using Go 1.18.1 on Linux/amd64), due to fewer dependencies. -#### `urfave_cli_no_suggest` - -When set, the capability enabled by setting `App.Suggest` will be a no-op. This -reduces the resulting binary size due to fewer dependencies. - ### GOPATH Make sure your `PATH` includes the `$GOPATH/bin` directory so your commands can diff --git a/docs.go b/docs.go index 4aba31b..8b1c9c8 100644 --- a/docs.go +++ b/docs.go @@ -1,5 +1,5 @@ -//go:build !urfave_cli_no_docs && !urfave_cli_core -// +build !urfave_cli_no_docs,!urfave_cli_core +//go:build !urfave_cli_no_docs +// +build !urfave_cli_no_docs package cli diff --git a/docs_test.go b/docs_test.go index bc3703f..12d5d3c 100644 --- a/docs_test.go +++ b/docs_test.go @@ -1,5 +1,5 @@ -//go:build !urfave_cli_no_docs && !urfave_cli_core -// +build !urfave_cli_no_docs,!urfave_cli_core +//go:build !urfave_cli_no_docs +// +build !urfave_cli_no_docs package cli diff --git a/internal/build/build.go b/internal/build/build.go index e74acfb..929ab0f 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -242,7 +242,7 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) { tags := c.String("tags") - if strings.Contains(tags, "urfave_cli_core") || strings.Contains(tags, "urfave_cli_no_docs") { + if strings.Contains(tags, "urfave_cli_no_docs") { desiredMinBinarySize = 1.39 } diff --git a/suggestions.go b/suggestions.go index 301c598..476af4d 100644 --- a/suggestions.go +++ b/suggestions.go @@ -1,6 +1,3 @@ -//go:build !urfave_cli_no_suggest && !urfave_cli_core -// +build !urfave_cli_no_suggest,!urfave_cli_core - package cli import ( diff --git a/suggestions_stubs.go b/suggestions_stubs.go deleted file mode 100644 index 48643bb..0000000 --- a/suggestions_stubs.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build urfave_cli_no_suggest || urfave_cli_core -// +build urfave_cli_no_suggest urfave_cli_core - -package cli - -func (a *App) suggestFlagFromError(err error, _ string) (string, error) { - return "", err -} - -func suggestCommand([]*Command, string) string { - return "" -} diff --git a/suggestions_test.go b/suggestions_test.go index fa7b0c9..4ebe9c0 100644 --- a/suggestions_test.go +++ b/suggestions_test.go @@ -1,6 +1,3 @@ -//go:build !urfave_cli_no_suggest && !urfave_cli_core -// +build !urfave_cli_no_suggest,!urfave_cli_core - package cli import ( From 68bd4903fd87f937c504c6dc44d3920cb0ecf11f Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 14 May 2022 08:24:44 -0400 Subject: [PATCH 088/125] Introduce override hooks for suggestions Related to https://github.com/urfave/cli/pull/1390#discussion_r871398659 --- app.go | 32 ++++++++++++++++++++++++++++++++ godoc-current.txt | 8 ++++++++ help.go | 2 +- suggestions.go | 30 +++--------------------------- suggestions_test.go | 5 ++--- testdata/godoc-v2.x.txt | 8 ++++++++ 6 files changed, 54 insertions(+), 31 deletions(-) diff --git a/app.go b/app.go index 463437a..45c5ab2 100644 --- a/app.go +++ b/app.go @@ -11,6 +11,8 @@ import ( "time" ) +const didYouMeanTemplate = "Did you mean '%s'?" + var ( changeLogURL = "https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md" appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL) @@ -18,6 +20,9 @@ var ( errInvalidActionType = NewExitError("ERROR invalid Action type. "+ fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+ fmt.Sprintf("See %s", appActionDeprecationURL), 2) + + SuggestFlag SuggestFlagFunc = suggestFlag + SuggestCommand SuggestCommandFunc = suggestCommand ) // App is the main structure of a cli application. It is recommended that @@ -100,6 +105,10 @@ type App struct { didSetup bool } +type SuggestFlagFunc func(flags []Flag, provided string, hideHelp bool) string + +type SuggestCommandFunc func(commands []*Command, provided string) string + // Tries to find out when this binary was compiled. // Returns the current time if it fails to find it. func compileTime() time.Time { @@ -332,6 +341,29 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { return err } +func (a *App) suggestFlagFromError(err error, command string) (string, error) { + flag, parseErr := flagFromError(err) + if parseErr != nil { + return "", err + } + + flags := a.Flags + if command != "" { + cmd := a.Command(command) + if cmd == nil { + return "", err + } + flags = cmd.Flags + } + + suggestion := SuggestFlag(flags, flag, a.HideHelp) + if len(suggestion) == 0 { + return "", err + } + + return fmt.Sprintf(didYouMeanTemplate+"\n\n", suggestion), nil +} + // RunAndExitOnError calls .Run() and exits non-zero if an error was returned // // Deprecated: instead you should return an error that fulfills cli.ExitCoder diff --git a/godoc-current.txt b/godoc-current.txt index 90700eb..e1879ee 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -26,6 +26,10 @@ application: VARIABLES +var ( + SuggestFlag SuggestFlagFunc = suggestFlag + SuggestCommand SuggestCommandFunc = suggestCommand +) var AppHelpTemplate = `NAME: {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} @@ -1539,6 +1543,10 @@ func (f *StringSliceFlag) String() string func (f *StringSliceFlag) TakesValue() bool TakesValue returns true of the flag takes a value, otherwise false +type SuggestCommandFunc func(commands []*Command, provided string) string + +type SuggestFlagFunc func(flags []Flag, provided string, hideHelp bool) string + type Timestamp struct { // Has unexported fields. } diff --git a/help.go b/help.go index 5103392..1ab7a6a 100644 --- a/help.go +++ b/help.go @@ -221,7 +221,7 @@ func ShowCommandHelp(ctx *Context, command string) error { if ctx.App.CommandNotFound == nil { errMsg := fmt.Sprintf("No help topic for '%v'", command) if ctx.App.Suggest { - if suggestion := suggestCommand(ctx.App.Commands, command); suggestion != "" { + if suggestion := SuggestCommand(ctx.App.Commands, command); suggestion != "" { errMsg += ". " + suggestion } } diff --git a/suggestions.go b/suggestions.go index 476af4d..8df0b32 100644 --- a/suggestions.go +++ b/suggestions.go @@ -6,37 +6,13 @@ import ( "github.com/antzucaro/matchr" ) -const didYouMeanTemplate = "Did you mean '%s'?" - -func (a *App) suggestFlagFromError(err error, command string) (string, error) { - flag, parseErr := flagFromError(err) - if parseErr != nil { - return "", err - } - - flags := a.Flags - if command != "" { - cmd := a.Command(command) - if cmd == nil { - return "", err - } - flags = cmd.Flags - } - - suggestion := a.suggestFlag(flags, flag) - if len(suggestion) == 0 { - return "", err - } - - return fmt.Sprintf(didYouMeanTemplate+"\n\n", suggestion), nil -} - -func (a *App) suggestFlag(flags []Flag, provided string) (suggestion string) { +func suggestFlag(flags []Flag, provided string, hideHelp bool) string { distance := 0.0 + suggestion := "" for _, flag := range flags { flagNames := flag.Names() - if !a.HideHelp { + if !hideHelp { flagNames = append(flagNames, HelpFlag.Names()...) } for _, name := range flagNames { diff --git a/suggestions_test.go b/suggestions_test.go index 4ebe9c0..c2a3d17 100644 --- a/suggestions_test.go +++ b/suggestions_test.go @@ -20,7 +20,7 @@ func TestSuggestFlag(t *testing.T) { {"s", "-s"}, } { // When - res := app.suggestFlag(app.Flags, testCase.provided) + res := suggestFlag(app.Flags, testCase.provided, false) // Then expect(t, res, testCase.expected) @@ -30,10 +30,9 @@ func TestSuggestFlag(t *testing.T) { func TestSuggestFlagHideHelp(t *testing.T) { // Given app := testApp() - app.HideHelp = true // When - res := app.suggestFlag(app.Flags, "hlp") + res := suggestFlag(app.Flags, "hlp", true) // Then expect(t, res, "--fl") diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt index 90700eb..e1879ee 100644 --- a/testdata/godoc-v2.x.txt +++ b/testdata/godoc-v2.x.txt @@ -26,6 +26,10 @@ application: VARIABLES +var ( + SuggestFlag SuggestFlagFunc = suggestFlag + SuggestCommand SuggestCommandFunc = suggestCommand +) var AppHelpTemplate = `NAME: {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} @@ -1539,6 +1543,10 @@ func (f *StringSliceFlag) String() string func (f *StringSliceFlag) TakesValue() bool TakesValue returns true of the flag takes a value, otherwise false +type SuggestCommandFunc func(commands []*Command, provided string) string + +type SuggestFlagFunc func(flags []Flag, provided string, hideHelp bool) string + type Timestamp struct { // Has unexported fields. } From 5bb9f453dceb1ddb3496edfd734aa80077c024ee Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 17 May 2022 09:24:17 -0400 Subject: [PATCH 089/125] Also make the did-you-mean template pluggable --- app.go | 9 +++++---- godoc-current.txt | 5 +++-- suggestions.go | 2 +- suggestions_test.go | 8 ++++---- testdata/godoc-v2.x.txt | 5 +++-- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/app.go b/app.go index 45c5ab2..dd7c9e4 100644 --- a/app.go +++ b/app.go @@ -11,7 +11,7 @@ import ( "time" ) -const didYouMeanTemplate = "Did you mean '%s'?" +const suggestDidYouMeanTemplate = "Did you mean %q?" var ( changeLogURL = "https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md" @@ -21,8 +21,9 @@ var ( fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+ fmt.Sprintf("See %s", appActionDeprecationURL), 2) - SuggestFlag SuggestFlagFunc = suggestFlag - SuggestCommand SuggestCommandFunc = suggestCommand + SuggestFlag SuggestFlagFunc = suggestFlag + SuggestCommand SuggestCommandFunc = suggestCommand + SuggestDidYouMeanTemplate string = suggestDidYouMeanTemplate ) // App is the main structure of a cli application. It is recommended that @@ -361,7 +362,7 @@ func (a *App) suggestFlagFromError(err error, command string) (string, error) { return "", err } - return fmt.Sprintf(didYouMeanTemplate+"\n\n", suggestion), nil + return fmt.Sprintf(SuggestDidYouMeanTemplate+"\n\n", suggestion), nil } // RunAndExitOnError calls .Run() and exits non-zero if an error was returned diff --git a/godoc-current.txt b/godoc-current.txt index e1879ee..acdac5e 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -27,8 +27,9 @@ application: VARIABLES var ( - SuggestFlag SuggestFlagFunc = suggestFlag - SuggestCommand SuggestCommandFunc = suggestCommand + SuggestFlag SuggestFlagFunc = suggestFlag + SuggestCommand SuggestCommandFunc = suggestCommand + SuggestDidYouMeanTemplate string = suggestDidYouMeanTemplate ) var AppHelpTemplate = `NAME: {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} diff --git a/suggestions.go b/suggestions.go index 8df0b32..65bb3cf 100644 --- a/suggestions.go +++ b/suggestions.go @@ -47,5 +47,5 @@ func suggestCommand(commands []*Command, provided string) (suggestion string) { } } - return fmt.Sprintf(didYouMeanTemplate, suggestion) + return fmt.Sprintf(SuggestDidYouMeanTemplate, suggestion) } diff --git a/suggestions_test.go b/suggestions_test.go index c2a3d17..4c0984e 100644 --- a/suggestions_test.go +++ b/suggestions_test.go @@ -56,7 +56,7 @@ func TestSuggestFlagFromError(t *testing.T) { ) // Then - expect(t, res, fmt.Sprintf(didYouMeanTemplate+"\n\n", testCase.expected)) + expect(t, res, fmt.Sprintf(SuggestDidYouMeanTemplate+"\n\n", testCase.expected)) } } @@ -116,7 +116,7 @@ func TestSuggestCommand(t *testing.T) { res := suggestCommand(app.Commands, testCase.provided) // Then - expect(t, res, fmt.Sprintf(didYouMeanTemplate, testCase.expected)) + expect(t, res, fmt.Sprintf(SuggestDidYouMeanTemplate, testCase.expected)) } } @@ -140,7 +140,7 @@ func ExampleApp_Suggest() { // Output: // Incorrect Usage. flag provided but not defined: -nema // - // Did you mean '--name'? + // Did you mean "--name"? // // (this space intentionally left blank) } @@ -181,7 +181,7 @@ func ExampleApp_Suggest_command() { // Output: // Incorrect Usage: flag provided but not defined: -sliming // - // Did you mean '--smiling'? + // Did you mean "--smiling"? // // (this space intentionally left blank) } diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt index e1879ee..acdac5e 100644 --- a/testdata/godoc-v2.x.txt +++ b/testdata/godoc-v2.x.txt @@ -27,8 +27,9 @@ application: VARIABLES var ( - SuggestFlag SuggestFlagFunc = suggestFlag - SuggestCommand SuggestCommandFunc = suggestCommand + SuggestFlag SuggestFlagFunc = suggestFlag + SuggestCommand SuggestCommandFunc = suggestCommand + SuggestDidYouMeanTemplate string = suggestDidYouMeanTemplate ) var AppHelpTemplate = `NAME: {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} From 061250ade5928b4d48941efc8b1e828b8a651a1e Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 18 May 2022 22:30:07 -0400 Subject: [PATCH 090/125] Use correctly spelled repo name in mkdocs config --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index c963d37..73b88c5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -6,7 +6,7 @@ site_name: urfave/cli site_url: https://cli.urfave.org/ -repo_url: https://github.com/urvafe/cli +repo_url: https://github.com/urfave/cli edit_uri: edit/main/docs/ nav: - Home: index.md From 4536c8742d626bba5d2678ff52bd1493e07cc693 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 18 May 2022 22:34:28 -0400 Subject: [PATCH 091/125] Point to the docs tree and site from top-level README --- README.md | 73 +++---------------------------------------------------- 1 file changed, 4 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index beb5963..eaed356 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -cli -=== +# 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) @@ -10,74 +9,10 @@ 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. -## Usage Documentation +## Documentation -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) - -Guides for migrating to newer versions: - -- `v1-to-v2` - [./docs/migrate-v1-to-v2.md](./docs/migrate-v1-to-v2.md) - -## Installation - -Using this package requires a working Go environment. [See the install instructions for Go](http://golang.org/doc/install.html). - -Go Modules are required when using this package. [See the go blog guide on using Go Modules](https://blog.golang.org/using-go-modules). - -### Using `v2` releases - -``` -$ go get github.com/urfave/cli/v2 -``` - -```go -... -import ( - "github.com/urfave/cli/v2" // imports as package "cli" -) -... -``` - -### Using `v1` releases - -``` -$ go get github.com/urfave/cli -``` - -```go -... -import ( - "github.com/urfave/cli" -) -... -``` - -### Build tags - -You can use the following build tags: - -#### `urfave_cli_no_docs` - -When set, this removes `ToMarkdown` and `ToMan` methods, so your application -won't be able to call those. This reduces the resulting binary size by about -300-400 KB (measured using Go 1.18.1 on Linux/amd64), due to fewer dependencies. - -### GOPATH - -Make sure your `PATH` includes the `$GOPATH/bin` directory so your commands can -be easily used: -``` -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. 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/main/.github/workflows/cli.yml). +More documentation is available in [`./docs`](./docs) or the hosted +documentation site at . ## License From 660d25f14d0ab72c9573208177ce05a45f438be4 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 18 May 2022 22:37:44 -0400 Subject: [PATCH 092/125] Un-delete non-redundant docs --- docs/index.md | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/docs/index.md b/docs/index.md index 7927044..5f02fed 100644 --- a/docs/index.md +++ b/docs/index.md @@ -19,3 +19,55 @@ In addition to the version-specific guides, these other documents are available: - [CONTRIBUTING](./CONTRIBUTING/) - [CODE OF CONDUCT](./CODE_OF_CONDUCT/) - [RELEASING](./RELEASING/) + +## Installation + +Using this package requires a working Go environment. [See the install instructions for Go](http://golang.org/doc/install.html). + +Go Modules are required when using this package. [See the go blog guide on using Go Modules](https://blog.golang.org/using-go-modules). + +### Using `v2` releases + +``` +$ go get github.com/urfave/cli/v2 +``` + +```go +... +import ( + "github.com/urfave/cli/v2" // imports as package "cli" +) +... +``` + +### Using `v1` releases + +``` +$ go get github.com/urfave/cli +``` + +```go +... +import ( + "github.com/urfave/cli" +) +... +``` + +### Build tags + +You can use the following build tags: + +#### `urfave_cli_no_docs` + +When set, this removes `ToMarkdown` and `ToMan` methods, so your application +won't be able to call those. This reduces the resulting binary size by about +300-400 KB (measured using Go 1.18.1 on Linux/amd64), due to fewer dependencies. + +### 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. This project uses Github Actions +for builds. To see our currently supported go versions and platforms, look at +the [github workflow +configuration](https://github.com/urfave/cli/blob/main/.github/workflows/cli.yml). From 522b7e0d929530d0a753bac01742028585c68473 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 18 May 2022 22:46:36 -0400 Subject: [PATCH 093/125] Use existing secret name in docs publish step --- .github/workflows/cli.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index a7c395a..61c9474 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -94,7 +94,7 @@ jobs: git diff --cached --exit-code publish: - if: startswith(github.ref, 'refs/tags/') + #if: startswith(github.ref, 'refs/tags/') name: publish runs-on: ubuntu-latest steps: @@ -104,5 +104,5 @@ jobs: - name: Publish Docs uses: mhausenblas/mkdocs-deploy-gh-pages@master env: - GITHUB_TOKEN: ${{ secrets.MKDOCS_PUBLISH_GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} REQUIREMENTS: mkdocs-requirements.txt From 70e1ed4c9d8f80940cbfc3c7328ecb626e709cc2 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 18 May 2022 22:58:46 -0400 Subject: [PATCH 094/125] Attempt to work around materialx yaml loader boom --- .github/workflows/cli.yml | 1 + mkdocs-requirements.txt | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 61c9474..77840f7 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -96,6 +96,7 @@ jobs: publish: #if: startswith(github.ref, 'refs/tags/') name: publish + needs: [test-docs] runs-on: ubuntu-latest steps: - name: Checkout Code diff --git a/mkdocs-requirements.txt b/mkdocs-requirements.txt index 1c2f874..482ad06 100644 --- a/mkdocs-requirements.txt +++ b/mkdocs-requirements.txt @@ -1,4 +1,5 @@ -mkdocs~=1.3 -mkdocs-material~=8.2 mkdocs-git-revision-date-localized-plugin~=1.0 +mkdocs-material-extensions~=1.0 +mkdocs-material~=8.2 +mkdocs~=1.3 pygments~=2.12 From 5988cc604001d76f5a32195f7a3390dfbd3e3570 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 18 May 2022 23:13:45 -0400 Subject: [PATCH 095/125] Try running mkdocs steps directly --- .github/workflows/cli.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 77840f7..cf88e91 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -12,6 +12,7 @@ on: jobs: test: + if: false strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] @@ -102,8 +103,13 @@ jobs: - name: Checkout Code uses: actions/checkout@v3 + - name: Setup mkdocs + run: | + pip install -U pip + pip install -r mkdocs-requirements.txt + git remote rm origin + git remote add origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/urfave/cli.git + - name: Publish Docs - uses: mhausenblas/mkdocs-deploy-gh-pages@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REQUIREMENTS: mkdocs-requirements.txt + run: | + mkdocs gh-deploy --force From e592640fdb667f72992236770f1c76551b66b9b2 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 18 May 2022 23:17:57 -0400 Subject: [PATCH 096/125] Unshallow the clone + toc cleanups --- .github/workflows/cli.yml | 14 ++++++-------- Makefile | 6 +----- internal/build/build.go | 13 ------------- 3 files changed, 7 insertions(+), 26 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index cf88e91..cedc051 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -77,18 +77,14 @@ jobs: uses: actions/checkout@v3 - name: Install Dependencies - run: - mkdir -p "${GITHUB_WORKSPACE}/.local/bin" && - curl -fsSL -o "${GITHUB_WORKSPACE}/.local/bin/gfmrun" "https://github.com/urfave/gfmrun/releases/download/v1.3.0/gfmrun-$(go env GOOS)-$(go env GOARCH)-v1.3.0" && - chmod +x "${GITHUB_WORKSPACE}/.local/bin/gfmrun" && - npm install -g markdown-toc@1.2.0 + run: > + mkdir -p "${GITHUB_WORKSPACE}/.local/bin" + curl -fsSL -o "${GITHUB_WORKSPACE}/.local/bin/gfmrun" "https://github.com/urfave/gfmrun/releases/download/v1.3.0/gfmrun-$(go env GOOS)-$(go env GOARCH)-v1.3.0" + chmod +x "${GITHUB_WORKSPACE}/.local/bin/gfmrun" - name: gfmrun run: go run internal/build/build.go gfmrun docs/v2/manual.md - - name: toc - run: go run internal/build/build.go toc docs/v2/manual.md - - name: diff check run: | git diff --exit-code @@ -102,6 +98,8 @@ jobs: steps: - name: Checkout Code uses: actions/checkout@v3 + with: + fetch-depth: 0 - name: Setup mkdocs run: | diff --git a/Makefile b/Makefile index ba783cb..3b0e5e0 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ # attention on files that are primarily Go. .PHONY: all -all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun toc v2diff +all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun v2diff # NOTE: this is a special catch-all rule to run any of the commands # defined in internal/build/build.go with optional arguments passed @@ -27,10 +27,6 @@ tag-check-binary-size: gfmrun: go run internal/build/build.go gfmrun docs/v2/manual.md -.PHONY: toc -toc: - go run internal/build/build.go toc docs/v2/manual.md - .PHONY: docs docs: mkdocs build diff --git a/internal/build/build.go b/internal/build/build.go index 929ab0f..2696760 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -63,10 +63,6 @@ func main() { Name: "gfmrun", Action: GfmrunActionFunc, }, - { - Name: "toc", - Action: TocActionFunc, - }, { Name: "check-binary-size", Action: checkBinarySizeActionFunc, @@ -216,15 +212,6 @@ func GfmrunActionFunc(c *cli.Context) error { return runCmd("gfmrun", "-c", fmt.Sprint(counter), "-s", filename) } -func TocActionFunc(c *cli.Context) error { - filename := c.Args().Get(0) - if filename == "" { - filename = "README.md" - } - - return runCmd("markdown-toc", "-i", filename) -} - // checkBinarySizeActionFunc checks the size of an example binary to ensure that we are keeping size down // this was originally inspired by https://github.com/urfave/cli/issues/1055, and followed up on as a part // of https://github.com/urfave/cli/issues/1057 From e7f3925a5ab86b2df8f7c3b19d5f48651553c3ce Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 18 May 2022 23:19:10 -0400 Subject: [PATCH 097/125] Use correct yaml multiline :facepalm: --- .github/workflows/cli.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index cedc051..7c55139 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -77,7 +77,7 @@ jobs: uses: actions/checkout@v3 - name: Install Dependencies - run: > + 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" From b3359c3e2754318c4b52948c8779d41c37b9e1cc Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 18 May 2022 23:20:47 -0400 Subject: [PATCH 098/125] Re-enable workflow conditions --- .github/workflows/cli.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 7c55139..3b31afc 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -12,7 +12,6 @@ on: jobs: test: - if: false strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] @@ -91,7 +90,7 @@ jobs: git diff --cached --exit-code publish: - #if: startswith(github.ref, 'refs/tags/') + if: startswith(github.ref, 'refs/tags/') name: publish needs: [test-docs] runs-on: ubuntu-latest From e66017d73a69165ac6216f85dffed3d2cc78c68c Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2022 09:07:03 -0400 Subject: [PATCH 099/125] Refinements to removal of zsh hack --- app_test.go | 5 ++++- autocomplete/zsh_autocomplete | 30 +++++++++++++++++------------- docs/v2/manual.md | 8 ++++---- help_test.go | 4 ++++ 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/app_test.go b/app_test.go index 5f20078..26ae28a 100644 --- a/app_test.go +++ b/app_test.go @@ -228,6 +228,7 @@ func ExampleApp_Run_subcommandNoAction() { } func ExampleApp_Run_bashComplete_withShortFlag() { + os.Setenv("SHELL", "bash") os.Args = []string{"greet", "-", "--generate-bash-completion"} app := NewApp() @@ -255,6 +256,7 @@ func ExampleApp_Run_bashComplete_withShortFlag() { } func ExampleApp_Run_bashComplete_withLongFlag() { + os.Setenv("SHELL", "bash") os.Args = []string{"greet", "--s", "--generate-bash-completion"} app := NewApp() @@ -283,6 +285,7 @@ func ExampleApp_Run_bashComplete_withLongFlag() { // --similar-flag } func ExampleApp_Run_bashComplete_withMultipleLongFlag() { + os.Setenv("SHELL", "bash") os.Args = []string{"greet", "--st", "--generate-bash-completion"} app := NewApp() @@ -315,7 +318,7 @@ func ExampleApp_Run_bashComplete_withMultipleLongFlag() { } func ExampleApp_Run_bashComplete() { - // set args for examples sake + os.Setenv("SHELL", "bash") os.Args = []string{"greet", "--generate-bash-completion"} app := &App{ diff --git a/autocomplete/zsh_autocomplete b/autocomplete/zsh_autocomplete index ee1b562..b519666 100644 --- a/autocomplete/zsh_autocomplete +++ b/autocomplete/zsh_autocomplete @@ -1,16 +1,20 @@ #compdef $PROG -local -a opts -local cur -cur=${words[-1]} -if [[ "$cur" == "-"* ]]; then - opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") -else - opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}") -fi +_cli_zsh_autocomplete() { + local -a opts + local cur + cur=${words[-1]} + if [[ "$cur" == "-"* ]]; then + opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") + else + opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}") + fi -if [[ "${opts[1]}" != "" ]]; then - _describe 'values' opts -else - _files -fi + if [[ "${opts[1]}" != "" ]]; then + _describe 'values' opts + else + _files + fi +} + +compdef _cli_zsh_autocomplete $PROG diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 3d96bb8..fd5656a 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -1165,10 +1165,10 @@ func main() { ``` #### ZSH Support -Auto-completion for ZSH is also supported using the `autocomplete/zsh_autocomplete` -file included in this repo. One environment variable is used, `PROG`. Set -`PROG` to the program name as before, and then `source path/to/autocomplete/zsh_autocomplete`. -Adding the following lines to your ZSH configuration file (usually `.zshrc`) +Auto-completion for ZSH is also supported using the `autocomplete/zsh_autocomplete` +file included in this repo. One environment variable is used, `PROG`. Set +`PROG` to the program name as before, and then `source path/to/autocomplete/zsh_autocomplete`. +Adding the following lines to your ZSH configuration file (usually `.zshrc`) will allow the auto-completion to persist across new shells: ``` diff --git a/help_test.go b/help_test.go index 98530dd..17a263d 100644 --- a/help_test.go +++ b/help_test.go @@ -1040,12 +1040,16 @@ func TestHideHelpCommand_WithSubcommands(t *testing.T) { } func TestDefaultCompleteWithFlags(t *testing.T) { + origEnv := os.Environ() origArgv := os.Args t.Cleanup(func() { os.Args = origArgv + resetEnv(origEnv) }) + os.Setenv("SHELL", "bash") + for _, tc := range []struct { name string c *Context From 5edc1b95e8367c51ffa9cdeeccf005f8770a7307 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 22 May 2022 22:18:00 -0400 Subject: [PATCH 100/125] Run docs tests against current work tree by creating a temporary go workspace to be used by gfmrun via `TMPDIR`. --- docs/v2/manual.md | 2 +- internal/build/build.go | 39 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index fd5656a..9ea6d53 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -363,7 +363,7 @@ For example this: ``` go package main diff --git a/internal/build/build.go b/internal/build/build.go index 2696760..ab4547e 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -179,8 +179,37 @@ func testCleanup(packages []string) error { return os.WriteFile("coverage.txt", out.Bytes(), 0644) } -func GfmrunActionFunc(c *cli.Context) error { - filename := c.Args().Get(0) +func GfmrunActionFunc(cCtx *cli.Context) error { + top := cCtx.Path("top") + + tmpDir, err := os.MkdirTemp("", "urfave-cli*") + if err != nil { + return err + } + + wd, err := os.Getwd() + if err != nil { + return err + } + + if err := os.Chdir(tmpDir); err != nil { + return err + } + + fmt.Fprintf(cCtx.App.ErrWriter, "# ---> workspace/TMPDIR is %q\n", tmpDir) + + if err := runCmd("go", "work", "init", top); err != nil { + return err + } + + os.Setenv("TMPDIR", tmpDir) + os.Setenv("SHELL", "bash") + + if err := os.Chdir(wd); err != nil { + return err + } + + filename := cCtx.Args().Get(0) if filename == "" { filename = "README.md" } @@ -209,7 +238,11 @@ func GfmrunActionFunc(c *cli.Context) error { return err } - return runCmd("gfmrun", "-c", fmt.Sprint(counter), "-s", filename) + if err := runCmd("gfmrun", "-c", fmt.Sprint(counter), "-s", filename); err != nil { + return err + } + + return os.RemoveAll(tmpDir) } // checkBinarySizeActionFunc checks the size of an example binary to ensure that we are keeping size down From 8f47e6dc97e4e04e99fbfaf1c6ea0d16ad8515d3 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 23 May 2022 07:44:58 -0400 Subject: [PATCH 101/125] Set absolute `bash` path and slightly revert assertion in docs test to ensure flag ordering --- docs/v2/manual.md | 2 +- internal/build/build.go | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 9ea6d53..7208ae6 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -363,7 +363,7 @@ For example this: ``` go package main diff --git a/internal/build/build.go b/internal/build/build.go index ab4547e..d94dae7 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -182,6 +182,13 @@ func testCleanup(packages []string) error { func GfmrunActionFunc(cCtx *cli.Context) error { top := cCtx.Path("top") + bash, err := exec.LookPath("bash") + if err != nil { + return err + } + + os.Setenv("SHELL", bash) + tmpDir, err := os.MkdirTemp("", "urfave-cli*") if err != nil { return err @@ -203,7 +210,6 @@ func GfmrunActionFunc(cCtx *cli.Context) error { } os.Setenv("TMPDIR", tmpDir) - os.Setenv("SHELL", "bash") if err := os.Chdir(wd); err != nil { return err From 4fd56cb22d4640b243dbb9a77c0b92d72a92ac87 Mon Sep 17 00:00:00 2001 From: Mostyn Bramley-Moore Date: Mon, 23 May 2022 08:08:02 +0200 Subject: [PATCH 102/125] Add word-wrap support, with wrap length provided by the user We could try to automatically detect the terminal width and wrap at that point, but this would increase the binary footprint for all users even if not using this feature. Instead, we can allow users to specify their preferred line length limit (if any), and those who want to bear the cost of checking the terminal size can do so if they wish. This also makes the feature more testable. Original patch by Sascha Grunert --- help.go | 74 +++++++++++++++++ help_test.go | 222 +++++++++++++++++++++++++++++++++++++++++++++++++++ template.go | 20 ++--- 3 files changed, 306 insertions(+), 10 deletions(-) diff --git a/help.go b/help.go index ff59ddc..9a8d243 100644 --- a/help.go +++ b/help.go @@ -64,6 +64,11 @@ var HelpPrinter helpPrinter = printHelp // HelpPrinterCustom is a function that writes the help output. It is used as // the default implementation of HelpPrinter, and may be called directly if // the ExtraInfo field is set on an App. +// +// In the default implementation, if the customFuncs argument contains a +// "wrapAt" key, which is a function which takes no arguments and returns +// an int, this int value will be used to produce a "wrap" function used +// by the default template to wrap long lines. var HelpPrinterCustom helpPrinterCustom = printHelpCustom // VersionPrinter prints the version for the App @@ -286,12 +291,29 @@ func ShowCommandCompletions(ctx *Context, command string) { // The customFuncs map will be combined with a default template.FuncMap to // allow using arbitrary functions in template rendering. func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs map[string]interface{}) { + + const maxLineLength = 10000 + funcMap := template.FuncMap{ "join": strings.Join, "indent": indent, "nindent": nindent, "trim": strings.TrimSpace, + "wrap": func(input string, offset int) string { return wrap(input, offset, maxLineLength) }, + "offset": offset, } + + if customFuncs["wrapAt"] != nil { + if wa, ok := customFuncs["wrapAt"]; ok { + if waf, ok := wa.(func() int); ok { + wrapAt := waf() + customFuncs["wrap"] = func(input string, offset int) string { + return wrap(input, offset, wrapAt) + } + } + } + } + for key, value := range customFuncs { funcMap[key] = value } @@ -402,3 +424,55 @@ func indent(spaces int, v string) string { func nindent(spaces int, v string) string { return "\n" + indent(spaces, v) } + +func wrap(input string, offset int, wrapAt int) string { + var sb strings.Builder + + lines := strings.Split(input, "\n") + + padding := strings.Repeat(" ", offset) + + for i, line := range lines { + if i != 0 { + sb.WriteString(padding) + } + + sb.WriteString(wrapLine(line, offset, wrapAt, padding)) + + if i != len(lines)-1 { + sb.WriteString("\n") + } + } + + return sb.String() +} + +func wrapLine(input string, offset int, wrapAt int, padding string) string { + if wrapAt <= offset || len(input) <= wrapAt-offset { + return input + } + + lineWidth := wrapAt - offset + words := strings.Fields(input) + if len(words) == 0 { + return input + } + + wrapped := words[0] + spaceLeft := lineWidth - len(wrapped) + for _, word := range words[1:] { + if len(word)+1 > spaceLeft { + wrapped += "\n" + padding + word + spaceLeft = lineWidth - len(word) + } else { + wrapped += " " + word + spaceLeft -= 1 + len(word) + } + } + + return wrapped +} + +func offset(input string, fixed int) int { + return len(input) + fixed +} diff --git a/help_test.go b/help_test.go index 17a263d..4feb7f0 100644 --- a/help_test.go +++ b/help_test.go @@ -1124,3 +1124,225 @@ func TestDefaultCompleteWithFlags(t *testing.T) { }) } } + +func TestWrappedHelp(t *testing.T) { + + // Reset HelpPrinter after this test. + defer func(old helpPrinter) { + HelpPrinter = old + }(HelpPrinter) + + output := new(bytes.Buffer) + app := &App{ + Writer: output, + Flags: []Flag{ + &BoolFlag{Name: "foo", + Aliases: []string{"h"}, + Usage: "here's a really long help text line, let's see where it wraps. blah blah blah and so on.", + }, + }, + Usage: "here's a sample App.Usage string long enough that it should be wrapped in this test", + UsageText: "i'm not sure how App.UsageText differs from App.Usage, but this should also be wrapped in this test", + // TODO: figure out how to make ArgsUsage appear in the help text, and test that + Description: `here's a sample App.Description string long enough that it should be wrapped in this test + +with a newline + and an indented line`, + Copyright: `Here's a sample copyright text string long enough that it should be wrapped. +Including newlines. + And also indented lines. + + +And then another long line. Blah blah blah does anybody ever read these things?`, + } + + c := NewContext(app, nil, nil) + + HelpPrinter = func(w io.Writer, templ string, data interface{}) { + funcMap := map[string]interface{}{ + "wrapAt": func() int { + return 30 + }, + } + + HelpPrinterCustom(w, templ, data, funcMap) + } + + _ = ShowAppHelp(c) + + expected := `NAME: + - here's a sample + App.Usage string long + enough that it should be + wrapped in this test + +USAGE: + i'm not sure how + App.UsageText differs from + App.Usage, but this should + also be wrapped in this + test + +DESCRIPTION: + here's a sample + App.Description string long + enough that it should be + wrapped in this test + + with a newline + and an indented line + +GLOBAL OPTIONS: + --foo, -h here's a + really long help text + line, let's see where it + wraps. blah blah blah + and so on. (default: + false) + +COPYRIGHT: + Here's a sample copyright + text string long enough + that it should be wrapped. + Including newlines. + And also indented lines. + + + And then another long line. + Blah blah blah does anybody + ever read these things? +` + + if output.String() != expected { + t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s", + output.String(), expected) + } +} + +func TestWrappedCommandHelp(t *testing.T) { + + // Reset HelpPrinter after this test. + defer func(old helpPrinter) { + HelpPrinter = old + }(HelpPrinter) + + output := new(bytes.Buffer) + app := &App{ + Writer: output, + Commands: []*Command{ + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + UsageText: "this is an even longer way of describing adding a task to the list", + Description: "and a description long enough to wrap in this test case", + Action: func(c *Context) error { + return nil + }, + }, + }, + } + + c := NewContext(app, nil, nil) + + HelpPrinter = func(w io.Writer, templ string, data interface{}) { + funcMap := map[string]interface{}{ + "wrapAt": func() int { + return 30 + }, + } + + HelpPrinterCustom(w, templ, data, funcMap) + } + + _ = ShowCommandHelp(c, "add") + + expected := `NAME: + - add a task to the list + +USAGE: + this is an even longer way + of describing adding a task + to the list + +DESCRIPTION: + and a description long + enough to wrap in this test + case +` + + if output.String() != expected { + t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s", + output.String(), expected) + } +} + +func TestWrappedSubcommandHelp(t *testing.T) { + + // Reset HelpPrinter after this test. + defer func(old helpPrinter) { + HelpPrinter = old + }(HelpPrinter) + + output := new(bytes.Buffer) + app := &App{ + Name: "cli.test", + Writer: output, + Commands: []*Command{ + { + Name: "bar", + Aliases: []string{"a"}, + Usage: "add a task to the list", + UsageText: "this is an even longer way of describing adding a task to the list", + Description: "and a description long enough to wrap in this test case", + Action: func(c *Context) error { + return nil + }, + Subcommands: []*Command{ + { + Name: "grok", + Usage: "remove an existing template", + UsageText: "longer usage text goes here, la la la, hopefully this is long enough to wrap even more", + Action: func(c *Context) error { + return nil + }, + }, + }, + }, + }, + } + + HelpPrinter = func(w io.Writer, templ string, data interface{}) { + funcMap := map[string]interface{}{ + "wrapAt": func() int { + return 30 + }, + } + + HelpPrinterCustom(w, templ, data, funcMap) + } + + _ = app.Run([]string{"foo", "bar", "grok", "--help"}) + + expected := `NAME: + cli.test bar grok - remove + an + existing + template + +USAGE: + longer usage text goes + here, la la la, hopefully + this is long enough to wrap + even more + +OPTIONS: + --help, -h show help (default: false) + +` + + if output.String() != expected { + t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s", + output.String(), expected) + } +} diff --git a/template.go b/template.go index 264eb85..f3116fd 100644 --- a/template.go +++ b/template.go @@ -4,16 +4,16 @@ package cli // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. var AppHelpTemplate = `NAME: - {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} + {{$v := offset .Name 6}}{{wrap .Name 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}} USAGE: - {{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}} + {{if .UsageText}}{{wrap .UsageText 3}}{{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 | nindent 3 | trim}}{{end}}{{if len .Authors}} + {{wrap .Description 3}}{{end}}{{if len .Authors}} AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: {{range $index, $author := .Authors}}{{if $index}} @@ -31,26 +31,26 @@ GLOBAL OPTIONS:{{range .VisibleFlagCategories}} GLOBAL OPTIONS: {{range $index, $option := .VisibleFlags}}{{if $index}} - {{end}}{{$option}}{{end}}{{end}}{{end}}{{if .Copyright}} + {{end}}{{wrap $option.String 6}}{{end}}{{end}}{{end}}{{if .Copyright}} COPYRIGHT: - {{.Copyright}}{{end}} + {{wrap .Copyright 3}}{{end}} ` // CommandHelpTemplate is the text template for the command help topic. // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. var CommandHelpTemplate = `NAME: - {{.HelpName}} - {{.Usage}} + {{$v := offset .HelpName 6}}{{wrap .HelpName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}} USAGE: - {{if .UsageText}}{{.UsageText | nindent 3 | trim}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} + {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} CATEGORY: {{.Category}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlagCategories}} + {{wrap .Description 3}}{{end}}{{if .VisibleFlagCategories}} OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} @@ -69,10 +69,10 @@ var SubcommandHelpTemplate = `NAME: {{.HelpName}} - {{.Usage}} USAGE: - {{if .UsageText}}{{.UsageText | nindent 3 | trim}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}} + {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description | nindent 3 | trim}}{{end}} + {{wrap .Description 3}}{{end}} COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{range .VisibleCommands}} From eecfd0046bd8f0694f59ed7f96fbfce29775fa8d Mon Sep 17 00:00:00 2001 From: Mostyn Bramley-Moore Date: Mon, 23 May 2022 08:06:57 +0200 Subject: [PATCH 103/125] Fix 'repetetive' typo --- docs/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 7e80bdb..b0d9bfc 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -85,7 +85,7 @@ step. #### generated code A significant portion of the project's source code is generated, with the goal being to -eliminate repetetive maintenance where other type-safe abstraction is impractical or +eliminate repetitive maintenance where other type-safe abstraction is impractical or impossible with Go versions `< 1.18`. In a future where the eldest Go version supported is `1.18.x`, there will likely be efforts to take advantage of [generics](https://go.dev/doc/tutorial/generics). From a14bd76b6ba5a82f8166cfbc06e95cdb3c6a14f6 Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Tue, 24 May 2022 14:49:30 +0900 Subject: [PATCH 104/125] Remove GPL2 dependency introduced in v2.7.0 This commit replaces github.com/antzucaro/matchr (GPL2, introduced in v2.7.0) with github.com/xrash/smetrics (MIT License). Fixes issue 1404 Signed-off-by: Akihiro Suda --- go.mod | 2 +- go.sum | 12 ++---------- suggestions.go | 15 ++++++++++++--- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index f9111b0..6343421 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.18 require ( github.com/BurntSushi/toml v1.1.0 - github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0 github.com/cpuguy83/go-md2man/v2 v2.0.1 + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 golang.org/x/text v0.3.7 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index afac06b..0d18f8c 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,13 @@ github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/antzucaro/matchr v0.0.0-20180616170659-cbc221335f3c h1:CucViv7orgFBMkehuFFdkCVF5ERovbkRRyhvaYaHu/k= -github.com/antzucaro/matchr v0.0.0-20180616170659-cbc221335f3c/go.mod h1:bV/CkX4+ANGDaBwbHkt9kK287al/i9BsB18PRBvyqYo= -github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0 h1:R/qAiUxFT3mNgQaNqJe0IVznjKRNm23ohAIh9lgtlzc= -github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0/go.mod h1:v3ZDlfVAL1OrkKHbGSFFK60k0/7hruHPDq2XMs9Gu6U= 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/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.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/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/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/suggestions.go b/suggestions.go index 65bb3cf..87fa905 100644 --- a/suggestions.go +++ b/suggestions.go @@ -3,9 +3,18 @@ package cli import ( "fmt" - "github.com/antzucaro/matchr" + "github.com/xrash/smetrics" ) +func jaroWinkler(a, b string) float64 { + // magic values are from https://github.com/xrash/smetrics/blob/039620a656736e6ad994090895784a7af15e0b80/jaro-winkler.go#L8 + const ( + boostThreshold = 0.7 + prefixSize = 4 + ) + return smetrics.JaroWinkler(a, b, boostThreshold, prefixSize) +} + func suggestFlag(flags []Flag, provided string, hideHelp bool) string { distance := 0.0 suggestion := "" @@ -16,7 +25,7 @@ func suggestFlag(flags []Flag, provided string, hideHelp bool) string { flagNames = append(flagNames, HelpFlag.Names()...) } for _, name := range flagNames { - newDistance := matchr.JaroWinkler(name, provided, true) + newDistance := jaroWinkler(name, provided) if newDistance > distance { distance = newDistance suggestion = name @@ -39,7 +48,7 @@ func suggestCommand(commands []*Command, provided string) (suggestion string) { distance := 0.0 for _, command := range commands { for _, name := range append(command.Names(), helpName, helpAlias) { - newDistance := matchr.JaroWinkler(name, provided, true) + newDistance := jaroWinkler(name, provided) if newDistance > distance { distance = newDistance suggestion = name From 3e31c9b44c36aec4630b44bb3b516d86066b4c80 Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Tue, 24 May 2022 15:28:30 +0900 Subject: [PATCH 105/125] CI: workaround for golang.org/x/tools error Signed-off-by: Akihiro Suda --- .github/workflows/cli.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 3b31afc..824bcca 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -34,6 +34,10 @@ jobs: if: matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest' run: test -z $(gofmt -l .) + # https://github.com/urfave/cli/pull/1405#issuecomment-1135458582 + - name: workaround for golang.org/x/tools error + run: go mod download golang.org/x/tools + - name: vet run: go run internal/build/build.go vet From 813ab33ba0a0416b85e7e39558ff37a0238b5abe Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 24 May 2022 08:18:55 -0400 Subject: [PATCH 106/125] Add some missing go.sum entries needed with 1.16.x --- go.sum | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.sum b/go.sum index 0d18f8c..8521fc3 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRT github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= From e77dd7bb6816628722681373965a1ce3a34e0810 Mon Sep 17 00:00:00 2001 From: Joseph Cumines Date: Tue, 7 Jun 2022 08:54:57 +1000 Subject: [PATCH 107/125] Add SliceFlag wrapper and fix bugs in existing implementations The SliceFlag implementation and associated aliases (MultiStringFlag, etc) extend the existing slice implementations (StringSliceFlag, etc) to support actual slices as the flag value and destination. This change also fixes various bugs in the existing implementation. Notably, the StringSliceFlag.Apply implementation would modify the input (default) Value, if an env var was set, and no destination was provided. The bugs fixed in the other three implementations were all already fixed in either StringSliceFlag, or in one case (ignoring empty env var) in Float64SliceFlag. --- flag_float64_slice.go | 55 ++- flag_int64_slice.go | 53 ++- flag_int_slice.go | 53 ++- flag_string_slice.go | 48 +- flag_test.go | 90 +++- sliceflag.go | 228 ++++++++++ sliceflag_test.go | 1002 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1473 insertions(+), 56 deletions(-) create mode 100644 sliceflag.go create mode 100644 sliceflag_test.go diff --git a/flag_float64_slice.go b/flag_float64_slice.go index bc347cc..8452d29 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -56,7 +56,12 @@ func (f *Float64Slice) Set(value string) error { // String returns a readable representation of this value (for usage defaults) func (f *Float64Slice) String() string { - return fmt.Sprintf("%#v", f.slice) + v := f.slice + if v == nil { + // treat nil the same as zero length non-nil + v = make([]float64, 0) + } + return fmt.Sprintf("%#v", v) } // Serialize allows Float64Slice to fulfill Serializer @@ -120,34 +125,60 @@ func (f *Float64SliceFlag) GetEnvVars() []string { // Apply populates the flag given the flag set and environment func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { + // apply any default + if f.Destination != nil && f.Value != nil { + f.Destination.slice = make([]float64, len(f.Value.slice)) + copy(f.Destination.slice, f.Value.slice) + } + + // resolve setValue (what we will assign to the set) + var setValue *Float64Slice + switch { + case f.Destination != nil: + setValue = f.Destination + case f.Value != nil: + setValue = f.Value.clone() + default: + setValue = new(Float64Slice) + } + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { - f.Value = &Float64Slice{} - for _, s := range flagSplitMultiValues(val) { - if err := f.Value.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as float64 slice value from %s for flag %s: %s", f.Value, source, f.Name, err) + if err := setValue.Set(strings.TrimSpace(s)); err != nil { + return fmt.Errorf("could not parse %q as float64 slice value from %s for flag %s: %s", val, source, f.Name, err) } } // Set this to false so that we reset the slice if we then set values from // flags that have already been set by the environment. - f.Value.hasBeenSet = false + setValue.hasBeenSet = false f.HasBeenSet = true } } - if f.Value == nil { - f.Value = &Float64Slice{} - } - copyValue := f.Value.clone() for _, name := range f.Names() { - set.Var(copyValue, name, f.Usage) + set.Var(setValue, name, f.Usage) } return nil } +func (f *Float64SliceFlag) SetValue(slice []float64) { + f.Value = newSliceFlagValue(NewFloat64Slice, slice) +} + +func (f *Float64SliceFlag) SetDestination(slice []float64) { + f.Destination = newSliceFlagValue(NewFloat64Slice, slice) +} + +func (f *Float64SliceFlag) GetDestination() []float64 { + if destination := f.Destination; destination != nil { + return destination.Value() + } + return nil +} + // Get returns the flag’s value in the given Context. func (f *Float64SliceFlag) Get(ctx *Context) []float64 { return ctx.Float64Slice(f.Name) @@ -165,7 +196,7 @@ func (cCtx *Context) Float64Slice(name string) []float64 { func lookupFloat64Slice(name string, set *flag.FlagSet) []float64 { f := set.Lookup(name) if f != nil { - if slice, ok := f.Value.(*Float64Slice); ok { + if slice, ok := unwrapFlagValue(f.Value).(*Float64Slice); ok { return slice.Value() } } diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 5f3d5cd..0efea54 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -57,7 +57,12 @@ func (i *Int64Slice) Set(value string) error { // String returns a readable representation of this value (for usage defaults) func (i *Int64Slice) String() string { - return fmt.Sprintf("%#v", i.slice) + v := i.slice + if v == nil { + // treat nil the same as zero length non-nil + v = make([]int64, 0) + } + return fmt.Sprintf("%#v", v) } // Serialize allows Int64Slice to fulfill Serializer @@ -121,32 +126,58 @@ func (f *Int64SliceFlag) GetEnvVars() []string { // Apply populates the flag given the flag set and environment func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { - if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { - f.Value = &Int64Slice{} + // apply any default + if f.Destination != nil && f.Value != nil { + f.Destination.slice = make([]int64, len(f.Value.slice)) + copy(f.Destination.slice, f.Value.slice) + } + // resolve setValue (what we will assign to the set) + var setValue *Int64Slice + switch { + case f.Destination != nil: + setValue = f.Destination + case f.Value != nil: + setValue = f.Value.clone() + default: + setValue = new(Int64Slice) + } + + if val, source, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok && val != "" { for _, s := range flagSplitMultiValues(val) { - if err := f.Value.Set(strings.TrimSpace(s)); err != nil { + if err := setValue.Set(strings.TrimSpace(s)); err != nil { return fmt.Errorf("could not parse %q as int64 slice value from %s for flag %s: %s", val, source, f.Name, err) } } // Set this to false so that we reset the slice if we then set values from // flags that have already been set by the environment. - f.Value.hasBeenSet = false + setValue.hasBeenSet = false f.HasBeenSet = true } - if f.Value == nil { - f.Value = &Int64Slice{} - } - copyValue := f.Value.clone() for _, name := range f.Names() { - set.Var(copyValue, name, f.Usage) + set.Var(setValue, name, f.Usage) } return nil } +func (f *Int64SliceFlag) SetValue(slice []int64) { + f.Value = newSliceFlagValue(NewInt64Slice, slice) +} + +func (f *Int64SliceFlag) SetDestination(slice []int64) { + f.Destination = newSliceFlagValue(NewInt64Slice, slice) +} + +func (f *Int64SliceFlag) GetDestination() []int64 { + if destination := f.Destination; destination != nil { + return destination.Value() + } + return nil +} + // Get returns the flag’s value in the given Context. func (f *Int64SliceFlag) Get(ctx *Context) []int64 { return ctx.Int64Slice(f.Name) @@ -164,7 +195,7 @@ func (cCtx *Context) Int64Slice(name string) []int64 { func lookupInt64Slice(name string, set *flag.FlagSet) []int64 { f := set.Lookup(name) if f != nil { - if slice, ok := f.Value.(*Int64Slice); ok { + if slice, ok := unwrapFlagValue(f.Value).(*Int64Slice); ok { return slice.Value() } } diff --git a/flag_int_slice.go b/flag_int_slice.go index 2ddf805..28f8c90 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -68,7 +68,12 @@ func (i *IntSlice) Set(value string) error { // String returns a readable representation of this value (for usage defaults) func (i *IntSlice) String() string { - return fmt.Sprintf("%#v", i.slice) + v := i.slice + if v == nil { + // treat nil the same as zero length non-nil + v = make([]int, 0) + } + return fmt.Sprintf("%#v", v) } // Serialize allows IntSlice to fulfill Serializer @@ -132,32 +137,58 @@ func (f *IntSliceFlag) GetEnvVars() []string { // Apply populates the flag given the flag set and environment func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { - if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { - f.Value = &IntSlice{} + // apply any default + if f.Destination != nil && f.Value != nil { + f.Destination.slice = make([]int, len(f.Value.slice)) + copy(f.Destination.slice, f.Value.slice) + } + // resolve setValue (what we will assign to the set) + var setValue *IntSlice + switch { + case f.Destination != nil: + setValue = f.Destination + case f.Value != nil: + setValue = f.Value.clone() + default: + setValue = new(IntSlice) + } + + if val, source, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok && val != "" { for _, s := range flagSplitMultiValues(val) { - if err := f.Value.Set(strings.TrimSpace(s)); err != nil { + if err := setValue.Set(strings.TrimSpace(s)); err != nil { return fmt.Errorf("could not parse %q as int slice value from %s for flag %s: %s", val, source, f.Name, err) } } // Set this to false so that we reset the slice if we then set values from // flags that have already been set by the environment. - f.Value.hasBeenSet = false + setValue.hasBeenSet = false f.HasBeenSet = true } - if f.Value == nil { - f.Value = &IntSlice{} - } - copyValue := f.Value.clone() for _, name := range f.Names() { - set.Var(copyValue, name, f.Usage) + set.Var(setValue, name, f.Usage) } return nil } +func (f *IntSliceFlag) SetValue(slice []int) { + f.Value = newSliceFlagValue(NewIntSlice, slice) +} + +func (f *IntSliceFlag) SetDestination(slice []int) { + f.Destination = newSliceFlagValue(NewIntSlice, slice) +} + +func (f *IntSliceFlag) GetDestination() []int { + if destination := f.Destination; destination != nil { + return destination.Value() + } + return nil +} + // Get returns the flag’s value in the given Context. func (f *IntSliceFlag) Get(ctx *Context) []int { return ctx.IntSlice(f.Name) @@ -175,7 +206,7 @@ func (cCtx *Context) IntSlice(name string) []int { func lookupIntSlice(name string, set *flag.FlagSet) []int { f := set.Lookup(name) if f != nil { - if slice, ok := f.Value.(*IntSlice); ok { + if slice, ok := unwrapFlagValue(f.Value).(*IntSlice); ok { return slice.Value() } } diff --git a/flag_string_slice.go b/flag_string_slice.go index 599f42c..2be2af6 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -115,41 +115,36 @@ func (f *StringSliceFlag) GetEnvVars() []string { // Apply populates the flag given the flag set and environment func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { - + // apply any default if f.Destination != nil && f.Value != nil { f.Destination.slice = make([]string, len(f.Value.slice)) copy(f.Destination.slice, f.Value.slice) + } + // resolve setValue (what we will assign to the set) + var setValue *StringSlice + switch { + case f.Destination != nil: + setValue = f.Destination + case f.Value != nil: + setValue = f.Value.clone() + default: + setValue = new(StringSlice) } if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { - if f.Value == nil { - f.Value = &StringSlice{} - } - destination := f.Value - if f.Destination != nil { - destination = f.Destination - } - for _, s := range flagSplitMultiValues(val) { - if err := destination.Set(strings.TrimSpace(s)); err != nil { + if err := setValue.Set(strings.TrimSpace(s)); err != nil { return fmt.Errorf("could not parse %q as string value from %s for flag %s: %s", val, source, f.Name, err) } } // Set this to false so that we reset the slice if we then set values from // flags that have already been set by the environment. - destination.hasBeenSet = false + setValue.hasBeenSet = false 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() { set.Var(setValue, name, f.Usage) } @@ -157,6 +152,21 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { return nil } +func (f *StringSliceFlag) SetValue(slice []string) { + f.Value = newSliceFlagValue(NewStringSlice, slice) +} + +func (f *StringSliceFlag) SetDestination(slice []string) { + f.Destination = newSliceFlagValue(NewStringSlice, slice) +} + +func (f *StringSliceFlag) GetDestination() []string { + if destination := f.Destination; destination != nil { + return destination.Value() + } + return nil +} + // Get returns the flag’s value in the given Context. func (f *StringSliceFlag) Get(ctx *Context) []string { return ctx.StringSlice(f.Name) @@ -174,7 +184,7 @@ func (cCtx *Context) StringSlice(name string) []string { func lookupStringSlice(name string, set *flag.FlagSet) []string { f := set.Lookup(name) if f != nil { - if slice, ok := f.Value.(*StringSlice); ok { + if slice, ok := unwrapFlagValue(f.Value).(*StringSlice); ok { return slice.Value() } } diff --git a/flag_test.go b/flag_test.go index ba90e91..0bb893a 100644 --- a/flag_test.go +++ b/flag_test.go @@ -114,7 +114,7 @@ func TestFlagsFromEnv(t *testing.T) { {"foobar", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value from environment variable "SECONDS" 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 from environment variable "SECONDS" for flag seconds: .*`}, + {"foobar", newSetFloat64Slice(), &Float64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as float64 slice value from environment variable "SECONDS" 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 from environment variable "SECONDS" for flag seconds: .*`}, @@ -604,7 +604,7 @@ func TestStringSliceFlagApply_SetsAllNames(t *testing.T) { expect(t, err, nil) } -func TestStringSliceFlagApply_UsesEnvValues(t *testing.T) { +func TestStringSliceFlagApply_UsesEnvValues_noDefault(t *testing.T) { defer resetEnv(os.Environ()) os.Clearenv() _ = os.Setenv("MY_GOAT", "vincent van goat,scape goat") @@ -615,7 +615,22 @@ func TestStringSliceFlagApply_UsesEnvValues(t *testing.T) { err := set.Parse(nil) expect(t, err, nil) - expect(t, val.Value(), NewStringSlice("vincent van goat", "scape goat").Value()) + expect(t, val.Value(), []string(nil)) + expect(t, set.Lookup("goat").Value.(*StringSlice).Value(), []string{"vincent van goat", "scape goat"}) +} + +func TestStringSliceFlagApply_UsesEnvValues_withDefault(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "vincent van goat,scape goat") + val := NewStringSlice(`some default`, `values here`) + 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(), []string{`some default`, `values here`}) + expect(t, set.Lookup("goat").Value.(*StringSlice).Value(), []string{"vincent van goat", "scape goat"}) } func TestStringSliceFlagApply_DefaultValueWithDestination(t *testing.T) { @@ -1406,6 +1421,75 @@ func TestParseMultiStringSliceWithDestinationAndEnv(t *testing.T) { }).Run([]string{"run", "-s", "10", "-s", "20"}) } +func TestParseMultiFloat64SliceWithDestinationAndEnv(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("APP_INTERVALS", "20,30,40") + + dest := &Float64Slice{} + _ = (&App{ + Flags: []Flag{ + &Float64SliceFlag{Name: "serve", Aliases: []string{"s"}, Destination: dest, EnvVars: []string{"APP_INTERVALS"}}, + }, + Action: func(ctx *Context) error { + expected := []float64{10, 20} + if !reflect.DeepEqual(dest.slice, expected) { + t.Errorf("main name not set: %v != %v", expected, ctx.StringSlice("serve")) + } + if !reflect.DeepEqual(dest.slice, expected) { + t.Errorf("short name not set: %v != %v", expected, ctx.StringSlice("s")) + } + return nil + }, + }).Run([]string{"run", "-s", "10", "-s", "20"}) +} + +func TestParseMultiInt64SliceWithDestinationAndEnv(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("APP_INTERVALS", "20,30,40") + + dest := &Int64Slice{} + _ = (&App{ + Flags: []Flag{ + &Int64SliceFlag{Name: "serve", Aliases: []string{"s"}, Destination: dest, EnvVars: []string{"APP_INTERVALS"}}, + }, + Action: func(ctx *Context) error { + expected := []int64{10, 20} + if !reflect.DeepEqual(dest.slice, expected) { + t.Errorf("main name not set: %v != %v", expected, ctx.StringSlice("serve")) + } + if !reflect.DeepEqual(dest.slice, expected) { + t.Errorf("short name not set: %v != %v", expected, ctx.StringSlice("s")) + } + return nil + }, + }).Run([]string{"run", "-s", "10", "-s", "20"}) +} + +func TestParseMultiIntSliceWithDestinationAndEnv(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("APP_INTERVALS", "20,30,40") + + dest := &IntSlice{} + _ = (&App{ + Flags: []Flag{ + &IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Destination: dest, EnvVars: []string{"APP_INTERVALS"}}, + }, + Action: func(ctx *Context) error { + expected := []int{10, 20} + if !reflect.DeepEqual(dest.slice, expected) { + t.Errorf("main name not set: %v != %v", expected, ctx.StringSlice("serve")) + } + if !reflect.DeepEqual(dest.slice, expected) { + t.Errorf("short name not set: %v != %v", expected, ctx.StringSlice("s")) + } + return nil + }, + }).Run([]string{"run", "-s", "10", "-s", "20"}) +} + func TestParseMultiStringSliceWithDefaultsUnset(t *testing.T) { _ = (&App{ Flags: []Flag{ diff --git a/sliceflag.go b/sliceflag.go new file mode 100644 index 0000000..80063ef --- /dev/null +++ b/sliceflag.go @@ -0,0 +1,228 @@ +package cli + +import ( + "flag" + "reflect" +) + +type ( + // SliceFlag extends implementations like StringSliceFlag and IntSliceFlag with support for using slices directly, + // as Value and/or Destination. + // See also SliceFlagTarget, MultiStringFlag, MultiFloat64Flag, MultiInt64Flag, MultiIntFlag. + SliceFlag[T SliceFlagTarget[E], S ~[]E, E any] struct { + Target T + Value S + Destination *S + } + + // SliceFlagTarget models a target implementation for use with SliceFlag. + // The three methods, SetValue, SetDestination, and GetDestination, are necessary to propagate Value and + // Destination, where Value is propagated inwards (initially), and Destination is propagated outwards (on every + // update). + SliceFlagTarget[E any] interface { + Flag + RequiredFlag + DocGenerationFlag + VisibleFlag + CategorizableFlag + + // SetValue should propagate the given slice to the target, ideally as a new value. + // Note that a nil slice should nil/clear any existing value (modelled as ~[]E). + SetValue(slice []E) + // SetDestination should propagate the given slice to the target, ideally as a new value. + // Note that a nil slice should nil/clear any existing value (modelled as ~*[]E). + SetDestination(slice []E) + // GetDestination should return the current value referenced by any destination, or nil if nil/unset. + GetDestination() []E + } + + // MultiStringFlag extends StringSliceFlag with support for using slices directly, as Value and/or Destination. + // See also SliceFlag. + MultiStringFlag = SliceFlag[*StringSliceFlag, []string, string] + + // MultiFloat64Flag extends Float64SliceFlag with support for using slices directly, as Value and/or Destination. + // See also SliceFlag. + MultiFloat64Flag = SliceFlag[*Float64SliceFlag, []float64, float64] + + // MultiInt64Flag extends Int64SliceFlag with support for using slices directly, as Value and/or Destination. + // See also SliceFlag. + MultiInt64Flag = SliceFlag[*Int64SliceFlag, []int64, int64] + + // MultiIntFlag extends IntSliceFlag with support for using slices directly, as Value and/or Destination. + // See also SliceFlag. + MultiIntFlag = SliceFlag[*IntSliceFlag, []int, int] + + flagValueHook struct { + value Generic + hook func() + } +) + +var ( + // compile time assertions + + _ SliceFlagTarget[string] = (*StringSliceFlag)(nil) + _ SliceFlagTarget[string] = (*SliceFlag[*StringSliceFlag, []string, string])(nil) + _ SliceFlagTarget[string] = (*MultiStringFlag)(nil) + _ SliceFlagTarget[float64] = (*MultiFloat64Flag)(nil) + _ SliceFlagTarget[int64] = (*MultiInt64Flag)(nil) + _ SliceFlagTarget[int] = (*MultiIntFlag)(nil) + + _ Generic = (*flagValueHook)(nil) + _ Serializer = (*flagValueHook)(nil) +) + +func (x *SliceFlag[T, S, E]) Apply(set *flag.FlagSet) error { + x.Target.SetValue(x.convertSlice(x.Value)) + + destination := x.Destination + if destination == nil { + x.Target.SetDestination(nil) + + return x.Target.Apply(set) + } + + x.Target.SetDestination(x.convertSlice(*destination)) + + return applyFlagValueHook(set, x.Target.Apply, func() { + *destination = x.Target.GetDestination() + }) +} + +func (x *SliceFlag[T, S, E]) convertSlice(slice S) []E { + result := make([]E, len(slice)) + copy(result, slice) + return result +} + +func (x *SliceFlag[T, S, E]) SetValue(slice S) { + x.Value = slice +} + +func (x *SliceFlag[T, S, E]) SetDestination(slice S) { + if slice != nil { + x.Destination = &slice + } else { + x.Destination = nil + } +} + +func (x *SliceFlag[T, S, E]) GetDestination() S { + if destination := x.Destination; destination != nil { + return *destination + } + return nil +} + +func (x *SliceFlag[T, S, E]) String() string { return x.Target.String() } +func (x *SliceFlag[T, S, E]) Names() []string { return x.Target.Names() } +func (x *SliceFlag[T, S, E]) IsSet() bool { return x.Target.IsSet() } +func (x *SliceFlag[T, S, E]) IsRequired() bool { return x.Target.IsRequired() } +func (x *SliceFlag[T, S, E]) TakesValue() bool { return x.Target.TakesValue() } +func (x *SliceFlag[T, S, E]) GetUsage() string { return x.Target.GetUsage() } +func (x *SliceFlag[T, S, E]) GetValue() string { return x.Target.GetValue() } +func (x *SliceFlag[T, S, E]) GetDefaultText() string { return x.Target.GetDefaultText() } +func (x *SliceFlag[T, S, E]) GetEnvVars() []string { return x.Target.GetEnvVars() } +func (x *SliceFlag[T, S, E]) IsVisible() bool { return x.Target.IsVisible() } +func (x *SliceFlag[T, S, E]) GetCategory() string { return x.Target.GetCategory() } + +func (x *flagValueHook) Set(value string) error { + if err := x.value.Set(value); err != nil { + return err + } + x.hook() + return nil +} + +func (x *flagValueHook) String() string { + // note: this is necessary due to the way Go's flag package handles defaults + isZeroValue := func(f flag.Value, v string) bool { + /* + https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/flag/flag.go;drc=2580d0e08d5e9f979b943758d3c49877fb2324cb;l=453 + + Copyright (c) 2009 The Go Authors. All rights reserved. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + // Build a zero value of the flag's Value type, and see if the + // result of calling its String method equals the value passed in. + // This works unless the Value type is itself an interface type. + typ := reflect.TypeOf(f) + var z reflect.Value + if typ.Kind() == reflect.Pointer { + z = reflect.New(typ.Elem()) + } else { + z = reflect.Zero(typ) + } + return v == z.Interface().(flag.Value).String() + } + if x.value != nil { + // only return non-empty if not the same string as returned by the zero value + if s := x.value.String(); !isZeroValue(x.value, s) { + return s + } + } + return `` +} + +func (x *flagValueHook) Serialize() string { + if value, ok := x.value.(Serializer); ok { + return value.Serialize() + } + return x.String() +} + +// applyFlagValueHook wraps calls apply then wraps flags to call a hook function on update and after initial apply. +func applyFlagValueHook(set *flag.FlagSet, apply func(set *flag.FlagSet) error, hook func()) error { + if apply == nil || set == nil || hook == nil { + panic(`invalid input`) + } + var tmp flag.FlagSet + if err := apply(&tmp); err != nil { + return err + } + tmp.VisitAll(func(f *flag.Flag) { set.Var(&flagValueHook{value: f.Value, hook: hook}, f.Name, f.Usage) }) + hook() + return nil +} + +// newSliceFlagValue is for implementing SliceFlagTarget.SetValue and SliceFlagTarget.SetDestination. +// It's e.g. as part of StringSliceFlag.SetValue, using the factory NewStringSlice. +func newSliceFlagValue[R any, S ~[]E, E any](factory func(defaults ...E) *R, defaults S) *R { + if defaults == nil { + return nil + } + return factory(defaults...) +} + +// unwrapFlagValue strips any/all *flagValueHook wrappers. +func unwrapFlagValue(v flag.Value) flag.Value { + for { + h, ok := v.(*flagValueHook) + if !ok { + return v + } + v = h.value + } +} diff --git a/sliceflag_test.go b/sliceflag_test.go new file mode 100644 index 0000000..8ff9055 --- /dev/null +++ b/sliceflag_test.go @@ -0,0 +1,1002 @@ +package cli + +import ( + "bytes" + "flag" + "fmt" + "os" + "reflect" + "testing" +) + +func ExampleMultiStringFlag() { + run := func(args ...string) { + // add $0 (the command being run) + args = append([]string{`-`}, args...) + type CustomStringSlice []string + type Config struct { + FlagOne []string + Two CustomStringSlice + } + cfg := Config{ + Two: []string{ + `default value 1`, + `default value 2`, + }, + } + if err := (&App{ + Flags: []Flag{ + &MultiStringFlag{ + Target: &StringSliceFlag{ + Name: `flag-one`, + Category: `category1`, + Usage: `this is the first flag`, + Aliases: []string{`1`}, + EnvVars: []string{`FLAG_ONE`}, + }, + Value: cfg.FlagOne, + Destination: &cfg.FlagOne, + }, + &SliceFlag[*StringSliceFlag, CustomStringSlice, string]{ + Target: &StringSliceFlag{ + Name: `two`, + Category: `category2`, + Usage: `this is the second flag`, + Aliases: []string{`2`}, + EnvVars: []string{`TWO`}, + }, + Value: cfg.Two, + Destination: &cfg.Two, + }, + &MultiStringFlag{ + Target: &StringSliceFlag{ + Name: `flag-three`, + Category: `category1`, + Usage: `this is the third flag`, + Aliases: []string{`3`}, + EnvVars: []string{`FLAG_THREE`}, + }, + Value: []string{`some value`}, + }, + &StringSliceFlag{ + Name: `flag-four`, + Category: `category2`, + Usage: `this is the fourth flag`, + Aliases: []string{`4`}, + EnvVars: []string{`FLAG_FOUR`}, + Value: NewStringSlice(`d1`, `d2`), + }, + }, + Action: func(c *Context) error { + fmt.Printf("Flag names: %q\n", c.FlagNames()) + fmt.Printf("Local flag names: %q\n", c.LocalFlagNames()) + fmt.Println(`Context values:`) + for _, name := range [...]string{`flag-one`, `two`, `flag-three`, `flag-four`} { + fmt.Printf("%q=%q\n", name, c.StringSlice(name)) + } + fmt.Println(`Destination values:`) + fmt.Printf("cfg.FlagOne=%q\n", cfg.FlagOne) + fmt.Printf("cfg.Two=%q\n", cfg.Two) + return nil + }, + Writer: os.Stdout, + ErrWriter: os.Stdout, + Name: `app-name`, + }).Run(args); err != nil { + panic(err) + } + } + + fmt.Printf("Show defaults...\n\n") + run() + + fmt.Printf("---\nSetting all flags via command line...\n\n") + allFlagsArgs := []string{ + `-1`, `v 1`, + `-1`, `v 2`, + `-2`, `v 3`, + `-2`, `v 4`, + `-3`, `v 5`, + `-3`, `v 6`, + `-4`, `v 7`, + `-4`, `v 8`, + } + run(allFlagsArgs...) + + func() { + defer resetEnv(os.Environ()) + os.Clearenv() + for _, args := range [...][2]string{ + {`FLAG_ONE`, `v 9, v 10`}, + {`TWO`, `v 11, v 12`}, + {`FLAG_THREE`, `v 13, v 14`}, + {`FLAG_FOUR`, `v 15, v 16`}, + } { + if err := os.Setenv(args[0], args[1]); err != nil { + panic(err) + } + } + + fmt.Printf("---\nSetting all flags via environment...\n\n") + run() + + fmt.Printf("---\nWith the same environment + args from the previous example...\n\n") + run(allFlagsArgs...) + }() + + //output: + //Show defaults... + // + //Flag names: [] + //Local flag names: [] + //Context values: + //"flag-one"=[] + //"two"=["default value 1" "default value 2"] + //"flag-three"=["some value"] + //"flag-four"=["d1" "d2"] + //Destination values: + //cfg.FlagOne=[] + //cfg.Two=["default value 1" "default value 2"] + //--- + //Setting all flags via command line... + // + //Flag names: ["1" "2" "3" "4" "flag-four" "flag-one" "flag-three" "two"] + //Local flag names: ["1" "2" "3" "4" "flag-four" "flag-one" "flag-three" "two"] + //Context values: + //"flag-one"=["v 1" "v 2"] + //"two"=["v 3" "v 4"] + //"flag-three"=["v 5" "v 6"] + //"flag-four"=["v 7" "v 8"] + //Destination values: + //cfg.FlagOne=["v 1" "v 2"] + //cfg.Two=["v 3" "v 4"] + //--- + //Setting all flags via environment... + // + //Flag names: [] + //Local flag names: [] + //Context values: + //"flag-one"=["v 9" "v 10"] + //"two"=["v 11" "v 12"] + //"flag-three"=["v 13" "v 14"] + //"flag-four"=["v 15" "v 16"] + //Destination values: + //cfg.FlagOne=["v 9" "v 10"] + //cfg.Two=["v 11" "v 12"] + //--- + //With the same environment + args from the previous example... + // + //Flag names: ["1" "2" "3" "4" "flag-four" "flag-one" "flag-three" "two"] + //Local flag names: ["1" "2" "3" "4" "flag-four" "flag-one" "flag-three" "two"] + //Context values: + //"flag-one"=["v 1" "v 2"] + //"two"=["v 3" "v 4"] + //"flag-three"=["v 5" "v 6"] + //"flag-four"=["v 7" "v 8"] + //Destination values: + //cfg.FlagOne=["v 1" "v 2"] + //cfg.Two=["v 3" "v 4"] +} + +func TestSliceFlag_Apply_string(t *testing.T) { + normalise := func(v any) any { + switch v := v.(type) { + case *[]string: + if v == nil { + return nil + } + return *v + case *StringSlice: + if v == nil { + return nil + } + return v.Value() + } + return v + } + expectEqual := func(t *testing.T, actual, expected any) { + t.Helper() + actual = normalise(actual) + expected = normalise(expected) + if !reflect.DeepEqual(actual, expected) { + t.Errorf("actual: %#v\nexpected: %#v", actual, expected) + } + } + type Config struct { + Flag SliceFlagTarget[string] + Value *[]string + Destination **[]string + Context *Context + Check func() + } + for _, tc := range [...]struct { + Name string + Factory func(t *testing.T, f *StringSliceFlag) Config + }{ + { + Name: `once`, + Factory: func(t *testing.T, f *StringSliceFlag) Config { + v := SliceFlag[*StringSliceFlag, []string, string]{Target: f} + return Config{ + Flag: &v, + Value: &v.Value, + Destination: &v.Destination, + Check: func() { + expectEqual(t, v.Value, v.Target.Value) + expectEqual(t, v.Destination, v.Target.Destination) + }, + } + }, + }, + { + Name: `twice`, + Factory: func(t *testing.T, f *StringSliceFlag) Config { + v := SliceFlag[*SliceFlag[*StringSliceFlag, []string, string], []string, string]{ + Target: &SliceFlag[*StringSliceFlag, []string, string]{Target: f}, + } + return Config{ + Flag: &v, + Value: &v.Value, + Destination: &v.Destination, + Check: func() { + expectEqual(t, v.Value, v.Target.Value) + expectEqual(t, v.Destination, v.Target.Destination) + + expectEqual(t, v.Value, v.Target.Target.Value) + expectEqual(t, v.Destination, v.Target.Target.Destination) + }, + } + }, + }, + { + Name: `thrice`, + Factory: func(t *testing.T, f *StringSliceFlag) Config { + v := SliceFlag[*SliceFlag[*SliceFlag[*StringSliceFlag, []string, string], []string, string], []string, string]{ + Target: &SliceFlag[*SliceFlag[*StringSliceFlag, []string, string], []string, string]{ + Target: &SliceFlag[*StringSliceFlag, []string, string]{Target: f}, + }, + } + return Config{ + Flag: &v, + Value: &v.Value, + Destination: &v.Destination, + Check: func() { + expectEqual(t, v.Value, v.Target.Value) + expectEqual(t, v.Destination, v.Target.Destination) + + expectEqual(t, v.Value, v.Target.Target.Value) + expectEqual(t, v.Destination, v.Target.Target.Destination) + + expectEqual(t, v.Value, v.Target.Target.Target.Value) + expectEqual(t, v.Destination, v.Target.Target.Target.Destination) + }, + } + }, + }, + } { + t.Run(tc.Name, func(t *testing.T) { + t.Run(`destination`, func(t *testing.T) { + c := tc.Factory(t, &StringSliceFlag{ + Name: `a`, + EnvVars: []string{`APP_A`}, + }) + defer c.Check() + vDefault := []string{`one`, ``, ``, `two`, ``} + var vTarget []string + *c.Value = vDefault + *c.Destination = &vTarget + if err := (&App{Action: func(c *Context) error { return nil }, Flags: []Flag{c.Flag}}).Run([]string{`-`, `--a=`, `--a=three`, `--a=`, `--a=`, `--a=four`, `--a=`, `--a=`}); err != nil { + t.Fatal(err) + } + expectEqual(t, vDefault, []string{`one`, ``, ``, `two`, ``}) + expectEqual(t, vTarget, []string{"", "three", "", "", "four", "", ""}) + }) + t.Run(`context`, func(t *testing.T) { + c := tc.Factory(t, &StringSliceFlag{ + Name: `a`, + EnvVars: []string{`APP_A`}, + }) + defer c.Check() + vDefault := []string{`one`, ``, ``, `two`, ``} + *c.Value = vDefault + var vTarget []string + if err := (&App{Action: func(c *Context) error { + vTarget = c.StringSlice(`a`) + return nil + }, Flags: []Flag{c.Flag}}).Run([]string{`-`, `--a=`, `--a=three`, `--a=`, `--a=`, `--a=four`, `--a=`, `--a=`}); err != nil { + t.Fatal(err) + } + expectEqual(t, vDefault, []string{`one`, ``, ``, `two`, ``}) + expectEqual(t, vTarget, []string{"", "three", "", "", "four", "", ""}) + }) + t.Run(`context with destination`, func(t *testing.T) { + c := tc.Factory(t, &StringSliceFlag{ + Name: `a`, + EnvVars: []string{`APP_A`}, + }) + defer c.Check() + vDefault := []string{`one`, ``, ``, `two`, ``} + *c.Value = vDefault + var vTarget []string + var destination []string + *c.Destination = &destination + if err := (&App{Action: func(c *Context) error { + vTarget = c.StringSlice(`a`) + return nil + }, Flags: []Flag{c.Flag}}).Run([]string{`-`, `--a=`, `--a=three`, `--a=`, `--a=`, `--a=four`, `--a=`, `--a=`}); err != nil { + t.Fatal(err) + } + expectEqual(t, vDefault, []string{`one`, ``, ``, `two`, ``}) + expectEqual(t, vTarget, []string{"", "three", "", "", "four", "", ""}) + expectEqual(t, destination, []string{"", "three", "", "", "four", "", ""}) + }) + t.Run(`stdlib flag usage with default`, func(t *testing.T) { + c := tc.Factory(t, &StringSliceFlag{Name: `a`}) + *c.Value = []string{`one`, `two`} + var vTarget []string + *c.Destination = &vTarget + set := flag.NewFlagSet(`flagset`, flag.ContinueOnError) + var output bytes.Buffer + set.SetOutput(&output) + if err := c.Flag.Apply(set); err != nil { + t.Fatal(err) + } + if err := set.Parse([]string{`-h`}); err != flag.ErrHelp { + t.Fatal(err) + } + if s := output.String(); s != "Usage of flagset:\n -a value\n \t (default [one two])\n" { + t.Errorf("unexpected output: %q\n%s", s, s) + } + }) + { + test := func(t *testing.T, value []string) { + c := tc.Factory(t, &StringSliceFlag{Name: `a`}) + *c.Value = value + var vTarget []string + *c.Destination = &vTarget + set := flag.NewFlagSet(`flagset`, flag.ContinueOnError) + var output bytes.Buffer + set.SetOutput(&output) + if err := c.Flag.Apply(set); err != nil { + t.Fatal(err) + } + if err := set.Parse([]string{`-h`}); err != flag.ErrHelp { + t.Fatal(err) + } + if s := output.String(); s != "Usage of flagset:\n -a value\n \t\n" { + t.Errorf("unexpected output: %q\n%s", s, s) + } + } + t.Run(`stdlib flag usage without default nil`, func(t *testing.T) { + test(t, nil) + }) + t.Run(`stdlib flag usage without default empty`, func(t *testing.T) { + test(t, make([]string, 0)) + }) + } + }) + } +} + +func TestSliceFlag_Apply_float64(t *testing.T) { + normalise := func(v any) any { + switch v := v.(type) { + case *[]float64: + if v == nil { + return nil + } + return *v + case *Float64Slice: + if v == nil { + return nil + } + return v.Value() + } + return v + } + expectEqual := func(t *testing.T, actual, expected any) { + t.Helper() + actual = normalise(actual) + expected = normalise(expected) + if !reflect.DeepEqual(actual, expected) { + t.Errorf("actual: %#v\nexpected: %#v", actual, expected) + } + } + type Config struct { + Flag SliceFlagTarget[float64] + Value *[]float64 + Destination **[]float64 + Context *Context + Check func() + } + for _, tc := range [...]struct { + Name string + Factory func(t *testing.T, f *Float64SliceFlag) Config + }{ + { + Name: `once`, + Factory: func(t *testing.T, f *Float64SliceFlag) Config { + v := SliceFlag[*Float64SliceFlag, []float64, float64]{Target: f} + return Config{ + Flag: &v, + Value: &v.Value, + Destination: &v.Destination, + Check: func() { + expectEqual(t, v.Value, v.Target.Value) + expectEqual(t, v.Destination, v.Target.Destination) + }, + } + }, + }, + { + Name: `twice`, + Factory: func(t *testing.T, f *Float64SliceFlag) Config { + v := SliceFlag[*SliceFlag[*Float64SliceFlag, []float64, float64], []float64, float64]{ + Target: &SliceFlag[*Float64SliceFlag, []float64, float64]{Target: f}, + } + return Config{ + Flag: &v, + Value: &v.Value, + Destination: &v.Destination, + Check: func() { + expectEqual(t, v.Value, v.Target.Value) + expectEqual(t, v.Destination, v.Target.Destination) + + expectEqual(t, v.Value, v.Target.Target.Value) + expectEqual(t, v.Destination, v.Target.Target.Destination) + }, + } + }, + }, + { + Name: `thrice`, + Factory: func(t *testing.T, f *Float64SliceFlag) Config { + v := SliceFlag[*SliceFlag[*SliceFlag[*Float64SliceFlag, []float64, float64], []float64, float64], []float64, float64]{ + Target: &SliceFlag[*SliceFlag[*Float64SliceFlag, []float64, float64], []float64, float64]{ + Target: &SliceFlag[*Float64SliceFlag, []float64, float64]{Target: f}, + }, + } + return Config{ + Flag: &v, + Value: &v.Value, + Destination: &v.Destination, + Check: func() { + expectEqual(t, v.Value, v.Target.Value) + expectEqual(t, v.Destination, v.Target.Destination) + + expectEqual(t, v.Value, v.Target.Target.Value) + expectEqual(t, v.Destination, v.Target.Target.Destination) + + expectEqual(t, v.Value, v.Target.Target.Target.Value) + expectEqual(t, v.Destination, v.Target.Target.Target.Destination) + }, + } + }, + }, + } { + t.Run(tc.Name, func(t *testing.T) { + t.Run(`destination`, func(t *testing.T) { + c := tc.Factory(t, &Float64SliceFlag{ + Name: `a`, + EnvVars: []string{`APP_A`}, + }) + defer c.Check() + vDefault := []float64{1, 2, 3} + var vTarget []float64 + *c.Value = vDefault + *c.Destination = &vTarget + if err := (&App{Action: func(c *Context) error { return nil }, Flags: []Flag{c.Flag}}).Run([]string{`-`, `--a=4`, `--a=5`}); err != nil { + t.Fatal(err) + } + expectEqual(t, vDefault, []float64{1, 2, 3}) + expectEqual(t, vTarget, []float64{4, 5}) + }) + t.Run(`context`, func(t *testing.T) { + c := tc.Factory(t, &Float64SliceFlag{ + Name: `a`, + EnvVars: []string{`APP_A`}, + }) + defer c.Check() + vDefault := []float64{1, 2, 3} + *c.Value = vDefault + var vTarget []float64 + if err := (&App{Action: func(c *Context) error { + vTarget = c.Float64Slice(`a`) + return nil + }, Flags: []Flag{c.Flag}}).Run([]string{`-`, `--a=4`, `--a=5`}); err != nil { + t.Fatal(err) + } + expectEqual(t, vDefault, []float64{1, 2, 3}) + expectEqual(t, vTarget, []float64{4, 5}) + }) + t.Run(`context with destination`, func(t *testing.T) { + c := tc.Factory(t, &Float64SliceFlag{ + Name: `a`, + EnvVars: []string{`APP_A`}, + }) + defer c.Check() + vDefault := []float64{1, 2, 3} + *c.Value = vDefault + var vTarget []float64 + var destination []float64 + *c.Destination = &destination + if err := (&App{Action: func(c *Context) error { + vTarget = c.Float64Slice(`a`) + return nil + }, Flags: []Flag{c.Flag}}).Run([]string{`-`, `--a=4`, `--a=5`}); err != nil { + t.Fatal(err) + } + expectEqual(t, vDefault, []float64{1, 2, 3}) + expectEqual(t, vTarget, []float64{4, 5}) + expectEqual(t, destination, []float64{4, 5}) + }) + t.Run(`stdlib flag usage with default`, func(t *testing.T) { + c := tc.Factory(t, &Float64SliceFlag{Name: `a`}) + *c.Value = []float64{1, 2} + var vTarget []float64 + *c.Destination = &vTarget + set := flag.NewFlagSet(`flagset`, flag.ContinueOnError) + var output bytes.Buffer + set.SetOutput(&output) + if err := c.Flag.Apply(set); err != nil { + t.Fatal(err) + } + if err := set.Parse([]string{`-h`}); err != flag.ErrHelp { + t.Fatal(err) + } + if s := output.String(); s != "Usage of flagset:\n -a value\n \t (default []float64{1, 2})\n" { + t.Errorf("unexpected output: %q\n%s", s, s) + } + }) + { + test := func(t *testing.T, value []float64) { + c := tc.Factory(t, &Float64SliceFlag{Name: `a`}) + *c.Value = value + var vTarget []float64 + *c.Destination = &vTarget + set := flag.NewFlagSet(`flagset`, flag.ContinueOnError) + var output bytes.Buffer + set.SetOutput(&output) + if err := c.Flag.Apply(set); err != nil { + t.Fatal(err) + } + if err := set.Parse([]string{`-h`}); err != flag.ErrHelp { + t.Fatal(err) + } + if s := output.String(); s != "Usage of flagset:\n -a value\n \t\n" { + t.Errorf("unexpected output: %q\n%s", s, s) + } + } + t.Run(`stdlib flag usage without default nil`, func(t *testing.T) { + test(t, nil) + }) + t.Run(`stdlib flag usage without default empty`, func(t *testing.T) { + test(t, make([]float64, 0)) + }) + } + }) + } +} + +func TestSliceFlag_Apply_int64(t *testing.T) { + normalise := func(v any) any { + switch v := v.(type) { + case *[]int64: + if v == nil { + return nil + } + return *v + case *Int64Slice: + if v == nil { + return nil + } + return v.Value() + } + return v + } + expectEqual := func(t *testing.T, actual, expected any) { + t.Helper() + actual = normalise(actual) + expected = normalise(expected) + if !reflect.DeepEqual(actual, expected) { + t.Errorf("actual: %#v\nexpected: %#v", actual, expected) + } + } + type Config struct { + Flag SliceFlagTarget[int64] + Value *[]int64 + Destination **[]int64 + Context *Context + Check func() + } + for _, tc := range [...]struct { + Name string + Factory func(t *testing.T, f *Int64SliceFlag) Config + }{ + { + Name: `once`, + Factory: func(t *testing.T, f *Int64SliceFlag) Config { + v := SliceFlag[*Int64SliceFlag, []int64, int64]{Target: f} + return Config{ + Flag: &v, + Value: &v.Value, + Destination: &v.Destination, + Check: func() { + expectEqual(t, v.Value, v.Target.Value) + expectEqual(t, v.Destination, v.Target.Destination) + }, + } + }, + }, + { + Name: `twice`, + Factory: func(t *testing.T, f *Int64SliceFlag) Config { + v := SliceFlag[*SliceFlag[*Int64SliceFlag, []int64, int64], []int64, int64]{ + Target: &SliceFlag[*Int64SliceFlag, []int64, int64]{Target: f}, + } + return Config{ + Flag: &v, + Value: &v.Value, + Destination: &v.Destination, + Check: func() { + expectEqual(t, v.Value, v.Target.Value) + expectEqual(t, v.Destination, v.Target.Destination) + + expectEqual(t, v.Value, v.Target.Target.Value) + expectEqual(t, v.Destination, v.Target.Target.Destination) + }, + } + }, + }, + { + Name: `thrice`, + Factory: func(t *testing.T, f *Int64SliceFlag) Config { + v := SliceFlag[*SliceFlag[*SliceFlag[*Int64SliceFlag, []int64, int64], []int64, int64], []int64, int64]{ + Target: &SliceFlag[*SliceFlag[*Int64SliceFlag, []int64, int64], []int64, int64]{ + Target: &SliceFlag[*Int64SliceFlag, []int64, int64]{Target: f}, + }, + } + return Config{ + Flag: &v, + Value: &v.Value, + Destination: &v.Destination, + Check: func() { + expectEqual(t, v.Value, v.Target.Value) + expectEqual(t, v.Destination, v.Target.Destination) + + expectEqual(t, v.Value, v.Target.Target.Value) + expectEqual(t, v.Destination, v.Target.Target.Destination) + + expectEqual(t, v.Value, v.Target.Target.Target.Value) + expectEqual(t, v.Destination, v.Target.Target.Target.Destination) + }, + } + }, + }, + } { + t.Run(tc.Name, func(t *testing.T) { + t.Run(`destination`, func(t *testing.T) { + c := tc.Factory(t, &Int64SliceFlag{ + Name: `a`, + EnvVars: []string{`APP_A`}, + }) + defer c.Check() + vDefault := []int64{1, 2, 3} + var vTarget []int64 + *c.Value = vDefault + *c.Destination = &vTarget + if err := (&App{Action: func(c *Context) error { return nil }, Flags: []Flag{c.Flag}}).Run([]string{`-`, `--a=4`, `--a=5`}); err != nil { + t.Fatal(err) + } + expectEqual(t, vDefault, []int64{1, 2, 3}) + expectEqual(t, vTarget, []int64{4, 5}) + }) + t.Run(`context`, func(t *testing.T) { + c := tc.Factory(t, &Int64SliceFlag{ + Name: `a`, + EnvVars: []string{`APP_A`}, + }) + defer c.Check() + vDefault := []int64{1, 2, 3} + *c.Value = vDefault + var vTarget []int64 + if err := (&App{Action: func(c *Context) error { + vTarget = c.Int64Slice(`a`) + return nil + }, Flags: []Flag{c.Flag}}).Run([]string{`-`, `--a=4`, `--a=5`}); err != nil { + t.Fatal(err) + } + expectEqual(t, vDefault, []int64{1, 2, 3}) + expectEqual(t, vTarget, []int64{4, 5}) + }) + t.Run(`context with destination`, func(t *testing.T) { + c := tc.Factory(t, &Int64SliceFlag{ + Name: `a`, + EnvVars: []string{`APP_A`}, + }) + defer c.Check() + vDefault := []int64{1, 2, 3} + *c.Value = vDefault + var vTarget []int64 + var destination []int64 + *c.Destination = &destination + if err := (&App{Action: func(c *Context) error { + vTarget = c.Int64Slice(`a`) + return nil + }, Flags: []Flag{c.Flag}}).Run([]string{`-`, `--a=4`, `--a=5`}); err != nil { + t.Fatal(err) + } + expectEqual(t, vDefault, []int64{1, 2, 3}) + expectEqual(t, vTarget, []int64{4, 5}) + expectEqual(t, destination, []int64{4, 5}) + }) + t.Run(`stdlib flag usage with default`, func(t *testing.T) { + c := tc.Factory(t, &Int64SliceFlag{Name: `a`}) + *c.Value = []int64{1, 2} + var vTarget []int64 + *c.Destination = &vTarget + set := flag.NewFlagSet(`flagset`, flag.ContinueOnError) + var output bytes.Buffer + set.SetOutput(&output) + if err := c.Flag.Apply(set); err != nil { + t.Fatal(err) + } + if err := set.Parse([]string{`-h`}); err != flag.ErrHelp { + t.Fatal(err) + } + if s := output.String(); s != "Usage of flagset:\n -a value\n \t (default []int64{1, 2})\n" { + t.Errorf("unexpected output: %q\n%s", s, s) + } + }) + { + test := func(t *testing.T, value []int64) { + c := tc.Factory(t, &Int64SliceFlag{Name: `a`}) + *c.Value = value + var vTarget []int64 + *c.Destination = &vTarget + set := flag.NewFlagSet(`flagset`, flag.ContinueOnError) + var output bytes.Buffer + set.SetOutput(&output) + if err := c.Flag.Apply(set); err != nil { + t.Fatal(err) + } + if err := set.Parse([]string{`-h`}); err != flag.ErrHelp { + t.Fatal(err) + } + if s := output.String(); s != "Usage of flagset:\n -a value\n \t\n" { + t.Errorf("unexpected output: %q\n%s", s, s) + } + } + t.Run(`stdlib flag usage without default nil`, func(t *testing.T) { + test(t, nil) + }) + t.Run(`stdlib flag usage without default empty`, func(t *testing.T) { + test(t, make([]int64, 0)) + }) + } + }) + } +} + +func TestSliceFlag_Apply_int(t *testing.T) { + normalise := func(v any) any { + switch v := v.(type) { + case *[]int: + if v == nil { + return nil + } + return *v + case *IntSlice: + if v == nil { + return nil + } + return v.Value() + } + return v + } + expectEqual := func(t *testing.T, actual, expected any) { + t.Helper() + actual = normalise(actual) + expected = normalise(expected) + if !reflect.DeepEqual(actual, expected) { + t.Errorf("actual: %#v\nexpected: %#v", actual, expected) + } + } + type Config struct { + Flag SliceFlagTarget[int] + Value *[]int + Destination **[]int + Context *Context + Check func() + } + for _, tc := range [...]struct { + Name string + Factory func(t *testing.T, f *IntSliceFlag) Config + }{ + { + Name: `once`, + Factory: func(t *testing.T, f *IntSliceFlag) Config { + v := SliceFlag[*IntSliceFlag, []int, int]{Target: f} + return Config{ + Flag: &v, + Value: &v.Value, + Destination: &v.Destination, + Check: func() { + expectEqual(t, v.Value, v.Target.Value) + expectEqual(t, v.Destination, v.Target.Destination) + }, + } + }, + }, + { + Name: `twice`, + Factory: func(t *testing.T, f *IntSliceFlag) Config { + v := SliceFlag[*SliceFlag[*IntSliceFlag, []int, int], []int, int]{ + Target: &SliceFlag[*IntSliceFlag, []int, int]{Target: f}, + } + return Config{ + Flag: &v, + Value: &v.Value, + Destination: &v.Destination, + Check: func() { + expectEqual(t, v.Value, v.Target.Value) + expectEqual(t, v.Destination, v.Target.Destination) + + expectEqual(t, v.Value, v.Target.Target.Value) + expectEqual(t, v.Destination, v.Target.Target.Destination) + }, + } + }, + }, + { + Name: `thrice`, + Factory: func(t *testing.T, f *IntSliceFlag) Config { + v := SliceFlag[*SliceFlag[*SliceFlag[*IntSliceFlag, []int, int], []int, int], []int, int]{ + Target: &SliceFlag[*SliceFlag[*IntSliceFlag, []int, int], []int, int]{ + Target: &SliceFlag[*IntSliceFlag, []int, int]{Target: f}, + }, + } + return Config{ + Flag: &v, + Value: &v.Value, + Destination: &v.Destination, + Check: func() { + expectEqual(t, v.Value, v.Target.Value) + expectEqual(t, v.Destination, v.Target.Destination) + + expectEqual(t, v.Value, v.Target.Target.Value) + expectEqual(t, v.Destination, v.Target.Target.Destination) + + expectEqual(t, v.Value, v.Target.Target.Target.Value) + expectEqual(t, v.Destination, v.Target.Target.Target.Destination) + }, + } + }, + }, + } { + t.Run(tc.Name, func(t *testing.T) { + t.Run(`destination`, func(t *testing.T) { + c := tc.Factory(t, &IntSliceFlag{ + Name: `a`, + EnvVars: []string{`APP_A`}, + }) + defer c.Check() + vDefault := []int{1, 2, 3} + var vTarget []int + *c.Value = vDefault + *c.Destination = &vTarget + if err := (&App{Action: func(c *Context) error { return nil }, Flags: []Flag{c.Flag}}).Run([]string{`-`, `--a=4`, `--a=5`}); err != nil { + t.Fatal(err) + } + expectEqual(t, vDefault, []int{1, 2, 3}) + expectEqual(t, vTarget, []int{4, 5}) + }) + t.Run(`context`, func(t *testing.T) { + c := tc.Factory(t, &IntSliceFlag{ + Name: `a`, + EnvVars: []string{`APP_A`}, + }) + defer c.Check() + vDefault := []int{1, 2, 3} + *c.Value = vDefault + var vTarget []int + if err := (&App{Action: func(c *Context) error { + vTarget = c.IntSlice(`a`) + return nil + }, Flags: []Flag{c.Flag}}).Run([]string{`-`, `--a=4`, `--a=5`}); err != nil { + t.Fatal(err) + } + expectEqual(t, vDefault, []int{1, 2, 3}) + expectEqual(t, vTarget, []int{4, 5}) + }) + t.Run(`context with destination`, func(t *testing.T) { + c := tc.Factory(t, &IntSliceFlag{ + Name: `a`, + EnvVars: []string{`APP_A`}, + }) + defer c.Check() + vDefault := []int{1, 2, 3} + *c.Value = vDefault + var vTarget []int + var destination []int + *c.Destination = &destination + if err := (&App{Action: func(c *Context) error { + vTarget = c.IntSlice(`a`) + return nil + }, Flags: []Flag{c.Flag}}).Run([]string{`-`, `--a=4`, `--a=5`}); err != nil { + t.Fatal(err) + } + expectEqual(t, vDefault, []int{1, 2, 3}) + expectEqual(t, vTarget, []int{4, 5}) + expectEqual(t, destination, []int{4, 5}) + }) + t.Run(`stdlib flag usage with default`, func(t *testing.T) { + c := tc.Factory(t, &IntSliceFlag{Name: `a`}) + *c.Value = []int{1, 2} + var vTarget []int + *c.Destination = &vTarget + set := flag.NewFlagSet(`flagset`, flag.ContinueOnError) + var output bytes.Buffer + set.SetOutput(&output) + if err := c.Flag.Apply(set); err != nil { + t.Fatal(err) + } + if err := set.Parse([]string{`-h`}); err != flag.ErrHelp { + t.Fatal(err) + } + if s := output.String(); s != "Usage of flagset:\n -a value\n \t (default []int{1, 2})\n" { + t.Errorf("unexpected output: %q\n%s", s, s) + } + }) + { + test := func(t *testing.T, value []int) { + c := tc.Factory(t, &IntSliceFlag{Name: `a`}) + *c.Value = value + var vTarget []int + *c.Destination = &vTarget + set := flag.NewFlagSet(`flagset`, flag.ContinueOnError) + var output bytes.Buffer + set.SetOutput(&output) + if err := c.Flag.Apply(set); err != nil { + t.Fatal(err) + } + if err := set.Parse([]string{`-h`}); err != flag.ErrHelp { + t.Fatal(err) + } + if s := output.String(); s != "Usage of flagset:\n -a value\n \t\n" { + t.Errorf("unexpected output: %q\n%s", s, s) + } + } + t.Run(`stdlib flag usage without default nil`, func(t *testing.T) { + test(t, nil) + }) + t.Run(`stdlib flag usage without default empty`, func(t *testing.T) { + test(t, make([]int, 0)) + }) + } + }) + } +} + +type intSliceWrapperDefaultingNil struct { + *IntSlice +} + +func (x intSliceWrapperDefaultingNil) String() string { + if x.IntSlice != nil { + return x.IntSlice.String() + } + return NewIntSlice().String() +} + +func TestFlagValueHook_String_struct(t *testing.T) { + wrap := func(values ...int) *flagValueHook { + return &flagValueHook{value: intSliceWrapperDefaultingNil{NewIntSlice(values...)}} + } + if s := wrap().String(); s != `` { + t.Error(s) + } + if s := wrap(1).String(); s != `[]int{1}` { + t.Error(s) + } +} From 4f795e3870f4edd720f02585c904ca4111466117 Mon Sep 17 00:00:00 2001 From: Joseph Cumines Date: Mon, 13 Jun 2022 08:23:41 +1000 Subject: [PATCH 108/125] Fix build for go < 1.18 --- flag_float64_slice.go | 15 ---------- flag_int64_slice.go | 15 ---------- flag_int_slice.go | 15 ---------- flag_string_slice.go | 15 ---------- sliceflag.go | 65 +++++++++++++++++++++++++++++++++++++++++++ sliceflag_pre18.go | 10 +++++++ sliceflag_test.go | 3 ++ 7 files changed, 78 insertions(+), 60 deletions(-) create mode 100644 sliceflag_pre18.go diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 8452d29..031ec1d 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -164,21 +164,6 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { return nil } -func (f *Float64SliceFlag) SetValue(slice []float64) { - f.Value = newSliceFlagValue(NewFloat64Slice, slice) -} - -func (f *Float64SliceFlag) SetDestination(slice []float64) { - f.Destination = newSliceFlagValue(NewFloat64Slice, slice) -} - -func (f *Float64SliceFlag) GetDestination() []float64 { - if destination := f.Destination; destination != nil { - return destination.Value() - } - return nil -} - // Get returns the flag’s value in the given Context. func (f *Float64SliceFlag) Get(ctx *Context) []float64 { return ctx.Float64Slice(f.Name) diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 0efea54..657aaaa 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -163,21 +163,6 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { return nil } -func (f *Int64SliceFlag) SetValue(slice []int64) { - f.Value = newSliceFlagValue(NewInt64Slice, slice) -} - -func (f *Int64SliceFlag) SetDestination(slice []int64) { - f.Destination = newSliceFlagValue(NewInt64Slice, slice) -} - -func (f *Int64SliceFlag) GetDestination() []int64 { - if destination := f.Destination; destination != nil { - return destination.Value() - } - return nil -} - // Get returns the flag’s value in the given Context. func (f *Int64SliceFlag) Get(ctx *Context) []int64 { return ctx.Int64Slice(f.Name) diff --git a/flag_int_slice.go b/flag_int_slice.go index 28f8c90..7c38393 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -174,21 +174,6 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { return nil } -func (f *IntSliceFlag) SetValue(slice []int) { - f.Value = newSliceFlagValue(NewIntSlice, slice) -} - -func (f *IntSliceFlag) SetDestination(slice []int) { - f.Destination = newSliceFlagValue(NewIntSlice, slice) -} - -func (f *IntSliceFlag) GetDestination() []int { - if destination := f.Destination; destination != nil { - return destination.Value() - } - return nil -} - // Get returns the flag’s value in the given Context. func (f *IntSliceFlag) Get(ctx *Context) []int { return ctx.IntSlice(f.Name) diff --git a/flag_string_slice.go b/flag_string_slice.go index 2be2af6..bcdfd4c 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -152,21 +152,6 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { return nil } -func (f *StringSliceFlag) SetValue(slice []string) { - f.Value = newSliceFlagValue(NewStringSlice, slice) -} - -func (f *StringSliceFlag) SetDestination(slice []string) { - f.Destination = newSliceFlagValue(NewStringSlice, slice) -} - -func (f *StringSliceFlag) GetDestination() []string { - if destination := f.Destination; destination != nil { - return destination.Value() - } - return nil -} - // Get returns the flag’s value in the given Context. func (f *StringSliceFlag) Get(ctx *Context) []string { return ctx.StringSlice(f.Name) diff --git a/sliceflag.go b/sliceflag.go index 80063ef..7dea357 100644 --- a/sliceflag.go +++ b/sliceflag.go @@ -1,3 +1,6 @@ +//go:build go1.18 +// +build go1.18 + package cli import ( @@ -226,3 +229,65 @@ func unwrapFlagValue(v flag.Value) flag.Value { v = h.value } } + +// NOTE: the methods below are in this file to make use of the build constraint + +func (f *Float64SliceFlag) SetValue(slice []float64) { + f.Value = newSliceFlagValue(NewFloat64Slice, slice) +} + +func (f *Float64SliceFlag) SetDestination(slice []float64) { + f.Destination = newSliceFlagValue(NewFloat64Slice, slice) +} + +func (f *Float64SliceFlag) GetDestination() []float64 { + if destination := f.Destination; destination != nil { + return destination.Value() + } + return nil +} + +func (f *Int64SliceFlag) SetValue(slice []int64) { + f.Value = newSliceFlagValue(NewInt64Slice, slice) +} + +func (f *Int64SliceFlag) SetDestination(slice []int64) { + f.Destination = newSliceFlagValue(NewInt64Slice, slice) +} + +func (f *Int64SliceFlag) GetDestination() []int64 { + if destination := f.Destination; destination != nil { + return destination.Value() + } + return nil +} + +func (f *IntSliceFlag) SetValue(slice []int) { + f.Value = newSliceFlagValue(NewIntSlice, slice) +} + +func (f *IntSliceFlag) SetDestination(slice []int) { + f.Destination = newSliceFlagValue(NewIntSlice, slice) +} + +func (f *IntSliceFlag) GetDestination() []int { + if destination := f.Destination; destination != nil { + return destination.Value() + } + return nil +} + +func (f *StringSliceFlag) SetValue(slice []string) { + f.Value = newSliceFlagValue(NewStringSlice, slice) +} + +func (f *StringSliceFlag) SetDestination(slice []string) { + f.Destination = newSliceFlagValue(NewStringSlice, slice) +} + +func (f *StringSliceFlag) GetDestination() []string { + if destination := f.Destination; destination != nil { + return destination.Value() + } + return nil +} diff --git a/sliceflag_pre18.go b/sliceflag_pre18.go new file mode 100644 index 0000000..1173ae7 --- /dev/null +++ b/sliceflag_pre18.go @@ -0,0 +1,10 @@ +//go:build !go1.18 +// +build !go1.18 + +package cli + +import ( + "flag" +) + +func unwrapFlagValue(v flag.Value) flag.Value { return v } diff --git a/sliceflag_test.go b/sliceflag_test.go index 8ff9055..179020b 100644 --- a/sliceflag_test.go +++ b/sliceflag_test.go @@ -1,3 +1,6 @@ +//go:build go1.18 +// +build go1.18 + package cli import ( From 3082652ad6b228e6e80dba5b9f923704ba9c0b91 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 18 Jun 2022 08:49:43 -0400 Subject: [PATCH 109/125] Update the v2 docs since merging #1409 --- godoc-current.txt | 101 ++++++++++++++++++++++++++++++++++++++++ testdata/godoc-v2.x.txt | 101 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) diff --git a/godoc-current.txt b/godoc-current.txt index d94e80d..050136a 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -1003,6 +1003,8 @@ func (f *Float64SliceFlag) GetCategory() string func (f *Float64SliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag +func (f *Float64SliceFlag) GetDestination() []float64 + func (f *Float64SliceFlag) GetEnvVars() []string GetEnvVars returns the env vars for this flag @@ -1025,6 +1027,10 @@ func (f *Float64SliceFlag) IsVisible() bool func (f *Float64SliceFlag) Names() []string Names returns the names of the flag +func (f *Float64SliceFlag) SetDestination(slice []float64) + +func (f *Float64SliceFlag) SetValue(slice []float64) + func (f *Float64SliceFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1215,6 +1221,8 @@ func (f *Int64SliceFlag) GetCategory() string func (f *Int64SliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag +func (f *Int64SliceFlag) GetDestination() []int64 + func (f *Int64SliceFlag) GetEnvVars() []string GetEnvVars returns the env vars for this flag @@ -1237,6 +1245,10 @@ func (f *Int64SliceFlag) IsVisible() bool func (f *Int64SliceFlag) Names() []string Names returns the names of the flag +func (f *Int64SliceFlag) SetDestination(slice []int64) + +func (f *Int64SliceFlag) SetValue(slice []int64) + func (f *Int64SliceFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1362,6 +1374,8 @@ func (f *IntSliceFlag) GetCategory() string func (f *IntSliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag +func (f *IntSliceFlag) GetDestination() []int + func (f *IntSliceFlag) GetEnvVars() []string GetEnvVars returns the env vars for this flag @@ -1384,6 +1398,10 @@ func (f *IntSliceFlag) IsVisible() bool func (f *IntSliceFlag) Names() []string Names returns the names of the flag +func (f *IntSliceFlag) SetDestination(slice []int) + +func (f *IntSliceFlag) SetValue(slice []int) + func (f *IntSliceFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1396,6 +1414,22 @@ type MultiError interface { } MultiError is an error that wraps multiple errors. +type MultiFloat64Flag = SliceFlag[*Float64SliceFlag, []float64, float64] + MultiFloat64Flag extends Float64SliceFlag with support for using slices + directly, as Value and/or Destination. See also SliceFlag. + +type MultiInt64Flag = SliceFlag[*Int64SliceFlag, []int64, int64] + MultiInt64Flag extends Int64SliceFlag with support for using slices + directly, as Value and/or Destination. See also SliceFlag. + +type MultiIntFlag = SliceFlag[*IntSliceFlag, []int, int] + MultiIntFlag extends IntSliceFlag with support for using slices directly, as + Value and/or Destination. See also SliceFlag. + +type MultiStringFlag = SliceFlag[*StringSliceFlag, []string, string] + MultiStringFlag extends StringSliceFlag with support for using slices + directly, as Value and/or Destination. See also SliceFlag. + type OnUsageErrorFunc func(cCtx *Context, err error, isSubcommand bool) error OnUsageErrorFunc is executed if a usage error occurs. This is useful for displaying customized usage error messages. This function is able to replace @@ -1480,6 +1514,67 @@ type Serializer interface { } Serializer is used to circumvent the limitations of flag.FlagSet.Set +type SliceFlag[T SliceFlagTarget[E], S ~[]E, E any] struct { + Target T + Value S + Destination *S +} + SliceFlag extends implementations like StringSliceFlag and IntSliceFlag with + support for using slices directly, as Value and/or Destination. See also + SliceFlagTarget, MultiStringFlag, MultiFloat64Flag, MultiInt64Flag, + MultiIntFlag. + +func (x *SliceFlag[T, S, E]) Apply(set *flag.FlagSet) error + +func (x *SliceFlag[T, S, E]) GetCategory() string + +func (x *SliceFlag[T, S, E]) GetDefaultText() string + +func (x *SliceFlag[T, S, E]) GetDestination() S + +func (x *SliceFlag[T, S, E]) GetEnvVars() []string + +func (x *SliceFlag[T, S, E]) GetUsage() string + +func (x *SliceFlag[T, S, E]) GetValue() string + +func (x *SliceFlag[T, S, E]) IsRequired() bool + +func (x *SliceFlag[T, S, E]) IsSet() bool + +func (x *SliceFlag[T, S, E]) IsVisible() bool + +func (x *SliceFlag[T, S, E]) Names() []string + +func (x *SliceFlag[T, S, E]) SetDestination(slice S) + +func (x *SliceFlag[T, S, E]) SetValue(slice S) + +func (x *SliceFlag[T, S, E]) String() string + +func (x *SliceFlag[T, S, E]) TakesValue() bool + +type SliceFlagTarget[E any] interface { + Flag + RequiredFlag + DocGenerationFlag + VisibleFlag + CategorizableFlag + + // SetValue should propagate the given slice to the target, ideally as a new value. + // Note that a nil slice should nil/clear any existing value (modelled as ~[]E). + SetValue(slice []E) + // SetDestination should propagate the given slice to the target, ideally as a new value. + // Note that a nil slice should nil/clear any existing value (modelled as ~*[]E). + SetDestination(slice []E) + // GetDestination should return the current value referenced by any destination, or nil if nil/unset. + GetDestination() []E +} + SliceFlagTarget models a target implementation for use with SliceFlag. The + three methods, SetValue, SetDestination, and GetDestination, are necessary + to propagate Value and Destination, where Value is propagated inwards + (initially), and Destination is propagated outwards (on every update). + type StringFlag struct { Name string @@ -1599,6 +1694,8 @@ func (f *StringSliceFlag) GetCategory() string func (f *StringSliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag +func (f *StringSliceFlag) GetDestination() []string + func (f *StringSliceFlag) GetEnvVars() []string GetEnvVars returns the env vars for this flag @@ -1621,6 +1718,10 @@ func (f *StringSliceFlag) IsVisible() bool func (f *StringSliceFlag) Names() []string Names returns the names of the flag +func (f *StringSliceFlag) SetDestination(slice []string) + +func (f *StringSliceFlag) SetValue(slice []string) + func (f *StringSliceFlag) String() string String returns a readable representation of this value (for usage defaults) diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt index d94e80d..050136a 100644 --- a/testdata/godoc-v2.x.txt +++ b/testdata/godoc-v2.x.txt @@ -1003,6 +1003,8 @@ func (f *Float64SliceFlag) GetCategory() string func (f *Float64SliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag +func (f *Float64SliceFlag) GetDestination() []float64 + func (f *Float64SliceFlag) GetEnvVars() []string GetEnvVars returns the env vars for this flag @@ -1025,6 +1027,10 @@ func (f *Float64SliceFlag) IsVisible() bool func (f *Float64SliceFlag) Names() []string Names returns the names of the flag +func (f *Float64SliceFlag) SetDestination(slice []float64) + +func (f *Float64SliceFlag) SetValue(slice []float64) + func (f *Float64SliceFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1215,6 +1221,8 @@ func (f *Int64SliceFlag) GetCategory() string func (f *Int64SliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag +func (f *Int64SliceFlag) GetDestination() []int64 + func (f *Int64SliceFlag) GetEnvVars() []string GetEnvVars returns the env vars for this flag @@ -1237,6 +1245,10 @@ func (f *Int64SliceFlag) IsVisible() bool func (f *Int64SliceFlag) Names() []string Names returns the names of the flag +func (f *Int64SliceFlag) SetDestination(slice []int64) + +func (f *Int64SliceFlag) SetValue(slice []int64) + func (f *Int64SliceFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1362,6 +1374,8 @@ func (f *IntSliceFlag) GetCategory() string func (f *IntSliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag +func (f *IntSliceFlag) GetDestination() []int + func (f *IntSliceFlag) GetEnvVars() []string GetEnvVars returns the env vars for this flag @@ -1384,6 +1398,10 @@ func (f *IntSliceFlag) IsVisible() bool func (f *IntSliceFlag) Names() []string Names returns the names of the flag +func (f *IntSliceFlag) SetDestination(slice []int) + +func (f *IntSliceFlag) SetValue(slice []int) + func (f *IntSliceFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1396,6 +1414,22 @@ type MultiError interface { } MultiError is an error that wraps multiple errors. +type MultiFloat64Flag = SliceFlag[*Float64SliceFlag, []float64, float64] + MultiFloat64Flag extends Float64SliceFlag with support for using slices + directly, as Value and/or Destination. See also SliceFlag. + +type MultiInt64Flag = SliceFlag[*Int64SliceFlag, []int64, int64] + MultiInt64Flag extends Int64SliceFlag with support for using slices + directly, as Value and/or Destination. See also SliceFlag. + +type MultiIntFlag = SliceFlag[*IntSliceFlag, []int, int] + MultiIntFlag extends IntSliceFlag with support for using slices directly, as + Value and/or Destination. See also SliceFlag. + +type MultiStringFlag = SliceFlag[*StringSliceFlag, []string, string] + MultiStringFlag extends StringSliceFlag with support for using slices + directly, as Value and/or Destination. See also SliceFlag. + type OnUsageErrorFunc func(cCtx *Context, err error, isSubcommand bool) error OnUsageErrorFunc is executed if a usage error occurs. This is useful for displaying customized usage error messages. This function is able to replace @@ -1480,6 +1514,67 @@ type Serializer interface { } Serializer is used to circumvent the limitations of flag.FlagSet.Set +type SliceFlag[T SliceFlagTarget[E], S ~[]E, E any] struct { + Target T + Value S + Destination *S +} + SliceFlag extends implementations like StringSliceFlag and IntSliceFlag with + support for using slices directly, as Value and/or Destination. See also + SliceFlagTarget, MultiStringFlag, MultiFloat64Flag, MultiInt64Flag, + MultiIntFlag. + +func (x *SliceFlag[T, S, E]) Apply(set *flag.FlagSet) error + +func (x *SliceFlag[T, S, E]) GetCategory() string + +func (x *SliceFlag[T, S, E]) GetDefaultText() string + +func (x *SliceFlag[T, S, E]) GetDestination() S + +func (x *SliceFlag[T, S, E]) GetEnvVars() []string + +func (x *SliceFlag[T, S, E]) GetUsage() string + +func (x *SliceFlag[T, S, E]) GetValue() string + +func (x *SliceFlag[T, S, E]) IsRequired() bool + +func (x *SliceFlag[T, S, E]) IsSet() bool + +func (x *SliceFlag[T, S, E]) IsVisible() bool + +func (x *SliceFlag[T, S, E]) Names() []string + +func (x *SliceFlag[T, S, E]) SetDestination(slice S) + +func (x *SliceFlag[T, S, E]) SetValue(slice S) + +func (x *SliceFlag[T, S, E]) String() string + +func (x *SliceFlag[T, S, E]) TakesValue() bool + +type SliceFlagTarget[E any] interface { + Flag + RequiredFlag + DocGenerationFlag + VisibleFlag + CategorizableFlag + + // SetValue should propagate the given slice to the target, ideally as a new value. + // Note that a nil slice should nil/clear any existing value (modelled as ~[]E). + SetValue(slice []E) + // SetDestination should propagate the given slice to the target, ideally as a new value. + // Note that a nil slice should nil/clear any existing value (modelled as ~*[]E). + SetDestination(slice []E) + // GetDestination should return the current value referenced by any destination, or nil if nil/unset. + GetDestination() []E +} + SliceFlagTarget models a target implementation for use with SliceFlag. The + three methods, SetValue, SetDestination, and GetDestination, are necessary + to propagate Value and Destination, where Value is propagated inwards + (initially), and Destination is propagated outwards (on every update). + type StringFlag struct { Name string @@ -1599,6 +1694,8 @@ func (f *StringSliceFlag) GetCategory() string func (f *StringSliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag +func (f *StringSliceFlag) GetDestination() []string + func (f *StringSliceFlag) GetEnvVars() []string GetEnvVars returns the env vars for this flag @@ -1621,6 +1718,10 @@ func (f *StringSliceFlag) IsVisible() bool func (f *StringSliceFlag) Names() []string Names returns the names of the flag +func (f *StringSliceFlag) SetDestination(slice []string) + +func (f *StringSliceFlag) SetValue(slice []string) + func (f *StringSliceFlag) String() string String returns a readable representation of this value (for usage defaults) From ee0756044cbcbc6360f9210d340eedd2a5dbd842 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sat, 18 Jun 2022 11:02:36 -0700 Subject: [PATCH 110/125] Upgrade gopkg.in/yaml to v3 Fixes vulnerability: https://github.com/advisories/GHSA-hp87-p4gw-j4gq YAML v3 deserializes maps as map[string]interface{} so we handle this in MapImportSource now. Signed-off-by: Jesse Szwedko --- altsrc/map_input_source.go | 14 +++++++++++--- altsrc/yaml_file_loader.go | 2 +- go.mod | 2 +- go.sum | 4 ++-- internal/genflags/cmd/genflags/main.go | 2 +- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index e065c7c..49a3c51 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -32,11 +32,19 @@ func nestedVal(name string, tree map[interface{}]interface{}) (interface{}, bool if !ok { return nil, false } - ctype, ok := child.(map[interface{}]interface{}) - if !ok { + + switch child := child.(type) { + case map[string]interface{}: + m := make(map[interface{}]interface{}, len(child)) + for k, v := range child { + m[k] = v + } + node = m + case map[interface{}]interface{}: + node = child + default: return nil, false } - node = ctype } if val, ok := node[sections[len(sections)-1]]; ok { return val, true diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 4ace1f2..315db18 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -11,7 +11,7 @@ import ( "github.com/urfave/cli/v2" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" ) type yamlSourceContext struct { diff --git a/go.mod b/go.mod index 6343421..965da5c 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.1 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 golang.org/x/text v0.3.7 - gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 ) require github.com/russross/blackfriday/v2 v2.1.0 // indirect diff --git a/go.sum b/go.sum index 8521fc3..3c7df2e 100644 --- a/go.sum +++ b/go.sum @@ -12,5 +12,5 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IK golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/genflags/cmd/genflags/main.go b/internal/genflags/cmd/genflags/main.go index cad2508..4212e60 100644 --- a/internal/genflags/cmd/genflags/main.go +++ b/internal/genflags/cmd/genflags/main.go @@ -15,7 +15,7 @@ import ( "github.com/urfave/cli/v2" "github.com/urfave/cli/v2/internal/genflags" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" ) const ( From 72dc91db747008d0554dba4056f11f98bf1e2bd4 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Sat, 18 Jun 2022 13:38:22 -0700 Subject: [PATCH 111/125] Re-use `node` variable Signed-off-by: Jesse Szwedko --- altsrc/map_input_source.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/altsrc/map_input_source.go b/altsrc/map_input_source.go index 49a3c51..07de00f 100644 --- a/altsrc/map_input_source.go +++ b/altsrc/map_input_source.go @@ -35,11 +35,10 @@ func nestedVal(name string, tree map[interface{}]interface{}) (interface{}, bool switch child := child.(type) { case map[string]interface{}: - m := make(map[interface{}]interface{}, len(child)) + node = make(map[interface{}]interface{}, len(child)) for k, v := range child { - m[k] = v + node[k] = v } - node = m case map[interface{}]interface{}: node = child default: From 1362627dd7c1cef4ebcc65497479a10cff5f86d4 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 19 Jun 2022 10:45:18 -0400 Subject: [PATCH 112/125] Approve usage wrapping docs changes --- godoc-current.txt | 25 +++++++++++++++---------- testdata/godoc-v2.x.txt | 25 +++++++++++++++---------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/godoc-current.txt b/godoc-current.txt index 050136a..a3a7fac 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -32,16 +32,16 @@ var ( SuggestDidYouMeanTemplate string = suggestDidYouMeanTemplate ) var AppHelpTemplate = `NAME: - {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} + {{$v := offset .Name 6}}{{wrap .Name 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}} USAGE: - {{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}} + {{if .UsageText}}{{wrap .UsageText 3}}{{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 | nindent 3 | trim}}{{end}}{{if len .Authors}} + {{wrap .Description 3}}{{end}}{{if len .Authors}} AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: {{range $index, $author := .Authors}}{{if $index}} @@ -59,26 +59,26 @@ GLOBAL OPTIONS:{{range .VisibleFlagCategories}} GLOBAL OPTIONS: {{range $index, $option := .VisibleFlags}}{{if $index}} - {{end}}{{$option}}{{end}}{{end}}{{end}}{{if .Copyright}} + {{end}}{{wrap $option.String 6}}{{end}}{{end}}{{end}}{{if .Copyright}} COPYRIGHT: - {{.Copyright}}{{end}} + {{wrap .Copyright 3}}{{end}} ` AppHelpTemplate is the text template for the Default help topic. cli.go uses text/template to render templates. You can render custom help text by setting this variable. var CommandHelpTemplate = `NAME: - {{.HelpName}} - {{.Usage}} + {{$v := offset .HelpName 6}}{{wrap .HelpName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}} USAGE: - {{if .UsageText}}{{.UsageText | nindent 3 | trim}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} + {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} CATEGORY: {{.Category}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlagCategories}} + {{wrap .Description 3}}{{end}}{{if .VisibleFlagCategories}} OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} @@ -150,10 +150,10 @@ var SubcommandHelpTemplate = `NAME: {{.HelpName}} - {{.Usage}} USAGE: - {{if .UsageText}}{{.UsageText | nindent 3 | trim}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}} + {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description | nindent 3 | trim}}{{end}} + {{wrap .Description 3}}{{end}} COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{range .VisibleCommands}} @@ -186,6 +186,11 @@ var HelpPrinterCustom helpPrinterCustom = printHelpCustom the default implementation of HelpPrinter, and may be called directly if the ExtraInfo field is set on an App. + In the default implementation, if the customFuncs argument contains a + "wrapAt" key, which is a function which takes no arguments and returns an + int, this int value will be used to produce a "wrap" function used by the + default template to wrap long lines. + FUNCTIONS diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt index 050136a..a3a7fac 100644 --- a/testdata/godoc-v2.x.txt +++ b/testdata/godoc-v2.x.txt @@ -32,16 +32,16 @@ var ( SuggestDidYouMeanTemplate string = suggestDidYouMeanTemplate ) var AppHelpTemplate = `NAME: - {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} + {{$v := offset .Name 6}}{{wrap .Name 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}} USAGE: - {{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}} + {{if .UsageText}}{{wrap .UsageText 3}}{{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 | nindent 3 | trim}}{{end}}{{if len .Authors}} + {{wrap .Description 3}}{{end}}{{if len .Authors}} AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: {{range $index, $author := .Authors}}{{if $index}} @@ -59,26 +59,26 @@ GLOBAL OPTIONS:{{range .VisibleFlagCategories}} GLOBAL OPTIONS: {{range $index, $option := .VisibleFlags}}{{if $index}} - {{end}}{{$option}}{{end}}{{end}}{{end}}{{if .Copyright}} + {{end}}{{wrap $option.String 6}}{{end}}{{end}}{{end}}{{if .Copyright}} COPYRIGHT: - {{.Copyright}}{{end}} + {{wrap .Copyright 3}}{{end}} ` AppHelpTemplate is the text template for the Default help topic. cli.go uses text/template to render templates. You can render custom help text by setting this variable. var CommandHelpTemplate = `NAME: - {{.HelpName}} - {{.Usage}} + {{$v := offset .HelpName 6}}{{wrap .HelpName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}} USAGE: - {{if .UsageText}}{{.UsageText | nindent 3 | trim}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} + {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} CATEGORY: {{.Category}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description | nindent 3 | trim}}{{end}}{{if .VisibleFlagCategories}} + {{wrap .Description 3}}{{end}}{{if .VisibleFlagCategories}} OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} @@ -150,10 +150,10 @@ var SubcommandHelpTemplate = `NAME: {{.HelpName}} - {{.Usage}} USAGE: - {{if .UsageText}}{{.UsageText | nindent 3 | trim}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}} + {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description | nindent 3 | trim}}{{end}} + {{wrap .Description 3}}{{end}} COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{range .VisibleCommands}} @@ -186,6 +186,11 @@ var HelpPrinterCustom helpPrinterCustom = printHelpCustom the default implementation of HelpPrinter, and may be called directly if the ExtraInfo field is set on an App. + In the default implementation, if the customFuncs argument contains a + "wrapAt" key, which is a function which takes no arguments and returns an + int, this int value will be used to produce a "wrap" function used by the + default template to wrap long lines. + FUNCTIONS From 59ce32aad4c4cb686e93b0949af5c865a72958dd Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 20 Jun 2022 09:34:30 -0400 Subject: [PATCH 113/125] Update dependencies to latest --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 965da5c..4e39308 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/BurntSushi/toml v1.1.0 - github.com/cpuguy83/go-md2man/v2 v2.0.1 + github.com/cpuguy83/go-md2man/v2 v2.0.2 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 golang.org/x/text v0.3.7 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index 3c7df2e..6beae99 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ 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/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= From 9f56fe708861d6fd02e9499f634129b8546f65f0 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 20 Jun 2022 11:07:56 -0400 Subject: [PATCH 114/125] Remove temporary go mod workaround --- .github/workflows/cli.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 824bcca..3b31afc 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -34,10 +34,6 @@ jobs: if: matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest' run: test -z $(gofmt -l .) - # https://github.com/urfave/cli/pull/1405#issuecomment-1135458582 - - name: workaround for golang.org/x/tools error - run: go mod download golang.org/x/tools - - name: vet run: go run internal/build/build.go vet From 58ccb0bf28b4ed4f5cf0226a85a337504f615717 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 20 Jun 2022 17:08:10 -0400 Subject: [PATCH 115/125] Clean up the v2 manual a bit includes: - running each example through `goimports` - wrapping text at ~80 cols - consistently using `&cli.App{...}` - consistently using code example gates - removing trailing blankspace --- docs/v2/manual.md | 1751 +++++++++++++++++++++++---------------------- 1 file changed, 879 insertions(+), 872 deletions(-) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 7208ae6..1527e82 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -9,17 +9,17 @@ discovery. So a cli app can be as little as one line of code in `main()`. "args": ["--help"], "output": "A new cli application" } --> -``` go +```go package main import ( - "os" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - (&cli.App{}).Run(os.Args) + (&cli.App{}).Run(os.Args) } ``` @@ -29,31 +29,30 @@ action to execute and some help documentation: -``` go +```go package main import ( - "fmt" - "log" - "os" + "fmt" + "log" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - app := &cli.App{ - Name: "boom", - Usage: "make an explosive entrance", - Action: func(c *cli.Context) error { - fmt.Println("boom! I say!") - return nil - }, - } + app := &cli.App{ + Name: "boom", + Usage: "make an explosive entrance", + Action: func(*cli.Context) error { + fmt.Println("boom! I say!") + return nil + }, + } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } ``` @@ -72,50 +71,49 @@ Start by creating a directory named `greet`, and within it, add a file, -``` go +```go package main import ( - "fmt" - "log" - "os" + "fmt" + "log" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - app := &cli.App{ - Name: "greet", - Usage: "fight the loneliness!", - Action: func(c *cli.Context) error { - fmt.Println("Hello friend!") - return nil - }, - } + app := &cli.App{ + Name: "greet", + Usage: "fight the loneliness!", + Action: func(*cli.Context) error { + fmt.Println("Hello friend!") + return nil + }, + } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } ``` Install our command to the `$GOPATH/bin` directory: -``` +```sh-session $ go install ``` Finally run our new command: -``` +```sh-session $ greet Hello friend! ``` cli also generates neat help text: -``` +```sh-session $ greet help NAME: greet - fight the loneliness! @@ -137,29 +135,28 @@ You can lookup arguments by calling the `Args` function on `cli.Context`, e.g.: -``` go +```go package main import ( - "fmt" - "log" - "os" + "fmt" + "log" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - app := &cli.App{ - Action: func(c *cli.Context) error { - fmt.Printf("Hello %q", c.Args().Get(0)) - return nil - }, - } + app := &cli.App{ + Action: func(cCtx *cli.Context) error { + fmt.Printf("Hello %q", cCtx.Args().Get(0)) + return nil + }, + } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } ``` @@ -170,44 +167,43 @@ Setting and querying flags is simple. -``` go +```go package main import ( - "fmt" - "log" - "os" + "fmt" + "log" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - app := &cli.App{ - Flags: []cli.Flag { - &cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", - }, - }, - Action: func(c *cli.Context) error { - name := "Nefertiti" - if c.NArg() > 0 { - name = c.Args().Get(0) - } - if c.String("lang") == "spanish" { - fmt.Println("Hola", name) - } else { - fmt.Println("Hello", name) - } - return nil - }, - } + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + }, + }, + Action: func(cCtx *cli.Context) error { + name := "Nefertiti" + if cCtx.NArg() > 0 { + name = cCtx.Args().Get(0) + } + if cCtx.String("lang") == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil + }, + } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } ``` @@ -217,47 +213,46 @@ scanned. -``` go +```go package main import ( - "log" - "os" - "fmt" + "fmt" + "log" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - var language string + var language string - app := &cli.App{ - Flags: []cli.Flag { - &cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", - Destination: &language, - }, - }, - Action: func(c *cli.Context) error { - name := "someone" - if c.NArg() > 0 { - name = c.Args().Get(0) - } - if language == "spanish" { - fmt.Println("Hola", name) - } else { - fmt.Println("Hello", name) - } - return nil - }, - } + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + Destination: &language, + }, + }, + Action: func(cCtx *cli.Context) error { + name := "someone" + if cCtx.NArg() > 0 { + name = cCtx.Args().Get(0) + } + if language == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil + }, + } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } ``` @@ -278,27 +273,26 @@ For example this: package main import ( - "log" - "os" + "log" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "config", - Aliases: []string{"c"}, - Usage: "Load configuration from `FILE`", - }, - }, - } + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: "Load configuration from `FILE`", + }, + }, + } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } ``` @@ -320,32 +314,31 @@ list for the `Name`. e.g. "args": ["--help"], "output": "--lang value, -l value.*language for the greeting.*default: \"english\"" } --> -``` go +```go package main import ( - "log" - "os" + "log" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - app := &cli.App{ - Flags: []cli.Flag { - &cli.StringFlag{ - Name: "lang", - Aliases: []string{"l"}, - Value: "english", - Usage: "language for the greeting", - }, - }, - } + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + }, + }, + } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } ``` @@ -365,59 +358,58 @@ For example this: "args": ["--help"], "output": ".*Load configuration from FILE\n.*\n.*Language for the greeting.*" } --> -``` go +```go package main import ( - "log" - "os" - "sort" + "log" + "os" + "sort" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "lang", - Aliases: []string{"l"}, - Value: "english", - Usage: "Language for the greeting", - }, - &cli.StringFlag{ - Name: "config", - Aliases: []string{"c"}, - Usage: "Load configuration from `FILE`", - }, - }, - Commands: []*cli.Command{ - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - return nil - }, - }, - { - Name: "add", - Aliases: []string{"a"}, - Usage: "add a task to the list", - Action: func(c *cli.Context) error { - return nil - }, - }, - }, - } + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "Language for the greeting", + }, + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: "Load configuration from `FILE`", + }, + }, + Commands: []*cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(*cli.Context) error { + return nil + }, + }, + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(*cli.Context) error { + return nil + }, + }, + }, + } - sort.Sort(cli.FlagsByName(app.Flags)) - sort.Sort(cli.CommandsByName(app.Commands)) + sort.Sort(cli.FlagsByName(app.Flags)) + sort.Sort(cli.CommandsByName(app.Commands)) - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } ``` @@ -436,33 +428,32 @@ You can also have the default value set from the environment via `EnvVars`. e.g "args": ["--help"], "output": "language for the greeting.*APP_LANG" } --> -``` go +```go package main import ( - "log" - "os" + "log" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - app := &cli.App{ - Flags: []cli.Flag { - &cli.StringFlag{ - Name: "lang", - Aliases: []string{"l"}, - Value: "english", - Usage: "language for the greeting", - EnvVars: []string{"APP_LANG"}, - }, - }, - } + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + EnvVars: []string{"APP_LANG"}, + }, + }, + } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } ``` @@ -473,33 +464,32 @@ resolves is used. "args": ["--help"], "output": "language for the greeting.*LEGACY_COMPAT_LANG.*APP_LANG.*LANG" } --> -``` go +```go package main import ( - "log" - "os" + "log" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "lang", - Aliases: []string{"l"}, - Value: "english", - Usage: "language for the greeting", - EnvVars: []string{"LEGACY_COMPAT_LANG", "APP_LANG", "LANG"}, - }, - }, - } + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang", + Aliases: []string{"l"}, + Value: "english", + Usage: "language for the greeting", + EnvVars: []string{"LEGACY_COMPAT_LANG", "APP_LANG", "LANG"}, + }, + }, + } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } ``` @@ -511,32 +501,31 @@ You can also have the default value set from file via `FilePath`. e.g. "args": ["--help"], "output": "password for the mysql database" } --> -``` go +```go package main import ( - "log" - "os" + "log" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - app := cli.NewApp() + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "password", + Aliases: []string{"p"}, + Usage: "password for the mysql database", + FilePath: "/etc/mysql/password", + }, + }, + } - app.Flags = []cli.Flag { - &cli.StringFlag{ - Name: "password", - Aliases: []string{"p"}, - Usage: "password for the mysql database", - FilePath: "/etc/mysql/password", - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } ``` @@ -549,21 +538,24 @@ There is a separate package altsrc that adds support for getting flag values from other file input sources. Currently supported input source formats: -* YAML -* JSON -* TOML + + YAML + JSON + TOML In order to get values for a flag from an alternate input source the following code would be added to wrap an existing cli.Flag like below: -``` go +```go + // --- >8 --- altsrc.NewIntFlag(&cli.IntFlag{Name: "test"}) ``` Initialization must also occur for these flags. Below is an example initializing getting data from a yaml file below. -``` go +```go + // --- >8 --- command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) ``` @@ -573,9 +565,9 @@ the yaml input source for any flags that are defined on that command. As a note the "load" flag used would also have to be defined on the command flags in order for this code snippet to work. -Currently only YAML, JSON, and TOML files are supported but developers can add support -for other input sources by implementing the altsrc.InputSourceContext for their -given sources. +Currently only YAML, JSON, and TOML files are supported but developers can add +support for other input sources by implementing the altsrc.InputSourceContext +for their given sources. Here is a more complete sample of a command using YAML support: @@ -583,33 +575,33 @@ Here is a more complete sample of a command using YAML support: "args": ["--help"], "output": "--test value.*default: 0" } --> -``` go +```go package main import ( - "fmt" - "os" + "fmt" + "os" - "github.com/urfave/cli/v2" - "github.com/urfave/cli/v2/altsrc" + "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2/altsrc" ) func main() { - flags := []cli.Flag{ - altsrc.NewIntFlag(&cli.IntFlag{Name: "test"}), - &cli.StringFlag{Name: "load"}, - } + flags := []cli.Flag{ + altsrc.NewIntFlag(&cli.IntFlag{Name: "test"}), + &cli.StringFlag{Name: "load"}, + } - app := &cli.App{ - Action: func(c *cli.Context) error { - fmt.Println("--test value.*default: 0") - return nil - }, - Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")), - Flags: flags, - } + app := &cli.App{ + Action: func(*cli.Context) error { + fmt.Println("--test value.*default: 0") + return nil + }, + Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")), + Flags: flags, + } - app.Run(os.Args) + app.Run(os.Args) } ``` @@ -627,40 +619,36 @@ Take for example this app that requires the `lang` flag: package main import ( - "fmt" - "log" - "os" + "fmt" + "log" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - app := cli.NewApp() + app := &cli.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + Required: true, + }, + }, + Action: func(cCtx *cli.Context) error { + output := "Hello" + if cCtx.String("lang") == "spanish" { + output = "Hola" + } + fmt.Println(output) + return nil + }, + } - 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 err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } ``` @@ -672,7 +660,9 @@ 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. +Sometimes it's useful to specify a flag's default help-text value within the +flag declaration. This can be useful if the default value for a flag is a +computed value. The default value can be set via the `DefaultText` struct field. For example this: @@ -684,28 +674,27 @@ For example this: package main import ( - "log" - "os" + "log" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.IntFlag{ - Name: "port", - Usage: "Use a randomized port", - Value: 0, - DefaultText: "random", - }, - }, - } + app := &cli.App{ + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "port", + Usage: "Use a randomized port", + Value: 0, + DefaultText: "random", + }, + }, + } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } ``` @@ -736,64 +725,63 @@ Subcommands can be defined for a more git-like command line app. package main import ( - "fmt" - "log" - "os" + "fmt" + "log" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - app := &cli.App{ - Commands: []*cli.Command{ - { - Name: "add", - Aliases: []string{"a"}, - Usage: "add a task to the list", - Action: func(c *cli.Context) error { - fmt.Println("added task: ", c.Args().First()) - return nil - }, - }, - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - fmt.Println("completed task: ", c.Args().First()) - return nil - }, - }, - { - Name: "template", - Aliases: []string{"t"}, - Usage: "options for task templates", - Subcommands: []*cli.Command{ - { - Name: "add", - Usage: "add a new template", - Action: func(c *cli.Context) error { - fmt.Println("new task template: ", c.Args().First()) - return nil - }, - }, - { - Name: "remove", - Usage: "remove an existing template", - Action: func(c *cli.Context) error { - fmt.Println("removed task template: ", c.Args().First()) - return nil - }, - }, - }, - }, - }, - } + app := &cli.App{ + Commands: []*cli.Command{ + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(cCtx *cli.Context) error { + fmt.Println("added task: ", cCtx.Args().First()) + return nil + }, + }, + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(cCtx *cli.Context) error { + fmt.Println("completed task: ", cCtx.Args().First()) + return nil + }, + }, + { + Name: "template", + Aliases: []string{"t"}, + Usage: "options for task templates", + Subcommands: []*cli.Command{ + { + Name: "add", + Usage: "add a new template", + Action: func(cCtx *cli.Context) error { + fmt.Println("new task template: ", cCtx.Args().First()) + return nil + }, + }, + { + Name: "remove", + Usage: "remove an existing template", + Action: func(cCtx *cli.Context) error { + fmt.Println("removed task template: ", cCtx.Args().First()) + return nil + }, + }, + }, + }, + }, + } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } ``` @@ -801,41 +789,38 @@ func main() { For additional organization in apps that have many subcommands, you can associate a category for each command to group them together in the help -output. - -E.g. +output, e.g.: ```go package main import ( - "log" - "os" + "log" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - app := &cli.App{ - Commands: []*cli.Command{ - { - Name: "noop", - }, - { - Name: "add", - Category: "template", - }, - { - Name: "remove", - Category: "template", - }, - }, - } + app := &cli.App{ + Commands: []*cli.Command{ + { + Name: "noop", + }, + { + Name: "add", + Category: "template", + }, + { + Name: "remove", + Category: "template", + }, + }, + } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } ``` @@ -859,36 +844,35 @@ may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a -``` go +```go package main import ( - "log" - "os" + "log" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - app := &cli.App{ - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "ginger-crouton", - Usage: "is it in the soup?", - }, - }, - Action: func(ctx *cli.Context) error { - if !ctx.Bool("ginger-crouton") { - return cli.Exit("Ginger croutons are not in the soup", 86) - } - return nil - }, - } + app := &cli.App{ + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "ginger-crouton", + Usage: "is it in the soup?", + }, + }, + Action: func(ctx *cli.Context) error { + if !ctx.Bool("ginger-crouton") { + return cli.Exit("Ginger croutons are not in the soup", 86) + } + return nil + }, + } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } ``` @@ -896,7 +880,7 @@ func main() { Traditional use of options using their shortnames look like this: -``` +```sh-session $ cmd -s -o -m "Some message" ``` @@ -909,42 +893,42 @@ example: "args": ["short", "-som", "Some message"], "output": "serve: true\noption: true\nmessage: Some message\n" } --> -``` go +```go package main import ( - "fmt" - "log" - "os" + "fmt" + "log" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - app := &cli.App{} - app.UseShortOptionHandling = true - app.Commands = []*cli.Command{ - { - Name: "short", - Usage: "complete a task on the list", - Flags: []cli.Flag{ - &cli.BoolFlag{Name: "serve", Aliases: []string{"s"}}, - &cli.BoolFlag{Name: "option", Aliases: []string{"o"}}, - &cli.StringFlag{Name: "message", Aliases: []string{"m"}}, - }, - Action: func(c *cli.Context) error { - fmt.Println("serve:", c.Bool("serve")) - fmt.Println("option:", c.Bool("option")) - fmt.Println("message:", c.String("message")) - return nil - }, - }, - } + app := &cli.App{ + UseShortOptionHandling: true, + Commands: []*cli.Command{ + { + Name: "short", + Usage: "complete a task on the list", + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "serve", Aliases: []string{"s"}}, + &cli.BoolFlag{Name: "option", Aliases: []string{"o"}}, + &cli.StringFlag{Name: "message", Aliases: []string{"m"}}, + }, + Action: func(cCtx *cli.Context) error { + fmt.Println("serve:", cCtx.Bool("serve")) + fmt.Println("option:", cCtx.Bool("option")) + fmt.Println("message:", cCtx.String("message")) + return nil + }, + }, + }, + } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } ``` @@ -953,7 +937,7 @@ optionally one non-bool flag `message`, with the short options of `-s`, `-o`, and `-m` respectively, setting `UseShortOptionHandling` will also support the following syntax: -``` +```sh-session $ cmd -som "Some message" ``` @@ -964,69 +948,73 @@ have a single leading `-` or this will result in failures. For example, ### Bash Completion -You can enable completion commands by setting the `EnableBashCompletion` -flag on the `App` object to `true`. By default, this setting will allow auto-completion -for an app's subcommands, but you can write your own completion methods for -the App or its subcommands as well. +You can enable completion commands by setting the `EnableBashCompletion` flag on +the `App` object to `true`. By default, this setting will allow auto-completion +for an app's subcommands, but you can write your own completion methods for the +App or its subcommands as well. #### Default auto-completion ```go package main + import ( "fmt" "log" "os" + "github.com/urfave/cli/v2" ) + func main() { - app := cli.NewApp() - app.EnableBashCompletion = true - app.Commands = []*cli.Command{ - { - Name: "add", - Aliases: []string{"a"}, - Usage: "add a task to the list", - Action: func(c *cli.Context) error { - fmt.Println("added task: ", c.Args().First()) - return nil - }, - }, - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - fmt.Println("completed task: ", c.Args().First()) - return nil - }, - }, - { - Name: "template", - Aliases: []string{"t"}, - Usage: "options for task templates", - Subcommands: []*cli.Command{ - { - Name: "add", - Usage: "add a new template", - Action: func(c *cli.Context) error { - fmt.Println("new task template: ", c.Args().First()) - return nil - }, + app := &cli.App{ + EnableBashCompletion: true, + Commands: []*cli.Command{ + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(cCtx *cli.Context) error { + fmt.Println("added task: ", cCtx.Args().First()) + return nil }, - { - Name: "remove", - Usage: "remove an existing template", - Action: func(c *cli.Context) error { - fmt.Println("removed task template: ", c.Args().First()) - return nil + }, + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(cCtx *cli.Context) error { + fmt.Println("completed task: ", cCtx.Args().First()) + return nil + }, + }, + { + Name: "template", + Aliases: []string{"t"}, + Usage: "options for task templates", + Subcommands: []*cli.Command{ + { + Name: "add", + Usage: "add a new template", + Action: func(cCtx *cli.Context) error { + fmt.Println("new task template: ", cCtx.Args().First()) + return nil + }, + }, + { + Name: "remove", + Usage: "remove an existing template", + Action: func(cCtx *cli.Context) error { + fmt.Println("removed task template: ", cCtx.Args().First()) + return nil + }, }, }, }, }, } - err := app.Run(os.Args) - if err != nil { + + if err := app.Run(os.Args); err != nil { log.Fatal(err) } } @@ -1038,48 +1026,47 @@ func main() { "args": ["complete", "--generate-bash-completion"], "output": "laundry" } --> -``` go +```go package main import ( - "fmt" - "log" - "os" + "fmt" + "log" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} + tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} - app := &cli.App{ - EnableBashCompletion: true, - Commands: []*cli.Command{ - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - fmt.Println("completed task: ", c.Args().First()) - return nil - }, - BashComplete: func(c *cli.Context) { - // This will complete if no args are passed - if c.NArg() > 0 { - return - } - for _, t := range tasks { - fmt.Println(t) - } - }, - }, - }, - } + app := &cli.App{ + EnableBashCompletion: true, + Commands: []*cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(cCtx *cli.Context) error { + fmt.Println("completed task: ", cCtx.Args().First()) + return nil + }, + BashComplete: func(cCtx *cli.Context) { + // This will complete if no args are passed + if cCtx.NArg() > 0 { + return + } + for _, t := range tasks { + fmt.Println(t) + } + }, + }, + }, + } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } ``` ![](/docs/v2/images/custom-bash-autocomplete.gif) @@ -1089,14 +1076,18 @@ func main() { To enable auto-completion for the current shell session, a bash script, `autocomplete/bash_autocomplete` is included in this repo. -To use `autocomplete/bash_autocomplete` set an environment variable named `PROG` to -the name of your program and then `source` the `autocomplete/bash_autocomplete` file. +To use `autocomplete/bash_autocomplete` set an environment variable named `PROG` +to the name of your program and then `source` the +`autocomplete/bash_autocomplete` file. For example, if your cli program is called `myprogram`: -`PROG=myprogram source path/to/cli/autocomplete/bash_autocomplete` +```sh-session +$ PROG=myprogram source path/to/cli/autocomplete/bash_autocomplete +``` -Auto-completion is now enabled for the current shell, but will not persist into a new shell. +Auto-completion is now enabled for the current shell, but will not persist into +a new shell. #### Distribution and Persistent Autocompletion @@ -1105,28 +1096,30 @@ it to the name of the program you wish to add autocomplete support for (or automatically install it there if you are distributing a package). Don't forget to source the file or restart your shell to activate the auto-completion. -``` -sudo cp path/to/autocomplete/bash_autocomplete /etc/bash_completion.d/ -source /etc/bash_completion.d/ +```sh-session +$ sudo cp path/to/autocomplete/bash_autocomplete /etc/bash_completion.d/ +$ source /etc/bash_completion.d/ ``` Alternatively, you can just document that users should `source` the generic -`autocomplete/bash_autocomplete` and set `$PROG` within their bash configuration +`autocomplete/bash_autocomplete` and set `$PROG` within their bash configuration file, adding these lines: +```sh-session +$ PROG= +$ source path/to/cli/autocomplete/bash_autocomplete ``` -PROG= -source path/to/cli/autocomplete/bash_autocomplete -``` -Keep in mind that if they are enabling auto-completion for more than one program, -they will need to set `PROG` and source `autocomplete/bash_autocomplete` for each -program, like so: -``` -PROG= -source path/to/cli/autocomplete/bash_autocomplete -PROG= -source path/to/cli/autocomplete/bash_autocomplete +Keep in mind that if they are enabling auto-completion for more than one +program, they will need to set `PROG` and source +`autocomplete/bash_autocomplete` for each program, like so: + +```sh-session +$ PROG= +$ source path/to/cli/autocomplete/bash_autocomplete + +$ PROG= +$ source path/to/cli/autocomplete/bash_autocomplete ``` #### Customization @@ -1138,64 +1131,73 @@ The default shell completion flag (`--generate-bash-completion`) is defined as "args": ["--generate-bash-completion"], "output": "wat\nhelp\nh" } --> -``` go +```go package main import ( - "log" - "os" + "log" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - app := &cli.App{ - EnableBashCompletion: true, - Commands: []*cli.Command{ - { - Name: "wat", - }, - }, - } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } + app := &cli.App{ + EnableBashCompletion: true, + Commands: []*cli.Command{ + { + Name: "wat", + }, + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } ``` #### ZSH Support -Auto-completion for ZSH is also supported using the `autocomplete/zsh_autocomplete` -file included in this repo. One environment variable is used, `PROG`. Set -`PROG` to the program name as before, and then `source path/to/autocomplete/zsh_autocomplete`. -Adding the following lines to your ZSH configuration file (usually `.zshrc`) -will allow the auto-completion to persist across new shells: +Auto-completion for ZSH is also supported using the +`autocomplete/zsh_autocomplete` file included in this repo. One environment +variable is used, `PROG`. Set `PROG` to the program name as before, and then +`source path/to/autocomplete/zsh_autocomplete`. Adding the following lines to +your ZSH configuration file (usually `.zshrc`) will allow the auto-completion to +persist across new shells: + +```sh-session +$ PROG= +$ source path/to/autocomplete/zsh_autocomplete ``` -PROG= -source path/to/autocomplete/zsh_autocomplete -``` + #### ZSH default auto-complete example ![](/docs/v2/images/default-zsh-autocomplete.gif) + #### ZSH custom auto-complete example ![](/docs/v2/images/custom-zsh-autocomplete.gif) #### PowerShell Support -Auto-completion for PowerShell is also supported using the `autocomplete/powershell_autocomplete.ps1` -file included in this repo. -Rename the script to `.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. +Auto-completion for PowerShell is also supported using the +`autocomplete/powershell_autocomplete.ps1` file included in this repo. -To activate it, enter `& path/to/autocomplete/.ps1` +Rename the script to `.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 persist across new shells, open the PowerShell profile (with `code $profile` or `notepad $profile`) -and add the line: -``` +To activate it, enter: + +```powershell & path/to/autocomplete/.ps1 ``` +To persist across new shells, open the PowerShell profile (with `code $profile` +or `notepad $profile`) and add the line: + +```powershell +& path/to/autocomplete/.ps1 +``` ### Generated Help Text @@ -1214,20 +1216,20 @@ e.g.: -``` go +```go package main import ( - "fmt" - "io" - "os" + "fmt" + "io" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - // EXAMPLE: Append to an existing template - cli.AppHelpTemplate = fmt.Sprintf(`%s + // EXAMPLE: Append to an existing template + cli.AppHelpTemplate = fmt.Sprintf(`%s WEBSITE: http://awesometown.example.com @@ -1235,8 +1237,8 @@ SUPPORT: support@awesometown.example.com `, cli.AppHelpTemplate) - // EXAMPLE: Override a template - cli.AppHelpTemplate = `NAME: + // EXAMPLE: Override a template + cli.AppHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} @@ -1257,12 +1259,12 @@ VERSION: {{end}} ` - // EXAMPLE: Replace the `HelpPrinter` func - cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { - fmt.Println("Ha HA. I pwnd the help!!1") - } + // EXAMPLE: Replace the `HelpPrinter` func + cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { + fmt.Println("Ha HA. I pwnd the help!!1") + } - (&cli.App{}).Run(os.Args) + (&cli.App{}).Run(os.Args) } ``` @@ -1273,24 +1275,24 @@ setting `cli.HelpFlag`, e.g.: "args": ["--halp"], "output": "haaaaalp.*HALP" } --> -``` go +```go package main import ( - "os" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - cli.HelpFlag = &cli.BoolFlag{ - Name: "haaaaalp", - Aliases: []string{"halp"}, - Usage: "HALP", - EnvVars: []string{"SHOW_HALP", "HALPPLZ"}, - } + cli.HelpFlag = &cli.BoolFlag{ + Name: "haaaaalp", + Aliases: []string{"halp"}, + Usage: "HALP", + EnvVars: []string{"SHOW_HALP", "HALPPLZ"}, + } - (&cli.App{}).Run(os.Args) + (&cli.App{}).Run(os.Args) } ``` @@ -1309,105 +1311,110 @@ setting `cli.VersionFlag`, e.g.: "args": ["--print-version"], "output": "partay version v19\\.99\\.0" } --> -``` go +```go package main import ( - "os" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - cli.VersionFlag = &cli.BoolFlag{ - Name: "print-version", - Aliases: []string{"V"}, - Usage: "print only the version", - } + cli.VersionFlag = &cli.BoolFlag{ + Name: "print-version", + Aliases: []string{"V"}, + Usage: "print only the version", + } - app := &cli.App{ - Name: "partay", - Version: "v19.99.0", - } - app.Run(os.Args) + app := &cli.App{ + Name: "partay", + Version: "v19.99.0", + } + app.Run(os.Args) } ``` -Alternatively, the version printer at `cli.VersionPrinter` may be overridden, e.g.: +Alternatively, the version printer at `cli.VersionPrinter` may be overridden, +e.g.: -``` go +```go package main import ( - "fmt" - "os" + "fmt" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) var ( - Revision = "fafafaf" + Revision = "fafafaf" ) func main() { - cli.VersionPrinter = func(c *cli.Context) { - fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision) - } + cli.VersionPrinter = func(cCtx *cli.Context) { + fmt.Printf("version=%s revision=%s\n", cCtx.App.Version, Revision) + } - app := &cli.App{ - Name: "partay", - Version: "v19.99.0", - } - app.Run(os.Args) + app := &cli.App{ + Name: "partay", + Version: "v19.99.0", + } + app.Run(os.Args) } ``` ### Timestamp Flag -Using the timestamp flag is simple. Please refer to [`time.Parse`](https://golang.org/pkg/time/#example_Parse) to get possible formats. +Using the timestamp flag is simple. Please refer to +[`time.Parse`](https://golang.org/pkg/time/#example_Parse) to get possible +formats. -``` go +```go package main import ( - "fmt" - "log" - "os" + "fmt" + "log" + "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func main() { - app := &cli.App{ - Flags: []cli.Flag { - &cli.TimestampFlag{Name: "meeting", Layout: "2006-01-02T15:04:05"}, - }, - Action: func(c *cli.Context) error { - fmt.Printf("%s", c.Timestamp("meeting").String()) - return nil - }, - } + app := &cli.App{ + Flags: []cli.Flag{ + &cli.TimestampFlag{Name: "meeting", Layout: "2006-01-02T15:04:05"}, + }, + Action: func(cCtx *cli.Context) error { + fmt.Printf("%s", cCtx.Timestamp("meeting").String()) + return nil + }, + } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } ``` -In this example the flag could be used like this : +In this example the flag could be used like this: -`myapp --meeting 2019-08-12T15:04:05` +```sh-session +$ myapp --meeting 2019-08-12T15:04:05 +``` -Side note: quotes may be necessary around the date depending on your layout (if you have spaces for instance) +Side note: quotes may be necessary around the date depending on your layout (if +you have spaces for instance) ### Suggestions @@ -1419,265 +1426,265 @@ available. ### Full API Example **Notice**: This is a contrived (functioning) example meant strictly for API -demonstration purposes. Use of one's imagination is encouraged. +demonstration purposes. Use of one's imagination is encouraged. -``` go +```go package main import ( - "errors" - "flag" - "fmt" - "io" - "io/ioutil" - "os" - "time" + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "time" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2" ) func init() { - cli.AppHelpTemplate += "\nCUSTOMIZED: you bet ur muffins\n" - cli.CommandHelpTemplate += "\nYMMV\n" - cli.SubcommandHelpTemplate += "\nor something\n" + cli.AppHelpTemplate += "\nCUSTOMIZED: you bet ur muffins\n" + cli.CommandHelpTemplate += "\nYMMV\n" + cli.SubcommandHelpTemplate += "\nor something\n" - cli.HelpFlag = &cli.BoolFlag{Name: "halp"} - cli.VersionFlag = &cli.BoolFlag{Name: "print-version", Aliases: []string{"V"}} + cli.HelpFlag = &cli.BoolFlag{Name: "halp"} + cli.VersionFlag = &cli.BoolFlag{Name: "print-version", Aliases: []string{"V"}} - cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { - fmt.Fprintf(w, "best of luck to you\n") - } - cli.VersionPrinter = func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "version=%s\n", c.App.Version) - } - cli.OsExiter = func(c int) { - fmt.Fprintf(cli.ErrWriter, "refusing to exit %d\n", c) - } - cli.ErrWriter = ioutil.Discard - cli.FlagStringer = func(fl cli.Flag) string { - return fmt.Sprintf("\t\t%s", fl.Names()[0]) - } + cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { + fmt.Fprintf(w, "best of luck to you\n") + } + cli.VersionPrinter = func(cCtx *cli.Context) { + fmt.Fprintf(cCtx.App.Writer, "version=%s\n", cCtx.App.Version) + } + cli.OsExiter = func(cCtx int) { + fmt.Fprintf(cli.ErrWriter, "refusing to exit %d\n", cCtx) + } + cli.ErrWriter = ioutil.Discard + cli.FlagStringer = func(fl cli.Flag) string { + return fmt.Sprintf("\t\t%s", fl.Names()[0]) + } } type hexWriter struct{} func (w *hexWriter) Write(p []byte) (int, error) { - for _, b := range p { - fmt.Printf("%x", b) - } - fmt.Printf("\n") + for _, b := range p { + fmt.Printf("%x", b) + } + fmt.Printf("\n") - return len(p), nil + return len(p), nil } type genericType struct { - s string + s string } func (g *genericType) Set(value string) error { - g.s = value - return nil + g.s = value + return nil } func (g *genericType) String() string { - return g.s + return g.s } func main() { - app := &cli.App{ - Name: "kənˈtrīv", - Version: "v19.99.0", - Compiled: time.Now(), - Authors: []*cli.Author{ - &cli.Author{ - Name: "Example Human", - Email: "human@example.com", - }, - }, - Copyright: "(c) 1999 Serious Enterprise", - HelpName: "contrive", - Usage: "demonstrate available API", - UsageText: "contrive - demonstrating the available API", - ArgsUsage: "[args and such]", - Commands: []*cli.Command{ - &cli.Command{ - Name: "doo", - Aliases: []string{"do"}, - Category: "motion", - Usage: "do the doo", - UsageText: "doo - does the dooing", - Description: "no really, there is a lot of dooing to be done", - ArgsUsage: "[arrgh]", - Flags: []cli.Flag{ - &cli.BoolFlag{Name: "forever", Aliases: []string{"forevvarr"}}, - }, - Subcommands: []*cli.Command{ - &cli.Command{ - Name: "wop", - Action: wopAction, - }, - }, - SkipFlagParsing: false, - HideHelp: false, - Hidden: false, - HelpName: "doo!", - BashComplete: func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "--better\n") - }, - Before: func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "brace for impact\n") - return nil - }, - After: func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "did we lose anyone?\n") - return nil - }, - Action: func(c *cli.Context) error { - c.Command.FullName() - c.Command.HasName("wop") - c.Command.Names() - c.Command.VisibleFlags() - fmt.Fprintf(c.App.Writer, "dodododododoodododddooooododododooo\n") - if c.Bool("forever") { - c.Command.Run(c) - } - return nil - }, - OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { - fmt.Fprintf(c.App.Writer, "for shame\n") - return err - }, - }, - }, - Flags: []cli.Flag{ - &cli.BoolFlag{Name: "fancy"}, - &cli.BoolFlag{Value: true, Name: "fancier"}, - &cli.DurationFlag{Name: "howlong", Aliases: []string{"H"}, Value: time.Second * 3}, - &cli.Float64Flag{Name: "howmuch"}, - &cli.GenericFlag{Name: "wat", Value: &genericType{}}, - &cli.Int64Flag{Name: "longdistance"}, - &cli.Int64SliceFlag{Name: "intervals"}, - &cli.IntFlag{Name: "distance"}, - &cli.IntSliceFlag{Name: "times"}, - &cli.StringFlag{Name: "dance-move", Aliases: []string{"d"}}, - &cli.StringSliceFlag{Name: "names", Aliases: []string{"N"}}, - &cli.UintFlag{Name: "age"}, - &cli.Uint64Flag{Name: "bigage"}, - }, - EnableBashCompletion: true, - HideHelp: false, - HideVersion: false, - BashComplete: func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") - }, - Before: func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "HEEEERE GOES\n") - return nil - }, - After: func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "Phew!\n") - return nil - }, - CommandNotFound: func(c *cli.Context, command string) { - fmt.Fprintf(c.App.Writer, "Thar be no %q here.\n", command) - }, - OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { - if isSubcommand { - return err - } + app := &cli.App{ + Name: "kənˈtrīv", + Version: "v19.99.0", + Compiled: time.Now(), + Authors: []*cli.Author{ + &cli.Author{ + Name: "Example Human", + Email: "human@example.com", + }, + }, + Copyright: "(c) 1999 Serious Enterprise", + HelpName: "contrive", + Usage: "demonstrate available API", + UsageText: "contrive - demonstrating the available API", + ArgsUsage: "[args and such]", + Commands: []*cli.Command{ + &cli.Command{ + Name: "doo", + Aliases: []string{"do"}, + Category: "motion", + Usage: "do the doo", + UsageText: "doo - does the dooing", + Description: "no really, there is a lot of dooing to be done", + ArgsUsage: "[arrgh]", + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "forever", Aliases: []string{"forevvarr"}}, + }, + Subcommands: []*cli.Command{ + &cli.Command{ + Name: "wop", + Action: wopAction, + }, + }, + SkipFlagParsing: false, + HideHelp: false, + Hidden: false, + HelpName: "doo!", + BashComplete: func(cCtx *cli.Context) { + fmt.Fprintf(cCtx.App.Writer, "--better\n") + }, + Before: func(cCtx *cli.Context) error { + fmt.Fprintf(cCtx.App.Writer, "brace for impact\n") + return nil + }, + After: func(cCtx *cli.Context) error { + fmt.Fprintf(cCtx.App.Writer, "did we lose anyone?\n") + return nil + }, + Action: func(cCtx *cli.Context) error { + cCtx.Command.FullName() + cCtx.Command.HasName("wop") + cCtx.Command.Names() + cCtx.Command.VisibleFlags() + fmt.Fprintf(cCtx.App.Writer, "dodododododoodododddooooododododooo\n") + if cCtx.Bool("forever") { + cCtx.Command.Run(cCtx) + } + return nil + }, + OnUsageError: func(cCtx *cli.Context, err error, isSubcommand bool) error { + fmt.Fprintf(cCtx.App.Writer, "for shame\n") + return err + }, + }, + }, + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "fancy"}, + &cli.BoolFlag{Value: true, Name: "fancier"}, + &cli.DurationFlag{Name: "howlong", Aliases: []string{"H"}, Value: time.Second * 3}, + &cli.Float64Flag{Name: "howmuch"}, + &cli.GenericFlag{Name: "wat", Value: &genericType{}}, + &cli.Int64Flag{Name: "longdistance"}, + &cli.Int64SliceFlag{Name: "intervals"}, + &cli.IntFlag{Name: "distance"}, + &cli.IntSliceFlag{Name: "times"}, + &cli.StringFlag{Name: "dance-move", Aliases: []string{"d"}}, + &cli.StringSliceFlag{Name: "names", Aliases: []string{"N"}}, + &cli.UintFlag{Name: "age"}, + &cli.Uint64Flag{Name: "bigage"}, + }, + EnableBashCompletion: true, + HideHelp: false, + HideVersion: false, + BashComplete: func(cCtx *cli.Context) { + fmt.Fprintf(cCtx.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") + }, + Before: func(cCtx *cli.Context) error { + fmt.Fprintf(cCtx.App.Writer, "HEEEERE GOES\n") + return nil + }, + After: func(cCtx *cli.Context) error { + fmt.Fprintf(cCtx.App.Writer, "Phew!\n") + return nil + }, + CommandNotFound: func(cCtx *cli.Context, command string) { + fmt.Fprintf(cCtx.App.Writer, "Thar be no %q here.\n", command) + }, + OnUsageError: func(cCtx *cli.Context, err error, isSubcommand bool) error { + if isSubcommand { + return err + } - fmt.Fprintf(c.App.Writer, "WRONG: %#v\n", err) - return nil - }, - Action: func(c *cli.Context) error { - cli.DefaultAppComplete(c) - cli.HandleExitCoder(errors.New("not an exit coder, though")) - cli.ShowAppHelp(c) - cli.ShowCommandCompletions(c, "nope") - cli.ShowCommandHelp(c, "also-nope") - cli.ShowCompletions(c) - cli.ShowSubcommandHelp(c) - cli.ShowVersion(c) + fmt.Fprintf(cCtx.App.Writer, "WRONG: %#v\n", err) + return nil + }, + Action: func(cCtx *cli.Context) error { + cli.DefaultAppComplete(cCtx) + cli.HandleExitCoder(errors.New("not an exit coder, though")) + cli.ShowAppHelp(cCtx) + cli.ShowCommandCompletions(cCtx, "nope") + cli.ShowCommandHelp(cCtx, "also-nope") + cli.ShowCompletions(cCtx) + cli.ShowSubcommandHelp(cCtx) + cli.ShowVersion(cCtx) - fmt.Printf("%#v\n", c.App.Command("doo")) - if c.Bool("infinite") { - c.App.Run([]string{"app", "doo", "wop"}) - } + fmt.Printf("%#v\n", cCtx.App.Command("doo")) + if cCtx.Bool("infinite") { + cCtx.App.Run([]string{"app", "doo", "wop"}) + } - if c.Bool("forevar") { - c.App.RunAsSubcommand(c) - } - c.App.Setup() - fmt.Printf("%#v\n", c.App.VisibleCategories()) - fmt.Printf("%#v\n", c.App.VisibleCommands()) - fmt.Printf("%#v\n", c.App.VisibleFlags()) + if cCtx.Bool("forevar") { + cCtx.App.RunAsSubcommand(cCtx) + } + cCtx.App.Setup() + fmt.Printf("%#v\n", cCtx.App.VisibleCategories()) + fmt.Printf("%#v\n", cCtx.App.VisibleCommands()) + fmt.Printf("%#v\n", cCtx.App.VisibleFlags()) - fmt.Printf("%#v\n", c.Args().First()) - if c.Args().Len() > 0 { - fmt.Printf("%#v\n", c.Args().Get(1)) - } - fmt.Printf("%#v\n", c.Args().Present()) - fmt.Printf("%#v\n", c.Args().Tail()) + fmt.Printf("%#v\n", cCtx.Args().First()) + if cCtx.Args().Len() > 0 { + fmt.Printf("%#v\n", cCtx.Args().Get(1)) + } + fmt.Printf("%#v\n", cCtx.Args().Present()) + fmt.Printf("%#v\n", cCtx.Args().Tail()) - set := flag.NewFlagSet("contrive", 0) - nc := cli.NewContext(c.App, set, c) + set := flag.NewFlagSet("contrive", 0) + nc := cli.NewContext(cCtx.App, set, cCtx) - fmt.Printf("%#v\n", nc.Args()) - fmt.Printf("%#v\n", nc.Bool("nope")) - fmt.Printf("%#v\n", !nc.Bool("nerp")) - fmt.Printf("%#v\n", nc.Duration("howlong")) - fmt.Printf("%#v\n", nc.Float64("hay")) - fmt.Printf("%#v\n", nc.Generic("bloop")) - fmt.Printf("%#v\n", nc.Int64("bonk")) - fmt.Printf("%#v\n", nc.Int64Slice("burnks")) - fmt.Printf("%#v\n", nc.Int("bips")) - fmt.Printf("%#v\n", nc.IntSlice("blups")) - fmt.Printf("%#v\n", nc.String("snurt")) - fmt.Printf("%#v\n", nc.StringSlice("snurkles")) - fmt.Printf("%#v\n", nc.Uint("flub")) - fmt.Printf("%#v\n", nc.Uint64("florb")) + fmt.Printf("%#v\n", nc.Args()) + fmt.Printf("%#v\n", nc.Bool("nope")) + fmt.Printf("%#v\n", !nc.Bool("nerp")) + fmt.Printf("%#v\n", nc.Duration("howlong")) + fmt.Printf("%#v\n", nc.Float64("hay")) + fmt.Printf("%#v\n", nc.Generic("bloop")) + fmt.Printf("%#v\n", nc.Int64("bonk")) + fmt.Printf("%#v\n", nc.Int64Slice("burnks")) + fmt.Printf("%#v\n", nc.Int("bips")) + fmt.Printf("%#v\n", nc.IntSlice("blups")) + fmt.Printf("%#v\n", nc.String("snurt")) + fmt.Printf("%#v\n", nc.StringSlice("snurkles")) + fmt.Printf("%#v\n", nc.Uint("flub")) + fmt.Printf("%#v\n", nc.Uint64("florb")) - fmt.Printf("%#v\n", nc.FlagNames()) - fmt.Printf("%#v\n", nc.IsSet("wat")) - fmt.Printf("%#v\n", nc.Set("wat", "nope")) - fmt.Printf("%#v\n", nc.NArg()) - fmt.Printf("%#v\n", nc.NumFlags()) - fmt.Printf("%#v\n", nc.Lineage()[1]) - nc.Set("wat", "also-nope") + fmt.Printf("%#v\n", nc.FlagNames()) + fmt.Printf("%#v\n", nc.IsSet("wat")) + fmt.Printf("%#v\n", nc.Set("wat", "nope")) + fmt.Printf("%#v\n", nc.NArg()) + fmt.Printf("%#v\n", nc.NumFlags()) + fmt.Printf("%#v\n", nc.Lineage()[1]) + nc.Set("wat", "also-nope") - ec := cli.Exit("ohwell", 86) - fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) - fmt.Printf("made it!\n") - return ec - }, - Metadata: map[string]interface{}{ - "layers": "many", - "explicable": false, - "whatever-values": 19.99, - }, - } + ec := cli.Exit("ohwell", 86) + fmt.Fprintf(cCtx.App.Writer, "%d", ec.ExitCode()) + fmt.Printf("made it!\n") + return ec + }, + Metadata: map[string]interface{}{ + "layers": "many", + "explicable": false, + "whatever-values": 19.99, + }, + } - if os.Getenv("HEXY") != "" { - app.Writer = &hexWriter{} - app.ErrWriter = &hexWriter{} - } + if os.Getenv("HEXY") != "" { + app.Writer = &hexWriter{} + app.ErrWriter = &hexWriter{} + } - app.Run(os.Args) + app.Run(os.Args) } -func wopAction(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, ":wave: over here, eh\n") - return nil +func wopAction(cCtx *cli.Context) error { + fmt.Fprintf(cCtx.App.Writer, ":wave: over here, eh\n") + return nil } ``` ## Migrating From Older Releases -There are a small set of breaking changes between v1 and v2. -Converting is relatively straightforward and typically takes less than -an hour. Specific steps are included in -[Migration Guide: v1 to v2](../migrate-v1-to-v2.md). Also see the [pkg.go.dev docs](https://pkg.go.dev/github.com/urfave/cli/v2) for v2 API documentation. - +There are a small set of breaking changes between v1 and v2. Converting is +relatively straightforward and typically takes less than an hour. Specific steps +are included in [Migration Guide: v1 to v2](../migrate-v1-to-v2.md). Also see +the [pkg.go.dev docs](https://pkg.go.dev/github.com/urfave/cli/v2) for v2 API +documentation. From 32dec1ddaac72dad1d412745dbe8df15570e0c6b Mon Sep 17 00:00:00 2001 From: James Alavosus Date: Thu, 5 May 2022 23:50:22 -0400 Subject: [PATCH 116/125] feature: add DefaultCommand field to App See issue #1307 for context. --- app.go | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++--- app_test.go | 36 +++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/app.go b/app.go index 333bd57..a9ad131 100644 --- a/app.go +++ b/app.go @@ -7,6 +7,7 @@ import ( "io" "os" "path/filepath" + "reflect" "sort" "time" ) @@ -43,6 +44,9 @@ type App struct { Version string // Description of the program Description string + // DefaultCommand is the (optional) name of a command + // to run if no command names are passed as CLI arguments. + DefaultCommand string // List of commands to execute Commands []*Command // List of flags to parse @@ -333,13 +337,33 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { } } + var c *Command args := cCtx.Args() if args.Present() { name := args.First() - c := a.Command(name) - if c != nil { - return c.Run(cCtx) + if a.validCommandName(name) { + c = a.Command(name) + } else { + isFlagName := false + for _, flagName := range cCtx.FlagNames() { + if name == flagName { + isFlagName = true + break + } + } + if isFlagName { + argsWithDefault := a.argsWithDefaultCommand(args) + if !reflect.DeepEqual(args, argsWithDefault) { + c = a.Command(argsWithDefault.First()) + } + } } + } else if a.DefaultCommand != "" { + c = a.Command(a.DefaultCommand) + } + + if c != nil { + return c.Run(cCtx) } if a.Action == nil { @@ -570,6 +594,41 @@ func (a *App) handleExitCoder(cCtx *Context, err error) { } } +func (a *App) commandNames() []string { + var cmdNames []string + + for _, cmd := range a.Commands { + cmdNames = append(cmdNames, cmd.Names()...) + } + + return cmdNames +} + +func (a *App) validCommandName(checkCmdName string) bool { + valid := false + allCommandNames := a.commandNames() + + for _, cmdName := range allCommandNames { + if checkCmdName == cmdName { + valid = true + break + } + } + + return valid +} + +func (a *App) argsWithDefaultCommand(oldArgs Args) Args { + if a.DefaultCommand != "" { + rawArgs := append([]string{a.DefaultCommand}, oldArgs.Slice()...) + newArgs := args(rawArgs) + + return &newArgs + } + + return oldArgs +} + // Author represents someone who has contributed to a cli project. type Author struct { Name string // The Authors name diff --git a/app_test.go b/app_test.go index 3dd73ab..8c2d3a9 100644 --- a/app_test.go +++ b/app_test.go @@ -469,6 +469,42 @@ func TestApp_Command(t *testing.T) { } } +var defaultCommandAppTests = []struct { + cmdName string + defaultCmd string + expected bool +}{ + {"foobar", "foobar", true}, + {"batbaz", "foobar", true}, + {"b", "", true}, + {"f", "", true}, + {"", "foobar", true}, + {"", "", true}, + {" ", "", false}, + {"bat", "batbaz", false}, + {"nothing", "batbaz", false}, + {"nothing", "", false}, +} + +func TestApp_RunDefaultCommand(t *testing.T) { + for _, test := range defaultCommandAppTests { + testTitle := fmt.Sprintf("command=%[1]s-default=%[2]s", test.cmdName, test.defaultCmd) + t.Run(testTitle, func(t *testing.T) { + app := &App{ + DefaultCommand: test.defaultCmd, + Commands: []*Command{ + {Name: "foobar", Aliases: []string{"f"}}, + {Name: "batbaz", Aliases: []string{"b"}}, + }, + } + + err := app.Run([]string{"c", test.cmdName}) + expect(t, err == nil, test.expected) + }) + } + +} + func TestApp_Setup_defaultsReader(t *testing.T) { app := &App{} app.Setup() From 77feee843d87e3a4839fee5644e13ec85a264a27 Mon Sep 17 00:00:00 2001 From: James Alavosus Date: Fri, 6 May 2022 00:06:05 -0400 Subject: [PATCH 117/125] Implement slightly wonky setup for checking against ... subcommand names of a default command (should it be set) --- app.go | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/app.go b/app.go index a9ad131..7e64c2d 100644 --- a/app.go +++ b/app.go @@ -344,14 +344,26 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { if a.validCommandName(name) { c = a.Command(name) } else { - isFlagName := false - for _, flagName := range cCtx.FlagNames() { - if name == flagName { - isFlagName = true - break + hasDefault := a.DefaultCommand != "" + isFlagName := checkStringSliceIncludes(name, cCtx.FlagNames()) + + var ( + isDefaultSubcommand = false + defaultHasSubcommands = false + ) + + if hasDefault { + dc := a.Command(a.DefaultCommand) + defaultHasSubcommands = len(dc.Subcommands) > 0 + for _, dcSub := range dc.Subcommands { + if checkStringSliceIncludes(name, dcSub.Names()) { + isDefaultSubcommand = true + break + } } } - if isFlagName { + + if isFlagName || (hasDefault && (defaultHasSubcommands && isDefaultSubcommand)) { argsWithDefault := a.argsWithDefaultCommand(args) if !reflect.DeepEqual(args, argsWithDefault) { c = a.Command(argsWithDefault.First()) @@ -661,3 +673,15 @@ func HandleAction(action interface{}, cCtx *Context) (err error) { return errInvalidActionType } + +func checkStringSliceIncludes(want string, sSlice []string) bool { + found := false + for _, s := range sSlice { + if want == s { + found = true + break + } + } + + return found +} From 1b3da50f163ae34fb7b1c06085a025d13b921aba Mon Sep 17 00:00:00 2001 From: James Alavosus Date: Fri, 6 May 2022 00:19:36 -0400 Subject: [PATCH 118/125] Add test cases for subcommands of default command --- app_test.go | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/app_test.go b/app_test.go index 8c2d3a9..3dd837f 100644 --- a/app_test.go +++ b/app_test.go @@ -502,7 +502,60 @@ func TestApp_RunDefaultCommand(t *testing.T) { expect(t, err == nil, test.expected) }) } +} +var defaultCommandSubCmdAppTests = []struct { + cmdName string + subCmd string + defaultCmd string + expected bool +}{ + {"foobar", "", "foobar", true}, + {"foobar", "carly", "foobar", true}, + {"batbaz", "", "foobar", true}, + {"b", "", "", true}, + {"f", "", "", true}, + {"", "", "foobar", true}, + {"", "", "", true}, + {"", "jimbob", "foobar", true}, + {"", "j", "foobar", true}, + {"", "carly", "foobar", true}, + {"", "jimmers", "foobar", true}, + {"", "jimmers", "", true}, + {" ", "jimmers", "foobar", false}, + {"", "", "", true}, + {" ", "", "", false}, + {" ", "j", "", false}, + {"bat", "", "batbaz", false}, + {"nothing", "", "batbaz", false}, + {"nothing", "", "", false}, + {"nothing", "j", "batbaz", false}, + {"nothing", "carly", "", false}, +} + +func TestApp_RunDefaultCommandWithSubCommand(t *testing.T) { + for _, test := range defaultCommandSubCmdAppTests { + testTitle := fmt.Sprintf("command=%[1]s-subcmd=%[2]s-default=%[3]s", test.cmdName, test.subCmd, test.defaultCmd) + t.Run(testTitle, func(t *testing.T) { + app := &App{ + DefaultCommand: test.defaultCmd, + Commands: []*Command{ + { + Name: "foobar", + Aliases: []string{"f"}, + Subcommands: []*Command{ + {Name: "jimbob", Aliases: []string{"j"}}, + {Name: "carly"}, + }, + }, + {Name: "batbaz", Aliases: []string{"b"}}, + }, + } + + err := app.Run([]string{"c", test.cmdName, test.subCmd}) + expect(t, err == nil, test.expected) + }) + } } func TestApp_Setup_defaultsReader(t *testing.T) { @@ -2333,4 +2386,4 @@ func TestSetupInitializesOnlyNilWriters(t *testing.T) { if a.Writer != os.Stdout { t.Errorf("expected a.Writer to be os.Stdout") } -} +} \ No newline at end of file From 1dfa9827f6b7cc866087a97bee6f5ca3efc8d309 Mon Sep 17 00:00:00 2001 From: James Alavosus Date: Tue, 21 Jun 2022 19:24:59 -0400 Subject: [PATCH 119/125] gofmt --- app_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app_test.go b/app_test.go index 3dd837f..e814cbb 100644 --- a/app_test.go +++ b/app_test.go @@ -551,7 +551,7 @@ func TestApp_RunDefaultCommandWithSubCommand(t *testing.T) { {Name: "batbaz", Aliases: []string{"b"}}, }, } - + err := app.Run([]string{"c", test.cmdName, test.subCmd}) expect(t, err == nil, test.expected) }) @@ -2386,4 +2386,4 @@ func TestSetupInitializesOnlyNilWriters(t *testing.T) { if a.Writer != os.Stdout { t.Errorf("expected a.Writer to be os.Stdout") } -} \ No newline at end of file +} From d8c93f867b42d7de5991bb707e9c884ca1e04f78 Mon Sep 17 00:00:00 2001 From: James Alavosus Date: Tue, 21 Jun 2022 19:50:27 -0400 Subject: [PATCH 120/125] app_test.go: add tests for default command + flag --- app_test.go | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/app_test.go b/app_test.go index e814cbb..437af25 100644 --- a/app_test.go +++ b/app_test.go @@ -558,6 +558,86 @@ func TestApp_RunDefaultCommandWithSubCommand(t *testing.T) { } } +var defaultCommandFlagAppTests = []struct { + cmdName string + flag string + defaultCmd string + expected bool +}{ + {"foobar", "", "foobar", true}, + {"foobar", "-c derp", "foobar", true}, + {"batbaz", "", "foobar", true}, + {"b", "", "", true}, + {"f", "", "", true}, + {"", "", "foobar", true}, + {"", "", "", true}, + {"", "-j", "foobar", true}, + {"", "-j", "foobar", true}, + {"", "-c derp", "foobar", true}, + {"", "--carly=derp", "foobar", true}, + {"", "-j", "foobar", true}, + {"", "-j", "", true}, + {" ", "-j", "foobar", false}, + {"", "", "", true}, + {" ", "", "", false}, + {" ", "-j", "", false}, + {"bat", "", "batbaz", false}, + {"nothing", "", "batbaz", false}, + {"nothing", "", "", false}, + {"nothing", "--jimbob", "batbaz", false}, + {"nothing", "--carly", "", false}, +} + +func TestApp_RunDefaultCommandWithFlags(t *testing.T) { + for _, test := range defaultCommandFlagAppTests { + testTitle := fmt.Sprintf("command=%[1]s-flag=%[2]s-default=%[3]s", test.cmdName, test.flag, test.defaultCmd) + t.Run(testTitle, func(t *testing.T) { + app := &App{ + DefaultCommand: test.defaultCmd, + Flags: []Flag{ + &StringFlag{ + Name: "carly", + Aliases: []string{"c"}, + Required: false, + }, + &BoolFlag{ + Name: "jimbob", + Aliases: []string{"j"}, + Required: false, + Value: true, + }, + }, + Commands: []*Command{ + { + Name: "foobar", + Aliases: []string{"f"}, + }, + {Name: "batbaz", Aliases: []string{"b"}}, + }, + } + + appArgs := []string{"c"} + + if test.flag != "" { + flags := strings.Split(test.flag, " ") + if len(flags) > 1 { + appArgs = append(appArgs, flags...) + } + + flags = strings.Split(test.flag, "=") + if len(flags) > 1 { + appArgs = append(appArgs, flags...) + } + } + + appArgs = append(appArgs, test.cmdName) + + err := app.Run(appArgs) + expect(t, err == nil, test.expected) + }) + } +} + func TestApp_Setup_defaultsReader(t *testing.T) { app := &App{} app.Setup() From 6dd82af86bd1befaf24f4e387f68882ebafe0ba4 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Wed, 22 Jun 2022 08:29:12 -0400 Subject: [PATCH 121/125] Fix list formatting in v2 manual --- docs/v2/manual.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index 1527e82..ebff418 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -539,9 +539,9 @@ from other file input sources. Currently supported input source formats: - YAML - JSON - TOML +- YAML +- JSON +- TOML In order to get values for a flag from an alternate input source the following code would be added to wrap an existing cli.Flag like below: From d7504f847b745fc2a0d1f922829dae63e8a8fc61 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Fri, 24 Jun 2022 07:55:49 -0400 Subject: [PATCH 122/125] Approve v2 addition of App.DefaultCommand related to #1388 --- godoc-current.txt | 3 +++ testdata/godoc-v2.x.txt | 3 +++ 2 files changed, 6 insertions(+) diff --git a/godoc-current.txt b/godoc-current.txt index a3a7fac..c7b460b 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -269,6 +269,9 @@ type App struct { Version string // Description of the program Description string + // DefaultCommand is the (optional) name of a command + // to run if no command names are passed as CLI arguments. + DefaultCommand string // List of commands to execute Commands []*Command // List of flags to parse diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt index a3a7fac..c7b460b 100644 --- a/testdata/godoc-v2.x.txt +++ b/testdata/godoc-v2.x.txt @@ -269,6 +269,9 @@ type App struct { Version string // Description of the program Description string + // DefaultCommand is the (optional) name of a command + // to run if no command names are passed as CLI arguments. + DefaultCommand string // List of commands to execute Commands []*Command // List of flags to parse From 1335a7022a9ebf6cc5f9568d0d5c31d384c1955e Mon Sep 17 00:00:00 2001 From: Balazs Nagy Date: Sat, 9 Jul 2022 13:43:14 +0200 Subject: [PATCH 123/125] accept timezone for timestamps --- docs/v2/manual.md | 13 +++++++++++++ flag-spec.yaml | 1 + flag_test.go | 12 ++++++++++++ flag_timestamp.go | 18 +++++++++++++++++- godoc-current.txt | 8 ++++++++ zz_generated.flags.go | 2 ++ 6 files changed, 53 insertions(+), 1 deletion(-) diff --git a/docs/v2/manual.md b/docs/v2/manual.md index ebff418..475e047 100644 --- a/docs/v2/manual.md +++ b/docs/v2/manual.md @@ -1413,6 +1413,19 @@ In this example the flag could be used like this: $ myapp --meeting 2019-08-12T15:04:05 ``` +When the layout doesn't contain timezones, timestamp will render with UTC. To +change behavior, a default timezone can be provided with flag definition: + +```go +app := &cli.App{ + Flags: []cli.Flag{ + &cli.TimestampFlag{Name: "meeting", Layout: "2006-01-02T15:04:05", Timezone: time.Local}, + }, +} +``` + +(time.Local contains the system's local time zone.) + Side note: quotes may be necessary around the date depending on your layout (if you have spaces for instance) diff --git a/flag-spec.yaml b/flag-spec.yaml index d85fa30..45f054d 100644 --- a/flag-spec.yaml +++ b/flag-spec.yaml @@ -43,6 +43,7 @@ flag_types: value_pointer: true struct_fields: - { name: Layout, type: string } + - { name: Timezone, type: "*time.Location" } # TODO: enable UintSlice # UintSlice: {} diff --git a/flag_test.go b/flag_test.go index 0bb893a..e46b1ef 100644 --- a/flag_test.go +++ b/flag_test.go @@ -2364,6 +2364,18 @@ func TestTimestampFlagApply_Fail_Parse_Wrong_Time(t *testing.T) { 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\"")) } +func TestTimestampFlagApply_Timezoned(t *testing.T) { + pdt := time.FixedZone("PDT", -7*60*60) + expectedResult, _ := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z") + fl := TimestampFlag{Name: "time", Aliases: []string{"t"}, Layout: time.ANSIC, Timezone: pdt} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{"--time", "Mon Jan 2 08:04:05 2006"}) + expect(t, err, nil) + expect(t, *fl.Value.timestamp, expectedResult.In(pdt)) +} + func TestTimestampFlagValueFromContext(t *testing.T) { set := flag.NewFlagSet("test", 0) now := time.Now() diff --git a/flag_timestamp.go b/flag_timestamp.go index 0522477..80e1f47 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -11,6 +11,7 @@ type Timestamp struct { timestamp *time.Time hasBeenSet bool layout string + location *time.Location } // Timestamp constructor @@ -31,9 +32,22 @@ func (t *Timestamp) SetLayout(layout string) { t.layout = layout } +// Set perceived timezone of the to-be parsed time string +func (t *Timestamp) SetLocation(loc *time.Location) { + t.location = loc +} + // Parses the string value to timestamp func (t *Timestamp) Set(value string) error { - timestamp, err := time.Parse(t.layout, value) + var timestamp time.Time + var err error + + if t.location != nil { + timestamp, err = time.ParseInLocation(t.layout, value, t.location) + } else { + timestamp, err = time.Parse(t.layout, value) + } + if err != nil { return err } @@ -104,9 +118,11 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error { f.Value = &Timestamp{} } f.Value.SetLayout(f.Layout) + f.Value.SetLocation(f.Timezone) if f.Destination != nil { f.Destination.SetLayout(f.Layout) + f.Destination.SetLocation(f.Timezone) } if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/godoc-current.txt b/godoc-current.txt index a3a7fac..1ba48cc 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -269,6 +269,9 @@ type App struct { Version string // Description of the program Description string + // DefaultCommand is the (optional) name of a command + // to run if no command names are passed as CLI arguments. + DefaultCommand string // List of commands to execute Commands []*Command // List of flags to parse @@ -1754,6 +1757,9 @@ func (t *Timestamp) Set(value string) error func (t *Timestamp) SetLayout(layout string) Set the timestamp string layout for future parsing +func (t *Timestamp) SetLocation(loc *time.Location) + Set perceived timezone of the to-be parsed time string + func (t *Timestamp) SetTimestamp(value time.Time) Set the timestamp value directly @@ -1782,6 +1788,8 @@ type TimestampFlag struct { EnvVars []string Layout string + + Timezone *time.Location } TimestampFlag is a flag with type *Timestamp diff --git a/zz_generated.flags.go b/zz_generated.flags.go index 3cae978..b89566f 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -280,6 +280,8 @@ type TimestampFlag struct { EnvVars []string Layout string + + Timezone *time.Location } // String returns a readable representation of this value (for usage defaults) From 8d46d3794ffb66500fdba0150814f60825909779 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 10 Jul 2022 12:16:59 -0400 Subject: [PATCH 124/125] Approve v2 addition of timestamp/timezone/location --- testdata/godoc-v2.x.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt index c7b460b..1ba48cc 100644 --- a/testdata/godoc-v2.x.txt +++ b/testdata/godoc-v2.x.txt @@ -1757,6 +1757,9 @@ func (t *Timestamp) Set(value string) error func (t *Timestamp) SetLayout(layout string) Set the timestamp string layout for future parsing +func (t *Timestamp) SetLocation(loc *time.Location) + Set perceived timezone of the to-be parsed time string + func (t *Timestamp) SetTimestamp(value time.Time) Set the timestamp value directly @@ -1785,6 +1788,8 @@ type TimestampFlag struct { EnvVars []string Layout string + + Timezone *time.Location } TimestampFlag is a flag with type *Timestamp From 25116be3f5978c88df488c79874edfe51e8fd8ba Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 16 Jul 2022 07:58:10 -0400 Subject: [PATCH 125/125] Revert "Merging main to v3 dev main" --- .github/workflows/cli.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 470a8aa..3b31afc 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -4,14 +4,11 @@ on: push: branches: - main - - v3-dev-main tags: - v2.* - - v3.* pull_request: branches: - main - - v3-dev-main jobs: test: