Files
conf/parser.go
Dmitry Fedotov b88b2fd5e6 WIP on v1
2025-04-09 02:13:47 +03:00

109 lines
2.1 KiB
Go

package conf
import (
"bufio"
"errors"
"fmt"
"io"
"os"
"strings"
)
const (
strComment = "#"
strEqSign = "="
)
var (
ErrFormat = errors.New("conf: line does not match \"key = value\" pattern")
ErrDuplicateKey = errors.New("conf: duplicate key found")
)
// Open reads values from file. It returns nil and error if os.Open(filename) fails.
// It would be wise to always check returned error.
// ParseFile captures two types of values: "key = value" and "option". Either key value pair or
// option must be put in its own line in the file.
// Key or option must be a single word. In example line:
//
// "option1 option2"
//
// only option1 will be captured. option2 needs to be in a separate line in the file to take effect.
// In line "key = value1,value2,value3" all of value1, value2, and value3 will be
// captured. They can be later accesed separately with Setting's Split() method.
func Open(filename string) (*Conf, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
c, err := parseReader(file)
if err != nil {
return nil, err
}
return c, file.Close()
}
// Read reads from r and returns Config.
func Read(r io.Reader) (*Conf, error) {
return parseReader(r)
}
func parseReader(r io.Reader) (*Conf, error) {
settings := make(map[string]string)
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
line = trim(stripComment(line))
if line == "" {
continue
}
key, value, ok := toKV(line)
if !ok {
return nil, ErrFormat
}
if _, ok := settings[key]; ok {
return nil, fmt.Errorf("%w: %s", ErrDuplicateKey, key)
}
settings[key] = value
}
if err := scanner.Err(); err != nil {
return nil, err
}
return &Conf{
settings: settings,
}, nil
}
func toKV(s string) (string, string, bool) {
k, v, ok := strings.Cut(s, strEqSign)
if !ok || k == "" || v == "" {
return "", "", false
}
return trim(k), trim(v), true
}
func trim(s string) string {
s = strings.TrimSpace(s)
return s
}
func stripComment(s string) string {
idx := strings.Index(s, strComment)
if idx > -1 {
s = s[:idx]
}
return s
}