Add support for amber files

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

Signed-off-by: Noah Campbell <noahcampbell@gmail.com>
This commit is contained in:
Fabrizio (Misto) Milo 2013-08-31 17:47:21 -07:00 committed by Noah Campbell
parent ee5865f239
commit ba82a20321
6 changed files with 171 additions and 108 deletions

View file

@ -21,7 +21,6 @@ import (
"fmt" "fmt"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/theplant/blackfriday" "github.com/theplant/blackfriday"
"html/template"
"io" "io"
"io/ioutil" "io/ioutil"
"launchpad.net/goyaml" "launchpad.net/goyaml"
@ -46,7 +45,7 @@ type Page struct {
contentType string contentType string
Draft bool Draft bool
Aliases []string Aliases []string
Tmpl *template.Template Tmpl Template
Markup string Markup string
PageMeta PageMeta
File File

View file

@ -1,7 +1,6 @@
package hugolib package hugolib
import ( import (
"html/template"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"

View file

@ -16,7 +16,6 @@ package hugolib
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"html/template"
"strings" "strings"
"unicode" "unicode"
) )
@ -37,7 +36,7 @@ type ShortcodeWithPage struct {
type Shortcodes map[string]ShortcodeFunc 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, "{{%") posStart := strings.Index(stringToParse, "{{%")
if posStart > 0 { if posStart > 0 {
posEnd := strings.Index(stringToParse[posStart:], "%}}") + posStart 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:]) 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) buffer := new(bytes.Buffer)
t.ExecuteTemplate(buffer, "shortcodes/"+name+".html", data) t.ExecuteTemplate(buffer, "shortcodes/"+name+".html", data)
return buffer.String() return buffer.String()

View file

@ -16,12 +16,9 @@ package hugolib
import ( import (
"bitbucket.org/pkg/inflect" "bitbucket.org/pkg/inflect"
"bytes" "bytes"
"errors"
"fmt" "fmt"
"github.com/spf13/hugo/target" "github.com/spf13/hugo/target"
"github.com/spf13/nitro" "github.com/spf13/nitro"
"html/template"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -33,7 +30,7 @@ var DefaultTimer = nitro.Initalize()
type Site struct { type Site struct {
Config Config Config Config
Pages Pages Pages Pages
Tmpl *template.Template Tmpl Template
Indexes IndexList Indexes IndexList
Files []string Files []string
Sections Index Sections Index
@ -44,7 +41,7 @@ type Site struct {
} }
type SiteInfo struct { type SiteInfo struct {
BaseUrl template.URL BaseUrl URL
Indexes OrderedIndexList Indexes OrderedIndexList
Recent *Pages Recent *Pages
LastChange time.Time LastChange time.Time
@ -70,8 +67,8 @@ func (s *Site) Build() (err error) {
if err = s.Render(); err != nil { if err = s.Render(); err != nil {
fmt.Printf("Error rendering site: %s\n", err) fmt.Printf("Error rendering site: %s\n", err)
fmt.Printf("Available templates:") fmt.Printf("Available templates:")
for _, template := range s.Tmpl.Templates() { for _, tpl := range s.Tmpl.Templates() {
fmt.Printf("\t%s\n", template.Name()) fmt.Printf("\t%s\n", tpl.Name())
} }
return return
} }
@ -84,6 +81,15 @@ func (s *Site) Analyze() {
s.checkDescriptions() 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) { func (s *Site) Process() (err error) {
s.initialize() s.initialize()
s.prepTemplates() 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() { func (s *Site) initialize() {
s.checkDirectories() s.checkDirectories()
@ -222,7 +169,7 @@ func (s *Site) initialize() {
filepath.Walk(s.absContentDir(), walker) filepath.Walk(s.absContentDir(), walker)
s.Info = SiteInfo{ s.Info = SiteInfo{
BaseUrl: template.URL(s.Config.BaseUrl), BaseUrl: URL(s.Config.BaseUrl),
Title: s.Config.Title, Title: s.Config.Title,
Recent: &s.Pages, Recent: &s.Pages,
Config: &s.Config, Config: &s.Config,
@ -258,22 +205,22 @@ func (s *Site) checkDirectories() {
} }
func (s *Site) ProcessShortcodes() { func (s *Site) ProcessShortcodes() {
for i, _ := range s.Pages { for _, page := range s.Pages {
s.Pages[i].Content = HTML(ShortcodesHandle(string(s.Pages[i].Content), s.Pages[i], s.Tmpl)) page.Content = HTML(ShortcodesHandle(string(page.Content), page, s.Tmpl))
} }
} }
func (s *Site) AbsUrlify() { func (s *Site) AbsUrlify() {
baseWithoutTrailingSlash := strings.TrimRight(s.Config.BaseUrl, "/") baseWithoutTrailingSlash := strings.TrimRight(s.Config.BaseUrl, "/")
baseWithSlash := baseWithoutTrailingSlash + "/" baseWithSlash := baseWithoutTrailingSlash + "/"
for i, _ := range s.Pages { for _, page := range s.Pages {
content := string(s.Pages[i].Content) content := string(page.Content)
content = strings.Replace(content, " src=\"/", " src=\""+baseWithSlash, -1) content = strings.Replace(content, " src=\"/", " src=\""+baseWithSlash, -1)
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, " href=\"/", " href=\""+baseWithSlash, -1) content = strings.Replace(content, " href=\"/", " href=\""+baseWithSlash, -1)
content = strings.Replace(content, baseWithoutTrailingSlash+"//", 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() { func (s *Site) setupPrevNext() {
for i, _ := range s.Pages { for i, page := range s.Pages {
if i < len(s.Pages)-1 { if i < len(s.Pages)-1 {
s.Pages[i].Next = s.Pages[i+1] page.Next = s.Pages[i+1]
} }
if i > 0 { 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)) x := strings.Split(y, string(os.PathSeparator))
if len(x) <= 1 { if len(x) <= 1 {
return errors.New("Zero length page name") return fmt.Errorf("Zero length page name")
} }
p.Section = strings.Trim(x[1], "/\\") p.Section = strings.Trim(x[1], "/\\")
@ -361,14 +308,14 @@ func (s *Site) BuildSiteMeta() (err error) {
for _, plural := range s.Config.Indexes { for _, plural := range s.Config.Indexes {
s.Indexes[plural] = make(Index) s.Indexes[plural] = make(Index)
for i, p := range s.Pages { for _, p := range s.Pages {
vals := p.GetParam(plural) vals := p.GetParam(plural)
if vals != nil { if vals != nil {
v, ok := vals.([]string) v, ok := vals.([]string)
if ok { if ok {
for _, idx := range v { for _, idx := range v {
s.Indexes[plural].Add(idx, s.Pages[i]) s.Indexes[plural].Add(idx, p)
} }
} else { } else {
PrintErr("Invalid " + plural + " in " + p.File.FileName) PrintErr("Invalid " + plural + " in " + p.File.FileName)
@ -380,8 +327,8 @@ func (s *Site) BuildSiteMeta() (err error) {
} }
} }
for i, p := range s.Pages { for _, p := range s.Pages {
s.Sections.Add(p.Section, s.Pages[i]) s.Sections.Add(p.Section, p)
} }
for k, _ := range s.Sections { for k, _ := range s.Sections {
@ -424,13 +371,13 @@ func inStringArray(arr []string, el string) bool {
} }
func (s *Site) RenderAliases() error { func (s *Site) RenderAliases() error {
for i, p := range s.Pages { for _, p := range s.Pages {
for _, a := range p.Aliases { for _, a := range p.Aliases {
t := "alias" t := "alias"
if strings.HasSuffix(a, ".xhtml") { if strings.HasSuffix(a, ".xhtml") {
t = "alias-xhtml" t = "alias-xhtml"
} }
content, err := s.RenderThing(s.Pages[i], t) content, err := s.RenderThing(p, t)
if strings.HasSuffix(a, "/") { if strings.HasSuffix(a, "/") {
a = a + "index.html" a = a + "index.html"
} }
@ -447,12 +394,12 @@ func (s *Site) RenderAliases() error {
} }
func (s *Site) RenderPages() error { func (s *Site) RenderPages() error {
for i, _ := range s.Pages { for _, p := range s.Pages {
content, err := s.RenderThingOrDefault(s.Pages[i], s.Pages[i].Layout(), "_default/single.html") content, err := s.RenderThingOrDefault(p, p.Layout(), "_default/single.html")
if err != nil { if err != nil {
return err return err
} }
s.Pages[i].RenderedContent = content p.RenderedContent = content
} }
return nil return nil
} }
@ -480,8 +427,8 @@ func (s *Site) RenderIndexes() error {
} else { } else {
n.Url = url + "/index.html" n.Url = url + "/index.html"
} }
n.Permalink = HTML(MakePermalink(string(n.Site.BaseUrl), string(plink))) n.Permalink = permalink(s, plink)
n.RSSlink = HTML(MakePermalink(string(n.Site.BaseUrl), string(url+".xml"))) n.RSSlink = permalink(s, url+".xml")
n.Date = o[0].Date n.Date = o[0].Date
n.Data[singular] = o n.Data[singular] = o
n.Data["Pages"] = o n.Data["Pages"] = o
@ -511,7 +458,7 @@ func (s *Site) RenderIndexes() error {
} else { } else {
n.Url = Urlize(plural + "/" + k + "/" + "index.xml") 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) s.Tmpl.ExecuteTemplate(y, "rss.xml", n)
err = s.WritePublic(base+".xml", y.Bytes()) err = s.WritePublic(base+".xml", y.Bytes())
if err != nil { if err != nil {
@ -531,7 +478,7 @@ func (s *Site) RenderIndexesIndexes() (err error) {
n.Title = strings.Title(plural) n.Title = strings.Title(plural)
url := Urlize(plural) url := Urlize(plural)
n.Url = url + "/index.html" 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["Singular"] = singular
n.Data["Plural"] = plural n.Data["Plural"] = plural
n.Data["Index"] = s.Indexes[plural] n.Data["Index"] = s.Indexes[plural]
@ -556,8 +503,8 @@ func (s *Site) RenderLists() error {
n := s.NewNode() n := s.NewNode()
n.Title = strings.Title(inflect.Pluralize(section)) n.Title = strings.Title(inflect.Pluralize(section))
n.Url = Urlize(section + "/" + "index.html") n.Url = Urlize(section + "/" + "index.html")
n.Permalink = HTML(MakePermalink(string(n.Site.BaseUrl), string(n.Url))) n.Permalink = permalink(s, n.Url)
n.RSSlink = HTML(MakePermalink(string(n.Site.BaseUrl), string(section+".xml"))) n.RSSlink = permalink(s, section+".xml")
n.Date = data[0].Date n.Date = data[0].Date
n.Data["Pages"] = data n.Data["Pages"] = data
layout := "indexes/" + section + ".html" layout := "indexes/" + section + ".html"
@ -592,8 +539,8 @@ func (s *Site) RenderHomePage() error {
n := s.NewNode() n := s.NewNode()
n.Title = n.Site.Title n.Title = n.Site.Title
n.Url = Urlize(string(n.Site.BaseUrl)) n.Url = Urlize(string(n.Site.BaseUrl))
n.RSSlink = HTML(MakePermalink(string(n.Site.BaseUrl), string("index.xml"))) n.RSSlink = permalink(s, "index.xml")
n.Permalink = HTML(string(n.Site.BaseUrl)) n.Permalink = permalink(s, "")
if len(s.Pages) > 0 { if len(s.Pages) > 0 {
n.Date = s.Pages[0].Date n.Date = s.Pages[0].Date
if len(s.Pages) < 9 { if len(s.Pages) < 9 {
@ -615,7 +562,7 @@ func (s *Site) RenderHomePage() error {
// XML Feed // XML Feed
n.Url = Urlize("index.xml") n.Url = Urlize("index.xml")
n.Title = "Recent Content" n.Title = "Recent Content"
n.Permalink = HTML(string(n.Site.BaseUrl) + "index.xml") n.Permalink = permalink(s, "index.xml")
y := s.NewXMLBuffer() y := s.NewXMLBuffer()
s.Tmpl.ExecuteTemplate(y, "rss.xml", n) s.Tmpl.ExecuteTemplate(y, "rss.xml", n)
err = s.WritePublic("index.xml", y.Bytes()) 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 { if a := s.Tmpl.Lookup("404.html"); a != nil {
n.Url = Urlize("404.html") n.Url = Urlize("404.html")
n.Title = "404 Page not found" 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") x, err := s.RenderThing(n, "404.html")
if err != nil { if err != nil {
return err return err
@ -644,15 +591,20 @@ func (s *Site) Stats() {
} }
} }
func (s *Site) NewNode() (y Node) { func permalink(s *Site, plink string) HTML {
y.Data = make(map[string]interface{}) return HTML(MakePermalink(string(s.Info.BaseUrl), plink))
y.Site = s.Info }
return y
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) { func (s *Site) RenderThing(d interface{}, layout string) (*bytes.Buffer, error) {
if s.Tmpl.Lookup(layout) == nil { 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) buffer := new(bytes.Buffer)
err := s.Tmpl.ExecuteTemplate(buffer, layout, d) err := s.Tmpl.ExecuteTemplate(buffer, layout, d)

View file

@ -39,10 +39,9 @@ func TestDegenerateRenderThingMissingTemplate(t *testing.T) {
} }
} }
func TestPrimeTempaltes(t *testing.T) { func TestPrimeTemplates(t *testing.T) {
s := new(Site) s := new(Site)
s.prepTemplates() s.prepTemplates()
s.primeTemplates()
if s.Tmpl.Lookup("alias") == nil { if s.Tmpl.Lookup("alias") == nil {
t.Fatalf("alias template not created.") t.Fatalf("alias template not created.")
} }

View file

@ -1,7 +1,13 @@
package hugolib package hugolib
import ( import (
"io/ioutil"
"github.com/eknkc/amber"
"html/template" "html/template"
"io"
"os"
"path/filepath"
"strings"
) )
// HTML encapsulates a known safe HTML document fragment. // HTML encapsulates a known safe HTML document fragment.
@ -9,3 +15,112 @@ import (
// unclosed tags or comments. The outputs of a sound HTML sanitizer // unclosed tags or comments. The outputs of a sound HTML sanitizer
// and a template escaped by this package are fine for use with HTML. // and a template escaped by this package are fine for use with HTML.
type HTML template.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)
}