Repository: urfave/cli
Pull request #1745 (2023-05-30 15:13:01)

Feat: Add value source
<!--
  This template provides some ideas of things to include in your PR description.
  To start, try providing a short summary of your changes in the Title above.
  If a section of the PR template does not apply to this PR, then delete that section.
 -->

## What type of PR is this?

_(REQUIRED)_

<!--
  Delete any of the following that do not apply:
 -->

- feature

## What this PR does / why we need it:

_(REQUIRED)_

<!--
  What goal is this change working towards?
  Provide a bullet pointed summary of how each file was changed.
  Briefly explain any decisions you made with respect to the changes.
  Include anything here that you didn't include in *Release Notes*
  above, such as changes to CI or changes to internal methods.
-->
This places Envs and Filepaths on same footing instead of having one take precedence over the other. In fact the user can choose which to prioritize but placing the sources in the given order. The new interface also allows plugin implementations i.e for reading values from networks, custom format files etc etc. 

## Which issue(s) this PR fixes:

_(REQUIRED)_

<!--
If this PR fixes one of more issues, list them here.
One line each, like so:

Fixes #123
Fixes #39
-->

## Special notes for your reviewer:

_(fill-in or delete this section)_

<!--
   Is there any particular feedback you would / wouldn't like?
   Which parts of the code should reviewers focus on?
-->

## Testing

_(fill-in or delete this section)_

<!--
  Describe how you tested this change.
-->

Updates all test case 

## Release Notes

_(REQUIRED)_
<!--
  If this PR makes user facing changes, please describe them here. This
  description will be copied into the release notes/changelog, whenever the
  next version is released. Keep this section short, and focus on high level
  changes.

  Put your text between the block. To omit notes, use NONE within the block.
-->

```release-note

```



