node to page: Basic outline
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Sun, 30 Oct 2016 16:59:24 +0000 (17:59 +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 [new file with mode: 0644]
hugolib/node_test.go
hugolib/page.go
hugolib/page_test.go
hugolib/site.go
hugolib/site_test.go

index 2ae58854b4768402bbeb03c903409fc598ce6ab2..5f0168d7a067f9e67c32c37468fd5ed1848a551b 100644 (file)
@@ -31,6 +31,15 @@ import (
        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.
 type HugoSites struct {
        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.
 func buildSiteSkipRender(s *Site, additionalTemplates ...string) error {
        return doBuildSite(s, false, additionalTemplates...)
index 16c83e2e8f0c4293868ccc4782605151735b014d..a3d9ca6997251713bd7330eddd4d02ca1f6838c6 100644 (file)
@@ -26,7 +26,23 @@ import (
        "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 {
+       NodeType NodeType
+
        // a natural key that should be unique for this site
        // for the home page this will typically be "home", but it can anything
        // as long as it is the same for repeated builds.
@@ -44,7 +60,6 @@ type Node struct {
        Lastmod     time.Time
        Sitemap     Sitemap
        URLPath
-       IsHome        bool
        paginator     *Pager
        paginatorInit sync.Once
        scratch       *Scratch
@@ -151,11 +166,15 @@ func (n *Node) RSSlink() template.HTML {
 }
 
 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 {
-       return !n.IsNode()
+       return n.NodeType == NodePage
 }
 
 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)
 }
+
+func nodeTypeFromFilename(filename string) NodeType {
+       // TODO(bep) np
+       if !strings.HasPrefix(filename, "_node") {
+               return NodePage
+       }
+       return NodeHome
+}
diff --git a/hugolib/node_as_page_test.go b/hugolib/node_as_page_test.go
new file mode 100644 (file)
index 0000000..0bbc99f
--- /dev/null
@@ -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())
+
+}
index 3b8f868dc3bddc2b9e3c9db4660986e36536df0f..98c97cae6cc1514d217ac9c5a46377e8806c86c0 100644 (file)
@@ -30,7 +30,7 @@ func TestNodeSimpleMethods(t *testing.T) {
                {func(n *Node) bool { return n.Now().Unix() == time.Now().Unix() }},
        } {
 
-               n := &Node{}
+               n := &Node{NodeType: NodeHome}
                n.RSSLink = "rssLink"
 
                if !this.assertFunc(n) {
index de739159942c385436ce41d5c3bfeed4ad795443..4c4b0d29e8ba45798df95efefd8ecdd8beab87db 100644 (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,
 // in that order.
 //
@@ -423,7 +415,7 @@ func (p *Page) getRenderingConfig() *helpers.Blackfriday {
 func newPage(filename string) *Page {
        page := Page{contentType: "",
                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{}),
                translations: make(Pages, 0),
        }
@@ -457,6 +449,11 @@ func (p *Page) layouts(l ...string) []string {
                return p.layoutsCalculated
        }
 
+       // TODO(bep) np
+       if p.NodeType == NodeHome {
+               return []string{"index.html", "_default/list.html"}
+       }
+
        if p.Layout != "" {
                return layouts(p.Type(), p.Layout)
        }
@@ -1136,6 +1133,10 @@ func (p *Page) FullFilePath() string {
 }
 
 func (p *Page) TargetPath() (outfile string) {
+       // TODO(bep) ml
+       if p.NodeType == NodeHome {
+               return "index.html"
+       }
        // Always use URL if it's specified
        if len(strings.TrimSpace(p.URLPath.URL)) > 2 {
                outfile = strings.TrimSpace(p.URLPath.URL)
index 386ea83095675ad85b3b2b0b8876c664935dae7d..86baf44f2cd9ead9a7a6a8ec73e924a255626661 100644 (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.
                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")
                checkPageContent(t, p, normalizeExpected(ext, "<p>Simple Page</p>\n"))
                checkPageSummary(t, p, "Simple Page")
index fa5f9f2cfba442d0e216da48fd98e6dff3834e88..55749aeb5cf509b3410e44602264c6dd0961b865 100644 (file)
@@ -1678,6 +1678,8 @@ func (s *Site) renderPages() error {
 func pageRenderer(s *Site, pages <-chan *Page, results chan<- error, wg *sync.WaitGroup) {
        defer wg.Done()
        for p := range pages {
+               // TODO(bep) np paginator
+               s.preparePage(p)
                err := s.renderAndWritePage("page "+p.FullFilePath(), p.TargetPath(), p, s.appendThemeTemplates(p.layouts())...)
                if err != nil {
                        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) {
        errMsgs := []string{}
        for err := range results {
@@ -2028,6 +2041,11 @@ func (s *Site) renderSectionLists(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)
        if prepare {
                return nil
@@ -2118,7 +2136,7 @@ func (s *Site) renderHomePage(prepare bool) error {
 func (s *Site) newHomeNode(prepare bool, counter int) *Node {
        n := s.nodeLookup("home", counter, prepare)
        n.Title = n.Site.Title
-       n.IsHome = true
+       n.NodeType = NodeHome
        s.setURLs(n, "/")
        n.Data["Pages"] = s.Pages
        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.
-       if n, ok := d.(*Node); ok && n.IsHome {
+       if n, ok := d.(*Node); ok && n.IsHome() {
                if !viper.GetBool("disableHugoGeneratorInject") {
                        transformLinks = append(transformLinks, transform.HugoGeneratorInject)
                }
index f368a959f60a620b54d8a2041388b55025cb8865..b71548c46c2ccaa96b676d8d35eb0acf40641069 100644 (file)
@@ -378,7 +378,7 @@ func doTestShouldAlwaysHaveUglyURLs(t *testing.T, uglyURLs bool) {
        }
 
        for _, p := range s.Pages {
-               assert.False(t, p.IsHome)
+               assert.False(t, p.IsHome())
        }
 
        for _, test := range tests {