From 14bb44495e2b23817c52c5ad5c1f3b03faeaa43e Mon Sep 17 00:00:00 2001 From: David Chung Date: Thu, 2 Feb 2017 19:22:22 -0800 Subject: [PATCH 1/3] Rename template function to avoid shadowing of `index` (#382) Signed-off-by: David Chung --- pkg/template/funcs.go | 6 +++--- pkg/template/funcs_test.go | 36 ++++++++++++++++---------------- pkg/template/integration_test.go | 28 +++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 21 deletions(-) diff --git a/pkg/template/funcs.go b/pkg/template/funcs.go index 55205b80f..0e123185b 100644 --- a/pkg/template/funcs.go +++ b/pkg/template/funcs.go @@ -76,9 +76,9 @@ func UnixTime() interface{} { return time.Now().Unix() } -// Index returns the index of search in array. -1 if not found or array is not iterable. An optional true will +// IndexOf returns the index of search in array. -1 if not found or array is not iterable. An optional true will // turn on strict type check while by default string representations are used to compare values. -func Index(srch interface{}, array interface{}, strictOptional ...bool) int { +func IndexOf(srch interface{}, array interface{}, strictOptional ...bool) int { strict := false if len(strictOptional) > 0 { strict = strictOptional[0] @@ -153,6 +153,6 @@ func (t *Template) DefaultFuncs() map[string]interface{} { "lines": SplitLines, "to_json": ToJSON, "from_json": FromJSON, - "index": Index, + "index_of": IndexOf, } } diff --git a/pkg/template/funcs_test.go b/pkg/template/funcs_test.go index 120c2829f..7733e5c7b 100644 --- a/pkg/template/funcs_test.go +++ b/pkg/template/funcs_test.go @@ -281,29 +281,29 @@ func TestMapEncodeDecode(t *testing.T) { require.Equal(t, expect, actual) } -func TestIndex(t *testing.T) { - require.Equal(t, -1, Index("a", []string{"x", "y", "z"})) - require.Equal(t, 1, Index("y", []string{"x", "y", "z"})) - require.Equal(t, -1, Index(25, []string{"x", "y", "z"})) - require.Equal(t, -1, Index(25, 26)) - require.Equal(t, 1, Index("y", []string{"x", "y", "z"})) - require.Equal(t, 1, Index("y", []interface{}{"x", "y", "z"})) - require.Equal(t, 1, Index(1, []interface{}{0, 1, 2})) - require.Equal(t, 1, Index("1", []interface{}{0, 1, 2})) - require.Equal(t, 1, Index(1, []interface{}{0, "1", 2})) - require.Equal(t, -1, Index("1", []interface{}{0, 1, 2}, true)) // strict case type must match - require.Equal(t, 1, Index("1", []interface{}{0, "1", 2}, true)) // strict case type must match - require.Equal(t, -1, Index(1, []interface{}{0, "1", 2}, true)) // strict case type must match +func TestIndexOf(t *testing.T) { + require.Equal(t, -1, IndexOf("a", []string{"x", "y", "z"})) + require.Equal(t, 1, IndexOf("y", []string{"x", "y", "z"})) + require.Equal(t, -1, IndexOf(25, []string{"x", "y", "z"})) + require.Equal(t, -1, IndexOf(25, 26)) + require.Equal(t, 1, IndexOf("y", []string{"x", "y", "z"})) + require.Equal(t, 1, IndexOf("y", []interface{}{"x", "y", "z"})) + require.Equal(t, 1, IndexOf(1, []interface{}{0, 1, 2})) + require.Equal(t, 1, IndexOf("1", []interface{}{0, 1, 2})) + require.Equal(t, 1, IndexOf(1, []interface{}{0, "1", 2})) + require.Equal(t, -1, IndexOf("1", []interface{}{0, 1, 2}, true)) // strict case type must match + require.Equal(t, 1, IndexOf("1", []interface{}{0, "1", 2}, true)) // strict case type must match + require.Equal(t, -1, IndexOf(1, []interface{}{0, "1", 2}, true)) // strict case type must match v := "1" - require.Equal(t, 1, Index(&v, []interface{}{0, "1", 2})) - require.Equal(t, 1, Index(&v, []interface{}{0, &v, 2}, true)) - require.Equal(t, 1, Index(&v, []interface{}{0, &v, 2})) + require.Equal(t, 1, IndexOf(&v, []interface{}{0, "1", 2})) + require.Equal(t, 1, IndexOf(&v, []interface{}{0, &v, 2}, true)) + require.Equal(t, 1, IndexOf(&v, []interface{}{0, &v, 2})) a := "0" c := "2" - require.Equal(t, 1, Index("1", []*string{&a, &v, &c})) + require.Equal(t, 1, IndexOf("1", []*string{&a, &v, &c})) // This doesn't work because the type information is gone and we have just an address - require.Equal(t, -1, Index("1", []interface{}{0, &v, 2})) + require.Equal(t, -1, IndexOf("1", []interface{}{0, &v, 2})) } diff --git a/pkg/template/integration_test.go b/pkg/template/integration_test.go index 4691282d7..23002ad97 100644 --- a/pkg/template/integration_test.go +++ b/pkg/template/integration_test.go @@ -158,3 +158,31 @@ The message is {{str}} require.True(t, context.Bool) require.Equal(t, 23, context.invokes) // note this is private state not accessible in template } + +func TestIndexIndexOf(t *testing.T) { + + { + tt, err := NewTemplate("str://{{ index . 1 }}", Options{}) + require.NoError(t, err) + + view, err := tt.Render([]string{"a", "b", "c", "d"}) + require.NoError(t, err) + require.Equal(t, "b", view) + } + { + tt, err := NewTemplate(`str://{{ index_of "c" . }}`, Options{}) + require.NoError(t, err) + + view, err := tt.Render([]string{"a", "b", "c", "d"}) + require.NoError(t, err) + require.Equal(t, "2", view) + } + { + tt, err := NewTemplate(`str://{{ index . 0 | cat "index-" | nospace }}`, Options{}) + require.NoError(t, err) + + view, err := tt.Render([]string{"a", "b", "c", "d"}) + require.NoError(t, err) + require.Equal(t, "index-a", view) + } +} From 201fa4495f9b234d6862d36d631a0905d74dc310 Mon Sep 17 00:00:00 2001 From: David Chung Date: Fri, 3 Feb 2017 15:14:34 -0800 Subject: [PATCH 2/3] Template function fixes and renaming (#384) Signed-off-by: David Chung --- pkg/template/funcs.go | 32 +++++++++++++++++++++++++++++--- pkg/template/funcs_test.go | 8 ++++++++ pkg/template/template.go | 30 +++++++++++++++++++----------- pkg/template/template_test.go | 17 +++++++++++++++-- 4 files changed, 71 insertions(+), 16 deletions(-) diff --git a/pkg/template/funcs.go b/pkg/template/funcs.go index 0e123185b..43aba9d17 100644 --- a/pkg/template/funcs.go +++ b/pkg/template/funcs.go @@ -33,7 +33,7 @@ func SplitLines(o interface{}) ([]string, error) { // FromJSON decode the input JSON encoded as string or byte slice into a map. func FromJSON(o interface{}) (interface{}, error) { - ret := map[string]interface{}{} + var ret interface{} switch o := o.(type) { case string: err := json.Unmarshal([]byte(o), &ret) @@ -136,11 +136,37 @@ func (t *Template) DefaultFuncs() map[string]interface{} { return make([]struct{}, c) }, - "var": func(name, doc string, v ...interface{}) interface{} { + "def": func(name string, args ...interface{}) (string, error) { + if _, has := t.defaults[name]; has { + // not sure if this is good, but should complain loudly + return "", fmt.Errorf("already defined: %v", name) + } + var doc string + var value interface{} + switch len(args) { + case 1: + // just value, no docs + value = args[0] + case 2: + // docs and value + doc = fmt.Sprintf("%v", args[0]) + value = args[1] + } + t.defaults[name] = defaultValue{ + Name: name, + Value: value, + Doc: doc, + } + return "", nil + }, + + "ref": func(name string) interface{} { if found, has := t.binds[name]; has { return found + } else if v, has := t.defaults[name]; has { + return v.Value } - return v // default + return nil }, "global": func(name string, v interface{}) interface{} { diff --git a/pkg/template/funcs_test.go b/pkg/template/funcs_test.go index 7733e5c7b..e70d41022 100644 --- a/pkg/template/funcs_test.go +++ b/pkg/template/funcs_test.go @@ -57,6 +57,14 @@ func TestQueryObjectEncodeDecode(t *testing.T) { require.NoError(t, err) require.Equal(t, decoded, decoded2) + + decoded, err = FromJSON("[]") + require.NoError(t, err) + require.Equal(t, []interface{}{}, decoded) + + decoded, err = FromJSON(`{"foo":"bar"}`) + require.NoError(t, err) + require.Equal(t, map[string]interface{}{"foo": "bar"}, decoded) } func TestQueryObject(t *testing.T) { diff --git a/pkg/template/template.go b/pkg/template/template.go index 3c3d85998..96095b075 100644 --- a/pkg/template/template.go +++ b/pkg/template/template.go @@ -45,16 +45,23 @@ type Options struct { SocketDir string } +type defaultValue struct { + Name string + Value interface{} + Doc string +} + // Template is the templating engine type Template struct { options Options - url string - body []byte - parsed *template.Template - funcs map[string]interface{} - binds map[string]interface{} - lock sync.Mutex + url string + body []byte + parsed *template.Template + funcs map[string]interface{} + binds map[string]interface{} + defaults map[string]defaultValue + lock sync.Mutex } // NewTemplate fetches the content at the url and returns a template. If the string begins @@ -84,11 +91,12 @@ func NewTemplateFromBytes(buff []byte, contextURL string, opt Options) (*Templat } return &Template{ - options: opt, - url: contextURL, - body: buff, - funcs: map[string]interface{}{}, - binds: map[string]interface{}{}, + options: opt, + url: contextURL, + body: buff, + funcs: map[string]interface{}{}, + binds: map[string]interface{}{}, + defaults: map[string]defaultValue{}, }, nil } diff --git a/pkg/template/template_test.go b/pkg/template/template_test.go index a67076369..35ece01a7 100644 --- a/pkg/template/template_test.go +++ b/pkg/template/template_test.go @@ -34,10 +34,15 @@ func TestVarAndGlobal(t *testing.T) { str := `{{ q "locations[?state == 'WA'].name | sort(@) | {WashingtonCities: join(', ', @)}" . | global "washington-cities"}} {{/* The query above is exported and referenced somewhere else */}} +{{ from_json "[\"SF\",\"LA\"]" | def "california-cities" "Default value for California cities" }} +{{ from_json "{\"SF\":\"94109\",\"LA\":\"90210\"}" | def "zip-codes" "Default value for zip codes" }} + { "test" : "hello", "val" : true, - "result" : {{var "washington-cities" "A json with washington cities" | to_json}} + "result" : {{ref "washington-cities" | to_json}}, + "california" : {{ ref "california-cities" | to_json}}, + "sf_zip" : {{ ref "zip-codes" | q "SF" | to_json }} } ` @@ -59,12 +64,20 @@ func TestVarAndGlobal(t *testing.T) { expected := ` + + + { "test" : "hello", "val" : true, "result" : { "WashingtonCities": "Bellevue, Olympia, Seattle" -} +}, + "california" : [ + "SF", + "LA" +], + "sf_zip" : "94109" } ` require.Equal(t, expected, view) From f82389641b85cd621af4bc1524b40995e329bb57 Mon Sep 17 00:00:00 2001 From: David Chung Date: Fri, 3 Feb 2017 15:14:34 -0800 Subject: [PATCH 3/3] Template function fixes and renaming (#384) Signed-off-by: David Chung --- pkg/template/funcs.go | 32 +++++++++++++++++++++++++++++--- pkg/template/funcs_test.go | 8 ++++++++ pkg/template/template.go | 30 +++++++++++++++++++----------- pkg/template/template_test.go | 17 +++++++++++++++-- 4 files changed, 71 insertions(+), 16 deletions(-) diff --git a/pkg/template/funcs.go b/pkg/template/funcs.go index 0e123185b..43aba9d17 100644 --- a/pkg/template/funcs.go +++ b/pkg/template/funcs.go @@ -33,7 +33,7 @@ func SplitLines(o interface{}) ([]string, error) { // FromJSON decode the input JSON encoded as string or byte slice into a map. func FromJSON(o interface{}) (interface{}, error) { - ret := map[string]interface{}{} + var ret interface{} switch o := o.(type) { case string: err := json.Unmarshal([]byte(o), &ret) @@ -136,11 +136,37 @@ func (t *Template) DefaultFuncs() map[string]interface{} { return make([]struct{}, c) }, - "var": func(name, doc string, v ...interface{}) interface{} { + "def": func(name string, args ...interface{}) (string, error) { + if _, has := t.defaults[name]; has { + // not sure if this is good, but should complain loudly + return "", fmt.Errorf("already defined: %v", name) + } + var doc string + var value interface{} + switch len(args) { + case 1: + // just value, no docs + value = args[0] + case 2: + // docs and value + doc = fmt.Sprintf("%v", args[0]) + value = args[1] + } + t.defaults[name] = defaultValue{ + Name: name, + Value: value, + Doc: doc, + } + return "", nil + }, + + "ref": func(name string) interface{} { if found, has := t.binds[name]; has { return found + } else if v, has := t.defaults[name]; has { + return v.Value } - return v // default + return nil }, "global": func(name string, v interface{}) interface{} { diff --git a/pkg/template/funcs_test.go b/pkg/template/funcs_test.go index 7733e5c7b..e70d41022 100644 --- a/pkg/template/funcs_test.go +++ b/pkg/template/funcs_test.go @@ -57,6 +57,14 @@ func TestQueryObjectEncodeDecode(t *testing.T) { require.NoError(t, err) require.Equal(t, decoded, decoded2) + + decoded, err = FromJSON("[]") + require.NoError(t, err) + require.Equal(t, []interface{}{}, decoded) + + decoded, err = FromJSON(`{"foo":"bar"}`) + require.NoError(t, err) + require.Equal(t, map[string]interface{}{"foo": "bar"}, decoded) } func TestQueryObject(t *testing.T) { diff --git a/pkg/template/template.go b/pkg/template/template.go index 3c3d85998..96095b075 100644 --- a/pkg/template/template.go +++ b/pkg/template/template.go @@ -45,16 +45,23 @@ type Options struct { SocketDir string } +type defaultValue struct { + Name string + Value interface{} + Doc string +} + // Template is the templating engine type Template struct { options Options - url string - body []byte - parsed *template.Template - funcs map[string]interface{} - binds map[string]interface{} - lock sync.Mutex + url string + body []byte + parsed *template.Template + funcs map[string]interface{} + binds map[string]interface{} + defaults map[string]defaultValue + lock sync.Mutex } // NewTemplate fetches the content at the url and returns a template. If the string begins @@ -84,11 +91,12 @@ func NewTemplateFromBytes(buff []byte, contextURL string, opt Options) (*Templat } return &Template{ - options: opt, - url: contextURL, - body: buff, - funcs: map[string]interface{}{}, - binds: map[string]interface{}{}, + options: opt, + url: contextURL, + body: buff, + funcs: map[string]interface{}{}, + binds: map[string]interface{}{}, + defaults: map[string]defaultValue{}, }, nil } diff --git a/pkg/template/template_test.go b/pkg/template/template_test.go index a67076369..35ece01a7 100644 --- a/pkg/template/template_test.go +++ b/pkg/template/template_test.go @@ -34,10 +34,15 @@ func TestVarAndGlobal(t *testing.T) { str := `{{ q "locations[?state == 'WA'].name | sort(@) | {WashingtonCities: join(', ', @)}" . | global "washington-cities"}} {{/* The query above is exported and referenced somewhere else */}} +{{ from_json "[\"SF\",\"LA\"]" | def "california-cities" "Default value for California cities" }} +{{ from_json "{\"SF\":\"94109\",\"LA\":\"90210\"}" | def "zip-codes" "Default value for zip codes" }} + { "test" : "hello", "val" : true, - "result" : {{var "washington-cities" "A json with washington cities" | to_json}} + "result" : {{ref "washington-cities" | to_json}}, + "california" : {{ ref "california-cities" | to_json}}, + "sf_zip" : {{ ref "zip-codes" | q "SF" | to_json }} } ` @@ -59,12 +64,20 @@ func TestVarAndGlobal(t *testing.T) { expected := ` + + + { "test" : "hello", "val" : true, "result" : { "WashingtonCities": "Bellevue, Olympia, Seattle" -} +}, + "california" : [ + "SF", + "LA" +], + "sf_zip" : "94109" } ` require.Equal(t, expected, view)