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"
|
|
|
|
}
|