From 09c8c17bf06ed5f215922b0755558e58a8723802 Mon Sep 17 00:00:00 2001 From: Cameron Moore Date: Wed, 9 Mar 2016 16:27:56 -0600 Subject: [PATCH] tpl: fix default function This commit fixes a few things: 1. `given` is now a variadic parameter so that piping works properly 2. add separate template tests to make sure piping works 3. support time values 4. add more tests of the dfault function --- docs/content/templates/functions.md | 2 +- tpl/template_funcs.go | 28 +++++++++++---- tpl/template_funcs_test.go | 55 +++++++++++++++++++++++++++-- 3 files changed, 75 insertions(+), 10 deletions(-) diff --git a/docs/content/templates/functions.md b/docs/content/templates/functions.md index f719ec42d..912bb3cde 100644 --- a/docs/content/templates/functions.md +++ b/docs/content/templates/functions.md @@ -37,7 +37,7 @@ non-nil for any other types. e.g. {{ index .Params "font" | default "Roboto" }} → default is "Roboto" - {{ default "Roboto" .Params.font }} → default is "Roboto" + {{ default "Roboto" (index .Params "font") }} → default is "Roboto" ### delimit Loops through any array, slice or map and returns a string of all the values separated by the delimiter. There is an optional third parameter that lets you choose a different delimiter to go between the last two values. diff --git a/tpl/template_funcs.go b/tpl/template_funcs.go index 8a0672726..4df811a1c 100644 --- a/tpl/template_funcs.go +++ b/tpl/template_funcs.go @@ -1243,10 +1243,21 @@ func dateFormat(layout string, v interface{}) (string, error) { // is not. "Set" in this context means true for booleans; non-zero for numeric // types; non-zero length for strings, arrays, slices, and maps; any struct // value; or non-nil for any other types. -func dfault(dflt, given interface{}) interface{} { - g := reflect.ValueOf(given) +func dfault(dflt interface{}, given ...interface{}) (interface{}, error) { + // given is variadic because the following construct will not pass a piped + // argument when the key is missing: {{ index . "key" | default "foo" }} + // The Go template will complain that we got 1 argument when we expectd 2. + + if given == nil || len(given) == 0 { + return dflt, nil + } + if len(given) != 1 { + return nil, fmt.Errorf("wrong number of args for default: want 2 got %d", len(given)+1) + } + + g := reflect.ValueOf(given[0]) if !g.IsValid() { - return dflt + return dflt, nil } set := false @@ -1265,16 +1276,21 @@ func dfault(dflt, given interface{}) interface{} { case reflect.Complex64, reflect.Complex128: set = g.Complex() != 0 case reflect.Struct: - set = true + switch actual := given[0].(type) { + case time.Time: + set = !actual.IsZero() + default: + set = true + } default: set = !g.IsNil() } if set { - return given + return given[0], nil } - return dflt + return dflt, nil } // safeHTMLAttr returns a given string as html/template HTMLAttr content. diff --git a/tpl/template_funcs_test.go b/tpl/template_funcs_test.go index ae1161f27..764478e3b 100644 --- a/tpl/template_funcs_test.go +++ b/tpl/template_funcs_test.go @@ -1844,7 +1844,10 @@ func TestDateFormat(t *testing.T) { } } -func TestDefault(t *testing.T) { +func TestDefaultFunc(t *testing.T) { + then := time.Now() + now := time.Now() + for i, this := range []struct { dflt interface{} given interface{} @@ -1854,34 +1857,80 @@ func TestDefault(t *testing.T) { {"test1", "set", "set"}, {"test2", "", "test2"}, + {"test3", nil, "test3"}, {[2]int{10, 20}, [2]int{1, 2}, [2]int{1, 2}}, {[2]int{10, 20}, [0]int{}, [2]int{10, 20}}, + {[2]int{100, 200}, nil, [2]int{100, 200}}, {[]string{"one"}, []string{"uno"}, []string{"uno"}}, - {[]string{"one"}, []string{}, []string{"one"}}, + {[]string{"two"}, []string{}, []string{"two"}}, + {[]string{"three"}, nil, []string{"three"}}, {map[string]int{"one": 1}, map[string]int{"uno": 1}, map[string]int{"uno": 1}}, {map[string]int{"one": 1}, map[string]int{}, map[string]int{"one": 1}}, + {map[string]int{"two": 2}, nil, map[string]int{"two": 2}}, {10, 1, 1}, {10, 0, 10}, + {20, nil, 20}, {float32(10), float32(1), float32(1)}, {float32(10), 0, float32(10)}, + {float32(20), nil, float32(20)}, {complex(2, -2), complex(1, -1), complex(1, -1)}, {complex(2, -2), complex(0, 0), complex(2, -2)}, + {complex(3, -3), nil, complex(3, -3)}, {struct{ f string }{f: "one"}, struct{ f string }{}, struct{ f string }{}}, + {struct{ f string }{f: "two"}, nil, struct{ f string }{f: "two"}}, + + {then, now, now}, + {then, time.Time{}, then}, } { - res := dfault(this.dflt, this.given) + res, err := dfault(this.dflt, this.given) + if err != nil { + t.Errorf("[%d] default returned an error: %s", i, err) + continue + } if !reflect.DeepEqual(this.expected, res) { t.Errorf("[%d] default returned %v, but expected %v", i, res, this.expected) } } } +func TestDefault(t *testing.T) { + for i, this := range []struct { + input interface{} + tpl string + expected string + ok bool + }{ + {map[string]string{"foo": "bar"}, `{{ index . "foo" | default "nope" }}`, `bar`, true}, + {map[string]string{"foo": "pop"}, `{{ index . "bar" | default "nada" }}`, `nada`, true}, + {map[string]string{"foo": "cat"}, `{{ default "nope" .foo }}`, `cat`, true}, + {map[string]string{"foo": "dog"}, `{{ default "nope" .foo "extra" }}`, ``, false}, + } { + tmpl, err := New().New("test").Parse(this.tpl) + if err != nil { + t.Errorf("[%d] unable to create new html template %q: %s", i, this.tpl, err) + continue + } + + buf := new(bytes.Buffer) + err = tmpl.Execute(buf, this.input) + if (err == nil) != this.ok { + t.Errorf("[%d] execute template returned unexpected error: %s", i, err) + continue + } + + if buf.String() != this.expected { + t.Errorf("[%d] execute template got %v, but expected %v", i, buf.String(), this.expected) + } + } +} + func TestSafeHTML(t *testing.T) { for i, this := range []struct { str string