node to page: Basic outline

Updates #2297
This commit is contained in:
Bjørn Erik Pedersen 2016-10-30 17:59:24 +01:00
parent e7d0bc8a74
commit e371ac0b6f
8 changed files with 185 additions and 17 deletions

View file

@ -31,6 +31,15 @@ import (
jww "github.com/spf13/jwalterweatherman" jww "github.com/spf13/jwalterweatherman"
) )
// 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
}
// HugoSites represents the sites to build. Each site represents a language. // HugoSites represents the sites to build. Each site represents a language.
type HugoSites struct { type HugoSites struct {
Sites []*Site Sites []*Site
@ -563,6 +572,16 @@ func (s *Site) updateBuildStats(page *Page) {
} }
} }
func (h *HugoSites) findPagesByNodeType(n NodeType) Pages {
var pages Pages
for _, p := range h.Sites[0].AllPages {
if p.NodeType == n {
pages = append(pages, p)
}
}
return pages
}
// Convenience func used in tests to build a single site/language excluding render phase. // Convenience func used in tests to build a single site/language excluding render phase.
func buildSiteSkipRender(s *Site, additionalTemplates ...string) error { func buildSiteSkipRender(s *Site, additionalTemplates ...string) error {
return doBuildSite(s, false, additionalTemplates...) return doBuildSite(s, false, additionalTemplates...)

View file

@ -26,7 +26,23 @@ import (
"github.com/spf13/hugo/helpers" "github.com/spf13/hugo/helpers"
) )
// TODO(bep) np add String()
type NodeType int
const (
NodePage NodeType = iota
// The rest are node types; home page, sections etc.
NodeHome
)
func (p NodeType) IsNode() bool {
return p >= NodeHome
}
type Node struct { type Node struct {
NodeType NodeType
// a natural key that should be unique for this site // a natural key that should be unique for this site
// for the home page this will typically be "home", but it can anything // for the home page this will typically be "home", but it can anything
// as long as it is the same for repeated builds. // as long as it is the same for repeated builds.
@ -44,7 +60,6 @@ type Node struct {
Lastmod time.Time Lastmod time.Time
Sitemap Sitemap Sitemap Sitemap
URLPath URLPath
IsHome bool
paginator *Pager paginator *Pager
paginatorInit sync.Once paginatorInit sync.Once
scratch *Scratch scratch *Scratch
@ -151,11 +166,15 @@ func (n *Node) RSSlink() template.HTML {
} }
func (n *Node) IsNode() bool { func (n *Node) IsNode() bool {
return true return n.NodeType.IsNode()
}
func (n *Node) IsHome() bool {
return n.NodeType == NodeHome
} }
func (n *Node) IsPage() bool { func (n *Node) IsPage() bool {
return !n.IsNode() return n.NodeType == NodePage
} }
func (n *Node) Ref(ref string) (string, error) { func (n *Node) Ref(ref string) (string, error) {
@ -316,3 +335,11 @@ func (n *Node) addLangFilepathPrefix(outfile string) string {
} }
return helpers.FilePathSeparator + filepath.Join(n.Lang(), outfile) return helpers.FilePathSeparator + filepath.Join(n.Lang(), outfile)
} }
func nodeTypeFromFilename(filename string) NodeType {
// TODO(bep) np
if !strings.HasPrefix(filename, "_node") {
return NodePage
}
return NodeHome
}

View file

@ -0,0 +1,103 @@
// Copyright 2016 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hugolib
import (
"fmt"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
/*
This file will test the "making everything a page" transition.
See https://github.com/spf13/hugo/issues/2297
*/
func TestHomeAsPage(t *testing.T) {
nodePageFeatureFlag = true
defer toggleNodePageFeatureFlag()
/* Will have to decide what to name the node content files, but:
Home page should have:
Content, shortcode support
Metadata (title, dates etc.)
Params
Taxonomies (categories, tags)
*/
testCommonResetState()
writeSource(t, filepath.Join("content", "_node.md"), `---
title: Home Sweet Home!
---
Home **Content!**
`)
writeSource(t, filepath.Join("layouts", "index.html"), `
Index Title: {{ .Title }}
Index Content: {{ .Content }}
# Pages: {{ len .Data.Pages }}
`)
writeSource(t, filepath.Join("layouts", "_default", "single.html"), `
Single Title: {{ .Title }}
Single Content: {{ .Content }}
`)
// Add some regular pages
for i := 0; i < 10; i++ {
writeSource(t, filepath.Join("content", fmt.Sprintf("regular%d.md", i)), fmt.Sprintf(`---
title: Page %d
---
Content Page %d
`, i, i))
}
s := newSiteDefaultLang()
if err := buildAndRenderSite(s); err != nil {
t.Fatalf("Failed to build site: %s", err)
}
assertFileContent(t, filepath.Join("public", "index.html"), false,
"Index Title: Home Sweet Home!",
"Home <strong>Content!</strong>",
"# Pages: 10")
assertFileContent(t, filepath.Join("public", "regular1", "index.html"), false, "Single Title: Page 1", "Content Page 1")
h := s.owner
nodes := h.findPagesByNodeType(NodeHome)
require.Len(t, nodes, 1)
home := nodes[0]
require.True(t, home.IsHome())
require.True(t, home.IsNode())
require.False(t, home.IsPage())
pages := h.findPagesByNodeType(NodePage)
require.Len(t, pages, 10)
first := pages[0]
require.False(t, first.IsHome())
require.False(t, first.IsNode())
require.True(t, first.IsPage())
}

View file

@ -30,7 +30,7 @@ func TestNodeSimpleMethods(t *testing.T) {
{func(n *Node) bool { return n.Now().Unix() == time.Now().Unix() }}, {func(n *Node) bool { return n.Now().Unix() == time.Now().Unix() }},
} { } {
n := &Node{} n := &Node{NodeType: NodeHome}
n.RSSLink = "rssLink" n.RSSLink = "rssLink"
if !this.assertFunc(n) { if !this.assertFunc(n) {

View file

@ -182,14 +182,6 @@ func (p *Page) initPlainWords() {
}) })
} }
func (p *Page) IsNode() bool {
return false
}
func (p *Page) IsPage() bool {
return true
}
// Param is a convenience method to do lookups in Page's and Site's Params map, // Param is a convenience method to do lookups in Page's and Site's Params map,
// in that order. // in that order.
// //
@ -423,7 +415,7 @@ func (p *Page) getRenderingConfig() *helpers.Blackfriday {
func newPage(filename string) *Page { func newPage(filename string) *Page {
page := Page{contentType: "", page := Page{contentType: "",
Source: Source{File: *source.NewFile(filename)}, Source: Source{File: *source.NewFile(filename)},
Node: Node{Keywords: []string{}, Sitemap: Sitemap{Priority: -1}}, Node: Node{NodeType: nodeTypeFromFilename(filename), Keywords: []string{}, Sitemap: Sitemap{Priority: -1}},
Params: make(map[string]interface{}), Params: make(map[string]interface{}),
translations: make(Pages, 0), translations: make(Pages, 0),
} }
@ -457,6 +449,11 @@ func (p *Page) layouts(l ...string) []string {
return p.layoutsCalculated return p.layoutsCalculated
} }
// TODO(bep) np
if p.NodeType == NodeHome {
return []string{"index.html", "_default/list.html"}
}
if p.Layout != "" { if p.Layout != "" {
return layouts(p.Type(), p.Layout) return layouts(p.Type(), p.Layout)
} }
@ -1136,6 +1133,10 @@ func (p *Page) FullFilePath() string {
} }
func (p *Page) TargetPath() (outfile string) { func (p *Page) TargetPath() (outfile string) {
// TODO(bep) ml
if p.NodeType == NodeHome {
return "index.html"
}
// Always use URL if it's specified // Always use URL if it's specified
if len(strings.TrimSpace(p.URLPath.URL)) > 2 { if len(strings.TrimSpace(p.URLPath.URL)) > 2 {
outfile = strings.TrimSpace(p.URLPath.URL) outfile = strings.TrimSpace(p.URLPath.URL)

View file

@ -645,7 +645,7 @@ func TestCreateNewPage(t *testing.T) {
// issue #2290: Path is relative to the content dir and will continue to be so. // issue #2290: Path is relative to the content dir and will continue to be so.
require.Equal(t, filepath.FromSlash(fmt.Sprintf("p0.%s", ext)), p.Path()) require.Equal(t, filepath.FromSlash(fmt.Sprintf("p0.%s", ext)), p.Path())
assert.False(t, p.IsHome) assert.False(t, p.IsHome())
checkPageTitle(t, p, "Simple") checkPageTitle(t, p, "Simple")
checkPageContent(t, p, normalizeExpected(ext, "<p>Simple Page</p>\n")) checkPageContent(t, p, normalizeExpected(ext, "<p>Simple Page</p>\n"))
checkPageSummary(t, p, "Simple Page") checkPageSummary(t, p, "Simple Page")

View file

@ -1678,6 +1678,8 @@ func (s *Site) renderPages() error {
func pageRenderer(s *Site, pages <-chan *Page, results chan<- error, wg *sync.WaitGroup) { func pageRenderer(s *Site, pages <-chan *Page, results chan<- error, wg *sync.WaitGroup) {
defer wg.Done() defer wg.Done()
for p := range pages { for p := range pages {
// TODO(bep) np paginator
s.preparePage(p)
err := s.renderAndWritePage("page "+p.FullFilePath(), p.TargetPath(), p, s.appendThemeTemplates(p.layouts())...) err := s.renderAndWritePage("page "+p.FullFilePath(), p.TargetPath(), p, s.appendThemeTemplates(p.layouts())...)
if err != nil { if err != nil {
results <- err results <- err
@ -1685,6 +1687,17 @@ func pageRenderer(s *Site, pages <-chan *Page, results chan<- error, wg *sync.Wa
} }
} }
func (s *Site) preparePage(p *Page) {
// TODO(bep) np the order of it all
switch p.NodeType {
case NodePage:
case NodeHome:
p.Data = make(map[string]interface{})
// TODO(bep) np cache the below
p.Data["Pages"] = s.owner.findPagesByNodeType(NodePage)
}
}
func errorCollator(results <-chan error, errs chan<- error) { func errorCollator(results <-chan error, errs chan<- error) {
errMsgs := []string{} errMsgs := []string{}
for err := range results { for err := range results {
@ -2028,6 +2041,11 @@ func (s *Site) renderSectionLists(prepare bool) error {
} }
func (s *Site) renderHomePage(prepare bool) error { func (s *Site) renderHomePage(prepare bool) error {
// TODO(bep) np remove this and related
if nodePageFeatureFlag {
return nil
}
n := s.newHomeNode(prepare, 0) n := s.newHomeNode(prepare, 0)
if prepare { if prepare {
return nil return nil
@ -2118,7 +2136,7 @@ func (s *Site) renderHomePage(prepare bool) error {
func (s *Site) newHomeNode(prepare bool, counter int) *Node { func (s *Site) newHomeNode(prepare bool, counter int) *Node {
n := s.nodeLookup("home", counter, prepare) n := s.nodeLookup("home", counter, prepare)
n.Title = n.Site.Title n.Title = n.Site.Title
n.IsHome = true n.NodeType = NodeHome
s.setURLs(n, "/") s.setURLs(n, "/")
n.Data["Pages"] = s.Pages n.Data["Pages"] = s.Pages
if len(s.Pages) != 0 { if len(s.Pages) != 0 {
@ -2373,7 +2391,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. // 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.(*Node); ok && n.IsHome() {
if !viper.GetBool("disableHugoGeneratorInject") { if !viper.GetBool("disableHugoGeneratorInject") {
transformLinks = append(transformLinks, transform.HugoGeneratorInject) transformLinks = append(transformLinks, transform.HugoGeneratorInject)
} }

View file

@ -378,7 +378,7 @@ func doTestShouldAlwaysHaveUglyURLs(t *testing.T, uglyURLs bool) {
} }
for _, p := range s.Pages { for _, p := range s.Pages {
assert.False(t, p.IsHome) assert.False(t, p.IsHome())
} }
for _, test := range tests { for _, test := range tests {