Move template library into it's own package (tpl). No longer dependent on hugolib. Can be used externally.

This commit is contained in:
spf13 2014-11-20 12:32:21 -05:00
parent 92a3372a3f
commit 73f203ad86
8 changed files with 151 additions and 113 deletions

View file

@ -17,10 +17,6 @@ import (
"bytes"
"errors"
"fmt"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/parser"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
"html/template"
"io"
"net/url"
@ -29,8 +25,13 @@ import (
"time"
"github.com/spf13/cast"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/parser"
"github.com/spf13/hugo/source"
"github.com/spf13/hugo/tpl"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
)
type Page struct {
@ -44,7 +45,7 @@ type Page struct {
Truncated bool
Draft bool
PublishDate time.Time
Tmpl Template
Tmpl tpl.Template
Markup string
extension string
@ -528,7 +529,7 @@ func (p *Page) Render(layout ...string) template.HTML {
curLayout = layout[0]
}
return ExecuteTemplateToHTML(p, p.Layout(curLayout)...)
return tpl.ExecuteTemplateToHTML(p, p.Layout(curLayout)...)
}
func (page *Page) guessMarkupType() string {
@ -629,7 +630,7 @@ func (page *Page) SaveSource() error {
return page.SaveSourceAs(page.FullFilePath())
}
func (p *Page) ProcessShortcodes(t Template) {
func (p *Page) ProcessShortcodes(t tpl.Template) {
// these short codes aren't used until after Page render,
// but processed here to avoid coupling

View file

@ -16,14 +16,16 @@ package hugolib
import (
"bytes"
"fmt"
"github.com/spf13/hugo/helpers"
jww "github.com/spf13/jwalterweatherman"
"html/template"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/tpl"
jww "github.com/spf13/jwalterweatherman"
)
type ShortcodeFunc func([]string) string
@ -117,7 +119,7 @@ func (sc shortcode) String() string {
// all in one go: extract, render and replace
// only used for testing
func ShortcodesHandle(stringToParse string, page *Page, t Template) string {
func ShortcodesHandle(stringToParse string, page *Page, t tpl.Template) string {
tmpContent, tmpShortcodes := extractAndRenderShortcodes(stringToParse, page, t)
@ -154,7 +156,7 @@ func createShortcodePlaceholder(id int) string {
return fmt.Sprintf("<div>%s-%d</div>", shortcodePlaceholderPrefix, id)
}
func renderShortcodes(sc shortcode, p *Page, t Template) string {
func renderShortcodes(sc shortcode, p *Page, t tpl.Template) string {
tokenizedRenderedShortcodes := make(map[string](string))
startCount := 0
@ -169,7 +171,7 @@ func renderShortcodes(sc shortcode, p *Page, t Template) string {
return shortcodes
}
func renderShortcode(sc shortcode, tokenizedShortcodes map[string](string), cnt int, p *Page, t Template) string {
func renderShortcode(sc shortcode, tokenizedShortcodes map[string](string), cnt int, p *Page, t tpl.Template) string {
var data = &ShortcodeWithPage{Params: sc.params, Page: p}
tmpl := GetTemplate(sc.name, t)
@ -209,7 +211,7 @@ func renderShortcode(sc shortcode, tokenizedShortcodes map[string](string), cnt
return ShortcodeRender(tmpl, data)
}
func extractAndRenderShortcodes(stringToParse string, p *Page, t Template) (string, map[string]string) {
func extractAndRenderShortcodes(stringToParse string, p *Page, t tpl.Template) (string, map[string]string) {
content, shortcodes, err := extractShortcodes(stringToParse, p, t)
renderedShortcodes := make(map[string]string)
@ -235,7 +237,7 @@ func extractAndRenderShortcodes(stringToParse string, p *Page, t Template) (stri
// pageTokens state:
// - before: positioned just before the shortcode start
// - after: shortcode(s) consumed (plural when they are nested)
func extractShortcode(pt *pageTokens, p *Page, t Template) (shortcode, error) {
func extractShortcode(pt *pageTokens, p *Page, t tpl.Template) (shortcode, error) {
sc := shortcode{}
var isInner = false
@ -334,7 +336,7 @@ Loop:
return sc, nil
}
func extractShortcodes(stringToParse string, p *Page, t Template) (string, map[string]shortcode, error) {
func extractShortcodes(stringToParse string, p *Page, t tpl.Template) (string, map[string]shortcode, error) {
shortCodes := make(map[string]shortcode)
@ -452,7 +454,7 @@ func replaceShortcodeTokens(source []byte, prefix string, numReplacements int, w
return buff[0:width], nil
}
func GetTemplate(name string, t Template) *template.Template {
func GetTemplate(name string, t tpl.Template) *template.Template {
if x := t.Lookup("shortcodes/" + name + ".html"); x != nil {
return x
}

View file

@ -2,20 +2,22 @@ package hugolib
import (
"fmt"
"github.com/spf13/hugo/helpers"
"github.com/spf13/viper"
"reflect"
"regexp"
"sort"
"strings"
"testing"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/tpl"
"github.com/spf13/viper"
)
func pageFromString(in, filename string) (*Page, error) {
return NewPageFrom(strings.NewReader(in), filename)
}
func CheckShortCodeMatch(t *testing.T, input, expected string, template Template) {
func CheckShortCodeMatch(t *testing.T, input, expected string, template tpl.Template) {
p, _ := pageFromString(SIMPLE_PAGE, "simple.md")
output := ShortcodesHandle(input, p, template)
@ -26,13 +28,13 @@ func CheckShortCodeMatch(t *testing.T, input, expected string, template Template
}
func TestNonSC(t *testing.T) {
tem := NewTemplate()
tem := tpl.New()
// notice the syntax diff from 0.12, now comment delims must be added
CheckShortCodeMatch(t, "{{%/* movie 47238zzb */%}}", "{{% movie 47238zzb %}}", tem)
}
func TestPositionalParamSC(t *testing.T) {
tem := NewTemplate()
tem := tpl.New()
tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 0 }}`)
CheckShortCodeMatch(t, "{{< video 47238zzb >}}", "Playing Video 47238zzb", tem)
@ -43,7 +45,7 @@ func TestPositionalParamSC(t *testing.T) {
}
func TestNamedParamSC(t *testing.T) {
tem := NewTemplate()
tem := tpl.New()
tem.AddInternalShortcode("img.html", `<img{{ with .Get "src" }} src="{{.}}"{{end}}{{with .Get "class"}} class="{{.}}"{{end}}>`)
CheckShortCodeMatch(t, `{{< img src="one" >}}`, `<img src="one">`, tem)
@ -55,7 +57,7 @@ func TestNamedParamSC(t *testing.T) {
}
func TestInnerSC(t *testing.T) {
tem := NewTemplate()
tem := tpl.New()
tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
CheckShortCodeMatch(t, `{{< inside class="aspen" >}}`, `<div class="aspen"></div>`, tem)
@ -64,7 +66,7 @@ func TestInnerSC(t *testing.T) {
}
func TestInnerSCWithMarkdown(t *testing.T) {
tem := NewTemplate()
tem := tpl.New()
tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
CheckShortCodeMatch(t, `{{% inside %}}
@ -76,7 +78,7 @@ func TestInnerSCWithMarkdown(t *testing.T) {
}
func TestInnerSCWithAndWithoutMarkdown(t *testing.T) {
tem := NewTemplate()
tem := tpl.New()
tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
CheckShortCodeMatch(t, `{{% inside %}}
@ -98,14 +100,14 @@ This is **plain** text.
}
func TestEmbeddedSC(t *testing.T) {
tem := NewTemplate()
tem := tpl.New()
CheckShortCodeMatch(t, "{{% test %}}", "This is a simple Test", tem)
CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" %}}`, "\n<figure class=\"bananas orange\">\n \n <img src=\"/found/here\" />\n \n \n</figure>\n", tem)
CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" caption="This is a caption" %}}`, "\n<figure class=\"bananas orange\">\n \n <img src=\"/found/here\" alt=\"This is a caption\" />\n \n \n <figcaption>\n <p>\n This is a caption\n \n \n \n </p> \n </figcaption>\n \n</figure>\n", tem)
}
func TestNestedSC(t *testing.T) {
tem := NewTemplate()
tem := tpl.New()
tem.AddInternalShortcode("scn1.html", `<div>Outer, inner is {{ .Inner }}</div>`)
tem.AddInternalShortcode("scn2.html", `<div>SC2</div>`)
@ -113,7 +115,7 @@ func TestNestedSC(t *testing.T) {
}
func TestNestedComplexSC(t *testing.T) {
tem := NewTemplate()
tem := tpl.New()
tem.AddInternalShortcode("row.html", `-row-{{ .Inner}}-rowStop-`)
tem.AddInternalShortcode("column.html", `-col-{{.Inner }}-colStop-`)
tem.AddInternalShortcode("aside.html", `-aside-{{ .Inner }}-asideStop-`)
@ -127,7 +129,7 @@ func TestNestedComplexSC(t *testing.T) {
}
func TestFigureImgWidth(t *testing.T) {
tem := NewTemplate()
tem := tpl.New()
CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" alt="apple" width="100px" %}}`, "\n<figure class=\"bananas orange\">\n \n <img src=\"/found/here\" alt=\"apple\" width=\"100px\" />\n \n \n</figure>\n", tem)
}
@ -138,7 +140,7 @@ func TestHighlight(t *testing.T) {
defer viper.Set("PygmentsStyle", viper.Get("PygmentsStyle"))
viper.Set("PygmentsStyle", "bw")
tem := NewTemplate()
tem := tpl.New()
code := `
{{< highlight java >}}
@ -196,7 +198,7 @@ func TestExtractShortcodes(t *testing.T) {
} {
p, _ := pageFromString(SIMPLE_PAGE, "simple.md")
tem := NewTemplate()
tem := tpl.New()
tem.AddInternalShortcode("tag.html", `tag`)
tem.AddInternalShortcode("sc1.html", `sc1`)
tem.AddInternalShortcode("sc2.html", `sc2`)

View file

@ -31,6 +31,7 @@ import (
"github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/source"
"github.com/spf13/hugo/target"
"github.com/spf13/hugo/tpl"
"github.com/spf13/hugo/transform"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/nitro"
@ -61,7 +62,7 @@ var DefaultTimer *nitro.B
type Site struct {
Pages Pages
Files []*source.File
Tmpl Template
Tmpl tpl.Template
Taxonomies TaxonomyList
Source source.Input
Sections Taxonomy
@ -166,7 +167,7 @@ func (s *Site) Analyze() {
}
func (s *Site) prepTemplates() {
s.Tmpl = NewTemplate()
s.Tmpl = tpl.T()
s.Tmpl.LoadTemplates(s.absLayoutDir())
if s.hasTheme() {
s.Tmpl.LoadTemplatesWithPrefix(s.absThemeDir()+"/layouts", "theme")

View file

@ -13,6 +13,7 @@ import (
"github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/source"
"github.com/spf13/hugo/target"
"github.com/spf13/hugo/tpl"
"github.com/spf13/viper"
)
@ -46,6 +47,14 @@ more text
`
)
func templatePrep(s *Site) {
s.Tmpl = tpl.New()
s.Tmpl.LoadTemplates(s.absLayoutDir())
if s.hasTheme() {
s.Tmpl.LoadTemplatesWithPrefix(s.absThemeDir()+"/layouts", "theme")
}
}
func pageMust(p *Page, err error) *Page {
if err != nil {
panic(err)
@ -57,7 +66,7 @@ func TestDegenerateRenderThingMissingTemplate(t *testing.T) {
p, _ := NewPageFrom(strings.NewReader(PAGE_SIMPLE_TITLE), "content/a/file.md")
p.Convert()
s := new(Site)
s.prepTemplates()
templatePrep(s)
err := s.renderThing(p, "foobar", nil)
if err == nil {
t.Errorf("Expected err to be returned when missing the template.")
@ -66,7 +75,7 @@ func TestDegenerateRenderThingMissingTemplate(t *testing.T) {
func TestAddInvalidTemplate(t *testing.T) {
s := new(Site)
s.prepTemplates()
templatePrep(s)
err := s.addTemplate("missing", TEMPLATE_MISSING_FUNC)
if err == nil {
t.Fatalf("Expecting the template to return an error")
@ -108,7 +117,7 @@ func TestRenderThing(t *testing.T) {
}
s := new(Site)
s.prepTemplates()
templatePrep(s)
for i, test := range tests {
p, err := NewPageFrom(strings.NewReader(test.content), "content/a/file.md")
@ -154,7 +163,7 @@ func TestRenderThingOrDefault(t *testing.T) {
hugofs.DestinationFS = new(afero.MemMapFs)
s := &Site{}
s.prepTemplates()
templatePrep(s)
for i, test := range tests {
p, err := NewPageFrom(strings.NewReader(PAGE_SIMPLE_TITLE), "content/a/file.md")
@ -306,7 +315,7 @@ func TestSkipRender(t *testing.T) {
}
s.initializeSiteInfo()
s.prepTemplates()
templatePrep(s)
must(s.addTemplate("_default/single.html", "{{.Content}}"))
must(s.addTemplate("head", "<head><script src=\"script.js\"></script></head>"))
@ -366,7 +375,7 @@ func TestAbsUrlify(t *testing.T) {
}
t.Logf("Rendering with BaseUrl %q and CanonifyUrls set %v", viper.GetString("baseUrl"), canonify)
s.initializeSiteInfo()
s.prepTemplates()
templatePrep(s)
must(s.addTemplate("blue/single.html", TEMPLATE_WITH_URL_ABS))
if err := s.CreatePages(); err != nil {

View file

@ -1,4 +1,17 @@
package hugolib
// 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"
@ -20,6 +33,82 @@ import (
)
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)
@ -484,71 +573,6 @@ func ModBool(a, b interface{}) (bool, error) {
return res == int64(0), nil
}
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
}
func NewTemplate() 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 Partial(name string, context_list ...interface{}) template.HTML {
if strings.HasPrefix("partials/", name) {
name = name[8:]

View file

@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package hugolib
package tpl
type Tmpl struct {
Name string

View file

@ -1,7 +1,6 @@
package hugolib
package tpl
import (
"github.com/spf13/hugo/source"
"reflect"
"testing"
)
@ -310,9 +309,9 @@ type TstX struct {
}
func TestWhere(t *testing.T) {
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")}}
// 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{}
@ -327,8 +326,8 @@ func TestWhere(t *testing.T) {
{[]*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}},
//{[]*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 {