diff --git a/read.go b/read.go index 8400cf1..02f2fe3 100644 --- a/read.go +++ b/read.go @@ -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 @@ -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 } @@ -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 } @@ -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 { diff --git a/read_test.go b/read_test.go index 3542a98..d9ff5f8 100644 --- a/read_test.go +++ b/read_test.go @@ -6,6 +6,9 @@ import ( "os" "reflect" "testing" + "bytes" + "strconv" + "github.com/pkg/errors" ) const ( @@ -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) + } +} diff --git a/set.go b/set.go index 4c4d2bd..a5639ea 100644 --- a/set.go +++ b/set.go @@ -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 { @@ -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 } @@ -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