shithub: hugo

Download patch

ref: 75c38071d8c61e2e1a56ae1949766b4144b68305
parent: f8bda16e154465c74a2cc42dd8149369e19f7833
author: Bjørn Erik Pedersen <[email protected]>
date: Tue Nov 1 18:39:24 EDT 2016

node to page: Create pages for nodes without content

Updates #2297

--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -25,6 +25,7 @@
 
 	"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 @@
 		return err
 	}
 
-	h.setupTranslations(firstSite)
+	h.setupTranslations()
 
 	if len(h.Sites) > 1 {
 		// Initialize the rest
@@ -236,6 +237,10 @@
 		}
 	}
 
+	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 @@
 	}
 
 	// Assign pages to sites per translation.
-	h.setupTranslations(firstSite)
+	h.setupTranslations()
 
 	if changed.source {
 		h.assembleGitInfo()
@@ -310,6 +315,10 @@
 		}
 	}
 
+	if err := h.createMissingNodes(); err != nil {
+		return err
+	}
+
 	if err := h.preRender(config, changed); err != nil {
 		return err
 	}
@@ -373,7 +382,147 @@
 	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() == "" {
--- a/hugolib/node.go
+++ b/hugolib/node.go
@@ -43,6 +43,25 @@
 	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 @@
 	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:
--- a/hugolib/node_as_page_test.go
+++ b/hugolib/node_as_page_test.go
@@ -49,6 +49,8 @@
 
 	testCommonResetState()
 
+	writeLayoutsForNodeAsPageTests(t)
+
 	writeSource(t, filepath.Join("content", "_node.md"), `---
 title: Home Sweet Home!
 ---
@@ -85,48 +87,6 @@
 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
 	for i := 1; i <= 4; i++ {
 		sect := "sect1"
@@ -212,4 +172,110 @@
 
 	// 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 }}
+`)
 }
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -470,7 +470,7 @@
 	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 @@
 	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 @@
 		// 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())
 		}
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -1606,13 +1606,25 @@
 }
 
 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)
 		}
 	}
 	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.
--- a/hugolib/site_render.go
+++ b/hugolib/site_render.go
@@ -65,7 +65,7 @@
 	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
 		}