diff options
-rw-r--r-- | common/hreflect/helpers.go | 5 | ||||
-rw-r--r-- | common/maps/cache.go | 13 | ||||
-rw-r--r-- | hugolib/page__paths.go | 13 | ||||
-rw-r--r-- | hugolib/page_permalink_test.go | 2 | ||||
-rw-r--r-- | resources/page/permalinks.go | 44 | ||||
-rw-r--r-- | resources/page/permalinks_integration_test.go | 39 | ||||
-rw-r--r-- | tpl/templates/templates.go | 6 |
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 |