]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
resources/page: Allow section and taxonomy pages to have a permalink configuration
authorMai-Lapyst <67418776+Mai-Lapyst@users.noreply.github.com>
Mon, 26 Jun 2023 13:31:01 +0000 (15:31 +0200)
committerGitHub <noreply@github.com>
Mon, 26 Jun 2023 13:31:01 +0000 (15:31 +0200)
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

config/allconfig/allconfig.go
config/allconfig/alldecoders.go
hugofs/files/classifier.go
hugolib/page__paths.go
resources/page/page_generate/generate_page_wrappers.go
resources/page/permalinks.go
resources/page/permalinks_test.go
resources/page/zero_file.autogen.go
source/fileInfo.go

index 9079e2ce33408d533f4a2980345e3ce0d1ef39ed..2dc409be77d6a46eaf4f62cc4f1a6517bfc9be93 100644 (file)
@@ -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:"-"`
index 1c8573f3d69dea556174cbcf3540ed1586e040f5..4d9ef4f85afee6f1d668ad1aabc6e31d927cce6a 100644 (file)
@@ -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
                },
        },
index 09b239c21a990ce48dbc75fff59a1d102692ff65..5690795ed7111a9d39c6464e37f7ef4aad9eb1ee 100644 (file)
@@ -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 {
index 72eac3182a25372a01fe232d742c758883ddd746..f98531c10854eb68e3499375ba8d942837d7a58c 100644 (file)
@@ -18,6 +18,7 @@ import (
        "strings"
 
        "github.com/gohugoio/hugo/helpers"
+       "github.com/gohugoio/hugo/hugofs/files"
 
        "github.com/gohugoio/hugo/resources/page"
 )
@@ -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)
@@ -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
index f4b40f717633a79d501a6aefc52cb2cf24b51dba..c034bc002698d9d0b2beb9f7ea4922863a1b4744 100644 (file)
@@ -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"))
@@ -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())
index 3dfc369376609e9ae70036e16535af9cd07ef5f3..ab418a20b9e8024b2964e0cb4b043db972821d6c 100644 (file)
@@ -26,6 +26,7 @@ import (
        "errors"
 
        "github.com/gohugoio/hugo/helpers"
+
 )
 
 // PermalinkExpander holds permalin mappings per section.
@@ -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
 }
@@ -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{
@@ -87,12 +88,15 @@ 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
 }
@@ -100,7 +104,13 @@ func NewPermalinkExpander(urlize func(uri string) string, patterns map[string]st
 // 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
@@ -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
 }
 
@@ -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
index b9c0ca9cb48c2ef5b3ee6460cc15c354ed9693dd..24eb27f5b6f1540d6d961252b0c30ffc4dfd986f 100644 (file)
@@ -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 {
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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 {
index 72d98998ec2fc5bf7b04f07c18c9e35b09e90ee7..979f92a0f0e0fb917d657f81fd42653a36d17cac 100644 (file)
@@ -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"
 )
 
@@ -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
index c58a0c3b90822bf1c09fc69cf88a50dde4a6f94e..03bb1b16cb9b7590cecd6d08efe6ffcda987b481 100644 (file)
@@ -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
@@ -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()