conf/conf.go

224 lines
5.4 KiB
Go
Raw Normal View History

2010-03-28 22:44:26 +00:00
// This package implements a parser for configuration files.
// This allows easy reading and writing of structured configuration files.
//
2010-04-05 00:27:02 +00:00
// Given the configuration file:
//
// [default]
// host = example.com
// port = 443
// php = on
//
// [service-1]
// host = s1.example.com
// allow-writing = false
//
// To read this configuration file, do:
//
// c, err := conf.ReadConfigFile("server.conf")
// c.GetString("default", "host") // returns example.com
// c.GetInt("", "port") // returns 443 (assumes "default")
// c.GetBool("", "php") // returns true
// c.GetString("service-1", "host") // returns s1.example.com
// c.GetBool("service-1","allow-writing") // returns false
// c.GetInt("service-1", "port") // returns 0 and a GetError
//
// Note that all section and option names are case insensitive. All values are case
// sensitive.
//
// Goconfig's string substitution syntax has not been removed. However, it may be
// taken out or modified in the future.
2010-03-28 22:44:26 +00:00
package conf
import (
"regexp"
"strings"
"fmt"
)
// ConfigFile is the representation of configuration settings.
// The public interface is entirely through methods.
type ConfigFile struct {
2010-04-08 17:39:05 +00:00
data map[string]map[string]string // Maps sections to options to values.
2010-03-28 22:44:26 +00:00
}
const (
// Get Errors
SectionNotFound = iota
OptionNotFound
MaxDepthReached
// Read Errors
BlankSection
// Get and Read Errors
CouldNotParse
)
var (
2010-04-08 17:39:05 +00:00
DefaultSection = "default" // Default section name (must be lower-case).
DepthValues = 200 // Maximum allowed depth when recursively substituing variable names.
2010-03-28 22:44:26 +00:00
// Strings accepted as bool.
2010-04-08 17:39:05 +00:00
BoolStrings = map[string]bool{
"t": true,
"true": true,
"y": true,
"yes": true,
"on": true,
"1": true,
"f": false,
2010-03-28 22:44:26 +00:00
"false": false,
2010-04-08 17:39:05 +00:00
"n": false,
"no": false,
"off": false,
"0": false,
}
2010-03-28 22:44:26 +00:00
2010-04-08 17:39:05 +00:00
varRegExp = regexp.MustCompile(`%\(([a-zA-Z0-9_.\-]+)\)s`)
2010-03-28 22:44:26 +00:00
)
// 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 {
2010-04-08 17:39:05 +00:00
section = strings.ToLower(section)
2010-03-28 22:44:26 +00:00
if _, ok := c.data[section]; ok {
return false
}
2010-04-08 17:39:05 +00:00
c.data[section] = make(map[string]string)
2010-03-28 22:44:26 +00:00
2010-04-08 17:39:05 +00:00
return true
2010-03-28 22:44:26 +00:00
}
// 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 {
2010-04-08 17:39:05 +00:00
section = strings.ToLower(section)
2010-03-28 22:44:26 +00:00
switch _, ok := c.data[section]; {
case !ok:
return false
case section == DefaultSection:
2010-04-08 17:39:05 +00:00
return false // default section cannot be removed
2010-03-28 22:44:26 +00:00
default:
for o, _ := range c.data[section] {
c.data[section][o] = "", false
}
2010-04-08 17:39:05 +00:00
c.data[section] = nil, false
2010-03-28 22:44:26 +00:00
}
2010-04-08 17:39:05 +00:00
return true
2010-03-28 22:44:26 +00:00
}
// 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 {
2010-04-08 17:39:05 +00:00
c.AddSection(section) // make sure section exists
2010-03-28 22:44:26 +00:00
2010-04-08 17:39:05 +00:00
section = strings.ToLower(section)
option = strings.ToLower(option)
2010-03-28 22:44:26 +00:00
2010-04-08 17:39:05 +00:00
_, ok := c.data[section][option]
c.data[section][option] = value
2010-03-28 22:44:26 +00:00
2010-04-08 17:39:05 +00:00
return !ok
2010-03-28 22:44:26 +00:00
}
// 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 {
2010-04-08 17:39:05 +00:00
section = strings.ToLower(section)
option = strings.ToLower(option)
2010-03-28 22:44:26 +00:00
if _, ok := c.data[section]; !ok {
return false
}
2010-04-08 17:39:05 +00:00
_, ok := c.data[section][option]
c.data[section][option] = "", false
2010-03-28 22:44:26 +00:00
2010-04-08 17:39:05 +00:00
return ok
2010-03-28 22:44:26 +00:00
}
// 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 {
2010-04-08 17:39:05 +00:00
c := new(ConfigFile)
c.data = make(map[string]map[string]string)
2010-03-28 22:44:26 +00:00
2010-04-08 17:39:05 +00:00
c.AddSection(DefaultSection) // default section always exists
2010-03-28 22:44:26 +00:00
2010-04-08 17:39:05 +00:00
return c
2010-03-28 22:44:26 +00:00
}
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]
}
}
2010-04-08 17:39:05 +00:00
return l
2010-03-28 22:44:26 +00:00
}
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
}
}
}
2010-04-08 17:39:05 +00:00
return -1
2010-03-28 22:44:26 +00:00
}
type GetError struct {
2010-04-08 17:39:05 +00:00
Reason int
2010-03-28 22:44:26 +00:00
ValueType string
2010-04-08 17:39:05 +00:00
Value string
Section string
Option string
2010-03-28 22:44:26 +00:00
}
func (err GetError) String() string {
switch err.Reason {
2010-04-08 17:39:05 +00:00
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)
2010-03-28 22:44:26 +00:00
}
2010-04-08 17:39:05 +00:00
2010-03-28 22:44:26 +00:00
return "invalid get error"
}
type ReadError struct {
Reason int
2010-04-08 17:39:05 +00:00
Line string
2010-03-28 22:44:26 +00:00
}
func (err ReadError) String() string {
switch err.Reason {
2010-04-08 17:39:05 +00:00
case BlankSection:
return "empty section name not allowed"
case CouldNotParse:
return fmt.Sprintf("could not parse line: %s", err.Line)
2010-03-28 22:44:26 +00:00
}
2010-04-08 17:39:05 +00:00
2010-03-28 22:44:26 +00:00
return "invalid read error"
}