Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace file searching API with a finder #1849

Merged
merged 5 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
go: ["1.21", "1.22"]
tags: ["", "finder", "viper_bind_struct"]
tags: ["", "viper_finder", "viper_bind_struct"]

steps:
- name: Checkout repository
Expand Down
2 changes: 1 addition & 1 deletion file.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build !finder
//go:build !viper_finder

package viper

Expand Down
30 changes: 17 additions & 13 deletions file_finder.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build finder
//go:build viper_finder

package viper

Expand All @@ -11,18 +11,22 @@ import (
// Search all configPaths for any config file.
// Returns the first path that exists (and is a config file).
func (v *Viper) findConfigFile() (string, error) {
var names []string

if v.configType != "" {
names = locafero.NameWithOptionalExtensions(v.configName, SupportedExts...)
} else {
names = locafero.NameWithExtensions(v.configName, SupportedExts...)
}

finder := locafero.Finder{
Paths: v.configPaths,
Names: names,
Type: locafero.FileTypeFile,
finder := v.finder

if finder == nil {
var names []string

if v.configType != "" {
names = locafero.NameWithOptionalExtensions(v.configName, SupportedExts...)
} else {
names = locafero.NameWithExtensions(v.configName, SupportedExts...)
}

finder = locafero.Finder{
Paths: v.configPaths,
Names: names,
Type: locafero.FileTypeFile,
}
}

results, err := finder.Find(v.fs)
Expand Down
47 changes: 47 additions & 0 deletions finder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package viper

import (
"errors"

"github.com/spf13/afero"
)

// WithFinder sets a custom [Finder].
func WithFinder(f Finder) Option {
return optionFunc(func(v *Viper) {
v.finder = f
})
}

// Finder looks for files and directories in an [afero.Fs] filesystem.
type Finder interface {
Find(fsys afero.Fs) ([]string, error)
}

// Finders combines multiple finders into one.
func Finders(finders ...Finder) Finder {
return &combinedFinder{finders: finders}
}

// combinedFinder is a Finder that combines multiple finders.
type combinedFinder struct {
finders []Finder
}

// Find implements the [Finder] interface.
func (c *combinedFinder) Find(fsys afero.Fs) ([]string, error) {
var results []string
var errs []error

for _, finder := range c.finders {
r, err := finder.Find(fsys)
if err != nil {
errs = append(errs, err)
continue
}

results = append(results, r...)
}

return results, errors.Join(errs...)
}
75 changes: 75 additions & 0 deletions finder_example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//go:build viper_finder

package viper_test

import (
"fmt"

"github.com/sagikazarmark/locafero"
"github.com/spf13/afero"

"github.com/spf13/viper"
)

func ExampleFinder() {
fs := afero.NewMemMapFs()

fs.Mkdir("/home/user", 0o777)

f, _ := fs.Create("/home/user/myapp.yaml")
f.WriteString("foo: bar")
f.Close()

// HCL will have a "lower" priority in the search order
fs.Create("/home/user/myapp.hcl")

finder := locafero.Finder{
Paths: []string{"/home/user"},
Names: locafero.NameWithExtensions("myapp", viper.SupportedExts...),
Type: locafero.FileTypeFile, // This is important!
}

v := viper.NewWithOptions(viper.WithFinder(finder))
v.SetFs(fs)
v.ReadInConfig()

fmt.Println(v.GetString("foo"))

// Output:
// bar
}

func ExampleFinders() {
fs := afero.NewMemMapFs()

fs.Mkdir("/home/user", 0o777)
f, _ := fs.Create("/home/user/myapp.yaml")
f.WriteString("foo: bar")
f.Close()

fs.Mkdir("/etc/myapp", 0o777)
fs.Create("/etc/myapp/config.yaml")

// Combine multiple finders to search for files in multiple locations with different criteria
finder := viper.Finders(
locafero.Finder{
Paths: []string{"/home/user"},
Names: locafero.NameWithExtensions("myapp", viper.SupportedExts...),
Type: locafero.FileTypeFile, // This is important!
},
locafero.Finder{
Paths: []string{"/etc/myapp"},
Names: []string{"config.yaml"}, // Only accept YAML files in the system config directory
Type: locafero.FileTypeFile, // This is important!
},
)

v := viper.NewWithOptions(viper.WithFinder(finder))
v.SetFs(fs)
v.ReadInConfig()

fmt.Println(v.GetString("foo"))

// Output:
// bar
}
42 changes: 42 additions & 0 deletions finder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package viper

import (
"testing"

"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

type finderStub struct {
results []string
}

func (f finderStub) Find(_ afero.Fs) ([]string, error) {
return f.results, nil
}

func TestFinders(t *testing.T) {
finder := Finders(
finderStub{
results: []string{
"/home/user/.viper.yaml",
},
},
finderStub{
results: []string{
"/etc/viper/config.yaml",
},
},
)

results, err := finder.Find(afero.NewMemMapFs())
require.NoError(t, err)

expected := []string{
"/home/user/.viper.yaml",
"/etc/viper/config.yaml",
}

assert.Equal(t, expected, results)
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/magiconair/properties v1.8.7
github.com/pelletier/go-toml/v2 v2.2.2
github.com/sagikazarmark/crypt v0.20.0
github.com/sagikazarmark/locafero v0.4.0
github.com/sagikazarmark/locafero v0.5.0
github.com/spf13/afero v1.11.0
github.com/spf13/cast v1.6.0
github.com/spf13/pflag v1.0.5
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,8 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.20.0 h1:aMLNL5RDUyk+/Bmoql3iYbv6C75/H0qvs6CWHzRGo6Y=
github.com/sagikazarmark/crypt v0.20.0/go.mod h1:ojgvzJOpJkRhzSrycMyk8cgctQgyfZN/d2/dVh4U68c=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/locafero v0.5.0 h1:zXz2JnQDgE5gDg0R9ThkNT0orQzm47i8IuO6hk6XSYY=
github.com/sagikazarmark/locafero v0.5.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
Expand Down
10 changes: 10 additions & 0 deletions viper.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ type Viper struct {
// The filesystem to read config from.
fs afero.Fs

finder Finder

// A set of remote providers to search for the configuration
remoteProviders []*defaultRemoteProvider

Expand Down Expand Up @@ -506,6 +508,10 @@ func (v *Viper) ConfigFileUsed() string { return v.configFile }
func AddConfigPath(in string) { v.AddConfigPath(in) }

func (v *Viper) AddConfigPath(in string) {
if v.finder != nil {
v.logger.Warn("ineffective call to function: custom finder takes precedence", slog.String("function", "AddConfigPath"))
}

if in != "" {
absin := absPathify(v.logger, in)

Expand Down Expand Up @@ -1955,6 +1961,10 @@ func (v *Viper) SetFs(fs afero.Fs) {
func SetConfigName(in string) { v.SetConfigName(in) }

func (v *Viper) SetConfigName(in string) {
if v.finder != nil {
v.logger.Warn("ineffective call to function: custom finder takes precedence", slog.String("function", "SetConfigName"))
}

if in != "" {
v.configName = in
v.configFile = ""
Expand Down