all tests passing
2 files changed, 89 insertions(+), 0 deletions(-)

A => ini.go
A => ini_test.go
A => ini.go +32 -0
@@ 0,0 1,32 @@ 
+package ini
+
+import (
+	"bufio"
+	"errors"
+	"io"
+	"strings"
+)
+
+type Tree map[string]map[string]string
+
+func Parse(r io.Reader) (Tree, error) {
+	t := make(Tree)
+	var section string
+	scanner := bufio.NewScanner(r)
+	for scanner.Scan() {
+		s := strings.TrimSpace(scanner.Text())
+		switch {
+		case len(s) > 0 && s[0] == ';': // comment
+			continue
+		case len(s) > 1 && s[0] == '[' && s[len(s)-1] == ']': // section header
+			section = s[1 : len(s)-1]
+			t[section] = make(map[string]string)
+		case strings.Index(s, "=") != -1: // key-value pair
+			if t[section] == nil {
+				return nil, errors.New("need section header before key-value pair")
+			}
+			t[section][s[:strings.Index(s, "=")]] = s[strings.Index(s, "=")+1:]
+		}
+	}
+	return t, scanner.Err()
+}
  No newline at end of file

          
A => ini_test.go +57 -0
@@ 0,0 1,57 @@ 
+package ini
+
+import (
+	"reflect"
+	"strings"
+	"testing"
+)
+
+var tests = []struct {
+	input     string
+	want      Tree
+	wantError bool
+}{
+	{"", make(Tree), false},
+	{"; comment", make(Tree), false},
+	{" ", make(Tree), false},
+	{"[] ", func() Tree { t := make(Tree, 1); t[""] = make(map[string]string); return t }(), false},
+	{"=", nil, true},
+	{"; =", make(Tree), true},
+	{"[section]", func() Tree { t := make(Tree); t["section"] = make(map[string]string); return t }(), false},
+	{"[person]\nname=John", func() Tree {
+		t := make(Tree)
+		p := make(map[string]string)
+		p["name"] = "John"
+		t["person"] = p
+		return t
+	}(), false},
+	{`[dmr]
+first name=Dennis
+last name=Ritchie
+
+[ken]
+first name=Kenneth
+last name=Thompson`, func() Tree {
+		t := make(Tree)
+		dmr := make(map[string]string)
+		dmr["first name"] = "Dennis"
+		dmr["last name"] = "Ritchie"
+		t["dmr"] = dmr
+		ken := make(map[string]string)
+		ken["first name"] = "Kenneth"
+		ken["last name"] = "Thompson"
+		t["ken"] = ken
+		return t
+	}(), false},
+}
+
+func TestParse(t *testing.T) {
+	for i, tc := range tests {
+		r := strings.NewReader(tc.input)
+		got, gotErr := Parse(r)
+		if !reflect.DeepEqual(got, tc.want) {
+			t.Errorf("%d: got %v, wanted %v; error: %v", i, got, tc.want, gotErr)
+		}
+		_ = gotErr
+	}
+}