diff options
Diffstat (limited to 'tpl')
-rw-r--r-- | tpl/template.go | 714 | ||||
-rw-r--r-- | tpl/template_embedded.go | 97 | ||||
-rw-r--r-- | tpl/template_test.go | 341 |
3 files changed, 1152 insertions, 0 deletions
diff --git a/tpl/template.go b/tpl/template.go new file mode 100644 index 000000000..b79b478eb --- /dev/null +++ b/tpl/template.go @@ -0,0 +1,714 @@ +// Copyright © 2013-14 Steve Francia <spf@spf13.com>. +// +// Licensed under the Simple Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://opensource.org/licenses/Simple-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tpl + +import ( + "bytes" + "errors" + "html" + "html/template" + "io" + "io/ioutil" + "os" + "path/filepath" + "reflect" + "strconv" + "strings" + + "github.com/eknkc/amber" + "github.com/spf13/cast" + "github.com/spf13/hugo/helpers" + jww "github.com/spf13/jwalterweatherman" +) + +var localTemplates *template.Template +var tmpl Template + +type Template interface { + ExecuteTemplate(wr io.Writer, name string, data interface{}) error + Lookup(name string) *template.Template + Templates() []*template.Template + New(name string) *template.Template + LoadTemplates(absPath string) + LoadTemplatesWithPrefix(absPath, prefix string) + AddTemplate(name, tpl string) error + AddInternalTemplate(prefix, name, tpl string) error + AddInternalShortcode(name, tpl string) error +} + +type templateErr struct { + name string + err error +} + +type GoHtmlTemplate struct { + template.Template + errors []*templateErr +} + +// The "Global" Template System +func T() Template { + if tmpl == nil { + tmpl = New() + } + + return tmpl +} + +// Return a new Hugo Template System +// With all the additional features, templates & functions +func New() Template { + var templates = &GoHtmlTemplate{ + Template: *template.New(""), + errors: make([]*templateErr, 0), + } + + localTemplates = &templates.Template + + funcMap := template.FuncMap{ + "urlize": helpers.Urlize, + "sanitizeurl": helpers.SanitizeUrl, + "eq": Eq, + "ne": Ne, + "gt": Gt, + "ge": Ge, + "lt": Lt, + "le": Le, + "in": In, + "intersect": Intersect, + "isset": IsSet, + "echoParam": ReturnWhenSet, + "safeHtml": SafeHtml, + "first": First, + "where": Where, + "highlight": Highlight, + "add": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '+') }, + "sub": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '-') }, + "div": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '/') }, + "mod": Mod, + "mul": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '*') }, + "modBool": ModBool, + "lower": func(a string) string { return strings.ToLower(a) }, + "upper": func(a string) string { return strings.ToUpper(a) }, + "title": func(a string) string { return strings.Title(a) }, + "partial": Partial, + } + + templates.Funcs(funcMap) + templates.LoadEmbedded() + return templates +} + +func Eq(x, y interface{}) bool { + return reflect.DeepEqual(x, y) +} + +func Ne(x, y interface{}) bool { + return !Eq(x, y) +} + +func Ge(a, b interface{}) bool { + left, right := compareGetFloat(a, b) + return left >= right +} + +func Gt(a, b interface{}) bool { + left, right := compareGetFloat(a, b) + return left > right +} + +func Le(a, b interface{}) bool { + left, right := compareGetFloat(a, b) + return left <= right +} + +func Lt(a, b interface{}) bool { + left, right := compareGetFloat(a, b) + return left < right +} + +func compareGetFloat(a interface{}, b interface{}) (float64, float64) { + var left, right float64 + av := reflect.ValueOf(a) + + switch av.Kind() { + case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: + left = float64(av.Len()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + left = float64(av.Int()) + case reflect.Float32, reflect.Float64: + left = av.Float() + case reflect.String: + left, _ = strconv.ParseFloat(av.String(), 64) + } + + bv := reflect.ValueOf(b) + + switch bv.Kind() { + case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: + right = float64(bv.Len()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + right = float64(bv.Int()) + case reflect.Float32, reflect.Float64: + right = bv.Float() + case reflect.String: + right, _ = strconv.ParseFloat(bv.String(), 64) + } + + return left, right +} + +func Intersect(l1, l2 interface{}) (interface{}, error) { + + if l1 == nil || l2 == nil { + return make([]interface{}, 0), nil + } + + l1v := reflect.ValueOf(l1) + l2v := reflect.ValueOf(l2) + + switch l1v.Kind() { + case reflect.Array, reflect.Slice: + switch l2v.Kind() { + case reflect.Array, reflect.Slice: + r := reflect.MakeSlice(l1v.Type(), 0, 0) + for i := 0; i < l1v.Len(); i++ { + l1vv := l1v.Index(i) + for j := 0; j < l2v.Len(); j++ { + l2vv := l2v.Index(j) + switch l1vv.Kind() { + case reflect.String: + if l1vv.Type() == l2vv.Type() && l1vv.String() == l2vv.String() && !In(r, l2vv) { + r = reflect.Append(r, l2vv) + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch l2vv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if l1vv.Int() == l2vv.Int() && !In(r, l2vv) { + r = reflect.Append(r, l2vv) + } + } + case reflect.Float32, reflect.Float64: + switch l2vv.Kind() { + case reflect.Float32, reflect.Float64: + if l1vv.Float() == l2vv.Float() && !In(r, l2vv) { + r = reflect.Append(r, l2vv) + } + } + } + } + } + return r.Interface(), nil + default: + return nil, errors.New("can't iterate over " + reflect.ValueOf(l2).Type().String()) + } + default: + return nil, errors.New("can't iterate over " + reflect.ValueOf(l1).Type().String()) + } +} + +func In(l interface{}, v interface{}) bool { + lv := reflect.ValueOf(l) + vv := reflect.ValueOf(v) + + switch lv.Kind() { + case reflect.Array, reflect.Slice: + for i := 0; i < lv.Len(); i++ { + lvv := lv.Index(i) + switch lvv.Kind() { + case reflect.String: + if vv.Type() == lvv.Type() && vv.String() == lvv.String() { + return true + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch vv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if vv.Int() == lvv.Int() { + return true + } + } + case reflect.Float32, reflect.Float64: + switch vv.Kind() { + case reflect.Float32, reflect.Float64: + if vv.Float() == lvv.Float() { + return true + } + } + } + } + case reflect.String: + if vv.Type() == lv.Type() && strings.Contains(lv.String(), vv.String()) { + return true + } + } + return false +} + +// First is exposed to templates, to iterate over the first N items in a +// rangeable list. +func First(limit interface{}, seq interface{}) (interface{}, error) { + + limitv, err := cast.ToIntE(limit) + + if err != nil { + return nil, err + } + + if limitv < 1 { + return nil, errors.New("can't return negative/empty count of items from sequence") + } + + seqv := reflect.ValueOf(seq) + // this is better than my first pass; ripped from text/template/exec.go indirect(): + for ; seqv.Kind() == reflect.Ptr || seqv.Kind() == reflect.Interface; seqv = seqv.Elem() { + if seqv.IsNil() { + return nil, errors.New("can't iterate over a nil value") + } + if seqv.Kind() == reflect.Interface && seqv.NumMethod() > 0 { + break + } + } + + switch seqv.Kind() { + case reflect.Array, reflect.Slice, reflect.String: + // okay + default: + return nil, errors.New("can't iterate over " + reflect.ValueOf(seq).Type().String()) + } + if limitv > seqv.Len() { + limitv = seqv.Len() + } + return seqv.Slice(0, limitv).Interface(), nil +} + +func Where(seq, key, match interface{}) (interface{}, error) { + seqv := reflect.ValueOf(seq) + kv := reflect.ValueOf(key) + mv := reflect.ValueOf(match) + + // this is better than my first pass; ripped from text/template/exec.go indirect(): + for ; seqv.Kind() == reflect.Ptr || seqv.Kind() == reflect.Interface; seqv = seqv.Elem() { + if seqv.IsNil() { + return nil, errors.New("can't iterate over a nil value") + } + if seqv.Kind() == reflect.Interface && seqv.NumMethod() > 0 { + break + } + } + + switch seqv.Kind() { + case reflect.Array, reflect.Slice: + r := reflect.MakeSlice(seqv.Type(), 0, 0) + for i := 0; i < seqv.Len(); i++ { + var vvv reflect.Value + vv := seqv.Index(i) + switch vv.Kind() { + case reflect.Map: + if kv.Type() == vv.Type().Key() && vv.MapIndex(kv).IsValid() { + vvv = vv.MapIndex(kv) + } + case reflect.Struct: + if kv.Kind() == reflect.String { + method := vv.MethodByName(kv.String()) + if method.IsValid() && method.Type().NumIn() == 0 && method.Type().NumOut() > 0 { + vvv = method.Call(nil)[0] + } else if vv.FieldByName(kv.String()).IsValid() { + vvv = vv.FieldByName(kv.String()) + } + } + case reflect.Ptr: + if !vv.IsNil() { + ev := vv.Elem() + switch ev.Kind() { + case reflect.Map: + if kv.Type() == ev.Type().Key() && ev.MapIndex(kv).IsValid() { + vvv = ev.MapIndex(kv) + } + case reflect.Struct: + if kv.Kind() == reflect.String { + method := vv.MethodByName(kv.String()) + if method.IsValid() && method.Type().NumIn() == 0 && method.Type().NumOut() > 0 { + vvv = method.Call(nil)[0] + } else if ev.FieldByName(kv.String()).IsValid() { + vvv = ev.FieldByName(kv.String()) + } + } + } + } + } + + if vvv.IsValid() && mv.Type() == vvv.Type() { + switch mv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if mv.Int() == vvv.Int() { + r = reflect.Append(r, vv) + } + case reflect.String: + if mv.String() == vvv.String() { + r = reflect.Append(r, vv) + } + } + } + } + return r.Interface(), nil + default: + return nil, errors.New("can't iterate over " + reflect.ValueOf(seq).Type().String()) + } +} + +func IsSet(a interface{}, key interface{}) bool { + av := reflect.ValueOf(a) + kv := reflect.ValueOf(key) + + switch av.Kind() { + case reflect.Array, reflect.Chan, reflect.Slice: + if int64(av.Len()) > kv.Int() { + return true + } + case reflect.Map: + if kv.Type() == av.Type().Key() { + return av.MapIndex(kv).IsValid() + } + } + + return false +} + +func ReturnWhenSet(a interface{}, index int) interface{} { + av := reflect.ValueOf(a) + + switch av.Kind() { + case reflect.Array, reflect.Slice: + if av.Len() > index { + + avv := av.Index(index) + switch avv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return avv.Int() + case reflect.String: + return avv.String() + } + } + } + + return "" +} + +func Highlight(in interface{}, lang string) template.HTML { + var str string + av := reflect.ValueOf(in) + switch av.Kind() { + case reflect.String: + str = av.String() + } + + return template.HTML(helpers.Highlight(html.UnescapeString(str), lang)) +} + +func SafeHtml(text string) template.HTML { + return template.HTML(text) +} + +func doArithmetic(a, b interface{}, op rune) (interface{}, error) { + av := reflect.ValueOf(a) + bv := reflect.ValueOf(b) + var ai, bi int64 + var af, bf float64 + var au, bu uint64 + switch av.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + ai = av.Int() + switch bv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + bi = bv.Int() + case reflect.Float32, reflect.Float64: + af = float64(ai) // may overflow + ai = 0 + bf = bv.Float() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + bu = bv.Uint() + if ai >= 0 { + au = uint64(ai) + ai = 0 + } else { + bi = int64(bu) // may overflow + bu = 0 + } + default: + return nil, errors.New("Can't apply the operator to the values") + } + case reflect.Float32, reflect.Float64: + af = av.Float() + switch bv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + bf = float64(bv.Int()) // may overflow + case reflect.Float32, reflect.Float64: + bf = bv.Float() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + bf = float64(bv.Uint()) // may overflow + default: + return nil, errors.New("Can't apply the operator to the values") + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + au = av.Uint() + switch bv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + bi = bv.Int() + if bi >= 0 { + bu = uint64(bi) + bi = 0 + } else { + ai = int64(au) // may overflow + au = 0 + } + case reflect.Float32, reflect.Float64: + af = float64(au) // may overflow + au = 0 + bf = bv.Float() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + bu = bv.Uint() + default: + return nil, errors.New("Can't apply the operator to the values") + } + case reflect.String: + as := av.String() + if bv.Kind() == reflect.String && op == '+' { + bs := bv.String() + return as + bs, nil + } else { + return nil, errors.New("Can't apply the operator to the values") + } + default: + return nil, errors.New("Can't apply the operator to the values") + } + + switch op { + case '+': + if ai != 0 || bi != 0 { + return ai + bi, nil + } else if af != 0 || bf != 0 { + return af + bf, nil + } else if au != 0 || bu != 0 { + return au + bu, nil + } else { + return 0, nil + } + case '-': + if ai != 0 || bi != 0 { + return ai - bi, nil + } else if af != 0 || bf != 0 { + return af - bf, nil + } else if au != 0 || bu != 0 { + return au - bu, nil + } else { + return 0, nil + } + case '*': + if ai != 0 || bi != 0 { + return ai * bi, nil + } else if af != 0 || bf != 0 { + return af * bf, nil + } else if au != 0 || bu != 0 { + return au * bu, nil + } else { + return 0, nil + } + case '/': + if bi != 0 { + return ai / bi, nil + } else if bf != 0 { + return af / bf, nil + } else if bu != 0 { + return au / bu, nil + } else { + return nil, errors.New("Can't divide the value by 0") + } + default: + return nil, errors.New("There is no such an operation") + } +} + +func Mod(a, b interface{}) (int64, error) { + av := reflect.ValueOf(a) + bv := reflect.ValueOf(b) + var ai, bi int64 + + switch av.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + ai = av.Int() + default: + return 0, errors.New("Modulo operator can't be used with non integer value") + } + + switch bv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + bi = bv.Int() + default: + return 0, errors.New("Modulo operator can't be used with non integer value") + } + + if bi == 0 { + return 0, errors.New("The number can't be divided by zero at modulo operation") + } + + return ai % bi, nil +} + +func ModBool(a, b interface{}) (bool, error) { + res, err := Mod(a, b) + if err != nil { + return false, err + } + return res == int64(0), nil +} + +func Partial(name string, context_list ...interface{}) template.HTML { + if strings.HasPrefix("partials/", name) { + name = name[8:] + } + var context interface{} + + if len(context_list) == 0 { + context = nil + } else { + context = context_list[0] + } + return ExecuteTemplateToHTML(context, "partials/"+name, "theme/partials/"+name) +} + +func ExecuteTemplate(context interface{}, layouts ...string) *bytes.Buffer { + buffer := new(bytes.Buffer) + worked := false + for _, layout := range layouts { + name := layout + + if localTemplates.Lookup(name) == nil { + name = layout + ".html" + } + + if localTemplates.Lookup(name) != nil { + err := localTemplates.ExecuteTemplate(buffer, name, context) + if err != nil { + jww.ERROR.Println(err, "in", name) + } + worked = true + break + } + } + if !worked { + jww.ERROR.Println("Unable to render", layouts) + jww.ERROR.Println("Expecting to find a template in either the theme/layouts or /layouts in one of the following relative locations", layouts) + } + + return buffer +} + +func ExecuteTemplateToHTML(context interface{}, layouts ...string) template.HTML { + b := ExecuteTemplate(context, layouts...) + return template.HTML(string(b.Bytes())) +} + +func (t *GoHtmlTemplate) LoadEmbedded() { + t.EmbedShortcodes() + t.EmbedTemplates() +} + +func (t *GoHtmlTemplate) AddInternalTemplate(prefix, name, tpl string) error { + if prefix != "" { + return t.AddTemplate("_internal/"+prefix+"/"+name, tpl) + } else { + return t.AddTemplate("_internal/"+name, tpl) + } +} + +func (t *GoHtmlTemplate) AddInternalShortcode(name, content string) error { + return t.AddInternalTemplate("shortcodes", name, content) +} + +func (t *GoHtmlTemplate) AddTemplate(name, tpl string) error { + _, err := t.New(name).Parse(tpl) + if err != nil { + t.errors = append(t.errors, &templateErr{name: name, err: err}) + } + return err +} + +func (t *GoHtmlTemplate) AddTemplateFile(name, path string) error { + // get the suffix and switch on that + ext := filepath.Ext(path) + switch ext { + case ".amber": + compiler := amber.New() + // Parse the input file + if err := compiler.ParseFile(path); err != nil { + return nil + } + + if _, err := compiler.CompileWithTemplate(t.New(name)); err != nil { + return err + } + default: + b, err := ioutil.ReadFile(path) + if err != nil { + return err + } + + return t.AddTemplate(name, string(b)) + } + + return nil + +} + +func (t *GoHtmlTemplate) generateTemplateNameFrom(base, path string) string { + return filepath.ToSlash(path[len(base)+1:]) +} + +func ignoreDotFile(path string) bool { + return filepath.Base(path)[0] == '.' +} + +func (t *GoHtmlTemplate) loadTemplates(absPath string, prefix string) { + walker := func(path string, fi os.FileInfo, err error) error { + if err != nil { + return nil + } + + if !fi.IsDir() { + if ignoreDotFile(path) { + return nil + } + + tplName := t.generateTemplateNameFrom(absPath, path) + + if prefix != "" { + tplName = strings.Trim(prefix, "/") + "/" + tplName + } + + t.AddTemplateFile(tplName, path) + + } + return nil + } + + filepath.Walk(absPath, walker) +} + +func (t *GoHtmlTemplate) LoadTemplatesWithPrefix(absPath string, prefix string) { + t.loadTemplates(absPath, prefix) +} + +func (t *GoHtmlTemplate) LoadTemplates(absPath string) { + t.loadTemplates(absPath, "") +} diff --git a/tpl/template_embedded.go b/tpl/template_embedded.go new file mode 100644 index 000000000..e2ad1fd93 --- /dev/null +++ b/tpl/template_embedded.go @@ -0,0 +1,97 @@ +// Copyright © 2013 Steve Francia <spf@spf13.com>. +// +// Licensed under the Simple Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://opensource.org/licenses/Simple-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tpl + +type Tmpl struct { + Name string + Data string +} + +func (t *GoHtmlTemplate) EmbedShortcodes() { + t.AddInternalShortcode("highlight.html", `{{ .Get 0 | highlight .Inner }}`) + t.AddInternalShortcode("test.html", `This is a simple Test`) + t.AddInternalShortcode("figure.html", `<!-- image --> +<figure {{ with .Get "class" }}class="{{.}}"{{ end }}> + {{ with .Get "link"}}<a href="{{.}}">{{ end }} + <img src="{{ .Get "src" }}" {{ if or (.Get "alt") (.Get "caption") }}alt="{{ with .Get "alt"}}{{.}}{{else}}{{ .Get "caption" }}{{ end }}" {{ end }}{{ with .Get "width" }}width="{{.}}" {{ end }}/> + {{ if .Get "link"}}</a>{{ end }} + {{ if or (or (.Get "title") (.Get "caption")) (.Get "attr")}} + <figcaption>{{ if isset .Params "title" }} + <h4>{{ .Get "title" }}</h4>{{ end }} + {{ if or (.Get "caption") (.Get "attr")}}<p> + {{ .Get "caption" }} + {{ with .Get "attrlink"}}<a href="{{.}}"> {{ end }} + {{ .Get "attr" }} + {{ if .Get "attrlink"}}</a> {{ end }} + </p> {{ end }} + </figcaption> + {{ end }} +</figure> +<!-- image -->`) +} + +func (t *GoHtmlTemplate) EmbedTemplates() { + + t.AddInternalTemplate("_default", "rss.xml", `<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> + <channel> + <title>{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}</title> + <link>{{ .Permalink }}</link> + <description>Recent content {{ with .Title }}in {{.}} {{ end }}on {{ .Site.Title }}</description> + <generator>Hugo -- gohugo.io</generator> + {{ with .Site.LanguageCode }}<language>{{.}}</language>{{end}} + {{ with .Site.Author.name }}<author>{{.}}</author>{{end}} + {{ with .Site.Copyright }}<copyright>{{.}}</copyright>{{end}} + <lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 MST" }}</lastBuildDate> + <atom:link href="{{.Url}}" rel="self" type="application/rss+xml" /> + {{ range first 15 .Data.Pages }} + <item> + <title>{{ .Title }}</title> + <link>{{ .Permalink }}</link> + <pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 MST" }}</pubDate> + {{with .Site.Author.name}}<author>{{.}}</author>{{end}} + <guid>{{ .Permalink }}</guid> + <description>{{ .Content | html }}</description> + </item> + {{ end }} + </channel> +</rss>`) + + t.AddInternalTemplate("_default", "sitemap.xml", `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> + {{ range .Data.Pages }} + <url> + <loc>{{ .Permalink }}</loc> + <lastmod>{{ safeHtml ( .Date.Format "2006-01-02T15:04:05-07:00" ) }}</lastmod>{{ with .Sitemap.ChangeFreq }} + <changefreq>{{ . }}</changefreq>{{ end }}{{ if ge .Sitemap.Priority 0.0 }} + <priority>{{ .Sitemap.Priority }}</priority>{{ end }} + </url> + {{ end }} +</urlset>`) + + t.AddInternalTemplate("", "disqus.html", `{{ if .Site.DisqusShortname }}<div id="disqus_thread"></div> +<script type="text/javascript"> + var disqus_shortname = '{{ .Site.DisqusShortname }}'; + var disqus_identifier = '{{with .GetParam "disqus_identifier" }}{{ . }}{{ else }}{{ .Permalink }}{{end}}'; + var disqus_title = '{{with .GetParam "disqus_title" }}{{ . }}{{ else }}{{ .Title }}{{end}}'; + var disqus_url = '{{with .GetParam "disqus_url" }}{{ . | html }}{{ else }}{{ .Permalink }}{{end}}'; + + (function() { + var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; + dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; + (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); + })(); +</script> +<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript> +<a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>{{end}}`) + +} diff --git a/tpl/template_test.go b/tpl/template_test.go new file mode 100644 index 000000000..5eff0f067 --- /dev/null +++ b/tpl/template_test.go @@ -0,0 +1,341 @@ +package tpl + +import ( + "reflect" + "testing" +) + +func TestGt(t *testing.T) { + for i, this := range []struct { + left interface{} + right interface{} + leftShouldWin bool + }{ + {5, 8, false}, + {8, 5, true}, + {5, 5, false}, + {-2, 1, false}, + {2, -5, true}, + {0.0, 1.23, false}, + {1.23, 0.0, true}, + {"8", "5", true}, + {"5", "0001", true}, + {[]int{100, 99}, []int{1, 2, 3, 4}, false}, + } { + leftIsBigger := Gt(this.left, this.right) + if leftIsBigger != this.leftShouldWin { + var which string + if leftIsBigger { + which = "expected right to be bigger, but left was" + } else { + which = "expected left to be bigger, but right was" + } + t.Errorf("[%d] %v compared to %v: %s", i, this.left, this.right, which) + } + } +} + +func TestDoArithmetic(t *testing.T) { + for i, this := range []struct { + a interface{} + b interface{} + op rune + expect interface{} + }{ + {3, 2, '+', int64(5)}, + {3, 2, '-', int64(1)}, + {3, 2, '*', int64(6)}, + {3, 2, '/', int64(1)}, + {3.0, 2, '+', float64(5)}, + {3.0, 2, '-', float64(1)}, + {3.0, 2, '*', float64(6)}, + {3.0, 2, '/', float64(1.5)}, + {3, 2.0, '+', float64(5)}, + {3, 2.0, '-', float64(1)}, + {3, 2.0, '*', float64(6)}, + {3, 2.0, '/', float64(1.5)}, + {3.0, 2.0, '+', float64(5)}, + {3.0, 2.0, '-', float64(1)}, + {3.0, 2.0, '*', float64(6)}, + {3.0, 2.0, '/', float64(1.5)}, + {uint(3), uint(2), '+', uint64(5)}, + {uint(3), uint(2), '-', uint64(1)}, + {uint(3), uint(2), '*', uint64(6)}, + {uint(3), uint(2), '/', uint64(1)}, + {uint(3), 2, '+', uint64(5)}, + {uint(3), 2, '-', uint64(1)}, + {uint(3), 2, '*', uint64(6)}, + {uint(3), 2, '/', uint64(1)}, + {3, uint(2), '+', uint64(5)}, + {3, uint(2), '-', uint64(1)}, + {3, uint(2), '*', uint64(6)}, + {3, uint(2), '/', uint64(1)}, + {uint(3), -2, '+', int64(1)}, + {uint(3), -2, '-', int64(5)}, + {uint(3), -2, '*', int64(-6)}, + {uint(3), -2, '/', int64(-1)}, + {-3, uint(2), '+', int64(-1)}, + {-3, uint(2), '-', int64(-5)}, + {-3, uint(2), '*', int64(-6)}, + {-3, uint(2), '/', int64(-1)}, + {uint(3), 2.0, '+', float64(5)}, + {uint(3), 2.0, '-', float64(1)}, + {uint(3), 2.0, '*', float64(6)}, + {uint(3), 2.0, '/', float64(1.5)}, + {3.0, uint(2), '+', float64(5)}, + {3.0, uint(2), '-', float64(1)}, + {3.0, uint(2), '*', float64(6)}, + {3.0, uint(2), '/', float64(1.5)}, + {0, 0, '+', 0}, + {0, 0, '-', 0}, + {0, 0, '*', 0}, + {"foo", "bar", '+', "foobar"}, + {3, 0, '/', false}, + {3.0, 0, '/', false}, + {3, 0.0, '/', false}, + {uint(3), uint(0), '/', false}, + {3, uint(0), '/', false}, + {-3, uint(0), '/', false}, + {uint(3), 0, '/', false}, + {3.0, uint(0), '/', false}, + {uint(3), 0.0, '/', false}, + {3, "foo", '+', false}, + {3.0, "foo", '+', false}, + {uint(3), "foo", '+', false}, + {"foo", 3, '+', false}, + {"foo", "bar", '-', false}, + {3, 2, '%', false}, + } { + result, err := doArithmetic(this.a, this.b, this.op) + if b, ok := this.expect.(bool); ok && !b { + if err == nil { + t.Errorf("[%d] doArithmetic didn't return an expected error") + } + } else { + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if !reflect.DeepEqual(result, this.expect) { + t.Errorf("[%d] doArithmetic got %v but expected %v", i, result, this.expect) + } + } + } +} + +func TestMod(t *testing.T) { + for i, this := range []struct { + a interface{} + b interface{} + expect interface{} + }{ + {3, 2, int64(1)}, + {3, 1, int64(0)}, + {3, 0, false}, + {0, 3, int64(0)}, + {3.1, 2, false}, + {3, 2.1, false}, + {3.1, 2.1, false}, + {int8(3), int8(2), int64(1)}, + {int16(3), int16(2), int64(1)}, + {int32(3), int32(2), int64(1)}, + {int64(3), int64(2), int64(1)}, + } { + result, err := Mod(this.a, this.b) + if b, ok := this.expect.(bool); ok && !b { + if err == nil { + t.Errorf("[%d] modulo didn't return an expected error") + } + } else { + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if !reflect.DeepEqual(result, this.expect) { + t.Errorf("[%d] modulo got %v but expected %v", i, result, this.expect) + } + } + } +} + +func TestModBool(t *testing.T) { + for i, this := range []struct { + a interface{} + b interface{} + expect interface{} + }{ + {3, 3, true}, + {3, 2, false}, + {3, 1, true}, + {3, 0, nil}, + {0, 3, true}, + {3.1, 2, nil}, + {3, 2.1, nil}, + {3.1, 2.1, nil}, + {int8(3), int8(3), true}, + {int8(3), int8(2), false}, + {int16(3), int16(3), true}, + {int16(3), int16(2), false}, + {int32(3), int32(3), true}, + {int32(3), int32(2), false}, + {int64(3), int64(3), true}, + {int64(3), int64(2), false}, + } { + result, err := ModBool(this.a, this.b) + if this.expect == nil { + if err == nil { + t.Errorf("[%d] modulo didn't return an expected error") + } + } else { + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if !reflect.DeepEqual(result, this.expect) { + t.Errorf("[%d] modulo got %v but expected %v", i, result, this.expect) + } + } + } +} + +func TestFirst(t *testing.T) { + for i, this := range []struct { + count interface{} + sequence interface{} + expect interface{} + }{ + {int(2), []string{"a", "b", "c"}, []string{"a", "b"}}, + {int32(3), []string{"a", "b"}, []string{"a", "b"}}, + {int64(2), []int{100, 200, 300}, []int{100, 200}}, + {100, []int{100, 200}, []int{100, 200}}, + {"1", []int{100, 200, 300}, []int{100}}, + {int64(-1), []int{100, 200, 300}, false}, + {"noint", []int{100, 200, 300}, false}, + } { + results, err := First(this.count, this.sequence) + if b, ok := this.expect.(bool); ok && !b { + if err == nil { + t.Errorf("[%d] First didn't return an expected error") + } + } else { + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if !reflect.DeepEqual(results, this.expect) { + t.Errorf("[%d] First %d items, got %v but expected %v", i, this.count, results, this.expect) + } + } + } +} + +func TestIn(t *testing.T) { + for i, this := range []struct { + v1 interface{} + v2 interface{} + expect bool + }{ + {[]string{"a", "b", "c"}, "b", true}, + {[]string{"a", "b", "c"}, "d", false}, + {[]string{"a", "12", "c"}, 12, false}, + {[]int{1, 2, 4}, 2, true}, + {[]int{1, 2, 4}, 3, false}, + {[]float64{1.23, 2.45, 4.67}, 1.23, true}, + {[]float64{1.234567, 2.45, 4.67}, 1.234568, false}, + {"this substring should be found", "substring", true}, + {"this substring should not be found", "subseastring", false}, + } { + result := In(this.v1, this.v2) + + if result != this.expect { + t.Errorf("[%d] Got %v but expected %v", i, result, this.expect) + } + } +} + +func TestIntersect(t *testing.T) { + for i, this := range []struct { + sequence1 interface{} + sequence2 interface{} + expect interface{} + }{ + {[]string{"a", "b", "c"}, []string{"a", "b"}, []string{"a", "b"}}, + {[]string{"a", "b"}, []string{"a", "b", "c"}, []string{"a", "b"}}, + {[]string{"a", "b", "c"}, []string{"d", "e"}, []string{}}, + {[]string{}, []string{}, []string{}}, + {[]string{"a", "b"}, nil, make([]interface{}, 0)}, + {nil, []string{"a", "b"}, make([]interface{}, 0)}, + {nil, nil, make([]interface{}, 0)}, + {[]string{"1", "2"}, []int{1, 2}, []string{}}, + {[]int{1, 2}, []string{"1", "2"}, []int{}}, + {[]int{1, 2, 4}, []int{2, 4}, []int{2, 4}}, + {[]int{2, 4}, []int{1, 2, 4}, []int{2, 4}}, + {[]int{1, 2, 4}, []int{3, 6}, []int{}}, + {[]float64{2.2, 4.4}, []float64{1.1, 2.2, 4.4}, []float64{2.2, 4.4}}, + } { + results, err := Intersect(this.sequence1, this.sequence2) + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if !reflect.DeepEqual(results, this.expect) { + t.Errorf("[%d] Got %v but expected %v", i, results, this.expect) + } + } + + _, err1 := Intersect("not an array or slice", []string{"a"}) + + if err1 == nil { + t.Error("Excpected error for non array as first arg") + } + + _, err2 := Intersect([]string{"a"}, "not an array or slice") + + if err2 == nil { + t.Error("Excpected error for non array as second arg") + } +} + +func (x *TstX) TstRp() string { + return "r" + x.A +} + +func (x TstX) TstRv() string { + return "r" + x.B +} + +type TstX struct { + A, B string +} + +func TestWhere(t *testing.T) { + // TODO(spf): Put these page tests back in + //page1 := &Page{contentType: "v", Source: Source{File: *source.NewFile("/x/y/z/source.md")}} + //page2 := &Page{contentType: "w", Source: Source{File: *source.NewFile("/y/z/a/source.md")}} + + for i, this := range []struct { + sequence interface{} + key interface{} + match interface{} + expect interface{} + }{ + {[]map[int]string{{1: "a", 2: "m"}, {1: "c", 2: "d"}, {1: "e", 3: "m"}}, 2, "m", []map[int]string{{1: "a", 2: "m"}}}, + {[]map[string]int{{"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 5, "x": 4}}, "b", 4, []map[string]int{{"a": 3, "b": 4}}}, + {[]TstX{{"a", "b"}, {"c", "d"}, {"e", "f"}}, "B", "f", []TstX{{"e", "f"}}}, + {[]*map[int]string{&map[int]string{1: "a", 2: "m"}, &map[int]string{1: "c", 2: "d"}, &map[int]string{1: "e", 3: "m"}}, 2, "m", []*map[int]string{&map[int]string{1: "a", 2: "m"}}}, + {[]*TstX{&TstX{"a", "b"}, &TstX{"c", "d"}, &TstX{"e", "f"}}, "B", "f", []*TstX{&TstX{"e", "f"}}}, + {[]*TstX{&TstX{"a", "b"}, &TstX{"c", "d"}, &TstX{"e", "c"}}, "TstRp", "rc", []*TstX{&TstX{"c", "d"}}}, + {[]TstX{TstX{"a", "b"}, TstX{"c", "d"}, TstX{"e", "c"}}, "TstRv", "rc", []TstX{TstX{"e", "c"}}}, + //{[]*Page{page1, page2}, "Type", "v", []*Page{page1}}, + //{[]*Page{page1, page2}, "Section", "y", []*Page{page2}}, + } { + results, err := Where(this.sequence, this.key, this.match) + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if !reflect.DeepEqual(results, this.expect) { + t.Errorf("[%d] Where clause matching %v with %v, got %v but expected %v", i, this.key, this.match, results, this.expect) + } + } +} |