Patch:
diff --git a/flag_bool_with_inverse.go b/flag_bool_with_inverse.go
index 634d17c..633b9bb 100644
--- a/flag_bool_with_inverse.go
+++ b/flag_bool_with_inverse.go
@@ -89,7 +89,7 @@ func (parent *BoolWithInverseFlag) initialize() {
 	parent.negativeFlag = &BoolFlag{
 		Category:    child.Category,
 		DefaultText: child.DefaultText,
-		FilePaths:   append([]string{}, child.FilePaths...),
+		Sources:     append(ValueSources{}, child.Sources...),
 		Usage:       child.Usage,
 		Required:    child.Required,
 		Hidden:      child.Hidden,
@@ -108,10 +108,10 @@ func (parent *BoolWithInverseFlag) initialize() {
 	parent.negativeFlag.Name = parent.inverseName()
 	parent.negativeFlag.Aliases = parent.inverseAliases()
 
-	if len(child.EnvVars) > 0 {
-		parent.negativeFlag.EnvVars = make([]string, len(child.EnvVars))
-		for idx, envVar := range child.EnvVars {
-			parent.negativeFlag.EnvVars[idx] = strings.ToUpper(parent.InversePrefix) + envVar
+	if len(child.Sources) > 0 {
+		parent.negativeFlag.Sources = make(ValueSources, len(child.Sources))
+		for idx, envVar := range child.GetEnvVars() {
+			parent.negativeFlag.Sources[idx] = EnvSource(strings.ToUpper(parent.InversePrefix) + envVar)
 		}
 	}
 }
diff --git a/flag_impl.go b/flag_impl.go
index a8530d8..a375351 100644
--- a/flag_impl.go
+++ b/flag_impl.go
@@ -82,10 +82,11 @@ type NoConfig struct{}
 type FlagBase[T any, C any, VC ValueCreator[T, C]] struct {
 	Name string // name of the flag
 
-	Category    string   // category of the flag, if any
-	DefaultText string   // default text of the flag for usage purposes
-	FilePaths   []string // file paths to load value from
-	Usage       string   // usage string for help output
+	Category    string // category of the flag, if any
+	DefaultText string // default text of the flag for usage purposes
+	Usage       string // usage string for help output
+
+	Sources ValueSources // sources to load flag value from
 
 	Required   bool // whether the flag is required or not
 	Hidden     bool // whether to hide the flag in help output
@@ -95,7 +96,6 @@ type FlagBase[T any, C any, VC ValueCreator[T, C]] struct {
 	Destination *T // destination pointer for value when set
 
 	Aliases []string // Aliases that are allowed for this flag
-	EnvVars []string // Set of environment variables that can influence this flag's value
 
 	TakesFile bool // whether this flag takes a file argument, mainly for shell completion purposes
 
@@ -132,7 +132,7 @@ func (f *FlagBase[T, C, V]) Apply(set *flag.FlagSet) error {
 	if !f.applied || !f.Persistent {
 		newVal := f.Value
 
-		if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePaths); found {
+		if val, source, found := f.Sources.Get(); found {
 			tmpVal := f.creator.Create(f.Value, new(T), f.Config)
 			if val != "" || reflect.TypeOf(f.Value).Kind() == reflect.String {
 				if err := tmpVal.Set(val); err != nil {
@@ -206,7 +206,13 @@ func (f *FlagBase[T, C, V]) GetUsage() string {
 
 // GetEnvVars returns the env vars for this flag
 func (f *FlagBase[T, C, V]) GetEnvVars() []string {
-	return f.EnvVars
+	var envs []string
+	for _, src := range f.Sources {
+		if esrc, ok := src.(EnvSource); ok {
+			envs = append(envs, string(esrc))
+		}
+	}
+	return envs
 }
 
 // TakesValue returns true if the flag takes a value, otherwise false
diff --git a/flag_value_source.go b/flag_value_source.go
new file mode 100644
index 0000000..a5941d3
--- /dev/null
+++ b/flag_value_source.go
@@ -0,0 +1,76 @@
+package cli
+
+import (
+	"fmt"
+	"os"
+	"strings"
+	"syscall"
+)
+
+// FlagValueSource encapsulates a source which can be used to
+// fetch a value
+type FlagValueSource interface {
+	// Returns the value from the source and if it was found
+	// otherwise returns an empty string & found is set to false
+	Get() (string, bool)
+
+	// The identifier for this source
+	Identifier() string
+}
+
+// ValueSources encapsulates all value sources
+type ValueSources []FlagValueSource
+
+func (v ValueSources) Get() (string, string, bool) {
+	for _, src := range v {
+		if value, found := src.Get(); found {
+			return value, src.Identifier(), true
+		}
+	}
+
+	return "", "", false
+}
+
+// EnvSource encapsulates an env
+type EnvSource string
+
+func (e EnvSource) Get() (string, bool) {
+	envVar := strings.TrimSpace(string(e))
+	return syscall.Getenv(envVar)
+}
+
+func (e EnvSource) Identifier() string {
+	return fmt.Sprintf("environment variable %q", string(e))
+}
+
+// EnvVars is a helper function to encapsulate a bunch
+// of envs together as ValueSources
+func EnvVars(envs ...string) ValueSources {
+	vs := []FlagValueSource{}
+	for _, env := range envs {
+		vs = append(vs, EnvSource(env))
+	}
+	return vs
+}
+
+// FileSource encapsulates an file source
+type FileSource string
+
+func (f FileSource) Get() (string, bool) {
+	data, err := os.ReadFile(string(f))
+	return string(data), err == nil
+}
+
+func (f FileSource) Identifier() string {
+	return fmt.Sprintf("file %q", string(f))
+}
+
+// FilePaths is a helper function to encapsulate a bunch
+// of file paths together as ValueSources
+func FilePaths(paths ...string) ValueSources {
+	vs := []FlagValueSource{}
+	for _, path := range paths {
+		vs = append(vs, FileSource(path))
+	}
+	return vs
+}
diff --git a/godoc-current.txt b/godoc-current.txt
index 6b7a4ee..4cab85a 100644
--- a/godoc-current.txt
+++ b/godoc-current.txt
@@ -793,6 +793,13 @@ type DocGenerationMultiValueFlag interface {
 
 type DurationFlag = FlagBase[time.Duration, NoConfig, durationValue]
 
+type EnvSource string
+    EnvSource encapsulates an env
+
+func (e EnvSource) Get() (string, bool)
+
+func (e EnvSource) Identifier() string
+
 type ErrorFormatter interface {
 	Format(s fmt.State, verb rune)
 }
@@ -818,6 +825,13 @@ type ExitErrHandlerFunc func(cCtx *Context, err error)
     ExitErrHandlerFunc is executed if provided in order to handle exitError
     values returned by Actions and Before/After functions.
 
+type FileSource string
+    FileSource encapsulates an file source
+
+func (f FileSource) Get() (string, bool)
+
+func (f FileSource) Identifier() string
+
 type Flag interface {
 	fmt.Stringer
 
@@ -859,10 +873,11 @@ var VersionFlag Flag = &BoolFlag{
 type FlagBase[T any, C any, VC ValueCreator[T, C]] struct {
 	Name string // name of the flag
 
-	Category    string   // category of the flag, if any
-	DefaultText string   // default text of the flag for usage purposes
-	FilePaths   []string // file paths to load value from
-	Usage       string   // usage string for help output
+	Category    string // category of the flag, if any
+	DefaultText string // default text of the flag for usage purposes
+	Usage       string // usage string for help output
+
+	Sources ValueSources // sources to load flag value from
 
 	Required   bool // whether the flag is required or not
 	Hidden     bool // whether to hide the flag in help output
@@ -872,7 +887,6 @@ type FlagBase[T any, C any, VC ValueCreator[T, C]] struct {
 	Destination *T // destination pointer for value when set
 
 	Aliases []string // Aliases that are allowed for this flag
-	EnvVars []string // Set of environment variables that can influence this flag's value
 
 	TakesFile bool // whether this flag takes a file argument, mainly for shell completion purposes
 
@@ -981,6 +995,16 @@ var FlagStringer FlagStringFunc = stringifyFlag
     FlagStringer converts a flag definition to a string. This is used by help to
     display a flag.
 
+type FlagValueSource interface {
+	// Returns the value from the source and if it was found
+	// otherwise returns an empty string & found is set to false
+	Get() (string, bool)
+
+	// The identifier for this source
+	Identifier() string
+}
+    FlagValueSource encapsulates a source which can be used to fetch a value
+
 type FlagsByName []Flag
     FlagsByName is a slice of Flag.
 
@@ -1182,6 +1206,19 @@ type ValueCreator[T any, C any] interface {
         T specifies the type
         C specifies the config for the type
 
+type ValueSources []FlagValueSource
+    ValueSources encapsulates all value sources
+
+func EnvVars(envs ...string) ValueSources
+    EnvVars is a helper function to encapsulate a bunch of envs together as
+    ValueSources
+
+func FilePaths(paths ...string) ValueSources
+    FilePaths is a helper function to encapsulate a bunch of file paths together
+    as ValueSources
+
+func (v ValueSources) Get() (string, string, bool)
+
 type VisibleFlag interface {
 	Flag
 
diff --git a/internal/build/build.go b/internal/build/build.go
index ca90bc0..61021cb 100644
--- a/internal/build/build.go
+++ b/internal/build/build.go
@@ -107,7 +107,7 @@ func main() {
 				Flags: []cli.Flag{
 					&cli.StringFlag{
 						Name:     "github-token",
-						EnvVars:  []string{"MKDOCS_REMOTE_GITHUB_TOKEN"},
+						Sources:  []cli.FlagValueSource{cli.EnvSource("MKDOCS_REMOTE_GITHUB_TOKEN")},
 						Required: true,
 					},
 				},


Test patch:
diff --git a/context_test.go b/context_test.go
index ffa021a..ed53bca 100644
--- a/context_test.go
+++ b/context_test.go
@@ -217,9 +217,9 @@ func TestContext_IsSet_fromEnv(t *testing.T) {
 	_ = os.Setenv("APP_PASSWORD", "")
 	a := App{
 		Flags: []Flag{
-			&Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}},
-			&StringFlag{Name: "password", Aliases: []string{"p"}, EnvVars: []string{"APP_PASSWORD"}},
-			&Float64Flag{Name: "unparsable", Aliases: []string{"u"}, EnvVars: []string{"APP_UNPARSABLE"}},
+			&Float64Flag{Name: "timeout", Aliases: []string{"t"}, Sources: EnvVars("APP_TIMEOUT_SECONDS")},
+			&StringFlag{Name: "password", Aliases: []string{"p"}, Sources: EnvVars("APP_PASSWORD")},
+			&Float64Flag{Name: "unparsable", Aliases: []string{"u"}, Sources: EnvVars("APP_UNPARSABLE")},
 			&Float64Flag{Name: "no-env-var", Aliases: []string{"n"}},
 		},
 		Action: func(ctx *Context) error {
@@ -500,7 +500,7 @@ func TestCheckRequiredFlags(t *testing.T) {
 		{
 			testCase: "required_and_present_via_env_var",
 			flags: []Flag{
-				&StringFlag{Name: "requiredFlag", Required: true, EnvVars: []string{"REQUIRED_FLAG"}},
+				&StringFlag{Name: "requiredFlag", Required: true, Sources: EnvVars("REQUIRED_FLAG")},
 			},
 			envVarInput: [2]string{"REQUIRED_FLAG", "true"},
 		},
@@ -525,7 +525,7 @@ func TestCheckRequiredFlags(t *testing.T) {
 			testCase: "required_and_optional_and_optional_present_via_env_var",
 			flags: []Flag{
 				&StringFlag{Name: "requiredFlag", Required: true},
-				&StringFlag{Name: "optionalFlag", EnvVars: []string{"OPTIONAL_FLAG"}},
+				&StringFlag{Name: "optionalFlag", Sources: EnvVars("OPTIONAL_FLAG")},
 			},
 			envVarInput:     [2]string{"OPTIONAL_FLAG", "true"},
 			expectedAnError: true,
diff --git a/fish_test.go b/fish_test.go
index 2826ba8..1ab8067 100644
--- a/fish_test.go
+++ b/fish_test.go
@@ -39,7 +39,7 @@ func testApp() *App {
 			Name:    "another-flag",
 			Aliases: []string{"b"},
 			Usage:   "another usage text",
-			EnvVars: []string{"EXAMPLE_VARIABLE_NAME"},
+			Sources: ValueSources{EnvSource("EXAMPLE_VARIABLE_NAME")},
 		},
 		&BoolFlag{
 			Name:   "hidden-flag",
diff --git a/flag_bool_with_inverse_test.go b/flag_bool_with_inverse_test.go
index 8ef0b0b..5fcedb4 100644
--- a/flag_bool_with_inverse_test.go
+++ b/flag_bool_with_inverse_test.go
@@ -198,7 +198,7 @@ func TestBoolWithInverseEnvVars(t *testing.T) {
 		return &cli.BoolWithInverseFlag{
 			BoolFlag: &cli.BoolFlag{
 				Name:    "env",
-				EnvVars: []string{"ENV"},
+				Sources: cli.ValueSources{cli.EnvSource("ENV")},
 			},
 		}
 	}
diff --git a/flag_test.go b/flag_test.go
index 4c1e8cc..6117fe7 100644
--- a/flag_test.go
+++ b/flag_test.go
@@ -113,73 +113,71 @@ func TestFlagsFromEnv(t *testing.T) {
 		flag      Flag
 		errRegexp string
 	}{
-		{"1", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""},
-		{"false", false, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, ""},
-		{"foobar", true, &BoolFlag{Name: "debug", EnvVars: []string{"DEBUG"}}, `could not parse "foobar" as bool value from environment variable "DEBUG" for flag debug: .*`},
-
-		{"1s", 1 * time.Second, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, ""},
-		{"foobar", false, &DurationFlag{Name: "time", EnvVars: []string{"TIME"}}, `could not parse "foobar" as time.Duration value from environment variable "TIME" for flag time: .*`},
-
-		{"1.2", 1.2, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
-		{"1", 1.0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
-		{"foobar", 0, &Float64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as float64 value from environment variable "SECONDS" for flag seconds: .*`},
-
-		{"1", int64(1), &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
-		{"1.2", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int64 value from environment variable "SECONDS" for flag seconds: .*`},
-		{"foobar", 0, &Int64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int64 value from environment variable "SECONDS" for flag seconds: .*`},
-
-		{"1", 1, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
-		{"08", 8, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}, Config: IntegerConfig{Base: 10}}, ""},
-		{"755", 493, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}, Config: IntegerConfig{Base: 8}}, ""},
-		{"deadBEEF", 3735928559, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}, Config: IntegerConfig{Base: 16}}, ""},
-		{"08", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}, Config: IntegerConfig{Base: 0}}, `could not parse "08" as int value from environment variable "SECONDS" for flag seconds: .*`},
-		{"1.2", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as int value from environment variable "SECONDS" for flag seconds: .*`},
-		{"foobar", 0, &IntFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as int value from environment variable "SECONDS" for flag seconds: .*`},
-
-		{"1.0,2", []float64{1, 2}, &Float64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
-		{"foobar", []float64{}, &Float64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as \[\]float64 value from environment variable "SECONDS" for flag seconds: .*`},
-
-		{"1,2", []int{1, 2}, &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
-		{"1.2,2", []int{}, &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as \[\]int value from environment variable "SECONDS" for flag seconds: .*`},
-		{"foobar", []int{}, &IntSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as \[\]int value from environment variable "SECONDS" for flag seconds: .*`},
-
-		{"1,2", []uint{1, 2}, &UintSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
-		{"1.2,2", []uint{}, &UintSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as \[\]uint value from environment variable "SECONDS" for flag seconds: .*`},
-		{"foobar", []uint{}, &UintSliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as \[\]uint value from environment variable "SECONDS" for flag seconds: .*`},
-
-		{"1,2", []int64{1, 2}, &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
-		{"1.2,2", []int64{}, &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as \[\]int64 value from environment variable "SECONDS" for flag seconds: .*`},
-		{"foobar", []int64{}, &Int64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as \[\]int64 value from environment variable "SECONDS" for flag seconds: .*`},
-
-		{"1,2", []uint64{1, 2}, &Uint64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
-		{"1.2,2", []uint64{}, &Uint64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2,2" as \[\]uint64 value from environment variable "SECONDS" for flag seconds: .*`},
-		{"foobar", []uint64{}, &Uint64SliceFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as \[\]uint64 value from environment variable "SECONDS" for flag seconds: .*`},
-
-		{"foo", "foo", &StringFlag{Name: "name", EnvVars: []string{"NAME"}}, ""},
-
-		{"foo,bar", []string{"foo", "bar"}, &StringSliceFlag{Name: "names", EnvVars: []string{"NAMES"}}, ""},
-
-		{"1", uint(1), &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
-		{"08", uint(8), &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}, Config: IntegerConfig{Base: 10}}, ""},
-		{"755", uint(493), &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}, Config: IntegerConfig{Base: 8}}, ""},
-		{"deadBEEF", uint(3735928559), &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}, Config: IntegerConfig{Base: 16}}, ""},
-		{"08", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}, Config: IntegerConfig{Base: 0}}, `could not parse "08" as uint value from environment variable "SECONDS" for flag seconds: .*`},
-		{"1.2", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as uint value from environment variable "SECONDS" for flag seconds: .*`},
-		{"foobar", 0, &UintFlag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint value from environment variable "SECONDS" for flag seconds: .*`},
-
-		{"1", uint64(1), &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, ""},
-		{"08", uint64(8), &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}, Config: IntegerConfig{Base: 10}}, ""},
-		{"755", uint64(493), &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}, Config: IntegerConfig{Base: 8}}, ""},
-		{"deadBEEF", uint64(3735928559), &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}, Config: IntegerConfig{Base: 16}}, ""},
-		{"08", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}, Config: IntegerConfig{Base: 0}}, `could not parse "08" as uint64 value from environment variable "SECONDS" for flag seconds: .*`},
-		{"1.2", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "1.2" as uint64 value from environment variable "SECONDS" for flag seconds: .*`},
-		{"foobar", 0, &Uint64Flag{Name: "seconds", EnvVars: []string{"SECONDS"}}, `could not parse "foobar" as uint64 value from environment variable "SECONDS" for flag seconds: .*`},
-
-		{"foo=bar,empty=", map[string]string{"foo": "bar", "empty": ""}, &StringMapFlag{Name: "names", EnvVars: []string{"NAMES"}}, ""},
-
-		{" foo", "foo", &StringFlag{Name: "names", EnvVars: []string{"NAMES"}, Config: StringConfig{TrimSpace: true}}, ""},
-		{"foo , bar ", []string{"foo", "bar"}, &StringSliceFlag{Name: "names", EnvVars: []string{"NAMES"}, Config: StringConfig{TrimSpace: true}}, ""},
-		{"foo= bar ", map[string]string{"foo": "bar"}, &StringMapFlag{Name: "names", EnvVars: []string{"NAMES"}, Config: StringConfig{TrimSpace: true}}, ""},
+		{"1", true, &BoolFlag{Name: "debug", Sources: EnvVars("DEBUG")}, ""},
+		{"false", false, &BoolFlag{Name: "debug", Sources: EnvVars("DEBUG")}, ""},
+		{"foobar", true, &BoolFlag{Name: "debug", Sources: EnvVars("DEBUG")}, `could not parse "foobar" as bool value from environment variable "DEBUG" for flag debug: .*`},
+
+		{"1s", 1 * time.Second, &DurationFlag{Name: "time", Sources: EnvVars("TIME")}, ""},
+		{"foobar", false, &DurationFlag{Name: "time", Sources: EnvVars("TIME")}, `could not parse "foobar" as time.Duration value from environment variable "TIME" for flag time: .*`},
+
+		{"1.2", 1.2, &Float64Flag{Name: "seconds", Sources: EnvVars("SECONDS")}, ""},
+		{"1", 1.0, &Float64Flag{Name: "seconds", Sources: EnvVars("SECONDS")}, ""},
+		{"foobar", 0, &Float64Flag{Name: "seconds", Sources: EnvVars("SECONDS")}, `could not parse "foobar" as float64 value from environment variable "SECONDS" for flag seconds: .*`},
+
+		{"1", int64(1), &Int64Flag{Name: "seconds", Sources: EnvVars("SECONDS")}, ""},
+		{"1.2", 0, &Int64Flag{Name: "seconds", Sources: EnvVars("SECONDS")}, `could not parse "1.2" as int64 value from environment variable "SECONDS" for flag seconds: .*`},
+		{"foobar", 0, &Int64Flag{Name: "seconds", Sources: EnvVars("SECONDS")}, `could not parse "foobar" as int64 value from environment variable "SECONDS" for flag seconds: .*`},
+
+		{"1", 1, &IntFlag{Name: "seconds", Sources: EnvVars("SECONDS")}, ""},
+		{"08", 8, &IntFlag{Name: "seconds", Sources: EnvVars("SECONDS"), Config: IntegerConfig{Base: 10}}, ""},
+		{"755", 493, &IntFlag{Name: "seconds", Sources: EnvVars("SECONDS"), Config: IntegerConfig{Base: 8}}, ""},
+		{"deadBEEF", 3735928559, &IntFlag{Name: "seconds", Sources: EnvVars("SECONDS"), Config: IntegerConfig{Base: 16}}, ""},
+		{"08", 0, &IntFlag{Name: "seconds", Sources: EnvVars("SECONDS"), Config: IntegerConfig{Base: 0}}, `could not parse "08" as int value from environment variable "SECONDS" for flag seconds: .*`},
+		{"1.2", 0, &IntFlag{Name: "seconds", Sources: EnvVars("SECONDS")}, `could not parse "1.2" as int value from environment variable "SECONDS" for flag seconds: .*`},
+		{"foobar", 0, &IntFlag{Name: "seconds", Sources: EnvVars("SECONDS")}, `could not parse "foobar" as int value from environment variable "SECONDS" for flag seconds: .*`},
+
+		{"1.0,2", []float64{1, 2}, &Float64SliceFlag{Name: "seconds", Sources: EnvVars("SECONDS")}, ""},
+		{"foobar", []float64{}, &Float64SliceFlag{Name: "seconds", Sources: EnvVars("SECONDS")}, `could not parse "foobar" as \[\]float64 value from environment variable "SECONDS" for flag seconds: .*`},
+
+		{"1,2", []int{1, 2}, &IntSliceFlag{Name: "seconds", Sources: EnvVars("SECONDS")}, ""},
+		{"1.2,2", []int{}, &IntSliceFlag{Name: "seconds", Sources: EnvVars("SECONDS")}, `could not parse "1.2,2" as \[\]int value from environment variable "SECONDS" for flag seconds: .*`},
+		{"foobar", []int{}, &IntSliceFlag{Name: "seconds", Sources: EnvVars("SECONDS")}, `could not parse "foobar" as \[\]int value from environment variable "SECONDS" for flag seconds: .*`},
+
+		{"1,2", []uint{1, 2}, &UintSliceFlag{Name: "seconds", Sources: EnvVars("SECONDS")}, ""},
+		{"1.2,2", []uint{}, &UintSliceFlag{Name: "seconds", Sources: EnvVars("SECONDS")}, `could not parse "1.2,2" as \[\]uint value from environment variable "SECONDS" for flag seconds: .*`},
+		{"foobar", []uint{}, &UintSliceFlag{Name: "seconds", Sources: EnvVars("SECONDS")}, `could not parse "foobar" as \[\]uint value from environment variable "SECONDS" for flag seconds: .*`},
+
+		{"1,2", []int64{1, 2}, &Int64SliceFlag{Name: "seconds", Sources: EnvVars("SECONDS")}, ""},
+		{"1.2,2", []int64{}, &Int64SliceFlag{Name: "seconds", Sources: EnvVars("SECONDS")}, `could not parse "1.2,2" as \[\]int64 value from environment variable "SECONDS" for flag seconds: .*`},
+		{"foobar", []int64{}, &Int64SliceFlag{Name: "seconds", Sources: EnvVars("SECONDS")}, `could not parse "foobar" as \[\]int64 value from environment variable "SECONDS" for flag seconds: .*`},
+
+		{"1,2", []uint64{1, 2}, &Uint64SliceFlag{Name: "seconds", Sources: EnvVars("SECONDS")}, ""},
+		{"1.2,2", []uint64{}, &Uint64SliceFlag{Name: "seconds", Sources: EnvVars("SECONDS")}, `could not parse "1.2,2" as \[\]uint64 value from environment variable "SECONDS" for flag seconds: .*`},
+		{"foobar", []uint64{}, &Uint64SliceFlag{Name: "seconds", Sources: EnvVars("SECONDS")}, `could not parse "foobar" as \[\]uint64 value from environment variable "SECONDS" for flag seconds: .*`},
+
+		{"foo", "foo", &StringFlag{Name: "name", Sources: EnvVars("NAME")}, ""},
+
+		{"foo,bar", []string{"foo", "bar"}, &StringSliceFlag{Name: "names", Sources: EnvVars("NAMES")}, ""},
+
+		{"1", uint(1), &UintFlag{Name: "seconds", Sources: EnvVars("SECONDS")}, ""},
+		{"08", uint(8), &UintFlag{Name: "seconds", Sources: EnvVars("SECONDS"), Config: IntegerConfig{Base: 10}}, ""},
+		{"755", uint(493), &UintFlag{Name: "seconds", Sources: EnvVars("SECONDS"), Config: IntegerConfig{Base: 8}}, ""},
+		{"deadBEEF", uint(3735928559), &UintFlag{Name: "seconds", Sources: EnvVars("SECONDS"), Config: IntegerConfig{Base: 16}}, ""},
+		{"08", 0, &UintFlag{Name: "seconds", Sources: EnvVars("SECONDS"), Config: IntegerConfig{Base: 0}}, `could not parse "08" as uint value from environment variable "SECONDS" for flag seconds: .*`},
+		{"1.2", 0, &UintFlag{Name: "seconds", Sources: EnvVars("SECONDS")}, `could not parse "1.2" as uint value from environment variable "SECONDS" for flag seconds: .*`},
+		{"foobar", 0, &UintFlag{Name: "seconds", Sources: EnvVars("SECONDS")}, `could not parse "foobar" as uint value from environment variable "SECONDS" for flag seconds: .*`},
+
+		{"1", uint64(1), &Uint64Flag{Name: "seconds", Sources: EnvVars("SECONDS")}, ""},
+		{"08", uint64(8), &Uint64Flag{Name: "seconds", Sources: EnvVars("SECONDS"), Config: IntegerConfig{Base: 10}}, ""},
+		{"755", uint64(493), &Uint64Flag{Name: "seconds", Sources: EnvVars("SECONDS"), Config: IntegerConfig{Base: 8}}, ""},
+		{"deadBEEF", uint64(3735928559), &Uint64Flag{Name: "seconds", Sources: EnvVars("SECONDS"), Config: IntegerConfig{Base: 16}}, ""},
+		{"08", 0, &Uint64Flag{Name: "seconds", Sources: EnvVars("SECONDS"), Config: IntegerConfig{Base: 0}}, `could not parse "08" as uint64 value from environment variable "SECONDS" for flag seconds: .*`},
+		{"1.2", 0, &Uint64Flag{Name: "seconds", Sources: EnvVars("SECONDS")}, `could not parse "1.2" as uint64 value from environment variable "SECONDS" for flag seconds: .*`},
+		{"foobar", 0, &Uint64Flag{Name: "seconds", Sources: EnvVars("SECONDS")}, `could not parse "foobar" as uint64 value from environment variable "SECONDS" for flag seconds: .*`},
+		{"foo=bar,empty=", map[string]string{"foo": "bar", "empty": ""}, &StringMapFlag{Name: "names", Sources: EnvVars("NAMES")}, ""},
+		{" foo", "foo", &StringFlag{Name: "names", Sources: EnvVars("NAMES"), Config: StringConfig{TrimSpace: true}}, ""},
+		{"foo , bar ", []string{"foo", "bar"}, &StringSliceFlag{Name: "names", Sources: EnvVars("NAMES"), Config: StringConfig{TrimSpace: true}}, ""},
+		{"foo= bar ", map[string]string{"foo": "bar"}, &StringMapFlag{Name: "names", Sources: EnvVars("NAMES"), Config: StringConfig{TrimSpace: true}}, ""},
 	}
 
 	for i, test := range flagTests {
@@ -454,7 +452,7 @@ func TestStringFlagWithEnvVarHelpOutput(t *testing.T) {
 	_ = os.Setenv("APP_FOO", "derp")
 
 	for _, test := range stringFlagTests {
-		fl := &StringFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_FOO"}}
+		fl := &StringFlag{Name: test.name, Aliases: test.aliases, Value: test.value, Sources: EnvVars("APP_FOO")}
 		output := fl.String()
 
 		expectedSuffix := withEnvHint([]string{"APP_FOO"}, "")
@@ -538,7 +536,7 @@ var _ = []struct {
 //
 //	for _, test := range envHintFlagTests {
 //		FlagEnvHinter = test.hinter
-//		fl := StringFlag{Name: test.name, EnvVars: []string{test.env}}
+//		fl := StringFlag{Name: test.name, Sources: ValueSources{test.env}}
 //		output := fl.String()
 //		if output != test.expected {
 //			t.Errorf("%q does not match %q", output, test.expected)
@@ -576,7 +574,7 @@ func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) {
 	_ = os.Setenv("APP_QWWX", "11,4")
 
 	for _, test := range stringSliceFlagTests {
-		fl := &StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_QWWX"}}
+		fl := &StringSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, Sources: EnvVars("APP_QWWX")}
 		output := fl.String()
 
 		expectedSuffix := withEnvHint([]string{"APP_QWWX"}, "")
@@ -599,7 +597,7 @@ func TestStringSliceFlagApply_UsesEnvValues_noDefault(t *testing.T) {
 	defer resetEnv(os.Environ())
 	os.Clearenv()
 	_ = os.Setenv("MY_GOAT", "vincent van goat,scape goat")
-	fl := StringSliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}}
+	fl := StringSliceFlag{Name: "goat", Sources: EnvVars("MY_GOAT")}
 	set := flag.NewFlagSet("test", 0)
 	_ = fl.Apply(set)
 
@@ -613,7 +611,7 @@ func TestStringSliceFlagApply_UsesEnvValues_withDefault(t *testing.T) {
 	os.Clearenv()
 	_ = os.Setenv("MY_GOAT", "vincent van goat,scape goat")
 	val := []string{`some default`, `values here`}
-	fl := StringSliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: val}
+	fl := StringSliceFlag{Name: "goat", Sources: EnvVars("MY_GOAT"), Value: val}
 	set := flag.NewFlagSet("test", 0)
 	_ = fl.Apply(set)
 	err := set.Parse(nil)
@@ -675,7 +673,7 @@ func TestIntFlagWithEnvVarHelpOutput(t *testing.T) {
 	_ = os.Setenv("APP_BAR", "2")
 
 	for _, test := range intFlagTests {
-		fl := &IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}}
+		fl := &IntFlag{Name: test.name, Sources: EnvVars("APP_BAR")}
 		output := fl.String()
 
 		expectedSuffix := withEnvHint([]string{"APP_BAR"}, "")
@@ -737,7 +735,7 @@ func TestInt64FlagWithEnvVarHelpOutput(t *testing.T) {
 	_ = os.Setenv("APP_BAR", "2")
 
 	for _, test := range int64FlagTests {
-		fl := IntFlag{Name: test.name, EnvVars: []string{"APP_BAR"}}
+		fl := IntFlag{Name: test.name, Sources: EnvVars("APP_BAR")}
 		output := fl.String()
 
 		expectedSuffix := withEnvHint([]string{"APP_BAR"}, "")
@@ -788,7 +786,7 @@ func TestUintFlagWithEnvVarHelpOutput(t *testing.T) {
 	_ = os.Setenv("APP_BAR", "2")
 
 	for _, test := range uintFlagTests {
-		fl := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}}
+		fl := UintFlag{Name: test.name, Sources: EnvVars("APP_BAR")}
 		output := fl.String()
 
 		expectedSuffix := withEnvHint([]string{"APP_BAR"}, "")
@@ -839,7 +837,7 @@ func TestUint64FlagWithEnvVarHelpOutput(t *testing.T) {
 	_ = os.Setenv("APP_BAR", "2")
 
 	for _, test := range uint64FlagTests {
-		fl := UintFlag{Name: test.name, EnvVars: []string{"APP_BAR"}}
+		fl := UintFlag{Name: test.name, Sources: EnvVars("APP_BAR")}
 		output := fl.String()
 
 		expectedSuffix := withEnvHint([]string{"APP_BAR"}, "")
@@ -890,7 +888,7 @@ func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) {
 	_ = os.Setenv("APP_BAR", "2h3m6s")
 
 	for _, test := range durationFlagTests {
-		fl := &DurationFlag{Name: test.name, EnvVars: []string{"APP_BAR"}}
+		fl := &DurationFlag{Name: test.name, Sources: EnvVars("APP_BAR")}
 		output := fl.String()
 
 		expectedSuffix := withEnvHint([]string{"APP_BAR"}, "")
@@ -947,7 +945,7 @@ func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) {
 	_ = os.Setenv("APP_SMURF", "42,3")
 
 	for _, test := range intSliceFlagTests {
-		fl := &IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_SMURF"}}
+		fl := &IntSliceFlag{Name: test.name, Aliases: test.aliases, Value: test.value, Sources: EnvVars("APP_SMURF")}
 		output := fl.String()
 
 		expectedSuffix := withEnvHint([]string{"APP_SMURF"}, "")
@@ -971,7 +969,7 @@ func TestIntSliceFlagApply_UsesEnvValues_noDefault(t *testing.T) {
 	os.Clearenv()
 	_ = os.Setenv("MY_GOAT", "1 , 2")
 
-	fl := IntSliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}}
+	fl := IntSliceFlag{Name: "goat", Sources: EnvVars("MY_GOAT")}
 	set := flag.NewFlagSet("test", 0)
 	_ = fl.Apply(set)
 
@@ -985,7 +983,7 @@ func TestIntSliceFlagApply_UsesEnvValues_withDefault(t *testing.T) {
 	os.Clearenv()
 	_ = os.Setenv("MY_GOAT", "1 , 2")
 	val := []int{3, 4}
-	fl := IntSliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: val}
+	fl := IntSliceFlag{Name: "goat", Sources: EnvVars("MY_GOAT"), Value: val}
 	set := flag.NewFlagSet("test", 0)
 	_ = fl.Apply(set)
 	err := set.Parse(nil)
@@ -1087,7 +1085,7 @@ func TestInt64SliceFlagWithEnvVarHelpOutput(t *testing.T) {
 	_ = os.Setenv("APP_SMURF", "42,17179869184")
 
 	for _, test := range int64SliceFlagTests {
-		fl := Int64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}}
+		fl := Int64SliceFlag{Name: test.name, Value: test.value, Sources: EnvVars("APP_SMURF")}
 		output := fl.String()
 
 		expectedSuffix := withEnvHint([]string{"APP_SMURF"}, "")
@@ -1110,7 +1108,7 @@ func TestInt64SliceFlagApply_UsesEnvValues_noDefault(t *testing.T) {
 	defer resetEnv(os.Environ())
 	os.Clearenv()
 	_ = os.Setenv("MY_GOAT", "1 , 2")
-	fl := Int64SliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}}
+	fl := Int64SliceFlag{Name: "goat", Sources: EnvVars("MY_GOAT")}
 	set := flag.NewFlagSet("test", 0)
 	_ = fl.Apply(set)
 
@@ -1124,7 +1122,7 @@ func TestInt64SliceFlagApply_UsesEnvValues_withDefault(t *testing.T) {
 	os.Clearenv()
 	_ = os.Setenv("MY_GOAT", "1 , 2")
 	val := NewInt64Slice(3, 4)
-	fl := Int64SliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: val.Value()}
+	fl := Int64SliceFlag{Name: "goat", Sources: EnvVars("MY_GOAT"), Value: val.Value()}
 	set := flag.NewFlagSet("test", 0)
 	_ = fl.Apply(set)
 	err := set.Parse(nil)
@@ -1242,7 +1240,7 @@ func TestUintSliceFlagWithEnvVarHelpOutput(t *testing.T) {
 	_ = os.Setenv("APP_SMURF", "42,17179869184")
 
 	for _, test := range uintSliceFlagTests {
-		fl := UintSliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}}
+		fl := UintSliceFlag{Name: test.name, Value: test.value, Sources: EnvVars("APP_SMURF")}
 		output := fl.String()
 
 		expectedSuffix := withEnvHint([]string{"APP_SMURF"}, "")
@@ -1266,7 +1264,7 @@ func TestUintSliceFlagApply_UsesEnvValues_noDefault(t *testing.T) {
 	os.Clearenv()
 	_ = os.Setenv("MY_GOAT", "1 , 2")
 
-	fl := UintSliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}}
+	fl := UintSliceFlag{Name: "goat", Sources: EnvVars("MY_GOAT")}
 	set := flag.NewFlagSet("test", 0)
 	_ = fl.Apply(set)
 
@@ -1280,7 +1278,7 @@ func TestUintSliceFlagApply_UsesEnvValues_withDefault(t *testing.T) {
 	os.Clearenv()
 	_ = os.Setenv("MY_GOAT", "1 , 2")
 	val := NewUintSlice(3, 4)
-	fl := UintSliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: val.Value()}
+	fl := UintSliceFlag{Name: "goat", Sources: EnvVars("MY_GOAT"), Value: val.Value()}
 	set := flag.NewFlagSet("test", 0)
 	_ = fl.Apply(set)
 	err := set.Parse(nil)
@@ -1390,7 +1388,7 @@ func TestUint64SliceFlagWithEnvVarHelpOutput(t *testing.T) {
 	_ = os.Setenv("APP_SMURF", "42,17179869184")
 
 	for _, test := range uint64SliceFlagTests {
-		fl := Uint64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}}
+		fl := Uint64SliceFlag{Name: test.name, Value: test.value, Sources: EnvVars("APP_SMURF")}
 		output := fl.String()
 
 		expectedSuffix := withEnvHint([]string{"APP_SMURF"}, "")
@@ -1413,7 +1411,7 @@ func TestUint64SliceFlagApply_UsesEnvValues_noDefault(t *testing.T) {
 	defer resetEnv(os.Environ())
 	os.Clearenv()
 	_ = os.Setenv("MY_GOAT", "1 , 2")
-	fl := Uint64SliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}}
+	fl := Uint64SliceFlag{Name: "goat", Sources: EnvVars("MY_GOAT")}
 	set := flag.NewFlagSet("test", 0)
 	_ = fl.Apply(set)
 
@@ -1427,7 +1425,7 @@ func TestUint64SliceFlagApply_UsesEnvValues_withDefault(t *testing.T) {
 	os.Clearenv()
 	_ = os.Setenv("MY_GOAT", "1 , 2")
 	val := []uint64{3, 4}
-	fl := Uint64SliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: val}
+	fl := Uint64SliceFlag{Name: "goat", Sources: EnvVars("MY_GOAT"), Value: val}
 	set := flag.NewFlagSet("test", 0)
 	_ = fl.Apply(set)
 	err := set.Parse(nil)
@@ -1528,7 +1526,7 @@ func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) {
 	_ = os.Setenv("APP_BAZ", "99.4")
 
 	for _, test := range float64FlagTests {
-		fl := &Float64Flag{Name: test.name, EnvVars: []string{"APP_BAZ"}}
+		fl := &Float64Flag{Name: test.name, Sources: EnvVars("APP_BAZ")}
 		output := fl.String()
 
 		expectedSuffix := withEnvHint([]string{"APP_BAZ"}, "")
@@ -1589,7 +1587,7 @@ func TestFloat64SliceFlagWithEnvVarHelpOutput(t *testing.T) {
 	os.Clearenv()
 	_ = os.Setenv("APP_SMURF", "0.1234,-10.5")
 	for _, test := range float64SliceFlagTests {
-		fl := Float64SliceFlag{Name: test.name, Value: test.value, EnvVars: []string{"APP_SMURF"}}
+		fl := Float64SliceFlag{Name: test.name, Value: test.value, Sources: EnvVars("APP_SMURF")}
 		output := fl.String()
 
 		expectedSuffix := withEnvHint([]string{"APP_SMURF"}, "")
@@ -1613,7 +1611,7 @@ func TestFloat64SliceFlagApply_UsesEnvValues_noDefault(t *testing.T) {
 	os.Clearenv()
 	_ = os.Setenv("MY_GOAT", "1.0 , 2.0")
 
-	fl := Float64SliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}}
+	fl := Float64SliceFlag{Name: "goat", Sources: EnvVars("MY_GOAT")}
 	set := flag.NewFlagSet("test", 0)
 	_ = fl.Apply(set)
 
@@ -1627,7 +1625,7 @@ func TestFloat64SliceFlagApply_UsesEnvValues_withDefault(t *testing.T) {
 	os.Clearenv()
 	_ = os.Setenv("MY_GOAT", "1.0 , 2.0")
 	val := []float64{3.0, 4.0}
-	fl := Float64SliceFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: val}
+	fl := Float64SliceFlag{Name: "goat", Sources: EnvVars("MY_GOAT"), Value: val}
 	set := flag.NewFlagSet("test", 0)
 	_ = fl.Apply(set)
 	err := set.Parse(nil)
@@ -1720,7 +1718,7 @@ func TestParseMultiStringFromEnv(t *testing.T) {
 	_ = os.Setenv("APP_COUNT", "20")
 	_ = (&App{
 		Flags: []Flag{
-			&StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"APP_COUNT"}},
+			&StringFlag{Name: "count", Aliases: []string{"c"}, Sources: EnvVars("APP_COUNT")},
 		},
 		Action: func(ctx *Context) error {
 			if ctx.String("count") != "20" {
@@ -1740,7 +1738,7 @@ func TestParseMultiStringFromEnvCascade(t *testing.T) {
 	_ = os.Setenv("APP_COUNT", "20")
 	_ = (&App{
 		Flags: []Flag{
-			&StringFlag{Name: "count", Aliases: []string{"c"}, EnvVars: []string{"COMPAT_COUNT", "APP_COUNT"}},
+			&StringFlag{Name: "count", Aliases: []string{"c"}, Sources: EnvVars("COMPAT_COUNT", "APP_COUNT")},
 		},
 		Action: func(ctx *Context) error {
 			if ctx.String("count") != "20" {
@@ -1817,7 +1815,7 @@ func TestParseMultiStringSliceWithDestinationAndEnv(t *testing.T) {
 	dest := []string{}
 	_ = (&App{
 		Flags: []Flag{
-			&StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Destination: &dest, EnvVars: []string{"APP_INTERVALS"}},
+			&StringSliceFlag{Name: "serve", Aliases: []string{"s"}, Destination: &dest, Sources: EnvVars("APP_INTERVALS")},
 		},
 		Action: func(ctx *Context) error {
 			expected := []string{"10", "20"}
@@ -1840,7 +1838,7 @@ func TestParseMultiFloat64SliceWithDestinationAndEnv(t *testing.T) {
 	dest := []float64{}
 	_ = (&App{
 		Flags: []Flag{
-			&Float64SliceFlag{Name: "serve", Aliases: []string{"s"}, Destination: &dest, EnvVars: []string{"APP_INTERVALS"}},
+			&Float64SliceFlag{Name: "serve", Aliases: []string{"s"}, Destination: &dest, Sources: EnvVars("APP_INTERVALS")},
 		},
 		Action: func(ctx *Context) error {
 			expected := []float64{10, 20}
@@ -1863,7 +1861,7 @@ func TestParseMultiInt64SliceWithDestinationAndEnv(t *testing.T) {
 	var dest []int64
 	_ = (&App{
 		Flags: []Flag{
-			&Int64SliceFlag{Name: "serve", Aliases: []string{"s"}, Destination: &dest, EnvVars: []string{"APP_INTERVALS"}},
+			&Int64SliceFlag{Name: "serve", Aliases: []string{"s"}, Destination: &dest, Sources: EnvVars("APP_INTERVALS")},
 		},
 		Action: func(ctx *Context) error {
 			expected := []int64{10, 20}
@@ -1886,7 +1884,7 @@ func TestParseMultiIntSliceWithDestinationAndEnv(t *testing.T) {
 	dest := []int{}
 	_ = (&App{
 		Flags: []Flag{
-			&IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Destination: &dest, EnvVars: []string{"APP_INTERVALS"}},
+			&IntSliceFlag{Name: "serve", Aliases: []string{"s"}, Destination: &dest, Sources: EnvVars("APP_INTERVALS")},
 		},
 		Action: func(ctx *Context) error {
 			expected := []int{10, 20}
@@ -1925,7 +1923,7 @@ func TestParseMultiStringSliceFromEnv(t *testing.T) {
 
 	_ = (&App{
 		Flags: []Flag{
-			&StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []string{}, EnvVars: []string{"APP_INTERVALS"}},
+			&StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []string{}, Sources: EnvVars("APP_INTERVALS")},
 		},
 		Action: func(ctx *Context) error {
 			if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
@@ -1946,7 +1944,7 @@ func TestParseMultiStringSliceFromEnvWithDefaults(t *testing.T) {
 
 	_ = (&App{
 		Flags: []Flag{
-			&StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []string{"1", "2", "5"}, EnvVars: []string{"APP_INTERVALS"}},
+			&StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []string{"1", "2", "5"}, Sources: EnvVars("APP_INTERVALS")},
 		},
 		Action: func(ctx *Context) error {
 			if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
@@ -1967,7 +1965,7 @@ func TestParseMultiStringSliceFromEnvCascade(t *testing.T) {
 
 	_ = (&App{
 		Flags: []Flag{
-			&StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []string{}, EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}},
+			&StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []string{}, Sources: EnvVars("COMPAT_INTERVALS", "APP_INTERVALS")},
 		},
 		Action: func(ctx *Context) error {
 			if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
@@ -1988,7 +1986,7 @@ func TestParseMultiStringSliceFromEnvCascadeWithDefaults(t *testing.T) {
 
 	_ = (&App{
 		Flags: []Flag{
-			&StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []string{"1", "2", "5"}, EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}},
+			&StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []string{"1", "2", "5"}, Sources: EnvVars("COMPAT_INTERVALS", "APP_INTERVALS")},
 		},
 		Action: func(ctx *Context) error {
 			if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
@@ -2010,7 +2008,7 @@ func TestParseMultiStringSliceFromEnvWithDestination(t *testing.T) {
 	dest := []string{}
 	_ = (&App{
 		Flags: []Flag{
-			&StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Destination: &dest, EnvVars: []string{"APP_INTERVALS"}},
+			&StringSliceFlag{Name: "intervals", Aliases: []string{"i"}, Destination: &dest, Sources: EnvVars("APP_INTERVALS")},
 		},
 		Action: func(*Context) error {
 			if !reflect.DeepEqual(dest, []string{"20", "30", "40"}) {
@@ -2065,7 +2063,7 @@ func TestParseMultiIntFromEnv(t *testing.T) {
 	_ = os.Setenv("APP_TIMEOUT_SECONDS", "10")
 	_ = (&App{
 		Flags: []Flag{
-			&IntFlag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}},
+			&IntFlag{Name: "timeout", Aliases: []string{"t"}, Sources: EnvVars("APP_TIMEOUT_SECONDS")},
 		},
 		Action: func(ctx *Context) error {
 			if ctx.Int("timeout") != 10 {
@@ -2085,7 +2083,7 @@ func TestParseMultiIntFromEnvCascade(t *testing.T) {
 	_ = os.Setenv("APP_TIMEOUT_SECONDS", "10")
 	_ = (&App{
 		Flags: []Flag{
-			&IntFlag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS"}},
+			&IntFlag{Name: "timeout", Aliases: []string{"t"}, Sources: EnvVars("COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS")},
 		},
 		Action: func(ctx *Context) error {
 			if ctx.Int("timeout") != 10 {
@@ -2157,7 +2155,7 @@ func TestParseMultiIntSliceFromEnv(t *testing.T) {
 
 	_ = (&App{
 		Flags: []Flag{
-			&IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []int{}, EnvVars: []string{"APP_INTERVALS"}},
+			&IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []int{}, Sources: EnvVars("APP_INTERVALS")},
 		},
 		Action: func(ctx *Context) error {
 			if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) {
@@ -2178,7 +2176,7 @@ func TestParseMultiIntSliceFromEnvWithDefaults(t *testing.T) {
 
 	_ = (&App{
 		Flags: []Flag{
-			&IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []int{1, 2, 5}, EnvVars: []string{"APP_INTERVALS"}},
+			&IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []int{1, 2, 5}, Sources: EnvVars("APP_INTERVALS")},
 		},
 		Action: func(ctx *Context) error {
 			if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) {
@@ -2199,7 +2197,7 @@ func TestParseMultiIntSliceFromEnvCascade(t *testing.T) {
 
 	_ = (&App{
 		Flags: []Flag{
-			&IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []int{}, EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}},
+			&IntSliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []int{}, Sources: EnvVars("COMPAT_INTERVALS", "APP_INTERVALS")},
 		},
 		Action: func(ctx *Context) error {
 			if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) {
@@ -2237,7 +2235,7 @@ func TestParseMultiInt64SliceFromEnv(t *testing.T) {
 
 	_ = (&App{
 		Flags: []Flag{
-			&Int64SliceFlag{Name: "intervals", Aliases: []string{"i"}, EnvVars: []string{"APP_INTERVALS"}},
+			&Int64SliceFlag{Name: "intervals", Aliases: []string{"i"}, Sources: EnvVars("APP_INTERVALS")},
 		},
 		Action: func(ctx *Context) error {
 			if !reflect.DeepEqual(ctx.Int64Slice("intervals"), []int64{20, 30, 17179869184}) {
@@ -2258,7 +2256,7 @@ func TestParseMultiInt64SliceFromEnvCascade(t *testing.T) {
 
 	_ = (&App{
 		Flags: []Flag{
-			&Int64SliceFlag{Name: "intervals", Aliases: []string{"i"}, EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}},
+			&Int64SliceFlag{Name: "intervals", Aliases: []string{"i"}, Sources: EnvVars("COMPAT_INTERVALS", "APP_INTERVALS")},
 		},
 		Action: func(ctx *Context) error {
 			if !reflect.DeepEqual(ctx.Int64Slice("intervals"), []int64{20, 30, 17179869184}) {
@@ -2313,7 +2311,7 @@ func TestParseMultiFloat64FromEnv(t *testing.T) {
 	_ = os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
 	_ = (&App{
 		Flags: []Flag{
-			&Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}},
+			&Float64Flag{Name: "timeout", Aliases: []string{"t"}, Sources: EnvVars("APP_TIMEOUT_SECONDS")},
 		},
 		Action: func(ctx *Context) error {
 			if ctx.Float64("timeout") != 15.5 {
@@ -2333,7 +2331,7 @@ func TestParseMultiFloat64FromEnvCascade(t *testing.T) {
 	_ = os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
 	_ = (&App{
 		Flags: []Flag{
-			&Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS"}},
+			&Float64Flag{Name: "timeout", Aliases: []string{"t"}, Sources: EnvVars("COMPAT_TIMEOUT_SECONDS", "APP_TIMEOUT_SECONDS")},
 		},
 		Action: func(ctx *Context) error {
 			if ctx.Float64("timeout") != 15.5 {
@@ -2354,7 +2352,7 @@ func TestParseMultiFloat64SliceFromEnv(t *testing.T) {
 
 	_ = (&App{
 		Flags: []Flag{
-			&Float64SliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []float64{}, EnvVars: []string{"APP_INTERVALS"}},
+			&Float64SliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []float64{}, Sources: EnvVars("APP_INTERVALS")},
 		},
 		Action: func(ctx *Context) error {
 			if !reflect.DeepEqual(ctx.Float64Slice("intervals"), []float64{0.1, -10.5}) {
@@ -2375,7 +2373,7 @@ func TestParseMultiFloat64SliceFromEnvCascade(t *testing.T) {
 
 	_ = (&App{
 		Flags: []Flag{
-			&Float64SliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []float64{}, EnvVars: []string{"COMPAT_INTERVALS", "APP_INTERVALS"}},
+			&Float64SliceFlag{Name: "intervals", Aliases: []string{"i"}, Value: []float64{}, Sources: EnvVars("COMPAT_INTERVALS", "APP_INTERVALS")},
 		},
 		Action: func(ctx *Context) error {
 			if !reflect.DeepEqual(ctx.Float64Slice("intervals"), []float64{0.1234, -10.5}) {
@@ -2454,7 +2452,7 @@ func TestParseMultiBoolFromEnv(t *testing.T) {
 	_ = os.Setenv("APP_DEBUG", "1")
 	_ = (&App{
 		Flags: []Flag{
-			&BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"APP_DEBUG"}},
+			&BoolFlag{Name: "debug", Aliases: []string{"d"}, Sources: EnvVars("APP_DEBUG")},
 		},
 		Action: func(ctx *Context) error {
 			if ctx.Bool("debug") != true {
@@ -2474,7 +2472,7 @@ func TestParseMultiBoolFromEnvCascade(t *testing.T) {
 	_ = os.Setenv("APP_DEBUG", "1")
 	_ = (&App{
 		Flags: []Flag{
-			&BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"COMPAT_DEBUG", "APP_DEBUG"}},
+			&BoolFlag{Name: "debug", Aliases: []string{"d"}, Sources: EnvVars("COMPAT_DEBUG", "APP_DEBUG")},
 		},
 		Action: func(ctx *Context) error {
 			if ctx.Bool("debug") != true {
@@ -2505,7 +2503,7 @@ func TestParseBoolFromEnv(t *testing.T) {
 		_ = os.Setenv("DEBUG", test.input)
 		_ = (&App{
 			Flags: []Flag{
-				&BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"DEBUG"}},
+				&BoolFlag{Name: "debug", Aliases: []string{"d"}, Sources: EnvVars("DEBUG")},
 			},
 			Action: func(ctx *Context) error {
 				if ctx.Bool("debug") != test.output {
@@ -2883,7 +2881,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) {
 	cases := []*flagDefaultTestCaseWithEnv{
 		{
 			name:    "stringSlice",
-			flag:    &StringSliceFlag{Name: "flag", Value: []string{"default1", "default2"}, EnvVars: []string{"ssflag"}},
+			flag:    &StringSliceFlag{Name: "flag", Value: []string{"default1", "default2"}, Sources: EnvVars("ssflag")},
 			toParse: []string{"--flag", "parsed"},
 			expect:  `--flag value [ --flag value ]	(default: "default1", "default2")` + withEnvHint([]string{"ssflag"}, ""),
 			environ: map[string]string{
@@ -2892,7 +2890,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) {
 		},
 		{
 			name:    "float64Slice",
-			flag:    &Float64SliceFlag{Name: "flag", Value: []float64{1.1, 2.2}, EnvVars: []string{"fsflag"}},
+			flag:    &Float64SliceFlag{Name: "flag", Value: []float64{1.1, 2.2}, Sources: EnvVars("fsflag")},
 			toParse: []string{"--flag", "13.3"},
 			expect:  `--flag value [ --flag value ]	(default: 1.1, 2.2)` + withEnvHint([]string{"fsflag"}, ""),
 			environ: map[string]string{
@@ -2901,7 +2899,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) {
 		},
 		{
 			name:    "int64Slice",
-			flag:    &Int64SliceFlag{Name: "flag", Value: []int64{1, 2}, EnvVars: []string{"isflag"}},
+			flag:    &Int64SliceFlag{Name: "flag", Value: []int64{1, 2}, Sources: EnvVars("isflag")},
 			toParse: []string{"--flag", "13"},
 			expect:  `--flag value [ --flag value ]	(default: 1, 2)` + withEnvHint([]string{"isflag"}, ""),
 			environ: map[string]string{
@@ -2910,7 +2908,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) {
 		},
 		{
 			name:    "intSlice",
-			flag:    &IntSliceFlag{Name: "flag", Value: []int{1, 2}, EnvVars: []string{"isflag"}},
+			flag:    &IntSliceFlag{Name: "flag", Value: []int{1, 2}, Sources: EnvVars("isflag")},
 			toParse: []string{"--flag", "13"},
 			expect:  `--flag value [ --flag value ]	(default: 1, 2)` + withEnvHint([]string{"isflag"}, ""),
 			environ: map[string]string{
@@ -2919,7 +2917,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) {
 		},
 		{
 			name:    "uint64Slice",
-			flag:    &Uint64SliceFlag{Name: "flag", Value: []uint64{1, 2}, EnvVars: []string{"uisflag"}},
+			flag:    &Uint64SliceFlag{Name: "flag", Value: []uint64{1, 2}, Sources: EnvVars("uisflag")},
 			toParse: []string{"--flag", "13"},
 			expect:  `--flag value [ --flag value ]	(default: 1, 2)` + withEnvHint([]string{"uisflag"}, ""),
 			environ: map[string]string{
@@ -2928,7 +2926,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) {
 		},
 		{
 			name:    "uintSlice",
-			flag:    &UintSliceFlag{Name: "flag", Value: []uint{1, 2}, EnvVars: []string{"uisflag"}},
+			flag:    &UintSliceFlag{Name: "flag", Value: []uint{1, 2}, Sources: EnvVars("uisflag")},
 			toParse: []string{"--flag", "13"},
 			expect:  `--flag value [ --flag value ]	(default: 1, 2)` + withEnvHint([]string{"uisflag"}, ""),
 			environ: map[string]string{
@@ -2937,7 +2935,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) {
 		},
 		{
 			name:    "string",
-			flag:    &StringFlag{Name: "flag", Value: "default", EnvVars: []string{"uflag"}},
+			flag:    &StringFlag{Name: "flag", Value: "default", Sources: EnvVars("uflag")},
 			toParse: []string{"--flag", "parsed"},
 			expect:  `--flag value	(default: "default")` + withEnvHint([]string{"uflag"}, ""),
 			environ: map[string]string{
@@ -2946,7 +2944,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) {
 		},
 		{
 			name:    "bool",
-			flag:    &BoolFlag{Name: "flag", Value: true, EnvVars: []string{"uflag"}},
+			flag:    &BoolFlag{Name: "flag", Value: true, Sources: EnvVars("uflag")},
 			toParse: []string{"--flag", "false"},
 			expect:  `--flag	(default: true)` + withEnvHint([]string{"uflag"}, ""),
 			environ: map[string]string{
@@ -2955,7 +2953,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) {
 		},
 		{
 			name:    "uint64",
-			flag:    &Uint64Flag{Name: "flag", Value: 1, EnvVars: []string{"uflag"}},
+			flag:    &Uint64Flag{Name: "flag", Value: 1, Sources: EnvVars("uflag")},
 			toParse: []string{"--flag", "13"},
 			expect:  `--flag value	(default: 1)` + withEnvHint([]string{"uflag"}, ""),
 			environ: map[string]string{
@@ -2964,7 +2962,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) {
 		},
 		{
 			name:    "uint",
-			flag:    &UintFlag{Name: "flag", Value: 1, EnvVars: []string{"uflag"}},
+			flag:    &UintFlag{Name: "flag", Value: 1, Sources: EnvVars("uflag")},
 			toParse: []string{"--flag", "13"},
 			expect:  `--flag value	(default: 1)` + withEnvHint([]string{"uflag"}, ""),
 			environ: map[string]string{
@@ -2973,7 +2971,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) {
 		},
 		{
 			name:    "int64",
-			flag:    &Int64Flag{Name: "flag", Value: 1, EnvVars: []string{"uflag"}},
+			flag:    &Int64Flag{Name: "flag", Value: 1, Sources: EnvVars("uflag")},
 			toParse: []string{"--flag", "13"},
 			expect:  `--flag value	(default: 1)` + withEnvHint([]string{"uflag"}, ""),
 			environ: map[string]string{
@@ -2982,7 +2980,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) {
 		},
 		{
 			name:    "int",
-			flag:    &IntFlag{Name: "flag", Value: 1, EnvVars: []string{"uflag"}},
+			flag:    &IntFlag{Name: "flag", Value: 1, Sources: EnvVars("uflag")},
 			toParse: []string{"--flag", "13"},
 			expect:  `--flag value	(default: 1)` + withEnvHint([]string{"uflag"}, ""),
 			environ: map[string]string{
@@ -2991,7 +2989,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) {
 		},
 		{
 			name:    "duration",
-			flag:    &DurationFlag{Name: "flag", Value: time.Second, EnvVars: []string{"uflag"}},
+			flag:    &DurationFlag{Name: "flag", Value: time.Second, Sources: EnvVars("uflag")},
 			toParse: []string{"--flag", "2m"},
 			expect:  `--flag value	(default: 1s)` + withEnvHint([]string{"uflag"}, ""),
 			environ: map[string]string{
@@ -3000,7 +2998,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) {
 		},
 		{
 			name:    "timestamp",
-			flag:    &TimestampFlag{Name: "flag", Value: ts, Config: TimestampConfig{Layout: time.RFC3339}, EnvVars: []string{"tflag"}},
+			flag:    &TimestampFlag{Name: "flag", Value: ts, Config: TimestampConfig{Layout: time.RFC3339}, Sources: EnvVars("tflag")},
 			toParse: []string{"--flag", "2006-11-02T15:04:05Z"},
 			expect:  `--flag value	(default: 2005-01-02 15:04:05 +0000 UTC)` + withEnvHint([]string{"tflag"}, ""),
 			environ: map[string]string{
@@ -3009,7 +3007,7 @@ func TestFlagDefaultValueWithEnv(t *testing.T) {
 		},
 		{
 			name:    "stringMap",
-			flag:    &StringMapFlag{Name: "flag", Value: map[string]string{"default1": "default2"}, EnvVars: []string{"ssflag"}},
+			flag:    &StringMapFlag{Name: "flag", Value: map[string]string{"default1": "default2"}, Sources: EnvVars("ssflag")},
 			toParse: []string{"--flag", "parsed="},
 			expect:  `--flag value [ --flag value ]	(default: default1="default2")` + withEnvHint([]string{"ssflag"}, ""),
 			environ: map[string]string{
@@ -3217,7 +3215,7 @@ func TestStringMapFlagWithEnvVarHelpOutput(t *testing.T) {
 	_ = os.Setenv("APP_QWWX", "11,4")
 
 	for _, test := range stringMapFlagTests {
-		fl := &StringMapFlag{Name: test.name, Aliases: test.aliases, Value: test.value, EnvVars: []string{"APP_QWWX"}}
+		fl := &StringMapFlag{Name: test.name, Aliases: test.aliases, Value: test.value, Sources: EnvVars("APP_QWWX")}
 		output := fl.String()
 
 		expectedSuffix := withEnvHint([]string{"APP_QWWX"}, "")
@@ -3241,7 +3239,7 @@ func TestStringMapFlagApply_UsesEnvValues_noDefault(t *testing.T) {
 	os.Clearenv()
 	_ = os.Setenv("MY_GOAT", "vincent van goat=scape goat")
 	var val map[string]string
-	fl := StringMapFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: val}
+	fl := StringMapFlag{Name: "goat", Sources: EnvVars("MY_GOAT"), Value: val}
 	set := flag.NewFlagSet("test", 0)
 	_ = fl.Apply(set)
 
@@ -3256,7 +3254,7 @@ func TestStringMapFlagApply_UsesEnvValues_withDefault(t *testing.T) {
 	os.Clearenv()
 	_ = os.Setenv("MY_GOAT", "vincent van goat=scape goat")
 	val := map[string]string{`some default`: `values here`}
-	fl := StringMapFlag{Name: "goat", EnvVars: []string{"MY_GOAT"}, Value: val}
+	fl := StringMapFlag{Name: "goat", Sources: EnvVars("MY_GOAT"), Value: val}
 	set := flag.NewFlagSet("test", 0)
 	_ = fl.Apply(set)
 	err := set.Parse(nil)
diff --git a/flag_value_source_test.go b/flag_value_source_test.go
new file mode 100644
index 0000000..7b74eab
--- /dev/null
+++ b/flag_value_source_test.go
@@ -0,0 +1,45 @@
+package cli
+
+import (
+	"fmt"
+	"os"
+	"testing"
+)
+
+func TestEnvSource(t *testing.T) {
+
+	t.Setenv("foo", "bar")
+
+	s := EnvSource("foo_1")
+	_, ok := s.Get()
+	expect(t, ok, false)
+
+	s = EnvSource("foo")
+	str, ok := s.Get()
+	expect(t, ok, true)
+	expect(t, str, "bar")
+
+	t.Setenv("myfoo", "mybar")
+
+	source := EnvVars("foo1", "myfoo")
+	str, id, ok := source.Get()
+	expect(t, ok, true)
+	expect(t, str, "mybar")
+	expect(t, id, fmt.Sprintf("environment variable %q", "myfoo"))
+}
+
+func TestFileSource(t *testing.T) {
+
+	f := FileSource("junk_file_name")
+	_, ok := f.Get()
+	expect(t, ok, false)
+
+	expect(t, os.WriteFile("some_file_name_1", []byte("Hello"), 0644), nil)
+	defer os.Remove("some_file_name_1")
+
+	sources := FilePaths("junk_file_name", "some_file_name_1")
+	s, id, ok := sources.Get()
+	expect(t, ok, true)
+	expect(t, s, "Hello")
+	expect(t, id, fmt.Sprintf("file %q", "some_file_name_1"))
+}
diff --git a/testdata/godoc-v3.x.txt b/testdata/godoc-v3.x.txt
index 6b7a4ee..4cab85a 100644
--- a/testdata/godoc-v3.x.txt
+++ b/testdata/godoc-v3.x.txt
@@ -793,6 +793,13 @@ type DocGenerationMultiValueFlag interface {
 
 type DurationFlag = FlagBase[time.Duration, NoConfig, durationValue]
 
+type EnvSource string
+    EnvSource encapsulates an env
+
+func (e EnvSource) Get() (string, bool)
+
+func (e EnvSource) Identifier() string
+
 type ErrorFormatter interface {
 	Format(s fmt.State, verb rune)
 }
@@ -818,6 +825,13 @@ type ExitErrHandlerFunc func(cCtx *Context, err error)
     ExitErrHandlerFunc is executed if provided in order to handle exitError
     values returned by Actions and Before/After functions.
 
+type FileSource string
+    FileSource encapsulates an file source
+
+func (f FileSource) Get() (string, bool)
+
+func (f FileSource) Identifier() string
+
 type Flag interface {
 	fmt.Stringer
 
@@ -859,10 +873,11 @@ var VersionFlag Flag = &BoolFlag{
 type FlagBase[T any, C any, VC ValueCreator[T, C]] struct {
 	Name string // name of the flag
 
-	Category    string   // category of the flag, if any
-	DefaultText string   // default text of the flag for usage purposes
-	FilePaths   []string // file paths to load value from
-	Usage       string   // usage string for help output
+	Category    string // category of the flag, if any
+	DefaultText string // default text of the flag for usage purposes
+	Usage       string // usage string for help output
+
+	Sources ValueSources // sources to load flag value from
 
 	Required   bool // whether the flag is required or not
 	Hidden     bool // whether to hide the flag in help output
@@ -872,7 +887,6 @@ type FlagBase[T any, C any, VC ValueCreator[T, C]] struct {
 	Destination *T // destination pointer for value when set
 
 	Aliases []string // Aliases that are allowed for this flag
-	EnvVars []string // Set of environment variables that can influence this flag's value
 
 	TakesFile bool // whether this flag takes a file argument, mainly for shell completion purposes
 
@@ -981,6 +995,16 @@ var FlagStringer FlagStringFunc = stringifyFlag
     FlagStringer converts a flag definition to a string. This is used by help to
     display a flag.
 
+type FlagValueSource interface {
+	// Returns the value from the source and if it was found
+	// otherwise returns an empty string & found is set to false
+	Get() (string, bool)
+
+	// The identifier for this source
+	Identifier() string
+}
+    FlagValueSource encapsulates a source which can be used to fetch a value
+
 type FlagsByName []Flag
     FlagsByName is a slice of Flag.
 
@@ -1182,6 +1206,19 @@ type ValueCreator[T any, C any] interface {
         T specifies the type
         C specifies the config for the type
 
+type ValueSources []FlagValueSource
+    ValueSources encapsulates all value sources
+
+func EnvVars(envs ...string) ValueSources
+    EnvVars is a helper function to encapsulate a bunch of envs together as
+    ValueSources
+
+func FilePaths(paths ...string) ValueSources
+    FilePaths is a helper function to encapsulate a bunch of file paths together
+    as ValueSources
+
+func (v ValueSources) Get() (string, string, bool)
+
 type VisibleFlag interface {
 	Flag