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/)
Used to render all rss documents.
### [Index](/layout/index)
### [Index](/layout/indexes)
Page that list multiple pieces of content.
### [Content](/layout/content)

View file

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

View file

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

View file

@ -46,6 +46,7 @@ type Page struct {
Tmpl bundle.Template
Markup string
renderable bool
layout string
PageMeta
File
Position
@ -151,11 +152,14 @@ func (p *Page) IsRenderable() bool {
func (p *Page) guessSection() {
if p.Section == "" {
x := strings.Split(p.FileName, "/")
if len(x) > 1 {
if section := x[len(x)-2]; section != "content" {
p.Section = section
}
x = x[:len(x)-1]
if len(x) == 0 {
return
}
if x[0] == "content" {
x = x[1:]
}
p.Section = path.Join(x...)
}
}
@ -171,7 +175,11 @@ func (page *Page) Type() string {
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 := ""
if len(l) == 0 {
layout = "single"
@ -179,11 +187,17 @@ func (page *Page) Layout(l ...string) string {
layout = l[0]
}
if x := page.layout; x != "" {
return x
}
return layouts(page.Type(), layout)
}
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) {
@ -214,7 +228,7 @@ func (p *Page) permalink() (*url.URL, error) {
pUrl := strings.TrimSpace(p.Url)
var permalink string
if len(pSlug) > 0 {
if p.Site.Config.UglyUrls {
if p.Site.Config != nil && p.Site.Config.UglyUrls {
permalink = section + "/" + p.Slug + "." + p.Extension
} else {
permalink = section + "/" + p.Slug + "/"
@ -404,7 +418,12 @@ func (p *Page) Render(layout ...string) template.HTML {
func (p *Page) ExecuteTemplate(layout string) *bytes.Buffer {
l := p.Layout(layout)
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
}

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) {
if page.Layout() != layout {
func checkPageLayout(t *testing.T, page *Page, layout ...string) {
if !listEqual(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")
checkPageSummary(t, p, "Simple Page")
checkPageType(t, p, "page")
checkPageLayout(t, p, "page/single.html")
checkPageLayout(t, p, "page/single.html", "single.html")
}
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")
checkPageSummary(t, p, "<p>Summary Next Line</p>\n")
checkPageType(t, p, "page")
checkPageLayout(t, p, "page/single.html")
checkPageLayout(t, p, "page/single.html", "single.html")
}
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")
checkPageSummary(t, p, "Summary Next Line. . More text here. Some more text")
checkPageType(t, p, "page")
checkPageLayout(t, p, "page/single.html")
checkPageLayout(t, p, "page/single.html", "single.html")
}
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")
checkPageSummary(t, p, "<p>Summary Same Line</p>\n")
checkPageType(t, p, "page")
checkPageLayout(t, p, "page/single.html")
checkPageLayout(t, p, "page/single.html", "single.html")
}
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) {
var (
path_content_one_dir = path.Join("content", "gub", "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_one_directory = path.Join("fub", "file1.md")
path_no_directory = path.Join("file1.md")
@ -326,35 +330,49 @@ func TestLayoutOverride(t *testing.T) {
tests := []struct {
content string
path string
expectedLayout string
expectedLayout []string
}{
{SIMPLE_PAGE_NOLAYOUT, path_content_two_dir, "sub/single.html"},
{SIMPLE_PAGE_NOLAYOUT, path_content_one_dir, "gub/single.html"},
{SIMPLE_PAGE_NOLAYOUT, path_content_no_dir, "page/single.html"},
{SIMPLE_PAGE_NOLAYOUT, path_one_directory, "fub/single.html"},
{SIMPLE_PAGE_NOLAYOUT, path_no_directory, "page/single.html"},
{SIMPLE_PAGE_LAYOUT_FOOBAR, path_content_two_dir, "foobar"},
{SIMPLE_PAGE_LAYOUT_FOOBAR, path_content_one_dir, "foobar"},
{SIMPLE_PAGE_LAYOUT_FOOBAR, path_one_directory, "foobar"},
{SIMPLE_PAGE_LAYOUT_FOOBAR, path_no_directory, "foobar"},
{SIMPLE_PAGE_TYPE_FOOBAR, path_content_two_dir, "foobar/single.html"},
{SIMPLE_PAGE_TYPE_FOOBAR, path_content_one_dir, "foobar/single.html"},
{SIMPLE_PAGE_TYPE_FOOBAR, path_content_no_dir, "foobar/single.html"},
{SIMPLE_PAGE_TYPE_FOOBAR, path_one_directory, "foobar/single.html"},
{SIMPLE_PAGE_TYPE_FOOBAR, path_no_directory, "foobar/single.html"},
{SIMPLE_PAGE_TYPE_LAYOUT, path_content_two_dir, "buzfoo"},
{SIMPLE_PAGE_TYPE_LAYOUT, path_content_one_dir, "buzfoo"},
{SIMPLE_PAGE_TYPE_LAYOUT, path_content_no_dir, "buzfoo"},
{SIMPLE_PAGE_TYPE_LAYOUT, path_one_directory, "buzfoo"},
{SIMPLE_PAGE_TYPE_LAYOUT, path_no_directory, "buzfoo"},
{SIMPLE_PAGE_NOLAYOUT, path_content_two_dir, L("dub/sub/single.html", "dub/single.html", "single.html")},
{SIMPLE_PAGE_NOLAYOUT, path_content_one_dir, L("gub/single.html", "single.html")},
{SIMPLE_PAGE_NOLAYOUT, path_content_no_dir, L("page/single.html", "single.html")},
{SIMPLE_PAGE_NOLAYOUT, path_one_directory, L("fub/single.html", "single.html")},
{SIMPLE_PAGE_NOLAYOUT, path_no_directory, L("page/single.html", "single.html")},
{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, L("gub/foobar.html", "foobar.html")},
{SIMPLE_PAGE_LAYOUT_FOOBAR, path_one_directory, L("fub/foobar.html", "foobar.html")},
{SIMPLE_PAGE_LAYOUT_FOOBAR, path_no_directory, L("page/foobar.html", "foobar.html")},
{SIMPLE_PAGE_TYPE_FOOBAR, path_content_two_dir, L("foobar/single.html", "single.html")},
{SIMPLE_PAGE_TYPE_FOOBAR, path_content_one_dir, L("foobar/single.html", "single.html")},
{SIMPLE_PAGE_TYPE_FOOBAR, path_content_no_dir, L("foobar/single.html", "single.html")},
{SIMPLE_PAGE_TYPE_FOOBAR, path_one_directory, L("foobar/single.html", "single.html")},
{SIMPLE_PAGE_TYPE_FOOBAR, path_no_directory, L("foobar/single.html", "single.html")},
{SIMPLE_PAGE_TYPE_LAYOUT, path_content_two_dir, L("barfoo/buzfoo.html", "buzfoo.html")},
{SIMPLE_PAGE_TYPE_LAYOUT, path_content_one_dir, L("barfoo/buzfoo.html", "buzfoo.html")},
{SIMPLE_PAGE_TYPE_LAYOUT, path_content_no_dir, L("barfoo/buzfoo.html", "buzfoo.html")},
{SIMPLE_PAGE_TYPE_LAYOUT, path_one_directory, L("barfoo/buzfoo.html", "buzfoo.html")},
{SIMPLE_PAGE_TYPE_LAYOUT, path_no_directory, L("barfoo/buzfoo.html", "buzfoo.html")},
}
for _, test := range tests {
p, err := ReadFrom(strings.NewReader(test.content), test.path)
if err != nil {
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())
}
}
}
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 {
input string
section string
layout string
layout []string
}{
{path.Join("sub", "foobar.html"), "sub", "sub/single.html"},
{path.Join("content", "sub", "foobar.html"), "sub", "sub/single.html"},
{path.Join("content", "dub", "sub", "foobar.html"), "sub", "sub/single.html"},
{path.Join("sub", "foobar.html"), "sub", L("sub/single.html", "single.html")},
{path.Join("content", "foobar.html"), "", L("page/single.html", "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 {
p, err := ReadFrom(strings.NewReader(SIMPLE_PAGE_YAML), el.input)
p.guessSection()
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 {
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 {
t.Fatalf("Layout incorrect. Expected: '%s', Got: '%s'", el.layout, p.Layout())
if !listEqual(p.Layout(), el.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)")
}
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, " canonical => ")

View file

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

View file

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