-
Notifications
You must be signed in to change notification settings - Fork 2
/
gofigure.go
151 lines (118 loc) · 3.93 KB
/
gofigure.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// GoFigure is a small utility library for reading configuration files. It's usefuly especially if you want
// to load many files recursively (think /etc/apache2/mods-enabled/*.conf).
//
// It can support multiple formats, as long as you take a file and unmarshal it into a struct containing
// your configurations. Right now the only implemented formats are YAML and JSON files, but feel free to
// add more :)
package gofigure
import (
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"github.com/EverythingMe/gofigure/yaml"
)
// DefaultDecoder is a yaml based decoder that can be used for convenience
var DefaultLoader = NewLoader(yaml.Decoder{}, true)
// Decoder is the interface for config decoders (right now we've just implemented a YAML one)
type Decoder interface {
// Decode reads data from the io stream, and unmarshals it to the config struct.
Decode(r io.Reader, config interface{}) error
// CanDecode should return true if a file is decode-able by the decoder,
// based on extension or similar mechanisms
CanDecode(path string) bool
}
// Loader traverses directories recursively and lets the decoder decode relevant files.
//
// It can also explicitly decode single files
type Loader struct {
decoder Decoder
// StrictMode determines whether the loader will completely fail on any IO or decoding error,
// or whether it will continue traversing all files even if one of them is invalid.
StrictMode bool
}
// NewLoader creates and returns a new Loader wrapping a decoder, using strict mode if specified
func NewLoader(d Decoder, strict bool) *Loader {
return &Loader{
decoder: d,
StrictMode: strict,
}
}
// LoadRecursive takes a pointer to a struct containing configurations, and a series of paths.
// It then traverses the paths recursively in their respective order, and lets the decoder decode
// every relevant file.
func (l Loader) LoadRecursive(config interface{}, paths ...string) error {
ch, cancelc := walk(paths...)
defer close(cancelc)
for path := range ch {
if l.decoder.CanDecode(path) {
err := l.LoadFile(config, path)
if err != nil {
log.Printf("Error loading %s: %s", path, err)
if l.StrictMode {
return err
}
}
}
}
return nil
}
// LoadFile takes a pointer to a struct containing configurations, and a path to a file,
// and uses the decoder to read the file's contents into the struct. It returns an
// error if the file could not be opened or properly decoded
func (l Loader) LoadFile(config interface{}, path string) error {
log.Println("Reading config file", path)
fp, err := os.Open(path)
if err != nil {
log.Printf("Error opening file %s: %s", path, err)
if l.StrictMode {
return err
}
}
defer fp.Close()
err = l.decoder.Decode(fp, config)
if err != nil {
log.Printf("Error decodeing file %s: %s", path, err)
if l.StrictMode {
return err
}
}
return nil
}
// walkDir recursively traverses a directory, sending every found file's path to the channel ch.
// If no one is reading from ch, it times out after a second of waiting, and quits
func walkDir(path string, ch chan string, cancelc <-chan struct{}) {
files, err := ioutil.ReadDir(path)
if err != nil {
log.Printf("Could not read path %s: %s", path, err)
return
}
for _, file := range files {
fullpath := filepath.Join(path, file.Name())
if file.IsDir() {
walkDir(fullpath, ch, cancelc)
continue
}
select {
case ch <- fullpath:
case <-cancelc:
log.Printf("Read canceled")
return
}
}
}
// walk takes a series of paths, and traverses them recursively by order, sending all found files
// in the returned channel. It then closes the channel
func walk(paths ...string) (pathchan <-chan string, cancelchan chan<- struct{}) {
// we make the channel buffered so it can be filled while the consumer loads files
ch := make(chan string, 100)
cancelc := make(chan struct{})
go func() {
defer close(ch)
for _, path := range paths {
walkDir(path, ch, cancelc)
}
}()
return ch, cancelc
}