From b1730c1956aed244d9d05255c95f427fdfcf4d1e Mon Sep 17 00:00:00 2001 From: Dave Henderson Date: Sun, 4 Jun 2017 16:04:36 -0400 Subject: Adding TOML support Signed-off-by: Dave Henderson --- data.go | 33 ++++++------ docs/content/functions.md | 68 +++++++++++++++++++++++- gomplate.go | 2 + test/integration/typeconv_funcs.bats | 19 +++++++ typeconv.go | 18 +++++++ typeconv_test.go | 100 +++++++++++++++++++++++++++++++++++ 6 files changed, 221 insertions(+), 19 deletions(-) diff --git a/data.go b/data.go index 483f141b..995eb5bd 100644 --- a/data.go +++ b/data.go @@ -20,24 +20,20 @@ import ( // logFatal is defined so log.Fatal calls can be overridden for testing var logFatalf = log.Fatalf -func init() { - // Add some types we want to be able to handle which can be missing by default - err := mime.AddExtensionType(".json", "application/json") - if err != nil { - log.Fatal(err) - } - err = mime.AddExtensionType(".yml", "application/yaml") - if err != nil { - log.Fatal(err) - } - err = mime.AddExtensionType(".yaml", "application/yaml") - if err != nil { - log.Fatal(err) - } - err = mime.AddExtensionType(".csv", "text/csv") +func regExtension(ext, typ string) { + err := mime.AddExtensionType(ext, typ) if err != nil { log.Fatal(err) } +} + +func init() { + // Add some types we want to be able to handle which can be missing by default + regExtension(".json", "application/json") + regExtension(".yml", "application/yaml") + regExtension(".yaml", "application/yaml") + regExtension(".csv", "text/csv") + regExtension(".toml", "application/toml") sourceReaders = make(map[string]func(*Source, ...string) ([]byte, error)) @@ -184,18 +180,19 @@ func (d *Data) Datasource(alias string, args ...string) interface{} { log.Fatalf("Couldn't read datasource '%s': %s", alias, err) } s := string(b) + ty := &TypeConv{} if source.Type == "application/json" { - ty := &TypeConv{} return ty.JSON(s) } if source.Type == "application/yaml" { - ty := &TypeConv{} return ty.YAML(s) } if source.Type == "text/csv" { - ty := &TypeConv{} return ty.CSV(s) } + if source.Type == "application/toml" { + return ty.TOML(s) + } log.Fatalf("Datasources of type %s not yet supported", source.Type) return nil } diff --git a/docs/content/functions.md b/docs/content/functions.md index c8962146..2cf6a6c7 100644 --- a/docs/content/functions.md +++ b/docs/content/functions.md @@ -372,6 +372,44 @@ $ gomplate < input.tmpl Hello world ``` +## `toml` + +Converts a [TOML](https://github.com/toml-lang/toml) document into an object. +This can be used to access properties of TOML documents. + +Compatible with [TOML v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md). + +### Usage + +```go +toml input +``` + +Can also be used in a pipeline: +```go +input | toml +``` + +### Arguments + +| name | description | +|--------|-------| +| `input` | the TOML document to parse | + +#### Example + +_`input.tmpl`:_ +``` +{{ $t := `[data] +hello = "world"` -}} +Hello {{ (toml $t).hello }} +``` + +```console +$ gomplate -f input.tmpl +Hello world +``` + ## `csv` Converts a CSV-format string into a 2-dimensional string array. @@ -547,6 +585,34 @@ $ gomplate < input.tmpl hello: world ``` +## `toTOML` + +Converts an object to a [TOML](https://github.com/toml-lang/toml) document. + +### Usage + +```go +toTOML obj +``` + +Can also be used in a pipeline: +```go +obj | toTOML +``` + +### Arguments + +| name | description | +|--------|-------| +| `obj` | the object to marshal as a TOML document | + +#### Example + +```console +$ gomplate -i '{{ `{"foo":"bar"}` | json | toTOML }}' +foo = "bar" +``` + ## `toCSV` Converts an object to a CSV document. The input object must be a 2-dimensional @@ -598,7 +664,7 @@ Parses a given datasource (provided by the [`--datasource/-d`](#--datasource-d) Currently, `file://`, `http://`, `https://`, and `vault://` URLs are supported. -Currently-supported formats are JSON, YAML, and CSV. +Currently-supported formats are JSON, YAML, TOML, and CSV. #### Examples diff --git a/gomplate.go b/gomplate.go index 78317634..f02c6f14 100644 --- a/gomplate.go +++ b/gomplate.go @@ -52,6 +52,7 @@ func NewGomplate(data *Data, leftDelim, rightDelim string) *Gomplate { "jsonArray": typeconv.JSONArray, "yaml": typeconv.YAML, "yamlArray": typeconv.YAMLArray, + "toml": typeconv.TOML, "csv": typeconv.CSV, "csvByRow": typeconv.CSVByRow, "csvByColumn": typeconv.CSVByColumn, @@ -61,6 +62,7 @@ func NewGomplate(data *Data, leftDelim, rightDelim string) *Gomplate { "toJSON": typeconv.ToJSON, "toJSONPretty": typeconv.toJSONPretty, "toYAML": typeconv.ToYAML, + "toTOML": typeconv.ToTOML, "toCSV": typeconv.ToCSV, "ec2meta": ec2meta.Meta, "ec2dynamic": ec2meta.Dynamic, diff --git a/test/integration/typeconv_funcs.bats b/test/integration/typeconv_funcs.bats index 46c735cc..0b22f311 100644 --- a/test/integration/typeconv_funcs.bats +++ b/test/integration/typeconv_funcs.bats @@ -89,3 +89,22 @@ Languages are: {{ join $c.lang " and " }}' [ "$status" -eq 0 ] [[ "${output}" == "Languages are: C and Go and COBOL" ]] } + +@test "'toml'" { + gomplate -i '{{ $t := `# comment +foo = "bar" + +[baz] +qux = "quux"` | toml -}} +{{ $t.baz.qux }}' + [ "$status" -eq 0 ] + [[ "${output}" == "quux" ]] +} + +@test "'toTOML'" { + gomplate -i '{{ "foo:\n bar:\n baz: qux" | yaml | toTOML }}' + [ "$status" -eq 0 ] + [[ "${output}" == "[foo] + [foo.bar] + baz = \"qux\"" ]] +} \ No newline at end of file diff --git a/typeconv.go b/typeconv.go index 2d1734c8..f2597d1a 100644 --- a/typeconv.go +++ b/typeconv.go @@ -12,6 +12,8 @@ import ( yaml "gopkg.in/yaml.v2" + // XXX: replace once https://github.com/BurntSushi/toml/pull/179 is merged + "github.com/hairyhenderson/toml" "github.com/ugorji/go/codec" ) @@ -69,6 +71,12 @@ func (t *TypeConv) YAMLArray(in string) []interface{} { return unmarshalArray(obj, in, yaml.Unmarshal) } +// TOML - Unmarshal a TOML Object +func (t *TypeConv) TOML(in string) interface{} { + obj := make(map[string]interface{}) + return unmarshalObj(obj, in, toml.Unmarshal) +} + func parseCSV(args ...string) (records [][]string, hdr []string) { delim := "," var in string @@ -249,6 +257,16 @@ func (t *TypeConv) ToYAML(in interface{}) string { return marshalObj(in, yaml.Marshal) } +// ToTOML - Stringify a struct as TOML +func (t *TypeConv) ToTOML(in interface{}) string { + buf := new(bytes.Buffer) + err := toml.NewEncoder(buf).Encode(in) + if err != nil { + log.Fatalf("Unable to marshal %s: %v", in, err) + } + return string(buf.Bytes()) +} + // Slice creates a slice from a bunch of arguments func (t *TypeConv) Slice(args ...interface{}) []interface{} { return args diff --git a/typeconv_test.go b/typeconv_test.go index 51b8cd87..5957d693 100644 --- a/typeconv_test.go +++ b/typeconv_test.go @@ -293,3 +293,103 @@ func TestToCSV(t *testing.T) { assert.Equal(t, expected, ty.ToCSV(";", in)) } + +func TestTOML(t *testing.T) { + ty := new(TypeConv) + in := `# This is a TOML document. Boom. + +title = "TOML Example" + +[owner] +name = "Tom Preston-Werner" +organization = "GitHub" +bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." +dob = 1979-05-27T07:32:00Z # First class dates? Why not? + +[database] +server = "192.168.1.1" +ports = [ 8001, 8001, 8002 ] +connection_max = 5000 +enabled = true + +[servers] + + # You can indent as you please. Tabs or spaces. TOML don't care. + [servers.alpha] + ip = "10.0.0.1" + dc = "eqdc10" + + [servers.beta] + ip = "10.0.0.2" + dc = "eqdc10" + +[clients] +data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it + +# Line breaks are OK when inside arrays +hosts = [ + "alpha", + "omega" +] +` + expected := map[string]interface{}{ + "title": "TOML Example", + "owner": map[string]interface{}{ + "name": "Tom Preston-Werner", + "organization": "GitHub", + "bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.", + "dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC), + }, + "database": map[string]interface{}{ + "server": "192.168.1.1", + "ports": []interface{}{int64(8001), int64(8001), int64(8002)}, + "connection_max": int64(5000), + "enabled": true, + }, + "servers": map[string]interface{}{ + "alpha": map[string]interface{}{ + "ip": "10.0.0.1", + "dc": "eqdc10", + }, + "beta": map[string]interface{}{ + "ip": "10.0.0.2", + "dc": "eqdc10", + }, + }, + "clients": map[string]interface{}{ + "data": []interface{}{ + []interface{}{"gamma", "delta"}, + []interface{}{int64(1), int64(2)}, + }, + "hosts": []interface{}{"alpha", "omega"}, + }, + } + + assert.Equal(t, expected, ty.TOML(in)) +} + +func TestToTOML(t *testing.T) { + ty := new(TypeConv) + expected := `foo = "bar" +one = 1 +true = true + +[down] + [down.the] + [down.the.rabbit] + hole = true +` + in := map[string]interface{}{ + "foo": "bar", + "one": 1, + "true": true, + "down": map[interface{}]interface{}{ + "the": map[interface{}]interface{}{ + "rabbit": map[interface{}]interface{}{ + "hole": true, + }, + }, + }, + } + assert.Equal(t, expected, ty.ToTOML(in)) +} -- cgit v1.2.3