|
@@ -13,23 +13,60 @@
|
|
|
// limitations under the License.
|
|
// limitations under the License.
|
|
|
//
|
|
//
|
|
|
// author: Cong Ding <dinggnu@gmail.com>
|
|
// author: Cong Ding <dinggnu@gmail.com>
|
|
|
-//
|
|
|
|
|
|
|
+
|
|
|
package config
|
|
package config
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
"bufio"
|
|
"bufio"
|
|
|
"errors"
|
|
"errors"
|
|
|
|
|
+ "fmt"
|
|
|
|
|
+ "io/ioutil"
|
|
|
"os"
|
|
"os"
|
|
|
"strings"
|
|
"strings"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
var commentPrefix = []string{"//", "#", ";"}
|
|
var commentPrefix = []string{"//", "#", ";"}
|
|
|
|
|
|
|
|
-func Read(filename string) (map[string]string, error) {
|
|
|
|
|
- var res = map[string]string{}
|
|
|
|
|
- in, err := os.Open(filename)
|
|
|
|
|
|
|
+// Config struct constructs a new configuration handler.
|
|
|
|
|
+type Config struct {
|
|
|
|
|
+ filename string
|
|
|
|
|
+ config map[string]map[string]string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// NewConfig function cnstructs a new Config struct with filename. You have to
|
|
|
|
|
+// call Read() function to let it read from the file. Otherwise you will get
|
|
|
|
|
+// empty string (i.e., "") when you are calling Get() function. Another usage
|
|
|
|
|
+// is that you call NewConfig() function and then call Add()/Set() function to
|
|
|
|
|
+// add new key-values to the configuration. Finally you can call Write()
|
|
|
|
|
+// function to write the new configuration to the file.
|
|
|
|
|
+func NewConfig(filename string) *Config {
|
|
|
|
|
+ c := new(Config)
|
|
|
|
|
+ c.filename = filename
|
|
|
|
|
+ c.config = make(map[string]map[string]string)
|
|
|
|
|
+ return c
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Filename function returns the filename of the configuration.
|
|
|
|
|
+func (c *Config) Filename() string {
|
|
|
|
|
+ return c.filename
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// SetFilename function sets the filename of the configuration.
|
|
|
|
|
+func (c *Config) SetFilename(filename string) {
|
|
|
|
|
+ c.filename = filename
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Reset function reset the map in the configuration.
|
|
|
|
|
+func (c *Config) Reset() {
|
|
|
|
|
+ c.config = make(map[string]map[string]string)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Read function reads configurations from the file defined in
|
|
|
|
|
+// Config.filename.
|
|
|
|
|
+func (c *Config) Read() error {
|
|
|
|
|
+ in, err := os.Open(c.filename)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return res, err
|
|
|
|
|
|
|
+ return err
|
|
|
}
|
|
}
|
|
|
defer in.Close()
|
|
defer in.Close()
|
|
|
scanner := bufio.NewScanner(in)
|
|
scanner := bufio.NewScanner(in)
|
|
@@ -40,9 +77,9 @@ func Read(filename string) (map[string]string, error) {
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
if line == "" {
|
|
if line == "" {
|
|
|
- sec := checkSection(scanner.Text())
|
|
|
|
|
- if sec != "" {
|
|
|
|
|
- section = sec + "."
|
|
|
|
|
|
|
+ sec, ok := checkSection(scanner.Text())
|
|
|
|
|
+ if ok {
|
|
|
|
|
+ section = sec
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -54,40 +91,103 @@ func Read(filename string) (map[string]string, error) {
|
|
|
line = line[:len(line)-1]
|
|
line = line[:len(line)-1]
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
- key, value, err := checkLine(line)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return res, errors.New("WRONG: " + line)
|
|
|
|
|
|
|
+ key, value, ok := checkLine(line)
|
|
|
|
|
+ if !ok {
|
|
|
|
|
+ return errors.New("WRONG: " + line)
|
|
|
}
|
|
}
|
|
|
- res[section+key] = value
|
|
|
|
|
|
|
+ c.Set(section, key, value)
|
|
|
line = ""
|
|
line = ""
|
|
|
}
|
|
}
|
|
|
- return res, nil
|
|
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Get function returns the value of a key in the configuration. If the key
|
|
|
|
|
+// does not exist, it returns empty string (i.e., "").
|
|
|
|
|
+func (c *Config) Get(section string, key string) string {
|
|
|
|
|
+ value, ok := c.config[section][key]
|
|
|
|
|
+ if !ok {
|
|
|
|
|
+ return ""
|
|
|
|
|
+ }
|
|
|
|
|
+ return value
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func checkSection(line string) string {
|
|
|
|
|
|
|
+// Set function updates the value of a key in the configuration. Function
|
|
|
|
|
+// Set() is exactly the same as function Add().
|
|
|
|
|
+func (c *Config) Set(section string, key string, value string) {
|
|
|
|
|
+ _, ok := c.config[section]
|
|
|
|
|
+ if !ok {
|
|
|
|
|
+ c.config[section] = make(map[string]string)
|
|
|
|
|
+ }
|
|
|
|
|
+ c.config[section][key] = value
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Add function adds a new key to the configuration. Function Add() is exactly
|
|
|
|
|
+// the same as function Set().
|
|
|
|
|
+func (c *Config) Add(section string, key string, value string) {
|
|
|
|
|
+ c.Set(section, key, value)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Del function deletes a key from the configuration.
|
|
|
|
|
+func (c *Config) Del(section string, key string) {
|
|
|
|
|
+ _, ok := c.config[section]
|
|
|
|
|
+ if ok {
|
|
|
|
|
+ delete(c.config[section], key)
|
|
|
|
|
+ if len(c.config[section]) == 0 {
|
|
|
|
|
+ delete(c.config, section)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Write function writes the updated configuration back.
|
|
|
|
|
+func (c *Config) Write() error {
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// WriteTo function writes the configuration to a new file. This function
|
|
|
|
|
+// re-organizes the configuration and deletes all the comments.
|
|
|
|
|
+func (c *Config) WriteTo(filename string) error {
|
|
|
|
|
+ content := ""
|
|
|
|
|
+ for k, v := range c.config {
|
|
|
|
|
+ format := "%v = %v\n"
|
|
|
|
|
+ if k != "" {
|
|
|
|
|
+ content += fmt.Sprintf("[%v]\n", k)
|
|
|
|
|
+ format = "\t" + format
|
|
|
|
|
+ }
|
|
|
|
|
+ for key, value := range v {
|
|
|
|
|
+ content += fmt.Sprintf(format, key, value)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return ioutil.WriteFile(filename, []byte(content), 0644)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// To check this line is a section or not. If it is not a section, it returns
|
|
|
|
|
+// "".
|
|
|
|
|
+func checkSection(line string) (string, bool) {
|
|
|
line = strings.TrimSpace(line)
|
|
line = strings.TrimSpace(line)
|
|
|
lineLen := len(line)
|
|
lineLen := len(line)
|
|
|
if lineLen < 2 {
|
|
if lineLen < 2 {
|
|
|
- return ""
|
|
|
|
|
|
|
+ return "", false
|
|
|
}
|
|
}
|
|
|
if line[0] == '[' && line[lineLen-1] == ']' {
|
|
if line[0] == '[' && line[lineLen-1] == ']' {
|
|
|
- return line[1 : lineLen-1]
|
|
|
|
|
|
|
+ return line[1 : lineLen-1], true
|
|
|
}
|
|
}
|
|
|
- return ""
|
|
|
|
|
|
|
+ return "", false
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func checkLine(line string) (string, string, error) {
|
|
|
|
|
|
|
+// To check this line is a valid key-value pair or not.
|
|
|
|
|
+func checkLine(line string) (string, string, bool) {
|
|
|
key := ""
|
|
key := ""
|
|
|
value := ""
|
|
value := ""
|
|
|
sp := strings.SplitN(line, "=", 2)
|
|
sp := strings.SplitN(line, "=", 2)
|
|
|
if len(sp) != 2 {
|
|
if len(sp) != 2 {
|
|
|
- return key, value, errors.New("WRONG: " + line)
|
|
|
|
|
|
|
+ return key, value, false
|
|
|
}
|
|
}
|
|
|
key = strings.TrimSpace(sp[0])
|
|
key = strings.TrimSpace(sp[0])
|
|
|
value = strings.TrimSpace(sp[1])
|
|
value = strings.TrimSpace(sp[1])
|
|
|
- return key, value, nil
|
|
|
|
|
|
|
+ return key, value, true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// To check this line is a whole line comment or not.
|
|
|
func checkComment(line string) bool {
|
|
func checkComment(line string) bool {
|
|
|
line = strings.TrimSpace(line)
|
|
line = strings.TrimSpace(line)
|
|
|
for p := range commentPrefix {
|
|
for p := range commentPrefix {
|