| // Package ini provides functions for parsing INI configuration files. |
| package ini |
| |
| import ( |
| "bufio" |
| "fmt" |
| "io" |
| "os" |
| "regexp" |
| "strings" |
| ) |
| |
| var ( |
| sectionRegex = regexp.MustCompile(`^\[(.*)\]$`) |
| assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`) |
| ) |
| |
| // ErrSyntax is returned when there is a syntax error in an INI file. |
| type ErrSyntax struct { |
| Line int |
| Source string // The contents of the erroneous line, without leading or trailing whitespace |
| } |
| |
| func (e ErrSyntax) Error() string { |
| return fmt.Sprintf("invalid INI syntax on line %d: %s", e.Line, e.Source) |
| } |
| |
| // A File represents a parsed INI file. |
| type File map[string]Section |
| |
| // A Section represents a single section of an INI file. |
| type Section map[string]string |
| |
| // Returns a named Section. A Section will be created if one does not already exist for the given name. |
| func (f File) Section(name string) Section { |
| section := f[name] |
| if section == nil { |
| section = make(Section) |
| f[name] = section |
| } |
| return section |
| } |
| |
| // Looks up a value for a key in a section and returns that value, along with a boolean result similar to a map lookup. |
| func (f File) Get(section, key string) (value string, ok bool) { |
| if s := f[section]; s != nil { |
| value, ok = s[key] |
| } |
| return |
| } |
| |
| // Loads INI data from a reader and stores the data in the File. |
| func (f File) Load(in io.Reader) (err error) { |
| bufin, ok := in.(*bufio.Reader) |
| if !ok { |
| bufin = bufio.NewReader(in) |
| } |
| return parseFile(bufin, f) |
| } |
| |
| // Loads INI data from a named file and stores the data in the File. |
| func (f File) LoadFile(file string) (err error) { |
| in, err := os.Open(file) |
| if err != nil { |
| return |
| } |
| defer in.Close() |
| return f.Load(in) |
| } |
| |
| func parseFile(in *bufio.Reader, file File) (err error) { |
| section := "" |
| lineNum := 0 |
| for done := false; !done; { |
| var line string |
| if line, err = in.ReadString('\n'); err != nil { |
| if err == io.EOF { |
| done = true |
| } else { |
| return |
| } |
| } |
| lineNum++ |
| line = strings.TrimSpace(line) |
| if len(line) == 0 { |
| // Skip blank lines |
| continue |
| } |
| if line[0] == ';' || line[0] == '#' { |
| // Skip comments |
| continue |
| } |
| |
| if groups := assignRegex.FindStringSubmatch(line); groups != nil { |
| key, val := groups[1], groups[2] |
| key, val = strings.TrimSpace(key), strings.TrimSpace(val) |
| file.Section(section)[key] = val |
| } else if groups := sectionRegex.FindStringSubmatch(line); groups != nil { |
| name := strings.TrimSpace(groups[1]) |
| section = name |
| // Create the section if it does not exist |
| file.Section(section) |
| } else { |
| return ErrSyntax{lineNum, line} |
| } |
| |
| } |
| return nil |
| } |
| |
| // Loads and returns a File from a reader. |
| func Load(in io.Reader) (File, error) { |
| file := make(File) |
| err := file.Load(in) |
| return file, err |
| } |
| |
| // Loads and returns an INI File from a file on disk. |
| func LoadFile(filename string) (File, error) { |
| file := make(File) |
| err := file.LoadFile(filename) |
| return file, err |
| } |