Initial commit
This commit is contained in:
168
ini.go
Normal file
168
ini.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package camfish
|
||||
|
||||
import "os"
|
||||
import "io"
|
||||
import "fmt"
|
||||
import "iter"
|
||||
import "slices"
|
||||
import "strings"
|
||||
import "strconv"
|
||||
import "path/filepath"
|
||||
|
||||
type iniConfig map[string] []iniValue
|
||||
|
||||
type iniValue struct {
|
||||
value string
|
||||
file string
|
||||
line int
|
||||
column int
|
||||
}
|
||||
|
||||
// ParseINI parses a string containing INI configuration data.
|
||||
func ParseINI(filename, input string) (MutableConfig, error) {
|
||||
ini := make(iniConfig)
|
||||
configErr := ConfigError {
|
||||
File: filename,
|
||||
}
|
||||
section := ""
|
||||
for index, line := range strings.Split(input, "\n") {
|
||||
configErr.Line = index + 1
|
||||
configErr.Key = ""
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" { continue }
|
||||
if strings.HasPrefix(line, "#") { continue }
|
||||
if strings.HasPrefix(line, "[") {
|
||||
// section heading
|
||||
if !strings.HasSuffix(line, "]") {
|
||||
configErr.Err = ErrSectionHeadingMalformed
|
||||
return nil, configErr
|
||||
}
|
||||
section = strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(line, "["), "]"))
|
||||
if section == "" {
|
||||
configErr.Err = ErrSectionHeadingMalformed
|
||||
return nil, configErr
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// split key/value
|
||||
key, value, ok := strings.Cut(line, "=")
|
||||
if !ok {
|
||||
configErr.Err = ErrPairMalformed
|
||||
return nil, configErr
|
||||
}
|
||||
|
||||
// key
|
||||
key = strings.TrimSpace(key)
|
||||
if key == "" {
|
||||
configErr.Err = ErrKeyEmpty
|
||||
return nil, configErr
|
||||
}
|
||||
configErr.Key = key
|
||||
if section != "" {
|
||||
key = fmt.Sprintf("%s.%s", section, key)
|
||||
}
|
||||
|
||||
// value
|
||||
value = strings.TrimSpace(value)
|
||||
if strings.HasPrefix(value, "\"") || strings.HasPrefix(value, "'") {
|
||||
unquoted, err := strconv.Unquote(value)
|
||||
if err != nil {
|
||||
configErr.Column = strings.Index(line, "=") + 2
|
||||
configErr.Err = err
|
||||
return nil, configErr
|
||||
}
|
||||
value = unquoted
|
||||
}
|
||||
ini.Add(key, value)
|
||||
}
|
||||
return ini, nil
|
||||
}
|
||||
|
||||
// DecodeINI decodes INI data from an io.Reader. The entire reader is consumed.
|
||||
func DecodeINI(filename string, input io.Reader) (MutableConfig, error) {
|
||||
buffer, err := io.ReadAll(input)
|
||||
if err != nil { return nil, err }
|
||||
return ParseINI(filename, string(buffer))
|
||||
}
|
||||
|
||||
func (ini iniConfig) Add(key, value string) {
|
||||
key = canonicalINIKey(key)
|
||||
ini[key] = append(ini[key], iniValue {
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
|
||||
func (ini iniConfig) Del(key string) {
|
||||
key = canonicalINIKey(key)
|
||||
delete(ini, key)
|
||||
}
|
||||
|
||||
func (ini iniConfig) Get(key string) string {
|
||||
key = canonicalINIKey(key)
|
||||
slice, ok := ini[key]
|
||||
if !ok { return "" }
|
||||
if len(slice) == 0 { return "" }
|
||||
return slice[0].value
|
||||
}
|
||||
|
||||
func (ini iniConfig) GetAll(key string) iter.Seq2[int, string] {
|
||||
return func(yield func(int, string) bool) {
|
||||
for index, value := range ini[key] {
|
||||
if !yield(index, value.value) { return }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ini iniConfig) Set(key, value string) {
|
||||
key = canonicalINIKey(key)
|
||||
valueInfo := iniValue { }
|
||||
if prevValues := ini[key]; len(prevValues) > 0 {
|
||||
valueInfo = prevValues[0]
|
||||
}
|
||||
valueInfo.value = value
|
||||
ini[key] = []iniValue { valueInfo }
|
||||
}
|
||||
|
||||
func (ini iniConfig) NewConfigError(key string, index int, wrapped error) ConfigError {
|
||||
if values, ok := ini[key]; ok {
|
||||
if index > 0 && index < len(values) {
|
||||
value := values[index]
|
||||
return ConfigError {
|
||||
File: value.file,
|
||||
Key: key,
|
||||
Line: value.line,
|
||||
Column: value.column,
|
||||
Err: wrapped,
|
||||
}
|
||||
}
|
||||
}
|
||||
return ConfigError {
|
||||
Key: key,
|
||||
Err: wrapped,
|
||||
}
|
||||
}
|
||||
|
||||
func canonicalINIKey(key string) string {
|
||||
return strings.ToLower(key)
|
||||
}
|
||||
|
||||
func mergeINI(inis ...iniConfig) iniConfig {
|
||||
ini := make(iniConfig)
|
||||
for index := len(inis) - 1; index >= 0; index -- {
|
||||
for key, values := range inis[index] {
|
||||
if _, exists := ini[key]; exists { continue }
|
||||
ini[key] = slices.Clone(values)
|
||||
}
|
||||
}
|
||||
return ini
|
||||
}
|
||||
|
||||
func configFiles(program string) ([]string, error) {
|
||||
userConfig, err := os.UserConfigDir()
|
||||
if err != nil { return nil, err }
|
||||
return []string {
|
||||
filepath.Join("/etc", program),
|
||||
filepath.Join(userConfig, program),
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user