ini-grinder/ini.go

144 lines
3.2 KiB
Go

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
}