package ini import ( "bufio" "io" "strings" ) // Singular key-value pair under the given section within an INI file. type Entry struct { Section string Key string Value string } // Returns the last error that occured during parsing. func (parser *Parser) Err() error { return parser.err } // Returns `true` if `parser` has reached the end of parsable tokens, either because the stream has // ended or it has encountered an error. func (parser *Parser) IsEnd() bool { return parser.isEnd } // Creates and returns a new [Parser] by reference from `reader` func NewParser(reader io.Reader) *Parser { return &Parser{ scanner: bufio.NewScanner(reader), isEnd: false, } } // Attempts to parse the next key-value pair in the `parser` stream, returning the parsed [Entry], // or an empty one if nothing was parsed, and a `bool` representing whether or not there is any // further data available to parse. // // Note that the `parser` does not guarantee any parse order for key-value pairs extracted from the // `parser` stream. func (parser *Parser) Parse() Entry { for parser.scanner.Scan() { var line = strings.TrimSpace(parser.scanner.Text()) var lineLen = len(line) if lineLen == 0 { // Skip empty lines. continue } if (line[0] == '#') || (line[0] == ';') { // Skip comment lines. continue } var lineTail = lineLen - 1 if (line[0] == '[') && (line[lineTail] == ']') { // Section name. parser.section = line[1:lineTail] continue } if assignmentIndex := strings.Index(line, "="); assignmentIndex > -1 { // Key with value. return Entry{ Section: parser.section, Key: unquote(strings.TrimSpace(line[0:assignmentIndex])), Value: unquote(strings.TrimSpace(line[assignmentIndex+1:])), } } // Key which is its own value. var keyValue = unquote(line[1:lineTail]) return Entry{ Section: parser.section, Key: keyValue, Value: keyValue, } } parser.err = parser.scanner.Err() parser.isEnd = true return Entry{} } // State machine for parsing streamable INI file sources. type Parser struct { scanner *bufio.Scanner err error section string isEnd bool } // Attempts to write `entries` to `writer`, returning any error that occured during writing. func Write(writer io.StringWriter, entries []Entry) error { // TODO: Not happy with this solution to writing files, needs more revision. var section = "" for _, entry := range entries { if entry.Section != section { section = entry.Section if _, writeError := writer.WriteString("["); writeError != nil { return writeError } if _, writeError := writer.WriteString(entry.Section); writeError != nil { return writeError } if _, writeError := writer.WriteString("]"); writeError != nil { return writeError } } if _, writeError := writer.WriteString(entry.Key); writeError != nil { return writeError } if _, writeError := writer.WriteString(" = "); writeError != nil { return writeError } if _, writeError := writer.WriteString(entry.Value); writeError != nil { return writeError } if _, writeError := writer.WriteString("\n"); writeError != nil { return writeError } } return nil } // Returns a string with the the outer-most quotes surrounding `s` trimmed, should they exist. func unquote(s string) string { var sLen = len(s) if sLen != 0 { var sTail = sLen - 1 if (s[0] == '"') && (s[sTail] == '"') { return s[1:sTail] } } return s }