Skip to content

Commit

Permalink
resources/page: Allow section and taxonomy pages to have a permalink …
Browse files Browse the repository at this point in the history
…configuration

Allows using permalink configuration for sections (branch bundles) and
also for taxonomy pages. Extends the current permalink configuration to
be able to specified per page kind while also staying backward compatible:
all permalink patterns not dedicated to a certain kind, get automatically
added for both normal pages and term pages.

Fixes #8523
  • Loading branch information
Mai-Lapyst committed Jun 26, 2023
1 parent e3308a0 commit cc14c6a
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 36 deletions.
2 changes: 1 addition & 1 deletion config/allconfig/allconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ type Config struct {
Minify minifiers.MinifyConfig `mapstructure:"-"`

// Permalink configuration.
Permalinks map[string]string `mapstructure:"-"`
Permalinks map[string]map[string]string `mapstructure:"-"`

// Taxonomy configuration.
Taxonomies map[string]string `mapstructure:"-"`
Expand Down
43 changes: 42 additions & 1 deletion config/allconfig/alldecoders.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,48 @@ var allDecoderSetups = map[string]decodeWeight{
"permalinks": {
key: "permalinks",
decode: func(d decodeWeight, p decodeConfig) error {
p.c.Permalinks = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
p.c.Permalinks = make(map[string]map[string]string)

p.c.Permalinks["page"] = make(map[string]string)
p.c.Permalinks["section"] = make(map[string]string)
p.c.Permalinks["taxonomy"] = make(map[string]string)
p.c.Permalinks["term"] = make(map[string]string)

config := maps.CleanConfigStringMap(p.p.GetStringMap(d.key))
for k, v := range config {
switch v := v.(type) {
case string:
// [permalinks]
// key = '...'

// To sucessfully be backward compatible, "default" patterns need to be set for both page and term
p.c.Permalinks["page"][k] = v;
p.c.Permalinks["term"][k] = v;

case maps.Params:
// [permalinks.key]
// xyz = ???

if (k == "page") || (k == "section") || (k == "taxonomy") || (k == "term") {
// TODO: warn if we overwrite an already set value
for k2, v2 := range v {
switch v2 := v2.(type) {
case string:
p.c.Permalinks[k][k2] = v2

default:
return fmt.Errorf("permalinks configuration invalid: unknown value %q for key %q for kind %q", v2, k2, k)
}
}
} else {
return fmt.Errorf("permalinks configuration only allows per-kind configuration 'page', 'section', 'taxonomy' and 'term'; unknown kind: %q", k)
}

default:
return fmt.Errorf("permalinks configuration invalid: unknown value %q for key %q", v, k)
}
}

return nil
},
},
Expand Down
1 change: 1 addition & 0 deletions hugofs/files/classifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ const (
ContentClassBranch ContentClass = "branch"
ContentClassFile ContentClass = "zfile" // Sort below
ContentClassContent ContentClass = "zcontent"
ContentClassZero ContentClass = "zero" // Special value for zeroFile
)

func (c ContentClass) IsBundle() bool {
Expand Down
35 changes: 21 additions & 14 deletions hugolib/page__paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"strings"

"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs/files"

"github.com/gohugoio/hugo/resources/page"
)
Expand Down Expand Up @@ -108,14 +109,16 @@ func createTargetPathDescriptor(s *Site, p page.Page, pm *pageMeta) (page.Target
)

d := s.Deps
classifier := files.ContentClassZero

if !p.File().IsZero() {
dir = p.File().Dir()
baseName = p.File().TranslationBaseName()
contentBaseName = p.File().ContentBaseName()
classifier = p.File().Classifier()
}

if baseName != contentBaseName {
if classifier == files.ContentClassLeaf {
// See https://github.com/gohugoio/hugo/issues/4870
// A leaf bundle
dir = strings.TrimSuffix(dir, contentBaseName+helpers.FilePathSeparator)
Expand Down Expand Up @@ -143,22 +146,26 @@ func createTargetPathDescriptor(s *Site, p page.Page, pm *pageMeta) (page.Target
desc.PrefixFilePath = s.getLanguageTargetPathLang(alwaysInSubDir)
desc.PrefixLink = s.getLanguagePermalinkLang(alwaysInSubDir)

// Expand only page.KindPage and page.KindTaxonomy; don't expand other Kinds of Pages
// like page.KindSection or page.KindTaxonomyTerm because they are "shallower" and
// the permalink configuration values are likely to be redundant, e.g.
// naively expanding /category/:slug/ would give /category/categories/ for
// the "categories" page.KindTaxonomyTerm.
if p.Kind() == page.KindPage || p.Kind() == page.KindTerm {
opath, err := d.ResourceSpec.Permalinks.Expand(p.Section(), p)
if err != nil {
return desc, err
}
opath, err := d.ResourceSpec.Permalinks.Expand(p.Section(), p)
if err != nil {
return desc, err
}

if opath != "" {
opath, _ = url.QueryUnescape(opath)
desc.ExpandedPermalink = opath
if opath != "" {
opath, _ = url.QueryUnescape(opath)
if strings.HasSuffix(opath, "//") {
// When rewriting the _index of the section the permalink config is applied to,
// we get douple slashes at the end sometimes; clear them up here
opath = strings.TrimSuffix(opath, "/")
}

desc.ExpandedPermalink = opath

if !p.File().IsZero() {
s.Log.Debugf("Set expanded permalink path for %s %s to %#v", p.Kind(), p.File().Path(), opath)
} else {
s.Log.Debugf("Set expanded permalink path for %s in %v to %#v", p.Kind(), desc.Sections, opath)
}
}

return desc, nil
Expand Down
7 changes: 6 additions & 1 deletion resources/page/page_generate/generate_page_wrappers.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ func generateFileIsZeroWrappers(c *codegen.Inspector) error {
methods := c.MethodsFromTypes([]reflect.Type{reflect.TypeOf((*source.File)(nil)).Elem()}, nil)

for _, m := range methods {
if m.Name == "IsZero" {
if m.Name == "IsZero" || m.Name == "Classifier" {
continue
}
fmt.Fprint(&buff, m.DeclarationNamed("zeroFile"))
Expand Down Expand Up @@ -255,6 +255,11 @@ func (zeroFile) IsZero() bool {
return true
}
func (z zeroFile) Classifier() files.ContentClass {
z.log.Warnln(".File.Classifier on zero object. Wrap it in if or with: {{ with .File }}{{ .Classifier }}{{ end }}")
return files.ContentClassZero
}
%s
`, header, importsString(pkgImports), buff.String())
Expand Down
32 changes: 24 additions & 8 deletions resources/page/permalinks.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"errors"

"github.com/gohugoio/hugo/helpers"

)

// PermalinkExpander holds permalin mappings per section.
Expand All @@ -35,7 +36,7 @@ type PermalinkExpander struct {
// to be used to replace that tag.
knownPermalinkAttributes map[string]pageToPermaAttribute

expanders map[string]func(Page) (string, error)
expanders map[string]map[string]func(Page) (string, error)

urlize func(uri string) string
}
Expand Down Expand Up @@ -68,7 +69,7 @@ func (p PermalinkExpander) callback(attr string) (pageToPermaAttribute, bool) {

// NewPermalinkExpander creates a new PermalinkExpander configured by the given
// urlize func.
func NewPermalinkExpander(urlize func(uri string) string, patterns map[string]string) (PermalinkExpander, error) {
func NewPermalinkExpander(urlize func(uri string) string, patterns map[string]map[string]string) (PermalinkExpander, error) {
p := PermalinkExpander{urlize: urlize}

p.knownPermalinkAttributes = map[string]pageToPermaAttribute{
Expand All @@ -87,20 +88,29 @@ func NewPermalinkExpander(urlize func(uri string) string, patterns map[string]st
"filename": p.pageToPermalinkFilename,
}

e, err := p.parse(patterns)
if err != nil {
return p, err
}
p.expanders = make(map[string]map[string]func(Page) (string, error))

p.expanders = e
for kind, patterns := range patterns {
e, err := p.parse(patterns)
if err != nil {
return p, err
}
p.expanders[kind] = e
}

return p, nil
}

// Expand expands the path in p according to the rules defined for the given key.
// If no rules are found for the given key, an empty string is returned.
func (l PermalinkExpander) Expand(key string, p Page) (string, error) {
expand, found := l.expanders[key]
expanders, found := l.expanders[p.Kind()]

if !found {
return "", nil
}

expand, found := expanders[key]

if !found {
return "", nil
Expand Down Expand Up @@ -242,6 +252,10 @@ 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) {
if p.File().TranslationBaseName() == "_index" {
return "", nil
}

return l.urlize(p.Title()), nil
}

Expand All @@ -252,6 +266,8 @@ func (l PermalinkExpander) pageToPermalinkFilename(p Page, _ string) (string, er
// Page bundles; the directory name will hopefully have a better name.
dir := strings.TrimSuffix(p.File().Dir(), helpers.FilePathSeparator)
_, name = filepath.Split(dir)
} else if name == "_index" {
return "", nil
}

return l.urlize(name), nil
Expand Down
35 changes: 24 additions & 11 deletions resources/page/permalinks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ func TestPermalinkExpansion(t *testing.T) {
page.date = d
page.section = "blue"
page.slug = "The Slug"
page.kind = "page"

for _, item := range testdataPermalinks {
if !item.valid {
Expand All @@ -79,8 +80,10 @@ func TestPermalinkExpansion(t *testing.T) {
name := specNameCleaner.ReplaceAllString(item.spec, "")

c.Run(name, func(c *qt.C) {
patterns := map[string]string{
"posts": item.spec,
patterns := map[string]map[string]string{
"page": {
"posts": item.spec,
},
}
expander, err := NewPermalinkExpander(urlize, patterns)
c.Assert(err, qt.IsNil)
Expand All @@ -103,14 +106,18 @@ func TestPermalinkExpansionMultiSection(t *testing.T) {
page.date = d
page.section = "blue"
page.slug = "The Slug"
page.kind = "page"

page_slug_fallback := newTestPageWithFile("/page-filename/index.md")
page_slug_fallback.title = "Page Title"

permalinksConfig := map[string]string{
"posts": "/:slug",
"blog": "/:section/:year",
"recipes": "/:slugorfilename",
page_slug_fallback.kind = "page"

permalinksConfig := map[string]map[string]string{
"page": {
"posts": "/:slug",
"blog": "/:section/:year",
"recipes": "/:slugorfilename",
},
}
expander, err := NewPermalinkExpander(urlize, permalinksConfig)
c.Assert(err, qt.IsNil)
Expand All @@ -137,8 +144,10 @@ func TestPermalinkExpansionConcurrent(t *testing.T) {

c := qt.New(t)

permalinksConfig := map[string]string{
"posts": "/:slug/",
permalinksConfig := map[string]map[string]string{
"page": {
"posts": "/:slug/",
},
}

expander, err := NewPermalinkExpander(urlize, permalinksConfig)
Expand All @@ -151,6 +160,7 @@ func TestPermalinkExpansionConcurrent(t *testing.T) {
go func(i int) {
defer wg.Done()
page := newTestPage()
page.kind = "page"
for j := 1; j < 20; j++ {
page.slug = fmt.Sprintf("slug%d", i+j)
expanded, err := expander.Expand("posts", page)
Expand Down Expand Up @@ -209,9 +219,12 @@ func BenchmarkPermalinkExpand(b *testing.B) {
page.title = "Hugo Rocks"
d, _ := time.Parse("2006-01-02", "2019-02-28")
page.date = d
page.kind = "page"

permalinksConfig := map[string]string{
"posts": "/:year-:month-:title",
permalinksConfig := map[string]map[string]string{
"page": {
"posts": "/:year-:month-:title",
},
}
expander, err := NewPermalinkExpander(urlize, permalinksConfig)
if err != nil {
Expand Down
6 changes: 6 additions & 0 deletions resources/page/zero_file.autogen.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package page
import (
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/source"
)

Expand All @@ -34,6 +35,11 @@ func (zeroFile) IsZero() bool {
return true
}

func (z zeroFile) Classifier() files.ContentClass {
z.log.Warnln(".File.Classifier on zero object. Wrap it in if or with: {{ with .File }}{{ .Classifier }}{{ end }}")
return files.ContentClassZero
}

func (z zeroFile) Path() (o0 string) {
z.log.Warnln(".File.Path on zero object. Wrap it in if or with: {{ with .File }}{{ .Path }}{{ end }}")
return
Expand Down
8 changes: 8 additions & 0 deletions source/fileInfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ type FileWithoutOverlap interface {
// if file is a leaf bundle.
ContentBaseName() string

// Classifier is the ContentClass of the file
Classifier() files.ContentClass

// UniqueID is the MD5 hash of the file's path and is for most practical applications,
// Hugo content files being one of them, considered to be unique.
UniqueID() string
Expand Down Expand Up @@ -170,6 +173,11 @@ func (fi *FileInfo) ContentBaseName() string {
return fi.contentBaseName
}

// Classifier is the ContentClass of the file
func (fi *FileInfo) Classifier() files.ContentClass {
return fi.classifier;
}

// Section returns a file's section.
func (fi *FileInfo) Section() string {
fi.init()
Expand Down

0 comments on commit cc14c6a

Please sign in to comment.