| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616 |
- package config
- import (
- "bufio"
- "errors"
- "fmt"
- "io"
- "os"
- "reflect"
- "sort"
- "strconv"
- "strings"
- )
- type configSection struct {
- name string
- values map[string]interface{}
- }
- /*
- Config holds the contents of an ini file organized into sections.
- */
- type Config struct {
- configSection
- sections map[string]*configSection
- }
- /*
- LoadConfiguration takes a path, treats it as a file and scans it for an ini configuration.
- */
- func LoadConfiguration(path string) (*Config, error) {
- config := new(Config)
- err := config.InitializeFromPath(path)
- if err != nil {
- return nil, err
- }
- return config, nil
- }
- /*
- LoadConfigurationFromReader takes a reader and scans it for an ini configuration.
- The caller should close the reader.
- */
- func LoadConfigurationFromReader(input io.Reader) (*Config, error) {
- config := new(Config)
- err := config.InitializeFromReader(input)
- if err != nil {
- return nil, err
- }
- return config, nil
- }
- /*
- InitializeFromPath takes a path, treats it as a file and scans it for an ini configuration.
- */
- func (config *Config) InitializeFromPath(path string) error {
- f, err := os.Open(path)
- if err != nil {
- return err
- }
- defer f.Close()
- return config.InitializeFromReader(bufio.NewReader(f))
- }
- /*
- InitializeFromReader takes a reader and scans it for an ini configuration.
- The caller should close the reader.
- */
- func (config *Config) InitializeFromReader(input io.Reader) error {
- var currentSection *configSection
- scanner := bufio.NewScanner(input)
- config.values = make(map[string]interface{})
- config.sections = make(map[string]*configSection)
- for scanner.Scan() {
- curLine := scanner.Text()
- curLine = strings.TrimSpace(curLine)
- if len(curLine) == 0 {
- continue // ignore empty lines
- }
- if strings.HasPrefix(curLine, ";") || strings.HasPrefix(curLine, "#") {
- continue // comment
- }
- if strings.HasPrefix(curLine, "[") {
- if !strings.HasSuffix(curLine, "]") {
- return errors.New("mini: section names must be surrounded by [ and ], as in [section]")
- }
- sectionName := curLine[1 : len(curLine)-1]
- if sect, ok := config.sections[sectionName]; !ok { //reuse sections
- currentSection = new(configSection)
- currentSection.name = sectionName
- currentSection.values = make(map[string]interface{})
- config.sections[currentSection.name] = currentSection
- } else {
- currentSection = sect
- }
- continue
- }
- index := strings.Index(curLine, "=")
- if index <= 0 {
- return errors.New("mini: configuration format requires an equals between the key and value")
- }
- key := strings.ToLower(strings.TrimSpace(curLine[0:index]))
- isArray := strings.HasSuffix(key, "[]")
- if isArray {
- key = key[0 : len(key)-2]
- }
- value := strings.TrimSpace(curLine[index+1:])
- value = strings.Trim(value, "\"'") //clear quotes
- valueMap := config.values
- if currentSection != nil {
- valueMap = currentSection.values
- }
- if isArray {
- arr := valueMap[key]
- if arr == nil {
- arr = make([]interface{}, 0)
- valueMap[key] = arr
- }
- valueMap[key] = append(arr.([]interface{}), value)
- } else {
- valueMap[key] = value
- }
- }
- return scanner.Err()
- }
- /*
- SetName sets the config's name, which allows it to be returned in SectionNames, or in get functions that take a name.
- */
- func (config *Config) SetName(name string) {
- config.name = name
- }
- //Return non-array values
- func get(values map[string]interface{}, key string) interface{} {
- if len(key) == 0 || values == nil {
- return nil
- }
- key = strings.ToLower(key)
- val, ok := values[key]
- if ok {
- switch val.(type) {
- case []interface{}:
- return nil
- default:
- return val
- }
- }
- return nil
- }
- //Return array values
- func getArray(values map[string]interface{}, key string) []interface{} {
- if len(key) == 0 || values == nil {
- return nil
- }
- key = strings.ToLower(key)
- val, ok := values[key]
- if ok {
- switch v := val.(type) {
- case []interface{}:
- return v
- default:
- retVal := make([]interface{}, 1)
- retVal[0] = val
- return retVal
- }
- }
- return nil
- }
- func getString(values map[string]interface{}, key string, def string) string {
- val := get(values, key)
- if val != nil {
- str, err := strconv.Unquote(fmt.Sprintf("\"%v\"", val))
- if err == nil {
- return str
- }
- return def
- }
- return def
- }
- func getBoolean(values map[string]interface{}, key string, def bool) bool {
- val := get(values, key)
- if val != nil {
- retVal, err := strconv.ParseBool(fmt.Sprint(val))
- if err != nil {
- return def
- }
- return retVal
- }
- return def
- }
- func getInteger(values map[string]interface{}, key string, def int64) int64 {
- val := get(values, key)
- if val != nil {
- retVal, err := strconv.ParseInt(fmt.Sprint(val), 0, 64)
- if err != nil {
- return def
- }
- return retVal
- }
- return def
- }
- func getFloat(values map[string]interface{}, key string, def float64) float64 {
- val := get(values, key)
- if val != nil {
- retVal, err := strconv.ParseFloat(fmt.Sprint(val), 64)
- if err != nil {
- return def
- }
- return retVal
- }
- return def
- }
- func getStrings(values map[string]interface{}, key string) []string {
- val := getArray(values, key)
- if val != nil {
- retVal := make([]string, len(val))
- var err error
- for i, v := range val {
- retVal[i], err = strconv.Unquote(fmt.Sprintf("\"%v\"", v))
- if err != nil {
- return nil
- }
- }
- return retVal
- }
- return nil
- }
- func getIntegers(values map[string]interface{}, key string) []int64 {
- val := getArray(values, key)
- if val != nil {
- retVal := make([]int64, len(val))
- var err error
- for i, v := range val {
- retVal[i], err = strconv.ParseInt(fmt.Sprint(v), 0, 64)
- if err != nil {
- return nil
- }
- }
- return retVal
- }
- return nil
- }
- func getFloats(values map[string]interface{}, key string) []float64 {
- val := getArray(values, key)
- if val != nil {
- retVal := make([]float64, len(val))
- var err error
- for i, v := range val {
- retVal[i], err = strconv.ParseFloat(fmt.Sprint(v), 64)
- if err != nil {
- return nil
- }
- }
- return retVal
- }
- return nil
- }
- /*
- String looks for the specified key and returns it as a string. If not found the default value def is returned.
- */
- func (config *Config) String(key string, def string) string {
- return getString(config.values, key, def)
- }
- /*
- Boolean looks for the specified key and returns it as a bool. If not found the default value def is returned.
- */
- func (config *Config) Boolean(key string, def bool) bool {
- return getBoolean(config.values, key, def)
- }
- /*
- Integer looks for the specified key and returns it as an int. If not found the default value def is returned.
- */
- func (config *Config) Integer(key string, def int64) int64 {
- return getInteger(config.values, key, def)
- }
- /*
- Float looks for the specified key and returns it as a float. If not found the default value def is returned.
- */
- func (config *Config) Float(key string, def float64) float64 {
- return getFloat(config.values, key, def)
- }
- /*
- Strings looks for an array of strings under the provided key.
- If no matches are found nil is returned. If only one matches an array of 1 is returned.
- */
- func (config *Config) Strings(key string) []string {
- return getStrings(config.values, key)
- }
- /*
- Integers looks for an array of ints under the provided key.
- If no matches are found nil is returned.
- */
- func (config *Config) Integers(key string) []int64 {
- return getIntegers(config.values, key)
- }
- /*
- Floats looks for an array of floats under the provided key.
- If no matches are found nil is returned.
- */
- func (config *Config) Floats(key string) []float64 {
- return getFloats(config.values, key)
- }
- func (config *Config) sectionForName(sectionName string) *configSection {
- if len(sectionName) == 0 || sectionName == config.name {
- return &(config.configSection)
- }
- return config.sections[sectionName]
- }
- /*
- StringFromSection looks for the specified key and returns it as a string. If not found the default value def is returned.
- If the section name matches the config.name or "" the global data is searched.
- */
- func (config *Config) StringFromSection(sectionName string, key string, def string) string {
- section := config.sectionForName(sectionName)
- if section != nil {
- return getString(section.values, key, def)
- }
- return def
- }
- /*
- BooleanFromSection looks for the specified key and returns it as a boolean. If not found the default value def is returned.
- If the section name matches the config.name or "" the global data is searched.
- */
- func (config *Config) BooleanFromSection(sectionName string, key string, def bool) bool {
- section := config.sectionForName(sectionName)
- if section != nil {
- return getBoolean(section.values, key, def)
- }
- return def
- }
- /*
- IntegerFromSection looks for the specified key and returns it as an int64. If not found the default value def is returned.
- If the section name matches the config.name or "" the global data is searched.
- */
- func (config *Config) IntegerFromSection(sectionName string, key string, def int64) int64 {
- section := config.sectionForName(sectionName)
- if section != nil {
- return getInteger(section.values, key, def)
- }
- return def
- }
- /*
- FloatFromSection looks for the specified key and returns it as a float. If not found the default value def is returned.
- If the section name matches the config.name or "" the global data is searched.
- */
- func (config *Config) FloatFromSection(sectionName string, key string, def float64) float64 {
- section := config.sectionForName(sectionName)
- if section != nil {
- return getFloat(section.values, key, def)
- }
- return def
- }
- /*
- StringsFromSection returns the value of an array key, if the value of the key is a non-array, then
- that value is returned in an array of length 1.
- If the section name matches the config.name or "" the global data is searched.
- */
- func (config *Config) StringsFromSection(sectionName string, key string) []string {
- section := config.sectionForName(sectionName)
- if section != nil {
- return getStrings(section.values, key)
- }
- return nil
- }
- /*
- IntegersFromSection looks for an array of integers in the provided section and under the provided key.
- If no matches are found nil is returned.
- */
- func (config *Config) IntegersFromSection(sectionName string, key string) []int64 {
- section := config.sectionForName(sectionName)
- if section != nil {
- return getIntegers(section.values, key)
- }
- return nil
- }
- /*
- FloatsFromSection looks for an array of floats in the provided section and under the provided key.
- If no matches are found nil is returned.
- If the section name matches the config.name or "" the global data is searched.
- */
- func (config *Config) FloatsFromSection(sectionName string, key string) []float64 {
- section := config.sectionForName(sectionName)
- if section != nil {
- return getFloats(section.values, key)
- }
- return nil
- }
- /*
- DataFromSection reads the values of a section into a struct. The values should be of the types:
- bool
- string
- []string
- int64
- []int64
- float64
- []float64
- Values that are missing in the section are not set, and values that are missing in the
- struct but present in the section are ignored.
- If the section name matches the config.name or "" the global data is searched.
- */
- func (config *Config) DataFromSection(sectionName string, data interface{}) bool {
- section := config.sectionForName(sectionName)
- if section == nil {
- return false
- }
- values := section.values
- fields := reflect.ValueOf(data).Elem()
- dataType := fields.Type()
- for i := 0; i < fields.NumField(); i++ {
- field := fields.Field(i)
- if !field.CanSet() {
- continue
- }
- fieldType := dataType.Field(i)
- fieldName := fieldType.Name
- switch field.Type().Kind() {
- case reflect.Bool:
- field.SetBool(getBoolean(values, fieldName, field.Interface().(bool)))
- case reflect.Int64:
- field.SetInt(getInteger(values, fieldName, field.Interface().(int64)))
- case reflect.Float64:
- field.SetFloat(getFloat(values, fieldName, field.Interface().(float64)))
- case reflect.String:
- field.SetString(getString(values, fieldName, field.Interface().(string)))
- case reflect.Array, reflect.Slice:
- switch fieldType.Type.Elem().Kind() {
- case reflect.Int64:
- ints := getIntegers(values, fieldName)
- if ints != nil {
- field.Set(reflect.ValueOf(ints))
- }
- case reflect.Float64:
- floats := getFloats(values, fieldName)
- if floats != nil {
- field.Set(reflect.ValueOf(floats))
- }
- case reflect.String:
- strings := getStrings(values, fieldName)
- if strings != nil {
- field.Set(reflect.ValueOf(strings))
- }
- }
- }
- }
- return true
- }
- /*
- Keys returns all of the global keys in the config.
- */
- func (config *Config) Keys() []string {
- keys := make([]string, 0, len(config.values))
- for key := range config.values {
- keys = append(keys, key)
- }
- sort.Strings(keys)
- return keys
- }
- /*
- KeysForSection returns all of the keys found in the section named sectionName.
- If the section name matches the config.name or "" the global data is searched.
- */
- func (config *Config) KeysForSection(sectionName string) []string {
- section := config.sectionForName(sectionName)
- if section != nil {
- keys := make([]string, 0, len(section.values))
- for key := range section.values {
- keys = append(keys, key)
- }
- sort.Strings(keys)
- return keys
- }
- return nil
- }
- /*
- SectionNames returns the names for each of the sections in a config structure. If the config was assigned
- a name, that name is included in the list. If the name is not set, then only explicitely named sections are returned.
- */
- func (config *Config) SectionNames() []string {
- sectionNames := make([]string, 0, len(config.sections))
- for name := range config.sections {
- sectionNames = append(sectionNames, name)
- }
- if len(config.name) > 0 {
- sectionNames = append(sectionNames, config.name)
- }
- sort.Strings(sectionNames)
- return sectionNames
- }
|