node to page: Create pages for nodes without content
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 1 Nov 2016 21:39:24 +0000 (22:39 +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/node.go
hugolib/node_as_page_test.go
hugolib/page.go
hugolib/site.go
hugolib/site_render.go

index 785fc847ff853ef9b857d85fe88333c386a51401..8332640dfdaeb047c6bc9b3dc235397fff7e2f3c 100644 (file)
@@ -25,6 +25,7 @@ import (
 
        "github.com/spf13/viper"
 
+       "github.com/bep/inflect"
        "github.com/fsnotify/fsnotify"
        "github.com/spf13/hugo/source"
        "github.com/spf13/hugo/tpl"
@@ -218,7 +219,7 @@ func (h *HugoSites) Build(config BuildCfg) error {
                return err
        }
 
-       h.setupTranslations(firstSite)
+       h.setupTranslations()
 
        if len(h.Sites) > 1 {
                // Initialize the rest
@@ -236,6 +237,10 @@ func (h *HugoSites) Build(config BuildCfg) error {
                }
        }
 
+       if err := h.createMissingNodes(); err != nil {
+               return err
+       }
+
        if err := h.preRender(config, whatChanged{source: true, other: true}); err != nil {
                return err
        }
@@ -299,7 +304,7 @@ func (h *HugoSites) Rebuild(config BuildCfg, events ...fsnotify.Event) error {
        }
 
        // Assign pages to sites per translation.
-       h.setupTranslations(firstSite)
+       h.setupTranslations()
 
        if changed.source {
                h.assembleGitInfo()
@@ -310,6 +315,10 @@ func (h *HugoSites) Rebuild(config BuildCfg, events ...fsnotify.Event) error {
                }
        }
 
+       if err := h.createMissingNodes(); err != nil {
+               return err
+       }
+
        if err := h.preRender(config, changed); err != nil {
                return err
        }
@@ -373,7 +382,147 @@ func (h *HugoSites) render() error {
        return nil
 }
 
-func (h *HugoSites) setupTranslations(master *Site) {
+// createMissingNodes creates home page, taxonomies etc. that isnt't created as an
+// effect of having a content file.
+func (h *HugoSites) createMissingNodes() error {
+       // TODO(bep) np revisit this on languages -- as this is currently run after the page language distribution (due to taxonomies)
+       // TODO(bep) np re above, Pages vs.
+       // TODO(bep) np check node title etc.
+       s := h.Sites[0]
+
+       home := s.findPagesByNodeType(NodeHome)
+
+       // home page
+       if len(home) == 0 {
+               s.Pages = append(s.Pages, s.newHomePage())
+       }
+
+       // taxonomy list and terms pages
+       taxonomies := s.Language.GetStringMapString("taxonomies")
+       if len(taxonomies) > 0 {
+               taxonomyPages := s.findPagesByNodeType(NodeTaxonomy)
+               taxonomyTermsPages := s.findPagesByNodeType(NodeTaxonomyTerms)
+               for _, plural := range taxonomies {
+                       tax := s.Taxonomies[plural]
+                       foundTaxonomyPage := false
+                       foundTaxonomyTermsPage := false
+                       for key, _ := range tax {
+                               for _, p := range taxonomyPages {
+                                       if p.sections[0] == plural && p.sections[1] == key {
+                                               foundTaxonomyPage = true
+                                               break
+                                       }
+                               }
+                               for _, p := range taxonomyTermsPages {
+                                       if p.sections[0] == plural {
+                                               foundTaxonomyTermsPage = true
+                                               break
+                                       }
+                               }
+                               if !foundTaxonomyPage {
+                                       s.Pages = append(s.Pages, s.newTaxonomyPage(plural, key))
+                               }
+
+                               if !foundTaxonomyTermsPage {
+                                       s.Pages = append(s.Pages, s.newTaxonomyTermsPage(plural))
+                               }
+                       }
+
+               }
+       }
+
+       // sections
+       sectionPages := s.findPagesByNodeType(NodeSection)
+       if len(sectionPages) < len(s.Sections) {
+               for name, section := range s.Sections {
+                       foundSection := false
+                       for _, sectionPage := range sectionPages {
+                               if sectionPage.sections[0] == name {
+                                       foundSection = true
+                                       break
+                               }
+                       }
+                       if !foundSection {
+                               s.Pages = append(s.Pages, s.newSectionPage(name, section))
+                       }
+               }
+       }
+
+       return nil
+}
+
+// Move the new* methods after cleanup in site.go
+func (s *Site) newNodePage(typ NodeType) *Page {
+       n := Node{
+               NodeType: typ,
+               Data:     make(map[string]interface{}),
+               Site:     &s.Info,
+               language: s.Language,
+       }
+
+       return &Page{Node: n}
+}
+
+func (s *Site) newHomePage() *Page {
+       p := s.newNodePage(NodeHome)
+       p.Title = s.Info.Title
+       // TODO(bep) np check Data pages
+       // TODO(bep) np check setURLs
+       return p
+}
+
+func (s *Site) newTaxonomyPage(plural, key string) *Page {
+
+       p := s.newNodePage(NodeTaxonomy)
+
+       p.sections = []string{plural, key}
+
+       if s.Info.preserveTaxonomyNames {
+               key = s.Info.pathSpec.MakePathSanitized(key)
+       }
+
+       if s.Info.preserveTaxonomyNames {
+               // keep as is in the title
+               p.Title = key
+       } else {
+               p.Title = strings.Replace(strings.Title(key), "-", " ", -1)
+       }
+
+       // TODO(bep) np check set url
+
+       return p
+}
+
+func (s *Site) newSectionPage(name string, section WeightedPages) *Page {
+
+       p := s.newNodePage(NodeSection)
+       p.sections = []string{name}
+
+       sectionName := name
+       if !s.Info.preserveTaxonomyNames && len(section) > 0 {
+               sectionName = section[0].Page.Section()
+       }
+
+       sectionName = helpers.FirstUpper(sectionName)
+       if viper.GetBool("pluralizeListTitles") {
+               p.Title = inflect.Pluralize(sectionName)
+       } else {
+               p.Title = sectionName
+       }
+
+       return p
+}
+
+func (s *Site) newTaxonomyTermsPage(plural string) *Page {
+       p := s.newNodePage(NodeTaxonomyTerms)
+       p.sections = []string{plural}
+       p.Title = strings.Title(plural)
+       return p
+}
+
+func (h *HugoSites) setupTranslations() {
+
+       master := h.Sites[0]
 
        for _, p := range master.rawAllPages {
                if p.Lang() == "" {
index 13e124608b3aeef81104b8cc3f33d6fdce70a73f..f01b4c822d7f73c348fb9e5a29e5b7711e291e7a 100644 (file)
@@ -43,6 +43,25 @@ const (
        NodeTaxonomyTerms
 )
 
+func (p NodeType) String() string {
+       switch p {
+       case NodePage:
+               return "page"
+       case NodeHome:
+               return "home page"
+       case NodeSection:
+               return "section list"
+       case NodeTaxonomy:
+               return "taxonomy list"
+       case NodeTaxonomyTerms:
+               return "taxonomy terms"
+       case NodeUnknown:
+               return "unknown"
+       default:
+               return "invalid value"
+       }
+}
+
 func (p NodeType) IsNode() bool {
        return p >= NodeHome
 }
@@ -384,7 +403,7 @@ func (p *Page) setNodeTypeVars(s *Site) {
        case NodeHome:
                p.URLPath.URL = ""
        case NodeSection:
-               p.URLPath.URL = p.Section()
+               p.URLPath.URL = p.sections[0]
        case NodeTaxonomy:
                p.URLPath.URL = path.Join(p.sections...)
        case NodeTaxonomyTerms:
index 090323cec818efaca6e10e64a56577d63dd25d02..9c85065038331affc8a982af6701446b643b40ed 100644 (file)
@@ -49,6 +49,8 @@ func TestNodesAsPage(t *testing.T) {
 
        testCommonResetState()
 
+       writeLayoutsForNodeAsPageTests(t)
+
        writeSource(t, filepath.Join("content", "_node.md"), `---
 title: Home Sweet Home!
 ---
@@ -83,48 +85,6 @@ Taxonomy Web **Content!**
 title: Taxonomy Term Categories
 ---
 Taxonomy Term Categories **Content!**
-`)
-
-       writeSource(t, filepath.Join("layouts", "index.html"), `
-Index Title: {{ .Title }}
-Index Content: {{ .Content }}
-# Pages: {{ len .Data.Pages }}
-{{ range .Paginator.Pages }}
-       Pag: {{ .Title }}
-{{ end }}
-`)
-
-       writeSource(t, filepath.Join("layouts", "_default", "single.html"), `
-Single Title: {{ .Title }}
-Single Content: {{ .Content }}
-`)
-
-       writeSource(t, filepath.Join("layouts", "_default", "section.html"), `
-Section Title: {{ .Title }}
-Section Content: {{ .Content }}
-# Pages: {{ len .Data.Pages }}
-{{ range .Paginator.Pages }}
-       Pag: {{ .Title }}
-{{ end }}
-`)
-
-       // Taxonomy lists
-       writeSource(t, filepath.Join("layouts", "_default", "taxonomy.html"), `
-Taxonomy Title: {{ .Title }}
-Taxonomy Content: {{ .Content }}
-# Pages: {{ len .Data.Pages }}
-{{ range .Paginator.Pages }}
-       Pag: {{ .Title }}
-{{ end }}
-`)
-
-       // Taxonomy terms
-       writeSource(t, filepath.Join("layouts", "_default", "terms.html"), `
-Taxonomy Terms Title: {{ .Title }}
-Taxonomy Terms Content: {{ .Content }}
-{{ range $key, $value := .Data.Terms }}
-       k/v: {{ $key }} / {{ printf "%=v" $value }}
-{{ end }}
 `)
 
        // Add some regular pages
@@ -213,3 +173,109 @@ Content Page %02d
        // There are no pages to paginate over in the taxonomy terms.
 
 }
+
+func TestNodesWithNoContentFile(t *testing.T) {
+       //jww.SetStdoutThreshold(jww.LevelDebug)
+       jww.SetStdoutThreshold(jww.LevelFatal)
+
+       nodePageFeatureFlag = true
+       defer toggleNodePageFeatureFlag()
+
+       testCommonResetState()
+
+       writeLayoutsForNodeAsPageTests(t)
+
+       for i := 1; i <= 4; i++ {
+               sect := "sect1"
+               if i > 2 {
+                       sect = "sect2"
+               }
+               writeSource(t, filepath.Join("content", sect, fmt.Sprintf("regular%d.md", i)), fmt.Sprintf(`---
+title: Page %02d
+categories:  [
+        "Hugo",
+               "Web"
+]
+---
+Content Page %02d
+`, i, i))
+       }
+
+       viper.Set("paginate", 1)
+       viper.Set("title", "Hugo Rocks!")
+
+       s := newSiteDefaultLang()
+
+       if err := buildAndRenderSite(s); err != nil {
+               t.Fatalf("Failed to build site: %s", err)
+       }
+
+       // Home page
+       homePages := s.findPagesByNodeType(NodeHome)
+       require.Len(t, homePages, 1)
+
+       homePage := homePages[0]
+       require.Len(t, homePage.Data["Pages"], 4)
+
+       assertFileContent(t, filepath.Join("public", "index.html"), false,
+               "Index Title: Hugo Rocks!")
+
+       // Taxonomy list
+       assertFileContent(t, filepath.Join("public", "categories", "hugo", "index.html"), false,
+               "Taxonomy Title: Hugo")
+
+       // Taxonomy terms
+       assertFileContent(t, filepath.Join("public", "categories", "index.html"), false,
+               "Taxonomy Terms Title: Categories")
+
+       // Sections
+       assertFileContent(t, filepath.Join("public", "sect1", "index.html"), false,
+               "Section Title: Sect1s")
+       assertFileContent(t, filepath.Join("public", "sect2", "index.html"), false,
+               "Section Title: Sect2s")
+
+}
+
+func writeLayoutsForNodeAsPageTests(t *testing.T) {
+       writeSource(t, filepath.Join("layouts", "index.html"), `
+Index Title: {{ .Title }}
+Index Content: {{ .Content }}
+# Pages: {{ len .Data.Pages }}
+{{ range .Paginator.Pages }}
+       Pag: {{ .Title }}
+{{ end }}
+`)
+
+       writeSource(t, filepath.Join("layouts", "_default", "single.html"), `
+Single Title: {{ .Title }}
+Single Content: {{ .Content }}
+`)
+
+       writeSource(t, filepath.Join("layouts", "_default", "section.html"), `
+Section Title: {{ .Title }}
+Section Content: {{ .Content }}
+# Pages: {{ len .Data.Pages }}
+{{ range .Paginator.Pages }}
+       Pag: {{ .Title }}
+{{ end }}
+`)
+
+       // Taxonomy lists
+       writeSource(t, filepath.Join("layouts", "_default", "taxonomy.html"), `
+Taxonomy Title: {{ .Title }}
+Taxonomy Content: {{ .Content }}
+# Pages: {{ len .Data.Pages }}
+{{ range .Paginator.Pages }}
+       Pag: {{ .Title }}
+{{ end }}
+`)
+
+       // Taxonomy terms
+       writeSource(t, filepath.Join("layouts", "_default", "terms.html"), `
+Taxonomy Terms Title: {{ .Title }}
+Taxonomy Terms Content: {{ .Content }}
+{{ range $key, $value := .Data.Terms }}
+       k/v: {{ $key }} / {{ printf "%=v" $value }}
+{{ end }}
+`)
+}
index c8ce5ca4a893e658711ec6751cd29d293955941d..ec728d4c89cadde5550108ec4f95ada2a6938a95 100644 (file)
@@ -470,7 +470,7 @@ func (p *Page) layouts(l ...string) []string {
        case NodeHome:
                return []string{"index.html", "_default/list.html"}
        case NodeSection:
-               section := p.Section()
+               section := p.sections[0]
                return []string{"section/" + section + ".html", "_default/section.html", "_default/list.html", "indexes/" + section + ".html", "_default/indexes.html"}
        case NodeTaxonomy:
                singular := p.site.taxonomiesPluralSingular[p.sections[0]]
@@ -1167,7 +1167,7 @@ func (p *Page) TargetPath() (outfile string) {
        case NodeHome:
                return "index.html"
        case NodeSection:
-               return filepath.Join(p.Section(), "index.html")
+               return filepath.Join(p.sections[0], "index.html")
        case NodeTaxonomy:
                return filepath.Join(append(p.sections, "index.html")...)
        case NodeTaxonomyTerms:
@@ -1242,7 +1242,7 @@ func (p *Page) prepareData(s *Site) error {
                // TODO(bep) np cache the below
                p.Data["Pages"] = s.owner.findAllPagesByNodeType(NodePage)
        case NodeSection:
-               sectionData, ok := s.Sections[p.Section()]
+               sectionData, ok := s.Sections[p.sections[0]]
                if !ok {
                        return fmt.Errorf("Data for section %s not found", p.Section())
                }
index 8eef37b0bdc52127e94c0bff6f6e798ffa73f0b6..6de1daaa9a223146811dc7130031ef6ffd6fac3a 100644 (file)
@@ -1606,8 +1606,12 @@ func (s *Site) nodeTypeFromSections(sections []string) NodeType {
 }
 
 func (s *Site) findPagesByNodeType(n NodeType) Pages {
+       return s.findPagesByNodeTypeIn(n, s.Pages)
+}
+
+func (s *Site) findPagesByNodeTypeIn(n NodeType, inPages Pages) Pages {
        var pages Pages
-       for _, p := range s.Pages {
+       for _, p := range inPages {
                if p.NodeType == n {
                        pages = append(pages, p)
                }
@@ -1615,6 +1619,14 @@ func (s *Site) findPagesByNodeType(n NodeType) Pages {
        return pages
 }
 
+func (s *Site) findAllPagesByNodeType(n NodeType) Pages {
+       return s.findPagesByNodeTypeIn(n, s.rawAllPages)
+}
+
+func (s *Site) findRawAllPagesByNodeType(n NodeType) Pages {
+       return s.findPagesByNodeTypeIn(n, s.rawAllPages)
+}
+
 // renderAliases renders shell pages that simply have a redirect in the header.
 func (s *Site) renderAliases() error {
        for _, p := range s.Pages {
index c91a784133f160c83bac31ca2d5a7c48cb3ce1b7..bad831d6c9edd3e8019c06262e21bff90e6121f8 100644 (file)
@@ -65,7 +65,7 @@ func pageRenderer(s *Site, pages <-chan *Page, results chan<- error, wg *sync.Wa
        for p := range pages {
                targetPath := p.TargetPath()
                layouts := p.layouts()
-               jww.DEBUG.Printf("Render Page to %q with layouts %q", targetPath, layouts)
+               jww.DEBUG.Printf("Render %s to %q with layouts %q", p.NodeType, targetPath, layouts)
                if err := s.renderAndWritePage("page "+p.FullFilePath(), targetPath, p, s.appendThemeTemplates(layouts)...); err != nil {
                        results <- err
                }