3 // configureit: A library for parsing configuration files.
5 // Copyright (C) 2011, Chris Collins <chris.collins@anchor.net.au>
18 // ParseErrors are returned by ConfigNodes when they encounter a
19 // problem with their input, or by the config reader when it
21 type ParseError struct {
26 var MissingEqualsOperator = os.NewError("No equals (=) sign on non-blank line")
28 func (err *ParseError) String() string {
29 return fmt.Sprintf("%s (at line %d)", err.InnerError, err.LineNumber)
32 func NewParseError(lineNumber int, inner os.Error) os.Error {
33 err := new(ParseError)
35 err.LineNumber = lineNumber
36 err.InnerError = inner
41 // Unknown option errors are thrown when the key name (left-hand side
42 // of a config item) is unknown.
43 type UnknownOptionError struct {
48 func (err *UnknownOptionError) String() string {
49 return fmt.Sprintf("Unknown Key \"%s\" at line %d", err.Key, err.LineNumber)
52 func NewUnknownOptionError(lineNumber int, key string) os.Error {
53 err := new(UnknownOptionError)
55 err.LineNumber = lineNumber
61 // A configuration is made up of many ConfigNodes.
63 // ConfigNodes are typed, and are handled by their own node
65 type ConfigNode interface {
66 // returns the value formatted as a string. Must be parsable with
67 // Parse() to produce the same value.
70 // parses the string and set the value. Clears default.
71 // Returns errors if the results can't be read.
72 Parse(newValue string) os.Error
74 // is the current value the default?
77 // reset to the default value.
81 // This represents a configuration.
83 structure map[string]ConfigNode
86 // Create a new configuration object.
87 func New() (config *Config) {
89 config.structure = make(map[string]ConfigNode)
94 // Add the specified ConfigNode to the configuration
95 func (config *Config) Add(keyname string, newNode ConfigNode) {
96 keyname = strings.ToLower(keyname)
97 config.structure[keyname] = newNode
100 // Reset the entire configuration.
101 func (config *Config) Reset() {
102 for _, v := range config.structure {
107 // Get the named node
108 func (config *Config) Get(keyName string) ConfigNode {
109 keyName = strings.ToLower(keyName)
110 citem, found := config.structure[keyName]
117 // Save spits out the configuration to the nominated writer.
118 // if emitDefaults is true, values that are set to the default
119 // will be omitted, otherwise they will be omitted.
121 // When in doubt, you probably want emitDefaults == false.
122 func (config *Config) Write(out io.Writer, emitDefaults bool) {
123 for k,v := range config.structure {
124 if !v.IsDefault() || emitDefaults {
125 // non-default value, must write!
126 line := fmt.Sprintf("%s=%s\n", k, v)
127 io.WriteString(out, line)
132 // Read the configuration from the specified reader.
134 // Special behaviour to note:
136 // Lines beginning with '#' or ';' are treated as comments. They are
137 // not comments anywhere else on the line unless the config node parser
138 // handles it itself.
140 // Whitespace surrounding the name of a configuration key will be ignored.
142 // Configuration key names will be tested case insensitively.
144 // firstLineNumber specifies the actual first line number in the file (for
145 // partial file reads, or resume from error)
146 func (config *Config) Read(in io.Reader, firstLineNumber int) os.Error {
147 bufin := bufio.NewReader(in)
149 // position the line number before the 'first' line.
150 var lineNumber int = (firstLineNumber-1)
153 var bline []byte = nil
157 // get a whole line of input, and handle buffer exhausation
159 bline, isPrefix, err = bufin.ReadLine()
170 contline, isPrefix, err = bufin.ReadLine()
174 bline = append(bline, contline...)
176 // advance the line number
179 // back convert the bytearray to a native string.
180 line := string(bline)
182 // now, start doing unspreakable things to it! (bwahaha)
185 line = strings.TrimLeftFunc(line, unicode.IsSpace)
192 // if a comment, skip.
193 if line[0] == '#' || line[0] == ';' {
197 // since it is neither, look for an equals sign.
198 epos := strings.Index(line, "=")
200 // no =. Throw a parse error.
201 return NewParseError(lineNumber, MissingEqualsOperator)
204 // take the two slices.
205 keyname := line[0:epos]
206 rawvalue := line[epos+1:len(line)]
208 // clean up the keyname
209 keyname = strings.TrimRightFunc(keyname,unicode.IsSpace)
210 keyname = strings.ToLower(keyname)
212 // find the correct key in the config.
213 cnode := config.Get(keyname)
215 return NewUnknownOptionError(lineNumber, keyname)
217 err := cnode.Parse(rawvalue)
219 return NewParseError(lineNumber, err)