// module conf // // import "code.uint32.ru/tiny/conf" // // Module conf implements a very simple config parser with two types of // values: key value pairs and single word options. Each of these must be // put on separate line in a file like this: // // key1 = value // key2 = value1, value2, value3 // key3=v1,v2,v3 // option1 // option2 // // Values can also be read from any io.Reader or io.ReadCloser. Note that // values in key value pairs mey either be separated with spaces or not. // Typical use case would look like this: // // config, err := conf.ParseFile("filename") // if err != nil { // // Means we failed to read from file // // config variable is now nil and unusable // } // value, err := config.Find("mykey") // if err != nil { // // Means that key has not been found // } // // value now holds conf.Setting. // n, err := value.Float64() // tries to parse float from Setting.Value field. // //if err is nil then n holds float64. // // There is also a quicker Get() method which returns no errors // ("i'm feeling lucky" way to lookup values). If it does not find // requested key, the returned Setting has empty string in Value field. // // value2 := config.Get("otherkey") // mybool, err := value2.Bool() // tries to interpret Setting.Value field as bool // // mybool now holds boolean if "otherkey" was found and error returned // // by Bool() method is nil. // // Even shorter syntax would be: // // listnumbers, err := config.Get("numbers").IntSlice() // // Note that we'd still like to check for errors even if // // we're sure the key exists to make sure all values are converted. // // listnumbers holds slice of ints. If err is nil all of found values // // have been converted successfully. // // To check whether single-word options were found use: // // if config.HasOption("wordoption") { // // do something // } // // See description of module's other methods which are // quite self-explanatory. package conf import ( "bufio" "errors" "fmt" "io" "os" ) var ( ErrFormat = errors.New("conf: line does not match \"key = value\" pattern") ErrDuplicateKey = errors.New("conf: duplicate key found") ) // Conf holds key value pairs. type Conf struct { values []*kv // values store key value pairs ("key = value" in config file) } type kv struct { k string v Value } // Open reads values from file. func Open(filename string) (*Conf, error) { c := new(Conf) if err := c.readFile(filename); err != nil { return nil, err } return c, nil } // Read reads from r and returns Conf. func Read(r io.Reader) (*Conf, error) { c := new(Conf) if err := c.read(r); err != nil { return nil, err } return c, nil } // Find looks up a the key and returns Value associated with it. // If bool is false then the returned Value would be zero. func (c *Conf) Find(key string) (Value, bool) { return c.get(key, "") } // Get returns a Value. If key was not found the returned Value // will be empty. func (c *Conf) Get(key string) Value { v, _ := c.get(key, "") return v } // GetDefault looks up a Value for requested key. // If lookup fails it returns Value set to def. func (c *Conf) GetDefault(key, def string) Value { v, _ := c.get(key, def) return v } func (c *Conf) Keys() []string { list := make([]string, 0, len(c.values)) for i := range c.values { list = append(list, c.values[i].k) } return list } func (c *Conf) Read(r io.Reader) error { return c.read(r) } func (c *Conf) ReadFile(name string) error { return c.readFile(name) } func (c *Conf) get(key string, def string) (Value, bool) { var ( v Value found bool ) for i := range c.values { if c.values[i].k == key { v = c.values[i].v found = true break } } if def != "" && !found { v = Value(def) } return v, found } func (c *Conf) readFile(name string) error { file, err := os.Open(name) if err != nil { return err } if err := c.read(file); err != nil { return err } return file.Close() } func (c *Conf) read(r io.Reader) error { uniq := make(map[string]struct{}) scanner := bufio.NewScanner(r) for scanner.Scan() { line := scanner.Text() line = trim(stripComment(line)) if line == "" { continue } key, value, ok := toKV(line, separatorKV) if !ok { return ErrFormat } if _, ok := uniq[key]; ok { return fmt.Errorf("%w: %s", ErrDuplicateKey, key) } uniq[key] = struct{}{} kv := &kv{ k: key, v: Value(value), } c.values = append(c.values, kv) } if err := scanner.Err(); err != nil { return err } return nil }