package ini import ( "bufio" "fmt" "io" "strings" ) // State machine for writing to streamable INI file sources. type Builder struct { writer io.Writer } // Singular key-value pair under the given section within an INI file. type Entry struct { 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 } // Appends a new key-value pair of `key` and `value` respectively in the `builder`. func (builder *Builder) KeyValue(key string, value string) error { var _, printError = fmt.Fprintf(builder.writer, "%s = %s\n", key, value) return printError } // Creates and returns a new [Builder] by reference from `writer`. // // Note that [NewBuilder] does not buffer write operations. func NewBuilder(writer io.Writer) *Builder { return &Builder{ writer: writer, } } // 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{ 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{ 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 } // Returns the name of the section last parsed, which will an empty string if currently in the // default section. func (parser *Parser) Section() string { return parser.section } // Appends a new section titled `name` in the `builder`. func (builder *Builder) Section(name string) error { var _, printError = fmt.Fprintf(builder.writer, "[%s]\n", name) return printError } // 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 }