Skip to content

Commit

Permalink
Switch to an extensions model
Browse files Browse the repository at this point in the history
- Move things back to `altsrc`
- Add support for extensions to `App`
- Implement `DetectableSourcesAppExtension` to hold `detectableSources`
- Refactor code and tests for the revised setup, including making the
  extension only needed for adding additional handlers
  • Loading branch information
danhunsaker committed Mar 19, 2023
1 parent 1d1e48a commit 3c3d505
Show file tree
Hide file tree
Showing 17 changed files with 409 additions and 315 deletions.
6 changes: 2 additions & 4 deletions altsrc/default_input_source.go
@@ -1,8 +1,6 @@
package altsrc

import "github.com/urfave/cli/v2"

// defaultInputSource creates a default cli.InputSourceContext.
func defaultInputSource() (cli.InputSourceContext, error) {
// defaultInputSource creates a default InputSourceContext.
func defaultInputSource() (InputSourceContext, error) {
return &MapInputSource{file: "", valueMap: map[interface{}]interface{}{}}, nil
}
22 changes: 13 additions & 9 deletions altsrc/detect_input_source.go
Expand Up @@ -9,25 +9,29 @@ import (
)

// defaultSources is a read-only map, making it concurrency-safe
var defaultSources = map[string]func(string) func(*cli.Context) (cli.InputSourceContext, error){
var defaultSources = map[string]func(string) func(*cli.Context) (InputSourceContext, error){
".conf": NewTomlSourceFromFlagFunc,
".json": NewJSONSourceFromFlagFunc,
".toml": NewTomlSourceFromFlagFunc,
".yaml": NewYamlSourceFromFlagFunc,
".yml": NewYamlSourceFromFlagFunc,
}

// DetectNewSourceFromFlagFunc creates a new cli.InputSourceContext from a provided flag name and source context.
func DetectNewSourceFromFlagFunc(flagFileName string) func(*cli.Context) (cli.InputSourceContext, error) {
return func(cCtx *cli.Context) (cli.InputSourceContext, error) {
detectableSources := cCtx.App.GetDetectableSources()

// DetectNewSourceFromFlagFunc creates a new InputSourceContext from a provided flag name and source context.
func DetectNewSourceFromFlagFunc(flagFileName string) func(*cli.Context) (InputSourceContext, error) {
return func(cCtx *cli.Context) (InputSourceContext, error) {
if fileFullPath := cCtx.String(flagFileName); fileFullPath != "" {
detectableSources := make(map[string]func(string) func(*cli.Context) (InputSourceContext, error))
fileExt := filepath.Ext(fileFullPath)

// Check if the App contains a handler for this extension first, allowing it to override the defaults
if handler, ok := detectableSources[fileExt]; ok {
return handler(flagFileName)(cCtx)
detectExt, isType := cCtx.App.GetExtension("DetectableSources").(DetectableSourcesAppExtension)
if isType {
detectableSources = detectExt.getDetectableSources()

if handler, ok := detectableSources[fileExt]; ok {
return handler(flagFileName)(cCtx)
}
}

// Fall back to the default sources implemented by the library itself
Expand All @@ -42,7 +46,7 @@ func DetectNewSourceFromFlagFunc(flagFileName string) func(*cli.Context) (cli.In
}
}

func detectableExtensions(detectableSources map[string]func(string) func(*cli.Context) (cli.InputSourceContext, error)) []string {
func detectableExtensions(detectableSources map[string]func(string) func(*cli.Context) (InputSourceContext, error)) []string {
// We don't preallocate because this generates empty space in the output
// It's less efficient, but this is for error messaging only at the moment
var extensions []string
Expand Down
26 changes: 14 additions & 12 deletions altsrc/detect_input_source_test.go
Expand Up @@ -10,7 +10,7 @@ import (
)

func TestDetectsConfCorrectly(t *testing.T) {
app := &cli.App{}
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
_ = os.WriteFile("current.conf", []byte("test = 15"), 0666)
defer os.Remove("current.conf")
Expand Down Expand Up @@ -40,7 +40,7 @@ func TestDetectsConfCorrectly(t *testing.T) {
}

func TestDetectsJsonCorrectly(t *testing.T) {
app := &cli.App{}
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
_ = os.WriteFile("current.json", []byte("{\"test\":15}"), 0666)
defer os.Remove("current.json")
Expand Down Expand Up @@ -70,7 +70,7 @@ func TestDetectsJsonCorrectly(t *testing.T) {
}

func TestDetectsTomlCorrectly(t *testing.T) {
app := &cli.App{}
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
_ = os.WriteFile("current.toml", []byte("test = 15"), 0666)
defer os.Remove("current.toml")
Expand Down Expand Up @@ -100,7 +100,7 @@ func TestDetectsTomlCorrectly(t *testing.T) {
}

func TestDetectsYamlCorrectly(t *testing.T) {
app := &cli.App{}
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
_ = os.WriteFile("current.yaml", []byte("test: 15"), 0666)
defer os.Remove("current.yaml")
Expand Down Expand Up @@ -130,7 +130,7 @@ func TestDetectsYamlCorrectly(t *testing.T) {
}

func TestDetectsYmlCorrectly(t *testing.T) {
app := &cli.App{}
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
_ = os.WriteFile("current.yml", []byte("test: 15"), 0666)
defer os.Remove("current.yml")
Expand Down Expand Up @@ -160,8 +160,9 @@ func TestDetectsYmlCorrectly(t *testing.T) {
}

func TestHandlesCustomTypeCorrectly(t *testing.T) {
app := &cli.App{}
app.RegisterDetectableSource(".custom", NewYamlSourceFromFlagFunc)
app := cli.NewApp()
app.AddExtension(NewDetectableSourcesAppExtension())
app.GetExtension("DetectableSources").(DetectableSourcesAppExtension).RegisterDetectableSource(".custom", NewYamlSourceFromFlagFunc)
set := flag.NewFlagSet("test", 0)
_ = os.WriteFile("current.custom", []byte("test: 15"), 0666)
defer os.Remove("current.custom")
Expand Down Expand Up @@ -191,8 +192,9 @@ func TestHandlesCustomTypeCorrectly(t *testing.T) {
}

func TestAllowsOverrides(t *testing.T) {
app := &cli.App{}
app.RegisterDetectableSource(".conf", NewYamlSourceFromFlagFunc)
app := cli.NewApp()
app.AddExtension(NewDetectableSourcesAppExtension())
app.GetExtension("DetectableSources").(DetectableSourcesAppExtension).RegisterDetectableSource(".conf", NewYamlSourceFromFlagFunc)
set := flag.NewFlagSet("test", 0)
_ = os.WriteFile("current.conf", []byte("test: 15"), 0666)
defer os.Remove("current.conf")
Expand Down Expand Up @@ -222,7 +224,7 @@ func TestAllowsOverrides(t *testing.T) {
}

func TestFailsOnUnrocegnized(t *testing.T) {
app := &cli.App{}
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
_ = os.WriteFile("current.fake", []byte("test: 15"), 0666)
defer os.Remove("current.fake")
Expand Down Expand Up @@ -252,7 +254,7 @@ func TestFailsOnUnrocegnized(t *testing.T) {
}

func TestSilentNoOpWithoutFlag(t *testing.T) {
app := &cli.App{}
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
_ = os.WriteFile("current.conf", []byte("test = 15"), 0666)
defer os.Remove("current.conf")
Expand Down Expand Up @@ -284,7 +286,7 @@ func TestSilentNoOpWithoutFlag(t *testing.T) {
func TestLoadDefaultConfig(t *testing.T) {
t.Skip("Fix parent implementation for default Flag values to get this working")

app := &cli.App{}
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
_ = os.WriteFile("current.conf", []byte("test = 15"), 0666)
defer os.Remove("current.conf")
Expand Down
32 changes: 32 additions & 0 deletions altsrc/detectable_sources_app_extension.go
@@ -0,0 +1,32 @@
package altsrc

import (
"github.com/urfave/cli/v2"
)

type DetectableSourcesAppExtension struct {
detectableSources map[string]func(string) func(*cli.Context) (InputSourceContext, error)
}

func NewDetectableSourcesAppExtension() DetectableSourcesAppExtension {
return DetectableSourcesAppExtension{
detectableSources: make(map[string]func(string) func(*cli.Context) (InputSourceContext, error)),
}
}

// MyName satisfies the cli.AppExtension interface, providing a name to register the extension under
func (e DetectableSourcesAppExtension) MyName() string {
return "DetectableSources"
}

// RegisterDetectableSource lets developers add support for their own altsrc filetypes to the autodetection list.
func (e DetectableSourcesAppExtension) RegisterDetectableSource(extension string, handler func(string) func(*cli.Context) (InputSourceContext, error)) {
if e.detectableSources == nil {
e.detectableSources = make(map[string]func(string) func(*cli.Context) (InputSourceContext, error))
}
e.detectableSources[extension] = handler
}

func (e DetectableSourcesAppExtension) getDetectableSources() map[string]func(string) func(*cli.Context) (InputSourceContext, error) {
return e.detectableSources
}
49 changes: 49 additions & 0 deletions altsrc/detectable_sources_app_extension_test.go
@@ -0,0 +1,49 @@
package altsrc

import (
"io"
"testing"

"github.com/urfave/cli/v2"
)

func newTestApp() *cli.App {
a := cli.NewApp()
a.Writer = io.Discard
a.AddExtension(NewDetectableSourcesAppExtension())
return a
}

func TestRegisterDetectableSource(t *testing.T) {
app := newTestApp()
testHandler := func(s string) func(*cli.Context) (InputSourceContext, error) {
return func(ctx *cli.Context) (InputSourceContext, error) {
return testInputSource{}, nil
}
}

app.GetExtension("DetectableSources").(DetectableSourcesAppExtension).RegisterDetectableSource(".test", testHandler)

_, ok := app.GetExtension("DetectableSources").(DetectableSourcesAppExtension).detectableSources[".test"]
expect(t, ok, true)
}

func TestGetDetectableSources(t *testing.T) {
app := newTestApp()
testHandler := func(s string) func(*cli.Context) (InputSourceContext, error) {
return func(ctx *cli.Context) (InputSourceContext, error) {
return testInputSource{}, nil
}
}

_, ok := app.GetExtension("DetectableSources").(DetectableSourcesAppExtension).getDetectableSources()[".test"]
expect(t, ok, false)

app.GetExtension("DetectableSources").(DetectableSourcesAppExtension).RegisterDetectableSource(".test", testHandler)

_, ok = app.GetExtension("DetectableSources").(DetectableSourcesAppExtension).detectableSources[".test"]
expect(t, ok, true)

_, ok = app.GetExtension("DetectableSources").(DetectableSourcesAppExtension).getDetectableSources()[".test"]
expect(t, ok, true)
}

0 comments on commit 3c3d505

Please sign in to comment.