Commit 619e1e2a authored by Matt Butcher's avatar Matt Butcher

feat(chart): add values parser

This adds code to parse TOML files into Values maps. These can then
easily be passed into the template engine.

Included in this is support for TOML "tables", subsections of TOML
files. We will be using those to pass config data to dependent charts.
parent 2d9563b4
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
......
......@@ -12,3 +12,4 @@ import:
- package: gopkg.in/yaml.v2
- package: github.com/Masterminds/semver
version: 1.1.0
- package: github.com/BurntSushi/toml
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"
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)
}
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
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment