-
Notifications
You must be signed in to change notification settings - Fork 0
/
config.go
140 lines (120 loc) · 3.02 KB
/
config.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
// Package usrconfig provides a minimal interface to store and update user specific
// application config on the supported platform. It takes away guess work and indecisiveness by
// using the default user config directory based on the running platform.
//
// It supports structs of `json`, `yaml` and `xml` format.
//
// It's as simple as:
// type MyConfig struct {
// Name string `yaml:"user_name"`
// Email string `yaml:"user_email"`
// }
//
// var conf MyConfig
// usrconfig.Load(&conf, "my-app")
//
// conf.Email = "[email protected]"
// usrconfig.Update(conf, "my-app")
package usrconfig
import (
"encoding/json"
"encoding/xml"
"errors"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"github.com/go-yaml/yaml"
)
const (
cftJSON = "json"
cftYAML = "yaml"
cftXML = "xml"
)
// Load will load an existing "config" for "app" saved under the
// user's config directory. If no config file exists for user,
// "config" remains empty and no error is returned.
func Load(config interface{}, app string) error {
cft, err := configFileType(config)
if err != nil {
return err
}
configFilePath, err := configFilePath(app)
if err != nil {
return err
}
configBody, err := ioutil.ReadFile(configFilePath)
switch {
case err == nil:
// NOOP
break
case os.IsNotExist(err):
return nil // config doesn't exist, treat it as empty
default:
return err
}
switch cft {
case cftJSON:
err = json.Unmarshal(configBody, config)
case cftYAML:
err = yaml.Unmarshal(configBody, config)
case cftXML:
err = xml.Unmarshal(configBody, config)
}
return err
}
// Update encodes the provided "config" and saves it to the "app" config file.
// The file is written to the default user config directory on the platform.
func Update(config interface{}, app string) error {
cft, err := configFileType(config)
if err != nil {
return err
}
configFilePath, err := configFilePath(app)
if err != nil {
return err
}
var cfgBody []byte
switch cft {
case cftJSON:
cfgBody, err = json.MarshalIndent(config, "", " ")
case cftYAML:
cfgBody, err = yaml.Marshal(config)
case cftXML:
cfgBody, err = xml.MarshalIndent(config, "", " ")
}
if err != nil {
return err
}
return ioutil.WriteFile(configFilePath, cfgBody, os.ModePerm)
}
func configFilePath(app string) (string, error) {
configDir, err := os.UserConfigDir()
if err != nil {
return "", err
}
err = os.MkdirAll(filepath.Join(configDir, app), os.ModePerm)
if err != nil {
return "", err
}
return filepath.Join(configDir, app, "config"), nil
}
func configFileType(config interface{}) (string, error) {
t := reflect.TypeOf(config)
if t.Kind() == reflect.Ptr {
// Extract the underlying object
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return "", errors.New("config must be a struct")
}
if t.NumField() < 1 {
return "", errors.New("config must have at least one field")
}
for _, cft := range []string{cftJSON, cftYAML, cftXML} {
if _, ok := t.Field(0).Tag.Lookup(cft); ok {
return cft, nil
}
}
return "", errors.New("unsupported config struct tag")
}