// This package implements a parser for configuration files. // This allows easy reading and writing of structured configuration files. // // You can get some example configuration files and documentation at // http://code.google.com/p/goconf/ package conf import ( "regexp" "strings" "fmt" ) // ConfigFile is the representation of configuration settings. // The public interface is entirely through methods. type ConfigFile struct { data map[string]map[string]string; // Maps sections to options to values. } const ( // Get Errors SectionNotFound = iota OptionNotFound MaxDepthReached // Read Errors BlankSection // Get and Read Errors CouldNotParse ) var ( DefaultSection = "default"; // Default section name (must be lower-case). DepthValues = 200; // Maximum allowed depth when recursively substituing variable names. // Strings accepted as bool. BoolStrings = map[string]bool{ "t": true, "true": true, "y": true, "yes": true, "on": true, "1": true, "f": false, "false": false, "n": false, "no": false, "off": false, "0": false, }; varRegExp = regexp.MustCompile(`%\(([a-zA-Z0-9_.\-]+)\)s`); ) // AddSection adds a new section to the configuration. // It returns true if the new section was inserted, and false if the section already existed. func (c *ConfigFile) AddSection(section string) bool { section = strings.ToLower(section); if _, ok := c.data[section]; ok { return false } c.data[section] = make(map[string]string); return true; } // RemoveSection removes a section from the configuration. // It returns true if the section was removed, and false if section did not exist. func (c *ConfigFile) RemoveSection(section string) bool { section = strings.ToLower(section); switch _, ok := c.data[section]; { case !ok: return false case section == DefaultSection: return false // default section cannot be removed default: for o, _ := range c.data[section] { c.data[section][o] = "", false } c.data[section] = nil, false; } return true; } // AddOption adds a new option and value to the configuration. // It returns true if the option and value were inserted, and false if the value was overwritten. // If the section does not exist in advance, it is created. func (c *ConfigFile) AddOption(section string, option string, value string) bool { c.AddSection(section); // make sure section exists section = strings.ToLower(section); option = strings.ToLower(option); _, ok := c.data[section][option]; c.data[section][option] = value; return !ok; } // RemoveOption removes a option and value from the configuration. // It returns true if the option and value were removed, and false otherwise, // including if the section did not exist. func (c *ConfigFile) RemoveOption(section string, option string) bool { section = strings.ToLower(section); option = strings.ToLower(option); if _, ok := c.data[section]; !ok { return false } _, ok := c.data[section][option]; c.data[section][option] = "", false; return ok; } // NewConfigFile creates an empty configuration representation. // This representation can be filled with AddSection and AddOption and then // saved to a file using WriteConfigFile. func NewConfigFile() *ConfigFile { c := new(ConfigFile); c.data = make(map[string]map[string]string); c.AddSection(DefaultSection); // default section always exists return c; } func stripComments(l string) string { // comments are preceded by space or TAB for _, c := range []string{" ;", "\t;", " #", "\t#"} { if i := strings.Index(l, c); i != -1 { l = l[0:i] } } return l; } func firstIndex(s string, delim []byte) int { for i := 0; i < len(s); i++ { for j := 0; j < len(delim); j++ { if s[i] == delim[j] { return i } } } return -1; } type GetError struct { Reason int ValueType string Value string Section string Option string } func (err GetError) String() string { switch err.Reason { case SectionNotFound: return fmt.Sprintf("section '%s' not found", err.Section) case OptionNotFound: return fmt.Sprintf("option '%s' not found in section '%s'", err.Option, err.Section) case CouldNotParse: return fmt.Sprintf("could not parse %s value '%s'", err.ValueType, err.Value) case MaxDepthReached: return fmt.Sprintf("possible cycle while unfolding variables: max depth of %d reached", DepthValues) } return "invalid get error" } type ReadError struct { Reason int Line string } func (err ReadError) String() string { switch err.Reason { case BlankSection: return "empty section name not allowed" case CouldNotParse: return fmt.Sprintf("could not parse line: %s", err.Line) } return "invalid read error" }