node to page: Handle aliases, 404, robots.txt, sitemap
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 8 Nov 2016 22:34:52 +0000 (23:34 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 22 Nov 2016 08:57:03 +0000 (09:57 +0100)
Updates #2297

hugolib/hugo_sites.go
hugolib/menu.go
hugolib/menu_test.go
hugolib/node.go
hugolib/node_as_page_test.go
hugolib/page.go
hugolib/site.go
hugolib/site_render.go
hugolib/site_test.go

index 3614f10a81bd5d0cc7d302e390c76d5140e6602f..fecaaffd9a167a57bb8f67e8631a723cf7dfd7d3 100644 (file)
@@ -37,11 +37,7 @@ import (
 // Temporary feature flag to ease the refactoring of node vs page, see
 // https://github.com/spf13/hugo/issues/2297
 // TODO(bep) eventually remove
-var nodePageFeatureFlag bool
-
-func toggleNodePageFeatureFlag() {
-       nodePageFeatureFlag = !nodePageFeatureFlag
-}
+var nodePageFeatureFlag bool = true
 
 // HugoSites represents the sites to build. Each site represents a language.
 type HugoSites struct {
@@ -516,13 +512,15 @@ func (h *HugoSites) createMissingNodes() error {
 
 // Move the new* methods after cleanup in site.go
 func (s *Site) newNodePage(typ NodeType) *Page {
-
-       return &Page{Node: Node{
-               NodeType: typ,
-               Data:     make(map[string]interface{}),
-               Site:     &s.Info,
-               language: s.Language,
-       }, site: s}
+       return &Page{
+               Node: Node{
+                       Date:     s.Info.LastChange,
+                       Lastmod:  s.Info.LastChange,
+                       NodeType: typ,
+                       Data:     make(map[string]interface{}),
+                       Site:     &s.Info,
+                       language: s.Language,
+               }, site: s}
 }
 
 func (s *Site) newHomePage() *Page {
index 116545a9a9ed8d1152b581c8a08c2180653f62cb..35991b1c7ef6ab9d292bd587654fecf6c3224368 100644 (file)
@@ -21,6 +21,8 @@ import (
        "github.com/spf13/cast"
 )
 
+// TODO(bep) np menu entries in section content etc.?
+
 // MenuEntry represents a menu item defined in either Page front matter
 // or in the site config.
 type MenuEntry struct {
index 8640d63e09cd74d75000236704fb780a123a0faf..cc408cade40f8985fc3ad7bea2b2323e877e802a 100644 (file)
@@ -477,10 +477,10 @@ func TestTaxonomyNodeMenu(t *testing.T) {
                        &MenuEntry{Name: "Somewhere else", URL: "/somewhereelse"}, false, false},
        } {
 
-               n, _ := s.newTaxonomyNode(true, this.taxInfo, i)
+               p := s.newTaxonomyPage(this.taxInfo.plural, this.taxInfo.key)
 
-               isMenuCurrent := n.IsMenuCurrent(this.menu, this.menuItem)
-               hasMenuCurrent := n.HasMenuCurrent(this.menu, this.menuItem)
+               isMenuCurrent := p.IsMenuCurrent(this.menu, this.menuItem)
+               hasMenuCurrent := p.HasMenuCurrent(this.menu, this.menuItem)
 
                if isMenuCurrent != this.isMenuCurrent {
                        t.Errorf("[%d] Wrong result from IsMenuCurrent: %v", i, isMenuCurrent)
index c9c7ac31ed33a9da5cdc88d73382710577478012..5fa345030a5fdeeec3783b98c5d0752907dd27b8 100644 (file)
@@ -41,6 +41,12 @@ const (
        NodeSection
        NodeTaxonomy
        NodeTaxonomyTerms
+
+       // The following are (currently) temporary nodes,
+       // i.e. nodes we create just to render in isolation.
+       NodeSitemap
+       NodeRobotsTXT
+       Node404
 )
 
 func (p NodeType) String() string {
@@ -55,6 +61,12 @@ func (p NodeType) String() string {
                return "taxonomy list"
        case NodeTaxonomyTerms:
                return "taxonomy terms"
+       case NodeSitemap:
+               return "sitemap"
+       case NodeRobotsTXT:
+               return "robots.txt"
+       case Node404:
+               return "404 Not Found"
        case NodeUnknown:
                return "unknown"
        default:
index 3a97d318ac1d84e3587a1229d80d4934aa2e318e..cde29e9d6ac809339d44804c5a52a648d925804e 100644 (file)
@@ -34,9 +34,6 @@ func TestNodesAsPage(t *testing.T) {
        //jww.SetStdoutThreshold(jww.LevelDebug)
        jww.SetStdoutThreshold(jww.LevelFatal)
 
-       nodePageFeatureFlag = true
-       defer toggleNodePageFeatureFlag()
-
        /* Will have to decide what to name the node content files, but:
 
                Home page should have:
@@ -152,9 +149,6 @@ func TestNodesWithNoContentFile(t *testing.T) {
        //jww.SetStdoutThreshold(jww.LevelDebug)
        jww.SetStdoutThreshold(jww.LevelFatal)
 
-       nodePageFeatureFlag = true
-       defer toggleNodePageFeatureFlag()
-
        testCommonResetState()
 
        writeLayoutsForNodeAsPageTests(t)
@@ -220,9 +214,6 @@ Content Page %02d
 
 func TestNodesAsPageMultilingual(t *testing.T) {
 
-       nodePageFeatureFlag = true
-       defer toggleNodePageFeatureFlag()
-
        testCommonResetState()
 
        writeLayoutsForNodeAsPageTests(t)
index 31acc9524a46f650b16f1f9e70b2f4e5e7c73c6b..8ebaeff05cc6908b632656ccdca5c320261e5ff1 100644 (file)
@@ -1202,13 +1202,13 @@ func (p *Page) TargetPath() (outfile string) {
        // TODO(bep) np
        switch p.NodeType {
        case NodeHome:
-               return p.addLangFilepathPrefix("index.html")
+               return p.addLangFilepathPrefix("/")
        case NodeSection:
-               return p.addLangFilepathPrefix(filepath.Join(p.sections[0], "index.html"))
+               return p.addLangFilepathPrefix(p.sections[0])
        case NodeTaxonomy:
-               return p.addLangFilepathPrefix(filepath.Join(append(p.sections, "index.html")...))
+               return p.addLangFilepathPrefix(filepath.Join(p.sections...))
        case NodeTaxonomyTerms:
-               return p.addLangFilepathPrefix(filepath.Join(append(p.sections, "index.html")...))
+               return p.addLangFilepathPrefix(filepath.Join(p.sections...))
        }
 
        // Always use URL if it's specified
index e3c54ece33299ed262cfcce9558e345e4a9172a4..51193fad80672eb6757c18ea9d384f3abf500829 100644 (file)
@@ -838,36 +838,21 @@ func (s *Site) render() (err error) {
                return
        }
 
-       if err = s.renderAliases(); err != nil {
-               return
-       }
-       s.timerStep("render and write aliases")
-       if err = s.renderTaxonomiesLists(false); err != nil {
-               return
-       }
-       s.timerStep("render and write taxonomies")
-       if err = s.renderListsOfTaxonomyTerms(false); err != nil {
-               return
-       }
-       s.timerStep("render & write taxonomy lists")
-       if err = s.renderSectionLists(false); err != nil {
-               return
-       }
-       s.timerStep("render and write lists")
-
        if err = s.preparePages(); err != nil {
                return
        }
+       s.timerStep("prepare pages")
 
        if err = s.renderPages(); err != nil {
                return
        }
-
        s.timerStep("render and write pages")
-       if err = s.renderHomePage(false); err != nil {
+
+       if err = s.renderAliases(); err != nil {
                return
        }
-       s.timerStep("render and write homepage")
+       s.timerStep("render and write aliases")
+
        if err = s.renderSitemap(); err != nil {
                return
        }
@@ -878,6 +863,11 @@ func (s *Site) render() (err error) {
        }
        s.timerStep("render and write robots.txt")
 
+       if err = s.render404(); err != nil {
+               return
+       }
+       s.timerStep("render and write 404")
+
        return
 }
 
@@ -1601,44 +1591,6 @@ func (s *Site) nodeTypeFromSections(sections []string) NodeType {
        return NodeSection
 }
 
-// renderAliases renders shell pages that simply have a redirect in the header.
-func (s *Site) renderAliases() error {
-       for _, p := range s.Pages {
-               if len(p.Aliases) == 0 {
-                       continue
-               }
-
-               plink, err := p.Permalink()
-               if err != nil {
-                       return err
-               }
-               for _, a := range p.Aliases {
-                       if err := s.writeDestAlias(a, plink, p); err != nil {
-                               return err
-                       }
-               }
-       }
-
-       if s.owner.multilingual.enabled() {
-               mainLang := s.owner.multilingual.DefaultLang.Lang
-               if s.Info.defaultContentLanguageInSubdir {
-                       mainLangURL := s.Info.pathSpec.AbsURL(mainLang, false)
-                       jww.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
-                       if err := s.publishDestAlias(s.languageAliasTarget(), "/", mainLangURL, nil); err != nil {
-                               return err
-                       }
-               } else {
-                       mainLangURL := s.Info.pathSpec.AbsURL("", false)
-                       jww.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
-                       if err := s.publishDestAlias(s.languageAliasTarget(), mainLang, mainLangURL, nil); err != nil {
-                               return err
-                       }
-               }
-       }
-
-       return nil
-}
-
 func (s *Site) preparePages() error {
        var errors []error
 
@@ -2085,21 +2037,8 @@ func (s *Site) renderHomePage(prepare bool) error {
                }
        }
 
-       if viper.GetBool("disable404") {
-               return nil
-       }
-
-       node404 := s.newNode("404")
-       node404.Title = "404 Page not found"
-       node404.Data["Pages"] = s.Pages
-       s.setURLs(node404, "404.html")
-
-       nfLayouts := []string{"404.html"}
-       if nfErr := s.renderAndWritePage("404 page", "404.html", node404, s.appendThemeTemplates(nfLayouts)...); nfErr != nil {
-               return nfErr
-       }
-
        return nil
+
 }
 
 func (s *Site) newHomeNode(prepare bool, counter int) *Node {
@@ -2115,80 +2054,6 @@ func (s *Site) newHomeNode(prepare bool, counter int) *Node {
        return n
 }
 
-func (s *Site) newPage() *Page {
-       page := &Page{}
-       page.language = s.Language
-       page.Date = s.Info.LastChange
-       page.Lastmod = s.Info.LastChange
-       page.Site = &s.Info
-       return page
-}
-
-func (s *Site) renderSitemap() error {
-       if viper.GetBool("disableSitemap") {
-               return nil
-       }
-
-       sitemapDefault := parseSitemap(viper.GetStringMap("sitemap"))
-
-       n := s.newNode("sitemap")
-
-       // Prepend homepage to the list of pages
-       pages := make(Pages, 0)
-
-       page := s.newPage()
-       page.URLPath.URL = ""
-       page.Sitemap.ChangeFreq = sitemapDefault.ChangeFreq
-       page.Sitemap.Priority = sitemapDefault.Priority
-
-       pages = append(pages, page)
-       pages = append(pages, s.Pages...)
-
-       n.Data["Pages"] = pages
-
-       for _, page := range pages {
-               if page.Sitemap.ChangeFreq == "" {
-                       page.Sitemap.ChangeFreq = sitemapDefault.ChangeFreq
-               }
-
-               if page.Sitemap.Priority == -1 {
-                       page.Sitemap.Priority = sitemapDefault.Priority
-               }
-
-               if page.Sitemap.Filename == "" {
-                       page.Sitemap.Filename = sitemapDefault.Filename
-               }
-       }
-
-       smLayouts := []string{"sitemap.xml", "_default/sitemap.xml", "_internal/_default/sitemap.xml"}
-       addLanguagePrefix := n.Site.IsMultiLingual()
-       if err := s.renderAndWriteXML("sitemap", n.addLangPathPrefixIfFlagSet(page.Sitemap.Filename, addLanguagePrefix), n, s.appendThemeTemplates(smLayouts)...); err != nil {
-               return err
-       }
-
-       return nil
-}
-
-func (s *Site) renderRobotsTXT() error {
-       if !viper.GetBool("enableRobotsTXT") {
-               return nil
-       }
-
-       n := s.newNode("robots")
-       n.Data["Pages"] = s.Pages
-
-       rLayouts := []string{"robots.txt", "_default/robots.txt", "_internal/_default/robots.txt"}
-       outBuffer := bp.GetBuffer()
-       defer bp.PutBuffer(outBuffer)
-       err := s.renderForLayouts("robots", n, outBuffer, s.appendThemeTemplates(rLayouts)...)
-
-       if err == nil {
-               err = s.writeDestFile("robots.txt", outBuffer)
-       }
-
-       return err
-}
-
 // Stats prints Hugo builds stats to the console.
 // This is what you see after a successful hugo build.
 func (s *Site) Stats() {
@@ -2223,15 +2088,19 @@ func (s *SiteInfo) permalinkStr(plink string) string {
                s.pathSpec.URLizeAndPrep(plink)).String()
 }
 
+// TODO(bep) np remove
 func (s *Site) newNode(nodeID string) *Node {
-       return s.nodeLookup(nodeID, 0, true)
+       return nil //s.nodeLookup(nodeID, 0, true)
 }
 
 func (s *Site) getNode(nodeID string) *Node {
-       return s.getOrAddNode(nodeID, false)
+       return nil //s.getOrAddNode(nodeID, false)
 }
 
 func (s *Site) getOrAddNode(nodeID string, add bool) *Node {
+       if true {
+               return nil
+       }
        s.nodeCacheInit.Do(func() {
                s.nodeCache = &nodeCache{m: make(map[string]*Node)}
        })
@@ -2342,7 +2211,8 @@ func (s *Site) renderAndWritePage(name string, dest string, d interface{}, layou
 
        var pageTarget target.Output
 
-       if p, ok := d.(*Page); ok && path.Ext(p.URLPath.URL) != "" {
+       // TODO(bep) np ugly urls vs frontmatter
+       if p, ok := d.(*Page); ok && p.IsPage() && path.Ext(p.URLPath.URL) != "" {
                // user has explicitly set a URL with extension for this page
                // make sure it sticks even if "ugly URLs" are turned off.
                pageTarget = s.pageUglyTarget()
@@ -2361,7 +2231,7 @@ func (s *Site) renderAndWritePage(name string, dest string, d interface{}, layou
        }
 
        // For performance reasons we only inject the Hugo generator tag on the home page.
-       if n, ok := d.(*Node); ok && n.IsHome() {
+       if n, ok := d.(*Page); ok && n.IsHome() {
                if !viper.GetBool("disableHugoGeneratorInject") {
                        transformLinks = append(transformLinks, transform.HugoGeneratorInject)
                }
index bc2bd2ecb8c290fa03014bb0a1647a4cb5792ce8..4bc4a5e70e6a98b338d3c3647ab17940e14ae764 100644 (file)
@@ -19,7 +19,9 @@ import (
        "path/filepath"
        "sync"
 
+       bp "github.com/spf13/hugo/bufferpool"
        "github.com/spf13/hugo/helpers"
+       "github.com/spf13/viper"
 
        jww "github.com/spf13/jwalterweatherman"
 )
@@ -152,3 +154,125 @@ func (s *Site) renderRSS(p *Page) error {
 
        return nil
 }
+
+func (s *Site) render404() error {
+       if viper.GetBool("disable404") {
+               return nil
+       }
+
+       p := s.newNodePage(Node404)
+       p.Title = "404 Page not found"
+       p.Data["Pages"] = s.Pages
+       s.setPageURLs(p, "404.html")
+
+       nfLayouts := []string{"404.html"}
+       if nfErr := s.renderAndWritePage("404 page", "404.html", p, s.appendThemeTemplates(nfLayouts)...); nfErr != nil {
+               return nfErr
+       }
+
+       return nil
+}
+
+func (s *Site) renderSitemap() error {
+       if viper.GetBool("disableSitemap") {
+               return nil
+       }
+
+       sitemapDefault := parseSitemap(viper.GetStringMap("sitemap"))
+
+       n := s.newNodePage(NodeSitemap)
+
+       // Prepend homepage to the list of pages
+       pages := make(Pages, 0)
+
+       page := s.newNodePage(NodeSitemap)
+       page.URLPath.URL = ""
+       page.Sitemap.ChangeFreq = sitemapDefault.ChangeFreq
+       page.Sitemap.Priority = sitemapDefault.Priority
+
+       pages = append(pages, page)
+       pages = append(pages, s.Pages...)
+
+       n.Data["Pages"] = pages
+
+       for _, page := range pages {
+               if page.Sitemap.ChangeFreq == "" {
+                       page.Sitemap.ChangeFreq = sitemapDefault.ChangeFreq
+               }
+
+               if page.Sitemap.Priority == -1 {
+                       page.Sitemap.Priority = sitemapDefault.Priority
+               }
+
+               if page.Sitemap.Filename == "" {
+                       page.Sitemap.Filename = sitemapDefault.Filename
+               }
+       }
+
+       smLayouts := []string{"sitemap.xml", "_default/sitemap.xml", "_internal/_default/sitemap.xml"}
+       addLanguagePrefix := n.Site.IsMultiLingual()
+       if err := s.renderAndWriteXML("sitemap", n.addLangPathPrefixIfFlagSet(page.Sitemap.Filename, addLanguagePrefix), n, s.appendThemeTemplates(smLayouts)...); err != nil {
+               return err
+       }
+
+       return nil
+}
+
+func (s *Site) renderRobotsTXT() error {
+       if !viper.GetBool("enableRobotsTXT") {
+               return nil
+       }
+
+       n := s.newNodePage(NodeRobotsTXT)
+       n.Data["Pages"] = s.Pages
+
+       rLayouts := []string{"robots.txt", "_default/robots.txt", "_internal/_default/robots.txt"}
+       outBuffer := bp.GetBuffer()
+       defer bp.PutBuffer(outBuffer)
+       err := s.renderForLayouts("robots", n, outBuffer, s.appendThemeTemplates(rLayouts)...)
+
+       if err == nil {
+               err = s.writeDestFile("robots.txt", outBuffer)
+       }
+
+       return err
+}
+
+// renderAliases renders shell pages that simply have a redirect in the header.
+// TODO(bep) np aliases of node types
+func (s *Site) renderAliases() error {
+       for _, p := range s.Pages {
+               if len(p.Aliases) == 0 {
+                       continue
+               }
+
+               plink, err := p.Permalink()
+               if err != nil {
+                       return err
+               }
+               for _, a := range p.Aliases {
+                       if err := s.writeDestAlias(a, plink, p); err != nil {
+                               return err
+                       }
+               }
+       }
+
+       if s.owner.multilingual.enabled() {
+               mainLang := s.owner.multilingual.DefaultLang.Lang
+               if s.Info.defaultContentLanguageInSubdir {
+                       mainLangURL := s.Info.pathSpec.AbsURL(mainLang, false)
+                       jww.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
+                       if err := s.publishDestAlias(s.languageAliasTarget(), "/", mainLangURL, nil); err != nil {
+                               return err
+                       }
+               } else {
+                       mainLangURL := s.Info.pathSpec.AbsURL("", false)
+                       jww.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
+                       if err := s.publishDestAlias(s.languageAliasTarget(), mainLang, mainLangURL, nil); err != nil {
+                               return err
+                       }
+               }
+       }
+
+       return nil
+}
index b71548c46c2ccaa96b676d8d35eb0acf40641069..500e1b65c2007e59350fcc825c415f5e22a364f2 100644 (file)
@@ -393,6 +393,7 @@ func doTestShouldAlwaysHaveUglyURLs(t *testing.T, uglyURLs bool) {
 
 // Issue #1176
 func TestSectionNaming(t *testing.T) {
+       //jww.SetStdoutThreshold(jww.LevelDebug)
 
        for _, canonify := range []bool{true, false} {
                for _, uglify := range []bool{true, false} {
@@ -404,7 +405,6 @@ func TestSectionNaming(t *testing.T) {
 }
 
 func doTestSectionNaming(t *testing.T, canonify, uglify, pluralize bool) {
-       hugofs.InitMemFs()
        testCommonResetState()
 
        viper.Set("baseURL", "http://auth/sub/")
@@ -427,12 +427,12 @@ func doTestSectionNaming(t *testing.T, canonify, uglify, pluralize bool) {
                {Name: filepath.FromSlash("ラーメン/doc3.html"), Content: []byte("doc3")},
        }
 
-       s := &Site{
-               Source:   &source.InMemorySource{ByteSource: sources},
-               targets:  targetList{page: &target.PagePub{UglyURLs: uglify}},
-               Language: helpers.NewDefaultLanguage(),
+       for _, source := range sources {
+               writeSource(t, filepath.Join("content", source.Name), string(source.Content))
        }
 
+       s := newSiteDefaultLang()
+
        if err := buildAndRenderSite(s,
                "_default/single.html", "{{.Content}}",
                "_default/list.html", "{{ .Title }}"); err != nil {
@@ -453,20 +453,12 @@ func doTestSectionNaming(t *testing.T, canonify, uglify, pluralize bool) {
        }
 
        for _, test := range tests {
-               file, err := hugofs.Destination().Open(test.doc)
-               if err != nil {
-                       t.Fatalf("Did not find %s in target: %s", test.doc, err)
-               }
-
-               content := helpers.ReaderToString(file)
 
                if test.pluralAware && pluralize {
                        test.expected = inflect.Pluralize(test.expected)
                }
 
-               if content != test.expected {
-                       t.Errorf("%s content expected:\n%q\ngot:\n%q", test.doc, test.expected, content)
-               }
+               assertFileContent(t, filepath.Join("public", test.doc), true, test.expected)
        }
 
 }