Make Page an interface

The main motivation of this commit is to add a `page.Page` interface to replace the very file-oriented `hugolib.Page` struct.
This is all a preparation step for issue  #5074, "pages from other data sources".

But this also fixes a set of annoying limitations, especially related to custom output formats, and shortcodes.

Most notable changes:

* The inner content of shortcodes using the `{{%` as the outer-most delimiter will now be sent to the content renderer, e.g. Blackfriday.
  This means that any markdown will partake in the global ToC and footnote context etc.
* The Custom Output formats are now "fully virtualized". This removes many of the current limitations.
* The taxonomy list type now has a reference to the `Page` object.
  This improves the taxonomy template `.Title` situation and make common template constructs much simpler.

See #5074
Fixes #5763
Fixes #5758
Fixes #5090
Fixes #5204
Fixes #4695
Fixes #5607
Fixes #5707
Fixes #5719
Fixes #3113
Fixes #5706
Fixes #5767
Fixes #5723
Fixes #5769
Fixes #5770
Fixes #5771
Fixes #5759
Fixes #5776
Fixes #5777
Fixes #5778
This commit is contained in:
Bjørn Erik Pedersen
2019-01-02 12:33:26 +01:00
parent 44f5c1c14c
commit 597e418cb0
206 changed files with 14442 additions and 9679 deletions

View File

