1
0

ini.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. // Package ini provides functions for parsing INI configuration files.
  2. package ini
  3. import (
  4. "bufio"
  5. "fmt"
  6. "io"
  7. "os"
  8. "regexp"
  9. "strings"
  10. )
  11. var (
  12. sectionRegex = regexp.MustCompile(`^\[(.*)\]$`)
  13. assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`)
  14. )
  15. // ErrSyntax is returned when there is a syntax error in an INI file.
  16. type ErrSyntax struct {
  17. Line int
  18. Source string // The contents of the erroneous line, without leading or trailing whitespace
  19. }
  20. func (e ErrSyntax) Error() string {
  21. return fmt.Sprintf("invalid INI syntax on line %d: %s", e.Line, e.Source)
  22. }
  23. // A File represents a parsed INI file.
  24. type File map[string]Section
  25. // A Section represents a single section of an INI file.
  26. type Section map[string]string
  27. // Returns a named Section. A Section will be created if one does not already exist for the given name.
  28. func (f File) Section(name string) Section {
  29. section := f[name]
  30. if section == nil {
  31. section = make(Section)
  32. f[name] = section
  33. }
  34. return section
  35. }
  36. // Looks up a value for a key in a section and returns that value, along with a boolean result similar to a map lookup.
  37. func (f File) Get(section, key string) (value string, ok bool) {
  38. if s := f[section]; s != nil {
  39. value, ok = s[key]
  40. }
  41. return
  42. }
  43. // Loads INI data from a reader and stores the data in the File.
  44. func (f File) Load(in io.Reader) (err error) {
  45. bufin, ok := in.(*bufio.Reader)
  46. if !ok {
  47. bufin = bufio.NewReader(in)
  48. }
  49. return parseFile(bufin, f)
  50. }
  51. // Loads INI data from a named file and stores the data in the File.
  52. func (f File) LoadFile(file string) (err error) {
  53. in, err := os.Open(file)
  54. if err != nil {
  55. return
  56. }
  57. defer in.Close()
  58. return f.Load(in)
  59. }
  60. func parseFile(in *bufio.Reader, file File) (err error) {
  61. section := ""
  62. lineNum := 0
  63. for done := false; !done; {
  64. var line string
  65. if line, err = in.ReadString('\n'); err != nil {
  66. if err == io.EOF {
  67. done = true
  68. } else {
  69. return
  70. }
  71. }
  72. lineNum++
  73. line = strings.TrimSpace(line)
  74. if len(line) == 0 {
  75. // Skip blank lines
  76. continue
  77. }
  78. if line[0] == ';' || line[0] == '#' {
  79. // Skip comments
  80. continue
  81. }
  82. if groups := assignRegex.FindStringSubmatch(line); groups != nil {
  83. key, val := groups[1], groups[2]
  84. key, val = strings.TrimSpace(key), strings.TrimSpace(val)
  85. file.Section(section)[key] = val
  86. } else if groups := sectionRegex.FindStringSubmatch(line); groups != nil {
  87. name := strings.TrimSpace(groups[1])
  88. section = name
  89. // Create the section if it does not exist
  90. file.Section(section)
  91. } else {
  92. return ErrSyntax{lineNum, line}
  93. }
  94. }
  95. return nil
  96. }
  97. // Loads and returns a File from a reader.
  98. func Load(in io.Reader) (File, error) {
  99. file := make(File)
  100. err := file.Load(in)
  101. return file, err
  102. }
  103. // Loads and returns an INI File from a file on disk.
  104. func LoadFile(filename string) (File, error) {
  105. file := make(File)
  106. err := file.LoadFile(filename)
  107. return file, err
  108. }