Refactor layout selection code

The render code path would use a fallback if there was an exception.
This change instead relies on explicit declaration of the layout to use
and includes a check to see if the layout indeed exists before
attempting to render it.
This commit is contained in:
Noah Campbell 2013-10-07 07:57:45 +03:00
parent 197aacb647
commit 9500ec1b6b
10 changed files with 134 additions and 84 deletions

View file

@ -22,7 +22,7 @@ The homepage of your site.
### [RSS](/layout/rss/) ### [RSS](/layout/rss/)
Used to render all rss documents. Used to render all rss documents.
### [Index](/layout/index) ### [Index](/layout/indexes)
Page that list multiple pieces of content. Page that list multiple pieces of content.
### [Content](/layout/content) ### [Content](/layout/content)

View file

@ -2,40 +2,40 @@
<li> <a href="/">Home</a></li> <li> <a href="/">Home</a></li>
<li class="divider"></li> <li class="divider"></li>
<li class="nav-header">Getting Started</li> <li class="nav-header">Getting Started</li>
<li> <a href="/overview/installing">Installing Hugo</a></li> <li hugo-nav="/overview/installing"> <a href="/overview/installing">Installing Hugo</a></li>
<li> <a href="/overview/usage">Usage</a> </li> <li hugo-nav="/overview/usage"> <a href="/overview/usage">Usage</a> </li>
<li> <a href="/overview/configuration">Configuration</a></li> <li hugo-nav="/overview/configuration"> <a href="/overview/configuration">Configuration</a></li>
<li> <a href="/overview/source-directory">Source Directory Layout</a></li> <li hugo-nav="/overview/source-directory"> <a href="/overview/source-directory">Source Directory Layout</a></li>
<li class="divider"></li> <li class="divider"></li>
<li class="nav-header">Layout</li> <li class="nav-header">Layout</li>
<li> <a href="/layout/templates">Overview</a></li> <li> <a href="/layout/templates">Overview</a></li>
<!--<li> <a href="/layout/go-templates">Go Templates</a></li>--> <!--<li> <a href="/layout/go-templates">Go Templates</a></li>-->
<li> <a href="/layout/variables">Variables</a></li> <li hugo-nav="/layout/variables"> <a href="/layout/variables">Variables</a></li>
<li> <a href="/layout/homepage">Homepage</a></li> <li hugo-nav="/layout/homepage"> <a href="/layout/homepage">Homepage</a></li>
<li> <a href="/layout/rss">RSS</a></li> <li hugo-nav="/layout/rss"> <a href="/layout/rss">RSS</a></li>
<li> <a href="/layout/index">Index</a></li> <li hugo-nav="/layout/indexes"> <a href="/layout/indexes">Index</a></li>
<li> <a href="/layout/content">Content</a></li> <li hugo-nav="/layout/content"> <a href="/layout/content">Content</a></li>
<li> <a href="/layout/views">Content Views</a></li> <li hugo-nav="/layout/views"> <a href="/layout/views">Content Views</a></li>
<li> <a href="/layout/chrome">Chrome</a></li> <li hugo-nav="/layout/chrome"> <a href="/layout/chrome">Chrome</a></li>
<li class="divider"></li> <li class="divider"></li>
<li class="nav-header">Content</li> <li class="nav-header">Content</li>
<li> <a href="/content/organization">Organization</a></li> <li hugo-nav="/content/organization"> <a href="/content/organization">Organization</a></li>
<li> <a href="/content/sections">Sections</a></li> <li hugo-nav="/content/sections"> <a href="/content/sections">Sections</a></li>
<li> <a href="/content/types">Types</a></li> <li hugo-nav="/content/types"> <a href="/content/types">Types</a></li>
<li> <a href="/content/front-matter">Front Matter</a></li> <li hugo-nav="/content/front-matter"> <a href="/content/front-matter">Front Matter</a></li>
<li> <a href="/content/example">Example</a></li> <li hugo-nav="/content/example"> <a href="/content/example">Example</a></li>
<li class="divider"></li> <li class="divider"></li>
<li class="nav-header">Extras</li> <li class="nav-header">Extras</li>
<li> <a href="/extras/shortcodes">ShortCodes</a></li> <li hugo-nav="/extras/shortcodes"> <a href="/extras/shortcodes">ShortCodes</a></li>
<li> <a href="/extras/aliases">Aliases</a></li> <li hugo-nav="/extras/aliases"> <a href="/extras/aliases">Aliases</a></li>
<li> <a href="/extras/indexes">Indexes</a></li> <li hugo-nav="/extras/indexes"> <a href="/extras/indexes">Indexes</a></li>
<li> <a href="/extras/indexes/category">Example Index - Category</a></li> <li hugo-nav="/extras/indexes/category"> <a href="/extras/indexes/category">Example Index - Category</a></li>
<!--<li> <a href="/extras/indexes/series">Example Index - Series</a></li>--> <!--<li> <a href="/extras/indexes/series">Example Index - Series</a></li>-->
<li class="divider"></li> <li class="divider"></li>
<li class="nav-header">Meta</li> <li class="nav-header">Meta</li>
<li> <a href="/meta/release-notes">Release Notes</a></li> <li hugo-nav="/meta/release-notes"> <a href="/meta/release-notes">Release Notes</a></li>
<li> <a href="/meta/roadmap">Roadmap</a> </li> <li hugo-nav="/meta/roadmap"> <a href="/meta/roadmap">Roadmap</a> </li>
<li> <a href="/meta/contributing">Contributing</a></li> <li hugo-nav="/meta/contributing"> <a href="/meta/contributing">Contributing</a></li>
<li> <a href="/meta/contributors">Contributors</a></li> <li hugo-nav="/meta/contributors"> <a href="/meta/contributors">Contributors</a></li>
<li> <a href="/meta/license">License</a></li> <li hugo-nav="/meta/license"> <a href="/meta/license">License</a></li>
</ul> </ul>

