/
fromio.go
160 lines (133 loc) · 3.41 KB
/
fromio.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
152
153
154
155
156
157
158
159
160
package construct
import (
"io"
"github.com/pierrec/construct/internal/structs"
"github.com/pkg/errors"
)
// LookupFn is the function signature used to return the runes used
// for (de)serializing data on a given key.
type LookupFn func(key ...string) []rune
// Store defines the interface for retrieving config items stored in
// various data formats.
//
// Check the constructs package for implementations.
type Store interface {
// Has check the existence of the key.
Has(keys ...string) bool
// Get retrieves the value of the given key.
Get(keys ...string) (interface{}, error)
// Set changes the value of the given key.
Set(value interface{}, keys ...string) error
// SetComment defines the comment for the given key.
SetComment(comment string, keys ...string) error
// Used when deserializing config items.
io.ReaderFrom
// Used when serializing config items.
io.WriterTo
// StructTag returns the tag id used in struct field tags for the data format.
// Field tags set to "-" are ignored.
StructTag() string
}
func ioLoad(from FromIO, LookupFn LookupFn) (Store, error) {
if from == nil {
return nil, nil
}
src, err := from.Load()
if err != nil {
return nil, err
}
if src == nil {
return nil, nil
}
defer src.Close()
store := from.New(LookupFn)
if _, err := store.ReadFrom(src); err != nil {
return nil, err
}
return store, nil
}
func ioComment(conf Config, store Store, keys ...string) error {
name := keys[len(keys)-1]
if comment := conf.Usage(name); comment != "" {
return store.SetComment(comment, keys...)
}
return nil
}
func (c *config) ioSave(store Store, from FromIO, LookupFn LookupFn) error {
dest, err := from.Save()
if err != nil || dest == nil {
return err
}
defer dest.Close()
if store == nil {
store = from.New(LookupFn)
}
// Global comment.
if err := ioComment(c.raw, store, "", ""); err != nil {
return err
}
if err := ioEncode(c.raw, store, nil, c.root); err != nil {
return err
}
_, err = store.WriteTo(dest)
return err
}
// ioEncode encodes root into the Store storage format.
func ioEncode(conf Config, store Store, keys []string, root *structs.StructStruct) error {
tag := store.StructTag()
for _, field := range root.Fields() {
if key := field.Tag().Get(tag); len(key) > 0 && key[0] == '-' {
// Skip discarded fields.
continue
}
if c, _ := getCommand(field); c != nil {
// Do not save subcommands.
continue
}
key := field.Name()
ks := append(keys, key)
if emb := field.Embedded(); emb != nil {
if emb.Inlined() {
ks = ks[:len(ks)-1]
}
conf := emb.Interface().(Config)
if err := ioEncode(conf, store, ks, emb); err != nil {
return err
}
continue
}
v := field.Interface()
if err := store.Set(v, ks...); err != nil {
return errors.Errorf("value %v: %v", v, err)
}
if err := ioComment(conf, store, ks...); err != nil {
return err
}
}
return nil
}
func (c *config) updateIO(store Store) error {
if store == nil {
return nil
}
for _, name := range c.trans {
keys := c.fromNameAll(name, c.options.gsep)
field := c.root.Lookup(keys...)
if !store.Has(keys...) {
// Add the config item to the store for saving.
v := field.Interface()
if err := store.Set(v, keys...); err != nil {
return err
}
continue
}
v, err := store.Get(keys...)
if err != nil {
return errors.Errorf("%s: %v", name, err)
}
if err := field.Set(v); err != nil {
return err
}
}
return nil
}