Skip to content

Commit 2f82a14

Browse files
authored
Merge pull request #87 from mvrahden/feature/filesystem
add fs.FS provider
2 parents 139fe40 + 023ecc6 commit 2f82a14

File tree

3 files changed

+186
-0
lines changed

3 files changed

+186
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,7 @@ Writing Providers and Parsers are easy. See the bundled implementations in the `
606606
| Package | Provider | Description |
607607
| ------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
608608
| providers/file | `file.Provider(filepath string)` | Reads a file and returns the raw bytes to be parsed. |
609+
| providers/fs | `fs.Provider(f fs.FS, filepath string)` | (**Experimental**) Reads a file from fs.FS and returns the raw bytes to be parsed. The provider requires `go v1.16` or higher. |
609610
| providers/basicflag | `basicflag.Provider(f *flag.FlagSet, delim string)` | Takes an stdlib `flag.FlagSet` |
610611
| providers/posflag | `posflag.Provider(f *pflag.FlagSet, delim string)` | Takes an `spft3/pflag.FlagSet` (advanced POSIX compatible flags with multiple types) and provides a nested config map based on delim. |
611612
| providers/env | `env.Provider(prefix, delim string, f func(s string) string)` | Takes an optional prefix to filter env variables by, an optional function that takes and returns a string to transform env variables, and returns a nested config map based on delim. |

providers/fs/fs.go

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Package fs implements a koanf.Provider that reads raw bytes
2+
// from given fs.FS to be used with a koanf.Parser to parse
3+
// into conf maps.
4+
5+
// +build go1.16
6+
7+
package fs
8+
9+
import (
10+
"errors"
11+
"io/fs"
12+
"io/ioutil"
13+
)
14+
15+
// FS implements an fs.FS provider.
16+
type FS struct {
17+
fs fs.FS
18+
path string
19+
}
20+
21+
// Provider returns an fs.FS provider.
22+
func Provider(fs fs.FS, filepath string) *FS {
23+
return &FS{fs: fs, path: filepath}
24+
}
25+
26+
// ReadBytes reads the contents of given filepath from fs.FS and returns the bytes.
27+
func (f *FS) ReadBytes() ([]byte, error) {
28+
fd, err := f.fs.Open(f.path)
29+
if err != nil {
30+
return nil, err
31+
}
32+
defer fd.Close()
33+
34+
return ioutil.ReadAll(fd)
35+
}
36+
37+
// Read is not supported by the fs.FS provider.
38+
func (f *FS) Read() (map[string]interface{}, error) {
39+
return nil, errors.New("fs.FS provider does not support this method")
40+
}
41+
42+
// Watch is not supported by the fs.FS provider.
43+
func (f *FS) Watch(cb func(event interface{}, err error)) error {
44+
return errors.New("fs.FS provider does not support this method")
45+
}

providers/fs/fs_test.go

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// +build go1.16
2+
3+
package fs_test
4+
5+
import (
6+
"fmt"
7+
"os"
8+
"testing"
9+
"testing/fstest"
10+
"time"
11+
12+
"github.com/knadh/koanf"
13+
"github.com/knadh/koanf/parsers/hcl"
14+
"github.com/knadh/koanf/parsers/json"
15+
"github.com/knadh/koanf/parsers/toml"
16+
"github.com/knadh/koanf/parsers/yaml"
17+
"github.com/knadh/koanf/providers/fs"
18+
"github.com/stretchr/testify/assert"
19+
"github.com/stretchr/testify/require"
20+
)
21+
22+
type Case struct {
23+
koanf *koanf.Koanf
24+
file string
25+
parser koanf.Parser
26+
typeName string
27+
}
28+
29+
func TestFSProvider(t *testing.T) {
30+
var (
31+
assert = assert.New(t)
32+
)
33+
34+
cases := []Case{
35+
{koanf: koanf.New("."), file: "mock.json", parser: json.Parser(), typeName: "json"},
36+
{koanf: koanf.New("."), file: "mock.yml", parser: yaml.Parser(), typeName: "yml"},
37+
{koanf: koanf.New("."), file: "mock.toml", parser: toml.Parser(), typeName: "toml"},
38+
{koanf: koanf.New("."), file: "mock.hcl", parser: hcl.Parser(true), typeName: "hcl"},
39+
}
40+
41+
// load file system
42+
testFS := os.DirFS("../../mock")
43+
44+
for _, c := range cases {
45+
// Test fs.FS before setting up kaonf
46+
err := fstest.TestFS(testFS, c.file)
47+
require.NoError(t, err, "failed asserting file existence in fs.FS")
48+
49+
// koanf setup
50+
p := fs.Provider(testFS, c.file)
51+
err = c.koanf.Load(p, c.parser)
52+
require.NoError(t, err, fmt.Sprintf("error loading: %v", c.file))
53+
54+
// Type.
55+
require.Equal(t, c.typeName, c.koanf.Get("type"))
56+
57+
assert.Equal(nil, c.koanf.Get("xxx"))
58+
assert.Equal(make(map[string]interface{}), c.koanf.Get("empty"))
59+
60+
// Int.
61+
assert.Equal(int64(0), c.koanf.Int64("xxxx"))
62+
assert.Equal(int64(1234), c.koanf.Int64("parent1.id"))
63+
64+
assert.Equal(int(0), c.koanf.Int("xxxx"))
65+
assert.Equal(int(1234), c.koanf.Int("parent1.id"))
66+
67+
assert.Equal([]int64{}, c.koanf.Int64s("xxxx"))
68+
assert.Equal([]int64{1, 2, 3}, c.koanf.Int64s("parent1.child1.grandchild1.ids"))
69+
70+
assert.Equal(map[string]int64{"key1": 1, "key2": 1, "key3": 1}, c.koanf.Int64Map("parent1.intmap"))
71+
assert.Equal(map[string]int64{}, c.koanf.Int64Map("parent1.boolmap"))
72+
assert.Equal(map[string]int64{}, c.koanf.Int64Map("xxxx"))
73+
assert.Equal(map[string]int64{"key1": 1, "key2": 1, "key3": 1}, c.koanf.Int64Map("parent1.floatmap"))
74+
75+
assert.Equal([]int{1, 2, 3}, c.koanf.Ints("parent1.child1.grandchild1.ids"))
76+
assert.Equal([]int{}, c.koanf.Ints("xxxx"))
77+
78+
assert.Equal(map[string]int{"key1": 1, "key2": 1, "key3": 1}, c.koanf.IntMap("parent1.intmap"))
79+
assert.Equal(map[string]int{}, c.koanf.IntMap("parent1.boolmap"))
80+
assert.Equal(map[string]int{}, c.koanf.IntMap("xxxx"))
81+
82+
// Float.
83+
assert.Equal(float64(0), c.koanf.Float64("xxx"))
84+
assert.Equal(float64(1234), c.koanf.Float64("parent1.id"))
85+
86+
assert.Equal([]float64{}, c.koanf.Float64s("xxxx"))
87+
assert.Equal([]float64{1, 2, 3}, c.koanf.Float64s("parent1.child1.grandchild1.ids"))
88+
89+
assert.Equal(map[string]float64{"key1": 1, "key2": 1, "key3": 1}, c.koanf.Float64Map("parent1.intmap"))
90+
assert.Equal(map[string]float64{"key1": 1.1, "key2": 1.2, "key3": 1.3}, c.koanf.Float64Map("parent1.floatmap"))
91+
assert.Equal(map[string]float64{}, c.koanf.Float64Map("parent1.boolmap"))
92+
assert.Equal(map[string]float64{}, c.koanf.Float64Map("xxxx"))
93+
94+
// String and bytes.
95+
assert.Equal([]byte{}, c.koanf.Bytes("xxxx"))
96+
assert.Equal([]byte("parent1"), c.koanf.Bytes("parent1.name"))
97+
98+
assert.Equal("", c.koanf.String("xxxx"))
99+
assert.Equal("parent1", c.koanf.String("parent1.name"))
100+
101+
assert.Equal([]string{}, c.koanf.Strings("xxxx"))
102+
assert.Equal([]string{"red", "blue", "orange"}, c.koanf.Strings("orphan"))
103+
104+
assert.Equal(map[string]string{"key1": "val1", "key2": "val2", "key3": "val3"}, c.koanf.StringMap("parent1.strmap"))
105+
assert.Equal(map[string][]string{"key1": {"val1", "val2", "val3"}, "key2": {"val4", "val5"}}, c.koanf.StringsMap("parent1.strsmap"))
106+
assert.Equal(map[string]string{}, c.koanf.StringMap("xxxx"))
107+
assert.Equal(map[string]string{}, c.koanf.StringMap("parent1.intmap"))
108+
109+
// Bools.
110+
assert.Equal(false, c.koanf.Bool("xxxx"))
111+
assert.Equal(false, c.koanf.Bool("type"))
112+
assert.Equal(true, c.koanf.Bool("parent1.child1.grandchild1.on"))
113+
assert.Equal(true, c.koanf.Bool("strbool"))
114+
115+
assert.Equal([]bool{}, c.koanf.Bools("xxxx"))
116+
assert.Equal([]bool{true, false, true}, c.koanf.Bools("bools"))
117+
assert.Equal([]bool{true, false, true}, c.koanf.Bools("intbools"))
118+
assert.Equal([]bool{true, true, false}, c.koanf.Bools("strbools"))
119+
120+
assert.Equal(map[string]bool{"ok1": true, "ok2": true, "notok3": false}, c.koanf.BoolMap("parent1.boolmap"))
121+
assert.Equal(map[string]bool{"key1": true, "key2": true, "key3": true}, c.koanf.BoolMap("parent1.intmap"))
122+
assert.Equal(map[string]bool{}, c.koanf.BoolMap("xxxx"))
123+
124+
// Others.
125+
assert.Equal(time.Duration(1234), c.koanf.Duration("parent1.id"))
126+
assert.Equal(time.Duration(0), c.koanf.Duration("xxxx"))
127+
assert.Equal(time.Second*3, c.koanf.Duration("duration"))
128+
129+
assert.Equal(time.Time{}, c.koanf.Time("xxxx", "2006-01-02"))
130+
assert.Equal(time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), c.koanf.Time("time", "2006-01-02"))
131+
132+
assert.Equal([]string{}, c.koanf.MapKeys("xxxx"), "map keys mismatch")
133+
assert.Equal([]string{"bools", "duration", "empty", "intbools", "orphan", "parent1", "parent2", "strbool", "strbools", "time", "type"},
134+
c.koanf.MapKeys(""), "map keys mismatch")
135+
assert.Equal([]string{"key1", "key2", "key3"}, c.koanf.MapKeys("parent1.strmap"), "map keys mismatch")
136+
137+
// Attempt to parse int=1234 as a Unix timestamp.
138+
assert.Equal(time.Date(1970, 1, 1, 0, 20, 34, 0, time.UTC), c.koanf.Time("parent1.id", "").UTC())
139+
}
140+
}

0 commit comments

Comments
 (0)