diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go index 785fc847f..8332640df 100644 --- a/hugolib/hugo_sites.go +++ b/hugolib/hugo_sites.go @@ -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() == "" { diff --git a/hugolib/node.go b/hugolib/node.go index 13e124608..f01b4c822 100644 --- a/hugolib/node.go +++ b/hugolib/node.go @@ -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: diff --git a/hugolib/node_as_page_test.go b/hugolib/node_as_page_test.go index 090323cec..9c8506503 100644 --- a/hugolib/node_as_page_test.go +++ b/hugolib/node_as_page_test.go @@ -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 }} +`) +} diff --git a/hugolib/page.go b/hugolib/page.go index c8ce5ca4a..ec728d4c8 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -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()) } diff --git a/hugolib/site.go b/hugolib/site.go index 8eef37b0b..6de1daaa9 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -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 { diff --git a/hugolib/site_render.go b/hugolib/site_render.go index c91a78413..bad831d6c 100644 --- a/hugolib/site_render.go +++ b/hugolib/site_render.go @@ -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 }