From 8e71595a4a9dae7e42e7e4fab5739dbabdf7a518 Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Tue, 21 Sep 2021 10:25:51 +0200 Subject: [PATCH] feat: implement new finder using io/fs Signed-off-by: Mark Sagi-Kazar --- fs.go | 65 ++++++++++++++++++++++++++++++++++ fs_test.go | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 fs.go create mode 100644 fs_test.go diff --git a/fs.go b/fs.go new file mode 100644 index 0000000..70631f8 --- /dev/null +++ b/fs.go @@ -0,0 +1,65 @@ +//go:build go1.16 +// +build go1.16 + +package viper + +import ( + "errors" + "io/fs" + "path/filepath" +) + +type finder struct { + paths []string + fileNames []string + extensions []string + + withoutExtension bool +} + +func (f finder) Find(fsys fs.FS) (string, error) { + for _, path := range f.paths { + for _, fileName := range f.fileNames { + for _, extension := range f.extensions { + filePath := filepath.Join(path, fileName+"."+extension) + + ok, err := fileExists(fsys, filePath) + if err != nil { + return "", err + } + + if ok { + return filePath, nil + } + } + + if f.withoutExtension { + filePath := filepath.Join(path, fileName) + + ok, err := fileExists(fsys, filePath) + if err != nil { + return "", err + } + + if ok { + return filePath, nil + } + } + } + } + + return "", nil +} + +func fileExists(fsys fs.FS, filePath string) (bool, error) { + fileInfo, err := fs.Stat(fsys, filePath) + if err == nil { + return !fileInfo.IsDir(), nil + } + + if errors.Is(err, fs.ErrNotExist) { + return false, nil + } + + return false, err +} diff --git a/fs_test.go b/fs_test.go new file mode 100644 index 0000000..d811f45 --- /dev/null +++ b/fs_test.go @@ -0,0 +1,100 @@ +//go:build go1.16 +// +build go1.16 + +package viper + +import ( + "io/fs" + "testing" + "testing/fstest" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestFinder(t *testing.T) { + t.Parallel() + + fsys := fstest.MapFS{ + "home/user/.config": &fstest.MapFile{}, + "home/user/config.json": &fstest.MapFile{}, + "home/user/config.yaml": &fstest.MapFile{}, + "home/user/data.json": &fstest.MapFile{}, + "etc/config/.config": &fstest.MapFile{}, + "etc/config/a_random_file.txt": &fstest.MapFile{}, + "etc/config/config.json": &fstest.MapFile{}, + "etc/config/config.yaml": &fstest.MapFile{}, + "etc/config/config.xml": &fstest.MapFile{}, + } + + testCases := []struct { + name string + fsys func() fs.FS + finder finder + result string + }{ + { + name: "find file", + fsys: func() fs.FS { return fsys }, + finder: finder{ + paths: []string{"etc/config"}, + fileNames: []string{"config"}, + extensions: []string{"json"}, + }, + result: "etc/config/config.json", + }, + { + name: "file not found", + fsys: func() fs.FS { return fsys }, + finder: finder{ + paths: []string{"var/config"}, + fileNames: []string{"config"}, + extensions: []string{"json"}, + }, + result: "", + }, + { + name: "empty search params", + fsys: func() fs.FS { return fsys }, + finder: finder{}, + result: "", + }, + { + name: "precedence", + fsys: func() fs.FS { return fsys }, + finder: finder{ + paths: []string{"var/config", "home/user", "etc/config"}, + fileNames: []string{"aconfig", "config"}, + extensions: []string{"zml", "xml", "json"}, + }, + result: "home/user/config.json", + }, + { + name: "without extension", + fsys: func() fs.FS { return fsys }, + finder: finder{ + paths: []string{"var/config", "home/user", "etc/config"}, + fileNames: []string{".config"}, + extensions: []string{"zml", "xml", "json"}, + + withoutExtension: true, + }, + result: "home/user/.config", + }, + } + + for _, testCase := range testCases { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + fsys := testCase.fsys() + + result, err := testCase.finder.Find(fsys) + require.NoError(t, err) + + assert.Equal(t, testCase.result, result) + }) + } +}