Refactor layout selection code
authorNoah Campbell <noahcampbell@gmail.com>
Mon, 7 Oct 2013 04:57:45 +0000 (07:57 +0300)
committerNoah Campbell <noahcampbell@gmail.com>
Tue, 8 Oct 2013 16:44:15 +0000 (18:44 +0200)
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.

docs/content/layout/templates.md
docs/layouts/chrome/menu.html
hugolib/node.go
hugolib/page.go
hugolib/page_permalink_test.go [new file with mode: 0644]
hugolib/page_test.go
hugolib/path_seperators_test.go
hugolib/planner.go
hugolib/site.go
hugolib/site_test.go

index 833546fccde8778ff6ee76a41409fcdd81b8c90c..8f21aada3ca8072e2c5b6a661abc1d07a3491f0a 100644 (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)
index 40b3b66934aeef53049c724f15f50ceaa21c22d9..89f1c841409c244f0beeb8c99c17974617da2424 100644 (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>
index 91e5f92b8a7da84e7d7fb90c52fcd433676b0504..c0779261c60d88f860bd277e00c7e65144404b19 100644 (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
index 44f10921476947801a63ce8f6a5d41c2d7791f04..dcfa841ca10eb4334863dd02257880ba715952b4 100644 (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
 }
 
diff --git a/hugolib/page_permalink_test.go b/hugolib/page_permalink_test.go
new file mode 100644 (file)
index 0000000..d318505
--- /dev/null
@@ -0,0 +1,9 @@
+package hugolib
+
+import (
+       "testing"
+)
+
+func TestPermalink(t *testing.T) {
+
+}
index 33da04e5de7cc34d7adcf04cdca105ef37c973c5..4832cc4f39d40568992e37e8d537c6a5fa61dcd0 100644 (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
+}
index 1c7f7eb10e85993124855d4eb36ee85bd9a1c907..50a9eedaec2dabb00c8575c9c6b764bdc2dc9ea5 100644 (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())
                }
        }
 }
index 637c27b10404e33f27826418ee4fbe8fc888610d..246312746d350250f8e3e070c5de48cceab2ab94 100644 (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 => ")
index 782bafbb7869616c6f0e438b2ba11f0f422868ea..e5d4fab4427fa4f0a89f7db479cc0c8b4076457a 100644 (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},
index dbc738b5cd6ffc45c7893c750a457f525483e31c..7fb562f92f02998578d3ed65fb42732806e46f0d 100644 (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()