2022-12-20 18:04:39 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-12-20 22:48:24 +01:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2022-12-20 18:04:39 +01:00
|
|
|
// Creates and returns a new [Parser] by reference from `reader`
|
|
|
|
func NewParser(reader io.Reader) *Parser {
|
|
|
|
return &Parser{
|
|
|
|
scanner: bufio.NewScanner(reader),
|
2022-12-20 22:48:24 +01:00
|
|
|
isEnd: false,
|
2022-12-20 18:04:39 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-21 00:09:07 +01:00
|
|
|
// 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.
|
2022-12-20 18:04:39 +01:00
|
|
|
//
|
|
|
|
// Note that the `parser` does not guarantee any parse order for key-value pairs extracted from the
|
|
|
|
// `parser` stream.
|
2022-12-20 22:48:24 +01:00
|
|
|
func (parser *Parser) Parse() Entry {
|
2022-12-20 18:04:39 +01:00
|
|
|
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,
|
2022-12-20 22:53:34 +01:00
|
|
|
Key: unquote(strings.TrimSpace(line[0:assignmentIndex])),
|
|
|
|
Value: unquote(strings.TrimSpace(line[assignmentIndex+1:])),
|
2022-12-20 22:48:24 +01:00
|
|
|
}
|
2022-12-20 18:04:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Key which is its own value.
|
2022-12-20 22:53:34 +01:00
|
|
|
var keyValue = unquote(line[1:lineTail])
|
2022-12-20 18:04:39 +01:00
|
|
|
|
|
|
|
return Entry{
|
|
|
|
Section: parser.section,
|
|
|
|
Key: keyValue,
|
|
|
|
Value: keyValue,
|
2022-12-20 22:48:24 +01:00
|
|
|
}
|
2022-12-20 18:04:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
parser.err = parser.scanner.Err()
|
2022-12-20 22:48:24 +01:00
|
|
|
parser.isEnd = true
|
2022-12-20 18:04:39 +01:00
|
|
|
|
2022-12-20 22:48:24 +01:00
|
|
|
return Entry{}
|
2022-12-20 18:04:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// State machine for parsing streamable INI file sources.
|
|
|
|
type Parser struct {
|
|
|
|
scanner *bufio.Scanner
|
|
|
|
err error
|
|
|
|
section string
|
2022-12-20 22:48:24 +01:00
|
|
|
isEnd bool
|
2022-12-20 18:04:39 +01:00
|
|
|
}
|
2022-12-20 22:53:34 +01:00
|
|
|
|
2022-12-21 00:09:07 +01:00
|
|
|
// Attempts to write `entries` to `writer`, returning any error that occured during writing.
|
2022-12-21 00:22:18 +01:00
|
|
|
func Write(writer io.StringWriter, entries []Entry) error {
|
2022-12-21 00:09:07 +01:00
|
|
|
// 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
|
|
|
|
|
2022-12-21 00:22:18 +01:00
|
|
|
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
|
2022-12-21 00:09:07 +01:00
|
|
|
}
|
|
|
|
|
2022-12-21 00:22:18 +01:00
|
|
|
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
|
|
|
|
}
|
2022-12-21 00:09:07 +01:00
|
|
|
}
|
2022-12-21 00:22:18 +01:00
|
|
|
|
|
|
|
return nil
|
2022-12-21 00:09:07 +01:00
|
|
|
}
|
|
|
|
|
2022-12-20 22:53:34 +01:00
|
|
|
// 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
|
|
|
|
}
|