-
Notifications
You must be signed in to change notification settings - Fork 0
/
conf.go
194 lines (163 loc) · 5.05 KB
/
conf.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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// This package implements a parser for configuration files.
// This allows easy reading and writing of structured configuration files.
//
// Given the configuration file:
//
// [default]
// host = example.com
// port = 443
// php = on
//
// [service-1]
// host = s1.example.com
// allow-writing = false
//
// To read this configuration file, do:
//
// c, err := conf.ReadConfigFile("server.conf")
// c.GetString("default", "host") // returns example.com
// c.GetInt("", "port") // returns 443 (assumes "default")
// c.GetBool("", "php") // returns true
// c.GetString("service-1", "host") // returns s1.example.com
// c.GetBool("service-1","allow-writing") // returns false
// c.GetInt("service-1", "port") // returns 0 and a GetError
//
// Note that all section and option names are case insensitive. All values are case
// sensitive.
//
// Goconfig's string substitution syntax has not been removed. However, it may be
// taken out or modified in the future.
package conf
import (
"fmt"
"regexp"
"strings"
)
// ConfigFile is the representation of configuration settings.
// The public interface is entirely through methods.
type ConfigFile struct {
data map[string]map[string]string // Maps sections to options to values.
}
const (
// Get Errors
SectionNotFound = iota
OptionNotFound
MaxDepthReached
// Read Errors
BlankSection
// Get and Read Errors
CouldNotParse
)
var (
DefaultSection = "default" // Default section name (must be lower-case).
DepthValues = 200 // Maximum allowed depth when recursively substituing variable names.
// Strings accepted as bool.
BoolStrings = map[string]bool{
"t": true,
"true": true,
"y": true,
"yes": true,
"on": true,
"1": true,
"f": false,
"false": false,
"n": false,
"no": false,
"off": false,
"0": false,
}
varRegExp = regexp.MustCompile(`%\(([a-zA-Z0-9_.\-]+)\)s`)
)
// AddSection adds a new section to the configuration.
// It returns true if the new section was inserted, and false if the section already existed.
func (c *ConfigFile) AddSection(section string) bool {
section = strings.ToLower(section)
if _, ok := c.data[section]; ok {
return false
}
c.data[section] = make(map[string]string)
return true
}
// RemoveSection removes a section from the configuration.
// It returns true if the section was removed, and false if section did not exist.
func (c *ConfigFile) RemoveSection(section string) bool {
section = strings.ToLower(section)
switch _, ok := c.data[section]; {
case !ok:
return false
case section == DefaultSection:
return false // default section cannot be removed
default:
for o, _ := range c.data[section] {
delete(c.data[section], o)
}
delete(c.data, section)
}
return true
}
// AddOption adds a new option and value to the configuration.
// It returns true if the option and value were inserted, and false if the value was overwritten.
// If the section does not exist in advance, it is created.
func (c *ConfigFile) AddOption(section string, option string, value string) bool {
c.AddSection(section) // make sure section exists
section = strings.ToLower(section)
option = strings.ToLower(option)
_, ok := c.data[section][option]
c.data[section][option] = value
return !ok
}
// RemoveOption removes a option and value from the configuration.
// It returns true if the option and value were removed, and false otherwise,
// including if the section did not exist.
func (c *ConfigFile) RemoveOption(section string, option string) bool {
section = strings.ToLower(section)
option = strings.ToLower(option)
if _, ok := c.data[section]; !ok {
return false
}
_, ok := c.data[section][option]
delete(c.data[section], option)
return ok
}
// NewConfigFile creates an empty configuration representation.
// This representation can be filled with AddSection and AddOption and then
// saved to a file using WriteConfigFile.
func NewConfigFile() *ConfigFile {
c := new(ConfigFile)
c.data = make(map[string]map[string]string)
c.AddSection(DefaultSection) // default section always exists
return c
}
type GetError struct {
Reason int
ValueType string
Value string
Section string
Option string
}
func (err GetError) Error() string {
switch err.Reason {
case SectionNotFound:
return fmt.Sprintf("section '%s' not found", string(err.Section))
case OptionNotFound:
return fmt.Sprintf("option '%s' not found in section '%s'", string(err.Option), string(err.Section))
case CouldNotParse:
return fmt.Sprintf("could not parse %s value '%s'", string(err.ValueType), string(err.Value))
case MaxDepthReached:
return fmt.Sprintf("possible cycle while unfolding variables: max depth of %d reached", int(DepthValues))
}
return "invalid get error"
}
type ReadError struct {
Reason int
Line string
}
func (err ReadError) Error() string {
switch err.Reason {
case BlankSection:
return "empty section name not allowed"
case CouldNotParse:
return fmt.Sprintf("could not parse line: %s", string(err.Line))
}
return "invalid read error"
}