Create a struct with all of Hugo's config options

Primary motivation is documentation, but it will also hopefully simplify the code.

Also,

* Lower case the default output format names; this is in line with the custom ones (map keys) and how
it's treated all the places. This avoids doing `stringds.EqualFold` everywhere.

Closes #10896
Closes #10620
This commit is contained in:
Bjørn Erik Pedersen
2023-01-04 18:24:36 +01:00
parent 6aededf6b4
commit 241b21b0fd
337 changed files with 13377 additions and 14898 deletions

View File

@@ -166,7 +166,7 @@ type OutputFormatsProvider interface {
OutputFormats() OutputFormats
}
// Page is the core interface in Hugo.
// Page is the core interface in Hugo and what you get as the top level data context in your templates.
type Page interface {
ContentProvider
TableOfContentsProvider
@@ -249,7 +249,7 @@ type PageMetaProvider interface {
// Sitemap returns the sitemap configuration for this page.
// This is for internal use only.
Sitemap() config.Sitemap
Sitemap() config.SitemapConfig
// Type is a discriminator used to select layouts etc. It is typically set
// in front matter, but will fall back to the root section.

View File

@@ -111,7 +111,7 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
Section string
SectionsEntries []string
SectionsPath string
Sitemap config.Sitemap
Sitemap config.SitemapConfig
Type string
Weight int
Language *langs.Language

View File

@@ -19,6 +19,7 @@ import (
"strings"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/hugofs/glob"
"github.com/mitchellh/mapstructure"
)
@@ -80,43 +81,90 @@ func (m PageMatcher) Matches(p Page) bool {
return true
}
// DecodeCascade decodes in which could be either a map or a slice of maps.
func DecodeCascade(in any) (map[PageMatcher]maps.Params, error) {
m, err := maps.ToSliceStringMap(in)
if err != nil {
return map[PageMatcher]maps.Params{
{}: maps.ToStringMap(in),
}, nil
}
func DecodeCascadeConfig(in any) (*config.ConfigNamespace[[]PageMatcherParamsConfig, map[PageMatcher]maps.Params], error) {
buildConfig := func(in any) (map[PageMatcher]maps.Params, any, error) {
cascade := make(map[PageMatcher]maps.Params)
if in == nil {
return cascade, []map[string]any{}, nil
}
ms, err := maps.ToSliceStringMap(in)
if err != nil {
return nil, nil, err
}
cascade := make(map[PageMatcher]maps.Params)
var cfgs []PageMatcherParamsConfig
for _, vv := range m {
var m PageMatcher
if mv, found := vv["_target"]; found {
err := DecodePageMatcher(mv, &m)
for _, m := range ms {
m = maps.CleanConfigStringMap(m)
c, err := mapToPageMatcherParamsConfig(m)
if err != nil {
return nil, err
return nil, nil, err
}
cfgs = append(cfgs, c)
}
c, found := cascade[m]
if found {
// Merge
for k, v := range vv {
if _, found := c[k]; !found {
c[k] = v
for _, cfg := range cfgs {
m := cfg.Target
c, found := cascade[m]
if found {
// Merge
for k, v := range cfg.Params {
if _, found := c[k]; !found {
c[k] = v
}
}
} else {
cascade[m] = cfg.Params
}
} else {
cascade[m] = vv
}
return cascade, cfgs, nil
}
return cascade, nil
return config.DecodeNamespace[[]PageMatcherParamsConfig](in, buildConfig)
}
// DecodePageMatcher decodes m into v.
func DecodePageMatcher(m any, v *PageMatcher) error {
// DecodeCascade decodes in which could be either a map or a slice of maps.
func DecodeCascade(in any) (map[PageMatcher]maps.Params, error) {
conf, err := DecodeCascadeConfig(in)
if err != nil {
return nil, err
}
return conf.Config, nil
}
func mapToPageMatcherParamsConfig(m map[string]any) (PageMatcherParamsConfig, error) {
var pcfg PageMatcherParamsConfig
for k, v := range m {
switch strings.ToLower(k) {
case "params":
// We simplified the structure of the cascade config in Hugo 0.111.0.
// There is a small chance that someone has used the old structure with the params keyword,
// those values will now be moved to the top level.
// This should be very unlikely as it would lead to constructs like .Params.params.foo,
// and most people see params as an Hugo internal keyword.
pcfg.Params = maps.ToStringMap(v)
case "_target", "target":
var target PageMatcher
if err := decodePageMatcher(v, &target); err != nil {
return pcfg, err
}
pcfg.Target = target
default:
// Legacy config.
if pcfg.Params == nil {
pcfg.Params = make(maps.Params)
}
pcfg.Params[k] = v
}
}
return pcfg, pcfg.init()
}
// decodePageMatcher decodes m into v.
func decodePageMatcher(m any, v *PageMatcher) error {
if err := mapstructure.WeakDecode(m, v); err != nil {
return err
}
@@ -140,3 +188,14 @@ func DecodePageMatcher(m any, v *PageMatcher) error {
return nil
}
type PageMatcherParamsConfig struct {
// Apply Params to all Pages matching Target.
Params maps.Params
Target PageMatcher
}
func (p *PageMatcherParamsConfig) init() error {
maps.PrepareParams(p.Params)
return nil
}

View File

@@ -18,6 +18,7 @@ import (
"testing"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/maps"
qt "github.com/frankban/quicktest"
)
@@ -71,13 +72,87 @@ func TestPageMatcher(t *testing.T) {
c.Run("Decode", func(c *qt.C) {
var v PageMatcher
c.Assert(DecodePageMatcher(map[string]any{"kind": "foo"}, &v), qt.Not(qt.IsNil))
c.Assert(DecodePageMatcher(map[string]any{"kind": "{foo,bar}"}, &v), qt.Not(qt.IsNil))
c.Assert(DecodePageMatcher(map[string]any{"kind": "taxonomy"}, &v), qt.IsNil)
c.Assert(DecodePageMatcher(map[string]any{"kind": "{taxonomy,foo}"}, &v), qt.IsNil)
c.Assert(DecodePageMatcher(map[string]any{"kind": "{taxonomy,term}"}, &v), qt.IsNil)
c.Assert(DecodePageMatcher(map[string]any{"kind": "*"}, &v), qt.IsNil)
c.Assert(DecodePageMatcher(map[string]any{"kind": "home", "path": filepath.FromSlash("/a/b/**")}, &v), qt.IsNil)
c.Assert(decodePageMatcher(map[string]any{"kind": "foo"}, &v), qt.Not(qt.IsNil))
c.Assert(decodePageMatcher(map[string]any{"kind": "{foo,bar}"}, &v), qt.Not(qt.IsNil))
c.Assert(decodePageMatcher(map[string]any{"kind": "taxonomy"}, &v), qt.IsNil)
c.Assert(decodePageMatcher(map[string]any{"kind": "{taxonomy,foo}"}, &v), qt.IsNil)
c.Assert(decodePageMatcher(map[string]any{"kind": "{taxonomy,term}"}, &v), qt.IsNil)
c.Assert(decodePageMatcher(map[string]any{"kind": "*"}, &v), qt.IsNil)
c.Assert(decodePageMatcher(map[string]any{"kind": "home", "path": filepath.FromSlash("/a/b/**")}, &v), qt.IsNil)
c.Assert(v, qt.Equals, PageMatcher{Kind: "home", Path: "/a/b/**"})
})
c.Run("mapToPageMatcherParamsConfig", func(c *qt.C) {
fn := func(m map[string]any) PageMatcherParamsConfig {
v, err := mapToPageMatcherParamsConfig(m)
c.Assert(err, qt.IsNil)
return v
}
// Legacy.
c.Assert(fn(map[string]any{"_target": map[string]any{"kind": "page"}, "foo": "bar"}), qt.DeepEquals, PageMatcherParamsConfig{
Params: maps.Params{
"foo": "bar",
},
Target: PageMatcher{Path: "", Kind: "page", Lang: "", Environment: ""},
})
// Current format.
c.Assert(fn(map[string]any{"target": map[string]any{"kind": "page"}, "params": map[string]any{"foo": "bar"}}), qt.DeepEquals, PageMatcherParamsConfig{
Params: maps.Params{
"foo": "bar",
},
Target: PageMatcher{Path: "", Kind: "page", Lang: "", Environment: ""},
})
})
}
func TestDecodeCascadeConfig(t *testing.T) {
c := qt.New(t)
in := []map[string]any{
{
"params": map[string]any{
"a": "av",
},
"target": map[string]any{
"kind": "page",
"Environment": "production",
},
},
{
"params": map[string]any{
"b": "bv",
},
"target": map[string]any{
"kind": "page",
},
},
}
got, err := DecodeCascadeConfig(in)
c.Assert(err, qt.IsNil)
c.Assert(got, qt.IsNotNil)
c.Assert(got.Config, qt.DeepEquals,
map[PageMatcher]maps.Params{
{Path: "", Kind: "page", Lang: "", Environment: ""}: {
"b": "bv",
},
{Path: "", Kind: "page", Lang: "", Environment: "production"}: {
"a": "av",
},
},
)
c.Assert(got.SourceStructure, qt.DeepEquals, []PageMatcherParamsConfig{
{
Params: maps.Params{"a": string("av")},
Target: PageMatcher{Kind: "page", Environment: "production"},
},
{Params: maps.Params{"b": string("bv")}, Target: PageMatcher{Kind: "page"}},
})
got, err = DecodeCascadeConfig(nil)
c.Assert(err, qt.IsNil)
c.Assert(got, qt.IsNotNil)
}

View File

@@ -67,8 +67,8 @@ func (p *nopPage) Aliases() []string {
return nil
}
func (p *nopPage) Sitemap() config.Sitemap {
return config.Sitemap{}
func (p *nopPage) Sitemap() config.SitemapConfig {
return config.SitemapConfig{}
}
func (p *nopPage) Layout() string {
@@ -217,7 +217,7 @@ func (p *nopPage) HasShortcode(name string) bool {
return false
}
func (p *nopPage) Hugo() (h hugo.Info) {
func (p *nopPage) Hugo() (h hugo.HugoInfo) {
return
}

View File

@@ -18,6 +18,7 @@ import (
"path/filepath"
"strings"
"github.com/gohugoio/hugo/common/urls"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/output"
)
@@ -91,18 +92,18 @@ func (p TargetPaths) RelPermalink(s *helpers.PathSpec) string {
}
func (p TargetPaths) PermalinkForOutputFormat(s *helpers.PathSpec, f output.Format) string {
var baseURL string
var baseURL urls.BaseURL
var err error
if f.Protocol != "" {
baseURL, err = s.BaseURL.WithProtocol(f.Protocol)
baseURL, err = s.Cfg.BaseURL().WithProtocol(f.Protocol)
if err != nil {
return ""
}
} else {
baseURL = s.BaseURL.String()
baseURL = s.Cfg.BaseURL()
}
return s.PermalinkForBaseURL(p.Link, baseURL)
baseURLstr := baseURL.String()
return s.PermalinkForBaseURL(p.Link, baseURLstr)
}
func isHtmlIndex(s string) bool {

View File

@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package page
package page_test
import (
"fmt"
@@ -20,6 +20,7 @@ import (
"testing"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/output"
)
@@ -27,7 +28,7 @@ import (
func TestPageTargetPath(t *testing.T) {
pathSpec := newTestPathSpec()
noExtNoDelimMediaType := media.WithDelimiterAndSuffixes(media.TextType, "", "")
noExtNoDelimMediaType := media.WithDelimiterAndSuffixes(media.Builtin.TextType, "", "")
noExtNoDelimMediaType.Delimiter = ""
// Netlify style _redirects
@@ -43,152 +44,152 @@ func TestPageTargetPath(t *testing.T) {
tests := []struct {
name string
d TargetPathDescriptor
expected TargetPaths
d page.TargetPathDescriptor
expected page.TargetPaths
}{
{"JSON home", TargetPathDescriptor{Kind: KindHome, Type: output.JSONFormat}, TargetPaths{TargetFilename: "/index.json", SubResourceBaseTarget: "", Link: "/index.json"}},
{"AMP home", TargetPathDescriptor{Kind: KindHome, Type: output.AMPFormat}, TargetPaths{TargetFilename: "/amp/index.html", SubResourceBaseTarget: "/amp", Link: "/amp/"}},
{"HTML home", TargetPathDescriptor{Kind: KindHome, BaseName: "_index", Type: output.HTMLFormat}, TargetPaths{TargetFilename: "/index.html", SubResourceBaseTarget: "", Link: "/"}},
{"Netlify redirects", TargetPathDescriptor{Kind: KindHome, BaseName: "_index", Type: noExtDelimFormat}, TargetPaths{TargetFilename: "/_redirects", SubResourceBaseTarget: "", Link: "/_redirects"}},
{"HTML section list", TargetPathDescriptor{
Kind: KindSection,
{"JSON home", page.TargetPathDescriptor{Kind: page.KindHome, Type: output.JSONFormat}, page.TargetPaths{TargetFilename: "/index.json", SubResourceBaseTarget: "", Link: "/index.json"}},
{"AMP home", page.TargetPathDescriptor{Kind: page.KindHome, Type: output.AMPFormat}, page.TargetPaths{TargetFilename: "/amp/index.html", SubResourceBaseTarget: "/amp", Link: "/amp/"}},
{"HTML home", page.TargetPathDescriptor{Kind: page.KindHome, BaseName: "_index", Type: output.HTMLFormat}, page.TargetPaths{TargetFilename: "/index.html", SubResourceBaseTarget: "", Link: "/"}},
{"Netlify redirects", page.TargetPathDescriptor{Kind: page.KindHome, BaseName: "_index", Type: noExtDelimFormat}, page.TargetPaths{TargetFilename: "/_redirects", SubResourceBaseTarget: "", Link: "/_redirects"}},
{"HTML section list", page.TargetPathDescriptor{
Kind: page.KindSection,
Sections: []string{"sect1"},
BaseName: "_index",
Type: output.HTMLFormat,
}, TargetPaths{TargetFilename: "/sect1/index.html", SubResourceBaseTarget: "/sect1", Link: "/sect1/"}},
{"HTML taxonomy term", TargetPathDescriptor{
Kind: KindTerm,
}, page.TargetPaths{TargetFilename: "/sect1/index.html", SubResourceBaseTarget: "/sect1", Link: "/sect1/"}},
{"HTML taxonomy term", page.TargetPathDescriptor{
Kind: page.KindTerm,
Sections: []string{"tags", "hugo"},
BaseName: "_index",
Type: output.HTMLFormat,
}, TargetPaths{TargetFilename: "/tags/hugo/index.html", SubResourceBaseTarget: "/tags/hugo", Link: "/tags/hugo/"}},
{"HTML taxonomy", TargetPathDescriptor{
Kind: KindTaxonomy,
}, page.TargetPaths{TargetFilename: "/tags/hugo/index.html", SubResourceBaseTarget: "/tags/hugo", Link: "/tags/hugo/"}},
{"HTML taxonomy", page.TargetPathDescriptor{
Kind: page.KindTaxonomy,
Sections: []string{"tags"},
BaseName: "_index",
Type: output.HTMLFormat,
}, TargetPaths{TargetFilename: "/tags/index.html", SubResourceBaseTarget: "/tags", Link: "/tags/"}},
}, page.TargetPaths{TargetFilename: "/tags/index.html", SubResourceBaseTarget: "/tags", Link: "/tags/"}},
{
"HTML page", TargetPathDescriptor{
Kind: KindPage,
"HTML page", page.TargetPathDescriptor{
Kind: page.KindPage,
Dir: "/a/b",
BaseName: "mypage",
Sections: []string{"a"},
Type: output.HTMLFormat,
}, TargetPaths{TargetFilename: "/a/b/mypage/index.html", SubResourceBaseTarget: "/a/b/mypage", Link: "/a/b/mypage/"},
}, page.TargetPaths{TargetFilename: "/a/b/mypage/index.html", SubResourceBaseTarget: "/a/b/mypage", Link: "/a/b/mypage/"},
},
{
"HTML page with index as base", TargetPathDescriptor{
Kind: KindPage,
"HTML page with index as base", page.TargetPathDescriptor{
Kind: page.KindPage,
Dir: "/a/b",
BaseName: "index",
Sections: []string{"a"},
Type: output.HTMLFormat,
}, TargetPaths{TargetFilename: "/a/b/index.html", SubResourceBaseTarget: "/a/b", Link: "/a/b/"},
}, page.TargetPaths{TargetFilename: "/a/b/index.html", SubResourceBaseTarget: "/a/b", Link: "/a/b/"},
},
{
"HTML page with special chars", TargetPathDescriptor{
Kind: KindPage,
"HTML page with special chars", page.TargetPathDescriptor{
Kind: page.KindPage,
Dir: "/a/b",
BaseName: "My Page!",
Type: output.HTMLFormat,
}, TargetPaths{TargetFilename: "/a/b/my-page/index.html", SubResourceBaseTarget: "/a/b/my-page", Link: "/a/b/my-page/"},
}, page.TargetPaths{TargetFilename: "/a/b/my-page/index.html", SubResourceBaseTarget: "/a/b/my-page", Link: "/a/b/my-page/"},
},
{"RSS home", TargetPathDescriptor{Kind: "rss", Type: output.RSSFormat}, TargetPaths{TargetFilename: "/index.xml", SubResourceBaseTarget: "", Link: "/index.xml"}},
{"RSS section list", TargetPathDescriptor{
{"RSS home", page.TargetPathDescriptor{Kind: "rss", Type: output.RSSFormat}, page.TargetPaths{TargetFilename: "/index.xml", SubResourceBaseTarget: "", Link: "/index.xml"}},
{"RSS section list", page.TargetPathDescriptor{
Kind: "rss",
Sections: []string{"sect1"},
Type: output.RSSFormat,
}, TargetPaths{TargetFilename: "/sect1/index.xml", SubResourceBaseTarget: "/sect1", Link: "/sect1/index.xml"}},
}, page.TargetPaths{TargetFilename: "/sect1/index.xml", SubResourceBaseTarget: "/sect1", Link: "/sect1/index.xml"}},
{
"AMP page", TargetPathDescriptor{
Kind: KindPage,
"AMP page", page.TargetPathDescriptor{
Kind: page.KindPage,
Dir: "/a/b/c",
BaseName: "myamp",
Type: output.AMPFormat,
}, TargetPaths{TargetFilename: "/amp/a/b/c/myamp/index.html", SubResourceBaseTarget: "/amp/a/b/c/myamp", Link: "/amp/a/b/c/myamp/"},
}, page.TargetPaths{TargetFilename: "/amp/a/b/c/myamp/index.html", SubResourceBaseTarget: "/amp/a/b/c/myamp", Link: "/amp/a/b/c/myamp/"},
},
{
"AMP page with URL with suffix", TargetPathDescriptor{
Kind: KindPage,
"AMP page with URL with suffix", page.TargetPathDescriptor{
Kind: page.KindPage,
Dir: "/sect/",
BaseName: "mypage",
URL: "/some/other/url.xhtml",
Type: output.HTMLFormat,
}, TargetPaths{TargetFilename: "/some/other/url.xhtml", SubResourceBaseTarget: "/some/other", Link: "/some/other/url.xhtml"},
}, page.TargetPaths{TargetFilename: "/some/other/url.xhtml", SubResourceBaseTarget: "/some/other", Link: "/some/other/url.xhtml"},
},
{
"JSON page with URL without suffix", TargetPathDescriptor{
Kind: KindPage,
"JSON page with URL without suffix", page.TargetPathDescriptor{
Kind: page.KindPage,
Dir: "/sect/",
BaseName: "mypage",
URL: "/some/other/path/",
Type: output.JSONFormat,
}, TargetPaths{TargetFilename: "/some/other/path/index.json", SubResourceBaseTarget: "/some/other/path", Link: "/some/other/path/index.json"},
}, page.TargetPaths{TargetFilename: "/some/other/path/index.json", SubResourceBaseTarget: "/some/other/path", Link: "/some/other/path/index.json"},
},
{
"JSON page with URL without suffix and no trailing slash", TargetPathDescriptor{
Kind: KindPage,
"JSON page with URL without suffix and no trailing slash", page.TargetPathDescriptor{
Kind: page.KindPage,
Dir: "/sect/",
BaseName: "mypage",
URL: "/some/other/path",
Type: output.JSONFormat,
}, TargetPaths{TargetFilename: "/some/other/path/index.json", SubResourceBaseTarget: "/some/other/path", Link: "/some/other/path/index.json"},
}, page.TargetPaths{TargetFilename: "/some/other/path/index.json", SubResourceBaseTarget: "/some/other/path", Link: "/some/other/path/index.json"},
},
{
"HTML page with URL without suffix and no trailing slash", TargetPathDescriptor{
Kind: KindPage,
"HTML page with URL without suffix and no trailing slash", page.TargetPathDescriptor{
Kind: page.KindPage,
Dir: "/sect/",
BaseName: "mypage",
URL: "/some/other/path",
Type: output.HTMLFormat,
}, TargetPaths{TargetFilename: "/some/other/path/index.html", SubResourceBaseTarget: "/some/other/path", Link: "/some/other/path/"},
}, page.TargetPaths{TargetFilename: "/some/other/path/index.html", SubResourceBaseTarget: "/some/other/path", Link: "/some/other/path/"},
},
{
"HTML page with URL containing double hyphen", TargetPathDescriptor{
Kind: KindPage,
"HTML page with URL containing double hyphen", page.TargetPathDescriptor{
Kind: page.KindPage,
Dir: "/sect/",
BaseName: "mypage",
URL: "/some/other--url/",
Type: output.HTMLFormat,
}, TargetPaths{TargetFilename: "/some/other--url/index.html", SubResourceBaseTarget: "/some/other--url", Link: "/some/other--url/"},
}, page.TargetPaths{TargetFilename: "/some/other--url/index.html", SubResourceBaseTarget: "/some/other--url", Link: "/some/other--url/"},
},
{
"HTML page with expanded permalink", TargetPathDescriptor{
Kind: KindPage,
"HTML page with expanded permalink", page.TargetPathDescriptor{
Kind: page.KindPage,
Dir: "/a/b",
BaseName: "mypage",
ExpandedPermalink: "/2017/10/my-title/",
Type: output.HTMLFormat,
}, TargetPaths{TargetFilename: "/2017/10/my-title/index.html", SubResourceBaseTarget: "/2017/10/my-title", Link: "/2017/10/my-title/"},
}, page.TargetPaths{TargetFilename: "/2017/10/my-title/index.html", SubResourceBaseTarget: "/2017/10/my-title", Link: "/2017/10/my-title/"},
},
{
"Paginated HTML home", TargetPathDescriptor{
Kind: KindHome,
"Paginated HTML home", page.TargetPathDescriptor{
Kind: page.KindHome,
BaseName: "_index",
Type: output.HTMLFormat,
Addends: "page/3",
}, TargetPaths{TargetFilename: "/page/3/index.html", SubResourceBaseTarget: "/page/3", Link: "/page/3/"},
}, page.TargetPaths{TargetFilename: "/page/3/index.html", SubResourceBaseTarget: "/page/3", Link: "/page/3/"},
},
{
"Paginated Taxonomy terms list", TargetPathDescriptor{
Kind: KindTerm,
"Paginated Taxonomy terms list", page.TargetPathDescriptor{
Kind: page.KindTerm,
BaseName: "_index",
Sections: []string{"tags", "hugo"},
Type: output.HTMLFormat,
Addends: "page/3",
}, TargetPaths{TargetFilename: "/tags/hugo/page/3/index.html", SubResourceBaseTarget: "/tags/hugo/page/3", Link: "/tags/hugo/page/3/"},
}, page.TargetPaths{TargetFilename: "/tags/hugo/page/3/index.html", SubResourceBaseTarget: "/tags/hugo/page/3", Link: "/tags/hugo/page/3/"},
},
{
"Regular page with addend", TargetPathDescriptor{
Kind: KindPage,
"Regular page with addend", page.TargetPathDescriptor{
Kind: page.KindPage,
Dir: "/a/b",
BaseName: "mypage",
Addends: "c/d/e",
Type: output.HTMLFormat,
}, TargetPaths{TargetFilename: "/a/b/mypage/c/d/e/index.html", SubResourceBaseTarget: "/a/b/mypage/c/d/e", Link: "/a/b/mypage/c/d/e/"},
}, page.TargetPaths{TargetFilename: "/a/b/mypage/c/d/e/index.html", SubResourceBaseTarget: "/a/b/mypage/c/d/e", Link: "/a/b/mypage/c/d/e/"},
},
}
@@ -206,8 +207,8 @@ func TestPageTargetPath(t *testing.T) {
expected := test.expected
// TODO(bep) simplify
if test.d.Kind == KindPage && test.d.BaseName == test.d.Type.BaseName {
} else if test.d.Kind == KindHome && test.d.Type.Path != "" {
if test.d.Kind == page.KindPage && test.d.BaseName == test.d.Type.BaseName {
} else if test.d.Kind == page.KindHome && test.d.Type.Path != "" {
} else if test.d.Type.MediaType.FirstSuffix.Suffix != "" && (!strings.HasPrefix(expected.TargetFilename, "/index") || test.d.Addends != "") && test.d.URL == "" && isUgly {
expected.TargetFilename = strings.Replace(expected.TargetFilename,
"/"+test.d.Type.BaseName+"."+test.d.Type.MediaType.FirstSuffix.Suffix,
@@ -228,7 +229,7 @@ func TestPageTargetPath(t *testing.T) {
expected.TargetFilename = filepath.FromSlash(expected.TargetFilename)
expected.SubResourceBaseTarget = filepath.FromSlash(expected.SubResourceBaseTarget)
pagePath := CreateTargetPaths(test.d)
pagePath := page.CreateTargetPaths(test.d)
if !eqTargetPaths(pagePath, expected) {
t.Fatalf("[%d] [%s] targetPath expected\n%#v, got:\n%#v", i, test.name, expected, pagePath)
@@ -244,18 +245,18 @@ func TestPageTargetPathPrefix(t *testing.T) {
pathSpec := newTestPathSpec()
tests := []struct {
name string
d TargetPathDescriptor
expected TargetPaths
d page.TargetPathDescriptor
expected page.TargetPaths
}{
{
"URL set, prefix both, no force",
TargetPathDescriptor{Kind: KindPage, Type: output.JSONFormat, URL: "/mydir/my.json", ForcePrefix: false, PrefixFilePath: "pf", PrefixLink: "pl"},
TargetPaths{TargetFilename: "/mydir/my.json", SubResourceBaseTarget: "/mydir", SubResourceBaseLink: "/mydir", Link: "/mydir/my.json"},
page.TargetPathDescriptor{Kind: page.KindPage, Type: output.JSONFormat, URL: "/mydir/my.json", ForcePrefix: false, PrefixFilePath: "pf", PrefixLink: "pl"},
page.TargetPaths{TargetFilename: "/mydir/my.json", SubResourceBaseTarget: "/mydir", SubResourceBaseLink: "/mydir", Link: "/mydir/my.json"},
},
{
"URL set, prefix both, force",
TargetPathDescriptor{Kind: KindPage, Type: output.JSONFormat, URL: "/mydir/my.json", ForcePrefix: true, PrefixFilePath: "pf", PrefixLink: "pl"},
TargetPaths{TargetFilename: "/pf/mydir/my.json", SubResourceBaseTarget: "/pf/mydir", SubResourceBaseLink: "/pl/mydir", Link: "/pl/mydir/my.json"},
page.TargetPathDescriptor{Kind: page.KindPage, Type: output.JSONFormat, URL: "/mydir/my.json", ForcePrefix: true, PrefixFilePath: "pf", PrefixLink: "pl"},
page.TargetPaths{TargetFilename: "/pf/mydir/my.json", SubResourceBaseTarget: "/pf/mydir", SubResourceBaseLink: "/pl/mydir", Link: "/pl/mydir/my.json"},
},
}
@@ -267,7 +268,7 @@ func TestPageTargetPathPrefix(t *testing.T) {
expected.TargetFilename = filepath.FromSlash(expected.TargetFilename)
expected.SubResourceBaseTarget = filepath.FromSlash(expected.SubResourceBaseTarget)
pagePath := CreateTargetPaths(test.d)
pagePath := page.CreateTargetPaths(test.d)
if pagePath != expected {
t.Fatalf("[%d] [%s] targetPath expected\n%#v, got:\n%#v", i, test.name, expected, pagePath)
@@ -276,7 +277,7 @@ func TestPageTargetPathPrefix(t *testing.T) {
}
}
func eqTargetPaths(p1, p2 TargetPaths) bool {
func eqTargetPaths(p1, p2 page.TargetPaths) bool {
if p1.Link != p2.Link {
return false
}

View File

@@ -31,7 +31,7 @@ import (
// FrontMatterHandler maps front matter into Page fields and .Params.
// Note that we currently have only extracted the date logic.
type FrontMatterHandler struct {
fmConfig frontmatterConfig
fmConfig FrontmatterConfig
dateHandler frontMatterFieldHandler
lastModHandler frontMatterFieldHandler
@@ -159,11 +159,15 @@ func (f FrontMatterHandler) newChainedFrontMatterFieldHandler(handlers ...frontM
}
}
type frontmatterConfig struct {
date []string
lastmod []string
publishDate []string
expiryDate []string
type FrontmatterConfig struct {
// Controls how the Date is set from front matter.
Date []string
// Controls how the Lastmod is set from front matter.
Lastmod []string
// Controls how the PublishDate is set from front matter.
PublishDate []string
// Controls how the ExpiryDate is set from front matter.
ExpiryDate []string
}
const (
@@ -185,16 +189,16 @@ const (
)
// This is the config you get when doing nothing.
func newDefaultFrontmatterConfig() frontmatterConfig {
return frontmatterConfig{
date: []string{fmDate, fmPubDate, fmLastmod},
lastmod: []string{fmGitAuthorDate, fmLastmod, fmDate, fmPubDate},
publishDate: []string{fmPubDate, fmDate},
expiryDate: []string{fmExpiryDate},
func newDefaultFrontmatterConfig() FrontmatterConfig {
return FrontmatterConfig{
Date: []string{fmDate, fmPubDate, fmLastmod},
Lastmod: []string{fmGitAuthorDate, fmLastmod, fmDate, fmPubDate},
PublishDate: []string{fmPubDate, fmDate},
ExpiryDate: []string{fmExpiryDate},
}
}
func newFrontmatterConfig(cfg config.Provider) (frontmatterConfig, error) {
func DecodeFrontMatterConfig(cfg config.Provider) (FrontmatterConfig, error) {
c := newDefaultFrontmatterConfig()
defaultConfig := c
@@ -204,13 +208,13 @@ func newFrontmatterConfig(cfg config.Provider) (frontmatterConfig, error) {
loki := strings.ToLower(k)
switch loki {
case fmDate:
c.date = toLowerSlice(v)
c.Date = toLowerSlice(v)
case fmPubDate:
c.publishDate = toLowerSlice(v)
c.PublishDate = toLowerSlice(v)
case fmLastmod:
c.lastmod = toLowerSlice(v)
c.Lastmod = toLowerSlice(v)
case fmExpiryDate:
c.expiryDate = toLowerSlice(v)
c.ExpiryDate = toLowerSlice(v)
}
}
}
@@ -221,10 +225,10 @@ func newFrontmatterConfig(cfg config.Provider) (frontmatterConfig, error) {
return out
}
c.date = expander(c.date, defaultConfig.date)
c.publishDate = expander(c.publishDate, defaultConfig.publishDate)
c.lastmod = expander(c.lastmod, defaultConfig.lastmod)
c.expiryDate = expander(c.expiryDate, defaultConfig.expiryDate)
c.Date = expander(c.Date, defaultConfig.Date)
c.PublishDate = expander(c.PublishDate, defaultConfig.PublishDate)
c.Lastmod = expander(c.Lastmod, defaultConfig.Lastmod)
c.ExpiryDate = expander(c.ExpiryDate, defaultConfig.ExpiryDate)
return c, nil
}
@@ -264,16 +268,11 @@ func toLowerSlice(in any) []string {
// NewFrontmatterHandler creates a new FrontMatterHandler with the given logger and configuration.
// If no logger is provided, one will be created.
func NewFrontmatterHandler(logger loggers.Logger, cfg config.Provider) (FrontMatterHandler, error) {
func NewFrontmatterHandler(logger loggers.Logger, frontMatterConfig FrontmatterConfig) (FrontMatterHandler, error) {
if logger == nil {
logger = loggers.NewErrorLogger()
}
frontMatterConfig, err := newFrontmatterConfig(cfg)
if err != nil {
return FrontMatterHandler{}, err
}
allDateKeys := make(map[string]bool)
addKeys := func(vals []string) {
for _, k := range vals {
@@ -283,10 +282,10 @@ func NewFrontmatterHandler(logger loggers.Logger, cfg config.Provider) (FrontMat
}
}
addKeys(frontMatterConfig.date)
addKeys(frontMatterConfig.expiryDate)
addKeys(frontMatterConfig.lastmod)
addKeys(frontMatterConfig.publishDate)
addKeys(frontMatterConfig.Date)
addKeys(frontMatterConfig.ExpiryDate)
addKeys(frontMatterConfig.Lastmod)
addKeys(frontMatterConfig.PublishDate)
f := FrontMatterHandler{logger: logger, fmConfig: frontMatterConfig, allDateKeys: allDateKeys}
@@ -300,7 +299,7 @@ func NewFrontmatterHandler(logger loggers.Logger, cfg config.Provider) (FrontMat
func (f *FrontMatterHandler) createHandlers() error {
var err error
if f.dateHandler, err = f.createDateHandler(f.fmConfig.date,
if f.dateHandler, err = f.createDateHandler(f.fmConfig.Date,
func(d *FrontMatterDescriptor, t time.Time) {
d.Dates.FDate = t
setParamIfNotSet(fmDate, t, d)
@@ -308,7 +307,7 @@ func (f *FrontMatterHandler) createHandlers() error {
return err
}
if f.lastModHandler, err = f.createDateHandler(f.fmConfig.lastmod,
if f.lastModHandler, err = f.createDateHandler(f.fmConfig.Lastmod,
func(d *FrontMatterDescriptor, t time.Time) {
setParamIfNotSet(fmLastmod, t, d)
d.Dates.FLastmod = t
@@ -316,7 +315,7 @@ func (f *FrontMatterHandler) createHandlers() error {
return err
}
if f.publishDateHandler, err = f.createDateHandler(f.fmConfig.publishDate,
if f.publishDateHandler, err = f.createDateHandler(f.fmConfig.PublishDate,
func(d *FrontMatterDescriptor, t time.Time) {
setParamIfNotSet(fmPubDate, t, d)
d.Dates.FPublishDate = t
@@ -324,7 +323,7 @@ func (f *FrontMatterHandler) createHandlers() error {
return err
}
if f.expiryDateHandler, err = f.createDateHandler(f.fmConfig.expiryDate,
if f.expiryDateHandler, err = f.createDateHandler(f.fmConfig.ExpiryDate,
func(d *FrontMatterDescriptor, t time.Time) {
setParamIfNotSet(fmExpiryDate, t, d)
d.Dates.FExpiryDate = t

View File

@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package pagemeta
package pagemeta_test
import (
"strings"
@@ -19,54 +19,20 @@ import (
"time"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/testconfig"
"github.com/gohugoio/hugo/resources/page/pagemeta"
"github.com/gohugoio/hugo/resources/resource"
qt "github.com/frankban/quicktest"
)
func TestDateAndSlugFromBaseFilename(t *testing.T) {
t.Parallel()
c := qt.New(t)
tests := []struct {
name string
date string
slug string
}{
{"page.md", "0001-01-01", ""},
{"2012-09-12-page.md", "2012-09-12", "page"},
{"2018-02-28-page.md", "2018-02-28", "page"},
{"2018-02-28_page.md", "2018-02-28", "page"},
{"2018-02-28 page.md", "2018-02-28", "page"},
{"2018-02-28page.md", "2018-02-28", "page"},
{"2018-02-28-.md", "2018-02-28", ""},
{"2018-02-28-.md", "2018-02-28", ""},
{"2018-02-28.md", "2018-02-28", ""},
{"2018-02-28-page", "2018-02-28", "page"},
{"2012-9-12-page.md", "0001-01-01", ""},
{"asdfasdf.md", "0001-01-01", ""},
}
for _, test := range tests {
expecteFDate, err := time.Parse("2006-01-02", test.date)
c.Assert(err, qt.IsNil)
gotDate, gotSlug := dateAndSlugFromBaseFilename(time.UTC, test.name)
c.Assert(gotDate, qt.Equals, expecteFDate)
c.Assert(gotSlug, qt.Equals, test.slug)
}
}
func newTestFd() *FrontMatterDescriptor {
return &FrontMatterDescriptor{
func newTestFd() *pagemeta.FrontMatterDescriptor {
return &pagemeta.FrontMatterDescriptor{
Frontmatter: make(map[string]any),
Params: make(map[string]any),
Dates: &resource.Dates{},
PageURLs: &URLPath{},
PageURLs: &pagemeta.URLPath{},
Location: time.UTC,
}
}
@@ -83,21 +49,21 @@ func TestFrontMatterNewConfig(t *testing.T) {
"publishDate": []string{"date"},
})
fc, err := newFrontmatterConfig(cfg)
fc, err := pagemeta.DecodeFrontMatterConfig(cfg)
c.Assert(err, qt.IsNil)
c.Assert(fc.date, qt.DeepEquals, []string{"publishdate", "pubdate", "published", "lastmod", "modified"})
c.Assert(fc.lastmod, qt.DeepEquals, []string{"publishdate", "pubdate", "published"})
c.Assert(fc.expiryDate, qt.DeepEquals, []string{"lastmod", "modified"})
c.Assert(fc.publishDate, qt.DeepEquals, []string{"date"})
c.Assert(fc.Date, qt.DeepEquals, []string{"publishdate", "pubdate", "published", "lastmod", "modified"})
c.Assert(fc.Lastmod, qt.DeepEquals, []string{"publishdate", "pubdate", "published"})
c.Assert(fc.ExpiryDate, qt.DeepEquals, []string{"lastmod", "modified"})
c.Assert(fc.PublishDate, qt.DeepEquals, []string{"date"})
// Default
cfg = config.New()
fc, err = newFrontmatterConfig(cfg)
fc, err = pagemeta.DecodeFrontMatterConfig(cfg)
c.Assert(err, qt.IsNil)
c.Assert(fc.date, qt.DeepEquals, []string{"date", "publishdate", "pubdate", "published", "lastmod", "modified"})
c.Assert(fc.lastmod, qt.DeepEquals, []string{":git", "lastmod", "modified", "date", "publishdate", "pubdate", "published"})
c.Assert(fc.expiryDate, qt.DeepEquals, []string{"expirydate", "unpublishdate"})
c.Assert(fc.publishDate, qt.DeepEquals, []string{"publishdate", "pubdate", "published", "date"})
c.Assert(fc.Date, qt.DeepEquals, []string{"date", "publishdate", "pubdate", "published", "lastmod", "modified"})
c.Assert(fc.Lastmod, qt.DeepEquals, []string{":git", "lastmod", "modified", "date", "publishdate", "pubdate", "published"})
c.Assert(fc.ExpiryDate, qt.DeepEquals, []string{"expirydate", "unpublishdate"})
c.Assert(fc.PublishDate, qt.DeepEquals, []string{"publishdate", "pubdate", "published", "date"})
// :default keyword
cfg.Set("frontmatter", map[string]any{
@@ -106,12 +72,12 @@ func TestFrontMatterNewConfig(t *testing.T) {
"expiryDate": []string{"d3", ":default"},
"publishDate": []string{"d4", ":default"},
})
fc, err = newFrontmatterConfig(cfg)
fc, err = pagemeta.DecodeFrontMatterConfig(cfg)
c.Assert(err, qt.IsNil)
c.Assert(fc.date, qt.DeepEquals, []string{"d1", "date", "publishdate", "pubdate", "published", "lastmod", "modified"})
c.Assert(fc.lastmod, qt.DeepEquals, []string{"d2", ":git", "lastmod", "modified", "date", "publishdate", "pubdate", "published"})
c.Assert(fc.expiryDate, qt.DeepEquals, []string{"d3", "expirydate", "unpublishdate"})
c.Assert(fc.publishDate, qt.DeepEquals, []string{"d4", "publishdate", "pubdate", "published", "date"})
c.Assert(fc.Date, qt.DeepEquals, []string{"d1", "date", "publishdate", "pubdate", "published", "lastmod", "modified"})
c.Assert(fc.Lastmod, qt.DeepEquals, []string{"d2", ":git", "lastmod", "modified", "date", "publishdate", "pubdate", "published"})
c.Assert(fc.ExpiryDate, qt.DeepEquals, []string{"d3", "expirydate", "unpublishdate"})
c.Assert(fc.PublishDate, qt.DeepEquals, []string{"d4", "publishdate", "pubdate", "published", "date"})
}
func TestFrontMatterDatesHandlers(t *testing.T) {
@@ -124,8 +90,8 @@ func TestFrontMatterDatesHandlers(t *testing.T) {
cfg.Set("frontmatter", map[string]any{
"date": []string{handlerID, "date"},
})
handler, err := NewFrontmatterHandler(nil, cfg)
conf := testconfig.GetTestConfig(nil, cfg)
handler, err := pagemeta.NewFrontmatterHandler(nil, conf.GetConfigSection("frontmatter").(pagemeta.FrontmatterConfig))
c.Assert(err, qt.IsNil)
d1, _ := time.Parse("2006-01-02", "2018-02-01")
@@ -166,7 +132,8 @@ func TestFrontMatterDatesCustomConfig(t *testing.T) {
"publishdate": []string{"publishdate"},
})
handler, err := NewFrontmatterHandler(nil, cfg)
conf := testconfig.GetTestConfig(nil, cfg)
handler, err := pagemeta.NewFrontmatterHandler(nil, conf.GetConfigSection("frontmatter").(pagemeta.FrontmatterConfig))
c.Assert(err, qt.IsNil)
testDate, err := time.Parse("2006-01-02", "2018-02-01")
@@ -213,7 +180,8 @@ func TestFrontMatterDatesDefaultKeyword(t *testing.T) {
"publishdate": []string{":default", "mypubdate"},
})
handler, err := NewFrontmatterHandler(nil, cfg)
conf := testconfig.GetTestConfig(nil, cfg)
handler, err := pagemeta.NewFrontmatterHandler(nil, conf.GetConfigSection("frontmatter").(pagemeta.FrontmatterConfig))
c.Assert(err, qt.IsNil)
testDate, _ := time.Parse("2006-01-02", "2018-02-01")
@@ -230,28 +198,3 @@ func TestFrontMatterDatesDefaultKeyword(t *testing.T) {
c.Assert(d.Dates.FPublishDate.Day(), qt.Equals, 4)
c.Assert(d.Dates.FExpiryDate.IsZero(), qt.Equals, true)
}
func TestExpandDefaultValues(t *testing.T) {
c := qt.New(t)
c.Assert(expandDefaultValues([]string{"a", ":default", "d"}, []string{"b", "c"}), qt.DeepEquals, []string{"a", "b", "c", "d"})
c.Assert(expandDefaultValues([]string{"a", "b", "c"}, []string{"a", "b", "c"}), qt.DeepEquals, []string{"a", "b", "c"})
c.Assert(expandDefaultValues([]string{":default", "a", ":default", "d"}, []string{"b", "c"}), qt.DeepEquals, []string{"b", "c", "a", "b", "c", "d"})
}
func TestFrontMatterDateFieldHandler(t *testing.T) {
t.Parallel()
c := qt.New(t)
handlers := new(frontmatterFieldHandlers)
fd := newTestFd()
d, _ := time.Parse("2006-01-02", "2018-02-01")
fd.Frontmatter["date"] = d
h := handlers.newDateFieldHandler("date", func(d *FrontMatterDescriptor, t time.Time) { d.Dates.FDate = t })
handled, err := h(fd)
c.Assert(handled, qt.Equals, true)
c.Assert(err, qt.IsNil)
c.Assert(fd.Dates.FDate, qt.Equals, d)
}

View File

@@ -16,6 +16,7 @@ package pagemeta
import (
"fmt"
"testing"
"time"
"github.com/gohugoio/hugo/htesting/hqt"
@@ -90,3 +91,46 @@ publishResources = true`
}
}
func TestDateAndSlugFromBaseFilename(t *testing.T) {
t.Parallel()
c := qt.New(t)
tests := []struct {
name string
date string
slug string
}{
{"page.md", "0001-01-01", ""},
{"2012-09-12-page.md", "2012-09-12", "page"},
{"2018-02-28-page.md", "2018-02-28", "page"},
{"2018-02-28_page.md", "2018-02-28", "page"},
{"2018-02-28 page.md", "2018-02-28", "page"},
{"2018-02-28page.md", "2018-02-28", "page"},
{"2018-02-28-.md", "2018-02-28", ""},
{"2018-02-28-.md", "2018-02-28", ""},
{"2018-02-28.md", "2018-02-28", ""},
{"2018-02-28-page", "2018-02-28", "page"},
{"2012-9-12-page.md", "0001-01-01", ""},
{"asdfasdf.md", "0001-01-01", ""},
}
for _, test := range tests {
expecteFDate, err := time.Parse("2006-01-02", test.date)
c.Assert(err, qt.IsNil)
gotDate, gotSlug := dateAndSlugFromBaseFilename(time.UTC, test.name)
c.Assert(gotDate, qt.Equals, expecteFDate)
c.Assert(gotSlug, qt.Equals, test.slug)
}
}
func TestExpandDefaultValues(t *testing.T) {
c := qt.New(t)
c.Assert(expandDefaultValues([]string{"a", ":default", "d"}, []string{"b", "c"}), qt.DeepEquals, []string{"a", "b", "c", "d"})
c.Assert(expandDefaultValues([]string{"a", "b", "c"}, []string{"a", "b", "c"}), qt.DeepEquals, []string{"a", "b", "c"})
c.Assert(expandDefaultValues([]string{":default", "a", ":default", "d"}, []string{"b", "c"}), qt.DeepEquals, []string{"b", "c", "a", "b", "c", "d"})
}

View File

@@ -50,6 +50,7 @@ func (p1 Pages) MergeByLanguage(p2 Pages) Pages {
// MergeByLanguageInterface is the generic version of MergeByLanguage. It
// is here just so it can be called from the tpl package.
// This is for internal use.
func (p1 Pages) MergeByLanguageInterface(in any) (any, error) {
if in == nil {
return p1, nil

View File

@@ -250,9 +250,9 @@ func splitPageGroups(pageGroups PagesGroup, size int) []paginatedElement {
return split
}
func ResolvePagerSize(cfg config.Provider, options ...any) (int, error) {
func ResolvePagerSize(conf config.AllProvider, options ...any) (int, error) {
if len(options) == 0 {
return cfg.GetInt("paginate"), nil
return conf.Paginate(), nil
}
if len(options) > 1 {
@@ -389,7 +389,7 @@ func newPaginationURLFactory(d TargetPathDescriptor) paginationURLFactory {
pathDescriptor := d
var rel string
if pageNumber > 1 {
rel = fmt.Sprintf("/%s/%d/", d.PathSpec.PaginatePath, pageNumber)
rel = fmt.Sprintf("/%s/%d/", d.PathSpec.Cfg.PaginatePath(), pageNumber)
pathDescriptor.Addends = rel
}

View File

@@ -19,10 +19,7 @@ import (
"html/template"
"testing"
"github.com/gohugoio/hugo/config"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/output"
)
func TestSplitPages(t *testing.T) {
@@ -194,58 +191,6 @@ func doTestPagerNoPages(t *testing.T, paginator *Paginator) {
c.Assert(pageOne.PageSize(), qt.Equals, 5)
}
func TestPaginationURLFactory(t *testing.T) {
t.Parallel()
c := qt.New(t)
cfg := config.New()
cfg.Set("paginatePath", "zoo")
for _, uglyURLs := range []bool{false, true} {
c.Run(fmt.Sprintf("uglyURLs=%t", uglyURLs), func(c *qt.C) {
tests := []struct {
name string
d TargetPathDescriptor
baseURL string
page int
expected string
expectedUgly string
}{
{
"HTML home page 32",
TargetPathDescriptor{Kind: KindHome, Type: output.HTMLFormat},
"http://example.com/", 32, "/zoo/32/", "/zoo/32.html",
},
{
"JSON home page 42",
TargetPathDescriptor{Kind: KindHome, Type: output.JSONFormat},
"http://example.com/", 42, "/zoo/42/index.json", "/zoo/42.json",
},
}
for _, test := range tests {
d := test.d
cfg.Set("baseURL", test.baseURL)
cfg.Set("uglyURLs", uglyURLs)
d.UglyURLs = uglyURLs
pathSpec := newTestPathSpecFor(cfg)
d.PathSpec = pathSpec
factory := newPaginationURLFactory(d)
got := factory(test.page)
if uglyURLs {
c.Assert(got, qt.Equals, test.expectedUgly)
} else {
c.Assert(got, qt.Equals, test.expected)
}
}
})
}
}
func TestProbablyEqualPageLists(t *testing.T) {
t.Parallel()
fivePages := createTestPages(5)

View File

@@ -37,7 +37,7 @@ type PermalinkExpander struct {
expanders map[string]func(Page) (string, error)
ps *helpers.PathSpec
urlize func(uri string) string
}
// Time for checking date formats. Every field is different than the
@@ -67,9 +67,9 @@ func (p PermalinkExpander) callback(attr string) (pageToPermaAttribute, bool) {
}
// NewPermalinkExpander creates a new PermalinkExpander configured by the given
// PathSpec.
func NewPermalinkExpander(ps *helpers.PathSpec) (PermalinkExpander, error) {
p := PermalinkExpander{ps: ps}
// urlize func.
func NewPermalinkExpander(urlize func(uri string) string, patterns map[string]string) (PermalinkExpander, error) {
p := PermalinkExpander{urlize: urlize}
p.knownPermalinkAttributes = map[string]pageToPermaAttribute{
"year": p.pageToPermalinkDate,
@@ -87,11 +87,6 @@ func NewPermalinkExpander(ps *helpers.PathSpec) (PermalinkExpander, error) {
"filename": p.pageToPermalinkFilename,
}
patterns := ps.Cfg.GetStringMapString("permalinks")
if patterns == nil {
return p, nil
}
e, err := p.parse(patterns)
if err != nil {
return p, err
@@ -180,6 +175,9 @@ var attributeRegexp = regexp.MustCompile(`:\w+(\[.+?\])?`)
// validate determines if a PathPattern is well-formed
func (l PermalinkExpander) validate(pp string) bool {
if len(pp) == 0 {
return false
}
fragments := strings.Split(pp[1:], "/")
bail := false
for i := range fragments {
@@ -244,7 +242,7 @@ func (l PermalinkExpander) pageToPermalinkDate(p Page, dateField string) (string
// pageToPermalinkTitle returns the URL-safe form of the title
func (l PermalinkExpander) pageToPermalinkTitle(p Page, _ string) (string, error) {
return l.ps.URLize(p.Title()), nil
return l.urlize(p.Title()), nil
}
// pageToPermalinkFilename returns the URL-safe form of the filename
@@ -256,13 +254,13 @@ func (l PermalinkExpander) pageToPermalinkFilename(p Page, _ string) (string, er
_, name = filepath.Split(dir)
}
return l.ps.URLize(name), nil
return l.urlize(name), nil
}
// if the page has a slug, return the slug, else return the title
func (l PermalinkExpander) pageToPermalinkSlugElseTitle(p Page, a string) (string, error) {
if p.Slug() != "" {
return l.ps.URLize(p.Slug()), nil
return l.urlize(p.Slug()), nil
}
return l.pageToPermalinkTitle(p, a)
}
@@ -270,7 +268,7 @@ func (l PermalinkExpander) pageToPermalinkSlugElseTitle(p Page, a string) (strin
// if the page has a slug, return the slug, else return the filename
func (l PermalinkExpander) pageToPermalinkSlugElseFilename(p Page, a string) (string, error) {
if p.Slug() != "" {
return l.ps.URLize(p.Slug()), nil
return l.urlize(p.Slug()), nil
}
return l.pageToPermalinkFilename(p, a)
}

View File

@@ -1,4 +1,4 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
// Copyright 2023 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.
@@ -16,6 +16,7 @@ package page
import (
"fmt"
"regexp"
"strings"
"sync"
"testing"
"time"
@@ -52,6 +53,11 @@ var testdataPermalinks = []struct {
{"/:2006-01-02", false, ""}, // valid date format but invalid attribute name
}
func urlize(uri string) string {
// This is just an approximation of the real urlize function.
return strings.ToLower(strings.ReplaceAll(uri, " ", "-"))
}
func TestPermalinkExpansion(t *testing.T) {
t.Parallel()
@@ -73,17 +79,11 @@ func TestPermalinkExpansion(t *testing.T) {
name := specNameCleaner.ReplaceAllString(item.spec, "")
c.Run(name, func(c *qt.C) {
permalinksConfig := map[string]string{
patterns := map[string]string{
"posts": item.spec,
}
ps := newTestPathSpec()
ps.Cfg.Set("permalinks", permalinksConfig)
expander, err := NewPermalinkExpander(ps)
expander, err := NewPermalinkExpander(urlize, patterns)
c.Assert(err, qt.IsNil)
expanded, err := expander.Expand("posts", page)
c.Assert(err, qt.IsNil)
c.Assert(expanded, qt.Equals, item.expandsTo)
@@ -112,11 +112,7 @@ func TestPermalinkExpansionMultiSection(t *testing.T) {
"blog": "/:section/:year",
"recipes": "/:slugorfilename",
}
ps := newTestPathSpec()
ps.Cfg.Set("permalinks", permalinksConfig)
expander, err := NewPermalinkExpander(ps)
expander, err := NewPermalinkExpander(urlize, permalinksConfig)
c.Assert(err, qt.IsNil)
expanded, err := expander.Expand("posts", page)
@@ -145,10 +141,7 @@ func TestPermalinkExpansionConcurrent(t *testing.T) {
"posts": "/:slug/",
}
ps := newTestPathSpec()
ps.Cfg.Set("permalinks", permalinksConfig)
expander, err := NewPermalinkExpander(ps)
expander, err := NewPermalinkExpander(urlize, permalinksConfig)
c.Assert(err, qt.IsNil)
var wg sync.WaitGroup
@@ -174,7 +167,8 @@ func TestPermalinkExpansionSliceSyntax(t *testing.T) {
t.Parallel()
c := qt.New(t)
exp, _ := NewPermalinkExpander(newTestPathSpec())
exp, err := NewPermalinkExpander(urlize, nil)
c.Assert(err, qt.IsNil)
slice := []string{"a", "b", "c", "d"}
fn := func(s string) []string {
return exp.toSliceFunc(s)(slice)
@@ -219,11 +213,7 @@ func BenchmarkPermalinkExpand(b *testing.B) {
permalinksConfig := map[string]string{
"posts": "/:year-:month-:title",
}
ps := newTestPathSpec()
ps.Cfg.Set("permalinks", permalinksConfig)
expander, err := NewPermalinkExpander(ps)
expander, err := NewPermalinkExpander(urlize, permalinksConfig)
if err != nil {
b.Fatal(err)
}

View File

@@ -18,7 +18,10 @@ import (
"time"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/config/privacy"
"github.com/gohugoio/hugo/config/services"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/tpl"
"github.com/gohugoio/hugo/config"
@@ -32,13 +35,15 @@ type Site interface {
// Returns the Language configured for this Site.
Language() *langs.Language
GetPage(ref ...string) (Page, error)
// Returns all the regular Pages in this Site.
RegularPages() Pages
// Returns all Pages in this Site.
Pages() Pages
// A shortcut to the home page.
// A shortcut to the home
Home() Page
// Returns true if we're running in a server.
@@ -50,6 +55,12 @@ type Site interface {
// Returns the configured title for this Site.
Title() string
// Returns the configured language code for this Site.
LanguageCode() string
// Returns the configured copyright information for this Site.
Copyright() string
// Returns all Sites for all languages.
Sites() Sites
@@ -57,7 +68,7 @@ type Site interface {
Current() Site
// Returns a struct with some information about the build.
Hugo() hugo.Info
Hugo() hugo.HugoInfo
// Returns the BaseURL for this Site.
BaseURL() template.URL
@@ -71,14 +82,36 @@ type Site interface {
// Returns the Menus for this site.
Menus() navigation.Menus
// The main sections in the site.
MainSections() []string
// Returns the Params configured for this site.
Params() maps.Params
// Returns a map of all the data inside /data.
Data() map[string]any
// Returns the site config.
Config() SiteConfig
// Returns the identity of this site.
// This is for internal use only.
GetIdentity() identity.Identity
// Author is deprecated and will be removed in a future release.
Author() map[string]interface{}
// Returns the social links for this site.
Social() map[string]string
// Deprecated: Use Config().Services.GoogleAnalytics instead.
GoogleAnalytics() string
// Deprecated: Use Config().Privacy.Disqus instead.
DisqusShortname() string
// For internal use only.
GetPageWithTemplateInfo(info tpl.Info, ref ...string) (Page, error)
}
// Sites represents an ordered list of sites (languages).
@@ -92,12 +125,139 @@ func (s Sites) First() Site {
return s[0]
}
type siteWrapper struct {
s Site
}
func WrapSite(s Site) Site {
if s == nil {
panic("Site is nil")
}
return &siteWrapper{s: s}
}
func (s *siteWrapper) Social() map[string]string {
return s.s.Social()
}
func (s *siteWrapper) Author() map[string]interface{} {
return s.s.Author()
}
func (s *siteWrapper) GoogleAnalytics() string {
return s.s.GoogleAnalytics()
}
func (s *siteWrapper) GetPage(ref ...string) (Page, error) {
return s.s.GetPage(ref...)
}
func (s *siteWrapper) Language() *langs.Language {
return s.s.Language()
}
func (s *siteWrapper) RegularPages() Pages {
return s.s.RegularPages()
}
func (s *siteWrapper) Pages() Pages {
return s.s.Pages()
}
func (s *siteWrapper) Home() Page {
return s.s.Home()
}
func (s *siteWrapper) IsServer() bool {
return s.s.IsServer()
}
func (s *siteWrapper) ServerPort() int {
return s.s.ServerPort()
}
func (s *siteWrapper) Title() string {
return s.s.Title()
}
func (s *siteWrapper) LanguageCode() string {
return s.s.LanguageCode()
}
func (s *siteWrapper) Copyright() string {
return s.s.Copyright()
}
func (s *siteWrapper) Sites() Sites {
return s.s.Sites()
}
func (s *siteWrapper) Current() Site {
return s.s.Current()
}
func (s *siteWrapper) Config() SiteConfig {
return s.s.Config()
}
func (s *siteWrapper) Hugo() hugo.HugoInfo {
return s.s.Hugo()
}
func (s *siteWrapper) BaseURL() template.URL {
return s.s.BaseURL()
}
func (s *siteWrapper) Taxonomies() TaxonomyList {
return s.s.Taxonomies()
}
func (s *siteWrapper) LastChange() time.Time {
return s.s.LastChange()
}
func (s *siteWrapper) Menus() navigation.Menus {
return s.s.Menus()
}
func (s *siteWrapper) MainSections() []string {
return s.s.MainSections()
}
func (s *siteWrapper) Params() maps.Params {
return s.s.Params()
}
func (s *siteWrapper) Data() map[string]any {
return s.s.Data()
}
func (s *siteWrapper) GetIdentity() identity.Identity {
return s.s.GetIdentity()
}
func (s *siteWrapper) GetPageWithTemplateInfo(info tpl.Info, ref ...string) (Page, error) {
return s.s.GetPageWithTemplateInfo(info, ref...)
}
func (s *siteWrapper) DisqusShortname() string {
return s.s.DisqusShortname()
}
type testSite struct {
h hugo.Info
h hugo.HugoInfo
l *langs.Language
}
func (t testSite) Hugo() hugo.Info {
func (s testSite) Author() map[string]interface{} {
return nil
}
func (s testSite) Social() map[string]string {
return make(map[string]string)
}
func (t testSite) Hugo() hugo.HugoInfo {
return t.h
}
@@ -113,14 +273,34 @@ func (t testSite) Title() string {
return "foo"
}
func (t testSite) LanguageCode() string {
return t.l.Lang
}
func (t testSite) Copyright() string {
return ""
}
func (t testSite) Sites() Sites {
return nil
}
func (t testSite) GetPage(ref ...string) (Page, error) {
return nil, nil
}
func (t testSite) Current() Site {
return t
}
func (t testSite) GoogleAnalytics() string {
return ""
}
func (t testSite) MainSections() []string {
return nil
}
func (t testSite) GetIdentity() identity.Identity {
return identity.KeyValueIdentity{Key: "site", Value: t.l.Lang}
}
@@ -165,10 +345,34 @@ func (t testSite) Data() map[string]any {
return nil
}
func (s testSite) Config() SiteConfig {
return SiteConfig{}
}
func (testSite) GetPageWithTemplateInfo(info tpl.Info, ref ...string) (Page, error) {
return nil, nil
}
func (testSite) DisqusShortname() string {
return ""
}
// NewDummyHugoSite creates a new minimal test site.
func NewDummyHugoSite(cfg config.Provider) Site {
return testSite{
h: hugo.NewInfo(hugo.EnvironmentProduction, nil),
l: langs.NewLanguage("en", cfg),
l: &langs.Language{
Lang: "en",
},
}
}
// SiteConfig holds the config in site.Config.
type SiteConfig struct {
// This contains all privacy related settings that can be used to
// make the YouTube template etc. GDPR compliant.
Privacy privacy.Config
// Services contains config for services such as Google Analytics etc.
Services services.Config
}

View File

@@ -0,0 +1,38 @@
// Copyright 2023 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 page_test
import (
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/testconfig"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/afero"
)
func newTestPathSpec() *helpers.PathSpec {
return newTestPathSpecFor(config.New())
}
func newTestPathSpecFor(cfg config.Provider) *helpers.PathSpec {
mfs := afero.NewMemMapFs()
conf := testconfig.GetTestConfig(mfs, cfg)
fs := hugofs.NewFrom(mfs, conf.BaseConfig())
ps, err := helpers.NewPathSpec(fs, conf, loggers.NewErrorLogger())
if err != nil {
panic(err)
}
return ps
}

View File

@@ -1,4 +1,4 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
// Copyright 2023 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.
@@ -26,9 +26,6 @@ import (
"github.com/gohugoio/hugo/markup/tableofcontents"
"github.com/gohugoio/hugo/tpl"
"github.com/gohugoio/hugo/modules"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/resources/resource"
"github.com/gohugoio/hugo/navigation"
@@ -58,6 +55,19 @@ func newTestPage() *testPage {
func newTestPageWithFile(filename string) *testPage {
filename = filepath.FromSlash(filename)
file := source.NewTestFile(filename)
l, err := langs.NewLanguage(
"en",
"en",
"UTC",
langs.LanguageConfig{
LanguageName: "English",
},
)
if err != nil {
panic(err)
}
return &testPage{
params: make(map[string]any),
data: make(map[string]any),
@@ -65,30 +75,10 @@ func newTestPageWithFile(filename string) *testPage {
currentSection: &testPage{
sectionEntries: []string{"a", "b", "c"},
},
site: testSite{l: langs.NewDefaultLanguage(config.New())},
site: testSite{l: l},
}
}
func newTestPathSpec() *helpers.PathSpec {
return newTestPathSpecFor(config.New())
}
func newTestPathSpecFor(cfg config.Provider) *helpers.PathSpec {
config.SetBaseTestDefaults(cfg)
langs.LoadLanguageSettings(cfg, nil)
mod, err := modules.CreateProjectModule(cfg)
if err != nil {
panic(err)
}
cfg.Set("allModules", modules.Modules{mod})
fs := hugofs.NewMem(cfg)
s, err := helpers.NewPathSpec(fs, cfg, nil)
if err != nil {
panic(err)
}
return s
}
type testPage struct {
kind string
description string
@@ -128,15 +118,15 @@ func (p *testPage) Err() resource.ResourceError {
}
func (p *testPage) Aliases() []string {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) AllTranslations() Pages {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) AlternativeOutputFormats() OutputFormats {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Author() Author {
@@ -148,19 +138,19 @@ func (p *testPage) Authors() AuthorList {
}
func (p *testPage) BaseFileName() string {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) BundleType() files.ContentClass {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Content(context.Context) (any, error) {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) ContentBaseName() string {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) CurrentSection() Page {
@@ -171,8 +161,8 @@ func (p *testPage) Data() any {
return p.data
}
func (p *testPage) Sitemap() config.Sitemap {
return config.Sitemap{}
func (p *testPage) Sitemap() config.SitemapConfig {
return config.SitemapConfig{}
}
func (p *testPage) Layout() string {
@@ -188,11 +178,11 @@ func (p *testPage) Description() string {
}
func (p *testPage) Dir() string {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Draft() bool {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Eq(other any) bool {
@@ -204,11 +194,11 @@ func (p *testPage) ExpiryDate() time.Time {
}
func (p *testPage) Ext() string {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Extension() string {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) File() source.File {
@@ -216,15 +206,15 @@ func (p *testPage) File() source.File {
}
func (p *testPage) FileInfo() hugofs.FileMetaInfo {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Filename() string {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) FirstSection() Page {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) FuzzyWordCount(context.Context) int {
@@ -232,19 +222,19 @@ func (p *testPage) FuzzyWordCount(context.Context) int {
}
func (p *testPage) GetPage(ref string) (Page, error) {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) GetPageWithTemplateInfo(info tpl.Info, ref string) (Page, error) {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) GetParam(key string) any {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) GetTerms(taxonomy string) Pages {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) GetRelatedDocsHandler() *RelatedDocsHandler {
@@ -260,27 +250,27 @@ func (p *testPage) CodeOwners() []string {
}
func (p *testPage) HasMenuCurrent(menuID string, me *navigation.MenuEntry) bool {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) HasShortcode(name string) bool {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Hugo() hugo.Info {
panic("not implemented")
func (p *testPage) Hugo() hugo.HugoInfo {
panic("tespage: not implemented")
}
func (p *testPage) InSection(other any) (bool, error) {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) IsAncestor(other any) (bool, error) {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) IsDescendant(other any) (bool, error) {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) IsDraft() bool {
@@ -288,27 +278,27 @@ func (p *testPage) IsDraft() bool {
}
func (p *testPage) IsHome() bool {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) IsMenuCurrent(menuID string, inme *navigation.MenuEntry) bool {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) IsNode() bool {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) IsPage() bool {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) IsSection() bool {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) IsTranslated() bool {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Keywords() []string {
@@ -324,7 +314,7 @@ func (p *testPage) Lang() string {
}
func (p *testPage) Language() *langs.Language {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) LanguagePrefix() string {
@@ -358,11 +348,11 @@ func (p *testPage) LinkTitle() string {
}
func (p *testPage) LogicalName() string {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) MediaType() media.Type {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Menus() navigation.PageMenus {
@@ -370,11 +360,11 @@ func (p *testPage) Menus() navigation.PageMenus {
}
func (p *testPage) Name() string {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Next() Page {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) NextInSection() Page {
@@ -386,19 +376,19 @@ func (p *testPage) NextPage() Page {
}
func (p *testPage) OutputFormats() OutputFormats {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Pages() Pages {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) RegularPages() Pages {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) RegularPagesRecursive() Pages {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Paginate(seq any, options ...any) (*Pager, error) {
@@ -422,11 +412,11 @@ func (p *testPage) Page() Page {
}
func (p *testPage) Parent() Page {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Ancestors() Pages {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Path() string {
@@ -438,19 +428,19 @@ func (p *testPage) Pathc() string {
}
func (p *testPage) Permalink() string {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Plain(context.Context) string {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) PlainWords(context.Context) []string {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Prev() Page {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) PrevInSection() Page {
@@ -470,15 +460,15 @@ func (p *testPage) RSSLink() template.URL {
}
func (p *testPage) RawContent() string {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) ReadingTime(context.Context) int {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Ref(argsm map[string]any) (string, error) {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) RefFrom(argsm map[string]any, source any) (string, error) {
@@ -486,11 +476,11 @@ func (p *testPage) RefFrom(argsm map[string]any, source any) (string, error) {
}
func (p *testPage) RelPermalink() string {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) RelRef(argsm map[string]any) (string, error) {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) RelRefFrom(argsm map[string]any, source any) (string, error) {
@@ -498,27 +488,27 @@ func (p *testPage) RelRefFrom(argsm map[string]any, source any) (string, error)
}
func (p *testPage) Render(ctx context.Context, layout ...string) (template.HTML, error) {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) RenderString(ctx context.Context, args ...any) (template.HTML, error) {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) ResourceType() string {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Resources() resource.Resources {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Scratch() *maps.Scratch {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Store() *maps.Scratch {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) RelatedKeywords(cfg related.IndexConfig) ([]related.Keyword, error) {
@@ -535,7 +525,7 @@ func (p *testPage) Section() string {
}
func (p *testPage) Sections() Pages {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) SectionsEntries() []string {
@@ -551,7 +541,7 @@ func (p *testPage) Site() Site {
}
func (p *testPage) Sites() Sites {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Slug() string {
@@ -563,11 +553,11 @@ func (p *testPage) String() string {
}
func (p *testPage) Summary(context.Context) template.HTML {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) TableOfContents(context.Context) template.HTML {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Title() string {
@@ -575,7 +565,7 @@ func (p *testPage) Title() string {
}
func (p *testPage) TranslationBaseName() string {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) TranslationKey() string {
@@ -583,11 +573,11 @@ func (p *testPage) TranslationKey() string {
}
func (p *testPage) Translations() Pages {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Truncated(context.Context) bool {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Type() string {
@@ -599,7 +589,7 @@ func (p *testPage) URL() string {
}
func (p *testPage) UniqueID() string {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) Weight() int {
@@ -607,11 +597,11 @@ func (p *testPage) Weight() int {
}
func (p *testPage) WordCount(context.Context) int {
panic("not implemented")
panic("tespage: not implemented")
}
func (p *testPage) GetIdentity() identity.Identity {
panic("not implemented")
panic("tespage: not implemented")
}
func createTestPages(num int) Pages {