resources/page: Revise the new contentbasename permalinks tokens
* Make it work for all pages, including those created from content adapters and not backed by a file. * Allow the `slug` to win, so the new tokens are: `:contentbasename`: 1. ContentBaseName `:slugorcontentbasename`: 1. Slug 2. ContentBaseName Note that a page will always have a `ContentBaseName`, so no need to fall back to e.g. the title. Closes #11722
This commit is contained in:
parent
cb7a4339b7
commit
7b7a0f3624
6 changed files with 145 additions and 60 deletions
|
@ -317,14 +317,6 @@ Use these tokens when defining the URL pattern. You can also use these tokens wh
|
|||
`:slugorfilename`
|
||||
: The slug as defined in front matter, else the content's file name without extension, applicable to the `page` page kind.
|
||||
|
||||
`:contentbasename`
|
||||
: The content base name, as defined in [`File.ContentBaseName`], applicable to pages backed by a file.
|
||||
|
||||
`:contentbasenameorslug`
|
||||
: The content base name, else the slug as defined above.
|
||||
|
||||
[`File.ContentBaseName`]: /methods/page/file/#contentbasename
|
||||
|
||||
For time-related values, you can also use the layout string components defined in Go's [time package]. For example:
|
||||
|
||||
[time package]: https://pkg.go.dev/time#pkg-constants
|
||||
|
|
|
@ -93,7 +93,7 @@ func NewPermalinkExpander(urlize func(uri string) string, patterns map[string]ma
|
|||
"slugorfilename": p.pageToPermalinkSlugElseFilename,
|
||||
"filename": p.pageToPermalinkFilename,
|
||||
"contentbasename": p.pageToPermalinkContentBaseName,
|
||||
"contentbasenameorslug": p.pageToPermalinkContentBaseNameOrSlug,
|
||||
"slugorcontentbasename": p.pageToPermalinkSlugOrContentBaseName,
|
||||
}
|
||||
|
||||
p.expanders = make(map[string]map[string]func(Page) (string, error))
|
||||
|
@ -311,22 +311,19 @@ func (l PermalinkExpander) pageToPermalinkSections(p Page, _ string) (string, er
|
|||
|
||||
// pageToPermalinkContentBaseName returns the URL-safe form of the content base name.
|
||||
func (l PermalinkExpander) pageToPermalinkContentBaseName(p Page, _ string) (string, error) {
|
||||
if p.File() == nil {
|
||||
return "", nil
|
||||
}
|
||||
return l.urlize(p.File().ContentBaseName()), nil
|
||||
return l.urlize(p.PathInfo().BaseNameNoIdentifier()), nil
|
||||
}
|
||||
|
||||
// pageToPermalinkContentBaseNameOrSlug returns the URL-safe form of the content base name, or the slug.
|
||||
func (l PermalinkExpander) pageToPermalinkContentBaseNameOrSlug(p Page, a string) (string, error) {
|
||||
// pageToPermalinkSlugOrContentBaseName returns the URL-safe form of the slug, content base name.
|
||||
func (l PermalinkExpander) pageToPermalinkSlugOrContentBaseName(p Page, a string) (string, error) {
|
||||
if p.Slug() != "" {
|
||||
return l.urlize(p.Slug()), nil
|
||||
}
|
||||
name, err := l.pageToPermalinkContentBaseName(p, a)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
if name != "" {
|
||||
return name, nil
|
||||
}
|
||||
return l.pageToPermalinkSlugElseTitle(p, a)
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func (l PermalinkExpander) translationBaseName(p Page) string {
|
||||
|
|
|
@ -277,3 +277,69 @@ title: p2
|
|||
// We strip colons from paths constructed by Hugo (they are not supported on Windows).
|
||||
b.AssertFileExists("public/cd/p2/index.html", true)
|
||||
}
|
||||
|
||||
func TestPermalinksContentbasenameContentAdapter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
[permalinks]
|
||||
[permalinks.page]
|
||||
a = "/:slugorcontentbasename/"
|
||||
b = "/:sections/:contentbasename/"
|
||||
-- content/_content.gotmpl --
|
||||
{{ $.AddPage (dict "kind" "page" "path" "a/b/contentbasename1" "title" "My A Page No Slug") }}
|
||||
{{ $.AddPage (dict "kind" "page" "path" "a/b/contentbasename2" "slug" "myslug" "title" "My A Page With Slug") }}
|
||||
{{ $.AddPage (dict "kind" "section" "path" "b/c" "title" "My B Section") }}
|
||||
{{ $.AddPage (dict "kind" "page" "path" "b/c/contentbasename3" "title" "My B Page No Slug") }}
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}|{{ .RelPermalink }}|{{ .Kind }}|
|
||||
`
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/contentbasename1/index.html", "My A Page No Slug|/contentbasename1/|page|")
|
||||
b.AssertFileContent("public/myslug/index.html", "My A Page With Slug|/myslug/|page|")
|
||||
}
|
||||
|
||||
func TestPermalinksContentbasenameWithAndWithoutFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
[permalinks.section]
|
||||
a = "/mya/:contentbasename/"
|
||||
[permalinks.page]
|
||||
a = "/myapage/:contentbasename/"
|
||||
[permalinks.term]
|
||||
categories = "/myc/:slugorcontentbasename/"
|
||||
-- content/b/c/_index.md --
|
||||
---
|
||||
title: "C section"
|
||||
---
|
||||
-- content/a/b/index.md --
|
||||
---
|
||||
title: "My Title"
|
||||
categories: ["c1", "c2"]
|
||||
---
|
||||
-- content/categories/c2/_index.md --
|
||||
---
|
||||
title: "C2"
|
||||
slug: "c2slug"
|
||||
---
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}|{{ .RelPermalink }}|{{ .Kind }}|
|
||||
-- layouts/_default/list.html --
|
||||
{{ .Title }}|{{ .RelPermalink }}|{{ .Kind }}|
|
||||
`
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
// Sections.
|
||||
b.AssertFileContent("public/mya/a/index.html", "As|/mya/a/|section|")
|
||||
|
||||
// Pages.
|
||||
b.AssertFileContent("public/myapage/b/index.html", "My Title|/myapage/b/|page|")
|
||||
|
||||
// Taxonomies.
|
||||
b.AssertFileContent("public/myc/c1/index.html", "C1|/myc/c1/|term|")
|
||||
b.AssertFileContent("public/myc/c2slug/index.html", "C2|/myc/c2slug/|term|")
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"time"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/source"
|
||||
)
|
||||
|
||||
// testdataPermalinks is used by a couple of tests; the expandsTo content is
|
||||
|
@ -29,32 +30,44 @@ import (
|
|||
var testdataPermalinks = []struct {
|
||||
spec string
|
||||
valid bool
|
||||
withPage func(p *testPage)
|
||||
expandsTo string
|
||||
}{
|
||||
{":title", true, "spf13-vim-3.0-release-and-new-website"},
|
||||
{"/:year-:month-:title", true, "/2012-04-spf13-vim-3.0-release-and-new-website"},
|
||||
{"/:year/:yearday/:month/:monthname/:day/:weekday/:weekdayname/", true, "/2012/97/04/April/06/5/Friday/"}, // Dates
|
||||
{"/:section/", true, "/blue/"}, // Section
|
||||
{"/:title/", true, "/spf13-vim-3.0-release-and-new-website/"}, // Title
|
||||
{"/:slug/", true, "/the-slug/"}, // Slug
|
||||
{"/:slugorfilename/", true, "/the-slug/"}, // Slug or filename
|
||||
{"/:filename/", true, "/test-page/"}, // Filename
|
||||
{"/:06-:1-:2-:Monday", true, "/12-4-6-Friday"}, // Dates with Go formatting
|
||||
{"/:2006_01_02_15_04_05.000", true, "/2012_04_06_03_01_59.000"}, // Complicated custom date format
|
||||
{"/:sections/", true, "/a/b/c/"}, // Sections
|
||||
{"/:sections[last]/", true, "/c/"}, // Sections
|
||||
{"/:sections[0]/:sections[last]/", true, "/a/c/"}, // Sections
|
||||
{"/\\:filename", true, "/:filename"}, // Escape sequence
|
||||
{"/special\\::slug/", true, "/special:the-slug/"}, // Escape sequence
|
||||
{"/:contentbasename/", true, "/index/"}, // Content base name
|
||||
{"/:contentbasenameorslug/", true, "/index/"}, // Content base name or slug
|
||||
|
||||
{":title", true, nil, "spf13-vim-3.0-release-and-new-website"},
|
||||
{"/:year-:month-:title", true, nil, "/2012-04-spf13-vim-3.0-release-and-new-website"},
|
||||
{"/:year/:yearday/:month/:monthname/:day/:weekday/:weekdayname/", true, nil, "/2012/97/04/April/06/5/Friday/"}, // Dates
|
||||
{"/:section/", true, nil, "/blue/"}, // Section
|
||||
{"/:title/", true, nil, "/spf13-vim-3.0-release-and-new-website/"}, // Title
|
||||
{"/:slug/", true, nil, "/the-slug/"}, // Slug
|
||||
{"/:slugorfilename/", true, nil, "/the-slug/"}, // Slug or filename
|
||||
{"/:filename/", true, nil, "/test-page/"}, // Filename
|
||||
{"/:06-:1-:2-:Monday", true, nil, "/12-4-6-Friday"}, // Dates with Go formatting
|
||||
{"/:2006_01_02_15_04_05.000", true, nil, "/2012_04_06_03_01_59.000"}, // Complicated custom date format
|
||||
{"/:sections/", true, nil, "/a/b/c/"}, // Sections
|
||||
{"/:sections[last]/", true, nil, "/c/"}, // Sections
|
||||
{"/:sections[0]/:sections[last]/", true, nil, "/a/c/"}, // Sections
|
||||
{"/\\:filename", true, nil, "/:filename"}, // Escape sequence
|
||||
{"/special\\::slug/", true, nil, "/special:the-slug/"},
|
||||
// contentbasename. // Escape sequence
|
||||
{"/:contentbasename/", true, nil, "/test-page/"},
|
||||
// slug, contentbasename. // Content base name
|
||||
{"/:slugorcontentbasename/", true, func(p *testPage) {
|
||||
p.slug = ""
|
||||
}, "/test-page/"},
|
||||
{"/:slugorcontentbasename/", true, func(p *testPage) {
|
||||
p.slug = "myslug"
|
||||
}, "/myslug/"},
|
||||
{"/:slugorcontentbasename/", true, func(p *testPage) {
|
||||
p.slug = ""
|
||||
p.title = "mytitle"
|
||||
p.file = source.NewContentFileInfoFrom("/", "_index.md")
|
||||
}, "/test-page/"},
|
||||
// Failures
|
||||
{"/blog/:fred", false, ""},
|
||||
{"/:year//:title", false, ""},
|
||||
{"/:TITLE", false, ""}, // case is not normalized
|
||||
{"/:2017", false, ""}, // invalid date format
|
||||
{"/:2006-01-02", false, ""}, // valid date format but invalid attribute name
|
||||
{"/blog/:fred", false, nil, ""},
|
||||
{"/:year//:title", false, nil, ""},
|
||||
{"/:TITLE", false, nil, ""}, // case is not normalized
|
||||
{"/:2017", false, nil, ""}, // invalid date format
|
||||
{"/:2006-01-02", false, nil, ""}, // valid date format but invalid attribute name
|
||||
}
|
||||
|
||||
func urlize(uri string) string {
|
||||
|
@ -67,21 +80,30 @@ func TestPermalinkExpansion(t *testing.T) {
|
|||
|
||||
c := qt.New(t)
|
||||
|
||||
page := newTestPageWithFile("/test-page/index.md")
|
||||
page.title = "Spf13 Vim 3.0 Release and new website"
|
||||
d, _ := time.Parse("2006-01-02 15:04:05", "2012-04-06 03:01:59")
|
||||
page.date = d
|
||||
page.section = "blue"
|
||||
page.slug = "The Slug"
|
||||
page.kind = "page"
|
||||
newPage := func() *testPage {
|
||||
page := newTestPageWithFile("/test-page/index.md")
|
||||
page.title = "Spf13 Vim 3.0 Release and new website"
|
||||
d, _ := time.Parse("2006-01-02 15:04:05", "2012-04-06 03:01:59")
|
||||
page.date = d
|
||||
page.section = "blue"
|
||||
page.slug = "The Slug"
|
||||
page.kind = "page"
|
||||
// page.pathInfo
|
||||
return page
|
||||
}
|
||||
|
||||
for _, item := range testdataPermalinks {
|
||||
for i, item := range testdataPermalinks {
|
||||
if !item.valid {
|
||||
continue
|
||||
}
|
||||
|
||||
page := newPage()
|
||||
if item.withPage != nil {
|
||||
item.withPage(page)
|
||||
}
|
||||
|
||||
specNameCleaner := regexp.MustCompile(`[\:\/\[\]]`)
|
||||
name := specNameCleaner.ReplaceAllString(item.spec, "")
|
||||
name := fmt.Sprintf("[%d] %s", i, specNameCleaner.ReplaceAllString(item.spec, "_"))
|
||||
|
||||
c.Run(name, func(c *qt.C) {
|
||||
patterns := map[string]map[string]string{
|
||||
|
|
|
@ -52,7 +52,7 @@ func newTestPage() *testPage {
|
|||
|
||||
func newTestPageWithFile(filename string) *testPage {
|
||||
filename = filepath.FromSlash(filename)
|
||||
file := source.NewFileInfoFrom(filename, filename)
|
||||
file := source.NewContentFileInfoFrom(filename, filename)
|
||||
|
||||
l, err := langs.NewLanguage(
|
||||
"en",
|
||||
|
@ -67,9 +67,10 @@ func newTestPageWithFile(filename string) *testPage {
|
|||
}
|
||||
|
||||
return &testPage{
|
||||
params: make(map[string]any),
|
||||
data: make(map[string]any),
|
||||
file: file,
|
||||
params: make(map[string]any),
|
||||
data: make(map[string]any),
|
||||
file: file,
|
||||
pathInfo: file.FileInfo().Meta().PathInfo,
|
||||
currentSection: &testPage{
|
||||
sectionEntries: []string{"a", "b", "c"},
|
||||
},
|
||||
|
@ -90,7 +91,8 @@ type testPage struct {
|
|||
|
||||
fuzzyWordCount int
|
||||
|
||||
path string
|
||||
path string
|
||||
pathInfo *paths.Path
|
||||
|
||||
slug string
|
||||
|
||||
|
@ -406,7 +408,7 @@ func (p *testPage) Path() string {
|
|||
}
|
||||
|
||||
func (p *testPage) PathInfo() *paths.Path {
|
||||
panic("testpage: not implemented")
|
||||
return p.pathInfo
|
||||
}
|
||||
|
||||
func (p *testPage) Permalink() string {
|
||||
|
|
|
@ -22,7 +22,7 @@ import (
|
|||
"github.com/gohugoio/hugo/common/hashing"
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hugio"
|
||||
|
||||
|
@ -132,11 +132,17 @@ func (fi *File) p() *paths.Path {
|
|||
return fi.fim.Meta().PathInfo.Unnormalized()
|
||||
}
|
||||
|
||||
var contentPathParser = &paths.PathParser{
|
||||
IsContentExt: func(ext string) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
// Used in tests.
|
||||
func NewFileInfoFrom(path, filename string) *File {
|
||||
func NewContentFileInfoFrom(path, filename string) *File {
|
||||
meta := &hugofs.FileMeta{
|
||||
Filename: filename,
|
||||
PathInfo: media.DefaultPathParser.Parse("", filepath.ToSlash(path)),
|
||||
PathInfo: contentPathParser.Parse(files.ComponentFolderContent, filepath.ToSlash(path)),
|
||||
}
|
||||
|
||||
return NewFileInfo(hugofs.NewFileMetaInfo(nil, meta))
|
||||
|
|
Loading…
Add table
Reference in a new issue