Add support for amber files
authorFabrizio (Misto) Milo <mistobaan@gmail.com>
Sun, 1 Sep 2013 00:47:21 +0000 (17:47 -0700)
committerNoah Campbell <noahcampbell@gmail.com>
Mon, 2 Sep 2013 02:51:32 +0000 (19:51 -0700)
If a layout file ends with .amber it will interpreted as a Amber file

Signed-off-by: Noah Campbell <noahcampbell@gmail.com>
hugolib/page.go
hugolib/page_test.go
hugolib/shortcode.go
hugolib/site.go
hugolib/site_test.go
hugolib/template.go

index 24b1bc860539e4b3a24e0257496040ca9f247c05..5b2b29704c06206fbef97ff3134e068285ab8fc2 100644 (file)
@@ -21,7 +21,6 @@ import (
        "fmt"
        "github.com/BurntSushi/toml"
        "github.com/theplant/blackfriday"
-       "html/template"
        "io"
        "io/ioutil"
        "launchpad.net/goyaml"
@@ -46,7 +45,7 @@ type Page struct {
        contentType     string
        Draft           bool
        Aliases         []string
-       Tmpl            *template.Template
+       Tmpl            Template
        Markup          string
        PageMeta
        File
index 0c61a703f00cb7086876b908f087924040fbef1b..99f7c3a1aa0adba6b0c52608e8986f22cd7ecbf0 100644 (file)
@@ -1,7 +1,6 @@
 package hugolib
 
 import (
-       "html/template"
        "path/filepath"
        "strings"
        "testing"
index fac6f5a2e78628301810b3ad4aca5c1a6cabe242..eede060cd415894835c8c5e1532f0a1f649a1c05 100644 (file)
@@ -16,7 +16,6 @@ package hugolib
 import (
        "bytes"
        "fmt"
-       "html/template"
        "strings"
        "unicode"
 )
@@ -37,7 +36,7 @@ type ShortcodeWithPage struct {
 
 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 @@ func SplitParams(in string) (name string, par2 string) {
        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()
index 8f51026f3b44ef0d72eff40f53063db7fd81c308..2c6c610bff5743b3833dfa525b980826cc98b5a6 100644 (file)
@@ -16,12 +16,9 @@ package hugolib
 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 @@ var DefaultTimer = nitro.Initalize()
 type Site struct {
        Config     Config
        Pages      Pages
-       Tmpl       *template.Template
+       Tmpl       Template
        Indexes    IndexList
        Files      []string
        Sections   Index
@@ -44,7 +41,7 @@ type Site struct {
 }
 
 type SiteInfo struct {
-       BaseUrl    template.URL
+       BaseUrl    URL
        Indexes    OrderedIndexList
        Recent     *Pages
        LastChange time.Time
@@ -70,8 +67,8 @@ func (s *Site) Build() (err error) {
        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 @@ func (s *Site) Analyze() {
        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) checkDescriptions() {
        }
 }
 
-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 @@ func (s *Site) initialize() {
 
        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,22 +205,22 @@ func (s *Site) checkDirectories() {
 }
 
 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))
        }
 }
 
 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) CreatePages() {
 }
 
 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 @@ func (s *Site) setUrlPath(p *Page) error {
        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,14 +308,14 @@ func (s *Site) BuildSiteMeta() (err error) {
 
        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 {
                                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 @@ func (s *Site) BuildSiteMeta() (err error) {
                }
        }
 
-       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 inStringArray(arr []string, el string) bool {
 }
 
 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) RenderAliases() error {
 }
 
 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 @@ func (s *Site) RenderIndexes() error {
                        } 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 @@ func (s *Site) RenderIndexes() error {
                                } 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 @@ func (s *Site) RenderIndexesIndexes() (err error) {
                        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 @@ func (s *Site) RenderLists() error {
                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 @@ func (s *Site) RenderHomePage() error {
        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 @@ func (s *Site) RenderHomePage() error {
                // 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 @@ func (s *Site) RenderHomePage() error {
        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) Stats() {
        }
 }
 
-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)
index 4df0bd60426458b8f0aa6d9ac6136dc0f2501703..0f614146fbc01e2e2f0f1392a1cfa1c1ff9e01e8 100644 (file)
@@ -39,10 +39,9 @@ func TestDegenerateRenderThingMissingTemplate(t *testing.T) {
        }
 }
 
-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.")
        }
index 2d2074c0798b1c023852e06d70bcf2332ced5bd7..f275c0cb9e08b64be6d8a33012708b6ca9f60622 100644 (file)
@@ -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 @@ import (
 // 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)
+}