Skip to content

Commit

Permalink
[config][util] Use MergeStringMap in config #2
Browse files Browse the repository at this point in the history
- add util function to merge two map[string]interface{}
- add ParseSingleDocumentBytes and call it in ParseMultipleDocumentBytes
  • Loading branch information
at15 committed Mar 28, 2017
1 parent 832eeea commit ce0743c
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 62 deletions.
118 changes: 58 additions & 60 deletions config/yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"strings"

"github.com/dyweb/gommon/cast"
"github.com/dyweb/gommon/util"
"github.com/flosch/pongo2"
"github.com/pkg/errors"
Expand Down Expand Up @@ -54,79 +55,76 @@ func (c *YAMLConfig) clear() {
}

func (c *YAMLConfig) ParseMultiDocumentBytes(data []byte) error {
c.mu.Lock()
defer c.mu.Unlock()

// split the doc, parse by order, add result to context so the following parser can use it
docs := SplitMultiDocument(data)
for _, doc := range docs {
pongoContext := pongo2.Context{
"vars": c.vars,
"envs": util.EnvAsMap(),
}
// we render the template twice, first time we use vars from previous documents and environment variables
// second time, we use vars declared in this document, if any.
// this is the first render
rendered, err := c.RenderDocumentBytes(doc, pongoContext)
err := c.ParseSingleDocumentBytes(doc)
if err != nil {
return errors.Wrap(err, "can't render template with previous documents' vars")
return errors.Wrap(err, "error when parsing one of the documents")
}
}
return nil
}

// TODO: need special flag/tag for this logging
fmt.Printf("01-before\n%s", doc)
fmt.Printf("01-after\n%s", rendered)
func (c *YAMLConfig) ParseSingleDocumentBytes(doc []byte) error {
c.mu.Lock()
defer c.mu.Unlock()

tmpData := make(map[string]interface{})
err = yaml.Unmarshal(rendered, &tmpData)
if err != nil {
return errors.Wrap(err, "can't parse rendered template yaml to map[string]interface{} after first render")
pongoContext := pongo2.Context{
"vars": c.vars,
"envs": util.EnvAsMap(),
}
// we render the template twice, first time we use vars from previous documents and environment variables
// second time, we use vars declared in this document, if any.
// this is the first render
rendered, err := c.RenderDocumentBytes(doc, pongoContext)
if err != nil {
return errors.Wrap(err, "can't render template with previous documents' vars")
}

// TODO: need special flag/tag for this logging
fmt.Printf("01-before\n%s", doc)
fmt.Printf("01-after\n%s", rendered)

tmpData := make(map[string]interface{})
err = yaml.Unmarshal(rendered, &tmpData)
if err != nil {
return errors.Wrap(err, "can't parse rendered template yaml to map[string]interface{} after first render")
}

// preserve the vars
if varsInCurrentDocument, hasVars := tmpData["vars"]; hasVars {
// NOTE: it's map[interface{}]interface{} instead of map[string]interface{}
// because how go-yaml handle the decoding
varsI, ok := varsInCurrentDocument.(map[interface{}]interface{})
if !ok {
// TODO: test this, it seems if ok == false, then go-yaml should return already, which means ok should always be true?
return errors.Errorf("unable to cast %s to map[interface{}]interface{}", reflect.TypeOf(varsInCurrentDocument))
}
// preserve the vars
// TODO: move it to other function
if varsInCurrentDocument, hasVars := tmpData["vars"]; hasVars {
// NOTE: it's map[interface{}]interface{} instead of map[string]interface{}
// because how go-yaml handle the decoding
// TODO: use the cast package
vars, ok := varsInCurrentDocument.(map[interface{}]interface{})
if !ok {
// TODO: test this, it seems if ok == false, then go-yaml should return already
return errors.Errorf("unable to cast %s to map[interface{}]interface{}", reflect.TypeOf(varsInCurrentDocument))
}
for k, v := range vars {
// TODO: does YAML support non-string as key? if not, this assert is use less
k, ok := k.(string)
if !ok {
// TODO: test this, it seems if ok == false, then go-yaml should return already
return errors.Errorf("unable to cast %s to string", reflect.TypeOf(k))
}
c.vars[k] = v
}

// render again using vars in current document
// NOTE: we don't need to assign c.vars to pongoContext again because it stores the reference to the map, not the copy of the map
// this is the second render
rendered, err = c.RenderDocumentBytes(doc, pongoContext)
if err != nil {
return errors.Wrap(err, "can't render template with vars in current document")
}

fmt.Printf("02-before\n%s", doc)
fmt.Printf("02-after\n%s", rendered)

tmpData = make(map[string]interface{})
err = yaml.Unmarshal(rendered, &tmpData)
if err != nil {
// TODO: different message with previous error
return errors.Wrap(err, "can't parse rendered template yaml to map[string]interface{} after second render")
}
vars := cast.ToStringMap(varsI)
util.MergeStringMap(c.vars, vars)

// render again using vars in current document
// NOTE: we don't need to assign c.vars to pongoContext again because it stores the reference to the map, not the copy of the map
rendered, err = c.RenderDocumentBytes(doc, pongoContext)
if err != nil {
return errors.Wrap(err, "can't render template with vars in current document")
}

// put the data into c.data
for k, v := range tmpData {
c.data[k] = v
fmt.Printf("02-before\n%s", doc)
fmt.Printf("02-after\n%s", rendered)

tmpData = make(map[string]interface{})
err = yaml.Unmarshal(rendered, &tmpData)
if err != nil {
return errors.Wrap(err, "can't parse rendered template yaml to map[string]interface{} after second render")
}
}

// put the data into c.data
for k, v := range tmpData {
c.data[k] = v
}
return nil
}

Expand Down
4 changes: 2 additions & 2 deletions config/yaml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package config

import (
asst "github.com/stretchr/testify/assert"
"log"
"testing"
)

Expand All @@ -27,9 +26,10 @@ b:
d: [3, 4]
`
// the print should show you the string has indent you may not be expecting
log.Print(invalidDat)
//log.Print(invalidDat)
err = c.ParseMultiDocumentBytes([]byte(invalidDat))
assert.NotNil(err)
//log.Print(err.Error())
}

func TestSplitMultiDocument(t *testing.T) {
Expand Down
9 changes: 9 additions & 0 deletions util/map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package util

// MergeStringMap merges the second map to first map, and keep the value of the second map if there is duplicate key
func MergeStringMap(dst map[string]interface{}, extra map[string]interface{}) {
// TODO: rename extra to a more clear name
for k, v := range extra {
dst[k] = v
}
}
18 changes: 18 additions & 0 deletions util/map_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package util

import (
"testing"
asst "github.com/stretchr/testify/assert"
)

func TestMergeStringMap(t *testing.T) {
assert := asst.New(t)
m := make(map[string]interface{})
m2 := make(map[string]interface{})
m["a"] = 123
m2["a"] = 124
m2["b"] = 125
MergeStringMap(m, m2)
assert.Equal(124, m["a"])
assert.Equal(2, len(m))
}

0 comments on commit ce0743c

Please sign in to comment.