Repository: urfave/cli (go)

Issue:
Support aliases in .yml files (altsrc.InitConfigFileInputSourceFunc)
## Checklist

* [x] Are you running the latest v2 release? The list of releases is [here](https://github.com/urfave/cli/releases).
* [x] Did you check the manual for your release? The v2 manual is [here](https://github.com/urfave/cli/blob/main/docs/v2/manual.md).
* [ ] Did you perform a search about this feature? Here's the [Github guide](https://help.github.com/en/github/managing-your-work-on-github/using-search-to-filter-issues-and-pull-requests) about searching.

## What problem does this solve?

Thank you for this project. It's fantastic. I've been a long time user and I'm very happy.

I just added the ability to use the `Aliases` in my yml file when loading through `altsrc` (see https://github.com/binwiederhier/ntfy/commit/91594faf288d13bd128067436e11345dc7a9d583#diff-d1253876bc7a1b30f67245948f61e61430af2f3f0bf72780e3c0314a24d152deR43-R50). I was wondering if that was something you'd be interested in adding as a feature. 

If not, I'm alright too and you can close this ticket.

## Solution description

Example:

``` go
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-http", Aliases: []string{"listen_http", "l"},  ...}),
```

Should be loadable via the .yml file:

``` yaml
# Currently supported:
listen-http: ":80"

# Would also be supported with the suggested change (see Aliases above):
listen_http: ":80"
l: ":80"
```


Pull request:
Add support for alias in YAMLs
<!--
  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:
 -->

- bug

## What this PR does / why we need it:

Currently, one can not process aliased flags via yaml config file because only the original name is used in the flag lookup.

<!--
  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.
-->

## Which issue(s) this PR fixes:

Fixes #1394

<!--
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:
**First time trying to create a PR.
Supports all flag types defined in flag.go**

<!--
   Is there any particular feedback you would / wouldn't like?
   Which parts of the code should reviewers focus on?
-->

## Testing

Unit Tested

<!--
  Describe how you tested this change.
-->

## Release Notes

<!--
  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
Supports the setting of flags through an alias name via yaml config file
```


Patch:
diff --git a/altsrc/flag.go b/altsrc/flag.go
index 9801644..1132ded 100644
--- a/altsrc/flag.go
+++ b/altsrc/flag.go
@@ -64,15 +64,22 @@ func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(cCtx *c
 
 // ApplyInputSourceValue applies a generic value to the flagSet if required
 func (f *GenericFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
-	if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.isSet(f.GenericFlag.Name) {
-		value, err := isc.Generic(f.GenericFlag.Name)
+	if f.set == nil || cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars) {
+		return nil
+	}
+	for _, name := range f.GenericFlag.Names() {
+		if !isc.isSet(name) {
+			continue
+		}
+		value, err := isc.Generic(name)
 		if err != nil {
 			return err
 		}
-		if value != nil {
-			for _, name := range f.Names() {
-				_ = f.set.Set(name, value.String())
-			}
+		if value == nil {
+			continue
+		}
+		for _, n := range f.Names() {
+			_ = f.set.Set(n, value.String())
 		}
 	}
 
@@ -81,19 +88,27 @@ func (f *GenericFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceCo
 
 // ApplyInputSourceValue applies a StringSlice value to the flagSet if required
 func (f *StringSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
-	if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.isSet(f.StringSliceFlag.Name) {
-		value, err := isc.StringSlice(f.StringSliceFlag.Name)
+	if f.set == nil || cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars) {
+		return nil
+	}
+	for _, name := range f.StringSliceFlag.Names() {
+		if !isc.isSet(name) {
+			continue
+		}
+		value, err := isc.StringSlice(name)
 		if err != nil {
 			return err
 		}
-		if value != nil {
-			var sliceValue cli.StringSlice = *(cli.NewStringSlice(value...))
-			for _, name := range f.Names() {
-				underlyingFlag := f.set.Lookup(name)
-				if underlyingFlag != nil {
-					underlyingFlag.Value = &sliceValue
-				}
+		if value == nil {
+			continue
+		}
+		var sliceValue = *(cli.NewStringSlice(value...))
+		for _, n := range f.Names() {
+			underlyingFlag := f.set.Lookup(n)
+			if underlyingFlag == nil {
+				continue
 			}
+			underlyingFlag.Value = &sliceValue
 		}
 	}
 	return nil
@@ -101,19 +116,27 @@ func (f *StringSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSour
 
 // ApplyInputSourceValue applies a IntSlice value if required
 func (f *IntSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
-	if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.isSet(f.IntSliceFlag.Name) {
-		value, err := isc.IntSlice(f.IntSliceFlag.Name)
+	if f.set == nil || cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars) {
+		return nil
+	}
+	for _, name := range f.IntSliceFlag.Names() {
+		if !isc.isSet(name) {
+			continue
+		}
+		value, err := isc.IntSlice(name)
 		if err != nil {
 			return err
 		}
-		if value != nil {
-			var sliceValue cli.IntSlice = *(cli.NewIntSlice(value...))
-			for _, name := range f.Names() {
-				underlyingFlag := f.set.Lookup(name)
-				if underlyingFlag != nil {
-					underlyingFlag.Value = &sliceValue
-				}
+		if value == nil {
+			continue
+		}
+		var sliceValue = *(cli.NewIntSlice(value...))
+		for _, n := range f.Names() {
+			underlyingFlag := f.set.Lookup(n)
+			if underlyingFlag == nil {
+				continue
 			}
+			underlyingFlag.Value = &sliceValue
 		}
 	}
 	return nil
@@ -121,13 +144,19 @@ func (f *IntSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceC
 
 // ApplyInputSourceValue applies a Bool value to the flagSet if required
 func (f *BoolFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
-	if f.set != nil && !cCtx.IsSet(f.Name) && !isEnvVarSet(f.EnvVars) && isc.isSet(f.BoolFlag.Name) {
-		value, err := isc.Bool(f.BoolFlag.Name)
+	if f.set == nil || cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars) {
+		return nil
+	}
+	for _, name := range f.BoolFlag.Names() {
+		if !isc.isSet(name) {
+			continue
+		}
+		value, err := isc.Bool(name)
 		if err != nil {
 			return err
 		}
-		for _, name := range f.Names() {
-			_ = f.set.Set(name, strconv.FormatBool(value))
+		for _, n := range f.Names() {
+			_ = f.set.Set(n, strconv.FormatBool(value))
 		}
 	}
 	return nil
@@ -135,15 +164,19 @@ func (f *BoolFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceConte
 
 // ApplyInputSourceValue applies a String value to the flagSet if required
 func (f *StringFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
-	if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.isSet(f.StringFlag.Name) {
-		value, err := isc.String(f.StringFlag.Name)
+	if f.set == nil || cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars) {
+		return nil
+	}
+	for _, name := range f.StringFlag.Names() {
+		if !isc.isSet(name) {
+			continue
+		}
+		value, err := isc.String(name)
 		if err != nil {
 			return err
 		}
-		if value != "" {
-			for _, name := range f.Names() {
-				_ = f.set.Set(name, value)
-			}
+		for _, n := range f.Names() {
+			_ = f.set.Set(n, value)
 		}
 	}
 	return nil
@@ -151,25 +184,29 @@ func (f *StringFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceCon
 
 // ApplyInputSourceValue applies a Path value to the flagSet if required
 func (f *PathFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
-	if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.isSet(f.PathFlag.Name) {
-		value, err := isc.String(f.PathFlag.Name)
+	if f.set == nil || cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars) {
+		return nil
+	}
+	for _, name := range f.PathFlag.Names() {
+		if !isc.isSet(name) {
+			continue
+		}
+		value, err := isc.String(name)
 		if err != nil {
 			return err
 		}
-		if value != "" {
-			for _, name := range f.Names() {
-
-				if !filepath.IsAbs(value) && isc.Source() != "" {
-					basePathAbs, err := filepath.Abs(isc.Source())
-					if err != nil {
-						return err
-					}
-
-					value = filepath.Join(filepath.Dir(basePathAbs), value)
+		if value == "" {
+			continue
+		}
+		for _, n := range f.Names() {
+			if !filepath.IsAbs(value) && isc.Source() != "" {
+				basePathAbs, err := filepath.Abs(isc.Source())
+				if err != nil {
+					return err
 				}
-
-				_ = f.set.Set(name, value)
+				value = filepath.Join(filepath.Dir(basePathAbs), value)
 			}
+			_ = f.set.Set(n, value)
 		}
 	}
 	return nil
@@ -177,13 +214,19 @@ func (f *PathFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceConte
 
 // ApplyInputSourceValue applies a int value to the flagSet if required
 func (f *IntFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
-	if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.isSet(f.IntFlag.Name) {
-		value, err := isc.Int(f.IntFlag.Name)
+	if f.set == nil || cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars) {
+		return nil
+	}
+	for _, name := range f.IntFlag.Names() {
+		if !isc.isSet(name) {
+			continue
+		}
+		value, err := isc.Int(name)
 		if err != nil {
 			return err
 		}
-		for _, name := range f.Names() {
-			_ = f.set.Set(name, strconv.FormatInt(int64(value), 10))
+		for _, n := range f.Names() {
+			_ = f.set.Set(n, strconv.FormatInt(int64(value), 10))
 		}
 	}
 	return nil
@@ -191,13 +234,19 @@ func (f *IntFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContex
 
 // ApplyInputSourceValue applies a Duration value to the flagSet if required
 func (f *DurationFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
-	if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.isSet(f.DurationFlag.Name) {
-		value, err := isc.Duration(f.DurationFlag.Name)
+	if f.set == nil || cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars) {
+		return nil
+	}
+	for _, name := range f.DurationFlag.Names() {
+		if !isc.isSet(name) {
+			continue
+		}
+		value, err := isc.Duration(name)
 		if err != nil {
 			return err
 		}
-		for _, name := range f.Names() {
-			_ = f.set.Set(name, value.String())
+		for _, n := range f.Names() {
+			_ = f.set.Set(n, value.String())
 		}
 	}
 	return nil
@@ -205,14 +254,20 @@ func (f *DurationFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceC
 
 // ApplyInputSourceValue applies a Float64 value to the flagSet if required
 func (f *Float64Flag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error {
-	if f.set != nil && !(cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars)) && isc.isSet(f.Float64Flag.Name) {
-		value, err := isc.Float64(f.Float64Flag.Name)
+	if f.set == nil || cCtx.IsSet(f.Name) || isEnvVarSet(f.EnvVars) {
+		return nil
+	}
+	for _, name := range f.Float64Flag.Names() {
+		if !isc.isSet(name) {
+			continue
+		}
+		value, err := isc.Float64(name)
 		if err != nil {
 			return err
 		}
 		floatStr := float64ToString(value)
-		for _, name := range f.Names() {
-			_ = f.set.Set(name, floatStr)
+		for _, n := range f.Names() {
+			_ = f.set.Set(n, floatStr)
 		}
 	}
 	return nil


Test patch:
diff --git a/altsrc/flag_test.go b/altsrc/flag_test.go
index e3725f7..bece6aa 100644
--- a/altsrc/flag_test.go
+++ b/altsrc/flag_test.go
@@ -37,6 +37,20 @@ func (ris *racyInputSource) isSet(name string) bool {
 	return true
 }
 
+func TestGenericApplyInputSourceValue_Alias(t *testing.T) {
+	v := &Parser{"abc", "def"}
+	tis := testApplyInputSource{
+		Flag:     NewGenericFlag(&cli.GenericFlag{Name: "test",  Aliases: []string{"test_alias"}, Value: &Parser{}}),
+		FlagName: "test_alias",
+		MapValue: v,
+	}
+	c := runTest(t, tis)
+	expect(t, v, c.Generic("test_alias"))
+
+	c = runRacyTest(t, tis)
+	refute(t, v, c.Generic("test_alias"))
+}
+
 func TestGenericApplyInputSourceValue(t *testing.T) {
 	v := &Parser{"abc", "def"}
 	tis := testApplyInputSource{
@@ -85,6 +99,19 @@ func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) {
 	refute(t, &Parser{"abc", "def"}, c.Generic("test"))
 }
 
+func TestStringSliceApplyInputSourceValue_Alias(t *testing.T) {
+	tis := testApplyInputSource{
+		Flag:     NewStringSliceFlag(&cli.StringSliceFlag{Name: "test", Aliases: []string{"test_alias"}}),
+		FlagName: "test_alias",
+		MapValue: []interface{}{"hello", "world"},
+	}
+	c := runTest(t, tis)
+	expect(t, c.StringSlice("test_alias"), []string{"hello", "world"})
+
+	c = runRacyTest(t, tis)
+	refute(t, c.StringSlice("test_alias"), []string{"hello", "world"})
+}
+
 func TestStringSliceApplyInputSourceValue(t *testing.T) {
 	tis := testApplyInputSource{
 		Flag:     NewStringSliceFlag(&cli.StringSliceFlag{Name: "test"}),
@@ -123,6 +150,19 @@ func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
 	refute(t, c.StringSlice("test"), []string{"oh", "no"})
 }
 
+func TestIntSliceApplyInputSourceValue_Alias(t *testing.T) {
+	tis := testApplyInputSource{
+		Flag:     NewIntSliceFlag(&cli.IntSliceFlag{Name: "test", Aliases: []string{"test_alias"}}),
+		FlagName: "test_alias",
+		MapValue: []interface{}{1, 2},
+	}
+	c := runTest(t, tis)
+	expect(t, c.IntSlice("test_alias"), []int{1, 2})
+
+	c = runRacyTest(t, tis)
+	refute(t, c.IntSlice("test_alias"), []int{1, 2})
+}
+
 func TestIntSliceApplyInputSourceValue(t *testing.T) {
 	tis := testApplyInputSource{
 		Flag:     NewIntSliceFlag(&cli.IntSliceFlag{Name: "test"}),
@@ -178,6 +218,19 @@ func TestBoolApplyInputSourceMethodSet(t *testing.T) {
 	refute(t, true, c.Bool("test"))
 }
 
+func TestBoolApplyInputSourceMethodSet_Alias(t *testing.T) {
+	tis := testApplyInputSource{
+		Flag:     NewBoolFlag(&cli.BoolFlag{Name: "test", Aliases: []string{"test_alias"}}),
+		FlagName: "test_alias",
+		MapValue: true,
+	}
+	c := runTest(t, tis)
+	expect(t, true, c.Bool("test_alias"))
+
+	c = runRacyTest(t, tis)
+	refute(t, true, c.Bool("test_alias"))
+}
+
 func TestBoolApplyInputSourceMethodContextSet(t *testing.T) {
 	tis := testApplyInputSource{
 		Flag:               NewBoolFlag(&cli.BoolFlag{Name: "test"}),
@@ -207,6 +260,19 @@ func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) {
 	refute(t, true, c.Bool("test"))
 }
 
+func TestStringApplyInputSourceMethodSet_Alias(t *testing.T) {
+	tis := testApplyInputSource{
+		Flag:     NewStringFlag(&cli.StringFlag{Name: "test", Aliases: []string{"test_alias"}}),
+		FlagName: "test_alias",
+		MapValue: "hello",
+	}
+	c := runTest(t, tis)
+	expect(t, "hello", c.String("test_alias"))
+
+	c = runRacyTest(t, tis)
+	refute(t, "hello", c.String("test_alias"))
+}
+
 func TestStringApplyInputSourceMethodSet(t *testing.T) {
 	tis := testApplyInputSource{
 		Flag:     NewStringFlag(&cli.StringFlag{Name: "test"}),
@@ -249,6 +315,31 @@ func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) {
 	refute(t, "goodbye", c.String("test"))
 }
 
+func TestPathApplyInputSourceMethodSet_Alias(t *testing.T) {
+	tis := testApplyInputSource{
+		Flag:       NewPathFlag(&cli.PathFlag{Name: "test", Aliases: []string{"test_alias"}}),
+		FlagName:   "test_alias",
+		MapValue:   "hello",
+		SourcePath: "/path/to/source/file",
+	}
+	c := runTest(t, tis)
+
+	expected := "/path/to/source/hello"
+	if runtime.GOOS == "windows" {
+		var err error
+		// Prepend the corresponding drive letter (or UNC path?), and change
+		// to windows-style path:
+		expected, err = filepath.Abs(expected)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}
+	expect(t, expected, c.String("test_alias"))
+
+	c = runRacyTest(t, tis)
+	refute(t, expected, c.String("test_alias"))
+}
+
 func TestPathApplyInputSourceMethodSet(t *testing.T) {
 	tis := testApplyInputSource{
 		Flag:       NewPathFlag(&cli.PathFlag{Name: "test"}),
@@ -305,6 +396,19 @@ func TestPathApplyInputSourceMethodEnvVarSet(t *testing.T) {
 	refute(t, "goodbye", c.String("test"))
 }
 
+func TestIntApplyInputSourceMethodSet_Alias(t *testing.T) {
+	tis := testApplyInputSource{
+		Flag:     NewIntFlag(&cli.IntFlag{Name: "test", Aliases: []string{"test_alias"}}),
+		FlagName: "test_alias",
+		MapValue: 15,
+	}
+	c := runTest(t, tis)
+	expect(t, 15, c.Int("test_alias"))
+
+	c = runRacyTest(t, tis)
+	refute(t, 15, c.Int("test_alias"))
+}
+
 func TestIntApplyInputSourceMethodSet(t *testing.T) {
 	tis := testApplyInputSource{
 		Flag:     NewIntFlag(&cli.IntFlag{Name: "test"}),
@@ -360,6 +464,19 @@ func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) {
 	refute(t, 12, c.Int("test"))
 }
 
+func TestDurationApplyInputSourceMethodSet_Alias(t *testing.T) {
+	tis := testApplyInputSource{
+		Flag:     NewDurationFlag(&cli.DurationFlag{Name: "test", Aliases: []string{"test_alias"}}),
+		FlagName: "test_alias",
+		MapValue: 30 * time.Second,
+	}
+	c := runTest(t, tis)
+	expect(t, 30*time.Second, c.Duration("test_alias"))
+
+	c = runRacyTest(t, tis)
+	refute(t, 30*time.Second, c.Duration("test_alias"))
+}
+
 func TestDurationApplyInputSourceMethodSet(t *testing.T) {
 	tis := testApplyInputSource{
 		Flag:     NewDurationFlag(&cli.DurationFlag{Name: "test"}),
@@ -428,6 +545,19 @@ func TestFloat64ApplyInputSourceMethodSet(t *testing.T) {
 	refute(t, 1.3, c.Float64("test"))
 }
 
+func TestFloat64ApplyInputSourceMethodSetNegativeValue_Alias(t *testing.T) {
+	tis := testApplyInputSource{
+		Flag:     NewFloat64Flag(&cli.Float64Flag{Name: "test", Aliases: []string{"test_alias"}}),
+		FlagName: "test_alias",
+		MapValue: -1.3,
+	}
+	c := runTest(t, tis)
+	expect(t, -1.3, c.Float64("test_alias"))
+
+	c = runRacyTest(t, tis)
+	refute(t, -1.3, c.Float64("test_alias"))
+}
+
 func TestFloat64ApplyInputSourceMethodSetNegativeValue(t *testing.T) {
 	tis := testApplyInputSource{
 		Flag:     NewFloat64Flag(&cli.Float64Flag{Name: "test"}),