rearrange source files
diff --git a/bool.go b/bool.go
new file mode 100644
index 0000000..24611a2
--- /dev/null
+++ b/bool.go
@@ -0,0 +1,21 @@
+package gcfg
+
+import (
+	"fmt"
+)
+
+type gbool bool
+
+var gboolValues = map[string]interface{}{
+	"true": true, "yes": true, "on": true, "1": true,
+	"false": false, "no": false, "off": false, "0": false}
+
+func (b *gbool) Scan(state fmt.ScanState, verb rune) error {
+	v, err := scanEnum(state, gboolValues, true)
+	if err != nil {
+		return err
+	}
+	bb, _ := v.(bool) // cannot be non-bool
+	*b = gbool(bb)
+	return nil
+}
diff --git a/gcfg_example_test.go b/example_test.go
similarity index 100%
rename from gcfg_example_test.go
rename to example_test.go
diff --git a/gcfg.go b/gcfg.go
index 7472877..0af614c 100644
--- a/gcfg.go
+++ b/gcfg.go
@@ -55,221 +55,3 @@
 //  - move TODOs to issue tracker (eventually)
 //
 package gcfg
-
-import (
-	"bufio"
-	"errors"
-	"fmt"
-	"io"
-	"os"
-	"reflect"
-	"regexp"
-	"strings"
-)
-
-var (
-	reCmnt    = regexp.MustCompile(`^([^;#"]*)[;#].*$`)
-	reCmntQ   = regexp.MustCompile(`^([^;#"]*"[^"]*"[^;#"]*)[;#].*$`)
-	reBlank   = regexp.MustCompile(`^\s*$`)
-	reSect    = regexp.MustCompile(`^\s*\[\s*([^"\s]*)\s*\]\s*$`)
-	reSectSub = regexp.MustCompile(`^\s*\[\s*([^"\s]*)\s*"([^"]+)"\s*\]\s*$`)
-	reVar     = regexp.MustCompile(`^\s*([^"=\s]+)\s*=\s*([^"\s]*)\s*$`)
-	reVarQ    = regexp.MustCompile(`^\s*([^"=\s]+)\s*=\s*"([^"\n\\]*)"\s*$`)
-	reVarDflt = regexp.MustCompile(`^\s*\b(.*)\b\s*$`)
-)
-
-const (
-	// Default value string in case a value for a variable isn't provided.
-	defaultValue = "true"
-)
-
-type gbool bool
-
-var gboolValues = map[string]interface{}{
-	"true": true, "yes": true, "on": true, "1": true,
-	"false": false, "no": false, "off": false, "0": false}
-
-func (b *gbool) Scan(state fmt.ScanState, verb rune) error {
-	v, err := scanEnum(state, gboolValues, true)
-	if err != nil {
-		return err
-	}
-	bb, _ := v.(bool) // cannot be non-bool
-	*b = gbool(bb)
-	return nil
-}
-
-func fieldFold(v reflect.Value, name string) reflect.Value {
-	n := strings.Replace(name, "-", "_", -1)
-	return v.FieldByNameFunc(func(fieldName string) bool {
-		return strings.EqualFold(n, fieldName)
-	})
-}
-
-func set(cfg interface{}, sect, sub, name, value string) error {
-	vDest := reflect.ValueOf(cfg).Elem()
-	vSect := fieldFold(vDest, sect)
-	if !vSect.IsValid() {
-		return fmt.Errorf("invalid section: section %q", sect)
-	}
-	if vSect.Kind() == reflect.Map {
-		if vSect.IsNil() {
-			vSect.Set(reflect.MakeMap(vSect.Type()))
-		}
-		k := reflect.ValueOf(sub)
-		pv := vSect.MapIndex(k)
-		if !pv.IsValid() {
-			vType := vSect.Type().Elem().Elem()
-			pv = reflect.New(vType)
-			vSect.SetMapIndex(k, pv)
-		}
-		vSect = pv.Elem()
-	} else if sub != "" {
-		return fmt.Errorf("invalid subsection: "+
-			"section %q subsection %q", sect, sub)
-	}
-	vName := fieldFold(vSect, name)
-	if !vName.IsValid() {
-		return fmt.Errorf("invalid variable: "+
-			"section %q subsection %q variable %q", sect, sub, name)
-	}
-	vAddr := vName.Addr().Interface()
-	switch v := vAddr.(type) {
-	case *string:
-		*v = value
-		return nil
-	case *bool:
-		vAddr = (*gbool)(v)
-	}
-	// attempt to read an extra rune to make sure the value is consumed 
-	var r rune
-	n, err := fmt.Sscanf(value, "%v%c", vAddr, &r)
-	switch {
-	case n < 1 || n == 1 && err != io.EOF:
-		return fmt.Errorf("failed to parse %q as %#v: parse error %v", value,
-			vName.Type(), err)
-	case n > 1:
-		return fmt.Errorf("failed to parse %q as %#v: extra characters", value,
-			vName.Type())
-	case n == 1 && err == io.EOF:
-		return nil
-	}
-	panic("never reached")
-}
-
-// ReadInto reads gcfg formatted data from reader and sets the values into the
-// corresponding fields in config.
-//
-// Config must be a pointer to a struct.
-// Each section corresponds to a struct field in config, and each variable in a
-// section corresponds to a data field in the section struct.
-// The name of the field must match the name of the section or variable,
-// ignoring case.
-// Hyphens in section and variable names correspond to underscores in field
-// names.
-//
-// For sections with subsections, the corresponding field in config must be a
-// map, rather than a struct, with string keys and pointer-to-struct values.
-// Values for subsection variables are stored in the map with the subsection
-// name used as the map key.
-// (Note that unlike section and variable names, subsection names are case
-// sensitive.)
-// When using a map, and there is a section with the same section name but
-// without a subsection name, its values are stored with the empty string used
-// as the key.
-//
-// The section structs in the config struct may contain arbitrary types.
-// For string fields, the (unquoted and unescaped) value string is assigned to
-// the field.
-// For bool fields, the field is set to true if the value is "true", "yes", "on"
-// or "1", and set to false if the value is "false", "no", "off" or "0",
-// ignoring case.
-// For all other types, fmt.Sscanf is used to parse the value and set it to the
-// field.
-// This means that built-in Go types are parseable using the standard format,
-// and any user-defined type is parseable if it implements the fmt.Scanner
-// interface.
-// Note that the value is considered invalid unless fmt.Scanner fully consumes
-// the value string without error.
-//
-// See ReadStringInto for examples.
-//
-func ReadInto(config interface{}, reader io.Reader) error {
-	r := bufio.NewReader(reader)
-	sect := (*string)(nil)
-	sectsub := ""
-	for line := 1; true; line++ {
-		l, pre, err := r.ReadLine()
-		if err != nil && err != io.EOF {
-			return err
-		} else if pre {
-			return errors.New("line too long")
-		}
-		// exclude comments
-		if c := reCmnt.FindSubmatch(l); c != nil {
-			l = c[1]
-		} else if c := reCmntQ.FindSubmatch(l); c != nil {
-			l = c[1]
-		}
-		if !reBlank.Match(l) {
-			// "switch" based on line contents
-			if sec := reSect.FindSubmatch(l); sec != nil {
-				strsec := string(sec[1])
-				sect, sectsub = &strsec, ""
-			} else if sec := reSectSub.FindSubmatch(l); sec != nil {
-				strsec := string(sec[1])
-				strsub := string(sec[2])
-				if strsub == "" {
-					return errors.New("empty subsection not allowed")
-				}
-				sect, sectsub = &strsec, strsub
-			} else if v, vq, vd := reVar.FindSubmatch(l),
-				reVarQ.FindSubmatch(l), reVarDflt.FindSubmatch(l); //
-			v != nil || vq != nil || vd != nil {
-				if sect == nil {
-					return errors.New("no section")
-				}
-				var name, value string
-				if v != nil {
-					name, value = string(v[1]), string(v[2])
-				} else if vq != nil {
-					name, value = string(vq[1]), string(vq[2])
-				} else { // vd != nil
-					name, value = string(vd[1]), defaultValue
-				}
-				err := set(config, *sect, sectsub, name, value)
-				if err != nil {
-					return err
-				}
-			} else {
-				return fmt.Errorf("invalid line %q", string(l))
-			}
-		}
-		if err == io.EOF {
-			break
-		}
-	}
-	return nil
-}
-
-// ReadStringInto reads gcfg formatted data from str and sets the values into
-// the corresponding fields in config.
-// ReadStringInfo is a wrapper for ReadInfo; see ReadInto(config, reader) for
-// detailed description of how data is read and set into config.
-func ReadStringInto(config interface{}, str string) error {
-	r := strings.NewReader(str)
-	return ReadInto(config, r)
-}
-
-// ReadFileInto reads gcfg formatted data from the file filename and sets the
-// values into the corresponding fields in config.
-// ReadFileInto is a wrapper for ReadInfo; see ReadInto(config, reader) for
-// detailed description of how data is read and set into config.
-func ReadFileInto(config interface{}, filename string) error {
-	f, err := os.Open(filename)
-	if err != nil {
-		return err
-	}
-	defer f.Close()
-	return ReadInto(config, f)
-}
diff --git a/read.go b/read.go
new file mode 100644
index 0000000..71c89ef
--- /dev/null
+++ b/read.go
@@ -0,0 +1,203 @@
+package gcfg
+
+import (
+	"bufio"
+	"errors"
+	"fmt"
+	"io"
+	"os"
+	"reflect"
+	"regexp"
+	"strings"
+)
+
+var (
+	reCmnt    = regexp.MustCompile(`^([^;#"]*)[;#].*$`)
+	reCmntQ   = regexp.MustCompile(`^([^;#"]*"[^"]*"[^;#"]*)[;#].*$`)
+	reBlank   = regexp.MustCompile(`^\s*$`)
+	reSect    = regexp.MustCompile(`^\s*\[\s*([^"\s]*)\s*\]\s*$`)
+	reSectSub = regexp.MustCompile(`^\s*\[\s*([^"\s]*)\s*"([^"]+)"\s*\]\s*$`)
+	reVar     = regexp.MustCompile(`^\s*([^"=\s]+)\s*=\s*([^"\s]*)\s*$`)
+	reVarQ    = regexp.MustCompile(`^\s*([^"=\s]+)\s*=\s*"([^"\n\\]*)"\s*$`)
+	reVarDflt = regexp.MustCompile(`^\s*\b(.*)\b\s*$`)
+)
+
+const (
+	// Default value string in case a value for a variable isn't provided.
+	defaultValue = "true"
+)
+
+func fieldFold(v reflect.Value, name string) reflect.Value {
+	n := strings.Replace(name, "-", "_", -1)
+	return v.FieldByNameFunc(func(fieldName string) bool {
+		return strings.EqualFold(n, fieldName)
+	})
+}
+
+func set(cfg interface{}, sect, sub, name, value string) error {
+	vDest := reflect.ValueOf(cfg).Elem()
+	vSect := fieldFold(vDest, sect)
+	if !vSect.IsValid() {
+		return fmt.Errorf("invalid section: section %q", sect)
+	}
+	if vSect.Kind() == reflect.Map {
+		if vSect.IsNil() {
+			vSect.Set(reflect.MakeMap(vSect.Type()))
+		}
+		k := reflect.ValueOf(sub)
+		pv := vSect.MapIndex(k)
+		if !pv.IsValid() {
+			vType := vSect.Type().Elem().Elem()
+			pv = reflect.New(vType)
+			vSect.SetMapIndex(k, pv)
+		}
+		vSect = pv.Elem()
+	} else if sub != "" {
+		return fmt.Errorf("invalid subsection: "+
+			"section %q subsection %q", sect, sub)
+	}
+	vName := fieldFold(vSect, name)
+	if !vName.IsValid() {
+		return fmt.Errorf("invalid variable: "+
+			"section %q subsection %q variable %q", sect, sub, name)
+	}
+	vAddr := vName.Addr().Interface()
+	switch v := vAddr.(type) {
+	case *string:
+		*v = value
+		return nil
+	case *bool:
+		vAddr = (*gbool)(v)
+	}
+	// attempt to read an extra rune to make sure the value is consumed
+	var r rune
+	n, err := fmt.Sscanf(value, "%v%c", vAddr, &r)
+	switch {
+	case n < 1 || n == 1 && err != io.EOF:
+		return fmt.Errorf("failed to parse %q as %#v: parse error %v", value,
+			vName.Type(), err)
+	case n > 1:
+		return fmt.Errorf("failed to parse %q as %#v: extra characters", value,
+			vName.Type())
+	case n == 1 && err == io.EOF:
+		return nil
+	}
+	panic("never reached")
+}
+
+// ReadInto reads gcfg formatted data from reader and sets the values into the
+// corresponding fields in config.
+//
+// Config must be a pointer to a struct.
+// Each section corresponds to a struct field in config, and each variable in a
+// section corresponds to a data field in the section struct.
+// The name of the field must match the name of the section or variable,
+// ignoring case.
+// Hyphens in section and variable names correspond to underscores in field
+// names.
+//
+// For sections with subsections, the corresponding field in config must be a
+// map, rather than a struct, with string keys and pointer-to-struct values.
+// Values for subsection variables are stored in the map with the subsection
+// name used as the map key.
+// (Note that unlike section and variable names, subsection names are case
+// sensitive.)
+// When using a map, and there is a section with the same section name but
+// without a subsection name, its values are stored with the empty string used
+// as the key.
+//
+// The section structs in the config struct may contain arbitrary types.
+// For string fields, the (unquoted and unescaped) value string is assigned to
+// the field.
+// For bool fields, the field is set to true if the value is "true", "yes", "on"
+// or "1", and set to false if the value is "false", "no", "off" or "0",
+// ignoring case.
+// For all other types, fmt.Sscanf is used to parse the value and set it to the
+// field.
+// This means that built-in Go types are parseable using the standard format,
+// and any user-defined type is parseable if it implements the fmt.Scanner
+// interface.
+// Note that the value is considered invalid unless fmt.Scanner fully consumes
+// the value string without error.
+//
+// See ReadStringInto for examples.
+//
+func ReadInto(config interface{}, reader io.Reader) error {
+	r := bufio.NewReader(reader)
+	sect := (*string)(nil)
+	sectsub := ""
+	for line := 1; true; line++ {
+		l, pre, err := r.ReadLine()
+		if err != nil && err != io.EOF {
+			return err
+		} else if pre {
+			return errors.New("line too long")
+		}
+		// exclude comments
+		if c := reCmnt.FindSubmatch(l); c != nil {
+			l = c[1]
+		} else if c := reCmntQ.FindSubmatch(l); c != nil {
+			l = c[1]
+		}
+		if !reBlank.Match(l) {
+			// "switch" based on line contents
+			if sec := reSect.FindSubmatch(l); sec != nil {
+				strsec := string(sec[1])
+				sect, sectsub = &strsec, ""
+			} else if sec := reSectSub.FindSubmatch(l); sec != nil {
+				strsec := string(sec[1])
+				strsub := string(sec[2])
+				if strsub == "" {
+					return errors.New("empty subsection not allowed")
+				}
+				sect, sectsub = &strsec, strsub
+			} else if v, vq, vd := reVar.FindSubmatch(l),
+				reVarQ.FindSubmatch(l), reVarDflt.FindSubmatch(l); //
+			v != nil || vq != nil || vd != nil {
+				if sect == nil {
+					return errors.New("no section")
+				}
+				var name, value string
+				if v != nil {
+					name, value = string(v[1]), string(v[2])
+				} else if vq != nil {
+					name, value = string(vq[1]), string(vq[2])
+				} else { // vd != nil
+					name, value = string(vd[1]), defaultValue
+				}
+				err := set(config, *sect, sectsub, name, value)
+				if err != nil {
+					return err
+				}
+			} else {
+				return fmt.Errorf("invalid line %q", string(l))
+			}
+		}
+		if err == io.EOF {
+			break
+		}
+	}
+	return nil
+}
+
+// ReadStringInto reads gcfg formatted data from str and sets the values into
+// the corresponding fields in config.
+// ReadStringInfo is a wrapper for ReadInfo; see ReadInto(config, reader) for
+// detailed description of how data is read and set into config.
+func ReadStringInto(config interface{}, str string) error {
+	r := strings.NewReader(str)
+	return ReadInto(config, r)
+}
+
+// ReadFileInto reads gcfg formatted data from the file filename and sets the
+// values into the corresponding fields in config.
+// ReadFileInto is a wrapper for ReadInfo; see ReadInto(config, reader) for
+// detailed description of how data is read and set into config.
+func ReadFileInto(config interface{}, filename string) error {
+	f, err := os.Open(filename)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	return ReadInto(config, f)
+}
diff --git a/gcfg_test.go b/read_test.go
similarity index 100%
rename from gcfg_test.go
rename to read_test.go