From dd9cd61befef88718336a379d1c0f555606f5f5d Mon Sep 17 00:00:00 2001 From: miyado <10195648+hmiyado@users.noreply.github.com> Date: Mon, 18 Jul 2022 16:33:53 +0900 Subject: [PATCH 01/97] Fix for TimestampFlag.GetValue to return empty string without value --- flag_timestamp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flag_timestamp.go b/flag_timestamp.go index 8759b85..7b525b0 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -75,7 +75,7 @@ func (t *Timestamp) Get() interface{} { // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *TimestampFlag) GetValue() string { - if f.Value != nil { + if f.Value != nil && f.Value.timestamp != nil { return f.Value.timestamp.String() } return "" From 67f592aadb5cbb6979c5e82a9be4d2b6131ff0ff Mon Sep 17 00:00:00 2001 From: Dokiy Date: Fri, 29 Jul 2022 15:26:06 +0800 Subject: [PATCH 02/97] Fix HideHelp --- app.go | 8 +++++-- app_test.go | 58 ++++++++++++++++++++++++++++++++++++--------- command.go | 8 +++++-- suggestions_test.go | 4 ++-- 4 files changed, 61 insertions(+), 17 deletions(-) diff --git a/app.go b/app.go index 834873a..e9493d3 100644 --- a/app.go +++ b/app.go @@ -273,7 +273,9 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { cCtx := NewContext(a, set, &Context{Context: ctx}) if nerr != nil { _, _ = fmt.Fprintln(a.Writer, nerr) - _ = ShowAppHelp(cCtx) + if !a.HideHelp { + _ = ShowAppHelp(cCtx) + } return nerr } cCtx.shellComplete = shellComplete @@ -294,7 +296,9 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { fmt.Fprintf(a.Writer, suggestion) } } - _ = ShowAppHelp(cCtx) + if !a.HideHelp { + _ = ShowAppHelp(cCtx) + } return err } diff --git a/app_test.go b/app_test.go index da3981c..e7c759e 100644 --- a/app_test.go +++ b/app_test.go @@ -1873,31 +1873,67 @@ func TestApp_Run_CommandSubcommandHelpName(t *testing.T) { } func TestApp_Run_Help(t *testing.T) { - var helpArguments = [][]string{{"boom", "--help"}, {"boom", "-h"}, {"boom", "help"}} - - for _, args := range helpArguments { - t.Run(fmt.Sprintf("checking with arguments %v", args), func(t *testing.T) { + var tests = []struct { + helpArguments []string + hideHelp bool + wantContains string + wantErr error + }{ + { + helpArguments: []string{"boom", "--help"}, + hideHelp: false, + wantContains: "boom - make an explosive entrance", + }, + { + helpArguments: []string{"boom", "-h"}, + hideHelp: false, + wantContains: "boom - make an explosive entrance", + }, + { + helpArguments: []string{"boom", "help"}, + hideHelp: false, + wantContains: "boom - make an explosive entrance", + }, + { + helpArguments: []string{"boom", "--help"}, + hideHelp: true, + wantErr: fmt.Errorf("flag: help requested"), + }, + { + helpArguments: []string{"boom", "-h"}, + hideHelp: true, + wantErr: fmt.Errorf("flag: help requested"), + }, + { + helpArguments: []string{"boom", "help"}, + hideHelp: true, + wantContains: "boom I say!", + }, + } + for _, tt := range tests { + t.Run(fmt.Sprintf("checking with arguments %v", tt.helpArguments), func(t *testing.T) { buf := new(bytes.Buffer) app := &App{ - Name: "boom", - Usage: "make an explosive entrance", - Writer: buf, + Name: "boom", + Usage: "make an explosive entrance", + Writer: buf, + HideHelp: tt.hideHelp, Action: func(c *Context) error { buf.WriteString("boom I say!") return nil }, } - err := app.Run(args) - if err != nil { - t.Error(err) + err := app.Run(tt.helpArguments) + if err != nil && err.Error() != tt.wantErr.Error() { + t.Errorf("want err: %s, did note %s\n", tt.wantErr, err) } output := buf.String() - if !strings.Contains(output, "boom - make an explosive entrance") { + if !strings.Contains(output, tt.wantContains) { t.Errorf("want help to contain %q, did not: \n%q", "boom - make an explosive entrance", output) } }) diff --git a/command.go b/command.go index 2cafd8e..13b79de 100644 --- a/command.go +++ b/command.go @@ -125,7 +125,9 @@ func (c *Command) Run(ctx *Context) (err error) { fmt.Fprintf(cCtx.App.Writer, suggestion) } } - _ = ShowCommandHelp(cCtx, c.Name) + if !c.HideHelp { + _ = ShowCommandHelp(cCtx, c.Name) + } return err } @@ -135,7 +137,9 @@ func (c *Command) Run(ctx *Context) (err error) { cerr := cCtx.checkRequiredFlags(c.Flags) if cerr != nil { - _ = ShowCommandHelp(cCtx, c.Name) + if !c.HideHelp { + _ = ShowCommandHelp(cCtx, c.Name) + } return cerr } diff --git a/suggestions_test.go b/suggestions_test.go index 4c0984e..909e29c 100644 --- a/suggestions_test.go +++ b/suggestions_test.go @@ -124,7 +124,7 @@ func ExampleApp_Suggest() { app := &App{ Name: "greet", Suggest: true, - HideHelp: true, + HideHelp: false, HideHelpCommand: true, CustomAppHelpTemplate: "(this space intentionally left blank)\n", Flags: []Flag{ @@ -149,7 +149,6 @@ func ExampleApp_Suggest_command() { app := &App{ Name: "greet", Suggest: true, - HideHelp: true, HideHelpCommand: true, CustomAppHelpTemplate: "(this space intentionally left blank)\n", Flags: []Flag{ @@ -162,6 +161,7 @@ func ExampleApp_Suggest_command() { Commands: []*Command{ { Name: "neighbors", + HideHelp: false, CustomHelpTemplate: "(this space intentionally left blank)\n", Flags: []Flag{ &BoolFlag{Name: "smiling"}, From 02eb3929162425186ac25dd21253755b8935c823 Mon Sep 17 00:00:00 2001 From: Dokiy Date: Thu, 28 Jul 2022 19:00:48 +0800 Subject: [PATCH 03/97] Fix After not run --- app.go | 24 ++++++++++++------------ app_test.go | 21 +++++++++++++++++++++ 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/app.go b/app.go index e9493d3..6b743bf 100644 --- a/app.go +++ b/app.go @@ -302,6 +302,18 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { return err } + if a.After != nil { + defer func() { + if afterErr := a.After(cCtx); afterErr != nil { + if err != nil { + err = newMultiError(err, afterErr) + } else { + err = afterErr + } + } + }() + } + if !a.HideHelp && checkHelp(cCtx) { _ = ShowAppHelp(cCtx) return nil @@ -318,18 +330,6 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { return cerr } - if a.After != nil { - defer func() { - if afterErr := a.After(cCtx); afterErr != nil { - if err != nil { - err = newMultiError(err, afterErr) - } else { - err = afterErr - } - } - }() - } - if a.Before != nil { beforeErr := a.Before(cCtx) if beforeErr != nil { diff --git a/app_test.go b/app_test.go index e7c759e..cbc1c8b 100644 --- a/app_test.go +++ b/app_test.go @@ -1309,6 +1309,27 @@ func TestApp_AfterFunc(t *testing.T) { if counts.SubCommand != 1 { t.Errorf("Subcommand not executed when expected") } + + /* + reset + */ + counts = &opCounts{} + + // run with none args + err = app.Run([]string{"command"}) + + // should be the same error produced by the Before func + if err != nil { + t.Fatalf("Run error: %s", err) + } + + if counts.After != 1 { + t.Errorf("After() not executed when expected") + } + + if counts.SubCommand != 0 { + t.Errorf("Subcommand not executed when expected") + } } func TestAppNoHelpFlag(t *testing.T) { From bc99b5865cc9ae1fb926171c11f05a169a5321d3 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sun, 14 Aug 2022 19:09:47 -0400 Subject: [PATCH 04/97] issue_62: Make slice options more posix like --- flag.go | 55 ++++--------------------------------------- flag_float64_slice.go | 14 ++++++++++- flag_int64_slice.go | 13 +++++++++- flag_int_slice.go | 13 +++++++++- flag_string_slice.go | 16 ++++++++++++- flag_test.go | 42 ++++++++++++++++----------------- 6 files changed, 77 insertions(+), 76 deletions(-) diff --git a/flag.go b/flag.go index a0b9834..9f4d1ab 100644 --- a/flag.go +++ b/flag.go @@ -7,7 +7,6 @@ import ( "io/ioutil" "regexp" "runtime" - "strconv" "strings" "syscall" "time" @@ -297,53 +296,6 @@ func stringifyFlag(f Flag) string { fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault)) } -func stringifyIntSliceFlag(f *IntSliceFlag) string { - var defaultVals []string - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, strconv.Itoa(i)) - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - -func stringifyInt64SliceFlag(f *Int64SliceFlag) string { - var defaultVals []string - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, strconv.FormatInt(i, 10)) - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - -func stringifyFloat64SliceFlag(f *Float64SliceFlag) string { - var defaultVals []string - - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, strings.TrimRight(strings.TrimRight(fmt.Sprintf("%f", i), "0"), ".")) - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - -func stringifyStringSliceFlag(f *StringSliceFlag) string { - var defaultVals []string - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, s := range f.Value.Value() { - if len(s) > 0 { - defaultVals = append(defaultVals, strconv.Quote(s)) - } - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - func stringifySliceFlag(usage string, names, defaultVals []string) string { placeholder, usage := unquoteUsage(usage) if placeholder == "" { @@ -356,11 +308,12 @@ func stringifySliceFlag(usage string, names, defaultVals []string) string { } usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) - multiInputString := "(accepts multiple inputs)" + /*multiInputString := "(accepts multiple inputs)" if usageWithDefault != "" { multiInputString = "\t" + multiInputString - } - return fmt.Sprintf("%s\t%s%s", prefixedNames(names, placeholder), usageWithDefault, multiInputString) + }*/ + pn := prefixedNames(names, placeholder) + return fmt.Sprintf("%s [ %s ]\t%s%s", pn, pn, usageWithDefault, "") } func hasFlag(flags []Flag, fl Flag) bool { diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 56745c1..833e65c 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -83,7 +83,7 @@ func (f *Float64Slice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *Float64SliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), stringifyFloat64SliceFlag(f)) + return withEnvHint(f.GetEnvVars(), f.stringify()) } // GetValue returns the flags value as string representation and an empty @@ -141,6 +141,18 @@ func (f *Float64SliceFlag) Get(ctx *Context) []float64 { return ctx.Float64Slice(f.Name) } +func (f *Float64SliceFlag) stringify() string { + var defaultVals []string + + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strings.TrimRight(strings.TrimRight(fmt.Sprintf("%f", i), "0"), ".")) + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + // Float64Slice looks up the value of a local Float64SliceFlag, returns // nil if not found func (cCtx *Context) Float64Slice(name string) []float64 { diff --git a/flag_int64_slice.go b/flag_int64_slice.go index 8316709..d848b45 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -84,7 +84,7 @@ func (i *Int64Slice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *Int64SliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), stringifyInt64SliceFlag(f)) + return withEnvHint(f.GetEnvVars(), f.stringify()) } // GetValue returns the flags value as string representation and an empty @@ -140,6 +140,17 @@ func (f *Int64SliceFlag) Get(ctx *Context) []int64 { return ctx.Int64Slice(f.Name) } +func (f *Int64SliceFlag) stringify() string { + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.FormatInt(i, 10)) + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + // Int64Slice looks up the value of a local Int64SliceFlag, returns // nil if not found func (cCtx *Context) Int64Slice(name string) []int64 { diff --git a/flag_int_slice.go b/flag_int_slice.go index 7865dab..96fb7a3 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -95,7 +95,7 @@ func (i *IntSlice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *IntSliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), stringifyIntSliceFlag(f)) + return withEnvHint(f.GetEnvVars(), f.stringify()) } // GetValue returns the flags value as string representation and an empty @@ -151,6 +151,17 @@ func (f *IntSliceFlag) Get(ctx *Context) []int { return ctx.IntSlice(f.Name) } +func (f *IntSliceFlag) stringify() string { + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.Itoa(i)) + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + // IntSlice looks up the value of a local IntSliceFlag, returns // nil if not found func (cCtx *Context) IntSlice(name string) []int { diff --git a/flag_string_slice.go b/flag_string_slice.go index f50323b..c5c9b78 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -4,6 +4,7 @@ import ( "encoding/json" "flag" "fmt" + "strconv" "strings" ) @@ -73,7 +74,7 @@ func (s *StringSlice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *StringSliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), stringifyStringSliceFlag(f)) + return withEnvHint(f.GetEnvVars(), f.stringify()) } // GetValue returns the flags value as string representation and an empty @@ -129,6 +130,19 @@ func (f *StringSliceFlag) Get(ctx *Context) []string { return ctx.StringSlice(f.Name) } +func (f *StringSliceFlag) stringify() string { + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, s := range f.Value.Value() { + if len(s) > 0 { + defaultVals = append(defaultVals, strconv.Quote(s)) + } + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + // StringSlice looks up the value of a local StringSliceFlag, returns // nil if not found func (cCtx *Context) StringSlice(name string) []string { diff --git a/flag_test.go b/flag_test.go index deac525..faec219 100644 --- a/flag_test.go +++ b/flag_test.go @@ -544,11 +544,11 @@ var stringSliceFlagTests = []struct { value *StringSlice expected string }{ - {"foo", nil, NewStringSlice(""), "--foo value\t(accepts multiple inputs)"}, - {"f", nil, NewStringSlice(""), "-f value\t(accepts multiple inputs)"}, - {"f", nil, NewStringSlice("Lipstick"), "-f value\t(default: \"Lipstick\")\t(accepts multiple inputs)"}, - {"test", nil, NewStringSlice("Something"), "--test value\t(default: \"Something\")\t(accepts multiple inputs)"}, - {"dee", []string{"d"}, NewStringSlice("Inka", "Dinka", "dooo"), "--dee value, -d value\t(default: \"Inka\", \"Dinka\", \"dooo\")\t(accepts multiple inputs)"}, + {"foo", nil, NewStringSlice(""), "--foo value [ --foo value ]\t"}, + {"f", nil, NewStringSlice(""), "-f value [ -f value ]\t"}, + {"f", nil, NewStringSlice("Lipstick"), "-f value [ -f value ]\t(default: \"Lipstick\")"}, + {"test", nil, NewStringSlice("Something"), "--test value [ --test value ]\t(default: \"Something\")"}, + {"dee", []string{"d"}, NewStringSlice("Inka", "Dinka", "dooo"), "--dee value, -d value [ --dee value, -d value ]\t(default: \"Inka\", \"Dinka\", \"dooo\")"}, } func TestStringSliceFlagHelpOutput(t *testing.T) { @@ -897,9 +897,9 @@ var intSliceFlagTests = []struct { value *IntSlice expected string }{ - {"heads", nil, NewIntSlice(), "--heads value\t(accepts multiple inputs)"}, - {"H", nil, NewIntSlice(), "-H value\t(accepts multiple inputs)"}, - {"H", []string{"heads"}, NewIntSlice(9, 3), "-H value, --heads value\t(default: 9, 3)\t(accepts multiple inputs)"}, + {"heads", nil, NewIntSlice(), "--heads value [ --heads value ]\t"}, + {"H", nil, NewIntSlice(), "-H value [ -H value ]\t"}, + {"H", []string{"heads"}, NewIntSlice(9, 3), "-H value, --heads value [ -H value, --heads value ]\t(default: 9, 3)"}, } func TestIntSliceFlagHelpOutput(t *testing.T) { @@ -994,10 +994,10 @@ var int64SliceFlagTests = []struct { value *Int64Slice expected string }{ - {"heads", nil, NewInt64Slice(), "--heads value\t(accepts multiple inputs)"}, - {"H", nil, NewInt64Slice(), "-H value\t(accepts multiple inputs)"}, + {"heads", nil, NewInt64Slice(), "--heads value [ --heads value ]\t"}, + {"H", nil, NewInt64Slice(), "-H value [ -H value ]\t"}, {"heads", []string{"H"}, NewInt64Slice(int64(2), int64(17179869184)), - "--heads value, -H value\t(default: 2, 17179869184)\t(accepts multiple inputs)"}, + "--heads value, -H value [ --heads value, -H value ]\t(default: 2, 17179869184)"}, } func TestInt64SliceFlagHelpOutput(t *testing.T) { @@ -1155,10 +1155,10 @@ var float64SliceFlagTests = []struct { value *Float64Slice expected string }{ - {"heads", nil, NewFloat64Slice(), "--heads value\t(accepts multiple inputs)"}, - {"H", nil, NewFloat64Slice(), "-H value\t(accepts multiple inputs)"}, + {"heads", nil, NewFloat64Slice(), "--heads value [ --heads value ]\t"}, + {"H", nil, NewFloat64Slice(), "-H value [ -H value ]\t"}, {"heads", []string{"H"}, NewFloat64Slice(0.1234, -10.5), - "--heads value, -H value\t(default: 0.1234, -10.5)\t(accepts multiple inputs)"}, + "--heads value, -H value [ --heads value, -H value ]\t(default: 0.1234, -10.5)"}, } func TestFloat64SliceFlagHelpOutput(t *testing.T) { @@ -2384,43 +2384,43 @@ func TestFlagDefaultValue(t *testing.T) { name: "stringSclice", flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")}, toParse: []string{"--flag", "parsed"}, - expect: `--flag value (default: "default1", "default2") (accepts multiple inputs)`, + expect: `--flag value [ --flag value ] (default: "default1", "default2")`, }, { 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)`, + expect: `--flag value [ --flag value ] (default: 1.1, 2.2)`, }, { name: "int64Sclice", flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "intSclice", flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "string", flag: &StringFlag{Name: "flag", Value: "default"}, toParse: []string{"--flag", "parsed"}, - expect: `--flag value (default: "default")`, + expect: `--flag value (default: "default")`, }, { name: "bool", flag: &BoolFlag{Name: "flag", Value: true}, toParse: []string{"--flag", "false"}, - expect: `--flag (default: true)`, + expect: `--flag (default: true)`, }, { name: "uint64", flag: &Uint64Flag{Name: "flag", Value: 1}, toParse: []string{"--flag", "13"}, - expect: `--flag value (default: 1)`, + expect: `--flag value (default: 1)`, }, } for i, v := range cases { From 45a1375078a57c3faad152809d0ca0e677bfbfea Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sun, 14 Aug 2022 20:32:10 -0400 Subject: [PATCH 05/97] Changes from code review --- flag.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/flag.go b/flag.go index 9f4d1ab..4f0871d 100644 --- a/flag.go +++ b/flag.go @@ -308,12 +308,8 @@ func stringifySliceFlag(usage string, names, defaultVals []string) string { } usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) - /*multiInputString := "(accepts multiple inputs)" - if usageWithDefault != "" { - multiInputString = "\t" + multiInputString - }*/ pn := prefixedNames(names, placeholder) - return fmt.Sprintf("%s [ %s ]\t%s%s", pn, pn, usageWithDefault, "") + return fmt.Sprintf("%s [ %s ]\t%s", pn, pn, usageWithDefault) } func hasFlag(flags []Flag, fl Flag) bool { From 2ca91434a85af894c974fb9b4051e7821988618b Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 14 Aug 2022 10:02:07 -0400 Subject: [PATCH 06/97] Move genflags tool to cmd/ and pin to previous release to alleviate problems caused by the circular dependency of using the same code as a library that is potentially being generated to adhere to a different API. --- .github/workflows/cli.yml | 3 + .gitignore | 1 + cli.go | 32 ++-- cmd/urfave-cli-genflags/Makefile | 21 +++ cmd/urfave-cli-genflags/README.md | 15 ++ .../urfave-cli-genflags}/generated.gotmpl | 0 .../generated_test.gotmpl | 0 cmd/urfave-cli-genflags/go.mod | 15 ++ cmd/urfave-cli-genflags/go.sum | 14 ++ .../urfave-cli-genflags}/main.go | 137 +++++++++++++++++- .../urfave-cli-genflags/main_test.go | 56 +++++-- docs/CONTRIBUTING.md | 2 +- flag-spec.yaml | 4 +- internal/build/build.go | 2 +- internal/genflags/package.go | 34 ----- internal/genflags/package_test.go | 41 ------ internal/genflags/spec.go | 105 -------------- 17 files changed, 268 insertions(+), 214 deletions(-) create mode 100644 cmd/urfave-cli-genflags/Makefile create mode 100644 cmd/urfave-cli-genflags/README.md rename {internal/genflags => cmd/urfave-cli-genflags}/generated.gotmpl (100%) rename {internal/genflags => cmd/urfave-cli-genflags}/generated_test.gotmpl (100%) create mode 100644 cmd/urfave-cli-genflags/go.mod create mode 100644 cmd/urfave-cli-genflags/go.sum rename {internal/genflags/cmd/genflags => cmd/urfave-cli-genflags}/main.go (53%) rename internal/genflags/spec_test.go => cmd/urfave-cli-genflags/main_test.go (52%) delete mode 100644 internal/genflags/package.go delete mode 100644 internal/genflags/package_test.go delete mode 100644 internal/genflags/spec.go diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 1e4a51f..0a3e7c3 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -46,6 +46,9 @@ jobs: - name: test run: go run internal/build/build.go test + - name: test urfave-cli-genflags + run: make -C cmd/urfave-cli-genflags + - name: check-binary-size run: go run internal/build/build.go check-binary-size diff --git a/.gitignore b/.gitignore index c04fcc5..4296f6c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,6 @@ internal/*/built-example coverage.txt /.local/ /site/ +/cmd/urfave-cli-genflags/urfave-cli-genflags *.exe diff --git a/cli.go b/cli.go index 2a11c5a..c0c5d9a 100644 --- a/cli.go +++ b/cli.go @@ -1,23 +1,25 @@ // 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) -// } +// +// 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) -// } +// 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) +// } package cli -//go:generate go run internal/genflags/cmd/genflags/main.go +//go:generate go run cmd/urfave-cli-genflags/main.go diff --git a/cmd/urfave-cli-genflags/Makefile b/cmd/urfave-cli-genflags/Makefile new file mode 100644 index 0000000..3b11415 --- /dev/null +++ b/cmd/urfave-cli-genflags/Makefile @@ -0,0 +1,21 @@ +GOTEST_FLAGS ?= -v --coverprofile main.coverprofile --covermode count --cover github.com/urfave/cli/v2/cmd/urfave-cli-genflags +GOBUILD_FLAGS ?= -x + +.PHONY: all +all: test build smoke-test + +.PHONY: test +test: + go test $(GOTEST_FLAGS) ./... + +.PHONY: build +build: + go build $(GOBUILD_FLAGS) ./... + +.PHONY: smoke-test +smoke-test: build + ./urfave-cli-genflags --help + +.PHONY: show-cover +show-cover: + go tool cover -func main.coverprofile diff --git a/cmd/urfave-cli-genflags/README.md b/cmd/urfave-cli-genflags/README.md new file mode 100644 index 0000000..9424234 --- /dev/null +++ b/cmd/urfave-cli-genflags/README.md @@ -0,0 +1,15 @@ +# urfave-cli-genflags + +This is a tool that is used internally by [urfave/cli] to generate +flag types and methods from a YAML input. It intentionally pins +usage of `github.com/urfave/cli/v2` to a *release* rather than +using the adjacent code so that changes don't result in *this* tool +refusing to compile. It's almost like dogfooding? + +## support warning + +This tool is maintained as a sub-project and is not covered by the +API and backward compatibility guaranteed by releases of +[urfave/cli]. + +[urfave/cli]: https://github.com/urfave/cli diff --git a/internal/genflags/generated.gotmpl b/cmd/urfave-cli-genflags/generated.gotmpl similarity index 100% rename from internal/genflags/generated.gotmpl rename to cmd/urfave-cli-genflags/generated.gotmpl diff --git a/internal/genflags/generated_test.gotmpl b/cmd/urfave-cli-genflags/generated_test.gotmpl similarity index 100% rename from internal/genflags/generated_test.gotmpl rename to cmd/urfave-cli-genflags/generated_test.gotmpl diff --git a/cmd/urfave-cli-genflags/go.mod b/cmd/urfave-cli-genflags/go.mod new file mode 100644 index 0000000..af40aaa --- /dev/null +++ b/cmd/urfave-cli-genflags/go.mod @@ -0,0 +1,15 @@ +module github.com/urfave/cli/v2/cmd/urfave-cli-genflags + +go 1.18 + +require ( + github.com/urfave/cli/v2 v2.11.2 + golang.org/x/text v0.3.7 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect +) diff --git a/cmd/urfave-cli-genflags/go.sum b/cmd/urfave-cli-genflags/go.sum new file mode 100644 index 0000000..e59916d --- /dev/null +++ b/cmd/urfave-cli-genflags/go.sum @@ -0,0 +1,14 @@ +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/urfave/cli/v2 v2.11.2 h1:FVfNg4m3vbjbBpLYxW//WjxUoHvJ9TlppXcqY9Q9ZfA= +github.com/urfave/cli/v2 v2.11.2/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= +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= +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.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/cmd/urfave-cli-genflags/main.go similarity index 53% rename from internal/genflags/cmd/genflags/main.go rename to cmd/urfave-cli-genflags/main.go index 4212e60..835216a 100644 --- a/internal/genflags/cmd/genflags/main.go +++ b/cmd/urfave-cli-genflags/main.go @@ -9,12 +9,14 @@ import ( "os/exec" "os/signal" "path/filepath" + "sort" "strings" "syscall" "text/template" "github.com/urfave/cli/v2" - "github.com/urfave/cli/v2/internal/genflags" + "golang.org/x/text/cases" + "golang.org/x/text/language" "gopkg.in/yaml.v3" ) @@ -22,6 +24,16 @@ const ( defaultPackageName = "cli" ) +var ( + //go:embed generated.gotmpl + TemplateString string + + //go:embed generated_test.gotmpl + TestTemplateString string + + titler = cases.Title(language.Und, cases.NoLower) +) + func sh(ctx context.Context, exe string, args ...string) (string, error) { cmd := exec.CommandContext(ctx, exe, args...) cmd.Stderr = os.Stderr @@ -92,7 +104,7 @@ func runGenFlags(cCtx *cli.Context) error { return err } - spec := &genflags.Spec{} + spec := &Spec{} if err := yaml.Unmarshal(specBytes, spec); err != nil { return err } @@ -123,12 +135,12 @@ func runGenFlags(cCtx *cli.Context) error { spec.UrfaveCLITestNamespace = "cli." } - genTmpl, err := template.New("gen").Parse(genflags.TemplateString) + genTmpl, err := template.New("gen").Parse(TemplateString) if err != nil { return err } - genTestTmpl, err := template.New("gen_test").Parse(genflags.TestTemplateString) + genTestTmpl, err := template.New("gen_test").Parse(TestTemplateString) if err != nil { return err } @@ -161,3 +173,120 @@ func runGenFlags(cCtx *cli.Context) error { return nil } + +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" +} + +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) 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 + } + + lowName := strings.ToLower(name) + + for _, interfaceName := range ft.Config.SkipInterfaces { + if strings.ToLower(interfaceName) == lowName { + return false + } + } + + return true +} diff --git a/internal/genflags/spec_test.go b/cmd/urfave-cli-genflags/main_test.go similarity index 52% rename from internal/genflags/spec_test.go rename to cmd/urfave-cli-genflags/main_test.go index 25a9c8b..b5c9fee 100644 --- a/internal/genflags/spec_test.go +++ b/cmd/urfave-cli-genflags/main_test.go @@ -1,29 +1,63 @@ -package genflags_test +package main_test import ( + "fmt" "reflect" "testing" - "github.com/urfave/cli/v2/internal/genflags" + main "github.com/urfave/cli/v2/cmd/urfave-cli-genflags" ) +func TestTypeName(t *testing.T) { + for _, tc := range []struct { + gt string + fc *main.FlagTypeConfig + expected string + }{ + {gt: "int", fc: nil, expected: "IntFlag"}, + {gt: "int", fc: &main.FlagTypeConfig{}, expected: "IntFlag"}, + {gt: "int", fc: &main.FlagTypeConfig{TypeName: "VeryIntyFlag"}, expected: "VeryIntyFlag"}, + {gt: "[]bool", fc: nil, expected: "BoolSliceFlag"}, + {gt: "[]bool", fc: &main.FlagTypeConfig{}, expected: "BoolSliceFlag"}, + {gt: "[]bool", fc: &main.FlagTypeConfig{TypeName: "ManyTruthsFlag"}, expected: "ManyTruthsFlag"}, + {gt: "time.Rumination", fc: nil, expected: "RuminationFlag"}, + {gt: "time.Rumination", fc: &main.FlagTypeConfig{}, expected: "RuminationFlag"}, + {gt: "time.Rumination", fc: &main.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 := main.TypeName(tc.gt, tc.fc) + if tc.expected != actual { + ct.Errorf("expected %q, got %q", tc.expected, actual) + } + }, + ) + } +} + func TestSpec_SortedFlagTypes(t *testing.T) { - spec := &genflags.Spec{ - FlagTypes: map[string]*genflags.FlagTypeConfig{ - "nerf": &genflags.FlagTypeConfig{}, + spec := &main.Spec{ + FlagTypes: map[string]*main.FlagTypeConfig{ + "nerf": &main.FlagTypeConfig{}, "gerf": nil, }, } actual := spec.SortedFlagTypes() - expected := []*genflags.FlagType{ + expected := []*main.FlagType{ { GoType: "gerf", Config: nil, }, { GoType: "nerf", - Config: &genflags.FlagTypeConfig{}, + Config: &main.FlagTypeConfig{}, }, } if !reflect.DeepEqual(expected, actual) { @@ -31,12 +65,12 @@ func TestSpec_SortedFlagTypes(t *testing.T) { } } -func genFlagType() *genflags.FlagType { - return &genflags.FlagType{ +func genFlagType() *main.FlagType { + return &main.FlagType{ GoType: "blerf", - Config: &genflags.FlagTypeConfig{ + Config: &main.FlagTypeConfig{ SkipInterfaces: []string{"fmt.Stringer"}, - StructFields: []*genflags.FlagStructField{ + StructFields: []*main.FlagStructField{ { Name: "Foibles", Type: "int", diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index b0d9bfc..4462899 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -95,7 +95,7 @@ The built-in `go generate` command is used to run the commands specified in line help system which may be consulted for further information, e.g.: ```sh -go run internal/genflags/cmd/genflags/main.go --help +go run cmd/urfave-cli-genflags/main.go --help ``` #### docs output diff --git a/flag-spec.yaml b/flag-spec.yaml index 3fa2d10..7199c19 100644 --- a/flag-spec.yaml +++ b/flag-spec.yaml @@ -1,6 +1,6 @@ # 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. +# ./cmd/urfave-cli-genflags/main.go which uses the +# `Spec` type that maps to this file structure. flag_types: bool: diff --git a/internal/build/build.go b/internal/build/build.go index d94dae7..51b560c 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -94,7 +94,7 @@ func main() { }, &cli.StringSliceFlag{ Name: "packages", - Value: cli.NewStringSlice("cli", "altsrc", "internal/build", "internal/genflags"), + Value: cli.NewStringSlice("cli", "altsrc", "internal/build"), }, } diff --git a/internal/genflags/package.go b/internal/genflags/package.go deleted file mode 100644 index 4e5de41..0000000 --- a/internal/genflags/package.go +++ /dev/null @@ -1,34 +0,0 @@ -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 deleted file mode 100644 index 3920540..0000000 --- a/internal/genflags/package_test.go +++ /dev/null @@ -1,41 +0,0 @@ -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 deleted file mode 100644 index 5b1d4d3..0000000 --- a/internal/genflags/spec.go +++ /dev/null @@ -1,105 +0,0 @@ -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"` - NoDefaultText bool `yaml:"no_default_text"` -} - -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) GenerateDefaultText() bool { - return !ft.Config.NoDefaultText -} - -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 -} From d8de3b5483649a3f75f524c84099f593f7c8a5d3 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 14 Aug 2022 10:06:26 -0400 Subject: [PATCH 07/97] Tidy up top-level go.mod --- go.mod | 1 - go.sum | 6 ------ 2 files changed, 7 deletions(-) diff --git a/go.mod b/go.mod index 4e39308..7fa4542 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/BurntSushi/toml v1.1.0 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 6beae99..0756e41 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,11 @@ 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= 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.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= From a5313eb2d5ec33c691b471cb3caa644d4d594fdc Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 14 Aug 2022 10:22:18 -0400 Subject: [PATCH 08/97] Add missing go.sum entries for go 1.16.x --- cmd/urfave-cli-genflags/go.sum | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/urfave-cli-genflags/go.sum b/cmd/urfave-cli-genflags/go.sum index e59916d..9821127 100644 --- a/cmd/urfave-cli-genflags/go.sum +++ b/cmd/urfave-cli-genflags/go.sum @@ -1,3 +1,5 @@ +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.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= @@ -8,6 +10,7 @@ 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/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.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= From cbc7f1ad1dd5aae71e00ac16807e989a53363a9f Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 14 Aug 2022 10:47:42 -0400 Subject: [PATCH 09/97] Use goimports as formatting standard given some disagreement with gofmt that seems to have shown up in 1.19 --- .github/workflows/cli.yml | 10 +++++++--- cli.go | 24 ++++++++++++------------ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 0a3e7c3..3bd66d6 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -30,12 +30,16 @@ jobs: - name: Set PATH run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}" + - name: install goimports + if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' + run: GOBIN=${PWD}/.local/bin go install golang.org/x/tools/cmd/goimports@latest + - name: Checkout Code uses: actions/checkout@v3 - - name: GOFMT Check - if: matrix.go == '1.18.x' && matrix.os == 'ubuntu-latest' - run: test -z $(gofmt -l .) + - name: goimports check + if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' + run: test -z $(goimports -l .) - name: vet run: go run internal/build/build.go vet diff --git a/cli.go b/cli.go index c0c5d9a..b3b864c 100644 --- a/cli.go +++ b/cli.go @@ -3,23 +3,23 @@ // cli application can be written as follows: // // func main() { -// (&cli.App{}).Run(os.Args) +// (&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 -// }, -// } +// 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) -// } +// app.Run(os.Args) +// } package cli //go:generate go run cmd/urfave-cli-genflags/main.go From 76bb9f100bc512938165bcbef9002caa4376926f Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 14 Aug 2022 10:31:53 -0400 Subject: [PATCH 10/97] Shift supported go versions and approve word-wrapping changes to godoc --- .github/workflows/cli.yml | 6 +++--- .gitignore | 13 +++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 3bd66d6..5364ca0 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - go: [1.18.x] + go: [1.18.x, 1.19.x] name: ${{ matrix.os }} @ Go ${{ matrix.go }} runs-on: ${{ matrix.os }} steps: @@ -60,7 +60,7 @@ jobs: 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' + if: success() && matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@v2 with: fail_ci_if_error: true @@ -72,7 +72,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.18.x + go-version: 1.19.x - name: Use Node.js 16 uses: actions/setup-node@v3 diff --git a/.gitignore b/.gitignore index 4296f6c..3c6768f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,12 @@ *.coverprofile +*.exe *.orig -vendor +.*envrc +.envrc .idea -internal/*/built-example -coverage.txt /.local/ -/site/ /cmd/urfave-cli-genflags/urfave-cli-genflags - -*.exe +/site/ +coverage.txt +internal/*/built-example +vendor From 1ae70fcaddd091dac0023829b99da5af6cc2fcf3 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sun, 14 Aug 2022 14:49:05 -0800 Subject: [PATCH 11/97] split v1 docs into individual pages --- docs/index.md | 2 +- docs/v1/examples/arguments.md | 32 + docs/v1/examples/bash-completions.md | 114 ++ docs/v1/examples/combining-short-options.md | 67 + docs/v1/examples/exit-codes.md | 38 + docs/v1/examples/flags.md | 457 ++++++ docs/v1/examples/generated-help-text.md | 101 ++ docs/v1/examples/greet.md | 69 + docs/v1/examples/subcommands-categories.md | 50 + docs/v1/examples/subcommands.md | 70 + docs/v1/examples/version-flag.md | 360 +++++ docs/v1/getting-started.md | 60 + docs/v1/index.md | 1 - docs/v1/manual.md | 1455 ------------------- docs/v1/migrating-to-v2.md | 6 + mkdocs.yml | 24 +- 16 files changed, 1448 insertions(+), 1458 deletions(-) create mode 100644 docs/v1/examples/arguments.md create mode 100644 docs/v1/examples/bash-completions.md create mode 100644 docs/v1/examples/combining-short-options.md create mode 100644 docs/v1/examples/exit-codes.md create mode 100644 docs/v1/examples/flags.md create mode 100644 docs/v1/examples/generated-help-text.md create mode 100644 docs/v1/examples/greet.md create mode 100644 docs/v1/examples/subcommands-categories.md create mode 100644 docs/v1/examples/subcommands.md create mode 100644 docs/v1/examples/version-flag.md create mode 100644 docs/v1/getting-started.md delete mode 120000 docs/v1/index.md delete mode 100644 docs/v1/manual.md create mode 100644 docs/v1/migrating-to-v2.md diff --git a/docs/index.md b/docs/index.md index 5f02fed..251679d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,7 +12,7 @@ an expressive way. These are the guides for each major supported version: - [`v2`](./v2/) -- [`v1`](./v1/) +- [`v1`](./v1/getting-started) In addition to the version-specific guides, these other documents are available: diff --git a/docs/v1/examples/arguments.md b/docs/v1/examples/arguments.md new file mode 100644 index 0000000..ba451b6 --- /dev/null +++ b/docs/v1/examples/arguments.md @@ -0,0 +1,32 @@ +### 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) + } +} +``` diff --git a/docs/v1/examples/bash-completions.md b/docs/v1/examples/bash-completions.md new file mode 100644 index 0000000..5648477 --- /dev/null +++ b/docs/v1/examples/bash-completions.md @@ -0,0 +1,114 @@ +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) + } +} +``` diff --git a/docs/v1/examples/combining-short-options.md b/docs/v1/examples/combining-short-options.md new file mode 100644 index 0000000..3ab6ebd --- /dev/null +++ b/docs/v1/examples/combining-short-options.md @@ -0,0 +1,67 @@ +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. diff --git a/docs/v1/examples/exit-codes.md b/docs/v1/examples/exit-codes.md new file mode 100644 index 0000000..39c0642 --- /dev/null +++ b/docs/v1/examples/exit-codes.md @@ -0,0 +1,38 @@ +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) + } +} +``` diff --git a/docs/v1/examples/flags.md b/docs/v1/examples/flags.md new file mode 100644 index 0000000..f6ae7be --- /dev/null +++ b/docs/v1/examples/flags.md @@ -0,0 +1,457 @@ +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 diff --git a/docs/v1/examples/generated-help-text.md b/docs/v1/examples/generated-help-text.md new file mode 100644 index 0000000..0c7f110 --- /dev/null +++ b/docs/v1/examples/generated-help-text.md @@ -0,0 +1,101 @@ +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) + } +} +``` diff --git a/docs/v1/examples/greet.md b/docs/v1/examples/greet.md new file mode 100644 index 0000000..7b47cf6 --- /dev/null +++ b/docs/v1/examples/greet.md @@ -0,0 +1,69 @@ +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 +``` diff --git a/docs/v1/examples/subcommands-categories.md b/docs/v1/examples/subcommands-categories.md new file mode 100644 index 0000000..b10c1f9 --- /dev/null +++ b/docs/v1/examples/subcommands-categories.md @@ -0,0 +1,50 @@ +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 +``` diff --git a/docs/v1/examples/subcommands.md b/docs/v1/examples/subcommands.md new file mode 100644 index 0000000..f062ba7 --- /dev/null +++ b/docs/v1/examples/subcommands.md @@ -0,0 +1,70 @@ +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) + } +} +``` diff --git a/docs/v1/examples/version-flag.md b/docs/v1/examples/version-flag.md new file mode 100644 index 0000000..482eea4 --- /dev/null +++ b/docs/v1/examples/version-flag.md @@ -0,0 +1,360 @@ +### 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 +} +``` diff --git a/docs/v1/getting-started.md b/docs/v1/getting-started.md new file mode 100644 index 0000000..b144483 --- /dev/null +++ b/docs/v1/getting-started.md @@ -0,0 +1,60 @@ +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. diff --git a/docs/v1/index.md b/docs/v1/index.md deleted file mode 120000 index 9d0493a..0000000 --- a/docs/v1/index.md +++ /dev/null @@ -1 +0,0 @@ -manual.md \ No newline at end of file diff --git a/docs/v1/manual.md b/docs/v1/manual.md deleted file mode 100644 index 6f568b7..0000000 --- a/docs/v1/manual.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/migrating-to-v2.md b/docs/v1/migrating-to-v2.md new file mode 100644 index 0000000..ec186ef --- /dev/null +++ b/docs/v1/migrating-to-v2.md @@ -0,0 +1,6 @@ +## 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/mkdocs.yml b/mkdocs.yml index 73b88c5..16d6748 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -11,7 +11,21 @@ edit_uri: edit/main/docs/ nav: - Home: index.md - v2 Manual: v2/index.md - - v1 Manual: v1/index.md + - v1 Manual: + - Getting Started: v1/getting-started.md + - Migrating to v2: v1/migrating-to-v2.md + - Examples: + - Greet: v1/examples/greet.md + - Arguments: v1/examples/arguments.md + - Flags: v1/examples/flags.md + - Subcommands: v1/examples/subcommands.md + - Subcommands (Categories): v1/examples/subcommands-categories.md + - Exit Codes: v1/examples/exit-codes.md + - Combining Short Options: v1/examples/combining-short-options.md + - Bash Completions: v1/examples/bash-completions.md + - Generated Help Text: v1/examples/generated-help-text.md + - Version Flag: v1/examples/version-flag.md + theme: name: material palette: @@ -25,6 +39,14 @@ theme: toggle: icon: material/brightness-7 name: light mode + features: + - content.code.annotate + - navigation.top + - navigation.instant + - navigation.expand + - navigation.sections + - navigation.tabs + - navigation.tabs.sticky plugins: - git-revision-date-localized - search From edaf885773813e3f086a53cd83413e444a53663c Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sun, 14 Aug 2022 15:55:31 -0800 Subject: [PATCH 12/97] breakup v2 documentation --- docs/v2/examples/arguments.md | 29 + docs/v2/examples/bash-completions.md | 250 +++ docs/v2/examples/combining-short-options.md | 67 + docs/v2/examples/exit-codes.md | 38 + docs/v2/examples/flags.md | 550 ++++++ docs/v2/examples/full-api-example.md | 255 +++ docs/v2/examples/generated-help-text.md | 94 + docs/v2/examples/greet.md | 66 + docs/v2/examples/subcommands-categories.md | 47 + docs/v2/examples/subcommands.md | 69 + docs/v2/examples/suggestions.md | 4 + docs/v2/examples/timestamp-flag.md | 57 + docs/v2/examples/version-flag.md | 70 + docs/v2/getting-started.md | 56 + docs/v2/index.md | 1 - docs/v2/manual.md | 1703 ------------------- docs/v2/migrating-from-older-releases.md | 5 + 17 files changed, 1657 insertions(+), 1704 deletions(-) create mode 100644 docs/v2/examples/arguments.md create mode 100644 docs/v2/examples/bash-completions.md create mode 100644 docs/v2/examples/combining-short-options.md create mode 100644 docs/v2/examples/exit-codes.md create mode 100644 docs/v2/examples/flags.md create mode 100644 docs/v2/examples/full-api-example.md create mode 100644 docs/v2/examples/generated-help-text.md create mode 100644 docs/v2/examples/greet.md create mode 100644 docs/v2/examples/subcommands-categories.md create mode 100644 docs/v2/examples/subcommands.md create mode 100644 docs/v2/examples/suggestions.md create mode 100644 docs/v2/examples/timestamp-flag.md create mode 100644 docs/v2/examples/version-flag.md create mode 100644 docs/v2/getting-started.md delete mode 120000 docs/v2/index.md delete mode 100644 docs/v2/manual.md create mode 100644 docs/v2/migrating-from-older-releases.md diff --git a/docs/v2/examples/arguments.md b/docs/v2/examples/arguments.md new file mode 100644 index 0000000..ccfd4c0 --- /dev/null +++ b/docs/v2/examples/arguments.md @@ -0,0 +1,29 @@ +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(cCtx *cli.Context) error { + fmt.Printf("Hello %q", cCtx.Args().Get(0)) + return nil + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` diff --git a/docs/v2/examples/bash-completions.md b/docs/v2/examples/bash-completions.md new file mode 100644 index 0000000..0dde02f --- /dev/null +++ b/docs/v2/examples/bash-completions.md @@ -0,0 +1,250 @@ +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.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: "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 + }, + }, + }, + }, + }, + } + + if err := app.Run(os.Args); 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(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) + } + }, + }, + }, + } + + if err := app.Run(os.Args); 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`: + +```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. + +#### 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. + +```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 +file, adding these lines: + +```sh-session +$ 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 + +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", + }, + }, + } + + 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: + +```sh-session +$ 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. + +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 +``` diff --git a/docs/v2/examples/combining-short-options.md b/docs/v2/examples/combining-short-options.md new file mode 100644 index 0000000..b0a590a --- /dev/null +++ b/docs/v2/examples/combining-short-options.md @@ -0,0 +1,67 @@ +Traditional use of options using their shortnames look like this: + +```sh-session +$ 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{ + 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 + }, + }, + }, + } + + if err := app.Run(os.Args); 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: + +```sh-session +$ 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. diff --git a/docs/v2/examples/exit-codes.md b/docs/v2/examples/exit-codes.md new file mode 100644 index 0000000..90e1384 --- /dev/null +++ b/docs/v2/examples/exit-codes.md @@ -0,0 +1,38 @@ +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 + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` diff --git a/docs/v2/examples/flags.md b/docs/v2/examples/flags.md new file mode 100644 index 0000000..a3c5e76 --- /dev/null +++ b/docs/v2/examples/flags.md @@ -0,0 +1,550 @@ +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(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 + }, + } + + if err := app.Run(os.Args); 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 ( + "fmt" + "log" + "os" + + "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(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 + }, + } + + if err := app.Run(os.Args); 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`", + }, + }, + } + + if err := app.Run(os.Args); 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", + }, + }, + } + + if err := app.Run(os.Args); 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(*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)) + + if err := app.Run(os.Args); 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"}, + }, + }, + } + + if err := app.Run(os.Args); 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"}, + }, + }, + } + + if err := app.Run(os.Args); 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.App{ + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "password", + Aliases: []string{"p"}, + Usage: "password for the mysql database", + FilePath: "/etc/mysql/password", + }, + }, + } + + if err := app.Run(os.Args); 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 + // --- >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 + // --- >8 --- + 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(*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.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 + }, + } + + if err := app.Run(os.Args); 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", + }, + }, + } + + if err := app.Run(os.Args); 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 diff --git a/docs/v2/examples/full-api-example.md b/docs/v2/examples/full-api-example.md new file mode 100644 index 0000000..53c76ea --- /dev/null +++ b/docs/v2/examples/full-api-example.md @@ -0,0 +1,255 @@ +**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(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") + + 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(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(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", cCtx.App.Command("doo")) + if cCtx.Bool("infinite") { + cCtx.App.Run([]string{"app", "doo", "wop"}) + } + + 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", 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(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.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(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{} + } + + app.Run(os.Args) +} + +func wopAction(cCtx *cli.Context) error { + fmt.Fprintf(cCtx.App.Writer, ":wave: over here, eh\n") + return nil +} +``` diff --git a/docs/v2/examples/generated-help-text.md b/docs/v2/examples/generated-help-text.md new file mode 100644 index 0000000..caa3a38 --- /dev/null +++ b/docs/v2/examples/generated-help-text.md @@ -0,0 +1,94 @@ +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) +} +``` diff --git a/docs/v2/examples/greet.md b/docs/v2/examples/greet.md new file mode 100644 index 0000000..cedee7d --- /dev/null +++ b/docs/v2/examples/greet.md @@ -0,0 +1,66 @@ +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(*cli.Context) error { + fmt.Println("Hello friend!") + return nil + }, + } + + 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! + +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) +``` diff --git a/docs/v2/examples/subcommands-categories.md b/docs/v2/examples/subcommands-categories.md new file mode 100644 index 0000000..e8ae185 --- /dev/null +++ b/docs/v2/examples/subcommands-categories.md @@ -0,0 +1,47 @@ +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", + }, + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` + +Will include: + +``` +COMMANDS: + noop + + Template actions: + add + remove +``` diff --git a/docs/v2/examples/subcommands.md b/docs/v2/examples/subcommands.md new file mode 100644 index 0000000..b7048b9 --- /dev/null +++ b/docs/v2/examples/subcommands.md @@ -0,0 +1,69 @@ +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(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 + }, + }, + }, + }, + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` diff --git a/docs/v2/examples/suggestions.md b/docs/v2/examples/suggestions.md new file mode 100644 index 0000000..3e60828 --- /dev/null +++ b/docs/v2/examples/suggestions.md @@ -0,0 +1,4 @@ +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. diff --git a/docs/v2/examples/timestamp-flag.md b/docs/v2/examples/timestamp-flag.md new file mode 100644 index 0000000..bcf0262 --- /dev/null +++ b/docs/v2/examples/timestamp-flag.md @@ -0,0 +1,57 @@ +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(cCtx *cli.Context) error { + fmt.Printf("%s", cCtx.Timestamp("meeting").String()) + return nil + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` + +In this example the flag could be used like this: + +```sh-session +$ 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/docs/v2/examples/version-flag.md b/docs/v2/examples/version-flag.md new file mode 100644 index 0000000..a014c29 --- /dev/null +++ b/docs/v2/examples/version-flag.md @@ -0,0 +1,70 @@ +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(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) +} +``` diff --git a/docs/v2/getting-started.md b/docs/v2/getting-started.md new file mode 100644 index 0000000..c2e0750 --- /dev/null +++ b/docs/v2/getting-started.md @@ -0,0 +1,56 @@ +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(*cli.Context) error { + fmt.Println("boom! I say!") + return nil + }, + } + + if err := app.Run(os.Args); 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. diff --git a/docs/v2/index.md b/docs/v2/index.md deleted file mode 120000 index 9d0493a..0000000 --- a/docs/v2/index.md +++ /dev/null @@ -1 +0,0 @@ -manual.md \ No newline at end of file diff --git a/docs/v2/manual.md b/docs/v2/manual.md deleted file mode 100644 index 475e047..0000000 --- a/docs/v2/manual.md +++ /dev/null @@ -1,1703 +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(*cli.Context) error { - fmt.Println("boom! I say!") - return nil - }, - } - - if err := app.Run(os.Args); 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(*cli.Context) error { - fmt.Println("Hello friend!") - return nil - }, - } - - 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! - -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(cCtx *cli.Context) error { - fmt.Printf("Hello %q", cCtx.Args().Get(0)) - return nil - }, - } - - if err := app.Run(os.Args); 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(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 - }, - } - - if err := app.Run(os.Args); 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 ( - "fmt" - "log" - "os" - - "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(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 - }, - } - - if err := app.Run(os.Args); 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`", - }, - }, - } - - if err := app.Run(os.Args); 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", - }, - }, - } - - if err := app.Run(os.Args); 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(*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)) - - if err := app.Run(os.Args); 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"}, - }, - }, - } - - if err := app.Run(os.Args); 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"}, - }, - }, - } - - if err := app.Run(os.Args); 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.App{ - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "password", - Aliases: []string{"p"}, - Usage: "password for the mysql database", - FilePath: "/etc/mysql/password", - }, - }, - } - - if err := app.Run(os.Args); 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 - // --- >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 - // --- >8 --- - 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(*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.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 - }, - } - - if err := app.Run(os.Args); 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", - }, - }, - } - - if err := app.Run(os.Args); 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(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 - }, - }, - }, - }, - }, - } - - if err := app.Run(os.Args); 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", - }, - }, - } - - if err := app.Run(os.Args); 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 - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` - -### Combining short options - -Traditional use of options using their shortnames look like this: - -```sh-session -$ 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{ - 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 - }, - }, - }, - } - - if err := app.Run(os.Args); 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: - -```sh-session -$ 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.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: "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 - }, - }, - }, - }, - }, - } - - if err := app.Run(os.Args); 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(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) - } - }, - }, - }, - } - - if err := app.Run(os.Args); 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`: - -```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. - -#### 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. - -```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 -file, adding these lines: - -```sh-session -$ 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 - -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", - }, - }, - } - - 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: - -```sh-session -$ 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. - -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 - -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(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) -} -``` - -### 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(cCtx *cli.Context) error { - fmt.Printf("%s", cCtx.Timestamp("meeting").String()) - return nil - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} -``` - -In this example the flag could be used like this: - -```sh-session -$ 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) - -### 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 -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(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") - - 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(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(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", cCtx.App.Command("doo")) - if cCtx.Bool("infinite") { - cCtx.App.Run([]string{"app", "doo", "wop"}) - } - - 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", 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(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.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(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{} - } - - app.Run(os.Args) -} - -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. diff --git a/docs/v2/migrating-from-older-releases.md b/docs/v2/migrating-from-older-releases.md new file mode 100644 index 0000000..564cd20 --- /dev/null +++ b/docs/v2/migrating-from-older-releases.md @@ -0,0 +1,5 @@ +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 18f8e6df0b33bb67d3da3fffd6e71092411fb861 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sun, 14 Aug 2022 15:55:40 -0800 Subject: [PATCH 13/97] update sidebar configuration --- mkdocs.yml | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 16d6748..84c2fa0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -9,8 +9,30 @@ site_url: https://cli.urfave.org/ repo_url: https://github.com/urfave/cli edit_uri: edit/main/docs/ nav: - - Home: index.md - - v2 Manual: v2/index.md + - Home: + - Welcome: index.md + - Contributing: CONTRIBUTING.md + - Code of Conduct: CODE_OF_CONDUCT.md + - Releasing: RELEASING.md + - Security: SECURITY.md + - Migrate v1 to v2: migrate-v1-to-v2.md + - v2 Manual: + - Getting Started: v2/getting-started.md + - Migrating From Older Releases: v2/migrating-from-older-releases.md + - Examples: + - Greet: v2/examples/greet.md + - Arguments: v2/examples/arguments.md + - Flags: v2/examples/flags.md + - Subcommands: v2/examples/subcommands.md + - Subcommands Categories: v2/examples/subcommands-categories.md + - Exit Codes: v2/examples/exit-codes.md + - Combining Short Options: v2/examples/combining-short-options.md + - Bash Completions: v2/examples/bash-completions.md + - Generated Help Text: v2/examples/generated-help-text.md + - Version Flag: v2/examples/version-flag.md + - Timestamp Flag: v2/examples/timestamp-flag.md + - Suggestions: v2/examples/suggestions.md + - Full API Example: v2/examples/full-api-example.md - v1 Manual: - Getting Started: v1/getting-started.md - Migrating to v2: v1/migrating-to-v2.md From 7cfafe8f806da4554f913a49ac09b562db505f9c Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sun, 14 Aug 2022 16:10:39 -0800 Subject: [PATCH 14/97] add tags plugin --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index 84c2fa0..02657b9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -72,6 +72,7 @@ theme: plugins: - git-revision-date-localized - search + - tags # NOTE: this is the recommended configuration from # https://squidfunk.github.io/mkdocs-material/setup/extensions/#recommended-configuration markdown_extensions: From 8036eac8dc7b23ef30af5597c2a8b4de2bd56409 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sun, 14 Aug 2022 16:11:56 -0800 Subject: [PATCH 15/97] add tags and boost for pages --- docs/v1/examples/arguments.md | 5 ++++- docs/v1/examples/bash-completions.md | 5 +++++ docs/v1/examples/combining-short-options.md | 5 +++++ docs/v1/examples/exit-codes.md | 5 +++++ docs/v1/examples/flags.md | 5 +++++ docs/v1/examples/generated-help-text.md | 5 +++++ docs/v1/examples/greet.md | 5 +++++ docs/v1/examples/subcommands-categories.md | 5 +++++ docs/v1/examples/subcommands.md | 5 +++++ docs/v1/examples/version-flag.md | 5 ++++- docs/v1/getting-started.md | 5 +++++ docs/v1/migrating-to-v2.md | 5 ++++- docs/v2/examples/arguments.md | 7 +++++++ docs/v2/examples/bash-completions.md | 7 +++++++ docs/v2/examples/combining-short-options.md | 7 +++++++ docs/v2/examples/exit-codes.md | 7 +++++++ docs/v2/examples/flags.md | 7 +++++++ docs/v2/examples/full-api-example.md | 7 +++++++ docs/v2/examples/generated-help-text.md | 7 +++++++ docs/v2/examples/greet.md | 7 +++++++ docs/v2/examples/subcommands-categories.md | 7 +++++++ docs/v2/examples/subcommands.md | 7 +++++++ docs/v2/examples/suggestions.md | 7 +++++++ docs/v2/examples/timestamp-flag.md | 7 +++++++ docs/v2/examples/version-flag.md | 7 +++++++ docs/v2/getting-started.md | 7 +++++++ docs/v2/migrating-from-older-releases.md | 7 +++++++ 27 files changed, 162 insertions(+), 3 deletions(-) diff --git a/docs/v1/examples/arguments.md b/docs/v1/examples/arguments.md index ba451b6..4416d32 100644 --- a/docs/v1/examples/arguments.md +++ b/docs/v1/examples/arguments.md @@ -1,4 +1,7 @@ -### Arguments +--- +tags: + - v1 +--- You can lookup arguments by calling the `Args` function on `cli.Context`, e.g.: diff --git a/docs/v1/examples/bash-completions.md b/docs/v1/examples/bash-completions.md index 5648477..73af176 100644 --- a/docs/v1/examples/bash-completions.md +++ b/docs/v1/examples/bash-completions.md @@ -1,3 +1,8 @@ +--- +tags: + - v1 +--- + 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 diff --git a/docs/v1/examples/combining-short-options.md b/docs/v1/examples/combining-short-options.md index 3ab6ebd..0496300 100644 --- a/docs/v1/examples/combining-short-options.md +++ b/docs/v1/examples/combining-short-options.md @@ -1,3 +1,8 @@ +--- +tags: + - v1 +--- + Traditional use of options using their shortnames look like this: ``` diff --git a/docs/v1/examples/exit-codes.md b/docs/v1/examples/exit-codes.md index 39c0642..1d6627d 100644 --- a/docs/v1/examples/exit-codes.md +++ b/docs/v1/examples/exit-codes.md @@ -1,3 +1,8 @@ +--- +tags: + - v1 +--- + 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 diff --git a/docs/v1/examples/flags.md b/docs/v1/examples/flags.md index f6ae7be..2f9300b 100644 --- a/docs/v1/examples/flags.md +++ b/docs/v1/examples/flags.md @@ -1,3 +1,8 @@ +--- +tags: + - v1 +--- + Setting and querying flags is simple. +```go +package main + +import ( + "fmt" + "log" + "os" + + "github.com/urfave/cli/v2" +) + +func main() { + var count int + + app := &cli.App{ + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "foo", + Usage: "foo greeting", + Count: &count, + }, + }, + Action: func(cCtx *cli.Context) error { + fmt.Println("Count %d", count) + return nil + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} +``` + #### Placeholder Values Sometimes it's useful to specify a flag's value within the usage string itself. @@ -550,6 +589,7 @@ Will result in help output like: #### Precedence The precedence for flag value sources is as follows (highest to lowest): +eikdcclkkujudfjnhknkgruvlncbgvuckugignuhturk 0. Command line flag value from user 0. Environment variable (if specified) From b694a25730c7974d5917a30d43314189168cd6ef Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 11:36:55 -0400 Subject: [PATCH 57/97] Update docs --- docs/v2/examples/flags.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/v2/examples/flags.md b/docs/v2/examples/flags.md index ed7dab2..805fb82 100644 --- a/docs/v2/examples/flags.md +++ b/docs/v2/examples/flags.md @@ -104,7 +104,7 @@ See full list of flags at https://pkg.go.dev/github.com/urfave/cli/v2 For bool flags you can specify the flag multiple times to get a count(e.g -v -v -v or -vvv) ```go package main @@ -129,7 +129,7 @@ func main() { }, }, Action: func(cCtx *cli.Context) error { - fmt.Println("Count %d", count) + fmt.Println("Count ", count) return nil }, } From cbd62ef958bcfbf2f8d8b664b945b094de77d9bd Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 11:43:09 -0400 Subject: [PATCH 58/97] Update godocs --- docs/v2/examples/flags.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/v2/examples/flags.md b/docs/v2/examples/flags.md index 805fb82..527e43c 100644 --- a/docs/v2/examples/flags.md +++ b/docs/v2/examples/flags.md @@ -129,7 +129,7 @@ func main() { }, }, Action: func(cCtx *cli.Context) error { - fmt.Println("Count ", count) + fmt.Println("count", count) return nil }, } From 3adf8fa48dac4815a15aeacebb3282ac4f53f143 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 14:28:33 -0400 Subject: [PATCH 59/97] Add args --- docs/v2/examples/flags.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/v2/examples/flags.md b/docs/v2/examples/flags.md index 527e43c..4307b01 100644 --- a/docs/v2/examples/flags.md +++ b/docs/v2/examples/flags.md @@ -104,7 +104,8 @@ See full list of flags at https://pkg.go.dev/github.com/urfave/cli/v2 For bool flags you can specify the flag multiple times to get a count(e.g -v -v -v or -vvv) ```go package main From fa3bbf91ed2b8024941ee819a379477e64c8334b Mon Sep 17 00:00:00 2001 From: dearchap Date: Mon, 5 Sep 2022 16:45:44 -0400 Subject: [PATCH 60/97] Update docs/v2/examples/flags.md Co-authored-by: Dan Buch --- docs/v2/examples/flags.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/v2/examples/flags.md b/docs/v2/examples/flags.md index 4307b01..990a928 100644 --- a/docs/v2/examples/flags.md +++ b/docs/v2/examples/flags.md @@ -104,7 +104,7 @@ See full list of flags at https://pkg.go.dev/github.com/urfave/cli/v2 For bool flags you can specify the flag multiple times to get a count(e.g -v -v -v or -vvv) ```go From c0a8506cea4bd5485e65f72d6ede6a9fd34ea1fa Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 17:35:30 -0400 Subject: [PATCH 61/97] Add context.Count --- context.go | 12 ++++++++++++ flag_bool.go | 18 +++++++++++------- flag_test.go | 29 +++++++++++++++++++++++++++-- godoc-current.txt | 3 +++ 4 files changed, 53 insertions(+), 9 deletions(-) diff --git a/context.go b/context.go index 9bdd453..5d43de0 100644 --- a/context.go +++ b/context.go @@ -105,6 +105,18 @@ func (cCtx *Context) Lineage() []*Context { return lineage } +// NumOccurrences returns the num of occurences of this flag +func (cCtx *Context) Count(name string) int { + if fs := cCtx.lookupFlagSet(name); fs != nil { + if bf, ok := fs.Lookup(name).Value.(*boolValue); ok { + if bf.count != nil { + return *bf.count + } + } + } + return 0 +} + // Value returns the value of the flag corresponding to `name` func (cCtx *Context) Value(name string) interface{} { if fs := cCtx.lookupFlagSet(name); fs != nil { diff --git a/flag_bool.go b/flag_bool.go index c727fbc..0870576 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -85,14 +85,18 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error { f.HasBeenSet = true } + count := f.Count + dest := f.Destination + + if count == nil { + count = new(int) + } + if dest == nil { + dest = new(bool) + } + for _, name := range f.Names() { - var value flag.Value - if f.Destination != nil { - value = newBoolValue(f.Value, f.Destination, f.Count) - } else { - t := new(bool) - value = newBoolValue(f.Value, t, f.Count) - } + value := newBoolValue(f.Value, dest, count) set.Var(value, name, f.Usage) } diff --git a/flag_test.go b/flag_test.go index f1d1629..5b1eb2c 100644 --- a/flag_test.go +++ b/flag_test.go @@ -67,14 +67,39 @@ func TestBoolFlagApply_SetsCount(t *testing.T) { count := 0 fl := BoolFlag{Name: "wat", Aliases: []string{"W", "huh"}, Destination: &v, Count: &count} set := flag.NewFlagSet("test", 0) - _ = fl.Apply(set) + err := fl.Apply(set) + expect(t, err, nil) - err := set.Parse([]string{"--wat", "-W", "--huh"}) + err = set.Parse([]string{"--wat", "-W", "--huh"}) expect(t, err, nil) expect(t, v, true) expect(t, count, 3) } +func TestBoolFlagCountFromContext(t *testing.T) { + set := flag.NewFlagSet("test", 0) + ctx := NewContext(nil, set, nil) + tf := &BoolFlag{Name: "tf", Aliases: []string{"w", "huh"}} + err := tf.Apply(set) + expect(t, err, nil) + + err = set.Parse([]string{"-tf", "-w", "-huh"}) + expect(t, err, nil) + expect(t, tf.Get(ctx), true) + expect(t, ctx.Count("tf"), 3) + + set1 := flag.NewFlagSet("test", 0) + ctx1 := NewContext(nil, set1, nil) + tf1 := &BoolFlag{Name: "tf", Aliases: []string{"w", "huh"}} + err = tf1.Apply(set1) + expect(t, err, nil) + + err = set1.Parse([]string{}) + expect(t, err, nil) + expect(t, tf1.Get(ctx1), false) + expect(t, ctx1.Count("tf"), 0) +} + func TestFlagsFromEnv(t *testing.T) { newSetFloat64Slice := func(defaults ...float64) Float64Slice { s := NewFloat64Slice(defaults...) diff --git a/godoc-current.txt b/godoc-current.txt index 697c130..0e09211 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -621,6 +621,9 @@ func (cCtx *Context) Args() Args func (cCtx *Context) Bool(name string) bool Bool looks up the value of a local BoolFlag, returns false if not found +func (cCtx *Context) Count(name string) int + NumOccurrences returns the num of occurences of this flag + func (cCtx *Context) Duration(name string) time.Duration Duration looks up the value of a local DurationFlag, returns 0 if not found From 6b0a3e80b52c82af51bf0485efcfd99e213432bd Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 17:40:42 -0400 Subject: [PATCH 62/97] Add parametrize tests --- flag_test.go | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/flag_test.go b/flag_test.go index 5b1eb2c..380a130 100644 --- a/flag_test.go +++ b/flag_test.go @@ -77,27 +77,36 @@ func TestBoolFlagApply_SetsCount(t *testing.T) { } func TestBoolFlagCountFromContext(t *testing.T) { - set := flag.NewFlagSet("test", 0) - ctx := NewContext(nil, set, nil) - tf := &BoolFlag{Name: "tf", Aliases: []string{"w", "huh"}} - err := tf.Apply(set) - expect(t, err, nil) - err = set.Parse([]string{"-tf", "-w", "-huh"}) - expect(t, err, nil) - expect(t, tf.Get(ctx), true) - expect(t, ctx.Count("tf"), 3) + boolCountTests := []struct { + input []string + expectedVal bool + expectedCount int + }{ + { + input: []string{"-tf", "-w", "-huh"}, + expectedVal: true, + expectedCount: 3, + }, + { + input: []string{}, + expectedVal: false, + expectedCount: 0, + }, + } - set1 := flag.NewFlagSet("test", 0) - ctx1 := NewContext(nil, set1, nil) - tf1 := &BoolFlag{Name: "tf", Aliases: []string{"w", "huh"}} - err = tf1.Apply(set1) - expect(t, err, nil) + for _, bct := range boolCountTests { + set := flag.NewFlagSet("test", 0) + ctx := NewContext(nil, set, nil) + tf := &BoolFlag{Name: "tf", Aliases: []string{"w", "huh"}} + err := tf.Apply(set) + expect(t, err, nil) - err = set1.Parse([]string{}) - expect(t, err, nil) - expect(t, tf1.Get(ctx1), false) - expect(t, ctx1.Count("tf"), 0) + err = set.Parse(bct.input) + expect(t, err, nil) + expect(t, tf.Get(ctx), bct.expectedVal) + expect(t, ctx.Count("tf"), bct.expectedCount) + } } func TestFlagsFromEnv(t *testing.T) { From a509290e7546f865d8fce8f318d763c1caf1cdf9 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 19:13:26 -0400 Subject: [PATCH 63/97] Add countable interface --- context.go | 8 +++----- flag.go | 6 ++++++ flag_bool.go | 7 +++++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/context.go b/context.go index 5d43de0..312efb5 100644 --- a/context.go +++ b/context.go @@ -105,13 +105,11 @@ func (cCtx *Context) Lineage() []*Context { return lineage } -// NumOccurrences returns the num of occurences of this flag +// Count returns the num of occurences of this flag func (cCtx *Context) Count(name string) int { if fs := cCtx.lookupFlagSet(name); fs != nil { - if bf, ok := fs.Lookup(name).Value.(*boolValue); ok { - if bf.count != nil { - return *bf.count - } + if cf, ok := fs.Lookup(name).Value.(Countable); ok { + return cf.Count() } } return 0 diff --git a/flag.go b/flag.go index 4f0871d..1618b4d 100644 --- a/flag.go +++ b/flag.go @@ -124,6 +124,12 @@ type Flag interface { GetValue() string } +// Countable is an interface to enable detection of flag values which support +// repetitive flags +type Countable interface { + Count() int +} + 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 0870576..e5fa700 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -51,6 +51,13 @@ func (b *boolValue) String() string { func (b *boolValue) IsBoolFlag() bool { return true } +func (b *boolValue) Count() int { + if b.count != nil { + return *b.count + } + return 0 +} + // 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 { From d58b3c70de7ea29021ae57b2d4b363dd38ea82f1 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 5 Sep 2022 20:04:11 -0400 Subject: [PATCH 64/97] Remove keystroke errors --- docs/v2/examples/flags.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/v2/examples/flags.md b/docs/v2/examples/flags.md index 990a928..dbb41ea 100644 --- a/docs/v2/examples/flags.md +++ b/docs/v2/examples/flags.md @@ -590,7 +590,6 @@ Will result in help output like: #### Precedence The precedence for flag value sources is as follows (highest to lowest): -eikdcclkkujudfjnhknkgruvlncbgvuckugignuhturk 0. Command line flag value from user 0. Environment variable (if specified) From fdcbf28476e361a9e29d723e6986da10b6a27476 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Fri, 12 Aug 2022 21:57:15 -0400 Subject: [PATCH 65/97] Merge changes from main --- flag-spec.yaml | 13 ++- flag.go | 23 ++++ flag_test.go | 272 +++++++++++++++++++++++++++++++++++++++++-- flag_uint64_slice.go | 192 ++++++++++++++++++++++++++++++ flag_uint_slice.go | 203 ++++++++++++++++++++++++++++++++ 5 files changed, 691 insertions(+), 12 deletions(-) create mode 100644 flag_uint64_slice.go create mode 100644 flag_uint_slice.go diff --git a/flag-spec.yaml b/flag-spec.yaml index 7e1a9e7..51601f0 100644 --- a/flag-spec.yaml +++ b/flag-spec.yaml @@ -58,7 +58,12 @@ flag_types: - { name: Layout, type: string } - { name: Timezone, type: "*time.Location" } - # TODO: enable UintSlice - # UintSlice: {} - # TODO: enable Uint64Slice once #1334 lands - # Uint64Slice: {} + UintSlice: + value_pointer: true + skip_interfaces: + - fmt.Stringer + Uint64Slice: + value_pointer: true + skip_interfaces: + - fmt.Stringer + diff --git a/flag.go b/flag.go index 1618b4d..9616cd3 100644 --- a/flag.go +++ b/flag.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "regexp" "runtime" + "strconv" "strings" "syscall" "time" @@ -302,6 +303,28 @@ func stringifyFlag(f Flag) string { fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault)) } +func stringifyUintSliceFlag(f *UintSliceFlag) string { + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.FormatUint(uint64(i), 10)) + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + +func stringifyUint64SliceFlag(f *Uint64SliceFlag) string { + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.FormatUint(i, 10)) + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + func stringifySliceFlag(usage string, names, defaultVals []string) string { placeholder, usage := unquoteUsage(usage) if placeholder == "" { diff --git a/flag_test.go b/flag_test.go index 380a130..87bf360 100644 --- a/flag_test.go +++ b/flag_test.go @@ -122,12 +122,24 @@ func TestFlagsFromEnv(t *testing.T) { return *s } + newSetUintSlice := func(defaults ...uint) UintSlice { + s := NewUintSlice(defaults...) + s.hasBeenSet = false + return *s + } + newSetInt64Slice := func(defaults ...int64) Int64Slice { s := NewInt64Slice(defaults...) s.hasBeenSet = false return *s } + newSetUint64Slice := func(defaults ...uint64) Uint64Slice { + s := NewUint64Slice(defaults...) + s.hasBeenSet = false + return *s + } + newSetStringSlice := func(defaults ...string) StringSlice { s := NewStringSlice(defaults...) s.hasBeenSet = false @@ -171,10 +183,18 @@ func TestFlagsFromEnv(t *testing.T) { {"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", newSetUintSlice(1, 2), &UintSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1.2,2", newSetUintSlice(), &UintSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as uint slice value from environment variable "SECONDS" for flag seconds: .*`}, + {"foobar", newSetUintSlice(), &UintSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint 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 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: .*`}, + {"1,2", newSetUint64Slice(1, 2), &Uint64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""}, + {"1.2,2", newSetUint64Slice(), &Uint64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as uint64 slice value from environment variable "SECONDS" for flag seconds: .*`}, + {"foobar", newSetUint64Slice(), &Uint64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint64 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"}}, ""}, @@ -315,6 +335,16 @@ func TestFlagStringifying(t *testing.T) { fl: &IntFlag{Name: "pens", DefaultText: "-19"}, expected: "--pens value\t(default: -19)", }, + { + name: "uint-slice-flag", + fl: &UintSliceFlag{Name: "pencils"}, + expected: "--pencils value\t", + }, + { + name: "uint-slice-flag-with-default-text", + fl: &UintFlag{Name: "pens", DefaultText: "29"}, + expected: "--pens value\t(default: 29)", + }, { name: "int64-flag", fl: &Int64Flag{Name: "flume"}, @@ -335,6 +365,16 @@ func TestFlagStringifying(t *testing.T) { fl: &Int64SliceFlag{Name: "handles", DefaultText: "-2"}, expected: "--handles value\t(default: -2)", }, + { + name: "uint64-slice-flag", + fl: &Uint64SliceFlag{Name: "drawers"}, + expected: "--drawers value\t", + }, + { + name: "uint64-slice-flag-with-default-text", + fl: &Uint64SliceFlag{Name: "handles", DefaultText: "-2"}, + expected: "--handles value\t(default: -2)", + }, { name: "path-flag", fl: &PathFlag{Name: "soup"}, @@ -1155,6 +1195,198 @@ func TestInt64SliceFlagValueFromContext(t *testing.T) { expect(t, f.Get(ctx), []int64{1, 2, 3}) } +var uintSliceFlagTests = []struct { + name string + aliases []string + value *UintSlice + expected string +}{ + {"heads", nil, NewUintSlice(), "--heads value\t(accepts multiple inputs)"}, + {"H", nil, NewUintSlice(), "-H value\t(accepts multiple inputs)"}, + {"heads", []string{"H"}, NewUintSlice(uint(2), uint(17179869184)), + "--heads value, -H value\t(default: 2, 17179869184)\t(accepts multiple inputs)"}, +} + +func TestUintSliceFlagHelpOutput(t *testing.T) { + for _, test := range uintSliceFlagTests { + fl := UintSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} + output := fl.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestUintSliceFlagWithEnvVarHelpOutput(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("APP_SMURF", "42,17179869184") + + for _, test := range uintSliceFlagTests { + fl := UintSliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}} + output := fl.String() + + expectedSuffix := " [$APP_SMURF]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_SMURF%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%q does not end with"+expectedSuffix, output) + } + } +} + +func TestUintSliceFlagApply_ParentContext(t *testing.T) { + _ = (&App{ + Flags: []Flag{ + &UintSliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewUintSlice(1, 2, 3)}, + }, + Commands: []*Command{ + { + Name: "child", + Action: func(ctx *Context) error { + expected := []uint{1, 2, 3} + if !reflect.DeepEqual(ctx.UintSlice("numbers"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.UintSlice("numbers")) + } + if !reflect.DeepEqual(ctx.UintSlice("n"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.UintSlice("n")) + } + return nil + }, + }, + }, + }).Run([]string{"run", "child"}) +} + +func TestUintSliceFlag_SetFromParentContext(t *testing.T) { + fl := &UintSliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewUintSlice(1, 2, 3, 4)} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + ctx := &Context{ + parentContext: &Context{ + flagSet: set, + }, + flagSet: flag.NewFlagSet("empty", 0), + } + expected := []uint{1, 2, 3, 4} + if !reflect.DeepEqual(ctx.UintSlice("numbers"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.UintSlice("numbers")) + } +} +func TestUintSliceFlag_ReturnNil(t *testing.T) { + fl := &UintSliceFlag{} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + ctx := &Context{ + parentContext: &Context{ + flagSet: set, + }, + flagSet: flag.NewFlagSet("empty", 0), + } + expected := []uint(nil) + if !reflect.DeepEqual(ctx.UintSlice("numbers"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.UintSlice("numbers")) + } +} + +var uint64SliceFlagTests = []struct { + name string + aliases []string + value *Uint64Slice + expected string +}{ + {"heads", nil, NewUint64Slice(), "--heads value\t(accepts multiple inputs)"}, + {"H", nil, NewUint64Slice(), "-H value\t(accepts multiple inputs)"}, + {"heads", []string{"H"}, NewUint64Slice(uint64(2), uint64(17179869184)), + "--heads value, -H value\t(default: 2, 17179869184)\t(accepts multiple inputs)"}, +} + +func TestUint64SliceFlagHelpOutput(t *testing.T) { + for _, test := range uint64SliceFlagTests { + fl := Uint64SliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value} + output := fl.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestUint64SliceFlagWithEnvVarHelpOutput(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("APP_SMURF", "42,17179869184") + + for _, test := range uint64SliceFlagTests { + fl := Uint64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}} + output := fl.String() + + expectedSuffix := " [$APP_SMURF]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_SMURF%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%q does not end with"+expectedSuffix, output) + } + } +} + +func TestUint64SliceFlagApply_ParentContext(t *testing.T) { + _ = (&App{ + Flags: []Flag{ + &Uint64SliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewUint64Slice(1, 2, 3)}, + }, + Commands: []*Command{ + { + Name: "child", + Action: func(ctx *Context) error { + expected := []uint64{1, 2, 3} + if !reflect.DeepEqual(ctx.Uint64Slice("numbers"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Uint64Slice("numbers")) + } + if !reflect.DeepEqual(ctx.Uint64Slice("n"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Uint64Slice("n")) + } + return nil + }, + }, + }, + }).Run([]string{"run", "child"}) +} + +func TestUint64SliceFlag_SetFromParentContext(t *testing.T) { + fl := &Uint64SliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewUint64Slice(1, 2, 3, 4)} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + ctx := &Context{ + parentContext: &Context{ + flagSet: set, + }, + flagSet: flag.NewFlagSet("empty", 0), + } + expected := []uint64{1, 2, 3, 4} + if !reflect.DeepEqual(ctx.Uint64Slice("numbers"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Uint64Slice("numbers")) + } +} +func TestUint64SliceFlag_ReturnNil(t *testing.T) { + fl := &Uint64SliceFlag{} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + ctx := &Context{ + parentContext: &Context{ + flagSet: set, + }, + flagSet: flag.NewFlagSet("empty", 0), + } + expected := []uint64(nil) + if !reflect.DeepEqual(ctx.Uint64Slice("numbers"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Uint64Slice("numbers")) + } +} + var float64FlagTests = []struct { name string expected string @@ -2444,29 +2676,41 @@ type flagDefaultTestCase struct { func TestFlagDefaultValue(t *testing.T) { cases := []*flagDefaultTestCase{ { - name: "stringSclice", + name: "stringSlice", flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")}, toParse: []string{"--flag", "parsed"}, expect: `--flag value [ --flag value ] (default: "default1", "default2")`, }, { - name: "float64Sclice", + name: "float64Slice", flag: &Float64SliceFlag{Name: "flag", Value: NewFloat64Slice(1.1, 2.2)}, toParse: []string{"--flag", "13.3"}, expect: `--flag value [ --flag value ] (default: 1.1, 2.2)`, }, { - name: "int64Sclice", + name: "int64Slice", flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2)}, toParse: []string{"--flag", "13"}, expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { - name: "intSclice", + name: "intSlice", flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)}, toParse: []string{"--flag", "13"}, expect: `--flag value [ --flag value ] (default: 1, 2)`, }, + { + name: "uint64Slice", + flag: &Uint64SliceFlag{Name: "flag", Value: NewUint64Slice(1, 2)}, + toParse: []string{"--flag", "13"}, + expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, + }, + { + name: "uintSlice", + flag: &UintSliceFlag{Name: "flag", Value: NewUintSlice(1, 2)}, + toParse: []string{"--flag", "13"}, + expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, + }, { name: "string", flag: &StringFlag{Name: "flag", Value: "default"}, @@ -2509,29 +2753,41 @@ type flagValueTestCase struct { func TestFlagValue(t *testing.T) { cases := []*flagValueTestCase{ &flagValueTestCase{ - name: "stringSclice", + name: "stringSlice", flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")}, toParse: []string{"--flag", "parsed,parsed2", "--flag", "parsed3,parsed4"}, expect: `[parsed parsed2 parsed3 parsed4]`, }, &flagValueTestCase{ - name: "float64Sclice", + name: "float64Slice", 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", + name: "int64Slice", 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", + name: "intSlice", flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)}, toParse: []string{"--flag", "13,14", "--flag", "15,16"}, expect: `[]int{13, 14, 15, 16}`, }, + &flagValueTestCase{ + name: "uint64Slice", + flag: &Uint64SliceFlag{Name: "flag", Value: NewUint64Slice(1, 2)}, + toParse: []string{"--flag", "13,14", "--flag", "15,16"}, + expect: `[]uint64{13, 14, 15, 16}`, + }, + &flagValueTestCase{ + name: "uintSlice", + flag: &UintSliceFlag{Name: "flag", Value: NewUintSlice(1, 2)}, + toParse: []string{"--flag", "13,14", "--flag", "15,16"}, + expect: `[]uint{13, 14, 15, 16}`, + }, } for i, v := range cases { set := flag.NewFlagSet("test", 0) diff --git a/flag_uint64_slice.go b/flag_uint64_slice.go new file mode 100644 index 0000000..da01ead --- /dev/null +++ b/flag_uint64_slice.go @@ -0,0 +1,192 @@ +package cli + +import ( + "encoding/json" + "flag" + "fmt" + "strconv" + "strings" +) + +// Uint64Slice wraps []int64 to satisfy flag.Value +type Uint64Slice struct { + slice []uint64 + hasBeenSet bool +} + +// NewUint64Slice makes an *Uint64Slice with default values +func NewUint64Slice(defaults ...uint64) *Uint64Slice { + return &Uint64Slice{slice: append([]uint64{}, defaults...)} +} + +// clone allocate a copy of self object +func (i *Uint64Slice) clone() *Uint64Slice { + n := &Uint64Slice{ + slice: make([]uint64, len(i.slice)), + hasBeenSet: i.hasBeenSet, + } + copy(n.slice, i.slice) + return n +} + +// Set parses the value into an integer and appends it to the list of values +func (i *Uint64Slice) Set(value string) error { + if !i.hasBeenSet { + i.slice = []uint64{} + i.hasBeenSet = true + } + + if strings.HasPrefix(value, slPfx) { + // Deserializing assumes overwrite + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.slice) + i.hasBeenSet = true + return nil + } + + for _, s := range flagSplitMultiValues(value) { + tmp, err := strconv.ParseUint(strings.TrimSpace(s), 0, 64) + if err != nil { + return err + } + + i.slice = append(i.slice, tmp) + } + + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (i *Uint64Slice) String() string { + v := i.slice + if v == nil { + // treat nil the same as zero length non-nil + v = make([]uint64, 0) + } + str := fmt.Sprintf("%d", v) + str = strings.Replace(str, " ", ", ", -1) + str = strings.Replace(str, "[", "{", -1) + str = strings.Replace(str, "]", "}", -1) + return fmt.Sprintf("[]uint64%s", str) +} + +// Serialize allows Uint64Slice to fulfill Serializer +func (i *Uint64Slice) Serialize() string { + jsonBytes, _ := json.Marshal(i.slice) + return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) +} + +// Value returns the slice of ints set by this flag +func (i *Uint64Slice) Value() []uint64 { + return i.slice +} + +// Get returns the slice of ints set by this flag +func (i *Uint64Slice) Get() interface{} { + return *i +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *Uint64SliceFlag) String() string { + return withEnvHint(f.GetEnvVars(), stringifyUint64SliceFlag(f)) +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *Uint64SliceFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *Uint64SliceFlag) GetUsage() string { + return f.Usage +} + +// GetCategory returns the category for the flag +func (f *Uint64SliceFlag) 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 *Uint64SliceFlag) GetValue() string { + if f.Value != nil { + return f.Value.String() + } + return "" +} + +// GetDefaultText returns the default text for this flag +func (f *Uint64SliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *Uint64SliceFlag) GetEnvVars() []string { + return f.EnvVars +} + +// Apply populates the flag given the flag set and environment +func (f *Uint64SliceFlag) Apply(set *flag.FlagSet) error { + // apply any default + if f.Destination != nil && f.Value != nil { + f.Destination.slice = make([]uint64, len(f.Value.slice)) + copy(f.Destination.slice, f.Value.slice) + } + + // resolve setValue (what we will assign to the set) + var setValue *Uint64Slice + switch { + case f.Destination != nil: + setValue = f.Destination + case f.Value != nil: + setValue = f.Value.clone() + default: + setValue = new(Uint64Slice) + } + + if val, source, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok && val != "" { + for _, s := range flagSplitMultiValues(val) { + if err := setValue.Set(strings.TrimSpace(s)); err != nil { + return fmt.Errorf("could not parse %q as uint64 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. + setValue.hasBeenSet = false + f.HasBeenSet = true + } + + for _, name := range f.Names() { + set.Var(setValue, name, f.Usage) + } + + return nil +} + +// Get returns the flag’s value in the given Context. +func (f *Uint64SliceFlag) Get(ctx *Context) []uint64 { + return ctx.Uint64Slice(f.Name) +} + +// Uint64Slice looks up the value of a local Uint64SliceFlag, returns +// nil if not found +func (cCtx *Context) Uint64Slice(name string) []uint64 { + if fs := cCtx.lookupFlagSet(name); fs != nil { + return lookupUint64Slice(name, fs) + } + return nil +} + +func lookupUint64Slice(name string, set *flag.FlagSet) []uint64 { + f := set.Lookup(name) + if f != nil { + if slice, ok := unwrapFlagValue(f.Value).(*Uint64Slice); ok { + return slice.Value() + } + } + return nil +} diff --git a/flag_uint_slice.go b/flag_uint_slice.go new file mode 100644 index 0000000..8cdd758 --- /dev/null +++ b/flag_uint_slice.go @@ -0,0 +1,203 @@ +package cli + +import ( + "encoding/json" + "flag" + "fmt" + "strconv" + "strings" +) + +// UintSlice wraps []int to satisfy flag.Value +type UintSlice struct { + slice []uint + hasBeenSet bool +} + +// NewUintSlice makes an *UintSlice with default values +func NewUintSlice(defaults ...uint) *UintSlice { + return &UintSlice{slice: append([]uint{}, defaults...)} +} + +// clone allocate a copy of self object +func (i *UintSlice) clone() *UintSlice { + n := &UintSlice{ + slice: make([]uint, len(i.slice)), + hasBeenSet: i.hasBeenSet, + } + copy(n.slice, i.slice) + return n +} + +// TODO: Consistently have specific Set function for Int64 and Float64 ? +// SetInt directly adds an integer to the list of values +func (i *UintSlice) SetUint(value uint) { + if !i.hasBeenSet { + i.slice = []uint{} + i.hasBeenSet = true + } + + i.slice = append(i.slice, value) +} + +// Set parses the value into an integer and appends it to the list of values +func (i *UintSlice) Set(value string) error { + if !i.hasBeenSet { + i.slice = []uint{} + i.hasBeenSet = true + } + + if strings.HasPrefix(value, slPfx) { + // Deserializing assumes overwrite + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.slice) + i.hasBeenSet = true + return nil + } + + for _, s := range flagSplitMultiValues(value) { + tmp, err := strconv.ParseUint(strings.TrimSpace(s), 0, 32) + if err != nil { + return err + } + + i.slice = append(i.slice, uint(tmp)) + } + + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (i *UintSlice) String() string { + v := i.slice + if v == nil { + // treat nil the same as zero length non-nil + v = make([]uint, 0) + } + str := fmt.Sprintf("%d", v) + str = strings.Replace(str, " ", ", ", -1) + str = strings.Replace(str, "[", "{", -1) + str = strings.Replace(str, "]", "}", -1) + return fmt.Sprintf("[]uint%s", str) +} + +// Serialize allows UintSlice to fulfill Serializer +func (i *UintSlice) Serialize() string { + jsonBytes, _ := json.Marshal(i.slice) + return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) +} + +// Value returns the slice of ints set by this flag +func (i *UintSlice) Value() []uint { + return i.slice +} + +// Get returns the slice of ints set by this flag +func (i *UintSlice) Get() interface{} { + return *i +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *UintSliceFlag) String() string { + return withEnvHint(f.GetEnvVars(), stringifyUintSliceFlag(f)) +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *UintSliceFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *UintSliceFlag) GetUsage() string { + return f.Usage +} + +// GetCategory returns the category for the flag +func (f *UintSliceFlag) 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 *UintSliceFlag) GetValue() string { + if f.Value != nil { + return f.Value.String() + } + return "" +} + +// GetDefaultText returns the default text for this flag +func (f *UintSliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *UintSliceFlag) GetEnvVars() []string { + return f.EnvVars +} + +// Apply populates the flag given the flag set and environment +func (f *UintSliceFlag) Apply(set *flag.FlagSet) error { + // apply any default + if f.Destination != nil && f.Value != nil { + f.Destination.slice = make([]uint, len(f.Value.slice)) + copy(f.Destination.slice, f.Value.slice) + } + + // resolve setValue (what we will assign to the set) + var setValue *UintSlice + switch { + case f.Destination != nil: + setValue = f.Destination + case f.Value != nil: + setValue = f.Value.clone() + default: + setValue = new(UintSlice) + } + + if val, source, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok && val != "" { + for _, s := range flagSplitMultiValues(val) { + if err := setValue.Set(strings.TrimSpace(s)); err != nil { + return fmt.Errorf("could not parse %q as uint 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. + setValue.hasBeenSet = false + f.HasBeenSet = true + } + + for _, name := range f.Names() { + set.Var(setValue, name, f.Usage) + } + + return nil +} + +// Get returns the flag’s value in the given Context. +func (f *UintSliceFlag) Get(ctx *Context) []uint { + return ctx.UintSlice(f.Name) +} + +// UintSlice looks up the value of a local UintSliceFlag, returns +// nil if not found +func (cCtx *Context) UintSlice(name string) []uint { + if fs := cCtx.lookupFlagSet(name); fs != nil { + return lookupUintSlice(name, fs) + } + return nil +} + +func lookupUintSlice(name string, set *flag.FlagSet) []uint { + f := set.Lookup(name) + if f != nil { + if slice, ok := unwrapFlagValue(f.Value).(*UintSlice); ok { + return slice.Value() + } + } + return nil +} From 0764ca2295864eac676a5292224ce7ef59bd2002 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 15 Aug 2022 10:26:36 -0400 Subject: [PATCH 66/97] Rebase --- flag.go | 23 ----------- zz_generated.flags.go | 80 ++++++++++++++++++++++++++++++++++++++ zz_generated.flags_test.go | 38 ++++++++++++++++++ 3 files changed, 118 insertions(+), 23 deletions(-) diff --git a/flag.go b/flag.go index 9616cd3..1618b4d 100644 --- a/flag.go +++ b/flag.go @@ -7,7 +7,6 @@ import ( "io/ioutil" "regexp" "runtime" - "strconv" "strings" "syscall" "time" @@ -303,28 +302,6 @@ func stringifyFlag(f Flag) string { fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault)) } -func stringifyUintSliceFlag(f *UintSliceFlag) string { - var defaultVals []string - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, strconv.FormatUint(uint64(i), 10)) - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - -func stringifyUint64SliceFlag(f *Uint64SliceFlag) string { - var defaultVals []string - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, strconv.FormatUint(i, 10)) - } - } - - return stringifySliceFlag(f.Usage, f.Names(), defaultVals) -} - func stringifySliceFlag(usage string, names, defaultVals []string) string { placeholder, usage := unquoteUsage(usage) if placeholder == "" { diff --git a/zz_generated.flags.go b/zz_generated.flags.go index a40f803..b4081d4 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -497,6 +497,86 @@ func (f *TimestampFlag) GetDefaultText() string { return f.GetValue() } +// Uint64SliceFlag is a flag with type *Uint64Slice +type Uint64SliceFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *Uint64Slice + Destination *Uint64Slice + + Aliases []string + EnvVars []string +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Uint64SliceFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *Uint64SliceFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether or not the flag is required +func (f *Uint64SliceFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Uint64SliceFlag) IsVisible() bool { + return !f.Hidden +} + +// UintSliceFlag is a flag with type *UintSlice +type UintSliceFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *UintSlice + Destination *UintSlice + + Aliases []string + EnvVars []string +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *UintSliceFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *UintSliceFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether or not the flag is required +func (f *UintSliceFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *UintSliceFlag) IsVisible() bool { + return !f.Hidden +} + // BoolFlag is a flag with type bool type BoolFlag struct { Name string diff --git a/zz_generated.flags_test.go b/zz_generated.flags_test.go index a0769a2..fa01014 100644 --- a/zz_generated.flags_test.go +++ b/zz_generated.flags_test.go @@ -160,6 +160,44 @@ func TestTimestampFlag_SatisfiesFmtStringerInterface(t *testing.T) { _ = f.String() } +func TestUint64SliceFlag_SatisfiesFlagInterface(t *testing.T) { + var f cli.Flag = &cli.Uint64SliceFlag{} + + _ = f.IsSet() + _ = f.Names() +} + +func TestUint64SliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.Uint64SliceFlag{} + + _ = f.IsRequired() +} + +func TestUint64SliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.Uint64SliceFlag{} + + _ = f.IsVisible() +} + +func TestUintSliceFlag_SatisfiesFlagInterface(t *testing.T) { + var f cli.Flag = &cli.UintSliceFlag{} + + _ = f.IsSet() + _ = f.Names() +} + +func TestUintSliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { + var f cli.RequiredFlag = &cli.UintSliceFlag{} + + _ = f.IsRequired() +} + +func TestUintSliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { + var f cli.VisibleFlag = &cli.UintSliceFlag{} + + _ = f.IsVisible() +} + func TestBoolFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.BoolFlag{} From 7097d0eedd477ce5f152d99c6247cce7d7e6d1a0 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 15 Aug 2022 11:41:28 -0400 Subject: [PATCH 67/97] Fix tests per latest main --- flag_test.go | 16 ++++++++-------- flag_uint64_slice.go | 13 ++++++++++++- flag_uint_slice.go | 13 ++++++++++++- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/flag_test.go b/flag_test.go index 87bf360..051135d 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1201,10 +1201,10 @@ var uintSliceFlagTests = []struct { value *UintSlice expected string }{ - {"heads", nil, NewUintSlice(), "--heads value\t(accepts multiple inputs)"}, - {"H", nil, NewUintSlice(), "-H value\t(accepts multiple inputs)"}, + {"heads", nil, NewUintSlice(), "--heads value [ --heads value ]\t"}, + {"H", nil, NewUintSlice(), "-H value [ -H value ]\t"}, {"heads", []string{"H"}, NewUintSlice(uint(2), uint(17179869184)), - "--heads value, -H value\t(default: 2, 17179869184)\t(accepts multiple inputs)"}, + "--heads value, -H value [ --heads value, -H value ]\t(default: 2, 17179869184)"}, } func TestUintSliceFlagHelpOutput(t *testing.T) { @@ -1297,10 +1297,10 @@ var uint64SliceFlagTests = []struct { value *Uint64Slice expected string }{ - {"heads", nil, NewUint64Slice(), "--heads value\t(accepts multiple inputs)"}, - {"H", nil, NewUint64Slice(), "-H value\t(accepts multiple inputs)"}, + {"heads", nil, NewUint64Slice(), "--heads value [ --heads value ]\t"}, + {"H", nil, NewUint64Slice(), "-H value [ -H value ]\t"}, {"heads", []string{"H"}, NewUint64Slice(uint64(2), uint64(17179869184)), - "--heads value, -H value\t(default: 2, 17179869184)\t(accepts multiple inputs)"}, + "--heads value, -H value [ --heads value, -H value ]\t(default: 2, 17179869184)"}, } func TestUint64SliceFlagHelpOutput(t *testing.T) { @@ -2703,13 +2703,13 @@ func TestFlagDefaultValue(t *testing.T) { name: "uint64Slice", flag: &Uint64SliceFlag{Name: "flag", Value: NewUint64Slice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "uintSlice", flag: &UintSliceFlag{Name: "flag", Value: NewUintSlice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value (default: 1, 2) (accepts multiple inputs)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "string", diff --git a/flag_uint64_slice.go b/flag_uint64_slice.go index da01ead..e60c3ea 100644 --- a/flag_uint64_slice.go +++ b/flag_uint64_slice.go @@ -88,7 +88,7 @@ func (i *Uint64Slice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *Uint64SliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), stringifyUint64SliceFlag(f)) + return withEnvHint(f.GetEnvVars(), f.stringify()) } // TakesValue returns true of the flag takes a value, otherwise false @@ -172,6 +172,17 @@ func (f *Uint64SliceFlag) Get(ctx *Context) []uint64 { return ctx.Uint64Slice(f.Name) } +func (f *Uint64SliceFlag) stringify() string { + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.FormatUint(i, 10)) + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + // Uint64Slice looks up the value of a local Uint64SliceFlag, returns // nil if not found func (cCtx *Context) Uint64Slice(name string) []uint64 { diff --git a/flag_uint_slice.go b/flag_uint_slice.go index 8cdd758..350b29c 100644 --- a/flag_uint_slice.go +++ b/flag_uint_slice.go @@ -99,7 +99,7 @@ func (i *UintSlice) Get() interface{} { // String returns a readable representation of this value // (for usage defaults) func (f *UintSliceFlag) String() string { - return withEnvHint(f.GetEnvVars(), stringifyUintSliceFlag(f)) + return withEnvHint(f.GetEnvVars(), f.stringify()) } // TakesValue returns true of the flag takes a value, otherwise false @@ -183,6 +183,17 @@ func (f *UintSliceFlag) Get(ctx *Context) []uint { return ctx.UintSlice(f.Name) } +func (f *UintSliceFlag) stringify() string { + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.FormatUint(uint64(i), 10)) + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + // UintSlice looks up the value of a local UintSliceFlag, returns // nil if not found func (cCtx *Context) UintSlice(name string) []uint { From 7405a90b381ee948c6803d08eddbcddf9dbebd58 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Wed, 31 Aug 2022 08:20:57 -0400 Subject: [PATCH 68/97] Fix formatting --- flag_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/flag_test.go b/flag_test.go index 051135d..158a627 100644 --- a/flag_test.go +++ b/flag_test.go @@ -2679,55 +2679,55 @@ func TestFlagDefaultValue(t *testing.T) { name: "stringSlice", flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")}, toParse: []string{"--flag", "parsed"}, - expect: `--flag value [ --flag value ] (default: "default1", "default2")`, + expect: `--flag value [ --flag value ] (default: "default1", "default2")`, }, { name: "float64Slice", flag: &Float64SliceFlag{Name: "flag", Value: NewFloat64Slice(1.1, 2.2)}, toParse: []string{"--flag", "13.3"}, - expect: `--flag value [ --flag value ] (default: 1.1, 2.2)`, + expect: `--flag value [ --flag value ] (default: 1.1, 2.2)`, }, { name: "int64Slice", flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value [ --flag value ] (default: 1, 2)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "intSlice", flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value [ --flag value ] (default: 1, 2)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "uint64Slice", flag: &Uint64SliceFlag{Name: "flag", Value: NewUint64Slice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value [ --flag value ] (default: 1, 2)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "uintSlice", flag: &UintSliceFlag{Name: "flag", Value: NewUintSlice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value [ --flag value ] (default: 1, 2)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "string", flag: &StringFlag{Name: "flag", Value: "default"}, toParse: []string{"--flag", "parsed"}, - expect: `--flag value (default: "default")`, + expect: `--flag value (default: "default")`, }, { name: "bool", flag: &BoolFlag{Name: "flag", Value: true}, toParse: []string{"--flag", "false"}, - expect: `--flag (default: true)`, + expect: `--flag (default: true)`, }, { name: "uint64", flag: &Uint64Flag{Name: "flag", Value: 1}, toParse: []string{"--flag", "13"}, - expect: `--flag value (default: 1)`, + expect: `--flag value (default: 1)`, }, } for i, v := range cases { From 9bcffd07a4f8784f75409682d343367446d0c9f0 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Wed, 31 Aug 2022 08:25:29 -0400 Subject: [PATCH 69/97] Merge from main --- altsrc/flag_test.go | 2 +- flag_test.go | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index 6222ec2..67ccf4f 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -40,7 +40,7 @@ func (ris *racyInputSource) isSet(name string) bool { func TestGenericApplyInputSourceValue_Alias(t *testing.T) { v := &Parser{"abc", "def"} tis := testApplyInputSource{ - Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Aliases: []string{"test_alias"}, Value: &Parser{}}), + Flag: NewGenericFlag(&cli.GenericFlag{Name: "test", Aliases: []string{"test_alias"}, Value: &Parser{}}), FlagName: "test_alias", MapValue: v, } diff --git a/flag_test.go b/flag_test.go index 158a627..051135d 100644 --- a/flag_test.go +++ b/flag_test.go @@ -2679,55 +2679,55 @@ func TestFlagDefaultValue(t *testing.T) { name: "stringSlice", flag: &StringSliceFlag{Name: "flag", Value: NewStringSlice("default1", "default2")}, toParse: []string{"--flag", "parsed"}, - expect: `--flag value [ --flag value ] (default: "default1", "default2")`, + expect: `--flag value [ --flag value ] (default: "default1", "default2")`, }, { name: "float64Slice", flag: &Float64SliceFlag{Name: "flag", Value: NewFloat64Slice(1.1, 2.2)}, toParse: []string{"--flag", "13.3"}, - expect: `--flag value [ --flag value ] (default: 1.1, 2.2)`, + expect: `--flag value [ --flag value ] (default: 1.1, 2.2)`, }, { name: "int64Slice", flag: &Int64SliceFlag{Name: "flag", Value: NewInt64Slice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value [ --flag value ] (default: 1, 2)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "intSlice", flag: &IntSliceFlag{Name: "flag", Value: NewIntSlice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value [ --flag value ] (default: 1, 2)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "uint64Slice", flag: &Uint64SliceFlag{Name: "flag", Value: NewUint64Slice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value [ --flag value ] (default: 1, 2)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "uintSlice", flag: &UintSliceFlag{Name: "flag", Value: NewUintSlice(1, 2)}, toParse: []string{"--flag", "13"}, - expect: `--flag value [ --flag value ] (default: 1, 2)`, + expect: `--flag value [ --flag value ] (default: 1, 2)`, }, { name: "string", flag: &StringFlag{Name: "flag", Value: "default"}, toParse: []string{"--flag", "parsed"}, - expect: `--flag value (default: "default")`, + expect: `--flag value (default: "default")`, }, { name: "bool", flag: &BoolFlag{Name: "flag", Value: true}, toParse: []string{"--flag", "false"}, - expect: `--flag (default: true)`, + expect: `--flag (default: true)`, }, { name: "uint64", flag: &Uint64Flag{Name: "flag", Value: 1}, toParse: []string{"--flag", "13"}, - expect: `--flag value (default: 1)`, + expect: `--flag value (default: 1)`, }, } for i, v := range cases { From 2a5960c1cb10a6b83a93d4e31553b21229e529bd Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Tue, 6 Sep 2022 07:10:47 -0400 Subject: [PATCH 70/97] Add coverage threshold --- .github/.codecov.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/.codecov.yml b/.github/.codecov.yml index 69cb760..5395ce7 100644 --- a/.github/.codecov.yml +++ b/.github/.codecov.yml @@ -1 +1,3 @@ comment: false +coverage: + threshold: 5% From 5b96605ae5082e8a32383c0d06af5d815a7fac83 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sat, 10 Sep 2022 09:27:33 -0400 Subject: [PATCH 71/97] Add additional test to fix codecov --- flag_test.go | 223 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) diff --git a/flag_test.go b/flag_test.go index 051135d..4434fce 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1044,6 +1044,47 @@ func TestIntSliceFlagApply_SetsAllNames(t *testing.T) { expect(t, err, nil) } +func TestIntSliceFlagApply_UsesEnvValues_noDefault(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "1 , 2") + var val IntSlice + fl := IntSliceFlag{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(), []int(nil)) + expect(t, set.Lookup("goat").Value.(*IntSlice).Value(), []int{1, 2}) +} + +func TestIntSliceFlagApply_UsesEnvValues_withDefault(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "1 , 2") + val := NewIntSlice(3, 4) + fl := IntSliceFlag{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(), []int{3, 4}) + expect(t, set.Lookup("goat").Value.(*IntSlice).Value(), []int{1, 2}) +} + +func TestIntSliceFlagApply_DefaultValueWithDestination(t *testing.T) { + defValue := []int{1, 2} + + fl := IntSliceFlag{Name: "country", Value: NewIntSlice(defValue...), Destination: NewIntSlice(3)} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{}) + expect(t, err, nil) + expect(t, defValue, fl.Destination.Value()) +} + func TestIntSliceFlagApply_ParentContext(t *testing.T) { _ = (&App{ Flags: []Flag{ @@ -1133,6 +1174,56 @@ func TestInt64SliceFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestInt64SliceFlagApply_SetsAllNames(t *testing.T) { + fl := Int64SliceFlag{Name: "bits", Aliases: []string{"B", "bips"}} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{"--bits", "23", "-B", "3", "--bips", "99"}) + expect(t, err, nil) +} + +func TestInt64SliceFlagApply_UsesEnvValues_noDefault(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "1 , 2") + var val Int64Slice + fl := Int64SliceFlag{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(), []int64(nil)) + expect(t, set.Lookup("goat").Value.(*Int64Slice).Value(), []int64{1, 2}) +} + +func TestInt64SliceFlagApply_UsesEnvValues_withDefault(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "1 , 2") + val := NewInt64Slice(3, 4) + fl := Int64SliceFlag{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(), []int64{3, 4}) + expect(t, set.Lookup("goat").Value.(*Int64Slice).Value(), []int64{1, 2}) +} + +func TestInt64SliceFlagApply_DefaultValueWithDestination(t *testing.T) { + defValue := []int64{1, 2} + + fl := Int64SliceFlag{Name: "country", Value: NewInt64Slice(defValue...), Destination: NewInt64Slice(3)} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{}) + expect(t, err, nil) + expect(t, defValue, fl.Destination.Value()) +} + func TestInt64SliceFlagApply_ParentContext(t *testing.T) { _ = (&App{ Flags: []Flag{ @@ -1237,6 +1328,56 @@ func TestUintSliceFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestUintSliceFlagApply_SetsAllNames(t *testing.T) { + fl := UintSliceFlag{Name: "bits", Aliases: []string{"B", "bips"}} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{"--bits", "23", "-B", "3", "--bips", "99"}) + expect(t, err, nil) +} + +func TestUintSliceFlagApply_UsesEnvValues_noDefault(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "1 , 2") + var val UintSlice + fl := UintSliceFlag{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(), []uint(nil)) + expect(t, set.Lookup("goat").Value.(*UintSlice).Value(), []uint{1, 2}) +} + +func TestUintSliceFlagApply_UsesEnvValues_withDefault(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "1 , 2") + val := NewUintSlice(3, 4) + fl := UintSliceFlag{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(), []uint{3, 4}) + expect(t, set.Lookup("goat").Value.(*UintSlice).Value(), []uint{1, 2}) +} + +func TestUintSliceFlagApply_DefaultValueWithDestination(t *testing.T) { + defValue := []uint{1, 2} + + fl := UintSliceFlag{Name: "country", Value: NewUintSlice(defValue...), Destination: NewUintSlice(3)} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{}) + expect(t, err, nil) + expect(t, defValue, fl.Destination.Value()) +} + func TestUintSliceFlagApply_ParentContext(t *testing.T) { _ = (&App{ Flags: []Flag{ @@ -1333,6 +1474,56 @@ func TestUint64SliceFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestUint64SliceFlagApply_SetsAllNames(t *testing.T) { + fl := Uint64SliceFlag{Name: "bits", Aliases: []string{"B", "bips"}} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{"--bits", "23", "-B", "3", "--bips", "99"}) + expect(t, err, nil) +} + +func TestUint64SliceFlagApply_UsesEnvValues_noDefault(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "1 , 2") + var val Uint64Slice + fl := Uint64SliceFlag{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(), []uint64(nil)) + expect(t, set.Lookup("goat").Value.(*Uint64Slice).Value(), []uint64{1, 2}) +} + +func TestUint64SliceFlagApply_UsesEnvValues_withDefault(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "1 , 2") + val := NewUint64Slice(3, 4) + fl := Uint64SliceFlag{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(), []uint64{3, 4}) + expect(t, set.Lookup("goat").Value.(*Uint64Slice).Value(), []uint64{1, 2}) +} + +func TestUint64SliceFlagApply_DefaultValueWithDestination(t *testing.T) { + defValue := []uint64{1, 2} + + fl := Uint64SliceFlag{Name: "country", Value: NewUint64Slice(defValue...), Destination: NewUint64Slice(3)} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{}) + expect(t, err, nil) + expect(t, defValue, fl.Destination.Value()) +} + func TestUint64SliceFlagApply_ParentContext(t *testing.T) { _ = (&App{ Flags: []Flag{ @@ -2577,6 +2768,38 @@ func TestInt64Slice_Serialized_Set(t *testing.T) { } } +func TestUintSlice_Serialized_Set(t *testing.T) { + sl0 := NewUintSlice(1, 2) + ser0 := sl0.Serialize() + + if len(ser0) < len(slPfx) { + t.Fatalf("serialized shorter than expected: %q", ser0) + } + + sl1 := NewUintSlice(3, 4) + _ = sl1.Set(ser0) + + if sl0.String() != sl1.String() { + t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1) + } +} + +func TestUint64Slice_Serialized_Set(t *testing.T) { + sl0 := NewUint64Slice(1, 2) + ser0 := sl0.Serialize() + + if len(ser0) < len(slPfx) { + t.Fatalf("serialized shorter than expected: %q", ser0) + } + + sl1 := NewUint64Slice(3, 4) + _ = sl1.Set(ser0) + + if sl0.String() != sl1.String() { + t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1) + } +} + func TestTimestamp_set(t *testing.T) { ts := Timestamp{ timestamp: nil, From 9c5b385796cc152b82a8c01e12dfb3822ad97aa6 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sat, 10 Sep 2022 09:36:30 -0400 Subject: [PATCH 72/97] Add additional test to fix codecov --- flag_test.go | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/flag_test.go b/flag_test.go index 4434fce..e7fa309 100644 --- a/flag_test.go +++ b/flag_test.go @@ -1676,6 +1676,56 @@ func TestFloat64SliceFlagWithEnvVarHelpOutput(t *testing.T) { } } +func TestFloat64SliceFlagApply_SetsAllNames(t *testing.T) { + fl := Float64SliceFlag{Name: "bits", Aliases: []string{"B", "bips"}} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{"--bits", "23", "-B", "3", "--bips", "99"}) + expect(t, err, nil) +} + +func TestFloat64SliceFlagApply_UsesEnvValues_noDefault(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "1.0 , 2.0") + var val Float64Slice + fl := Float64SliceFlag{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(), []float64(nil)) + expect(t, set.Lookup("goat").Value.(*Float64Slice).Value(), []float64{1, 2}) +} + +func TestFloat64SliceFlagApply_UsesEnvValues_withDefault(t *testing.T) { + defer resetEnv(os.Environ()) + os.Clearenv() + _ = os.Setenv("MY_GOAT", "1.0 , 2.0") + val := NewFloat64Slice(3.0, 4.0) + fl := Float64SliceFlag{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(), []float64{3, 4}) + expect(t, set.Lookup("goat").Value.(*Float64Slice).Value(), []float64{1, 2}) +} + +func TestFloat64SliceFlagApply_DefaultValueWithDestination(t *testing.T) { + defValue := []float64{1.0, 2.0} + + fl := Float64SliceFlag{Name: "country", Value: NewFloat64Slice(defValue...), Destination: NewFloat64Slice(3)} + set := flag.NewFlagSet("test", 0) + _ = fl.Apply(set) + + err := set.Parse([]string{}) + expect(t, err, nil) + expect(t, defValue, fl.Destination.Value()) +} + func TestFloat64SliceFlagValueFromContext(t *testing.T) { set := flag.NewFlagSet("test", 0) set.Var(NewFloat64Slice(1.23, 4.56), "myflag", "doc") @@ -1684,6 +1734,29 @@ func TestFloat64SliceFlagValueFromContext(t *testing.T) { expect(t, f.Get(ctx), []float64{1.23, 4.56}) } +func TestFloat64SliceFlagApply_ParentContext(t *testing.T) { + _ = (&App{ + Flags: []Flag{ + &Float64SliceFlag{Name: "numbers", Aliases: []string{"n"}, Value: NewFloat64Slice(1.0, 2.0, 3.0)}, + }, + Commands: []*Command{ + { + Name: "child", + Action: func(ctx *Context) error { + expected := []float64{1.0, 2.0, 3.0} + if !reflect.DeepEqual(ctx.Float64Slice("numbers"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Float64Slice("numbers")) + } + if !reflect.DeepEqual(ctx.Float64Slice("n"), expected) { + t.Errorf("child context unable to view parent flag: %v != %v", expected, ctx.Float64Slice("n")) + } + return nil + }, + }, + }, + }).Run([]string{"run", "child"}) +} + var genericFlagTests = []struct { name string value Generic From 0658d61a0e3fc5eb3717f39a4c41511f9bf5dc59 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sat, 10 Sep 2022 10:29:41 -0400 Subject: [PATCH 73/97] Set codecov status thresholds to 5% and rename file to be less dotty --- .github/.codecov.yml | 3 --- .github/codecov.yml | 9 +++++++++ 2 files changed, 9 insertions(+), 3 deletions(-) delete mode 100644 .github/.codecov.yml create mode 100644 .github/codecov.yml diff --git a/.github/.codecov.yml b/.github/.codecov.yml deleted file mode 100644 index 5395ce7..0000000 --- a/.github/.codecov.yml +++ /dev/null @@ -1,3 +0,0 @@ -comment: false -coverage: - threshold: 5% diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 0000000..256cd10 --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,9 @@ +comment: false +coverage: + status: + project: + default: + threshold: 5% + patch: + default: + threshold: 5% From e13c16bb10ec44e8c839dd3b03ff077c1027522a Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sat, 10 Sep 2022 18:03:25 -0400 Subject: [PATCH 74/97] Fix: dont generate pointer for dest for Generic flag --- cmd/urfave-cli-genflags/generated.gotmpl | 2 +- cmd/urfave-cli-genflags/main.go | 17 +++-- flag-spec.yaml | 89 +++++++++++++----------- zz_generated.flags.go | 2 +- 4 files changed, 63 insertions(+), 47 deletions(-) diff --git a/cmd/urfave-cli-genflags/generated.gotmpl b/cmd/urfave-cli-genflags/generated.gotmpl index 4511803..85bd66f 100644 --- a/cmd/urfave-cli-genflags/generated.gotmpl +++ b/cmd/urfave-cli-genflags/generated.gotmpl @@ -17,7 +17,7 @@ type {{.TypeName}} struct { HasBeenSet bool Value {{if .ValuePointer}}*{{end}}{{.GoType}} - Destination *{{.GoType}} + Destination {{if .NoDestinationPointer}}{{else}}*{{end}}{{.GoType}} Aliases []string EnvVars []string diff --git a/cmd/urfave-cli-genflags/main.go b/cmd/urfave-cli-genflags/main.go index c754147..1b0fe53 100644 --- a/cmd/urfave-cli-genflags/main.go +++ b/cmd/urfave-cli-genflags/main.go @@ -223,10 +223,11 @@ func (gfs *Spec) SortedFlagTypes() []*FlagType { } type FlagTypeConfig struct { - SkipInterfaces []string `yaml:"skip_interfaces"` - StructFields []*FlagStructField `yaml:"struct_fields"` - TypeName string `yaml:"type_name"` - ValuePointer bool `yaml:"value_pointer"` + SkipInterfaces []string `yaml:"skip_interfaces"` + StructFields []*FlagStructField `yaml:"struct_fields"` + TypeName string `yaml:"type_name"` + ValuePointer bool `yaml:"value_pointer"` + NoDestinationPointer bool `yaml:"no_destination_pointer"` } type FlagStructField struct { @@ -256,6 +257,14 @@ func (ft *FlagType) ValuePointer() bool { return ft.Config.ValuePointer } +func (ft *FlagType) NoDestinationPointer() bool { + if ft.Config == nil { + return false + } + + return ft.Config.NoDestinationPointer +} + func (ft *FlagType) TypeName() string { return TypeName(ft.GoType, ft.Config) } diff --git a/flag-spec.yaml b/flag-spec.yaml index 51601f0..1fdeb6e 100644 --- a/flag-spec.yaml +++ b/flag-spec.yaml @@ -6,64 +6,71 @@ flag_types: bool: no_default_text: true struct_fields: - - { name: Count, type: int, pointer: true } - float64: {} - int64: - struct_fields: - - { name: Base, type: int } - int: - struct_fields: - - { name: Base, type: int } - time.Duration: {} - uint64: - struct_fields: - - { name: Base, type: int } - uint: - struct_fields: - - { name: Base, type: int } - - string: - no_default_text: true - struct_fields: - - { name: TakesFile, type: bool } - Generic: - struct_fields: - - { name: TakesFile, type: bool } - Path: - no_default_text: true - struct_fields: - - { name: TakesFile, type: bool } - + - name: Count + type: int + pointer: true + float64: Float64Slice: value_pointer: true skip_interfaces: - fmt.Stringer - Int64Slice: - value_pointer: true - skip_interfaces: - - fmt.Stringer + int: + struct_fields: + - name: Base + type: int IntSlice: value_pointer: true skip_interfaces: - fmt.Stringer - StringSlice: + int64: + struct_fields: + - name: Base + type: int + Int64Slice: value_pointer: true skip_interfaces: - fmt.Stringer + uint: struct_fields: - - { name: TakesFile, type: bool } - Timestamp: - value_pointer: true - struct_fields: - - { name: Layout, type: string } - - { name: Timezone, type: "*time.Location" } - + - name: Base + type: int UintSlice: value_pointer: true skip_interfaces: - fmt.Stringer + uint64: + struct_fields: + - name: Base + type: int Uint64Slice: + value_pointer: true + skip_interfaces: + - fmt.Stringer + string: + struct_fields: + - name: TakesFile + type: bool + StringSlice: value_pointer: true skip_interfaces: - fmt.Stringer - + struct_fields: + - name: TakesFile + type: bool + time.Duration: + Timestamp: + value_pointer: true + struct_fields: + - name: Layout + type: string + - name: Timezone + type: "*time.Location" + Generic: + no_destination_pointer: true + struct_fields: + - name: TakesFile + type: bool + Path: + struct_fields: + - name: TakesFile + type: bool diff --git a/zz_generated.flags.go b/zz_generated.flags.go index b4081d4..2c5bea0 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -86,7 +86,7 @@ type GenericFlag struct { HasBeenSet bool Value Generic - Destination *Generic + Destination Generic Aliases []string EnvVars []string From 8e3fa067e04a6e4d315d47e5f17abee4efb6ec89 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sun, 11 Sep 2022 10:21:56 -0400 Subject: [PATCH 75/97] Fix: Help name consistency among app/commands and subcommands --- app.go | 1 - help_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/app.go b/app.go index 3785b83..d07839f 100644 --- a/app.go +++ b/app.go @@ -133,7 +133,6 @@ func compileTime() time.Time { func NewApp() *App { return &App{ Name: filepath.Base(os.Args[0]), - HelpName: filepath.Base(os.Args[0]), Usage: "A new cli application", UsageText: "", BashComplete: DefaultAppComplete, diff --git a/help_test.go b/help_test.go index 9422b4a..ade6f3d 100644 --- a/help_test.go +++ b/help_test.go @@ -428,6 +428,58 @@ func TestShowCommandHelp_CommandAliases(t *testing.T) { } } +func TestHelpNameConsistency(t *testing.T) { + // Setup some very basic templates based on actual AppHelp, CommandHelp + // and SubcommandHelp templates to display the help name + // The inconsistency shows up when users use NewApp() as opposed to + // using App{...} directly + SubcommandHelpTemplate = `{{.HelpName}}` + app := NewApp() + app.Name = "bar" + app.CustomAppHelpTemplate = `{{.HelpName}}` + app.Commands = []*Command{ + { + Name: "command1", + CustomHelpTemplate: `{{.HelpName}}`, + Subcommands: []*Command{ + { + Name: "subcommand1", + CustomHelpTemplate: `{{.HelpName}}`, + }, + }, + }, + } + + tests := []struct { + name string + args []string + }{ + { + name: "App help", + args: []string{"foo"}, + }, + { + name: "Command help", + args: []string{"foo", "command1"}, + }, + { + name: "Subcommand help", + args: []string{"foo", "command1", "subcommand1"}, + }, + } + + for _, tt := range tests { + output := &bytes.Buffer{} + app.Writer = output + if err := app.Run(tt.args); err != nil { + t.Error(err) + } + if !strings.Contains(output.String(), "bar") { + t.Errorf("expected output to contain bar; got: %q", output.String()) + } + } +} + func TestShowSubcommandHelp_CommandAliases(t *testing.T) { app := &App{ Commands: []*Command{ From 8cc0a9c5dadd4074a706c46e78d1f2cfdfeea31c Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 11 Sep 2022 12:43:29 -0400 Subject: [PATCH 76/97] Move more functionality into internal/build/build.go and use make targets in CI, pass flag spec YAML through yq includes result of running `make v2approve` --- .github/workflows/cli.yml | 107 ++++------- Makefile | 20 ++- docs/CONTRIBUTING.md | 2 +- flag-spec.yaml | 18 +- internal/build/build.go | 368 ++++++++++++++++++++++++++++++-------- 5 files changed, 343 insertions(+), 172 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index c479c1e..665507f 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -1,5 +1,4 @@ name: Run Tests - on: push: branches: @@ -12,10 +11,8 @@ on: branches: - main - v3-dev-main - permissions: contents: read - jobs: test: strategy: @@ -25,45 +22,26 @@ jobs: name: ${{ matrix.os }} @ Go ${{ matrix.go }} runs-on: ${{ matrix.os }} steps: - - name: Set up Go ${{ matrix.go }} - uses: actions/setup-go@v3 + - uses: actions/setup-go@v3 with: go-version: ${{ matrix.go }} - - name: Set PATH - run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}" - - - name: install goimports - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' - run: GOBIN=${PWD}/.local/bin go install golang.org/x/tools/cmd/goimports@latest - - - name: Checkout Code - uses: actions/checkout@v3 - - - name: goimports check - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' - run: test -z $(goimports -l .) - - - name: vet - run: go run internal/build/build.go vet - - - name: test with urfave_cli_no_docs tag - run: go run internal/build/build.go -tags urfave_cli_no_docs test - - - name: test - run: go run internal/build/build.go test - - - name: test urfave-cli-genflags - run: make -C cmd/urfave-cli-genflags - - - 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 - - - name: Upload coverage to Codecov - if: success() && matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' + run: echo "${GITHUB_WORKSPACE}/.local/bin" | tee -a "${GITHUB_PATH}" >/dev/null + - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' + run: make ensure-goimports + - uses: actions/checkout@v3 + - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' + run: make lint + - run: make vet + - run: make tag-test + - run: make test + - run: make -C cmd/urfave-cli-genflags + - run: make check-binary-size + - run: make tag-check-binary-size + - run: make yamlfmt + - run: make diffcheck + - run: make v2diff + - if: success() && matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} @@ -73,36 +51,18 @@ jobs: name: test-docs runs-on: ubuntu-latest steps: - - name: Set up Go - uses: actions/setup-go@v3 + - uses: actions/setup-go@v3 with: go-version: 1.19.x - - - name: Use Node.js 16 - uses: actions/setup-node@v3 + - uses: actions/setup-node@v3 with: node-version: '16' - - name: Set PATH - run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}" - - - name: Checkout Code - 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" - - - name: gfmrun - run: go run internal/build/build.go gfmrun --walk docs/v3/ - - - name: diff check - run: | - git diff --exit-code - git diff --cached --exit-code - + run: echo "${GITHUB_WORKSPACE}/.local/bin" | tee -a "${GITHUB_PATH}" >/dev/null + - uses: actions/checkout@v3 + - run: make ensure-gfmrun + - run: make gfmrun + - run: make diffcheck publish: permissions: contents: write @@ -114,18 +74,11 @@ jobs: needs: [test-docs] runs-on: ubuntu-latest steps: - - name: Checkout Code - uses: actions/checkout@v3 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - - 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 - run: | - mkdocs gh-deploy --force + - run: make ci-ensure-mkdocs + - run: make set-mkdocs-remote + env: + MKDOCS_REMOTE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - run: make deploy-mkdocs diff --git a/Makefile b/Makefile index 3b0e5e0..46deea2 100644 --- a/Makefile +++ b/Makefile @@ -4,8 +4,10 @@ # are very important so that maintainers and contributors can focus their # attention on files that are primarily Go. +GO_RUN_BUILD := go run internal/build/build.go + .PHONY: all -all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun v2diff +all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun yamlfmt 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 @@ -13,28 +15,28 @@ all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun v # # $ make test GFLAGS='--packages cli' %: - go run internal/build/build.go $(GFLAGS) $* $(FLAGS) + $(GO_RUN_BUILD) $(GFLAGS) $* $(FLAGS) .PHONY: tag-test tag-test: - go run internal/build/build.go -tags urfave_cli_no_docs test + $(GO_RUN_BUILD) -tags urfave_cli_no_docs 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_BUILD) -tags urfave_cli_no_docs check-binary-size .PHONY: gfmrun gfmrun: - go run internal/build/build.go gfmrun docs/v2/manual.md + $(GO_RUN_BUILD) gfmrun --walk docs/v2/ + +.PHONY: ci-ensure-mkdocs +ci-ensure-mkdocs: + $(GO_RUN_BUILD) ensure-mkdocs --upgrade-pip .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/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 4462899..d657477 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -107,7 +107,7 @@ following `make` targets may be used if desired: ```sh # install documentation dependencies with `pip` -make docs-deps +make ensure-mkdocs ``` ```sh diff --git a/flag-spec.yaml b/flag-spec.yaml index 1fdeb6e..66745c6 100644 --- a/flag-spec.yaml +++ b/flag-spec.yaml @@ -3,13 +3,13 @@ # `Spec` type that maps to this file structure. flag_types: - bool: + bool: no_default_text: true struct_fields: - - name: Count + - name: Count type: int pointer: true - float64: + float64: Float64Slice: value_pointer: true skip_interfaces: @@ -34,7 +34,7 @@ flag_types: struct_fields: - name: Base type: int - UintSlice: + UintSlice: value_pointer: true skip_interfaces: - fmt.Stringer @@ -42,10 +42,10 @@ flag_types: struct_fields: - name: Base type: int - Uint64Slice: + Uint64Slice: value_pointer: true skip_interfaces: - - fmt.Stringer + - fmt.Stringer string: struct_fields: - name: TakesFile @@ -57,12 +57,12 @@ flag_types: struct_fields: - name: TakesFile type: bool - time.Duration: + time.Duration: Timestamp: value_pointer: true struct_fields: - - name: Layout - type: string + - name: Layout + type: string - name: Timezone type: "*time.Location" Generic: diff --git a/internal/build/build.go b/internal/build/build.go index e346e2d..2c82f13 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -5,12 +5,17 @@ package main import ( "bufio" "bytes" + "errors" "fmt" + "io" "log" "math" + "net/http" + "net/url" "os" "os/exec" "path/filepath" + "runtime" "strings" "github.com/urfave/cli/v3" @@ -21,6 +26,8 @@ const ( goodNewsEmoji = "✨" checksPassedEmoji = "✅" + gfmrunVersion = "v1.3.0" + v2diffWarning = ` # The unified diff above indicates that the public API surface area # has changed. If you feel that the changes are acceptable and adhere @@ -45,63 +52,108 @@ func main() { log.Fatal(err) } - app := cli.NewApp() - - app.Name = "builder" - app.Usage = "Generates a new urfave/cli build!" - - app.Commands = cli.Commands{ - { - Name: "vet", - Action: VetActionFunc, - }, - { - Name: "test", - Action: TestActionFunc, - }, - { - Name: "gfmrun", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "walk", - Value: false, - Usage: "Walk the specified directory and perform validation on all markdown files", + app := &cli.App{ + Name: "builder", + Usage: "Do a thing for urfave/cli! (maybe build?)", + Commands: cli.Commands{ + { + Name: "vet", + Action: topRunAction("go", "vet", "./..."), + }, + { + Name: "test", + Action: TestActionFunc, + }, + { + Name: "gfmrun", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "walk", + Value: false, + Usage: "Walk the specified directory and perform validation on all markdown files", + }, }, + Action: GfmrunActionFunc, }, - Action: GfmrunActionFunc, - }, - { - Name: "check-binary-size", - Action: checkBinarySizeActionFunc, - }, - { - Name: "generate", - Action: GenerateActionFunc, - }, - { - Name: "v2diff", - Flags: []cli.Flag{ - &cli.BoolFlag{Name: "color", Value: false}, + { + Name: "check-binary-size", + Action: checkBinarySizeActionFunc, + }, + { + Name: "generate", + Action: GenerateActionFunc, + }, + { + Name: "yamlfmt", + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "strict", Value: false, Usage: "require presence of yq"}, + }, + Action: YAMLFmtActionFunc, + }, + { + Name: "diffcheck", + Action: DiffCheckActionFunc, + }, + { + Name: "ensure-goimports", + Action: EnsureGoimportsActionFunc, + }, + { + Name: "ensure-gfmrun", + Action: EnsureGfmrunActionFunc, + }, + { + Name: "ensure-mkdocs", + Action: EnsureMkdocsActionFunc, + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "upgrade-pip"}, + }, + }, + { + Name: "set-mkdocs-remote", + Action: SetMkdocsRemoteActionFunc, + Flags: []cli.Flag{ + &cli.StringFlag{Name: "github-token", Required: true}, + }, + }, + { + Name: "deploy-mkdocs", + Action: topRunAction("mkdocs", "gh-deploy", "--force"), + }, + { + Name: "lint", + Action: LintActionFunc, + }, + { + Name: "v2diff", + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "color", Value: false}, + }, + Action: V2Diff, + }, + { + Name: "v2approve", + Action: topRunAction( + "cp", + "-v", + "godoc-current.txt", + filepath.Join("testdata", "godoc-v2.x.txt"), + ), }, - Action: V2Diff, - }, - { - Name: "v2approve", - Action: V2Approve, - }, - } - 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"), + 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"), + }, }, } @@ -120,6 +172,14 @@ func sh(exe string, args ...string) (string, error) { return string(outBytes), err } +func topRunAction(arg string, args ...string) cli.ActionFunc { + return func(cCtx *cli.Context) error { + os.Chdir(cCtx.Path("top")) + + return runCmd(arg, args...) + } +} + func runCmd(arg string, args ...string) error { cmd := exec.Command(arg, args...) @@ -131,6 +191,43 @@ func runCmd(arg string, args ...string) error { return cmd.Run() } +func downloadFile(src, dest string, dirPerm, perm os.FileMode) error { + req, err := http.NewRequest(http.MethodGet, src, nil) + if err != nil { + return err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + + defer resp.Body.Close() + + if resp.StatusCode >= 300 { + return fmt.Errorf("download response %[1]v", resp.StatusCode) + } + + if err := os.MkdirAll(filepath.Dir(dest), dirPerm); err != nil { + return err + } + + out, err := os.Create(dest) + if err != nil { + return err + } + + if _, err := io.Copy(out, resp.Body); err != nil { + return err + } + + if err := out.Close(); err != nil { + return err + } + + return os.Chmod(dest, perm) +} + func VetActionFunc(cCtx *cli.Context) error { return runCmd("go", "vet", cCtx.Path("top")+"/...") } @@ -145,15 +242,20 @@ func TestActionFunc(c *cli.Context) error { packageName = fmt.Sprintf("github.com/urfave/cli/v3/%s", pkg) } - if err := runCmd( - "go", "test", - "-tags", tags, + args := []string{"test"} + if tags != "" { + args = append(args, []string{"-tags", tags}...) + } + + args = append(args, []string{ "-v", - "--coverprofile", pkg+".coverprofile", + "--coverprofile", pkg + ".coverprofile", "--covermode", "count", "--cover", packageName, packageName, - ); err != nil { + }...) + + if err := runCmd("go", args...); err != nil { return err } } @@ -411,6 +513,125 @@ func GenerateActionFunc(cCtx *cli.Context) error { return runCmd("go", "generate", cCtx.Path("top")+"/...") } +func YAMLFmtActionFunc(cCtx *cli.Context) error { + yqBin, err := exec.LookPath("yq") + if err != nil { + if !cCtx.Bool("strict") { + fmt.Fprintln(cCtx.App.ErrWriter, "# ---> no yq found; skipping") + return nil + } + + return err + } + + os.Chdir(cCtx.Path("top")) + + return runCmd(yqBin, "eval", "--inplace", "flag-spec.yaml") +} + +func DiffCheckActionFunc(cCtx *cli.Context) error { + os.Chdir(cCtx.Path("top")) + + if err := runCmd("git", "diff", "--exit-code"); err != nil { + return err + } + + return runCmd("git", "diff", "--cached", "--exit-code") +} + +func EnsureGoimportsActionFunc(cCtx *cli.Context) error { + top := cCtx.Path("top") + os.Chdir(top) + + if err := runCmd( + "goimports", + "-d", + filepath.Join(top, "internal/build/build.go"), + ); err == nil { + return nil + } + + os.Setenv("GOBIN", filepath.Join(top, ".local/bin")) + + return runCmd("go", "install", "golang.org/x/tools/cmd/goimports@latest") +} + +func EnsureGfmrunActionFunc(cCtx *cli.Context) error { + top := cCtx.Path("top") + gfmrunExe := filepath.Join(top, ".local/bin/gfmrun") + + os.Chdir(top) + + if v, err := sh(gfmrunExe, "--version"); err == nil && strings.TrimSpace(v) == gfmrunVersion { + return nil + } + + gfmrunURL, err := url.Parse( + fmt.Sprintf( + "https://github.com/urfave/gfmrun/releases/download/%[1]s/gfmrun-%[2]s-%[3]s-%[1]s", + gfmrunVersion, runtime.GOOS, runtime.GOARCH, + ), + ) + if err != nil { + return err + } + + return downloadFile(gfmrunURL.String(), gfmrunExe, 0755, 0755) +} + +func EnsureMkdocsActionFunc(cCtx *cli.Context) error { + os.Chdir(cCtx.Path("top")) + + if err := runCmd("mkdocs", "--version"); err == nil { + return nil + } + + if cCtx.Bool("upgrade-pip") { + if err := runCmd("pip", "install", "-U", "pip"); err != nil { + return err + } + } + + return runCmd("pip", "install", "-r", "mkdocs-requirements.txt") +} + +func SetMkdocsRemoteActionFunc(cCtx *cli.Context) error { + ghToken := strings.TrimSpace(cCtx.String("github-token")) + if ghToken == "" { + return errors.New("empty github token") + } + + os.Chdir(cCtx.Path("top")) + + if err := runCmd("git", "remote", "rm", "origin"); err != nil { + return err + } + + return runCmd( + "git", "remote", "add", "origin", + fmt.Sprintf("https://x-access-token:%[1]s@github.com/urfave/cli.git", ghToken), + ) +} + +func LintActionFunc(cCtx *cli.Context) error { + top := cCtx.Path("top") + os.Chdir(top) + + out, err := sh(filepath.Join(top, ".local/bin/goimports"), "-l", ".") + if err != nil { + return err + } + + if strings.TrimSpace(out) != "" { + fmt.Fprintln(cCtx.App.ErrWriter, "# ---> goimports -l is non-empty:") + fmt.Fprintln(cCtx.App.ErrWriter, out) + + return errors.New("goimports needed") + } + + return nil +} + func V2Diff(cCtx *cli.Context) error { os.Chdir(cCtx.Path("top")) @@ -439,34 +660,29 @@ func V2Diff(cCtx *cli.Context) error { return err } -func V2Approve(cCtx *cli.Context) error { - top := cCtx.Path("top") +func getSize(sourcePath, builtPath, tags string) (int64, error) { + args := []string{"build"} - return runCmd( - "cp", - "-v", - filepath.Join(top, "godoc-current.txt"), - filepath.Join(top, "testdata", "godoc-v2.x.txt"), - ) -} + if tags != "" { + args = append(args, []string{"-tags", tags}...) + } -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) - if err != nil { + args = append(args, []string{ + "-o", builtPath, + "-ldflags", "-s -w", + sourcePath, + }...) + + if err := runCmd("go", args...); err != nil { fmt.Println("issue getting size for example binary") return 0, err } - // get file info fileInfo, err := os.Stat(builtPath) if err != nil { fmt.Println("issue getting size for example binary") return 0, err } - // size! - size = fileInfo.Size() - - return size, nil + return fileInfo.Size(), nil } From d9d960a58a1808ed36eb7ab9e8316b36ddf591c9 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 11 Sep 2022 16:12:16 -0400 Subject: [PATCH 77/97] Run make target after the Makefile is available --- .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 665507f..b666745 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -27,9 +27,9 @@ jobs: go-version: ${{ matrix.go }} - name: Set PATH run: echo "${GITHUB_WORKSPACE}/.local/bin" | tee -a "${GITHUB_PATH}" >/dev/null + - uses: actions/checkout@v3 - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' run: make ensure-goimports - - uses: actions/checkout@v3 - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' run: make lint - run: make vet From 2bec081c3a6f9d7d9131c2d0b8b349ad551ac265 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 11 Sep 2022 16:37:56 -0400 Subject: [PATCH 78/97] Use windows compatible path append --- .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 b666745..341f3c3 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -26,7 +26,7 @@ jobs: with: go-version: ${{ matrix.go }} - name: Set PATH - run: echo "${GITHUB_WORKSPACE}/.local/bin" | tee -a "${GITHUB_PATH}" >/dev/null + run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}" - uses: actions/checkout@v3 - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' run: make ensure-goimports @@ -58,7 +58,7 @@ jobs: with: node-version: '16' - name: Set PATH - run: echo "${GITHUB_WORKSPACE}/.local/bin" | tee -a "${GITHUB_PATH}" >/dev/null + run: echo "${GITHUB_WORKSPACE}/.local/bin" >>"${GITHUB_PATH}" - uses: actions/checkout@v3 - run: make ensure-gfmrun - run: make gfmrun From 0593812915690af82f5fbf5e88f55d0dce02ca79 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 11 Sep 2022 18:22:43 -0400 Subject: [PATCH 79/97] Only run `make v2diff` on go `1.19.x` + `ubuntu-latest` --- .github/workflows/cli.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 341f3c3..addd90e 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -40,7 +40,8 @@ jobs: - run: make tag-check-binary-size - run: make yamlfmt - run: make diffcheck - - run: make v2diff + - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' + run: make v2diff - if: success() && matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@v3 with: From d6c9f6a8988ce5be34cef2ddce3dd6bf9683bb4a Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 11 Sep 2022 18:32:04 -0400 Subject: [PATCH 80/97] Replace a few more custom make targets --- .github/workflows/cli.yml | 14 +++++++++++--- Makefile | 16 ---------------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index addd90e..92f58b2 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -33,11 +33,15 @@ jobs: - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' run: make lint - run: make vet - - run: make tag-test + - run: make test + env: + FLAGS: -tags urfave_cli_no_docs - run: make test - run: make -C cmd/urfave-cli-genflags - run: make check-binary-size - - run: make tag-check-binary-size + env: + FLAGS: -tags urfave_cli_no_docs + - run: make check-binary-size - run: make yamlfmt - run: make diffcheck - if: matrix.go == '1.19.x' && matrix.os == 'ubuntu-latest' @@ -63,6 +67,8 @@ jobs: - uses: actions/checkout@v3 - run: make ensure-gfmrun - run: make gfmrun + env: + FLAGS: --walk docs/v2/ - run: make diffcheck publish: permissions: @@ -78,7 +84,9 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 - - run: make ci-ensure-mkdocs + - run: make ensure-mkdocs + env: + FLAGS: --upgrade-pip - run: make set-mkdocs-remote env: MKDOCS_REMOTE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/Makefile b/Makefile index 46deea2..797d093 100644 --- a/Makefile +++ b/Makefile @@ -17,22 +17,6 @@ all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun y %: $(GO_RUN_BUILD) $(GFLAGS) $* $(FLAGS) -.PHONY: tag-test -tag-test: - $(GO_RUN_BUILD) -tags urfave_cli_no_docs test - -.PHONY: tag-check-binary-size -tag-check-binary-size: - $(GO_RUN_BUILD) -tags urfave_cli_no_docs check-binary-size - -.PHONY: gfmrun -gfmrun: - $(GO_RUN_BUILD) gfmrun --walk docs/v2/ - -.PHONY: ci-ensure-mkdocs -ci-ensure-mkdocs: - $(GO_RUN_BUILD) ensure-mkdocs --upgrade-pip - .PHONY: docs docs: mkdocs build From 9ed5a09fbbd2a0e214e310f30c74e3daa10cb43f Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 11 Sep 2022 18:34:13 -0400 Subject: [PATCH 81/97] Use correct env var for global flags --- .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 92f58b2..7a3ac9b 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -35,12 +35,12 @@ jobs: - run: make vet - run: make test env: - FLAGS: -tags urfave_cli_no_docs + GFLAGS: -tags urfave_cli_no_docs - run: make test - run: make -C cmd/urfave-cli-genflags - run: make check-binary-size env: - FLAGS: -tags urfave_cli_no_docs + GFLAGS: -tags urfave_cli_no_docs - run: make check-binary-size - run: make yamlfmt - run: make diffcheck From 1c3ebfab32b9b680c14a3cf119addc885b273c1c Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 12 Sep 2022 07:33:08 -0400 Subject: [PATCH 82/97] Accept the `MKDOCS_REMOTE_GITHUB_TOKEN` var as intended --- internal/build/build.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/build/build.go b/internal/build/build.go index 2c82f13..dcef50b 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -113,7 +113,11 @@ func main() { Name: "set-mkdocs-remote", Action: SetMkdocsRemoteActionFunc, Flags: []cli.Flag{ - &cli.StringFlag{Name: "github-token", Required: true}, + &cli.StringFlag{ + Name: "github-token", + EnvVars: []string{"MKDOCS_REMOTE_GITHUB_TOKEN"}, + Required: true, + }, }, }, { From d2acd0ed5545f86a6786f0a8e7c5a005a717c581 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 12 Sep 2022 08:54:22 -0400 Subject: [PATCH 83/97] Fix:(issue_1197) Set destination field from altsrc for slice flags --- altsrc/flag.go | 6 ++++++ altsrc/yaml_file_loader.go | 4 +--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/altsrc/flag.go b/altsrc/flag.go index 3518434..c60e151 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -109,6 +109,9 @@ func (f *StringSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSour continue } underlyingFlag.Value = &sliceValue + if f.Destination != nil { + f.Destination.Set(sliceValue.Serialize()) + } } } return nil @@ -137,6 +140,9 @@ func (f *IntSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceC continue } underlyingFlag.Value = &sliceValue + if f.Destination != nil { + f.Destination.Set(sliceValue.Serialize()) + } } } return nil diff --git a/altsrc/yaml_file_loader.go b/altsrc/yaml_file_loader.go index 66afe58..b1f6c84 100644 --- a/altsrc/yaml_file_loader.go +++ b/altsrc/yaml_file_loader.go @@ -33,11 +33,9 @@ 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(cCtx *cli.Context) (InputSourceContext, error) { return func(cCtx *cli.Context) (InputSourceContext, error) { - if cCtx.IsSet(flagFileName) { - filePath := cCtx.String(flagFileName) + if filePath := cCtx.String(flagFileName); filePath != "" { return NewYamlSourceFromFile(filePath) } - return defaultInputSource() } } From e9e87f624de33c2ca946a2b8ab7333e2ce04b91a Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Mon, 12 Sep 2022 13:40:08 -0400 Subject: [PATCH 84/97] Add unit tests --- altsrc/flag.go | 12 ++++----- altsrc/flag_test.go | 64 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/altsrc/flag.go b/altsrc/flag.go index c60e151..a12262b 100644 --- a/altsrc/flag.go +++ b/altsrc/flag.go @@ -109,9 +109,9 @@ func (f *StringSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSour continue } underlyingFlag.Value = &sliceValue - if f.Destination != nil { - f.Destination.Set(sliceValue.Serialize()) - } + } + if f.Destination != nil { + f.Destination.Set(sliceValue.Serialize()) } } return nil @@ -140,9 +140,9 @@ func (f *IntSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceC continue } underlyingFlag.Value = &sliceValue - if f.Destination != nil { - f.Destination.Set(sliceValue.Serialize()) - } + } + if f.Destination != nil { + f.Destination.Set(sliceValue.Serialize()) } } return nil diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go index 67ccf4f..1a5da15 100644 --- a/altsrc/flag_test.go +++ b/altsrc/flag_test.go @@ -100,39 +100,61 @@ func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) { } func TestStringSliceApplyInputSourceValue_Alias(t *testing.T) { + dest := cli.NewStringSlice() tis := testApplyInputSource{ - Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", Aliases: []string{"test_alias"}}), + Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", Aliases: []string{"test_alias"}, Destination: dest}), FlagName: "test_alias", MapValue: []interface{}{"hello", "world"}, } c := runTest(t, tis) expect(t, c.StringSlice("test_alias"), []string{"hello", "world"}) + expect(t, dest.Value(), []string{"hello", "world"}) + // reset dest + dest = cli.NewStringSlice() + tis = testApplyInputSource{ + Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", Aliases: []string{"test_alias"}, Destination: dest}), + FlagName: "test_alias", + MapValue: []interface{}{"hello", "world"}, + } c = runRacyTest(t, tis) refute(t, c.StringSlice("test_alias"), []string{"hello", "world"}) + refute(t, dest.Value(), []string{"hello", "world"}) } func TestStringSliceApplyInputSourceValue(t *testing.T) { + dest := cli.NewStringSlice() tis := testApplyInputSource{ - Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}), + Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", Destination: dest}), FlagName: "test", MapValue: []interface{}{"hello", "world"}, } c := runTest(t, tis) expect(t, c.StringSlice("test"), []string{"hello", "world"}) + expect(t, dest.Value(), []string{"hello", "world"}) + // reset dest + dest = cli.NewStringSlice() + tis = testApplyInputSource{ + Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", Destination: dest}), + FlagName: "test", + MapValue: []interface{}{"hello", "world"}, + } c = runRacyTest(t, tis) refute(t, c.StringSlice("test"), []string{"hello", "world"}) + refute(t, dest.Value(), []string{"hello", "world"}) } func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) { + dest := cli.NewStringSlice() c := runTest(t, testApplyInputSource{ - Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}), + Flag: NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", Destination: dest}), FlagName: "test", MapValue: []interface{}{"hello", "world"}, ContextValueString: "ohno", }) expect(t, c.StringSlice("test"), []string{"ohno"}) + expect(t, dest.Value(), []string{"ohno"}) } func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { @@ -151,43 +173,73 @@ func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { } func TestIntSliceApplyInputSourceValue_Alias(t *testing.T) { + dest := cli.NewIntSlice() tis := testApplyInputSource{ - Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", Aliases: []string{"test_alias"}}), + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", Aliases: []string{"test_alias"}, Destination: dest}), FlagName: "test_alias", MapValue: []interface{}{1, 2}, } c := runTest(t, tis) expect(t, c.IntSlice("test_alias"), []int{1, 2}) + expect(t, dest.Value(), []int{1, 2}) + dest = cli.NewIntSlice() + tis = testApplyInputSource{ + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", Aliases: []string{"test_alias"}, Destination: dest}), + FlagName: "test_alias", + MapValue: []interface{}{1, 2}, + } c = runRacyTest(t, tis) refute(t, c.IntSlice("test_alias"), []int{1, 2}) + refute(t, dest.Value(), []int{1, 2}) } func TestIntSliceApplyInputSourceValue(t *testing.T) { + dest := cli.NewIntSlice() tis := testApplyInputSource{ - Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}), + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", Destination: dest}), FlagName: "test", MapValue: []interface{}{1, 2}, } c := runTest(t, tis) expect(t, c.IntSlice("test"), []int{1, 2}) + expect(t, dest.Value(), []int{1, 2}) + // reset dest + dest = cli.NewIntSlice() + tis = testApplyInputSource{ + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", Destination: dest}), + FlagName: "test", + MapValue: []interface{}{1, 2}, + } c = runRacyTest(t, tis) refute(t, c.IntSlice("test"), []int{1, 2}) + refute(t, dest.Value(), []int{1, 2}) } func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) { + dest := cli.NewIntSlice() tis := testApplyInputSource{ - Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}), + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", Destination: dest}), FlagName: "test", MapValue: []interface{}{1, 2}, ContextValueString: "3", } c := runTest(t, tis) expect(t, c.IntSlice("test"), []int{3}) + expect(t, dest.Value(), []int{3}) + // reset dest + dest = cli.NewIntSlice() + tis = testApplyInputSource{ + Flag: NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", Destination: dest}), + FlagName: "test", + MapValue: []interface{}{1, 2}, + ContextValueString: "3", + } c = runRacyTest(t, tis) refute(t, c.IntSlice("test"), []int{3}) + refute(t, dest.Value(), []int{3}) } func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { From a2c3729797c5277dcaaada09d8c363d629808f5e Mon Sep 17 00:00:00 2001 From: torwang Date: Tue, 20 Sep 2022 16:15:59 +0800 Subject: [PATCH 85/97] fix: Context.Set no such flag --- context.go | 8 +++++--- context_test.go | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index 312efb5..81416da 100644 --- a/context.go +++ b/context.go @@ -3,6 +3,7 @@ package cli import ( "context" "flag" + "fmt" "strings" ) @@ -46,10 +47,11 @@ func (cCtx *Context) NumFlags() int { // Set sets a context flag to a value. func (cCtx *Context) Set(name, value string) error { - if cCtx.flagSet.Lookup(name) == nil { - cCtx.onInvalidFlag(name) + if fs := cCtx.lookupFlagSet(name); fs != nil { + return fs.Set(name, value) } - return cCtx.flagSet.Set(name, value) + + return fmt.Errorf("no such flag -%s", name) } // IsSet determines if the flag was actually set diff --git a/context_test.go b/context_test.go index 6601155..246590d 100644 --- a/context_test.go +++ b/context_test.go @@ -643,3 +643,19 @@ func TestCheckRequiredFlags(t *testing.T) { }) } } + +func TestContext_ParentContext_Set(t *testing.T) { + parentSet := flag.NewFlagSet("parent", flag.ContinueOnError) + parentSet.String("Name", "", "") + + context := NewContext( + nil, + flag.NewFlagSet("child", flag.ContinueOnError), + NewContext(nil, parentSet, nil), + ) + + err := context.Set("Name", "aaa") + if err != nil { + t.Errorf("expect nil. set parent context flag return err: %s", err) + } +} From e2e14ec6ef1638253e6aaf076374ffb06aef2662 Mon Sep 17 00:00:00 2001 From: Wendell Sun Date: Tue, 15 Feb 2022 23:49:41 +0800 Subject: [PATCH 86/97] feat: flag action --- app.go | 26 +++++++++++++++++ app_test.go | 67 +++++++++++++++++++++++++++++++++++++++++++ command.go | 4 +++ flag.go | 2 ++ flag_bool.go | 9 ++++++ flag_duration.go | 9 ++++++ flag_float64.go | 9 ++++++ flag_float64_slice.go | 9 ++++++ flag_generic.go | 9 ++++++ flag_int.go | 9 ++++++ flag_int64.go | 9 ++++++ flag_int64_slice.go | 9 ++++++ flag_int_slice.go | 9 ++++++ flag_path.go | 9 ++++++ flag_string.go | 9 ++++++ flag_string_slice.go | 9 ++++++ flag_timestamp.go | 9 ++++++ flag_uint.go | 9 ++++++ flag_uint64.go | 9 ++++++ 19 files changed, 234 insertions(+) diff --git a/app.go b/app.go index d07839f..7be6f76 100644 --- a/app.go +++ b/app.go @@ -340,6 +340,10 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { } } + if err = runFlagActions(cCtx, a.Flags); err != nil { + return err + } + var c *Command args := cCtx.Args() if args.Present() { @@ -521,6 +525,10 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } } + if err = runFlagActions(cCtx, a.Flags); err != nil { + return err + } + args := cCtx.Args() if args.Present() { name := args.First() @@ -644,6 +652,24 @@ func (a *App) argsWithDefaultCommand(oldArgs Args) Args { return oldArgs } +func runFlagActions(c *Context, fs []Flag) error { + for _, f := range fs { + isSet := false + for _, name := range f.Names() { + if c.IsSet(name) { + isSet = true + break + } + } + if isSet { + if err := f.RunAction(c); err != nil { + return err + } + } + } + return nil +} + // 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 59e03b5..8ae01e8 100644 --- a/app_test.go +++ b/app_test.go @@ -2357,6 +2357,10 @@ func (c *customBoolFlag) Apply(set *flag.FlagSet) error { return nil } +func (c *customBoolFlag) RunAction(*Context) error { + return nil +} + func (c *customBoolFlag) IsSet() bool { return false } @@ -2596,3 +2600,66 @@ func TestSetupInitializesOnlyNilWriters(t *testing.T) { t.Errorf("expected a.Writer to be os.Stdout") } } + +func TestFlagAction(t *testing.T) { + r := []string{} + actionFunc := func(c *Context, s string) error { + r = append(r, s) + return nil + } + + app := &App{ + Name: "command", + Writer: io.Discard, + Flags: []Flag{&StringFlag{Name: "flag", Action: actionFunc}}, + Commands: []*Command{ + { + Name: "command1", + Flags: []Flag{&StringFlag{Name: "flag1", Aliases: []string{"f1"}, Action: actionFunc}}, + Subcommands: []*Command{ + { + Name: "command2", + Flags: []Flag{&StringFlag{Name: "flag2", Action: actionFunc}}, + }, + }, + }, + }, + } + + tests := []struct { + args []string + exp []string + }{ + { + args: []string{"command", "--flag=f"}, + exp: []string{"f"}, + }, + { + args: []string{"command", "command1", "-f1=f1", "command2"}, + exp: []string{"f1"}, + }, + { + args: []string{"command", "command1", "-f1=f1", "command2", "--flag2=f2"}, + exp: []string{"f1", "f2"}, + }, + { + args: []string{"command", "--flag=f", "command1", "-flag1=f1"}, + exp: []string{"f", "f1"}, + }, + { + args: []string{"command", "--flag=f", "command1", "-f1=f1"}, + exp: []string{"f", "f1"}, + }, + { + args: []string{"command", "--flag=f", "command1", "-f1=f1", "command2", "--flag2=f2"}, + exp: []string{"f", "f1", "f2"}, + }, + } + + for _, test := range tests { + r = []string{} + err := app.Run(test.args) + expect(t, err, nil) + expect(t, r, test.exp) + } +} diff --git a/command.go b/command.go index 13b79de..cd8ea91 100644 --- a/command.go +++ b/command.go @@ -165,6 +165,10 @@ func (c *Command) Run(ctx *Context) (err error) { } } + if err = runFlagActions(cCtx, c.Flags); err != nil { + return err + } + if c.Action == nil { c.Action = helpSubcommand.Action } diff --git a/flag.go b/flag.go index 1618b4d..aade1de 100644 --- a/flag.go +++ b/flag.go @@ -122,6 +122,8 @@ type Flag interface { // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. GetValue() string + + RunAction(*Context) error } // Countable is an interface to enable detection of flag values which support diff --git a/flag_bool.go b/flag_bool.go index e5fa700..55b719a 100644 --- a/flag_bool.go +++ b/flag_bool.go @@ -72,6 +72,15 @@ func (f *BoolFlag) GetDefaultText() string { return fmt.Sprintf("%v", f.Value) } +// RunAction executes flag action if set +func (f *BoolFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Bool(f.Name)) + } + + return nil +} + // Apply populates the flag given the flag set and environment func (f *BoolFlag) Apply(set *flag.FlagSet) error { if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { diff --git a/flag_duration.go b/flag_duration.go index 35b376d..b6adce3 100644 --- a/flag_duration.go +++ b/flag_duration.go @@ -42,6 +42,15 @@ func (f *DurationFlag) Get(ctx *Context) time.Duration { return ctx.Duration(f.Name) } +// RunAction executes flag action if set +func (f *DurationFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Duration(f.Name)) + } + + return nil +} + // Duration looks up the value of a local DurationFlag, returns // 0 if not found func (cCtx *Context) Duration(name string) time.Duration { diff --git a/flag_float64.go b/flag_float64.go index b7b8044..3e21a01 100644 --- a/flag_float64.go +++ b/flag_float64.go @@ -42,6 +42,15 @@ func (f *Float64Flag) Get(ctx *Context) float64 { return ctx.Float64(f.Name) } +// RunAction executes flag action if set +func (f *Float64Flag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Float64(f.Name)) + } + + return nil +} + // Float64 looks up the value of a local Float64Flag, returns // 0 if not found func (cCtx *Context) Float64(name string) float64 { diff --git a/flag_float64_slice.go b/flag_float64_slice.go index 833e65c..5bb2693 100644 --- a/flag_float64_slice.go +++ b/flag_float64_slice.go @@ -153,6 +153,15 @@ func (f *Float64SliceFlag) stringify() string { return stringifySliceFlag(f.Usage, f.Names(), defaultVals) } +// RunAction executes flag action if set +func (f *Float64SliceFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Float64Slice(f.Name)) + } + + return nil +} + // Float64Slice looks up the value of a local Float64SliceFlag, returns // nil if not found func (cCtx *Context) Float64Slice(name string) []float64 { diff --git a/flag_generic.go b/flag_generic.go index 4264015..9f06338 100644 --- a/flag_generic.go +++ b/flag_generic.go @@ -45,6 +45,15 @@ func (f *GenericFlag) Get(ctx *Context) interface{} { return ctx.Generic(f.Name) } +// RunAction executes flag action if set +func (f *GenericFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Generic(f.Name)) + } + + return nil +} + // Generic looks up the value of a local GenericFlag, returns // nil if not found func (cCtx *Context) Generic(name string) interface{} { diff --git a/flag_int.go b/flag_int.go index b269e23..bcaab7f 100644 --- a/flag_int.go +++ b/flag_int.go @@ -43,6 +43,15 @@ func (f *IntFlag) Get(ctx *Context) int { return ctx.Int(f.Name) } +// RunAction executes flag action if set +func (f *IntFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Int(f.Name)) + } + + return nil +} + // Int looks up the value of a local IntFlag, returns // 0 if not found func (cCtx *Context) Int(name string) int { diff --git a/flag_int64.go b/flag_int64.go index 35d95d9..d8e591d 100644 --- a/flag_int64.go +++ b/flag_int64.go @@ -42,6 +42,15 @@ func (f *Int64Flag) Get(ctx *Context) int64 { return ctx.Int64(f.Name) } +// RunAction executes flag action if set +func (f *Int64Flag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Int64(f.Name)) + } + + return nil +} + // Int64 looks up the value of a local Int64Flag, returns // 0 if not found func (cCtx *Context) Int64(name string) int64 { diff --git a/flag_int64_slice.go b/flag_int64_slice.go index d848b45..89fd445 100644 --- a/flag_int64_slice.go +++ b/flag_int64_slice.go @@ -151,6 +151,15 @@ func (f *Int64SliceFlag) stringify() string { return stringifySliceFlag(f.Usage, f.Names(), defaultVals) } +// RunAction executes flag action if set +func (f *Int64SliceFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Int64Slice(f.Name)) + } + + return nil +} + // Int64Slice looks up the value of a local Int64SliceFlag, returns // nil if not found func (cCtx *Context) Int64Slice(name string) []int64 { diff --git a/flag_int_slice.go b/flag_int_slice.go index 96fb7a3..f7a6b06 100644 --- a/flag_int_slice.go +++ b/flag_int_slice.go @@ -162,6 +162,15 @@ func (f *IntSliceFlag) stringify() string { return stringifySliceFlag(f.Usage, f.Names(), defaultVals) } +// RunAction executes flag action if set +func (f *IntSliceFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.IntSlice(f.Name)) + } + + return nil +} + // IntSlice looks up the value of a local IntSliceFlag, returns // nil if not found func (cCtx *Context) IntSlice(name string) []int { diff --git a/flag_path.go b/flag_path.go index e622c40..f8bb9c3 100644 --- a/flag_path.go +++ b/flag_path.go @@ -47,6 +47,15 @@ func (f *PathFlag) Get(ctx *Context) string { return ctx.Path(f.Name) } +// RunAction executes flag action if set +func (f *PathFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Path(f.Name)) + } + + return nil +} + // Path looks up the value of a local PathFlag, returns // "" if not found func (cCtx *Context) Path(name string) string { diff --git a/flag_string.go b/flag_string.go index 31cd89d..669f254 100644 --- a/flag_string.go +++ b/flag_string.go @@ -45,6 +45,15 @@ func (f *StringFlag) Get(ctx *Context) string { return ctx.String(f.Name) } +// RunAction executes flag action if set +func (f *StringFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.String(f.Name)) + } + + return nil +} + // String looks up the value of a local StringFlag, returns // "" if not found func (cCtx *Context) String(name string) string { diff --git a/flag_string_slice.go b/flag_string_slice.go index c5c9b78..2a24ec5 100644 --- a/flag_string_slice.go +++ b/flag_string_slice.go @@ -143,6 +143,15 @@ func (f *StringSliceFlag) stringify() string { return stringifySliceFlag(f.Usage, f.Names(), defaultVals) } +// RunAction executes flag action if set +func (f *StringSliceFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.StringSlice(f.Name)) + } + + return nil +} + // StringSlice looks up the value of a local StringSliceFlag, returns // nil if not found func (cCtx *Context) StringSlice(name string) []string { diff --git a/flag_timestamp.go b/flag_timestamp.go index 7b525b0..671be8b 100644 --- a/flag_timestamp.go +++ b/flag_timestamp.go @@ -120,6 +120,15 @@ func (f *TimestampFlag) Get(ctx *Context) *time.Time { return ctx.Timestamp(f.Name) } +// RunAction executes flag action if set +func (f *TimestampFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Timestamp(f.Name)) + } + + return nil +} + // Timestamp gets the timestamp from a flag name func (cCtx *Context) Timestamp(name string) *time.Time { if fs := cCtx.lookupFlagSet(name); fs != nil { diff --git a/flag_uint.go b/flag_uint.go index 417a9ef..e1b0450 100644 --- a/flag_uint.go +++ b/flag_uint.go @@ -42,6 +42,15 @@ func (f *UintFlag) Get(ctx *Context) uint { return ctx.Uint(f.Name) } +// RunAction executes flag action if set +func (f *UintFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Uint(f.Name)) + } + + return nil +} + // Uint looks up the value of a local UintFlag, returns // 0 if not found func (cCtx *Context) Uint(name string) uint { diff --git a/flag_uint64.go b/flag_uint64.go index 875635d..593b260 100644 --- a/flag_uint64.go +++ b/flag_uint64.go @@ -42,6 +42,15 @@ func (f *Uint64Flag) Get(ctx *Context) uint64 { return ctx.Uint64(f.Name) } +// RunAction executes flag action if set +func (f *Uint64Flag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Uint64(f.Name)) + } + + return nil +} + // Uint64 looks up the value of a local Uint64Flag, returns // 0 if not found func (cCtx *Context) Uint64(name string) uint64 { From f9ceca5dfae694fa2d79ede8b4cc58fea2c5dcc9 Mon Sep 17 00:00:00 2001 From: Wendell Sun Date: Sun, 1 May 2022 02:49:01 +0800 Subject: [PATCH 87/97] Add more tests about flag-level action --- app_test.go | 253 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 224 insertions(+), 29 deletions(-) diff --git a/app_test.go b/app_test.go index 8ae01e8..ec4e236 100644 --- a/app_test.go +++ b/app_test.go @@ -9,8 +9,10 @@ import ( "io/ioutil" "os" "reflect" + "strconv" "strings" "testing" + "time" ) var ( @@ -2601,65 +2603,258 @@ func TestSetupInitializesOnlyNilWriters(t *testing.T) { } } +type stringGeneric struct { + value string +} + +func (s *stringGeneric) Set(value string) error { + s.value = value + return nil +} + +func (s *stringGeneric) String() string { + return s.value +} + func TestFlagAction(t *testing.T) { - r := []string{} - actionFunc := func(c *Context, s string) error { - r = append(r, s) - return nil + stringFlag := &StringFlag{ + Name: "f_string", + Action: func(c *Context, v string) error { + c.App.Writer.Write([]byte(v + " ")) + return nil + }, } - app := &App{ - Name: "command", - Writer: io.Discard, - Flags: []Flag{&StringFlag{Name: "flag", Action: actionFunc}}, + Name: "app", Commands: []*Command{ { - Name: "command1", - Flags: []Flag{&StringFlag{Name: "flag1", Aliases: []string{"f1"}, Action: actionFunc}}, + Name: "c1", + Flags: []Flag{stringFlag}, + Action: func(ctx *Context) error { return nil }, Subcommands: []*Command{ { - Name: "command2", - Flags: []Flag{&StringFlag{Name: "flag2", Action: actionFunc}}, + Name: "sub1", + Action: func(ctx *Context) error { return nil }, + Flags: []Flag{stringFlag}, }, }, }, }, + Flags: []Flag{ + stringFlag, + &StringSliceFlag{ + Name: "f_string_slice", + Action: func(c *Context, v []string) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &BoolFlag{ + Name: "f_bool", + Action: func(c *Context, v bool) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%t ", v))) + return nil + }, + }, + &DurationFlag{ + Name: "f_duration", + Action: func(c *Context, v time.Duration) error { + c.App.Writer.Write([]byte(v.String() + " ")) + return nil + }, + }, + &Float64Flag{ + Name: "f_float64", + Action: func(c *Context, v float64) error { + c.App.Writer.Write([]byte(strconv.FormatFloat(v, 'f', -1, 64) + " ")) + return nil + }, + }, + &Float64SliceFlag{ + Name: "f_float64_slice", + Action: func(c *Context, v []float64) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &GenericFlag{ + Name: "f_generic", + Value: new(stringGeneric), + Action: func(c *Context, v interface{}) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &IntFlag{ + Name: "f_int", + Action: func(c *Context, v int) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &IntSliceFlag{ + Name: "f_int_slice", + Action: func(c *Context, v []int) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &Int64Flag{ + Name: "f_int64", + Action: func(c *Context, v int64) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &Int64SliceFlag{ + Name: "f_int64_slice", + Action: func(c *Context, v []int64) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &PathFlag{ + Name: "f_path", + Action: func(c *Context, v string) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &TimestampFlag{ + Name: "f_timestamp", + Layout: "2006-01-02 15:04:05", + Action: func(c *Context, v *time.Time) error { + c.App.Writer.Write([]byte(v.Format(time.RFC3339) + " ")) + return nil + }, + }, + &UintFlag{ + Name: "f_uint", + Action: func(c *Context, v uint) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + &Uint64Flag{ + Name: "f_uint64", + Action: func(c *Context, v uint64) error { + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) + return nil + }, + }, + }, + Action: func(ctx *Context) error { return nil }, } tests := []struct { + name string args []string - exp []string + exp string }{ { - args: []string{"command", "--flag=f"}, - exp: []string{"f"}, + name: "flag_empty", + args: []string{"app"}, + exp: "", + }, + { + name: "flag_string", + args: []string{"app", "--f_string=string"}, + exp: "string ", + }, + { + name: "flag_string_slice", + args: []string{"app", "--f_string_slice=s1,s2,s3"}, + exp: "[s1 s2 s3] ", + }, + { + name: "flag_bool", + args: []string{"app", "--f_bool"}, + exp: "true ", + }, + { + name: "flag_duration", + args: []string{"app", "--f_duration=1h30m20s"}, + exp: "1h30m20s ", }, { - args: []string{"command", "command1", "-f1=f1", "command2"}, - exp: []string{"f1"}, + name: "flag_float64", + args: []string{"app", "--f_float64=3.14159"}, + exp: "3.14159 ", }, { - args: []string{"command", "command1", "-f1=f1", "command2", "--flag2=f2"}, - exp: []string{"f1", "f2"}, + name: "flag_float64_slice", + args: []string{"app", "--f_float64_slice=1.1,2.2,3.3"}, + exp: "[1.1 2.2 3.3] ", }, { - args: []string{"command", "--flag=f", "command1", "-flag1=f1"}, - exp: []string{"f", "f1"}, + name: "flag_generic", + args: []string{"app", "--f_generic=1"}, + exp: "1 ", }, { - args: []string{"command", "--flag=f", "command1", "-f1=f1"}, - exp: []string{"f", "f1"}, + name: "flag_int", + args: []string{"app", "--f_int=1"}, + exp: "1 ", }, { - args: []string{"command", "--flag=f", "command1", "-f1=f1", "command2", "--flag2=f2"}, - exp: []string{"f", "f1", "f2"}, + name: "flag_int_slice", + args: []string{"app", "--f_int_slice=1,2,3"}, + exp: "[1 2 3] ", + }, + { + name: "flag_int64", + args: []string{"app", "--f_int64=1"}, + exp: "1 ", + }, + { + name: "flag_int64_slice", + args: []string{"app", "--f_int64_slice=1,2,3"}, + exp: "[1 2 3] ", + }, + { + name: "flag_path", + args: []string{"app", "--f_path=/root"}, + exp: "/root ", + }, + { + name: "flag_timestamp", + args: []string{"app", "--f_timestamp", "2022-05-01 02:26:20"}, + exp: "2022-05-01T02:26:20Z ", + }, + { + name: "flag_uint", + args: []string{"app", "--f_uint=1"}, + exp: "1 ", + }, + { + name: "flag_uint64", + args: []string{"app", "--f_uint64=1"}, + exp: "1 ", + }, + { + name: "command_flag", + args: []string{"app", "c1", "--f_string=c1"}, + exp: "c1 ", + }, + { + name: "subCommand_flag", + args: []string{"app", "c1", "sub1", "--f_string=sub1"}, + exp: "sub1 ", + }, + { + name: "mixture", + args: []string{"app", "--f_string=app", "--f_uint=1", "--f_int_slice=1,2,3", "--f_duration=1h30m20s", "c1", "--f_string=c1", "sub1", "--f_string=sub1"}, + exp: "app 1h30m20s [1 2 3] 1 c1 sub1 ", }, } for _, test := range tests { - r = []string{} - err := app.Run(test.args) - expect(t, err, nil) - expect(t, r, test.exp) + t.Run(test.name, func(t *testing.T) { + buf := new(bytes.Buffer) + app.Writer = buf + err := app.Run(test.args) + expect(t, err, nil) + expect(t, buf.String(), test.exp) + }) } } From d213683beec892938ab61dac6d05ac40e2d4ab00 Mon Sep 17 00:00:00 2001 From: Wendell Sun Date: Sun, 22 May 2022 23:07:10 +0800 Subject: [PATCH 88/97] Rebase main, update flag-spec.yaml to add Action field --- cmd/urfave-cli-genflags/go.sum | 3 --- flag-spec.yaml | 41 ++++++++++++++++++++++++++++++++++ zz_generated.flags.go | 34 ++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/cmd/urfave-cli-genflags/go.sum b/cmd/urfave-cli-genflags/go.sum index 9821127..e59916d 100644 --- a/cmd/urfave-cli-genflags/go.sum +++ b/cmd/urfave-cli-genflags/go.sum @@ -1,5 +1,3 @@ -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.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= @@ -10,7 +8,6 @@ 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/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.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/flag-spec.yaml b/flag-spec.yaml index 66745c6..ea75670 100644 --- a/flag-spec.yaml +++ b/flag-spec.yaml @@ -9,47 +9,77 @@ flag_types: - name: Count type: int pointer: true + - name: Action + type: "func(*Context, bool) error" float64: + struct_fields: + - name: Action + type: "func(*Context, float64) error" Float64Slice: value_pointer: true skip_interfaces: - fmt.Stringer + struct_fields: + - name: Action + type: "func(*Context, []float64) error" int: struct_fields: - name: Base type: int + - name: Action + type: "func(*Context, int) error" IntSlice: value_pointer: true skip_interfaces: - fmt.Stringer + struct_fields: + - name: Action + type: "func(*Context, []int) error" int64: struct_fields: - name: Base type: int + - name: Action + type: "func(*Context, int64) error" Int64Slice: value_pointer: true skip_interfaces: - fmt.Stringer + struct_fields: + - name: Action + type: "func(*Context, []int64) error" uint: struct_fields: - name: Base type: int + - name: Action + type: "func(*Context, uint) error" UintSlice: value_pointer: true skip_interfaces: - fmt.Stringer + struct_fields: + - name: Action + type: "func(*Context, []uint) error" uint64: struct_fields: - name: Base type: int + - name: Action + type: "func(*Context, uint64) error" Uint64Slice: value_pointer: true skip_interfaces: - fmt.Stringer + struct_fields: + - name: Action + type: "func(*Context, []uint64) error" string: struct_fields: - name: TakesFile type: bool + - name: Action + type: "func(*Context, string) error" StringSlice: value_pointer: true skip_interfaces: @@ -57,7 +87,12 @@ flag_types: struct_fields: - name: TakesFile type: bool + - name: Action + type: "func(*Context, []string) error" time.Duration: + struct_fields: + - name: Action + type: "func(*Context, time.Duration) error" Timestamp: value_pointer: true struct_fields: @@ -65,12 +100,18 @@ flag_types: type: string - name: Timezone type: "*time.Location" + - name: Action + type: "func(*Context, *time.Time) error" Generic: no_destination_pointer: true struct_fields: - name: TakesFile type: bool + - name: Action + type: "func(*Context, interface{}) error" Path: struct_fields: - name: TakesFile type: bool + - name: Action + type: "func(*Context, Path) error" diff --git a/zz_generated.flags.go b/zz_generated.flags.go index 2c5bea0..68934c7 100644 --- a/zz_generated.flags.go +++ b/zz_generated.flags.go @@ -22,6 +22,8 @@ type Float64SliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []float64) error } // IsSet returns whether or not the flag has been set through env or file @@ -92,6 +94,8 @@ type GenericFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, interface{}) error } // String returns a readable representation of this value (for usage defaults) @@ -165,6 +169,8 @@ type Int64SliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []int64) error } // IsSet returns whether or not the flag has been set through env or file @@ -233,6 +239,8 @@ type IntSliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []int) error } // IsSet returns whether or not the flag has been set through env or file @@ -303,6 +311,8 @@ type PathFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, Path) error } // String returns a readable representation of this value (for usage defaults) @@ -370,6 +380,8 @@ type StringSliceFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, []string) error } // IsSet returns whether or not the flag has been set through env or file @@ -442,6 +454,8 @@ type TimestampFlag struct { Layout string Timezone *time.Location + + Action func(*Context, *time.Time) error } // String returns a readable representation of this value (for usage defaults) @@ -515,6 +529,8 @@ type Uint64SliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []uint64) error } // IsSet returns whether or not the flag has been set through env or file @@ -555,6 +571,8 @@ type UintSliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []uint) error } // IsSet returns whether or not the flag has been set through env or file @@ -597,6 +615,8 @@ type BoolFlag struct { EnvVars []string Count *int + + Action func(*Context, bool) error } // String returns a readable representation of this value (for usage defaults) @@ -662,6 +682,8 @@ type Float64Flag struct { Aliases []string EnvVars []string + + Action func(*Context, float64) error } // String returns a readable representation of this value (for usage defaults) @@ -737,6 +759,8 @@ type IntFlag struct { EnvVars []string Base int + + Action func(*Context, int) error } // String returns a readable representation of this value (for usage defaults) @@ -812,6 +836,8 @@ type Int64Flag struct { EnvVars []string Base int + + Action func(*Context, int64) error } // String returns a readable representation of this value (for usage defaults) @@ -887,6 +913,8 @@ type StringFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, string) error } // String returns a readable representation of this value (for usage defaults) @@ -952,6 +980,8 @@ type DurationFlag struct { Aliases []string EnvVars []string + + Action func(*Context, time.Duration) error } // String returns a readable representation of this value (for usage defaults) @@ -1027,6 +1057,8 @@ type UintFlag struct { EnvVars []string Base int + + Action func(*Context, uint) error } // String returns a readable representation of this value (for usage defaults) @@ -1102,6 +1134,8 @@ type Uint64Flag struct { EnvVars []string Base int + + Action func(*Context, uint64) error } // String returns a readable representation of this value (for usage defaults) From 1e30f50959e08bc8d7a249ec702e476bd25c9a03 Mon Sep 17 00:00:00 2001 From: Wendell Sun Date: Fri, 16 Sep 2022 00:42:24 +0900 Subject: [PATCH 89/97] make v2approve --- testdata/godoc-v2.x.txt | 52 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt index 1ba48cc..562b7ff 100644 --- a/testdata/godoc-v2.x.txt +++ b/testdata/godoc-v2.x.txt @@ -250,6 +250,13 @@ TYPES type ActionFunc func(*Context) error ActionFunc is the action to execute when no subcommands are specified +type ActionableFlag interface { + Flag + RunAction(*Context) error +} + ActionableFlag is an interface that wraps Flag interface and RunAction + operation. + 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 @@ -487,6 +494,9 @@ func (f *BoolFlag) IsVisible() bool func (f *BoolFlag) Names() []string Names returns the names of the flag +func (f *BoolFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *BoolFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -777,6 +787,9 @@ func (f *DurationFlag) IsVisible() bool func (f *DurationFlag) Names() []string Names returns the names of the flag +func (f *DurationFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *DurationFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -950,6 +963,9 @@ func (f *Float64Flag) IsVisible() bool func (f *Float64Flag) Names() []string Names returns the names of the flag +func (f *Float64Flag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *Float64Flag) String() string String returns a readable representation of this value (for usage defaults) @@ -1035,6 +1051,9 @@ func (f *Float64SliceFlag) IsVisible() bool func (f *Float64SliceFlag) Names() []string Names returns the names of the flag +func (f *Float64SliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *Float64SliceFlag) SetDestination(slice []float64) func (f *Float64SliceFlag) SetValue(slice []float64) @@ -1108,6 +1127,9 @@ func (f *GenericFlag) IsVisible() bool func (f *GenericFlag) Names() []string Names returns the names of the flag +func (f *GenericFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *GenericFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1168,6 +1190,9 @@ func (f *Int64Flag) IsVisible() bool func (f *Int64Flag) Names() []string Names returns the names of the flag +func (f *Int64Flag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *Int64Flag) String() string String returns a readable representation of this value (for usage defaults) @@ -1253,6 +1278,9 @@ func (f *Int64SliceFlag) IsVisible() bool func (f *Int64SliceFlag) Names() []string Names returns the names of the flag +func (f *Int64SliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *Int64SliceFlag) SetDestination(slice []int64) func (f *Int64SliceFlag) SetValue(slice []int64) @@ -1317,6 +1345,9 @@ func (f *IntFlag) IsVisible() bool func (f *IntFlag) Names() []string Names returns the names of the flag +func (f *IntFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *IntFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1406,6 +1437,9 @@ func (f *IntSliceFlag) IsVisible() bool func (f *IntSliceFlag) Names() []string Names returns the names of the flag +func (f *IntSliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *IntSliceFlag) SetDestination(slice []int) func (f *IntSliceFlag) SetValue(slice []int) @@ -1502,6 +1536,9 @@ func (f *PathFlag) IsVisible() bool func (f *PathFlag) Names() []string Names returns the names of the flag +func (f *PathFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *PathFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1639,6 +1676,9 @@ func (f *StringFlag) IsVisible() bool func (f *StringFlag) Names() []string Names returns the names of the flag +func (f *StringFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *StringFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1726,6 +1766,9 @@ func (f *StringSliceFlag) IsVisible() bool func (f *StringSliceFlag) Names() []string Names returns the names of the flag +func (f *StringSliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *StringSliceFlag) SetDestination(slice []string) func (f *StringSliceFlag) SetValue(slice []string) @@ -1827,6 +1870,9 @@ func (f *TimestampFlag) IsVisible() bool func (f *TimestampFlag) Names() []string Names returns the names of the flag +func (f *TimestampFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *TimestampFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1887,6 +1933,9 @@ func (f *Uint64Flag) IsVisible() bool func (f *Uint64Flag) Names() []string Names returns the names of the flag +func (f *Uint64Flag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *Uint64Flag) String() string String returns a readable representation of this value (for usage defaults) @@ -1947,6 +1996,9 @@ func (f *UintFlag) IsVisible() bool func (f *UintFlag) Names() []string Names returns the names of the flag +func (f *UintFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *UintFlag) String() string String returns a readable representation of this value (for usage defaults) From 126297af13c1f846e7a2cc583c75167ae6950640 Mon Sep 17 00:00:00 2001 From: Wendell Sun Date: Sat, 17 Sep 2022 16:05:26 +0900 Subject: [PATCH 90/97] Add more test cases --- app_test.go | 147 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 140 insertions(+), 7 deletions(-) diff --git a/app_test.go b/app_test.go index ec4e236..45876a6 100644 --- a/app_test.go +++ b/app_test.go @@ -2620,6 +2620,9 @@ func TestFlagAction(t *testing.T) { stringFlag := &StringFlag{ Name: "f_string", Action: func(c *Context, v string) error { + if v == "" { + return fmt.Errorf("empty string") + } c.App.Writer.Write([]byte(v + " ")) return nil }, @@ -2642,9 +2645,15 @@ func TestFlagAction(t *testing.T) { }, Flags: []Flag{ stringFlag, + &StringFlag{ + Name: "f_no_action", + }, &StringSliceFlag{ Name: "f_string_slice", Action: func(c *Context, v []string) error { + if v[0] == "err" { + return fmt.Errorf("error string slice") + } c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) return nil }, @@ -2652,6 +2661,9 @@ func TestFlagAction(t *testing.T) { &BoolFlag{ Name: "f_bool", Action: func(c *Context, v bool) error { + if !v { + return fmt.Errorf("value is false") + } c.App.Writer.Write([]byte(fmt.Sprintf("%t ", v))) return nil }, @@ -2659,6 +2671,9 @@ func TestFlagAction(t *testing.T) { &DurationFlag{ Name: "f_duration", Action: func(c *Context, v time.Duration) error { + if v == 0 { + return fmt.Errorf("empty duration") + } c.App.Writer.Write([]byte(v.String() + " ")) return nil }, @@ -2666,6 +2681,9 @@ func TestFlagAction(t *testing.T) { &Float64Flag{ Name: "f_float64", Action: func(c *Context, v float64) error { + if v < 0 { + return fmt.Errorf("negative float64") + } c.App.Writer.Write([]byte(strconv.FormatFloat(v, 'f', -1, 64) + " ")) return nil }, @@ -2673,6 +2691,9 @@ func TestFlagAction(t *testing.T) { &Float64SliceFlag{ Name: "f_float64_slice", Action: func(c *Context, v []float64) error { + if len(v) > 0 && v[0] < 0 { + return fmt.Errorf("invalid float64 slice") + } c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) return nil }, @@ -2681,6 +2702,14 @@ func TestFlagAction(t *testing.T) { Name: "f_generic", Value: new(stringGeneric), Action: func(c *Context, v interface{}) error { + fmt.Printf("%T %v\n", v, v) + switch vv := v.(type) { + case *stringGeneric: + if vv.value == "" { + return fmt.Errorf("generic value not set") + } + } + c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) return nil }, @@ -2688,6 +2717,9 @@ func TestFlagAction(t *testing.T) { &IntFlag{ Name: "f_int", Action: func(c *Context, v int) error { + if v < 0 { + return fmt.Errorf("negative int") + } c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) return nil }, @@ -2695,6 +2727,9 @@ func TestFlagAction(t *testing.T) { &IntSliceFlag{ Name: "f_int_slice", Action: func(c *Context, v []int) error { + if len(v) > 0 && v[0] < 0 { + return fmt.Errorf("invalid int slice") + } c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) return nil }, @@ -2702,6 +2737,9 @@ func TestFlagAction(t *testing.T) { &Int64Flag{ Name: "f_int64", Action: func(c *Context, v int64) error { + if v < 0 { + return fmt.Errorf("negative int64") + } c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) return nil }, @@ -2709,6 +2747,9 @@ func TestFlagAction(t *testing.T) { &Int64SliceFlag{ Name: "f_int64_slice", Action: func(c *Context, v []int64) error { + if len(v) > 0 && v[0] < 0 { + return fmt.Errorf("invalid int64 slice") + } c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) return nil }, @@ -2716,6 +2757,9 @@ func TestFlagAction(t *testing.T) { &PathFlag{ Name: "f_path", Action: func(c *Context, v string) error { + if v == "" { + return fmt.Errorf("empty path") + } c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) return nil }, @@ -2724,6 +2768,9 @@ func TestFlagAction(t *testing.T) { Name: "f_timestamp", Layout: "2006-01-02 15:04:05", Action: func(c *Context, v *time.Time) error { + if v.IsZero() { + return fmt.Errorf("zero timestamp") + } c.App.Writer.Write([]byte(v.Format(time.RFC3339) + " ")) return nil }, @@ -2731,6 +2778,9 @@ func TestFlagAction(t *testing.T) { &UintFlag{ Name: "f_uint", Action: func(c *Context, v uint) error { + if v == 0 { + return fmt.Errorf("zero uint") + } c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) return nil }, @@ -2738,6 +2788,9 @@ func TestFlagAction(t *testing.T) { &Uint64Flag{ Name: "f_uint64", Action: func(c *Context, v uint64) error { + if v == 0 { + return fmt.Errorf("zero uint64") + } c.App.Writer.Write([]byte(fmt.Sprintf("%v ", v))) return nil }, @@ -2749,88 +2802,164 @@ func TestFlagAction(t *testing.T) { tests := []struct { name string args []string + err error exp string }{ - { - name: "flag_empty", - args: []string{"app"}, - exp: "", - }, { name: "flag_string", args: []string{"app", "--f_string=string"}, exp: "string ", }, + { + name: "flag_string_error", + args: []string{"app", "--f_string="}, + err: fmt.Errorf("empty string"), + }, { name: "flag_string_slice", args: []string{"app", "--f_string_slice=s1,s2,s3"}, exp: "[s1 s2 s3] ", }, + { + name: "flag_string_slice_error", + args: []string{"app", "--f_string_slice=err"}, + err: fmt.Errorf("error string slice"), + }, { name: "flag_bool", args: []string{"app", "--f_bool"}, exp: "true ", }, + { + name: "flag_bool_error", + args: []string{"app", "--f_bool=false"}, + err: fmt.Errorf("value is false"), + }, { name: "flag_duration", args: []string{"app", "--f_duration=1h30m20s"}, exp: "1h30m20s ", }, + { + name: "flag_duration_error", + args: []string{"app", "--f_duration=0"}, + err: fmt.Errorf("empty duration"), + }, { name: "flag_float64", args: []string{"app", "--f_float64=3.14159"}, exp: "3.14159 ", }, + { + name: "flag_float64_error", + args: []string{"app", "--f_float64=-1"}, + err: fmt.Errorf("negative float64"), + }, { name: "flag_float64_slice", args: []string{"app", "--f_float64_slice=1.1,2.2,3.3"}, exp: "[1.1 2.2 3.3] ", }, + { + name: "flag_float64_slice_error", + args: []string{"app", "--f_float64_slice=-1"}, + err: fmt.Errorf("invalid float64 slice"), + }, { name: "flag_generic", args: []string{"app", "--f_generic=1"}, exp: "1 ", }, + { + name: "flag_generic_error", + args: []string{"app", "--f_generic="}, + err: fmt.Errorf("generic value not set"), + }, { name: "flag_int", args: []string{"app", "--f_int=1"}, exp: "1 ", }, + { + name: "flag_int_error", + args: []string{"app", "--f_int=-1"}, + err: fmt.Errorf("negative int"), + }, { name: "flag_int_slice", args: []string{"app", "--f_int_slice=1,2,3"}, exp: "[1 2 3] ", }, + { + name: "flag_int_slice_error", + args: []string{"app", "--f_int_slice=-1"}, + err: fmt.Errorf("invalid int slice"), + }, { name: "flag_int64", args: []string{"app", "--f_int64=1"}, exp: "1 ", }, + { + name: "flag_int64_error", + args: []string{"app", "--f_int64=-1"}, + err: fmt.Errorf("negative int64"), + }, { name: "flag_int64_slice", args: []string{"app", "--f_int64_slice=1,2,3"}, exp: "[1 2 3] ", }, + { + name: "flag_int64_slice", + args: []string{"app", "--f_int64_slice=-1"}, + err: fmt.Errorf("invalid int64 slice"), + }, { name: "flag_path", args: []string{"app", "--f_path=/root"}, exp: "/root ", }, + { + name: "flag_path_error", + args: []string{"app", "--f_path="}, + err: fmt.Errorf("empty path"), + }, { name: "flag_timestamp", args: []string{"app", "--f_timestamp", "2022-05-01 02:26:20"}, exp: "2022-05-01T02:26:20Z ", }, + { + name: "flag_timestamp_error", + args: []string{"app", "--f_timestamp", "0001-01-01 00:00:00"}, + err: fmt.Errorf("zero timestamp"), + }, { name: "flag_uint", args: []string{"app", "--f_uint=1"}, exp: "1 ", }, + { + name: "flag_uint_error", + args: []string{"app", "--f_uint=0"}, + err: fmt.Errorf("zero uint"), + }, { name: "flag_uint64", args: []string{"app", "--f_uint64=1"}, exp: "1 ", }, + { + name: "flag_uint64_error", + args: []string{"app", "--f_uint64=0"}, + err: fmt.Errorf("zero uint64"), + }, + { + name: "flag_no_action", + args: []string{"app", "--f_no_action="}, + exp: "", + }, { name: "command_flag", args: []string{"app", "c1", "--f_string=c1"}, @@ -2853,8 +2982,12 @@ func TestFlagAction(t *testing.T) { buf := new(bytes.Buffer) app.Writer = buf err := app.Run(test.args) - expect(t, err, nil) - expect(t, buf.String(), test.exp) + if test.err != nil { + expect(t, err, test.err) + } else { + expect(t, err, nil) + expect(t, buf.String(), test.exp) + } }) } } From 4c637d8ac73e3ff113dddf4fbebebb0aaa99062d Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sun, 21 Aug 2022 14:21:23 -0400 Subject: [PATCH 91/97] Fix:(issue_557) Change app help name --- app.go | 1 + 1 file changed, 1 insertion(+) diff --git a/app.go b/app.go index 7be6f76..689ee04 100644 --- a/app.go +++ b/app.go @@ -133,6 +133,7 @@ func compileTime() time.Time { func NewApp() *App { return &App{ Name: filepath.Base(os.Args[0]), + HelpName: "", // setup will fill this later Usage: "A new cli application", UsageText: "", BashComplete: DefaultAppComplete, From 57ff098ca7523826b0226d4bf46650197e8ac42a Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Tue, 20 Sep 2022 21:18:03 -0400 Subject: [PATCH 92/97] Fix:(issue_557) Make help output consistent between different invocations --- app_test.go | 5 +++- command.go | 50 +++++++++++++++++++++++++++++++++++++--- help.go | 64 ++++++++++++++++++++++++++++++++++++++++------------ help_test.go | 12 ++++++---- template.go | 9 +++----- 5 files changed, 111 insertions(+), 29 deletions(-) diff --git a/app_test.go b/app_test.go index 45876a6..8247769 100644 --- a/app_test.go +++ b/app_test.go @@ -174,13 +174,16 @@ func ExampleApp_Run_commandHelp() { _ = app.Run(os.Args) // Output: // NAME: - // greet describeit - use it to see a description + // greet describeit [command options] [arguments...] // // USAGE: // greet describeit [arguments...] // // DESCRIPTION: // This is how we describe describeit the function + // + // OPTIONS: + // --help, -h show help (default: false) } func ExampleApp_Run_noAction() { diff --git a/command.go b/command.go index cd8ea91..ea03a55 100644 --- a/command.go +++ b/command.go @@ -62,6 +62,9 @@ type Command struct { // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. CustomHelpTemplate string + + // categories contains the categorized commands and is populated on app startup + categories CommandCategories } type Commands []*Command @@ -170,7 +173,7 @@ func (c *Command) Run(ctx *Context) (err error) { } if c.Action == nil { - c.Action = helpSubcommand.Action + c.Action = helpCommand.Action } cCtx.Command = c @@ -284,7 +287,7 @@ func (c *Command) startApp(ctx *Context) error { if c.Action != nil { app.Action = c.Action } else { - app.Action = helpSubcommand.Action + app.Action = helpCommand.Action } app.OnUsageError = c.OnUsageError @@ -298,7 +301,12 @@ 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 { - return []VisibleFlagCategory{} + c.flagCategories = newFlagCategories() + for _, fl := range c.Flags { + if cf, ok := fl.(CategorizableFlag); ok { + c.flagCategories.AddFlag(cf.GetCategory(), cf) + } + } } return c.flagCategories.VisibleCategories() } @@ -308,6 +316,42 @@ func (c *Command) VisibleFlags() []Flag { return visibleFlags(c.Flags) } +// VisibleCategories returns a slice of categories and commands that are +// Hidden=false +func (c *Command) VisibleCategories() []CommandCategory { + ret := []CommandCategory{} + if c.categories == nil { + c.categories = newCommandCategories() + for _, command := range c.Subcommands { + c.categories.AddCommand(command.Category, command) + } + sort.Sort(c.categories.(*commandCategories)) + } + for _, category := range c.categories.Categories() { + if visible := func() CommandCategory { + if len(category.VisibleCommands()) > 0 { + return category + } + return nil + }(); visible != nil { + ret = append(ret, visible) + } + } + + return ret +} + +// VisibleCommands returns a slice of the Commands with Hidden=false +func (c *Command) VisibleCommands() []*Command { + var ret []*Command + for _, command := range c.Subcommands { + if !command.Hidden { + ret = append(ret, command) + } + } + return ret +} + func (c *Command) appendFlag(fl Flag) { if !hasFlag(c.Flags, fl) { c.Flags = append(c.Flags, fl) diff --git a/help.go b/help.go index d6caea4..ba5f803 100644 --- a/help.go +++ b/help.go @@ -15,33 +15,59 @@ const ( helpAlias = "h" ) -var helpCommand = &Command{ +// this instance is to avoid recursion in the ShowCommandHelp which can +// add a help command again +var helpCommandDontUse = &Command{ Name: helpName, Aliases: []string{helpAlias}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", - Action: func(cCtx *Context) error { - args := cCtx.Args() - if args.Present() { - return ShowCommandHelp(cCtx, args.First()) - } - - _ = ShowAppHelp(cCtx) - return nil - }, } -var helpSubcommand = &Command{ +var helpCommand = &Command{ Name: helpName, Aliases: []string{helpAlias}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", Action: func(cCtx *Context) error { args := cCtx.Args() - if args.Present() { - return ShowCommandHelp(cCtx, args.First()) + argsPresent := args.First() != "" + firstArg := args.First() + + // This action can be triggered by a "default" action of a command + // or via cmd.Run when cmd == helpCmd. So we have following possibilities + // + // 1 $ app + // 2 $ app help + // 3 $ app foo + // 4 $ app help foo + // 5 $ app foo help + + // Case 4. when executing a help command set the context to parent + // to allow resolution of subsequent args. This will transform + // $ app help foo + // to + // $ app foo + // which will then be handled as case 3 + if cCtx.Command.Name == helpName || cCtx.Command.Name == helpAlias { + cCtx = cCtx.parentContext } + // Case 4. $ app hello foo + // foo is the command for which help needs to be shown + if argsPresent { + return ShowCommandHelp(cCtx, firstArg) + } + + // Case 1 & 2 + // Special case when running help on main app itself as opposed to indivdual + // commands/subcommands + if cCtx.parentContext.App == nil { + _ = ShowAppHelp(cCtx) + return nil + } + + // Case 3, 5 return ShowSubcommandHelp(cCtx) }, } @@ -212,9 +238,19 @@ func ShowCommandHelp(ctx *Context, command string) error { for _, c := range ctx.App.Commands { if c.HasName(command) { + if !ctx.App.HideHelpCommand && !c.HasName(helpName) && len(c.Subcommands) != 0 { + c.Subcommands = append(c.Subcommands, helpCommandDontUse) + } + if !ctx.App.HideHelp && HelpFlag != nil { + c.appendFlag(HelpFlag) + } templ := c.CustomHelpTemplate if templ == "" { - templ = CommandHelpTemplate + if len(c.Subcommands) == 0 { + templ = CommandHelpTemplate + } else { + templ = SubcommandHelpTemplate + } } HelpPrinter(ctx.App.Writer, templ, c) diff --git a/help_test.go b/help_test.go index ade6f3d..5a50586 100644 --- a/help_test.go +++ b/help_test.go @@ -186,7 +186,7 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) { c := NewContext(app, set, nil) - err := helpSubcommand.Action(c) + err := helpCommand.Action(c) if err == nil { t.Fatalf("expected error from helpCommand.Action(), but got nil") @@ -248,7 +248,7 @@ func TestShowCommandHelp_HelpPrinter(t *testing.T) { fmt.Fprint(w, "yo") }, command: "", - wantTemplate: SubcommandHelpTemplate, + wantTemplate: AppHelpTemplate, wantOutput: "yo", }, { @@ -333,7 +333,7 @@ func TestShowCommandHelp_HelpPrinterCustom(t *testing.T) { fmt.Fprint(w, "yo") }, command: "", - wantTemplate: SubcommandHelpTemplate, + wantTemplate: AppHelpTemplate, wantOutput: "yo", }, { @@ -1357,10 +1357,13 @@ DESCRIPTION: and a description long enough to wrap in this test case + +OPTIONS: + --help, -h show help (default: false) ` if output.String() != expected { - t.Errorf("Unexpected wrapping, got:\n%s\nexpected: %s", + t.Errorf("Unexpected wrapping, got:\n%s\nexpected:\n%s", output.String(), expected) } } @@ -1426,7 +1429,6 @@ USAGE: OPTIONS: --help, -h show help (default: false) - ` if output.String() != expected { diff --git a/template.go b/template.go index 7ed2370..9e13604 100644 --- a/template.go +++ b/template.go @@ -54,12 +54,10 @@ DESCRIPTION: OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} - {{end}}{{range .Flags}}{{.}} - {{end}}{{end}}{{else}}{{if .VisibleFlags}} + {{end}}{{range .Flags}}{{.}}{{end}}{{end}}{{else}}{{if .VisibleFlags}} OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}}{{end}} + {{range .VisibleFlags}}{{.}}{{end}}{{end}}{{end}} ` // SubcommandHelpTemplate is the text template for the subcommand help topic. @@ -80,8 +78,7 @@ COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}} + {{range .VisibleFlags}}{{.}}{{end}}{{end}} ` var MarkdownDocTemplate = `{{if gt .SectionNum 0}}% {{ .App.Name }} {{ .SectionNum }} From c2ecb4469f400da2ca97b935358cc804b8a1a932 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Wed, 21 Sep 2022 18:18:36 -0400 Subject: [PATCH 93/97] Remove un-needed func --- command.go | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/command.go b/command.go index ea03a55..ad83ca1 100644 --- a/command.go +++ b/command.go @@ -316,31 +316,6 @@ func (c *Command) VisibleFlags() []Flag { return visibleFlags(c.Flags) } -// VisibleCategories returns a slice of categories and commands that are -// Hidden=false -func (c *Command) VisibleCategories() []CommandCategory { - ret := []CommandCategory{} - if c.categories == nil { - c.categories = newCommandCategories() - for _, command := range c.Subcommands { - c.categories.AddCommand(command.Category, command) - } - sort.Sort(c.categories.(*commandCategories)) - } - for _, category := range c.categories.Categories() { - if visible := func() CommandCategory { - if len(category.VisibleCommands()) > 0 { - return category - } - return nil - }(); visible != nil { - ret = append(ret, visible) - } - } - - return ret -} - // VisibleCommands returns a slice of the Commands with Hidden=false func (c *Command) VisibleCommands() []*Command { var ret []*Command From 8ef92d2a2407eb8051a7d3de4da3bc5d022a77be Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Wed, 21 Sep 2022 18:24:03 -0400 Subject: [PATCH 94/97] Remove un-needed func --- app.go | 1 - command.go | 14 -------------- 2 files changed, 15 deletions(-) diff --git a/app.go b/app.go index 689ee04..7be6f76 100644 --- a/app.go +++ b/app.go @@ -133,7 +133,6 @@ func compileTime() time.Time { func NewApp() *App { return &App{ Name: filepath.Base(os.Args[0]), - HelpName: "", // setup will fill this later Usage: "A new cli application", UsageText: "", BashComplete: DefaultAppComplete, diff --git a/command.go b/command.go index ad83ca1..d24b61e 100644 --- a/command.go +++ b/command.go @@ -62,9 +62,6 @@ type Command struct { // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. CustomHelpTemplate string - - // categories contains the categorized commands and is populated on app startup - categories CommandCategories } type Commands []*Command @@ -316,17 +313,6 @@ func (c *Command) VisibleFlags() []Flag { return visibleFlags(c.Flags) } -// VisibleCommands returns a slice of the Commands with Hidden=false -func (c *Command) VisibleCommands() []*Command { - var ret []*Command - for _, command := range c.Subcommands { - if !command.Hidden { - ret = append(ret, command) - } - } - return ret -} - func (c *Command) appendFlag(fl Flag) { if !hasFlag(c.Flags, fl) { c.Flags = append(c.Flags, fl) From 5db9db6d380c1c51db7f52ce7c4db3c58fea700b Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Thu, 29 Sep 2022 11:24:42 -0400 Subject: [PATCH 95/97] Remove nonexistent phony targets so that running `make` or `make all` only runs targets that exist. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 797d093..f0d4190 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ GO_RUN_BUILD := go run internal/build/build.go .PHONY: all -all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun yamlfmt v2diff +all: generate vet test check-binary-size gfmrun yamlfmt 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 From f8faf77e4309e9abb86bd1b3512b18dba73db673 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 3 Oct 2022 10:06:01 -0400 Subject: [PATCH 96/97] Post-porting fixes for v3 --- app_test.go | 4 +- command.go | 4 +- flag-spec.yaml | 1 - flag_uint64_slice.go | 9 + flag_uint_slice.go | 9 + godoc-current.txt | 338 ++++++++++++++++++++++++-- sliceflag.go | 23 +- testdata/godoc-v2.x.txt | 476 +++++++++++++++++++++++++++---------- zz_generated.flags_test.go | 24 -- 9 files changed, 691 insertions(+), 197 deletions(-) diff --git a/app_test.go b/app_test.go index 8247769..1428c39 100644 --- a/app_test.go +++ b/app_test.go @@ -174,10 +174,10 @@ func ExampleApp_Run_commandHelp() { _ = app.Run(os.Args) // Output: // NAME: - // greet describeit [command options] [arguments...] + // greet describeit - use it to see a description // // USAGE: - // greet describeit [arguments...] + // greet describeit [command options] [arguments...] // // DESCRIPTION: // This is how we describe describeit the function diff --git a/command.go b/command.go index d24b61e..decfb73 100644 --- a/command.go +++ b/command.go @@ -300,9 +300,7 @@ func (c *Command) VisibleFlagCategories() []VisibleFlagCategory { if c.flagCategories == nil { c.flagCategories = newFlagCategories() for _, fl := range c.Flags { - if cf, ok := fl.(CategorizableFlag); ok { - c.flagCategories.AddFlag(cf.GetCategory(), cf) - } + c.flagCategories.AddFlag(fl.GetCategory(), fl) } } return c.flagCategories.VisibleCategories() diff --git a/flag-spec.yaml b/flag-spec.yaml index ea75670..a38b933 100644 --- a/flag-spec.yaml +++ b/flag-spec.yaml @@ -1,7 +1,6 @@ # NOTE: this file is used by the tool defined in # ./cmd/urfave-cli-genflags/main.go which uses the # `Spec` type that maps to this file structure. - flag_types: bool: no_default_text: true diff --git a/flag_uint64_slice.go b/flag_uint64_slice.go index e60c3ea..662a03e 100644 --- a/flag_uint64_slice.go +++ b/flag_uint64_slice.go @@ -183,6 +183,15 @@ func (f *Uint64SliceFlag) stringify() string { return stringifySliceFlag(f.Usage, f.Names(), defaultVals) } +// RunAction executes flag action if set +func (f *Uint64SliceFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.Uint64Slice(f.Name)) + } + + return nil +} + // Uint64Slice looks up the value of a local Uint64SliceFlag, returns // nil if not found func (cCtx *Context) Uint64Slice(name string) []uint64 { diff --git a/flag_uint_slice.go b/flag_uint_slice.go index 350b29c..3689eaa 100644 --- a/flag_uint_slice.go +++ b/flag_uint_slice.go @@ -194,6 +194,15 @@ func (f *UintSliceFlag) stringify() string { return stringifySliceFlag(f.Usage, f.Names(), defaultVals) } +// RunAction executes flag action if set +func (f *UintSliceFlag) RunAction(c *Context) error { + if f.Action != nil { + return f.Action(c, c.UintSlice(f.Name)) + } + + return nil +} + // UintSlice looks up the value of a local UintSliceFlag, returns // nil if not found func (cCtx *Context) UintSlice(name string) []uint { diff --git a/godoc-current.txt b/godoc-current.txt index 0e09211..2c8b5e0 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -5,24 +5,24 @@ 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) + (&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) - } + 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 @@ -49,8 +49,8 @@ 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 .VisibleFlagCategories}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} + {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} GLOBAL OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} @@ -82,12 +82,10 @@ DESCRIPTION: OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} - {{end}}{{range .Flags}}{{.}} - {{end}}{{end}}{{else}}{{if .VisibleFlags}} + {{end}}{{range .Flags}}{{.}}{{end}}{{end}}{{else}}{{if .VisibleFlags}} OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}}{{end}} + {{range .VisibleFlags}}{{.}}{{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 @@ -157,12 +155,11 @@ DESCRIPTION: 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}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} + {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}} + {{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 @@ -300,6 +297,8 @@ type App struct { CommandNotFound CommandNotFoundFunc // Execute this function if a usage error occurs OnUsageError OnUsageErrorFunc + // Execute this function when an invalid flag is accessed from the context + InvalidFlagAccessHandler InvalidFlagAccessFunc // Compilation date Compiled time.Time // List of all authors who contributed @@ -450,6 +449,10 @@ type BoolFlag struct { Aliases []string EnvVars []string + + Count *int + + Action func(*Context, bool) error } BoolFlag is a flag with type bool @@ -487,6 +490,9 @@ func (f *BoolFlag) IsVisible() bool func (f *BoolFlag) Names() []string Names returns the names of the flag +func (f *BoolFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *BoolFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -622,7 +628,7 @@ func (cCtx *Context) Bool(name string) bool Bool looks up the value of a local BoolFlag, returns false if not found func (cCtx *Context) Count(name string) int - NumOccurrences returns the num of occurences of this flag + Count returns the num of occurences of this flag func (cCtx *Context) Duration(name string) time.Duration Duration looks up the value of a local DurationFlag, returns 0 if not found @@ -693,9 +699,23 @@ func (cCtx *Context) Uint(name string) uint func (cCtx *Context) Uint64(name string) uint64 Uint64 looks up the value of a local Uint64Flag, returns 0 if not found +func (cCtx *Context) Uint64Slice(name string) []uint64 + Uint64Slice looks up the value of a local Uint64SliceFlag, returns nil if + not found + +func (cCtx *Context) UintSlice(name string) []uint + UintSlice looks up the value of a local UintSliceFlag, returns nil if not + found + func (cCtx *Context) Value(name string) interface{} Value returns the value of the flag corresponding to `name` +type Countable interface { + Count() int +} + Countable is an interface to enable detection of flag values which support + repetitive flags + type DurationFlag struct { Name string @@ -713,6 +733,8 @@ type DurationFlag struct { Aliases []string EnvVars []string + + Action func(*Context, time.Duration) error } DurationFlag is a flag with type time.Duration @@ -750,6 +772,9 @@ func (f *DurationFlag) IsVisible() bool func (f *DurationFlag) Names() []string Names returns the names of the flag +func (f *DurationFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *DurationFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -823,6 +848,8 @@ type Flag interface { // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. GetValue() string + + RunAction(*Context) error } Flag is a common interface related to parsing flags in cli. For more advanced flag parsing techniques, it is recommended that this interface be @@ -916,6 +943,8 @@ type Float64Flag struct { Aliases []string EnvVars []string + + Action func(*Context, float64) error } Float64Flag is a flag with type float64 @@ -953,6 +982,9 @@ func (f *Float64Flag) IsVisible() bool func (f *Float64Flag) Names() []string Names returns the names of the flag +func (f *Float64Flag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *Float64Flag) String() string String returns a readable representation of this value (for usage defaults) @@ -999,6 +1031,8 @@ type Float64SliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []float64) error } Float64SliceFlag is a flag with type *Float64Slice @@ -1038,6 +1072,9 @@ func (f *Float64SliceFlag) IsVisible() bool func (f *Float64SliceFlag) Names() []string Names returns the names of the flag +func (f *Float64SliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *Float64SliceFlag) SetDestination(slice []float64) func (f *Float64SliceFlag) SetValue(slice []float64) @@ -1067,16 +1104,18 @@ type GenericFlag struct { HasBeenSet bool Value Generic - Destination *Generic + Destination Generic Aliases []string EnvVars []string TakesFile bool + + Action func(*Context, interface{}) error } GenericFlag is a flag with type Generic -func (f GenericFlag) Apply(set *flag.FlagSet) error +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 @@ -1111,6 +1150,9 @@ func (f *GenericFlag) IsVisible() bool func (f *GenericFlag) Names() []string Names returns the names of the flag +func (f *GenericFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *GenericFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1134,6 +1176,10 @@ type Int64Flag struct { Aliases []string EnvVars []string + + Base int + + Action func(*Context, int64) error } Int64Flag is a flag with type int64 @@ -1171,6 +1217,9 @@ func (f *Int64Flag) IsVisible() bool func (f *Int64Flag) Names() []string Names returns the names of the flag +func (f *Int64Flag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *Int64Flag) String() string String returns a readable representation of this value (for usage defaults) @@ -1217,6 +1266,8 @@ type Int64SliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []int64) error } Int64SliceFlag is a flag with type *Int64Slice @@ -1256,6 +1307,9 @@ func (f *Int64SliceFlag) IsVisible() bool func (f *Int64SliceFlag) Names() []string Names returns the names of the flag +func (f *Int64SliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *Int64SliceFlag) SetDestination(slice []int64) func (f *Int64SliceFlag) SetValue(slice []int64) @@ -1283,6 +1337,10 @@ type IntFlag struct { Aliases []string EnvVars []string + + Base int + + Action func(*Context, int) error } IntFlag is a flag with type int @@ -1320,6 +1378,9 @@ func (f *IntFlag) IsVisible() bool func (f *IntFlag) Names() []string Names returns the names of the flag +func (f *IntFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *IntFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1370,6 +1431,8 @@ type IntSliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []int) error } IntSliceFlag is a flag with type *IntSlice @@ -1409,6 +1472,9 @@ func (f *IntSliceFlag) IsVisible() bool func (f *IntSliceFlag) Names() []string Names returns the names of the flag +func (f *IntSliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *IntSliceFlag) SetDestination(slice []int) func (f *IntSliceFlag) SetValue(slice []int) @@ -1419,6 +1485,10 @@ func (f *IntSliceFlag) String() string func (f *IntSliceFlag) TakesValue() bool TakesValue returns true if the flag takes a value, otherwise false +type InvalidFlagAccessFunc func(*Context, string) + InvalidFlagAccessFunc is executed when an invalid flag is accessed from the + context. + type MultiError interface { error Errors() []error @@ -1468,6 +1538,8 @@ type PathFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, Path) error } PathFlag is a flag with type Path @@ -1505,6 +1577,9 @@ func (f *PathFlag) IsVisible() bool func (f *PathFlag) Names() []string Names returns the names of the flag +func (f *PathFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *PathFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1548,6 +1623,8 @@ func (x *SliceFlag[T, S, E]) IsVisible() bool func (x *SliceFlag[T, S, E]) Names() []string +func (x *SliceFlag[T, S, E]) RunAction(c *Context) error + func (x *SliceFlag[T, S, E]) SetDestination(slice S) func (x *SliceFlag[T, S, E]) SetValue(slice S) @@ -1592,6 +1669,8 @@ type StringFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, string) error } StringFlag is a flag with type string @@ -1629,6 +1708,9 @@ func (f *StringFlag) IsVisible() bool func (f *StringFlag) Names() []string Names returns the names of the flag +func (f *StringFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *StringFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1677,6 +1759,8 @@ type StringSliceFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, []string) error } StringSliceFlag is a flag with type *StringSlice @@ -1716,6 +1800,9 @@ func (f *StringSliceFlag) IsVisible() bool func (f *StringSliceFlag) Names() []string Names returns the names of the flag +func (f *StringSliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *StringSliceFlag) SetDestination(slice []string) func (f *StringSliceFlag) SetValue(slice []string) @@ -1780,6 +1867,8 @@ type TimestampFlag struct { Layout string Timezone *time.Location + + Action func(*Context, *time.Time) error } TimestampFlag is a flag with type *Timestamp @@ -1817,6 +1906,9 @@ func (f *TimestampFlag) IsVisible() bool func (f *TimestampFlag) Names() []string Names returns the names of the flag +func (f *TimestampFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *TimestampFlag) String() string String returns a readable representation of this value (for usage defaults) @@ -1840,6 +1932,10 @@ type Uint64Flag struct { Aliases []string EnvVars []string + + Base int + + Action func(*Context, uint64) error } Uint64Flag is a flag with type uint64 @@ -1877,12 +1973,103 @@ func (f *Uint64Flag) IsVisible() bool func (f *Uint64Flag) Names() []string Names returns the names of the flag +func (f *Uint64Flag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *Uint64Flag) String() string String returns a readable representation of this value (for usage defaults) func (f *Uint64Flag) TakesValue() bool TakesValue returns true if the flag takes a value, otherwise false +type Uint64Slice struct { + // Has unexported fields. +} + Uint64Slice wraps []int64 to satisfy flag.Value + +func NewUint64Slice(defaults ...uint64) *Uint64Slice + NewUint64Slice makes an *Uint64Slice with default values + +func (i *Uint64Slice) Get() interface{} + Get returns the slice of ints set by this flag + +func (i *Uint64Slice) Serialize() string + Serialize allows Uint64Slice to fulfill Serializer + +func (i *Uint64Slice) Set(value string) error + Set parses the value into an integer and appends it to the list of values + +func (i *Uint64Slice) String() string + String returns a readable representation of this value (for usage defaults) + +func (i *Uint64Slice) Value() []uint64 + Value returns the slice of ints set by this flag + +type Uint64SliceFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *Uint64Slice + Destination *Uint64Slice + + Aliases []string + EnvVars []string + + Action func(*Context, []uint64) error +} + Uint64SliceFlag is a flag with type *Uint64Slice + +func (f *Uint64SliceFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *Uint64SliceFlag) Get(ctx *Context) []uint64 + Get returns the flag’s value in the given Context. + +func (f *Uint64SliceFlag) GetCategory() string + GetCategory returns the category for the flag + +func (f *Uint64SliceFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *Uint64SliceFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *Uint64SliceFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *Uint64SliceFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *Uint64SliceFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *Uint64SliceFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *Uint64SliceFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *Uint64SliceFlag) Names() []string + Names returns the names of the flag + +func (f *Uint64SliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + +func (f *Uint64SliceFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Uint64SliceFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + type UintFlag struct { Name string @@ -1900,6 +2087,10 @@ type UintFlag struct { Aliases []string EnvVars []string + + Base int + + Action func(*Context, uint) error } UintFlag is a flag with type uint @@ -1937,12 +2128,107 @@ func (f *UintFlag) IsVisible() bool func (f *UintFlag) Names() []string Names returns the names of the flag +func (f *UintFlag) RunAction(c *Context) error + RunAction executes flag action if set + func (f *UintFlag) String() string String returns a readable representation of this value (for usage defaults) func (f *UintFlag) TakesValue() bool TakesValue returns true if the flag takes a value, otherwise false +type UintSlice struct { + // Has unexported fields. +} + UintSlice wraps []int to satisfy flag.Value + +func NewUintSlice(defaults ...uint) *UintSlice + NewUintSlice makes an *UintSlice with default values + +func (i *UintSlice) Get() interface{} + Get returns the slice of ints set by this flag + +func (i *UintSlice) Serialize() string + Serialize allows UintSlice to fulfill Serializer + +func (i *UintSlice) Set(value string) error + Set parses the value into an integer and appends it to the list of values + +func (i *UintSlice) SetUint(value uint) + TODO: Consistently have specific Set function for Int64 and Float64 ? SetInt + directly adds an integer to the list of values + +func (i *UintSlice) String() string + String returns a readable representation of this value (for usage defaults) + +func (i *UintSlice) Value() []uint + Value returns the slice of ints set by this flag + +type UintSliceFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *UintSlice + Destination *UintSlice + + Aliases []string + EnvVars []string + + Action func(*Context, []uint) error +} + UintSliceFlag is a flag with type *UintSlice + +func (f *UintSliceFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *UintSliceFlag) Get(ctx *Context) []uint + Get returns the flag’s value in the given Context. + +func (f *UintSliceFlag) GetCategory() string + GetCategory returns the category for the flag + +func (f *UintSliceFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *UintSliceFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *UintSliceFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *UintSliceFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *UintSliceFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *UintSliceFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *UintSliceFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *UintSliceFlag) Names() []string + Names returns the names of the flag + +func (f *UintSliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + +func (f *UintSliceFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *UintSliceFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + type VisibleFlagCategory interface { // Name returns the category name string Name() string diff --git a/sliceflag.go b/sliceflag.go index 89f3e0c..c8670cb 100644 --- a/sliceflag.go +++ b/sliceflag.go @@ -110,17 +110,18 @@ func (x *SliceFlag[T, S, E]) GetDestination() S { 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 *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 *SliceFlag[T, S, E]) RunAction(c *Context) error { return x.Target.RunAction(c) } func (x *flagValueHook) Set(value string) error { if err := x.value.Set(value); err != nil { diff --git a/testdata/godoc-v2.x.txt b/testdata/godoc-v2.x.txt index 562b7ff..2c8b5e0 100644 --- a/testdata/godoc-v2.x.txt +++ b/testdata/godoc-v2.x.txt @@ -1,28 +1,28 @@ -package cli // import "github.com/urfave/cli/v2" +package cli // import "github.com/urfave/cli/v3" 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) + (&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) - } + 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 @@ -49,8 +49,8 @@ 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 .VisibleFlagCategories}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} + {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} GLOBAL OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} @@ -64,8 +64,8 @@ GLOBAL OPTIONS: COPYRIGHT: {{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 + 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: @@ -82,12 +82,10 @@ DESCRIPTION: OPTIONS:{{range .VisibleFlagCategories}} {{if .Name}}{{.Name}} - {{end}}{{range .Flags}}{{.}} - {{end}}{{end}}{{else}}{{if .VisibleFlags}} + {{end}}{{range .Flags}}{{.}}{{end}}{{end}}{{else}}{{if .VisibleFlags}} OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}}{{end}} + {{range .VisibleFlags}}{{.}}{{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 @@ -157,12 +155,11 @@ DESCRIPTION: 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}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} + {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}} + {{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 @@ -201,9 +198,9 @@ func DefaultAppComplete(cCtx *Context) 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! + 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 @@ -250,13 +247,6 @@ TYPES type ActionFunc func(*Context) error ActionFunc is the action to execute when no subcommands are specified -type ActionableFlag interface { - Flag - RunAction(*Context) error -} - ActionableFlag is an interface that wraps Flag interface and RunAction - operation. - 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 @@ -307,6 +297,8 @@ type App struct { CommandNotFound CommandNotFoundFunc // Execute this function if a usage error occurs OnUsageError OnUsageErrorFunc + // Execute this function when an invalid flag is accessed from the context + InvalidFlagAccessHandler InvalidFlagAccessFunc // Compilation date Compiled time.Time // List of all authors who contributed @@ -367,14 +359,14 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) 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 + 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. + 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 @@ -457,6 +449,10 @@ type BoolFlag struct { Aliases []string EnvVars []string + + Count *int + + Action func(*Context, bool) error } BoolFlag is a flag with type bool @@ -467,7 +463,7 @@ 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 + GetCategory returns the category of the flag func (f *BoolFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -501,15 +497,7 @@ 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 CategorizableFlag interface { - VisibleFlag - - GetCategory() string -} - CategorizableFlag is an interface that allows us to potentially use a flag - in a categorized representation. + TakesValue returns true if the flag takes a value, otherwise false type Command struct { // The name of the command @@ -639,6 +627,9 @@ func (cCtx *Context) Args() Args func (cCtx *Context) Bool(name string) bool Bool looks up the value of a local BoolFlag, returns false if not found +func (cCtx *Context) Count(name string) int + Count returns the num of occurences of this flag + func (cCtx *Context) Duration(name string) time.Duration Duration looks up the value of a local DurationFlag, returns 0 if not found @@ -708,30 +699,22 @@ func (cCtx *Context) Uint(name string) uint 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 +func (cCtx *Context) Uint64Slice(name string) []uint64 + Uint64Slice looks up the value of a local Uint64SliceFlag, returns nil if + not found - // GetValue returns the flags value as string representation and an empty - // string if the flag takes no value at all. - GetValue() string +func (cCtx *Context) UintSlice(name string) []uint + UintSlice looks up the value of a local UintSliceFlag, returns nil if not + found - // GetDefaultText returns the default text for this flag - GetDefaultText() string +func (cCtx *Context) Value(name string) interface{} + Value returns the value of the flag corresponding to `name` - // GetEnvVars returns the env vars for this flag - GetEnvVars() []string +type Countable interface { + Count() int } - DocGenerationFlag is an interface that allows documentation generation for - the flag + Countable is an interface to enable detection of flag values which support + repetitive flags type DurationFlag struct { Name string @@ -750,6 +733,8 @@ type DurationFlag struct { Aliases []string EnvVars []string + + Action func(*Context, time.Duration) error } DurationFlag is a flag with type time.Duration @@ -760,7 +745,7 @@ 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 + GetCategory returns the category of the flag func (f *DurationFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -794,7 +779,7 @@ 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 + TakesValue returns true if the flag takes a value, otherwise false type ErrorFormatter interface { Format(s fmt.State, verb rune) @@ -812,9 +797,9 @@ 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 + 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 @@ -829,10 +814,42 @@ type ExitErrHandlerFunc func(cCtx *Context, err error) type Flag interface { fmt.Stringer + // Apply Flag settings to the given flag set Apply(*flag.FlagSet) error + + // All possible names for this flag Names() []string + + // Whether the flag has been set or not IsSet() bool + + // whether the flag is a required flag or not + IsRequired() bool + + // IsVisible returns true if the flag is not hidden, otherwise false + IsVisible() bool + + // Returns the category of the flag + GetCategory() string + + // GetUsage returns the usage string for the flag + GetUsage() string + + // GetEnvVars returns the env vars for this flag + GetEnvVars() []string + + // TakesValue returns true if the flag takes a value, otherwise false + TakesValue() bool + + // GetDefaultText returns the default text for this flag + GetDefaultText() string + + // GetValue returns the flags value as string representation and an empty + // string if the flag takes no value at all. + GetValue() string + + RunAction(*Context) error } Flag is a common interface related to parsing flags in cli. For more advanced flag parsing techniques, it is recommended that this interface be @@ -926,6 +943,8 @@ type Float64Flag struct { Aliases []string EnvVars []string + + Action func(*Context, float64) error } Float64Flag is a flag with type float64 @@ -936,7 +955,7 @@ 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 + GetCategory returns the category of the flag func (f *Float64Flag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -970,7 +989,7 @@ 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 + TakesValue returns true if the flag takes a value, otherwise false type Float64Slice struct { // Has unexported fields. @@ -1012,6 +1031,8 @@ type Float64SliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []float64) error } Float64SliceFlag is a flag with type *Float64Slice @@ -1022,7 +1043,7 @@ 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 + GetCategory returns the category of the flag func (f *Float64SliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1083,16 +1104,18 @@ type GenericFlag struct { HasBeenSet bool Value Generic - Destination *Generic + Destination Generic Aliases []string EnvVars []string TakesFile bool + + Action func(*Context, interface{}) error } GenericFlag is a flag with type Generic -func (f GenericFlag) Apply(set *flag.FlagSet) error +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 @@ -1100,7 +1123,7 @@ 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 + GetCategory returns the category of the flag func (f *GenericFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1134,7 +1157,7 @@ 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 + TakesValue returns true if the flag takes a value, otherwise false type Int64Flag struct { Name string @@ -1153,6 +1176,10 @@ type Int64Flag struct { Aliases []string EnvVars []string + + Base int + + Action func(*Context, int64) error } Int64Flag is a flag with type int64 @@ -1163,7 +1190,7 @@ 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 + GetCategory returns the category of the flag func (f *Int64Flag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1197,7 +1224,7 @@ 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 + TakesValue returns true if the flag takes a value, otherwise false type Int64Slice struct { // Has unexported fields. @@ -1239,6 +1266,8 @@ type Int64SliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []int64) error } Int64SliceFlag is a flag with type *Int64Slice @@ -1249,7 +1278,7 @@ 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 + GetCategory returns the category of the flag func (f *Int64SliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1289,7 +1318,7 @@ 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 + TakesValue returns true if the flag takes a value, otherwise false type IntFlag struct { Name string @@ -1308,6 +1337,10 @@ type IntFlag struct { Aliases []string EnvVars []string + + Base int + + Action func(*Context, int) error } IntFlag is a flag with type int @@ -1318,7 +1351,7 @@ 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 + GetCategory returns the category of the flag func (f *IntFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1352,7 +1385,7 @@ 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 + TakesValue returns true if the flag takes a value, otherwise false type IntSlice struct { // Has unexported fields. @@ -1398,6 +1431,8 @@ type IntSliceFlag struct { Aliases []string EnvVars []string + + Action func(*Context, []int) error } IntSliceFlag is a flag with type *IntSlice @@ -1408,7 +1443,7 @@ 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 + GetCategory returns the category of the flag func (f *IntSliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1448,7 +1483,11 @@ 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 + TakesValue returns true if the flag takes a value, otherwise false + +type InvalidFlagAccessFunc func(*Context, string) + InvalidFlagAccessFunc is executed when an invalid flag is accessed from the + context. type MultiError interface { error @@ -1465,8 +1504,8 @@ type MultiInt64Flag = SliceFlag[*Int64SliceFlag, []int64, int64] 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. + 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 @@ -1499,6 +1538,8 @@ type PathFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, Path) error } PathFlag is a flag with type Path @@ -1509,7 +1550,7 @@ 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 + GetCategory returns the category of the flag func (f *PathFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1543,16 +1584,7 @@ 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 + TakesValue returns true if the flag takes a value, otherwise false type Serializer interface { Serialize() string @@ -1564,9 +1596,9 @@ type SliceFlag[T SliceFlagTarget[E], S ~[]E, E any] struct { 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, + 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 @@ -1591,6 +1623,8 @@ func (x *SliceFlag[T, S, E]) IsVisible() bool func (x *SliceFlag[T, S, E]) Names() []string +func (x *SliceFlag[T, S, E]) RunAction(c *Context) error + func (x *SliceFlag[T, S, E]) SetDestination(slice S) func (x *SliceFlag[T, S, E]) SetValue(slice S) @@ -1601,10 +1635,6 @@ 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). @@ -1639,6 +1669,8 @@ type StringFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, string) error } StringFlag is a flag with type string @@ -1649,7 +1681,7 @@ 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 + GetCategory returns the category of the flag func (f *StringFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1683,7 +1715,7 @@ 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 + TakesValue returns true if the flag takes a value, otherwise false type StringSlice struct { // Has unexported fields. @@ -1727,6 +1759,8 @@ type StringSliceFlag struct { EnvVars []string TakesFile bool + + Action func(*Context, []string) error } StringSliceFlag is a flag with type *StringSlice @@ -1737,7 +1771,7 @@ 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 + GetCategory returns the category of the flag func (f *StringSliceFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1777,7 +1811,7 @@ 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 + TakesValue returns true if the flag takes a value, otherwise false type SuggestCommandFunc func(commands []*Command, provided string) string @@ -1833,6 +1867,8 @@ type TimestampFlag struct { Layout string Timezone *time.Location + + Action func(*Context, *time.Time) error } TimestampFlag is a flag with type *Timestamp @@ -1843,7 +1879,7 @@ 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 + GetCategory returns the category of the flag func (f *TimestampFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1877,7 +1913,7 @@ 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 + TakesValue returns true if the flag takes a value, otherwise false type Uint64Flag struct { Name string @@ -1896,6 +1932,10 @@ type Uint64Flag struct { Aliases []string EnvVars []string + + Base int + + Action func(*Context, uint64) error } Uint64Flag is a flag with type uint64 @@ -1906,7 +1946,7 @@ 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 + GetCategory returns the category of the flag func (f *Uint64Flag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -1940,6 +1980,94 @@ func (f *Uint64Flag) String() string String returns a readable representation of this value (for usage defaults) func (f *Uint64Flag) TakesValue() bool + TakesValue returns true if the flag takes a value, otherwise false + +type Uint64Slice struct { + // Has unexported fields. +} + Uint64Slice wraps []int64 to satisfy flag.Value + +func NewUint64Slice(defaults ...uint64) *Uint64Slice + NewUint64Slice makes an *Uint64Slice with default values + +func (i *Uint64Slice) Get() interface{} + Get returns the slice of ints set by this flag + +func (i *Uint64Slice) Serialize() string + Serialize allows Uint64Slice to fulfill Serializer + +func (i *Uint64Slice) Set(value string) error + Set parses the value into an integer and appends it to the list of values + +func (i *Uint64Slice) String() string + String returns a readable representation of this value (for usage defaults) + +func (i *Uint64Slice) Value() []uint64 + Value returns the slice of ints set by this flag + +type Uint64SliceFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *Uint64Slice + Destination *Uint64Slice + + Aliases []string + EnvVars []string + + Action func(*Context, []uint64) error +} + Uint64SliceFlag is a flag with type *Uint64Slice + +func (f *Uint64SliceFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *Uint64SliceFlag) Get(ctx *Context) []uint64 + Get returns the flag’s value in the given Context. + +func (f *Uint64SliceFlag) GetCategory() string + GetCategory returns the category for the flag + +func (f *Uint64SliceFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *Uint64SliceFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *Uint64SliceFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *Uint64SliceFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *Uint64SliceFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *Uint64SliceFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *Uint64SliceFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *Uint64SliceFlag) Names() []string + Names returns the names of the flag + +func (f *Uint64SliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + +func (f *Uint64SliceFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Uint64SliceFlag) TakesValue() bool TakesValue returns true of the flag takes a value, otherwise false type UintFlag struct { @@ -1959,6 +2087,10 @@ type UintFlag struct { Aliases []string EnvVars []string + + Base int + + Action func(*Context, uint) error } UintFlag is a flag with type uint @@ -1969,7 +2101,7 @@ 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 + GetCategory returns the category of the flag func (f *UintFlag) GetDefaultText() string GetDefaultText returns the default text for this flag @@ -2003,25 +2135,109 @@ 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 + TakesValue returns true if the flag takes a value, otherwise false -type VisibleFlag interface { - Flag +type UintSlice struct { + // Has unexported fields. +} + UintSlice wraps []int to satisfy flag.Value - // IsVisible returns true if the flag is not hidden, otherwise false - IsVisible() bool +func NewUintSlice(defaults ...uint) *UintSlice + NewUintSlice makes an *UintSlice with default values + +func (i *UintSlice) Get() interface{} + Get returns the slice of ints set by this flag + +func (i *UintSlice) Serialize() string + Serialize allows UintSlice to fulfill Serializer + +func (i *UintSlice) Set(value string) error + Set parses the value into an integer and appends it to the list of values + +func (i *UintSlice) SetUint(value uint) + TODO: Consistently have specific Set function for Int64 and Float64 ? SetInt + directly adds an integer to the list of values + +func (i *UintSlice) String() string + String returns a readable representation of this value (for usage defaults) + +func (i *UintSlice) Value() []uint + Value returns the slice of ints set by this flag + +type UintSliceFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *UintSlice + Destination *UintSlice + + Aliases []string + EnvVars []string + + Action func(*Context, []uint) error } - VisibleFlag is an interface that allows to check if a flag is visible + UintSliceFlag is a flag with type *UintSlice + +func (f *UintSliceFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *UintSliceFlag) Get(ctx *Context) []uint + Get returns the flag’s value in the given Context. + +func (f *UintSliceFlag) GetCategory() string + GetCategory returns the category for the flag + +func (f *UintSliceFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *UintSliceFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *UintSliceFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *UintSliceFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *UintSliceFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *UintSliceFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *UintSliceFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *UintSliceFlag) Names() []string + Names returns the names of the flag + +func (f *UintSliceFlag) RunAction(c *Context) error + RunAction executes flag action if set + +func (f *UintSliceFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *UintSliceFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false type VisibleFlagCategory interface { // Name returns the category name string Name() string // Flags returns a slice of VisibleFlag sorted by name - Flags() []VisibleFlag + Flags() []Flag } VisibleFlagCategory is a category containing flags. -package altsrc // import "github.com/urfave/cli/v2/altsrc" +package altsrc // import "github.com/urfave/cli/v3/altsrc" FUNCTIONS @@ -2038,9 +2254,9 @@ func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceCont 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 + 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 diff --git a/zz_generated.flags_test.go b/zz_generated.flags_test.go index fa01014..be6dadf 100644 --- a/zz_generated.flags_test.go +++ b/zz_generated.flags_test.go @@ -167,18 +167,6 @@ func TestUint64SliceFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } -func TestUint64SliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.RequiredFlag = &cli.Uint64SliceFlag{} - - _ = f.IsRequired() -} - -func TestUint64SliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.VisibleFlag = &cli.Uint64SliceFlag{} - - _ = f.IsVisible() -} - func TestUintSliceFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.UintSliceFlag{} @@ -186,18 +174,6 @@ func TestUintSliceFlag_SatisfiesFlagInterface(t *testing.T) { _ = f.Names() } -func TestUintSliceFlag_SatisfiesRequiredFlagInterface(t *testing.T) { - var f cli.RequiredFlag = &cli.UintSliceFlag{} - - _ = f.IsRequired() -} - -func TestUintSliceFlag_SatisfiesVisibleFlagInterface(t *testing.T) { - var f cli.VisibleFlag = &cli.UintSliceFlag{} - - _ = f.IsVisible() -} - func TestBoolFlag_SatisfiesFlagInterface(t *testing.T) { var f cli.Flag = &cli.BoolFlag{} From a851a773a88fd2ea9014a30b2b2e9f5f600ef4de Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Mon, 3 Oct 2022 10:12:37 -0400 Subject: [PATCH 97/97] Point docs tests at v3 --- .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 7a3ac9b..800bbeb 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -68,7 +68,7 @@ jobs: - run: make ensure-gfmrun - run: make gfmrun env: - FLAGS: --walk docs/v2/ + FLAGS: --walk docs/v3/ - run: make diffcheck publish: permissions: