Skip to content

Commit

Permalink
add ReadWithCallback.
Browse files Browse the repository at this point in the history
* ReadWithCallback can be used to plug custom deserialization
  logic.
  • Loading branch information
smola committed Oct 26, 2016
1 parent 5b9f94e commit bdb40b4
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 9 deletions.
47 changes: 41 additions & 6 deletions read.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ func unquote(s string) string {
return string(u)
}

func readIntoPass(c *warnings.Collector, config interface{}, fset *token.FileSet,
file *token.File, src []byte, subsectPass bool) error {
func read(c *warnings.Collector, callback func(string,string,string,string,bool)error,
fset *token.FileSet, file *token.File, src []byte) error {
//
var s scanner.Scanner
var errs scanner.ErrorList
Expand Down Expand Up @@ -122,7 +122,7 @@ func readIntoPass(c *warnings.Collector, config interface{}, fset *token.FileSet
// If a section/subsection header was found, ensure a
// container object is created, even if there are no
// variables further down.
err := c.Collect(set(c, config, sect, sectsub, "", true, "", subsectPass))
err := c.Collect(callback(sect, sectsub, "", "", true))
if err != nil {
return err
}
Expand Down Expand Up @@ -168,7 +168,7 @@ func readIntoPass(c *warnings.Collector, config interface{}, fset *token.FileSet
}
}
}
err := set(c, config, sect, sectsub, n, blank, v, subsectPass)
err := c.Collect(callback(sect, sectsub, n, v, blank))
if err != nil {
return err
}
Expand All @@ -190,17 +190,52 @@ func readInto(config interface{}, fset *token.FileSet, file *token.File,
src []byte) error {
//
c := warnings.NewCollector(isFatal)
err := readIntoPass(c, config, fset, file, src, false)
firstPassCallback := func(s string, ss string, k string, v string, bv bool) error {
return set(c, config, s, ss, k, v, bv, false)
}
err := read(c, firstPassCallback, fset, file, src)
if err != nil {
return err
}
err = readIntoPass(c, config, fset, file, src, true)
secondPassCallback := func(s string, ss string, k string, v string, bv bool) error {
return set(c, config, s, ss, k, v, bv, true)
}
err = read(c, secondPassCallback, fset, file, src)
if err != nil {
return err
}
return c.Done()
}

// ReadWithCallback reads gcfg formatted data from reader and calls
// callback with each section and option found.
//
// Callback is called with section, subsection, option key, option value
// and blank value flag as arguments.
//
// When a section is found, callback is called with nil subsection, option key
// and option value.
//
// When a subsection is found, callback is called with nil option key and
// option value.
//
// If blank value flag is true, it means that the value was not set for an option
// (as opposed to set to empty string).
//
// If callback returns an error, ReadWithCallback terminates with an error too.
func ReadWithCallback(reader io.Reader, callback func(string,string,string,string,bool)error) error {
src, err := ioutil.ReadAll(reader)
if err != nil {
return err
}

fset := token.NewFileSet()
file := fset.AddFile("", fset.Base(), len(src))
c := warnings.NewCollector(isFatal)

return read(c, callback, fset, file, src)
}

// ReadInto reads gcfg formatted data from reader and sets the values into the
// corresponding fields in config.
func ReadInto(config interface{}, reader io.Reader) error {
Expand Down
96 changes: 96 additions & 0 deletions read_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import (
"os"
"reflect"
"testing"
"bytes"
"strconv"
"github.com/pkg/errors"
)

const (
Expand Down Expand Up @@ -376,3 +379,96 @@ func TestReadStringIntoExtraData(t *testing.T) {
t.Errorf("res.Section.Name=%q; want %q", res.Section.Name, "value")
}
}

func TestReadWithCallback(t *testing.T) {
results := [][]string{}
cb := func(s string, ss string, k string, v string, bv bool) error {
results = append(results, []string{s, ss, k, v, strconv.FormatBool(bv)})
return nil
}
text := `
[sect1]
key1=value1
[sect1 "subsect1"]
key2=value2
key3=value3
key4
key5=
[sect1 "subsect2"]
[sect2]
`
expected := [][]string{
[]string{"sect1", "", "", "", "true"},
[]string{"sect1", "", "key1", "value1", "false"},
[]string{"sect1", "subsect1", "", "", "true"},
[]string{"sect1", "subsect1", "key2", "value2", "false"},
[]string{"sect1", "subsect1", "key3", "value3", "false"},
[]string{"sect1", "subsect1", "key4", "", "true"},
[]string{"sect1", "subsect1", "key5", "", "false"},
[]string{"sect1", "subsect2", "", "", "true"},
[]string{"sect2", "", "", "", "true"},
}
err := ReadWithCallback(bytes.NewReader([]byte(text)), cb)
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(results, expected) {
t.Errorf("expected %+v, got %+v", expected, results)
}

i := 0
expectedErr := errors.New("FATAL ERROR")
results = [][]string{}
cbWithError := func(s string, ss string, k string, v string, bv bool) error {
results = append(results, []string{s, ss, k, v, strconv.FormatBool(bv)})
i += 1
if i == 3 {
return expectedErr
}
return nil
}
err = ReadWithCallback(bytes.NewReader([]byte(text)), cbWithError)
if err != expectedErr {
t.Errorf("expected error: %+v", err)
}
if !reflect.DeepEqual(results, expected[:3]) {
t.Errorf("expected %+v, got %+v", expected, results[:3])
}
}

func TestReadWithCallback_WithError(t *testing.T) {
results := [][]string{}
cb := func(s string, ss string, k string, v string, bv bool) error {
results = append(results, []string{s, ss, k, v, strconv.FormatBool(bv)})
return nil
}
text := `
[sect1]
key1=value1
[sect1 "subsect1"]
key2=value2
key3=value3
key4
key5=
[sect1 "subsect2"]
[sect2]
`
expected := [][]string{
[]string{"sect1", "", "", "", "true"},
[]string{"sect1", "", "key1", "value1", "false"},
[]string{"sect1", "subsect1", "", "", "true"},
[]string{"sect1", "subsect1", "key2", "value2", "false"},
[]string{"sect1", "subsect1", "key3", "value3", "false"},
[]string{"sect1", "subsect1", "key4", "", "true"},
[]string{"sect1", "subsect1", "key5", "", "false"},
[]string{"sect1", "subsect2", "", "", "true"},
[]string{"sect2", "", "", "", "true"},
}
err := ReadWithCallback(bytes.NewReader([]byte(text)), cb)
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(results, expected) {
t.Errorf("expected %+v, got %+v", expected, results)
}
}
6 changes: 3 additions & 3 deletions set.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ func newValue(c *warnings.Collector, sect string, vCfg reflect.Value,
}

func set(c *warnings.Collector, cfg interface{}, sect, sub, name string,
blank bool, value string, subsectPass bool) error {
value string, blankValue bool, subsectPass bool) error {
//
vPCfg := reflect.ValueOf(cfg)
if vPCfg.Kind() != reflect.Ptr || vPCfg.Elem().Kind() != reflect.Struct {
Expand Down Expand Up @@ -285,7 +285,7 @@ func set(c *warnings.Collector, cfg interface{}, sect, sub, name string,
}
vVar = vVar.Elem()
}
if isMulti && blank {
if isMulti && blankValue {
vVar.Set(reflect.Zero(vVar.Type()))
return nil
}
Expand All @@ -309,7 +309,7 @@ func set(c *warnings.Collector, cfg interface{}, sect, sub, name string,
vAddrI := vAddr.Interface()
err, ok := error(nil), false
for _, s := range setters {
err = s(vAddrI, blank, value, t)
err = s(vAddrI, blankValue, value, t)
if err == nil {
ok = true
break
Expand Down

0 comments on commit bdb40b4

Please sign in to comment.