shithub: hugo

Download patch

ref: ba82a20321b7c093077c787ced76e6fa45b5bd67
parent: ee5865f239786f354c6d3f14bf14bcb5890a6c22
author: Fabrizio (Misto) Milo <[email protected]>
date: Sat Aug 31 13:47:21 EDT 2013

Add support for amber files

If a layout file ends with .amber it will interpreted as a Amber file

Signed-off-by: Noah Campbell <[email protected]>

--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -21,7 +21,6 @@
 	"fmt"
 	"github.com/BurntSushi/toml"
 	"github.com/theplant/blackfriday"
-	"html/template"
 	"io"
 	"io/ioutil"
 	"launchpad.net/goyaml"
@@ -46,7 +45,7 @@
 	contentType     string
 	Draft           bool
 	Aliases         []string
-	Tmpl            *template.Template
+	Tmpl            Template
 	Markup          string
 	PageMeta
 	File
--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -1,7 +1,6 @@
 package hugolib
 
 import (
-	"html/template"
 	"path/filepath"
 	"strings"
 	"testing"
--- a/hugolib/shortcode.go
+++ b/hugolib/shortcode.go
@@ -16,7 +16,6 @@
 import (
 	"bytes"
 	"fmt"
-	"html/template"
 	"strings"
 	"unicode"
 )
@@ -37,7 +36,7 @@
 
 type Shortcodes map[string]ShortcodeFunc
 
-func ShortcodesHandle(stringToParse string, p *Page, t *template.Template) string {
+func ShortcodesHandle(stringToParse string, p *Page, t Template) string {
 	posStart := strings.Index(stringToParse, "{{%")
 	if posStart > 0 {
 		posEnd := strings.Index(stringToParse[posStart:], "%}}") + posStart
@@ -124,7 +123,7 @@
 	return strings.TrimSpace(in[:i+1]), strings.TrimSpace(in[i+1:])
 }
 
-func ShortcodeRender(name string, data *ShortcodeWithPage, t *template.Template) string {
+func ShortcodeRender(name string, data *ShortcodeWithPage, t Template) string {
 	buffer := new(bytes.Buffer)
 	t.ExecuteTemplate(buffer, "shortcodes/"+name+".html", data)
 	return buffer.String()
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -16,12 +16,9 @@
 import (
 	"bitbucket.org/pkg/inflect"
 	"bytes"
-	"errors"
 	"fmt"
 	"github.com/spf13/hugo/target"
 	"github.com/spf13/nitro"
-	"html/template"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
@@ -33,7 +30,7 @@
 type Site struct {
 	Config     Config
 	Pages      Pages
-	Tmpl       *template.Template
+	Tmpl       Template
 	Indexes    IndexList
 	Files      []string
 	Sections   Index
@@ -44,7 +41,7 @@
 }
 
 type SiteInfo struct {
-	BaseUrl    template.URL
+	BaseUrl    URL
 	Indexes    OrderedIndexList
 	Recent     *Pages
 	LastChange time.Time
@@ -70,8 +67,8 @@
 	if err = s.Render(); err != nil {
 		fmt.Printf("Error rendering site: %s\n", err)
 		fmt.Printf("Available templates:")
-		for _, template := range s.Tmpl.Templates() {
-			fmt.Printf("\t%s\n", template.Name())
+		for _, tpl := range s.Tmpl.Templates() {
+			fmt.Printf("\t%s\n", tpl.Name())
 		}
 		return
 	}
@@ -84,6 +81,15 @@
 	s.checkDescriptions()
 }
 
+func (s *Site) prepTemplates() {
+	s.Tmpl = NewTemplate()
+	s.Tmpl.LoadTemplates(s.absLayoutDir())
+}
+
+func (s *Site) addTemplate(name, data string) error {
+	return s.Tmpl.AddTemplate(name, data)
+}
+
 func (s *Site) Process() (err error) {
 	s.initialize()
 	s.prepTemplates()
@@ -136,65 +142,6 @@
 	}
 }
 
-func (s *Site) prepTemplates() {
-	var templates = template.New("")
-
-	funcMap := template.FuncMap{
-		"urlize":    Urlize,
-		"gt":        Gt,
-		"isset":     IsSet,
-		"echoParam": ReturnWhenSet,
-	}
-
-	templates.Funcs(funcMap)
-
-	s.Tmpl = templates
-	s.primeTemplates()
-	s.loadTemplates()
-}
-
-func (s *Site) loadTemplates() {
-	walker := func(path string, fi os.FileInfo, err error) error {
-		if err != nil {
-			PrintErr("Walker: ", err)
-			return nil
-		}
-
-		if !fi.IsDir() {
-			if ignoreDotFile(path) {
-				return nil
-			}
-			filetext, err := ioutil.ReadFile(path)
-			if err != nil {
-				return err
-			}
-			s.addTemplate(s.generateTemplateNameFrom(path), string(filetext))
-		}
-		return nil
-	}
-
-	filepath.Walk(s.absLayoutDir(), walker)
-}
-
-func (s *Site) addTemplate(name, tmpl string) (err error) {
-	_, err = s.Tmpl.New(name).Parse(tmpl)
-	return
-}
-
-func (s *Site) generateTemplateNameFrom(path string) (name string) {
-	name = filepath.ToSlash(path[len(s.absLayoutDir())+1:])
-	return
-}
-
-func (s *Site) primeTemplates() {
-	alias := "<!DOCTYPE html>\n <html>\n <head>\n <link rel=\"canonical\" href=\"{{ .Permalink }}\"/>\n <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n <meta http-equiv=\"refresh\" content=\"0;url={{ .Permalink }}\" />\n </head>\n </html>"
-	alias_xhtml := "<!DOCTYPE html>\n <html xmlns=\"http://www.w3.org/1999/xhtml\">\n <head>\n <link rel=\"canonical\" href=\"{{ .Permalink }}\"/>\n <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n <meta http-equiv=\"refresh\" content=\"0;url={{ .Permalink }}\" />\n </head>\n </html>"
-
-	s.addTemplate("alias", alias)
-	s.addTemplate("alias-xhtml", alias_xhtml)
-
-}
-
 func (s *Site) initialize() {
 	s.checkDirectories()
 
@@ -222,7 +169,7 @@
 
 	filepath.Walk(s.absContentDir(), walker)
 	s.Info = SiteInfo{
-		BaseUrl: template.URL(s.Config.BaseUrl),
+		BaseUrl: URL(s.Config.BaseUrl),
 		Title:   s.Config.Title,
 		Recent:  &s.Pages,
 		Config:  &s.Config,
@@ -258,8 +205,8 @@
 }
 
 func (s *Site) ProcessShortcodes() {
-	for i, _ := range s.Pages {
-		s.Pages[i].Content = HTML(ShortcodesHandle(string(s.Pages[i].Content), s.Pages[i], s.Tmpl))
+	for _, page := range s.Pages {
+		page.Content = HTML(ShortcodesHandle(string(page.Content), page, s.Tmpl))
 	}
 }
 
@@ -266,14 +213,14 @@
 func (s *Site) AbsUrlify() {
 	baseWithoutTrailingSlash := strings.TrimRight(s.Config.BaseUrl, "/")
 	baseWithSlash := baseWithoutTrailingSlash + "/"
-	for i, _ := range s.Pages {
-		content := string(s.Pages[i].Content)
+	for _, page := range s.Pages {
+		content := string(page.Content)
 		content = strings.Replace(content, " src=\"/", " src=\""+baseWithSlash, -1)
 		content = strings.Replace(content, " src='/", " src='"+baseWithSlash, -1)
 		content = strings.Replace(content, " href='/", " href='"+baseWithSlash, -1)
 		content = strings.Replace(content, " href=\"/", " href=\""+baseWithSlash, -1)
 		content = strings.Replace(content, baseWithoutTrailingSlash+"//", baseWithSlash, -1)
-		s.Pages[i].Content = HTML(content)
+		page.Content = HTML(content)
 	}
 }
 
@@ -294,13 +241,13 @@
 }
 
 func (s *Site) setupPrevNext() {
-	for i, _ := range s.Pages {
+	for i, page := range s.Pages {
 		if i < len(s.Pages)-1 {
-			s.Pages[i].Next = s.Pages[i+1]
+			page.Next = s.Pages[i+1]
 		}
 
 		if i > 0 {
-			s.Pages[i].Prev = s.Pages[i-1]
+			page.Prev = s.Pages[i-1]
 		}
 	}
 }
@@ -310,7 +257,7 @@
 	x := strings.Split(y, string(os.PathSeparator))
 
 	if len(x) <= 1 {
-		return errors.New("Zero length page name")
+		return fmt.Errorf("Zero length page name")
 	}
 
 	p.Section = strings.Trim(x[1], "/\\")
@@ -361,7 +308,7 @@
 
 	for _, plural := range s.Config.Indexes {
 		s.Indexes[plural] = make(Index)
-		for i, p := range s.Pages {
+		for _, p := range s.Pages {
 			vals := p.GetParam(plural)
 
 			if vals != nil {
@@ -368,7 +315,7 @@
 				v, ok := vals.([]string)
 				if ok {
 					for _, idx := range v {
-						s.Indexes[plural].Add(idx, s.Pages[i])
+						s.Indexes[plural].Add(idx, p)
 					}
 				} else {
 					PrintErr("Invalid " + plural + " in " + p.File.FileName)
@@ -380,8 +327,8 @@
 		}
 	}
 
-	for i, p := range s.Pages {
-		s.Sections.Add(p.Section, s.Pages[i])
+	for _, p := range s.Pages {
+		s.Sections.Add(p.Section, p)
 	}
 
 	for k, _ := range s.Sections {
@@ -424,13 +371,13 @@
 }
 
 func (s *Site) RenderAliases() error {
-	for i, p := range s.Pages {
+	for _, p := range s.Pages {
 		for _, a := range p.Aliases {
 			t := "alias"
 			if strings.HasSuffix(a, ".xhtml") {
 				t = "alias-xhtml"
 			}
-			content, err := s.RenderThing(s.Pages[i], t)
+			content, err := s.RenderThing(p, t)
 			if strings.HasSuffix(a, "/") {
 				a = a + "index.html"
 			}
@@ -447,12 +394,12 @@
 }
 
 func (s *Site) RenderPages() error {
-	for i, _ := range s.Pages {
-		content, err := s.RenderThingOrDefault(s.Pages[i], s.Pages[i].Layout(), "_default/single.html")
+	for _, p := range s.Pages {
+		content, err := s.RenderThingOrDefault(p, p.Layout(), "_default/single.html")
 		if err != nil {
 			return err
 		}
-		s.Pages[i].RenderedContent = content
+		p.RenderedContent = content
 	}
 	return nil
 }
@@ -480,8 +427,8 @@
 			} else {
 				n.Url = url + "/index.html"
 			}
-			n.Permalink = HTML(MakePermalink(string(n.Site.BaseUrl), string(plink)))
-			n.RSSlink = HTML(MakePermalink(string(n.Site.BaseUrl), string(url+".xml")))
+			n.Permalink = permalink(s, plink)
+			n.RSSlink = permalink(s, url+".xml")
 			n.Date = o[0].Date
 			n.Data[singular] = o
 			n.Data["Pages"] = o
@@ -511,7 +458,7 @@
 				} else {
 					n.Url = Urlize(plural + "/" + k + "/" + "index.xml")
 				}
-				n.Permalink = HTML(string(n.Site.BaseUrl) + n.Url)
+				n.Permalink = permalink(s, n.Url)
 				s.Tmpl.ExecuteTemplate(y, "rss.xml", n)
 				err = s.WritePublic(base+".xml", y.Bytes())
 				if err != nil {
@@ -531,7 +478,7 @@
 			n.Title = strings.Title(plural)
 			url := Urlize(plural)
 			n.Url = url + "/index.html"
-			n.Permalink = HTML(MakePermalink(string(n.Site.BaseUrl), string(n.Url)))
+			n.Permalink = permalink(s, n.Url)
 			n.Data["Singular"] = singular
 			n.Data["Plural"] = plural
 			n.Data["Index"] = s.Indexes[plural]
@@ -556,8 +503,8 @@
 		n := s.NewNode()
 		n.Title = strings.Title(inflect.Pluralize(section))
 		n.Url = Urlize(section + "/" + "index.html")
-		n.Permalink = HTML(MakePermalink(string(n.Site.BaseUrl), string(n.Url)))
-		n.RSSlink = HTML(MakePermalink(string(n.Site.BaseUrl), string(section+".xml")))
+		n.Permalink = permalink(s, n.Url)
+		n.RSSlink = permalink(s, section+".xml")
 		n.Date = data[0].Date
 		n.Data["Pages"] = data
 		layout := "indexes/" + section + ".html"
@@ -592,8 +539,8 @@
 	n := s.NewNode()
 	n.Title = n.Site.Title
 	n.Url = Urlize(string(n.Site.BaseUrl))
-	n.RSSlink = HTML(MakePermalink(string(n.Site.BaseUrl), string("index.xml")))
-	n.Permalink = HTML(string(n.Site.BaseUrl))
+	n.RSSlink = permalink(s, "index.xml")
+	n.Permalink = permalink(s, "")
 	if len(s.Pages) > 0 {
 		n.Date = s.Pages[0].Date
 		if len(s.Pages) < 9 {
@@ -615,7 +562,7 @@
 		// XML Feed
 		n.Url = Urlize("index.xml")
 		n.Title = "Recent Content"
-		n.Permalink = HTML(string(n.Site.BaseUrl) + "index.xml")
+		n.Permalink = permalink(s, "index.xml")
 		y := s.NewXMLBuffer()
 		s.Tmpl.ExecuteTemplate(y, "rss.xml", n)
 		err = s.WritePublic("index.xml", y.Bytes())
@@ -625,7 +572,7 @@
 	if a := s.Tmpl.Lookup("404.html"); a != nil {
 		n.Url = Urlize("404.html")
 		n.Title = "404 Page not found"
-		n.Permalink = HTML(string(n.Site.BaseUrl) + "404.html")
+		n.Permalink = permalink(s, "404.html")
 		x, err := s.RenderThing(n, "404.html")
 		if err != nil {
 			return err
@@ -644,15 +591,20 @@
 	}
 }
 
-func (s *Site) NewNode() (y Node) {
-	y.Data = make(map[string]interface{})
-	y.Site = s.Info
-	return y
+func permalink(s *Site, plink string) HTML {
+	return HTML(MakePermalink(string(s.Info.BaseUrl), plink))
 }
 
+func (s *Site) NewNode() *Node {
+	return &Node{
+		Data: make(map[string]interface{}),
+		Site: s.Info,
+	}
+}
+
 func (s *Site) RenderThing(d interface{}, layout string) (*bytes.Buffer, error) {
 	if s.Tmpl.Lookup(layout) == nil {
-		return nil, errors.New(fmt.Sprintf("Layout not found: %s", layout))
+		return nil, fmt.Errorf("Layout not found: %s", layout)
 	}
 	buffer := new(bytes.Buffer)
 	err := s.Tmpl.ExecuteTemplate(buffer, layout, d)
--- a/hugolib/site_test.go
+++ b/hugolib/site_test.go
@@ -39,10 +39,9 @@
 	}
 }
 
-func TestPrimeTempaltes(t *testing.T) {
+func TestPrimeTemplates(t *testing.T) {
 	s := new(Site)
 	s.prepTemplates()
-	s.primeTemplates()
 	if s.Tmpl.Lookup("alias") == nil {
 		t.Fatalf("alias template not created.")
 	}
--- a/hugolib/template.go
+++ b/hugolib/template.go
@@ -1,7 +1,13 @@
 package hugolib
 
 import (
+	"io/ioutil"
+	"github.com/eknkc/amber"
 	"html/template"
+	"io"
+	"os"
+	"path/filepath"
+	"strings"
 )
 
 // HTML encapsulates a known safe HTML document fragment.
@@ -9,3 +15,112 @@
 // unclosed tags or comments. The outputs of a sound HTML sanitizer
 // and a template escaped by this package are fine for use with HTML.
 type HTML template.HTML
+
+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)
+	AddTemplate(name, tpl string) error
+}
+
+type URL template.URL
+
+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),
+	}
+
+	funcMap := template.FuncMap{
+		"urlize":    Urlize,
+		"gt":        Gt,
+		"isset":     IsSet,
+		"echoParam": ReturnWhenSet,
+	}
+
+	templates.Funcs(funcMap)
+	templates.primeTemplates()
+	return templates
+}
+
+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 {
+	b, err := ioutil.ReadFile(path)
+	if err != nil {
+		return err
+	}
+	s := string(b)
+	_, err = t.New(name).Parse(s)
+	if err != nil {
+		t.errors = append(t.errors, &templateErr{name: name, err: err})
+	}
+	return err
+}
+
+func (t *GoHtmlTemplate) generateTemplateNameFrom(base, path string) string {
+	return filepath.ToSlash(path[len(base)+1:])
+}
+
+func (t *GoHtmlTemplate) primeTemplates() {
+	alias := "<!DOCTYPE html>\n <html>\n <head>\n <link rel=\"canonical\" href=\"{{ .Permalink }}\"/>\n <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n <meta http-equiv=\"refresh\" content=\"0;url={{ .Permalink }}\" />\n </head>\n </html>"
+	alias_xhtml := "<!DOCTYPE html>\n <html xmlns=\"http://www.w3.org/1999/xhtml\">\n <head>\n <link rel=\"canonical\" href=\"{{ .Permalink }}\"/>\n <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n <meta http-equiv=\"refresh\" content=\"0;url={{ .Permalink }}\" />\n </head>\n </html>"
+
+	t.AddTemplate("alias", alias)
+	t.AddTemplate("alias-xhtml", alias_xhtml)
+}
+
+func (t *GoHtmlTemplate) LoadTemplates(absPath string) {
+	walker := func(path string, fi os.FileInfo, err error) error {
+		if err != nil {
+			PrintErr("Walker: ", err)
+			return nil
+		}
+
+		if !fi.IsDir() {
+			if ignoreDotFile(path) {
+				return nil
+			}
+
+			tplName := t.generateTemplateNameFrom(absPath, path)
+
+			if strings.HasSuffix(path, ".amber") {
+				compiler := amber.New()
+				// Parse the input file
+				if err := compiler.ParseFile(path); err != nil {
+					return nil
+				}
+
+				// note t.New(tplName)
+				if _, err := compiler.CompileWithTemplate(t.New(tplName)); err != nil {
+					PrintErr("Could not compile amber file: "+path, err)
+					return err
+				}
+
+			} else {
+				t.AddTemplateFile(tplName, path)
+			}
+		}
+		return nil
+	}
+
+	filepath.Walk(absPath, walker)
+}