Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/gohugoio/hugo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/tpl
diff options
context:
space:
mode:
authorspf13 <steve.francia@gmail.com>2014-11-20 20:32:21 +0300
committerspf13 <steve.francia@gmail.com>2014-11-20 20:36:57 +0300
commit73f203ad86a71136a1d35e0aa1bba574edb91a51 (patch)
tree8e1e336f03df4b583a27e15f4e1bf28182331605 /tpl
parent92a3372a3fdc0365a06a75e6d97f24e624db879b (diff)
Move template library into it's own package (tpl). No longer dependent on hugolib. Can be used externally.
Diffstat (limited to 'tpl')
-rw-r--r--tpl/template.go714
-rw-r--r--tpl/template_embedded.go97
-rw-r--r--tpl/template_test.go341
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)
+ }
+ }
+}