config.go raw

   1  /**
   2   * // This file is borrowed from https://github.com/vaughan0/go-ini/blob/master/ini.go
   3   * // which is distributed under the MIT license (https://github.com/vaughan0/go-ini/blob/master/LICENSE).
   4   *
   5   * Copyright (c) 2013 Vaughan Newton
   6   *
   7   * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
   8   * associated documentation files (the "Software"), to deal in the Software without restriction, including
   9   * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10   * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
  11   * following conditions:
  12   *
  13   * The above copyright notice and this permission notice shall be included in all copies or substantial
  14   * portions of the Software.
  15   *
  16   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
  17   * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  18   * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  19   * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  20   * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  21   */
  22  
  23  // Package config provides functions for parsing INI configuration files.
  24  package config
  25  
  26  import (
  27  	"bufio"
  28  	"fmt"
  29  	"io"
  30  	"os"
  31  	"regexp"
  32  	"strings"
  33  )
  34  
  35  var (
  36  	sectionRegex = regexp.MustCompile(`^\[(.*)\]$`)
  37  	assignRegex  = regexp.MustCompile(`^([^=]+)=(.*)$`)
  38  )
  39  
  40  // ErrSyntax is returned when there is a syntax error in an INI file.
  41  type ErrSyntax struct {
  42  	Line   int
  43  	Source string // The contents of the erroneous line, without leading or trailing whitespace
  44  }
  45  
  46  func (e ErrSyntax) Error() string {
  47  	return fmt.Sprintf("invalid INI syntax on line %d: %s", e.Line, e.Source)
  48  }
  49  
  50  // A File represents a parsed INI file.
  51  type File map[string]Section
  52  
  53  // A Section represents a single section of an INI file.
  54  type Section map[string]string
  55  
  56  // Returns a named Section. A Section will be created if one does not already exist for the given name.
  57  func (f File) Section(name string) Section {
  58  	section := f[name]
  59  	if section == nil {
  60  		section = make(Section)
  61  		f[name] = section
  62  	}
  63  	return section
  64  }
  65  
  66  // Looks up a value for a key in a section and returns that value, along with a boolean result similar to a map lookup.
  67  func (f File) Get(section, key string) (value string, ok bool) {
  68  	if s := f[section]; s != nil {
  69  		value, ok = s[key]
  70  	}
  71  	return
  72  }
  73  
  74  // Loads INI data from a reader and stores the data in the File.
  75  func (f File) Load(in io.Reader) error {
  76  	bufin, ok := in.(*bufio.Reader)
  77  	if !ok {
  78  		bufin = bufio.NewReader(in)
  79  	}
  80  	return parseFile(bufin, f)
  81  }
  82  
  83  // Loads INI data from a named file and stores the data in the File.
  84  func (f File) LoadFile(file string) (err error) {
  85  	in, err := os.Open(file)
  86  	if err != nil {
  87  		return
  88  	}
  89  	defer in.Close()
  90  	return f.Load(in)
  91  }
  92  
  93  func parseFile(in *bufio.Reader, file File) (err error) {
  94  	section := ""
  95  	lineNum := 0
  96  	for done := false; !done; {
  97  		var line string
  98  		if line, err = in.ReadString('\n'); err != nil {
  99  			if err == io.EOF {
 100  				done = true
 101  			} else {
 102  				return
 103  			}
 104  		}
 105  		lineNum++
 106  		line = strings.TrimSpace(line)
 107  		if len(line) == 0 {
 108  			// Skip blank lines
 109  			continue
 110  		}
 111  		if line[0] == ';' || line[0] == '#' {
 112  			// Skip comments
 113  			continue
 114  		}
 115  
 116  		if groups := assignRegex.FindStringSubmatch(line); groups != nil {
 117  			key, val := groups[1], groups[2]
 118  			key, val = strings.TrimSpace(key), strings.TrimSpace(val)
 119  			file.Section(section)[key] = val
 120  		} else if groups := sectionRegex.FindStringSubmatch(line); groups != nil {
 121  			name := strings.TrimSpace(groups[1])
 122  			section = name
 123  			// Create the section if it does not exist
 124  			file.Section(section)
 125  		} else {
 126  			return ErrSyntax{Line: lineNum, Source: line}
 127  		}
 128  
 129  	}
 130  	return nil
 131  }
 132  
 133  // Loads and returns a File from a reader.
 134  func Load(in io.Reader) (File, error) {
 135  	file := make(File)
 136  	err := file.Load(in)
 137  	return file, err
 138  }
 139  
 140  // Loads and returns an INI File from a file on disk.
 141  func LoadFile(filename string) (File, error) {
 142  	file := make(File)
 143  	err := file.LoadFile(filename)
 144  	return file, err
 145  }
 146