View file

@ -21,7 +21,7 @@ import (
type Node struct { type Node struct {
RSSlink template.HTML RSSlink template.HTML
Site SiteInfo Site SiteInfo
layout string // layout string
Data map[string]interface{} Data map[string]interface{}
Title string Title string
Description string Description string

View file

@ -46,6 +46,7 @@ type Page struct {
Tmpl bundle.Template Tmpl bundle.Template
Markup string Markup string
renderable bool renderable bool
layout string
PageMeta PageMeta
File File
Position Position
@ -151,11 +152,14 @@ func (p *Page) IsRenderable() bool {
func (p *Page) guessSection() { func (p *Page) guessSection() {
if p.Section == "" { if p.Section == "" {
x := strings.Split(p.FileName, "/") x := strings.Split(p.FileName, "/")
if len(x) > 1 { x = x[:len(x)-1]
if section := x[len(x)-2]; section != "content" { if len(x) == 0 {
p.Section = section return
}
} }
if x[0] == "content" {
x = x[1:]
}
p.Section = path.Join(x...)
} }
} }
@ -171,7 +175,11 @@ func (page *Page) Type() string {
return "page" return "page"
} }
func (page *Page) Layout(l ...string) string { func (page *Page) Layout(l ...string) []string {
if page.layout != "" {
return layouts(page.Type(), page.layout)
}
layout := "" layout := ""
if len(l) == 0 { if len(l) == 0 {
layout = "single" layout = "single"
@ -179,11 +187,17 @@ func (page *Page) Layout(l ...string) string {
layout = l[0] layout = l[0]
} }
if x := page.layout; x != "" { return layouts(page.Type(), layout)
return x }
}
return strings.ToLower(page.Type()) + "/" + layout + ".html" func layouts(types string, layout string) (layouts []string) {
t := strings.Split(types, "/")
for i := range t {
search := t[:len(t)-i]
layouts = append(layouts, fmt.Sprintf("%s/%s.html", strings.ToLower(path.Join(search...)), layout))
}
layouts = append(layouts, fmt.Sprintf("%s.html", layout))
return
} }
func ReadFrom(buf io.Reader, name string) (page *Page, err error) { func ReadFrom(buf io.Reader, name string) (page *Page, err error) {
@ -214,7 +228,7 @@ func (p *Page) permalink() (*url.URL, error) {
pUrl := strings.TrimSpace(p.Url) pUrl := strings.TrimSpace(p.Url)
var permalink string var permalink string
if len(pSlug) > 0 { if len(pSlug) > 0 {
if p.Site.Config.UglyUrls { if p.Site.Config != nil && p.Site.Config.UglyUrls {
permalink = section + "/" + p.Slug + "." + p.Extension permalink = section + "/" + p.Slug + "." + p.Extension
} else { } else {
permalink = section + "/" + p.Slug + "/" permalink = section + "/" + p.Slug + "/"
@ -404,7 +418,12 @@ func (p *Page) Render(layout ...string) template.HTML {
func (p *Page) ExecuteTemplate(layout string) *bytes.Buffer { func (p *Page) ExecuteTemplate(layout string) *bytes.Buffer {
l := p.Layout(layout) l := p.Layout(layout)
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
p.Tmpl.ExecuteTemplate(buffer, l, p) for _, layout := range l {
if p.Tmpl.Lookup(layout) != nil {
p.Tmpl.ExecuteTemplate(buffer, layout, p)
break
}
}
return buffer return buffer
} }

View file

@ -0,0 +1,9 @@
package hugolib
import (
"testing"
)
func TestPermalink(t *testing.T) {
}

View file

@ -180,8 +180,8 @@ func checkPageType(t *testing.T, page *Page, pageType string) {
} }
} }
func checkPageLayout(t *testing.T, page *Page, layout string) { func checkPageLayout(t *testing.T, page *Page, layout ...string) {
if page.Layout() != layout { if !listEqual(page.Layout(), layout) {
t.Fatalf("Page layout is: %s. Expected: %s", page.Layout(), layout) t.Fatalf("Page layout is: %s. Expected: %s", page.Layout(), layout)
} }
} }
@ -201,7 +201,7 @@ func TestCreateNewPage(t *testing.T) {
checkPageContent(t, p, "<p>Simple Page</p>\n") checkPageContent(t, p, "<p>Simple Page</p>\n")
checkPageSummary(t, p, "Simple Page") checkPageSummary(t, p, "Simple Page")
checkPageType(t, p, "page") checkPageType(t, p, "page")
checkPageLayout(t, p, "page/single.html") checkPageLayout(t, p, "page/single.html", "single.html")
} }
func TestPageWithDelimiter(t *testing.T) { func TestPageWithDelimiter(t *testing.T) {
@ -213,7 +213,7 @@ func TestPageWithDelimiter(t *testing.T) {
checkPageContent(t, p, "<p>Summary Next Line</p>\n\n<p>Some more text</p>\n") checkPageContent(t, p, "<p>Summary Next Line</p>\n\n<p>Some more text</p>\n")
checkPageSummary(t, p, "<p>Summary Next Line</p>\n") checkPageSummary(t, p, "<p>Summary Next Line</p>\n")
checkPageType(t, p, "page") checkPageType(t, p, "page")
checkPageLayout(t, p, "page/single.html") checkPageLayout(t, p, "page/single.html", "single.html")
} }
func TestPageWithShortCodeInSummary(t *testing.T) { func TestPageWithShortCodeInSummary(t *testing.T) {
@ -225,7 +225,7 @@ func TestPageWithShortCodeInSummary(t *testing.T) {
checkPageContent(t, p, "<p>Summary Next Line. {{% img src=&ldquo;/not/real&rdquo; %}}.\nMore text here.</p>\n\n<p>Some more text</p>\n") checkPageContent(t, p, "<p>Summary Next Line. {{% img src=&ldquo;/not/real&rdquo; %}}.\nMore text here.</p>\n\n<p>Some more text</p>\n")
checkPageSummary(t, p, "Summary Next Line. . More text here. Some more text") checkPageSummary(t, p, "Summary Next Line. . More text here. Some more text")
checkPageType(t, p, "page") checkPageType(t, p, "page")
checkPageLayout(t, p, "page/single.html") checkPageLayout(t, p, "page/single.html", "single.html")
} }
func TestPageWithMoreTag(t *testing.T) { func TestPageWithMoreTag(t *testing.T) {
@ -237,7 +237,7 @@ func TestPageWithMoreTag(t *testing.T) {
checkPageContent(t, p, "<p>Summary Same Line</p>\n\n<p>Some more text</p>\n") checkPageContent(t, p, "<p>Summary Same Line</p>\n\n<p>Some more text</p>\n")
checkPageSummary(t, p, "<p>Summary Same Line</p>\n") checkPageSummary(t, p, "<p>Summary Same Line</p>\n")
checkPageType(t, p, "page") checkPageType(t, p, "page")
checkPageLayout(t, p, "page/single.html") checkPageLayout(t, p, "page/single.html", "single.html")
} }
func TestPageWithDate(t *testing.T) { func TestPageWithDate(t *testing.T) {
@ -315,10 +315,14 @@ func TestSectionEvaluation(t *testing.T) {
} }
} }
func L(s ...string) []string {
return s
}
func TestLayoutOverride(t *testing.T) { func TestLayoutOverride(t *testing.T) {
var ( var (
path_content_one_dir = path.Join("content", "gub", "file1.md")
path_content_two_dir = path.Join("content", "dub", "sub", "file1.md") path_content_two_dir = path.Join("content", "dub", "sub", "file1.md")
path_content_one_dir = path.Join("content", "gub", "file1.md")
path_content_no_dir = path.Join("content", "file1") path_content_no_dir = path.Join("content", "file1")
path_one_directory = path.Join("fub", "file1.md") path_one_directory = path.Join("fub", "file1.md")
path_no_directory = path.Join("file1.md") path_no_directory = path.Join("file1.md")
@ -326,35 +330,49 @@ func TestLayoutOverride(t *testing.T) {
tests := []struct { tests := []struct {
content string content string
path string path string
expectedLayout string expectedLayout []string
}{ }{
{SIMPLE_PAGE_NOLAYOUT, path_content_two_dir, "sub/single.html"}, {SIMPLE_PAGE_NOLAYOUT, path_content_two_dir, L("dub/sub/single.html", "dub/single.html", "single.html")},
{SIMPLE_PAGE_NOLAYOUT, path_content_one_dir, "gub/single.html"}, {SIMPLE_PAGE_NOLAYOUT, path_content_one_dir, L("gub/single.html", "single.html")},
{SIMPLE_PAGE_NOLAYOUT, path_content_no_dir, "page/single.html"}, {SIMPLE_PAGE_NOLAYOUT, path_content_no_dir, L("page/single.html", "single.html")},
{SIMPLE_PAGE_NOLAYOUT, path_one_directory, "fub/single.html"}, {SIMPLE_PAGE_NOLAYOUT, path_one_directory, L("fub/single.html", "single.html")},
{SIMPLE_PAGE_NOLAYOUT, path_no_directory, "page/single.html"}, {SIMPLE_PAGE_NOLAYOUT, path_no_directory, L("page/single.html", "single.html")},
{SIMPLE_PAGE_LAYOUT_FOOBAR, path_content_two_dir, "foobar"}, {SIMPLE_PAGE_LAYOUT_FOOBAR, path_content_two_dir, L("dub/sub/foobar.html", "dub/foobar.html", "foobar.html")},
{SIMPLE_PAGE_LAYOUT_FOOBAR, path_content_one_dir, "foobar"}, {SIMPLE_PAGE_LAYOUT_FOOBAR, path_content_one_dir, L("gub/foobar.html", "foobar.html")},
{SIMPLE_PAGE_LAYOUT_FOOBAR, path_one_directory, "foobar"}, {SIMPLE_PAGE_LAYOUT_FOOBAR, path_one_directory, L("fub/foobar.html", "foobar.html")},
{SIMPLE_PAGE_LAYOUT_FOOBAR, path_no_directory, "foobar"}, {SIMPLE_PAGE_LAYOUT_FOOBAR, path_no_directory, L("page/foobar.html", "foobar.html")},
{SIMPLE_PAGE_TYPE_FOOBAR, path_content_two_dir, "foobar/single.html"}, {SIMPLE_PAGE_TYPE_FOOBAR, path_content_two_dir, L("foobar/single.html", "single.html")},
{SIMPLE_PAGE_TYPE_FOOBAR, path_content_one_dir, "foobar/single.html"}, {SIMPLE_PAGE_TYPE_FOOBAR, path_content_one_dir, L("foobar/single.html", "single.html")},
{SIMPLE_PAGE_TYPE_FOOBAR, path_content_no_dir, "foobar/single.html"}, {SIMPLE_PAGE_TYPE_FOOBAR, path_content_no_dir, L("foobar/single.html", "single.html")},
{SIMPLE_PAGE_TYPE_FOOBAR, path_one_directory, "foobar/single.html"}, {SIMPLE_PAGE_TYPE_FOOBAR, path_one_directory, L("foobar/single.html", "single.html")},
{SIMPLE_PAGE_TYPE_FOOBAR, path_no_directory, "foobar/single.html"}, {SIMPLE_PAGE_TYPE_FOOBAR, path_no_directory, L("foobar/single.html", "single.html")},
{SIMPLE_PAGE_TYPE_LAYOUT, path_content_two_dir, "buzfoo"}, {SIMPLE_PAGE_TYPE_LAYOUT, path_content_two_dir, L("barfoo/buzfoo.html", "buzfoo.html")},
{SIMPLE_PAGE_TYPE_LAYOUT, path_content_one_dir, "buzfoo"}, {SIMPLE_PAGE_TYPE_LAYOUT, path_content_one_dir, L("barfoo/buzfoo.html", "buzfoo.html")},
{SIMPLE_PAGE_TYPE_LAYOUT, path_content_no_dir, "buzfoo"}, {SIMPLE_PAGE_TYPE_LAYOUT, path_content_no_dir, L("barfoo/buzfoo.html", "buzfoo.html")},
{SIMPLE_PAGE_TYPE_LAYOUT, path_one_directory, "buzfoo"}, {SIMPLE_PAGE_TYPE_LAYOUT, path_one_directory, L("barfoo/buzfoo.html", "buzfoo.html")},
{SIMPLE_PAGE_TYPE_LAYOUT, path_no_directory, "buzfoo"}, {SIMPLE_PAGE_TYPE_LAYOUT, path_no_directory, L("barfoo/buzfoo.html", "buzfoo.html")},
} }
for _, test := range tests { for _, test := range tests {
p, err := ReadFrom(strings.NewReader(test.content), test.path) p, err := ReadFrom(strings.NewReader(test.content), test.path)
if err != nil { if err != nil {
t.Fatalf("Unable to parse content:\n%s\n", test.content) t.Fatalf("Unable to parse content:\n%s\n", test.content)
} }
if p.Layout() != test.expectedLayout { if !listEqual(p.Layout(), test.expectedLayout) {
t.Errorf("Layout mismatch. Expected: %s, got: %s", test.expectedLayout, p.Layout()) t.Errorf("Layout mismatch. Expected: %s, got: %s", test.expectedLayout, p.Layout())
} }
} }
} }
func listEqual(left, right []string) bool {
if len(left) != len(right) {
return false
}
for i := range left {
if left[i] != right[i] {
return false
}
}
return true
}

View file

@ -26,25 +26,26 @@ func TestNewPageWithFilePath(t *testing.T) {
toCheck := []struct { toCheck := []struct {
input string input string
section string section string
layout string layout []string
}{ }{
{path.Join("sub", "foobar.html"), "sub", "sub/single.html"}, {path.Join("sub", "foobar.html"), "sub", L("sub/single.html", "single.html")},
{path.Join("content", "sub", "foobar.html"), "sub", "sub/single.html"}, {path.Join("content", "foobar.html"), "", L("page/single.html", "single.html")},
{path.Join("content", "dub", "sub", "foobar.html"), "sub", "sub/single.html"}, {path.Join("content", "sub", "foobar.html"), "sub", L("sub/single.html", "single.html")},
{path.Join("content", "dub", "sub", "foobar.html"), "dub/sub", L("dub/sub/single.html", "dub/single.html", "single.html")},
} }
for _, el := range toCheck { for _, el := range toCheck {
p, err := ReadFrom(strings.NewReader(SIMPLE_PAGE_YAML), el.input) p, err := ReadFrom(strings.NewReader(SIMPLE_PAGE_YAML), el.input)
p.guessSection() p.guessSection()
if err != nil { if err != nil {
t.Fatalf("Reading from SIMPLE_PAGE_YAML resulted in an error: %s", err) t.Errorf("Reading from SIMPLE_PAGE_YAML resulted in an error: %s", err)
} }
if p.Section != el.section { if p.Section != el.section {
t.Fatalf("Section not set to %s for page %s. Got: %s", el.section, el.input, p.Section) t.Errorf("Section not set to %s for page %s. Got: %s", el.section, el.input, p.Section)
} }
if p.Layout() != el.layout { if !listEqual(p.Layout(), el.layout) {
t.Fatalf("Layout incorrect. Expected: '%s', Got: '%s'", el.layout, p.Layout()) t.Errorf("Layout incorrect. Expected: '%s', Got: '%s'", el.layout, p.Layout())
} }
} }
} }

View file

@ -18,7 +18,9 @@ func (s *Site) ShowPlan(out io.Writer) (err error) {
fmt.Fprintf(out, " (renderer: n/a)") fmt.Fprintf(out, " (renderer: n/a)")
} }
if s.Tmpl != nil { if s.Tmpl != nil {
fmt.Fprintf(out, " (layout: %s, exists: %t)", p.Layout(), s.Tmpl.Lookup(p.Layout()) != nil) for _, l := range p.Layout() {
fmt.Fprintf(out, " (layout: %s, exists: %t)", l, s.Tmpl.Lookup(l) != nil)
}
} }
fmt.Fprintf(out, "\n") fmt.Fprintf(out, "\n")
fmt.Fprintf(out, " canonical => ") fmt.Fprintf(out, " canonical => ")

View file

@ -67,6 +67,7 @@ type Site struct {
Transformer transform.Transformer Transformer transform.Transformer
Target target.Output Target target.Output
Alias target.AliasPublisher Alias target.AliasPublisher
Completed chan bool
} }
type SiteInfo struct { type SiteInfo struct {
@ -364,19 +365,21 @@ func (s *Site) RenderAliases() error {
func (s *Site) RenderPages() (err error) { func (s *Site) RenderPages() (err error) {
for _, p := range s.Pages { for _, p := range s.Pages {
var layout string var layout []string
if !p.IsRenderable() { if !p.IsRenderable() {
layout = "__" + p.TargetPath() self := "__" + p.TargetPath()
_, err := s.Tmpl.New(layout).Parse(string(p.Content)) _, err := s.Tmpl.New(self).Parse(string(p.Content))
if err != nil { if err != nil {
return err return err
} }
layout = append(layout, self)
} else { } else {
layout = p.Layout() layout = append(layout, p.Layout()...)
layout = append(layout, "_default/single.html")
} }
err := s.render(p, p.TargetPath(), layout, "_default/single.html") err := s.render(p, p.TargetPath(), layout...)
if err != nil { if err != nil {
return err return err
} }
@ -484,7 +487,7 @@ func (s *Site) RenderHomePage() error {
n.Data["Pages"] = s.Pages[:9] n.Data["Pages"] = s.Pages[:9]
} }
} }
err := s.render(n, "/", "index.html") err := s.render(n, "/", "index.html", "_default/single.html")
if err != nil { if err != nil {
return err return err
} }
@ -554,8 +557,6 @@ func (s *Site) render(d interface{}, out string, layouts ...string) (err error)
section, _ = page.RelPermalink() section, _ = page.RelPermalink()
} }
fmt.Println("Section is:", section)
transformer := transform.NewChain( transformer := transform.NewChain(
&transform.AbsURL{BaseURL: s.Config.BaseUrl}, &transform.AbsURL{BaseURL: s.Config.BaseUrl},
&transform.NavActive{Section: section}, &transform.NavActive{Section: section},

View file

@ -227,7 +227,7 @@ func TestSkipRender(t *testing.T) {
s := &Site{ s := &Site{
Target: target, Target: target,
Config: Config{BaseUrl: "http://auth/bub/"}, Config: Config{Verbose: true, BaseUrl: "http://auth/bub/"},
Source: &source.InMemorySource{sources}, Source: &source.InMemorySource{sources},
} }
s.initializeSiteInfo() s.initializeSiteInfo()