@@ -1,4 +1,4 @@
// Copyright 2016 The Hugo Authors. All rights reserved.
// Copyright 2019 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.
@@ -15,6 +15,7 @@ package hugolib
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
@@ -24,6 +25,7 @@ import (
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/resources/page"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -77,13 +79,13 @@ func TestDraftAndFutureRender(t *testing.T) {
// Testing Defaults.. Only draft:true and publishDate in the past should be rendered
s := siteSetup(t)
if len(s.RegularPages) != 1 {
if len(s.RegularPages()) != 1 {
t.Fatal("Draft or Future dated content published unexpectedly")
}
// only publishDate in the past should be rendered
s = siteSetup(t, "buildDrafts", true)
if len(s.RegularPages) != 2 {
if len(s.RegularPages()) != 2 {
t.Fatal("Future Dated Posts published unexpectedly")
}
@@ -92,7 +94,7 @@ func TestDraftAndFutureRender(t *testing.T) {
"buildDrafts", false,
"buildFuture", true)
if len(s.RegularPages) != 2 {
if len(s.RegularPages()) != 2 {
t.Fatal("Draft posts published unexpectedly")
}
@@ -101,7 +103,7 @@ func TestDraftAndFutureRender(t *testing.T) {
"buildDrafts", true,
"buildFuture", true)
if len(s.RegularPages) != 4 {
if len(s.RegularPages()) != 4 {
t.Fatal("Drafts or Future posts not included as expected")
}
@@ -128,17 +130,17 @@ func TestFutureExpirationRender(t *testing.T) {
s := siteSetup(t)
if len(s.AllPages) != 1 {
if len(s.RegularPages) > 1 {
if len(s.AllPages()) != 1 {
if len(s.RegularPages()) > 1 {
t.Fatal("Expired content published unexpectedly")
}
if len(s.RegularPages) < 1 {
if len(s.RegularPages()) < 1 {
t.Fatal("Valid content expired unexpectedly")
}
}
if s.AllPages[0].title == "doc2" {
if s.AllPages()[0].Title() == "doc2" {
t.Fatal("Expired content published unexpectedly")
}
}
@@ -156,8 +158,8 @@ func TestLastChange(t *testing.T) {
s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
require.False(t, s.Info.LastChange.IsZero(), "Site.LastChange is zero")
require.Equal(t, 2017, s.Info.LastChange.Year(), "Site.LastChange should be set to the page with latest Lastmod (year 2017)")
require.False(t, s.Info.LastChange().IsZero(), "Site.LastChange is zero")
require.Equal(t, 2017, s.Info.LastChange().Year(), "Site.LastChange should be set to the page with latest Lastmod (year 2017)")
}
// Issue #_index
@@ -170,7 +172,7 @@ func TestPageWithUnderScoreIndexInFilename(t *testing.T) {
s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
require.Len(t, s.RegularPages, 1)
require.Len(t, s.RegularPages(), 1)
}
@@ -255,7 +257,7 @@ THE END.`, refShortcode),
WithTemplate: createWithTemplateFromNameValues("_default/single.html", "{{.Content}}")},
BuildCfg{})
require.Len(t, s.RegularPages, 4)
require.Len(t, s.RegularPages(), 4)
th := testHelper{s.Cfg, s.Fs, t}
@@ -328,13 +330,13 @@ func doTestShouldAlwaysHaveUglyURLs(t *testing.T, uglyURLs bool) {
{filepath.FromSlash("public/index.html"), "Home Sweet Home."},
{filepath.FromSlash(expectedPagePath), "\n\n<h1 id=\"title\">title</h1>\n\n<p>some <em>content</em></p>\n"},
{filepath.FromSlash("public/404.html"), "Page Not Found."},
{filepath.FromSlash("public/index.xml"), "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>\n<root>RSS</root>"},
{filepath.FromSlash("public/sitemap.xml"), "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>\n<root>SITEMAP</root>"},
{filepath.FromSlash("public/index.xml"), "<root>RSS</root>"},
{filepath.FromSlash("public/sitemap.xml"), "<root>SITEMAP</root>"},
// Issue #1923
{filepath.FromSlash("public/ugly.html"), "\n\n<h1 id=\"title\">title</h1>\n\n<p>doc2 <em>content</em></p>\n"},
}
for _, p := range s.RegularPages {
for _, p := range s.RegularPages() {
assert.False(t, p.IsHome())
}
@@ -406,7 +408,7 @@ func doTestSectionNaming(t *testing.T, canonify, uglify, pluralize bool) {
}
writeSource(t, fs, filepath.Join("layouts", "_default/single.html"), "{{.Content}}")
writeSource(t, fs, filepath.Join("layouts", "_default/list.html"), "{{.Title}}")
writeSource(t, fs, filepath.Join("layouts", "_default/list.html"), "{{ .Kind }}|{{.Title}}")
s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
@@ -491,6 +493,7 @@ func TestSkipRender(t *testing.T) {
for _, test := range tests {
file, err := fs.Destination.Open(test.doc)
if err != nil {
helpers.PrintFs(fs.Destination, "public", os.Stdout)
t.Fatalf("Did not find %s in target.", test.doc)
}
@@ -610,40 +613,40 @@ func TestOrderedPages(t *testing.T) {
s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
if s.getPage(KindSection, "sect").Pages[1].title != "Three" || s.getPage(KindSection, "sect").Pages[2].title != "Four" {
if s.getPage(page.KindSection, "sect").Pages()[1].Title() != "Three" || s.getPage(page.KindSection, "sect").Pages()[2].Title() != "Four" {
t.Error("Pages in unexpected order.")
}
bydate := s.RegularPages.ByDate()
bydate := s.RegularPages().ByDate()
if bydate[0].title != "One" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "One", bydate[0].title)
if bydate[0].Title() != "One" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "One", bydate[0].Title())
}
rev := bydate.Reverse()
if rev[0].title != "Three" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "Three", rev[0].title)
if rev[0].Title() != "Three" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "Three", rev[0].Title())
}
bypubdate := s.RegularPages.ByPublishDate()
bypubdate := s.RegularPages().ByPublishDate()
if bypubdate[0].title != "One" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "One", bypubdate[0].title)
if bypubdate[0].Title() != "One" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "One", bypubdate[0].Title())
}
rbypubdate := bypubdate.Reverse()
if rbypubdate[0].title != "Three" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "Three", rbypubdate[0].title)
if rbypubdate[0].Title() != "Three" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "Three", rbypubdate[0].Title())
}
bylength := s.RegularPages.ByLength()
if bylength[0].title != "One" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "One", bylength[0].title)
bylength := s.RegularPages().ByLength()
if bylength[0].Title() != "One" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "One", bylength[0].Title())
}
rbylength := bylength.Reverse()
if rbylength[0].title != "Four" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "Four", rbylength[0].title)
if rbylength[0].Title() != "Four" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "Four", rbylength[0].Title())
}
}
@@ -668,7 +671,7 @@ func TestGroupedPages(t *testing.T) {
writeSourcesToSource(t, "content", fs, groupedSources...)
s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
rbysection, err := s.RegularPages.GroupBy("Section", "desc")
rbysection, err := s.RegularPages().GroupBy("Section", "desc")
if err != nil {
t.Fatalf("Unable to make PageGroup array: %s", err)
}
@@ -682,14 +685,14 @@ func TestGroupedPages(t *testing.T) {
if rbysection[2].Key != "sect1" {
t.Errorf("PageGroup array in unexpected order. Third group key should be '%s', got '%s'", "sect1", rbysection[2].Key)
}
if rbysection[0].Pages[0].title != "Four" {
t.Errorf("PageGroup has an unexpected page. First group's pages should have '%s', got '%s'", "Four", rbysection[0].Pages[0].title)
if rbysection[0].Pages[0].Title() != "Four" {
t.Errorf("PageGroup has an unexpected page. First group's pages should have '%s', got '%s'", "Four", rbysection[0].Pages[0].Title())
}
if len(rbysection[2].Pages) != 2 {
t.Errorf("PageGroup has unexpected number of pages. Third group should have '%d' pages, got '%d' pages", 2, len(rbysection[2].Pages))
}
bytype, err := s.RegularPages.GroupBy("Type", "asc")
bytype, err := s.RegularPages().GroupBy("Type", "asc")
if err != nil {
t.Fatalf("Unable to make PageGroup array: %s", err)
}
@@ -702,14 +705,14 @@ func TestGroupedPages(t *testing.T) {
if bytype[2].Key != "sect3" {
t.Errorf("PageGroup array in unexpected order. Third group key should be '%s', got '%s'", "sect3", bytype[2].Key)
}
if bytype[2].Pages[0].title != "Four" {
t.Errorf("PageGroup has an unexpected page. Third group's data should have '%s', got '%s'", "Four", bytype[0].Pages[0].title)
if bytype[2].Pages[0].Title() != "Four" {
t.Errorf("PageGroup has an unexpected page. Third group's data should have '%s', got '%s'", "Four", bytype[0].Pages[0].Title())
}
if len(bytype[0].Pages) != 2 {
t.Errorf("PageGroup has unexpected number of pages. First group should have '%d' pages, got '%d' pages", 2, len(bytype[2].Pages))
}
bydate, err := s.RegularPages.GroupByDate("2006-01", "asc")
bydate, err := s.RegularPages().GroupByDate("2006-01", "asc")
if err != nil {
t.Fatalf("Unable to make PageGroup array: %s", err)
}
@@ -720,7 +723,7 @@ func TestGroupedPages(t *testing.T) {
t.Errorf("PageGroup array in unexpected order. Second group key should be '%s', got '%s'", "2012-01", bydate[1].Key)
}
bypubdate, err := s.RegularPages.GroupByPublishDate("2006")
bypubdate, err := s.RegularPages().GroupByPublishDate("2006")
if err != nil {
t.Fatalf("Unable to make PageGroup array: %s", err)
}
@@ -730,14 +733,14 @@ func TestGroupedPages(t *testing.T) {
if bypubdate[1].Key != "0001" {
t.Errorf("PageGroup array in unexpected order. Second group key should be '%s', got '%s'", "0001", bypubdate[1].Key)
}
if bypubdate[0].Pages[0].title != "Three" {
t.Errorf("PageGroup has an unexpected page. Third group's pages should have '%s', got '%s'", "Three", bypubdate[0].Pages[0].title)
if bypubdate[0].Pages[0].Title() != "Three" {
t.Errorf("PageGroup has an unexpected page. Third group's pages should have '%s', got '%s'", "Three", bypubdate[0].Pages[0].Title())
}
if len(bypubdate[0].Pages) != 3 {
t.Errorf("PageGroup has unexpected number of pages. First group should have '%d' pages, got '%d' pages", 3, len(bypubdate[0].Pages))
}
byparam, err := s.RegularPages.GroupByParam("my_param", "desc")
byparam, err := s.RegularPages().GroupByParam("my_param", "desc")
if err != nil {
t.Fatalf("Unable to make PageGroup array: %s", err)
}
@@ -750,19 +753,19 @@ func TestGroupedPages(t *testing.T) {
if byparam[2].Key != "bar" {
t.Errorf("PageGroup array in unexpected order. Third group key should be '%s', got '%s'", "bar", byparam[2].Key)
}
if byparam[2].Pages[0].title != "Three" {
t.Errorf("PageGroup has an unexpected page. Third group's pages should have '%s', got '%s'", "Three", byparam[2].Pages[0].title)
if byparam[2].Pages[0].Title() != "Three" {
t.Errorf("PageGroup has an unexpected page. Third group's pages should have '%s', got '%s'", "Three", byparam[2].Pages[0].Title())
}
if len(byparam[0].Pages) != 2 {
t.Errorf("PageGroup has unexpected number of pages. First group should have '%d' pages, got '%d' pages", 2, len(byparam[0].Pages))
}
_, err = s.RegularPages.GroupByParam("not_exist")
_, err = s.RegularPages().GroupByParam("not_exist")
if err == nil {
t.Errorf("GroupByParam didn't return an expected error")
}
byOnlyOneParam, err := s.RegularPages.GroupByParam("only_one")
byOnlyOneParam, err := s.RegularPages().GroupByParam("only_one")
if err != nil {
t.Fatalf("Unable to make PageGroup array: %s", err)
}
@@ -773,7 +776,7 @@ func TestGroupedPages(t *testing.T) {
t.Errorf("PageGroup array in unexpected order. First group key should be '%s', got '%s'", "yes", byOnlyOneParam[0].Key)
}
byParamDate, err := s.RegularPages.GroupByParamDate("my_date", "2006-01")
byParamDate, err := s.RegularPages().GroupByParamDate("my_date", "2006-01")
if err != nil {
t.Fatalf("Unable to make PageGroup array: %s", err)
}
@@ -783,8 +786,8 @@ func TestGroupedPages(t *testing.T) {
if byParamDate[1].Key != "1979-05" {
t.Errorf("PageGroup array in unexpected order. Second group key should be '%s', got '%s'", "1979-05", byParamDate[1].Key)
}
if byParamDate[1].Pages[0].title != "One" {
t.Errorf("PageGroup has an unexpected page. Second group's pages should have '%s', got '%s'", "One", byParamDate[1].Pages[0].title)
if byParamDate[1].Pages[0].Title() != "One" {
t.Errorf("PageGroup has an unexpected page. Second group's pages should have '%s', got '%s'", "One", byParamDate[1].Pages[0].Title())
}
if len(byParamDate[0].Pages) != 2 {
t.Errorf("PageGroup has unexpected number of pages. First group should have '%d' pages, got '%d' pages", 2, len(byParamDate[2].Pages))
@@ -840,16 +843,16 @@ func TestWeightedTaxonomies(t *testing.T) {
writeSourcesToSource(t, "content", fs, sources...)
s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
if s.Taxonomies["tags"]["a"][0].Page.title != "foo" {
t.Errorf("Pages in unexpected order, 'foo' expected first, got '%v'", s.Taxonomies["tags"]["a"][0].Page.title)
if s.Taxonomies["tags"]["a"][0].Page.Title() != "foo" {
t.Errorf("Pages in unexpected order, 'foo' expected first, got '%v'", s.Taxonomies["tags"]["a"][0].Page.Title())
}
if s.Taxonomies["categories"]["d"][0].Page.title != "bar" {
t.Errorf("Pages in unexpected order, 'bar' expected first, got '%v'", s.Taxonomies["categories"]["d"][0].Page.title)
if s.Taxonomies["categories"]["d"][0].Page.Title() != "bar" {
t.Errorf("Pages in unexpected order, 'bar' expected first, got '%v'", s.Taxonomies["categories"]["d"][0].Page.Title())
}
if s.Taxonomies["categories"]["e"][0].Page.title != "bza" {
t.Errorf("Pages in unexpected order, 'bza' expected first, got '%v'", s.Taxonomies["categories"]["e"][0].Page.title)
if s.Taxonomies["categories"]["e"][0].Page.Title() != "bza" {
t.Errorf("Pages in unexpected order, 'bza' expected first, got '%v'", s.Taxonomies["categories"]["e"][0].Page.Title())
}
}
@@ -897,7 +900,7 @@ func TestRefLinking(t *testing.T) {
t.Parallel()
site := setupLinkingMockSite(t)
currentPage := site.getPage(KindPage, "level2/level3/start.md")
currentPage := site.getPage(page.KindPage, "level2/level3/start.md")
if currentPage == nil {
t.Fatalf("failed to find current page in site")
}
@@ -952,8 +955,8 @@ func TestRefLinking(t *testing.T) {
// TODO: and then the failure cases.
}
func checkLinkCase(site *Site, link string, currentPage *Page, relative bool, outputFormat string, expected string, t *testing.T, i int) {
func checkLinkCase(site *Site, link string, currentPage page.Page, relative bool, outputFormat string, expected string, t *testing.T, i int) {
if out, err := site.refLink(link, currentPage, relative, outputFormat); err != nil || out != expected {
t.Errorf("[%d] Expected %q from %q to resolve to %q, got %q - error: %s", i, link, currentPage.absoluteSourceRef(), expected, out, err)
t.Fatalf("[%d] Expected %q from %q to resolve to %q, got %q - error: %s", i, link, currentPage.Path(), expected, out, err)
}
}