aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorn1xx1 <[email protected]>2024-08-01 12:14:29 +0200
committerGitHub <[email protected]>2024-08-01 12:14:29 +0200
commit566fe7ba12e7ecabadffa9ec76b319d04063c78b (patch)
tree227ffdfe6d1cb203f87f2930b9d892adb243805c
parent92573012e83bf8d71a247027d2f7f7f43a9c42b7 (diff)
downloadhugo-566fe7ba12e7ecabadffa9ec76b319d04063c78b.tar.gz
hugo-566fe7ba12e7ecabadffa9ec76b319d04063c78b.zip
resources/page: Expand parmalinks tokens in `url`
This change allows to use permalink tokens in url front matter fields. This should be useful to target more specific pages instead of using a global permalink configuration. It's expected to be used with cascade. Fixes #9714
-rw-r--r--common/hreflect/helpers.go5
-rw-r--r--common/maps/cache.go13
-rw-r--r--hugolib/page__paths.go13
-rw-r--r--hugolib/page_permalink_test.go2
-rw-r--r--resources/page/permalinks.go44
-rw-r--r--resources/page/permalinks_integration_test.go39
-rw-r--r--tpl/templates/templates.go6
7 files changed, 101 insertions, 21 deletions
diff --git a/common/hreflect/helpers.go b/common/hreflect/helpers.go
index b5a8bacc9..5113a3886 100644
--- a/common/hreflect/helpers.go
+++ b/common/hreflect/helpers.go
@@ -268,7 +268,8 @@ func IsContextType(tp reflect.Type) bool {
return true
}
- return isContextCache.GetOrCreate(tp, func() bool {
- return tp.Implements(contextInterface)
+ isContext, _ := isContextCache.GetOrCreate(tp, func() (bool, error) {
+ return tp.Implements(contextInterface), nil
})
+ return isContext
}
diff --git a/common/maps/cache.go b/common/maps/cache.go
index 7cd7410c2..0175974b5 100644
--- a/common/maps/cache.go
+++ b/common/maps/cache.go
@@ -40,22 +40,25 @@ func (c *Cache[K, T]) Get(key K) (T, bool) {
}
// GetOrCreate gets the value for the given key if it exists, or creates it if not.
-func (c *Cache[K, T]) GetOrCreate(key K, create func() T) T {
+func (c *Cache[K, T]) GetOrCreate(key K, create func() (T, error)) (T, error) {
c.RLock()
v, found := c.m[key]
c.RUnlock()
if found {
- return v
+ return v, nil
}
c.Lock()
defer c.Unlock()
v, found = c.m[key]
if found {
- return v
+ return v, nil
+ }
+ v, err := create()
+ if err != nil {
+ return v, err
}
- v = create()
c.m[key] = v
- return v
+ return v, nil
}
// Set sets the given key to the given value.
diff --git a/hugolib/page__paths.go b/hugolib/page__paths.go
index d89388f81..6324b5871 100644
--- a/hugolib/page__paths.go
+++ b/hugolib/page__paths.go
@@ -141,6 +141,19 @@ func createTargetPathDescriptor(p *pageState) (page.TargetPathDescriptor, error)
desc.PrefixFilePath = s.getLanguageTargetPathLang(alwaysInSubDir)
desc.PrefixLink = s.getLanguagePermalinkLang(alwaysInSubDir)
+ if desc.URL != "" && strings.IndexByte(desc.URL, ':') >= 0 {
+ // Attempt to parse and expand an url
+ opath, err := d.ResourceSpec.Permalinks.ExpandPattern(desc.URL, p)
+ if err != nil {
+ return desc, err
+ }
+
+ if opath != "" {
+ opath, _ = url.QueryUnescape(opath)
+ desc.URL = opath
+ }
+ }
+
opath, err := d.ResourceSpec.Permalinks.Expand(p.Section(), p)
if err != nil {
return desc, err
diff --git a/hugolib/page_permalink_test.go b/hugolib/page_permalink_test.go
index bc89638d3..d8fd99d79 100644
--- a/hugolib/page_permalink_test.go
+++ b/hugolib/page_permalink_test.go
@@ -59,6 +59,8 @@ func TestPermalink(t *testing.T) {
// test URL overrides
{"x/y/z/boofar.md", "", "", "/z/y/q/", false, false, "/z/y/q/", "/z/y/q/"},
+ // test URL override with expands
+ {"x/y/z/boofar.md", "", "test", "/z/:slug/", false, false, "/z/test/", "/z/test/"},
}
for i, test := range tests {
diff --git a/resources/page/permalinks.go b/resources/page/permalinks.go
index 67c63c4b2..05911f0ea 100644
--- a/resources/page/permalinks.go
+++ b/resources/page/permalinks.go
@@ -40,6 +40,8 @@ type PermalinkExpander struct {
expanders map[string]map[string]func(Page) (string, error)
urlize func(uri string) string
+
+ patternCache *maps.Cache[string, func(Page) (string, error)]
}
// Time for checking date formats. Every field is different than the
@@ -71,7 +73,10 @@ 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]map[string]string) (PermalinkExpander, error) {
- p := PermalinkExpander{urlize: urlize}
+ p := PermalinkExpander{
+ urlize: urlize,
+ patternCache: maps.NewCache[string, func(Page) (string, error)](),
+ }
p.knownPermalinkAttributes = map[string]pageToPermaAttribute{
"year": p.pageToPermalinkDate,
@@ -102,6 +107,16 @@ func NewPermalinkExpander(urlize func(uri string) string, patterns map[string]ma
return p, nil
}
+// ExpandPattern expands the path in p with the specified expand pattern.
+func (l PermalinkExpander) ExpandPattern(pattern string, p Page) (string, error) {
+ expander, err := l.getOrParsePattern(pattern)
+ if err != nil {
+ return "", err
+ }
+
+ return expander(p)
+}
+
// 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) {
@@ -129,17 +144,11 @@ func init() {
}
}
-func (l PermalinkExpander) parse(patterns map[string]string) (map[string]func(Page) (string, error), error) {
- expanders := make(map[string]func(Page) (string, error))
-
- for k, pattern := range patterns {
- k = strings.Trim(k, sectionCutSet)
-
+func (l PermalinkExpander) getOrParsePattern(pattern string) (func(Page) (string, error), error) {
+ return l.patternCache.GetOrCreate(pattern, func() (func(Page) (string, error), error) {
if !l.validate(pattern) {
return nil, &permalinkExpandError{pattern: pattern, err: errPermalinkIllFormed}
}
-
- pattern := pattern
matches := attributeRegexp.FindAllStringSubmatch(pattern, -1)
callbacks := make([]pageToPermaAttribute, len(matches))
@@ -157,7 +166,7 @@ func (l PermalinkExpander) parse(patterns map[string]string) (map[string]func(Pa
callbacks[i] = callback
}
- expanders[k] = func(p Page) (string, error) {
+ return func(p Page) (string, error) {
if matches == nil {
return pattern, nil
}
@@ -173,12 +182,25 @@ func (l PermalinkExpander) parse(patterns map[string]string) (map[string]func(Pa
}
newField = strings.Replace(newField, replacement, newAttr, 1)
-
}
return newField, nil
+ }, nil
+ })
+}
+
+func (l PermalinkExpander) parse(patterns map[string]string) (map[string]func(Page) (string, error), error) {
+ expanders := make(map[string]func(Page) (string, error))
+
+ for k, pattern := range patterns {
+ k = strings.Trim(k, sectionCutSet)
+
+ expander, err := l.getOrParsePattern(pattern)
+ if err != nil {
+ return nil, err
}
+ expanders[k] = expander
}
return expanders, nil
diff --git a/resources/page/permalinks_integration_test.go b/resources/page/permalinks_integration_test.go
index 9a76ac602..2b9e878b1 100644
--- a/resources/page/permalinks_integration_test.go
+++ b/resources/page/permalinks_integration_test.go
@@ -193,3 +193,42 @@ List.
b.AssertFileContent("public/libros/fiction/index.html", "List.")
b.AssertFileContent("public/libros/fiction/2023/book1/index.html", "Single.")
}
+
+func TestPermalinksUrlCascade(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- layouts/_default/list.html --
+List|{{ .Kind }}|{{ .RelPermalink }}|
+-- layouts/_default/single.html --
+Single|{{ .Kind }}|{{ .RelPermalink }}|
+-- hugo.toml --
+-- content/cooking/delicious-recipes/_index.md --
+---
+url: /delicious-recipe/
+cascade:
+ url: /delicious-recipe/:slug/
+---
+-- content/cooking/delicious-recipes/example1.md --
+---
+title: Recipe 1
+---
+-- content/cooking/delicious-recipes/example2.md --
+---
+title: Recipe 2
+slug: custom-recipe-2
+---
+`
+ b := hugolib.NewIntegrationTestBuilder(
+ hugolib.IntegrationTestConfig{
+ T: t,
+ TxtarString: files,
+ LogLevel: logg.LevelWarn,
+ }).Build()
+
+ t.Log(b.LogString())
+ b.Assert(b.H.Log.LoggCount(logg.LevelWarn), qt.Equals, 0)
+ b.AssertFileContent("public/delicious-recipe/index.html", "List|section|/delicious-recipe/")
+ b.AssertFileContent("public/delicious-recipe/recipe-1/index.html", "Single|page|/delicious-recipe/recipe-1/")
+ b.AssertFileContent("public/delicious-recipe/custom-recipe-2/index.html", "Single|page|/delicious-recipe/custom-recipe-2/")
+}
diff --git a/tpl/templates/templates.go b/tpl/templates/templates.go
index 98b4b4c38..0be44a013 100644
--- a/tpl/templates/templates.go
+++ b/tpl/templates/templates.go
@@ -90,14 +90,14 @@ func (ns *Namespace) DoDefer(ctx context.Context, id string, optsv any) string {
id = fmt.Sprintf("%s_%s%s", id, key, tpl.HugoDeferredTemplateSuffix)
- _ = ns.deps.BuildState.DeferredExecutions.Executions.GetOrCreate(id,
- func() *tpl.DeferredExecution {
+ _, _ = ns.deps.BuildState.DeferredExecutions.Executions.GetOrCreate(id,
+ func() (*tpl.DeferredExecution, error) {
return &tpl.DeferredExecution{
TemplateName: templateName,
Ctx: ctx,
Data: opts.Data,
Executed: false,
- }
+ }, nil
})
return id