package config import "os" import "bufio" import "errors" import "strings" import "strconv" import "image/color" import "path/filepath" // Type represents the data type of a configuration parameter. type Type int const ( // string TypeString Type = iota // image/color.RGBA TypeColor // int TypeInteger // float64 TypeFloat // bool TypeBoolean ) // Config holds a list of configuration parameters. type Config struct { // LegalParameters holds the names and types of all parameters that can // be parsed. If the parser runs into a parameter that is not listed // here, it will print out an error message and keep on parsing. LegalParameters map[string] Type // Parameters holds the names and values of all parsed parameters. If a // value is non-nil, it can be safely type asserted into whatever type // was requested. Parameters map[string] any } // Load loads and parses the files /etc//.conf and // /.config//.conf. func (config *Config) Load (name string) (err error) { if config.LegalParameters == nil { config.LegalParameters = make(map[string] Type) } if config.Parameters == nil { config.Parameters = make(map[string] any) } config.loadFile("/etc/" + name + "/" + name + ".conf") var homeDirectory string homeDirectory, err = os.UserHomeDir() if err != nil { return } config.loadFile(filepath.Join(homeDirectory, "/.config/" + name + "/" + name + ".conf")) return } func (config *Config) loadFile (path string) { file, err := os.Open(path) if err != nil { return } scanner := bufio.NewScanner(file) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if len(line) == 0 { continue } if line[0] == '#' { continue } key, value, found := strings.Cut(scanner.Text(), ":") if !found { println ( "config: error in file", path + ": key-value separator missing") println(scanner.Text()) continue } key = strings.TrimSpace(key) value = strings.TrimSpace(value) what, isKnown := config.LegalParameters[key] if !isKnown { println ( "config: error in file", path + ": unknown parameter") println(scanner.Text()) continue } switch what { case TypeString: config.Parameters[key] = value case TypeColor: var valueColor color.Color valueColor, err = parseColor(value) if err != nil { println ( "config: error in file", path + ":", err) println(scanner.Text()) continue } config.Parameters[key] = valueColor case TypeInteger: var valueInt int valueInt, err = strconv.Atoi(value) if err != nil { println ( "config: error in file", path + ": malformed integer literal") println(scanner.Text()) continue } config.Parameters[key] = valueInt case TypeFloat: var valueFloat float64 valueFloat, err = strconv.ParseFloat(value, 64) if err != nil { println ( "config: error in file", path + ": malformed float literal") println(scanner.Text()) continue } config.Parameters[key] = valueFloat case TypeBoolean: value = strings.ToLower(value) truthy := value == "true" || value == "yes" || value == "on" || value == "1" config.Parameters[key] = truthy } } return } func parseColor (value string) (valueColor color.Color, err error) { if value[0] == '#' { if len(value) != 7 { err = errors.New("wrong length color literal") return } var colorInt uint64 colorInt, err = strconv.ParseUint(value[1:7], 16, 24) if err != nil { err = errors.New("malformed color literal") return } valueColor = color.RGBA { R: uint8(colorInt >> 16), G: uint8(colorInt >> 8), B: uint8(colorInt), A: 0xFF, } } else { err = errors.New("malformed color literal") return } return }