diff --git a/glide.lock b/glide.lock index 795c132ac..71ba5d45c 100644 --- a/glide.lock +++ b/glide.lock @@ -1,8 +1,10 @@ -hash: 7f9a27ad54a10edaa7c57521246676477d0f84ef4246524bae75f8df9d049983 -updated: 2016-04-14T12:24:49.130995956-06:00 +hash: e7c99013acb06eb359cf20390579af9a4553ef0fbed3f7bbb784b4ab7c8df807 +updated: 2016-04-15T15:15:21.87772545-06:00 imports: - name: github.com/aokoli/goutils version: 9c37978a95bd5c709a15883b6242714ea6709e64 +- name: github.com/BurntSushi/toml + version: bbd5bb678321a0d6e58f1099321dfa73391c1b6f - name: github.com/codegangsta/cli version: 71f57d300dd6a780ac1856c005c4b518cfd498ec - name: github.com/golang/protobuf @@ -28,7 +30,7 @@ imports: - http2/hpack - internal/timeseries - name: google.golang.org/grpc - version: 9ac074585f926c8506b6351bfdc396d2b19b1cb1 + version: 8eeecf2291de9d171d0b1392a27ff3975679f4f5 subpackages: - codes - credentials diff --git a/glide.yaml b/glide.yaml index a47f5a981..2b1531d12 100644 --- a/glide.yaml +++ b/glide.yaml @@ -12,3 +12,4 @@ import: - package: gopkg.in/yaml.v2 - package: github.com/Masterminds/semver version: 1.1.0 +- package: github.com/BurntSushi/toml diff --git a/pkg/chart/testdata/coleridge.toml b/pkg/chart/testdata/coleridge.toml new file mode 100644 index 000000000..bd16a8c84 --- /dev/null +++ b/pkg/chart/testdata/coleridge.toml @@ -0,0 +1,11 @@ +poet = "Coleridge" +title = "Rime of the Ancient Mariner" +stanza = ["at", "length", "did", "cross", "an", "Albatross"] + +[mariner] +with = "crossbow" +shot = "ALBATROSS" + +[water.water] +where = "everywhere" +nor = "any drop to drink" diff --git a/pkg/chart/values.go b/pkg/chart/values.go new file mode 100644 index 000000000..8c1c1200d --- /dev/null +++ b/pkg/chart/values.go @@ -0,0 +1,67 @@ +package chart + +import ( + "errors" + "io/ioutil" + "strings" + + "github.com/BurntSushi/toml" +) + +var ErrNoTable = errors.New("no table") + +type Values map[string]interface{} + +// Table gets a table (TOML subsection) from a Values object. +// +// The table is returned as a Values. +// +// Compound table names may be specified with dots: +// +// foo.bar +// +// The above will be evaluated as "The table bar inside the table +// foo". +// +// An ErrNoTable is returned if the table does not exist. +func (v Values) Table(name string) (Values, error) { + names := strings.Split(name, ".") + table := v + var err error + + for _, n := range names { + table, err = tableLookup(table, n) + if err != nil { + return table, err + } + } + return table, err +} + +func tableLookup(v Values, simple string) (Values, error) { + v2, ok := v[simple] + if !ok { + return v, ErrNoTable + } + vv, ok := v2.(map[string]interface{}) + if !ok { + return vv, ErrNoTable + } + return vv, nil +} + +// ReadValues will parse TOML byte data into a Values. +func ReadValues(data []byte) (Values, error) { + out := map[string]interface{}{} + err := toml.Unmarshal(data, out) + return out, err +} + +// ReadValuesFile will parse a TOML file into a Values. +func ReadValuesFile(filename string) (Values, error) { + data, err := ioutil.ReadFile(filename) + if err != nil { + return map[string]interface{}{}, err + } + return ReadValues(data) +} diff --git a/pkg/chart/values_test.go b/pkg/chart/values_test.go new file mode 100644 index 000000000..ecb6b3df9 --- /dev/null +++ b/pkg/chart/values_test.go @@ -0,0 +1,134 @@ +package chart + +import ( + "bytes" + "fmt" + "testing" + "text/template" +) + +func TestReadValues(t *testing.T) { + doc := `# Test TOML parse +poet = "Coleridge" +title = "Rime of the Ancient Mariner" +stanza = ["at", "length", "did", "cross", "an", "Albatross"] + +[mariner] +with = "crossbow" +shot = "ALBATROSS" + +[water.water] +where = "everywhere" +nor = "any drop to drink" +` + + data, err := ReadValues([]byte(doc)) + if err != nil { + t.Fatalf("Error parsing bytes: %s", err) + } + matchValues(t, data) +} + +func TestReadValuesFile(t *testing.T) { + data, err := ReadValuesFile("./testdata/coleridge.toml") + if err != nil { + t.Fatalf("Error reading TOML file: %s", err) + } + matchValues(t, data) +} + +func ExampleValues() { + doc := `title="Moby Dick" +[chapter.one] +title = "Loomings" + +[chapter.two] +title = "The Carpet-Bag" + +[chapter.three] +title = "The Spouter Inn" +` + d, err := ReadValues([]byte(doc)) + if err != nil { + panic(err) + } + ch1, err := d.Table("chapter.one") + if err != nil { + panic("could not find chapter one") + } + fmt.Print(ch1["title"]) + // Output: + // Loomings +} + +func TestTable(t *testing.T) { + doc := `title="Moby Dick" +[chapter.one] +title = "Loomings" + +[chapter.two] +title = "The Carpet-Bag" + +[chapter.three] +title = "The Spouter Inn" +` + d, err := ReadValues([]byte(doc)) + if err != nil { + t.Fatalf("Failed to parse the White Whale: %s", err) + } + + if _, err := d.Table("title"); err == nil { + t.Fatalf("Title is not a table.") + } + + if _, err := d.Table("chapter"); err != nil { + t.Fatalf("Failed to get the chapter table: %s\n%v", err, d) + } + + if v, err := d.Table("chapter.one"); err != nil { + t.Errorf("Failed to get chapter.one: %s", err) + } else if v["title"] != "Loomings" { + t.Errorf("Unexpected title: %s", v["title"]) + } + + if _, err := d.Table("chapter.three"); err != nil { + t.Errorf("Chapter three is missing: %s\n%v", err, d) + } + + if _, err := d.Table("chapter.OneHundredThirtySix"); err == nil { + t.Errorf("I think you mean 'Epilogue'") + } +} + +func matchValues(t *testing.T, data map[string]interface{}) { + if data["poet"] != "Coleridge" { + t.Errorf("Unexpected poet: %s", data["poet"]) + } + + if o, err := ttpl("{{len .stanza}}", data); err != nil { + t.Errorf("len stanza: %s", err) + } else if o != "6" { + t.Errorf("Expected 6, got %s", o) + } + + if o, err := ttpl("{{.mariner.shot}}", data); err != nil { + t.Errorf(".mariner.shot: %s", err) + } else if o != "ALBATROSS" { + t.Errorf("Expected that mariner shot ALBATROSS") + } + + if o, err := ttpl("{{.water.water.where}}", data); err != nil { + t.Errorf(".water.water.where: %s", err) + } else if o != "everywhere" { + t.Errorf("Expected water water everywhere") + } +} + +func ttpl(tpl string, v map[string]interface{}) (string, error) { + var b bytes.Buffer + tt := template.Must(template.New("t").Parse(tpl)) + if err := tt.Execute(&b, v); err != nil { + return "", err + } + return b.String(), nil +}