From 11d45572f9727acfbc93daa8565f379d396125d6 Mon Sep 17 00:00:00 2001 From: rliebz Date: Sat, 26 Aug 2017 07:42:25 -0400 Subject: [PATCH 1/2] Export funcs to configure flag prefix/env hints This will allow users to customize the prefix section or env hint section of the flag entries in the help menu without having to reimplement the rest of the logic required in defining FlagStringer. --- flag.go | 20 ++++++++++++++------ funcs.go | 8 ++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/flag.go b/flag.go index 877ff35..b17f5b9 100644 --- a/flag.go +++ b/flag.go @@ -37,6 +37,14 @@ var HelpFlag Flag = BoolFlag{ // to display a flag. var FlagStringer FlagStringFunc = stringifyFlag +// FlagNamePrefixer converts a full flag name and its placeholder into the help +// message flag prefix. This is used by the default FlagStringer. +var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames + +// FlagEnvHinter annotates flag help message with the environment variable +// details. This is used by the default FlagStringer. +var FlagEnvHinter FlagEnvHintFunc = withEnvHint + // FlagsByName is a slice of Flag. type FlagsByName []Flag @@ -710,13 +718,13 @@ func stringifyFlag(f Flag) string { switch f.(type) { case IntSliceFlag: - return withEnvHint(fv.FieldByName("EnvVar").String(), + return FlagEnvHinter(fv.FieldByName("EnvVar").String(), stringifyIntSliceFlag(f.(IntSliceFlag))) case Int64SliceFlag: - return withEnvHint(fv.FieldByName("EnvVar").String(), + return FlagEnvHinter(fv.FieldByName("EnvVar").String(), stringifyInt64SliceFlag(f.(Int64SliceFlag))) case StringSliceFlag: - return withEnvHint(fv.FieldByName("EnvVar").String(), + return FlagEnvHinter(fv.FieldByName("EnvVar").String(), stringifyStringSliceFlag(f.(StringSliceFlag))) } @@ -744,8 +752,8 @@ func stringifyFlag(f Flag) string { usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString)) - return withEnvHint(fv.FieldByName("EnvVar").String(), - fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault)) + return FlagEnvHinter(fv.FieldByName("EnvVar").String(), + fmt.Sprintf("%s\t%s", FlagNamePrefixer(fv.FieldByName("Name").String(), placeholder), usageWithDefault)) } func stringifyIntSliceFlag(f IntSliceFlag) string { @@ -795,5 +803,5 @@ func stringifySliceFlag(usage, name string, defaultVals []string) string { } usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) - return fmt.Sprintf("%s\t%s", prefixedNames(name, placeholder), usageWithDefault) + return fmt.Sprintf("%s\t%s", FlagNamePrefixer(name, placeholder), usageWithDefault) } diff --git a/funcs.go b/funcs.go index cba5e6c..3ad3c6d 100644 --- a/funcs.go +++ b/funcs.go @@ -26,3 +26,11 @@ type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error // FlagStringFunc is used by the help generation to display a flag, which is // expected to be a single line. type FlagStringFunc func(Flag) string + +// FlagNamePrefixFunc is used by the default FlagStringFunc to create prefix +// text for a flag's full name. +type FlagNamePrefixFunc func(fullName, placeholder string) string + +// FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help +// with the environment variable details. +type FlagEnvHintFunc func(envVar, str string) string From cbbe4c1a2c34e52c8ad0937c01c9c15ef407a6d5 Mon Sep 17 00:00:00 2001 From: Robert Liebowitz Date: Mon, 18 Sep 2017 00:44:42 -0400 Subject: [PATCH 2/2] Add tests for custom flag prefix/env hints --- flag_test.go | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/flag_test.go b/flag_test.go index 1ccb639..bc840b5 100644 --- a/flag_test.go +++ b/flag_test.go @@ -158,6 +158,83 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { } } +var prefixStringFlagTests = []struct { + name string + usage string + value string + prefixer FlagNamePrefixFunc + expected string +}{ + {"foo", "", "", func(a, b string) string { + return fmt.Sprintf("name: %s, ph: %s", a, b) + }, "name: foo, ph: value\t"}, + {"f", "", "", func(a, b string) string { + return fmt.Sprintf("name: %s, ph: %s", a, b) + }, "name: f, ph: value\t"}, + {"f", "The total `foo` desired", "all", func(a, b string) string { + return fmt.Sprintf("name: %s, ph: %s", a, b) + }, "name: f, ph: foo\tThe total foo desired (default: \"all\")"}, + {"test", "", "Something", func(a, b string) string { + return fmt.Sprintf("name: %s, ph: %s", a, b) + }, "name: test, ph: value\t(default: \"Something\")"}, + {"config,c", "Load configuration from `FILE`", "", func(a, b string) string { + return fmt.Sprintf("name: %s, ph: %s", a, b) + }, "name: config,c, ph: FILE\tLoad configuration from FILE"}, + {"config,c", "Load configuration from `CONFIG`", "config.json", func(a, b string) string { + return fmt.Sprintf("name: %s, ph: %s", a, b) + }, "name: config,c, ph: CONFIG\tLoad configuration from CONFIG (default: \"config.json\")"}, +} + +func TestFlagNamePrefixer(t *testing.T) { + defer func() { + FlagNamePrefixer = prefixedNames + }() + + for _, test := range prefixStringFlagTests { + FlagNamePrefixer = test.prefixer + flag := StringFlag{Name: test.name, Usage: test.usage, Value: test.value} + output := flag.String() + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +var envHintFlagTests = []struct { + name string + env string + hinter FlagEnvHintFunc + expected string +}{ + {"foo", "", func(a, b string) string { + return fmt.Sprintf("env: %s, str: %s", a, b) + }, "env: , str: --foo value\t"}, + {"f", "", func(a, b string) string { + return fmt.Sprintf("env: %s, str: %s", a, b) + }, "env: , str: -f value\t"}, + {"foo", "ENV_VAR", func(a, b string) string { + return fmt.Sprintf("env: %s, str: %s", a, b) + }, "env: ENV_VAR, str: --foo value\t"}, + {"f", "ENV_VAR", func(a, b string) string { + return fmt.Sprintf("env: %s, str: %s", a, b) + }, "env: ENV_VAR, str: -f value\t"}, +} + +func TestFlagEnvHinter(t *testing.T) { + defer func() { + FlagEnvHinter = withEnvHint + }() + + for _, test := range envHintFlagTests { + FlagEnvHinter = test.hinter + flag := StringFlag{Name: test.name, EnvVar: test.env} + output := flag.String() + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + var stringSliceFlagTests = []struct { name string value *StringSlice