@@ 3,6 3,7 @@ package ini // import "go.spiff.io/go-ini"
import (
"bytes"
+ "errors"
"fmt"
"io"
"strings"
@@ 48,8 49,9 @@ func (v Values) Set(key, value string) {
}
// Add adds a value to key's value slice (allocating one if none is present).
-func (v Values) Add(key, value string) {
+func (v Values) Add(key, value string) error {
v[key] = append(v[key], value)
+ return nil
}
// Get returns the first value for key. If key does not exist or has an empty value slice, Get
@@ 160,10 162,20 @@ func ReadINI(b []byte, out Values) (Values, error) {
return out, err
}
-func (d *decoder) add(key, value string) {
- if d.dst != nil {
- d.dst.Add(key, value)
+func (d *decoder) add(key, value string) error {
+ if d.dst == nil {
+ return nil
+
+ }
+ err := d.dst.Add(key, value)
+ if err != nil {
+ err = &RecordingError{
+ Key: key,
+ Value: value,
+ Err: err,
+ }
}
+ return err
}
func (d *decoder) syntaxerr(err error, msg ...interface{}) *SyntaxError {
@@ 316,8 328,7 @@ func (d *decoder) readKey() (nextfunc, error) {
}
if errors.Is(err, io.EOF) {
- d.add(d.buffer.String(), d.true)
- return nil, nil
+ return nil, d.add(d.buffer.String(), d.true)
}
d.key = d.buffer.String()
@@ 328,25 339,24 @@ func (d *decoder) readKey() (nextfunc, error) {
func (d *decoder) readValueSep() (next nextfunc, err error) {
if err = must(d.skipSpace(false), io.EOF, nil); errors.Is(err, io.EOF) {
- d.add(d.key, d.true)
- return nil, nil
+ return nil, d.add(d.key, d.true)
}
defer stopOnEOF(&next, &err)
// Aside from whitespace, the only thing that can follow a key is a newline or =.
switch d.current {
case rNewline:
- d.add(d.key, d.true)
+ if err := d.add(d.key, d.true); err != nil {
+ return nil, err
+ }
return d.readElem, d.skip()
case rEquals:
if err = d.skip(); errors.Is(err, io.EOF) {
- d.add(d.key, "")
- return nil, nil
+ return nil, d.add(d.key, "")
}
return d.readValue, nil
case rHash, rSemicolon:
- d.add(d.key, d.true)
- return d.readComment, nil
+ return d.readComment, d.add(d.key, d.true)
default:
return nil, d.syntaxerr(BadCharError(d.current), "expected either =, newline, or a comment")
}
@@ 414,7 424,9 @@ func (d *decoder) readStringValue() (next nextfunc, err error) {
}
defer stopOnEOF(&next, &err)
- d.add(d.key, d.buffer.String())
+ if err := d.add(d.key, d.buffer.String()); err != nil {
+ return nil, err
+ }
return d.readElem, d.skip()
}
@@ 432,21 444,24 @@ func (d *decoder) readRawValue() (next nextfunc, err error) {
}
defer stopOnEOF(&next, &err)
- d.add(d.key, d.buffer.String())
+ if err := d.add(d.key, d.buffer.String()); err != nil {
+ return nil, err
+ }
return d.readElem, d.skip()
}
func (d *decoder) readValue() (next nextfunc, err error) {
if err = must(d.skipSpace(false), io.EOF); errors.Is(err, io.EOF) {
- d.add(d.key, "")
- return nil, nil
+ return nil, d.add(d.key, "")
}
switch d.current {
case rNewline:
// Terminated by newline
defer stopOnEOF(&next, &err)
- d.add(d.key, "")
+ if err := d.add(d.key, ""); err != nil {
+ return nil, err
+ }
return d.readElem, d.skip()
case rQuote:
return d.readStringValue, nil
@@ 454,8 469,7 @@ func (d *decoder) readValue() (next nextfunc, err error) {
return d.readRawValue, nil
case rHash, rSemicolon:
// Terminated by comment
- d.add(d.key, "")
- return d.readComment, nil
+ return d.readComment, d.add(d.key, "")
}
defer stopOnEOF(&next, &err)
@@ 463,7 477,9 @@ func (d *decoder) readValue() (next nextfunc, err error) {
must(d.readUntil(runestr("\n;#"), true, nil), io.EOF)
value := string(bytes.TrimRightFunc(d.buffer.Bytes(), unicode.IsSpace))
- d.add(d.key, value)
+ if err := d.add(d.key, value); err != nil {
+ return nil, err
+ }
return d.readElem, err
}
@@ 694,7 710,7 @@ var DefaultDecoder = Reader{
// key. It is up to the Recorder to decide if it discards or appends to prior versions of a key. If
// Add panics, the value that it panics with is returned as an error.
type Recorder interface {
- Add(key, value string)
+ Add(key, value string) error
}
// Reader is an INI reader configuration. It does not hold state and may be copied as needed.
@@ 717,6 733,9 @@ type Reader struct {
// Read decodes INI file input from r and conveys it to dst. If an error occurs, it is returned. If
// the error is an EOF before parsing is finished, io.ErrUnexpectedEOF is returned.
+//
+// If the Recorder returns an error when adding a key, an error of type *RecordingError is returned,
+// wrapping the error that the Recorder returned.
func (d *Reader) Read(r io.Reader, dst Recorder) error {
var dec decoder
dec.reset(d, dst, r)