From 8b2394685096e7b7141e04ffa6a9e82cf36a67f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Tue, 25 Feb 2025 11:28:59 +0100 Subject: [PATCH] Fix some related content issues with content adapters Fixes #13443 --- hugolib/page.go | 14 ++++ hugolib/page__common.go | 1 - hugolib/page__meta.go | 12 ---- hugolib/page__new.go | 1 - .../pagesfromgotmpl_integration_test.go | 24 ++++++- related/inverted_index.go | 3 + related/inverted_index_test.go | 45 +++++++++++++ resources/page/page.go | 67 +++++++++++++++++++ 8 files changed, 151 insertions(+), 16 deletions(-) diff --git a/hugolib/page.go b/hugolib/page.go index 3653767379a..a6966b1af71 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -151,6 +151,20 @@ func (p *pageState) Key() string { return "page-" + strconv.FormatUint(p.pid, 10) } +// RelatedKeywords implements the related.Document interface needed for fast page searches. +func (p *pageState) RelatedKeywords(cfg related.IndexConfig) ([]related.Keyword, error) { + v, found, err := page.PageMetaValueNamedValue(p, cfg.Name) + if err != nil { + return nil, err + } + + if !found { + return nil, nil + } + + return cfg.ToKeywords(v) +} + func (p *pageState) resetBuildState() { // Nothing to do for now. } diff --git a/hugolib/page__common.go b/hugolib/page__common.go index a120849b380..7407f7140cc 100644 --- a/hugolib/page__common.go +++ b/hugolib/page__common.go @@ -68,7 +68,6 @@ type pageCommon struct { page.PageMetaInternalProvider page.Positioner page.RawContentProvider - page.RelatedKeywordsProvider page.RefProvider page.ShortcodeInfoProvider page.SitesProvider diff --git a/hugolib/page__meta.go b/hugolib/page__meta.go index 56bc1d96fbf..e44224bae9d 100644 --- a/hugolib/page__meta.go +++ b/hugolib/page__meta.go @@ -27,8 +27,6 @@ import ( "github.com/gohugoio/hugo/markup/converter" xmaps "golang.org/x/exp/maps" - "github.com/gohugoio/hugo/related" - "github.com/gohugoio/hugo/source" "github.com/gohugoio/hugo/common/constants" @@ -215,16 +213,6 @@ func (p *pageMeta) PathInfo() *paths.Path { return p.pathInfo } -// RelatedKeywords implements the related.Document interface needed for fast page searches. -func (p *pageMeta) RelatedKeywords(cfg related.IndexConfig) ([]related.Keyword, error) { - v, err := p.Param(cfg.Name) - if err != nil { - return nil, err - } - - return cfg.ToKeywords(v) -} - func (p *pageMeta) IsSection() bool { return p.Kind() == kinds.KindSection } diff --git a/hugolib/page__new.go b/hugolib/page__new.go index 7d948ef5882..b8c3483add6 100644 --- a/hugolib/page__new.go +++ b/hugolib/page__new.go @@ -202,7 +202,6 @@ func (h *HugoSites) doNewPage(m *pageMeta) (*pageState, *paths.Path, error) { ResourceParamsProvider: m, PageMetaProvider: m, PageMetaInternalProvider: m, - RelatedKeywordsProvider: m, OutputFormatsProvider: page.NopPage, ResourceTypeProvider: pageTypesProvider, MediaTypeProvider: pageTypesProvider, diff --git a/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go b/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go index e1d562d4d21..b674ef0b6cd 100644 --- a/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go +++ b/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 The Hugo Authors. All rights reserved. +// Copyright 2025 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import ( "github.com/gohugoio/hugo/markup/asciidocext" "github.com/gohugoio/hugo/markup/pandoc" "github.com/gohugoio/hugo/markup/rst" + "github.com/gohugoio/hugo/related" ) const filesPagesFromDataTempleBasic = ` @@ -73,10 +74,11 @@ Pfile Content {{ $title := printf "%s:%s" $pd $pp }} {{ $date := "2023-03-01" | time.AsTime }} {{ $dates := dict "date" $date }} +{{ $keywords := slice "foo" "Bar"}} {{ $contentMarkdown := dict "value" "**Hello World**" "mediaType" "text/markdown" }} {{ $contentMarkdownDefault := dict "value" "**Hello World Default**" }} {{ $contentHTML := dict "value" "Hello World! No **markdown** here." "mediaType" "text/html" }} -{{ $.AddPage (dict "kind" "page" "path" "P1" "title" $title "dates" $dates "content" $contentMarkdown "params" (dict "param1" "param1v" ) ) }} +{{ $.AddPage (dict "kind" "page" "path" "P1" "title" $title "dates" $dates "keywords" $keywords "content" $contentMarkdown "params" (dict "param1" "param1v" ) ) }} {{ $.AddPage (dict "kind" "page" "path" "p2" "title" "p2title" "dates" $dates "content" $contentHTML ) }} {{ $.AddPage (dict "kind" "page" "path" "p3" "title" "p3title" "dates" $dates "content" $contentMarkdownDefault "draft" false ) }} {{ $.AddPage (dict "kind" "page" "path" "p4" "title" "p4title" "dates" $dates "content" $contentMarkdownDefault "draft" $data.draft ) }} @@ -329,6 +331,24 @@ func TestPagesFromGoTmplRemoveGoTmpl(t *testing.T) { b.AssertFileContent("public/docs/index.html", "RegularPagesRecursive: pfile:/docs/pfile|$") } +// Issue #13443. +func TestPagesFromGoRelatedKeywords(t *testing.T) { + t.Parallel() + b := hugolib.Test(t, filesPagesFromDataTempleBasic) + + p1 := b.H.Sites[0].RegularPages()[0] + icfg := related.IndexConfig{ + Name: "keywords", + } + k, err := p1.RelatedKeywords(icfg) + b.Assert(err, qt.IsNil) + b.Assert(k, qt.DeepEquals, icfg.StringsToKeywords("foo", "Bar")) + icfg.Name = "title" + k, err = p1.RelatedKeywords(icfg) + b.Assert(err, qt.IsNil) + b.Assert(k, qt.DeepEquals, icfg.StringsToKeywords("p1:p1")) +} + func TestPagesFromGoTmplLanguagePerFile(t *testing.T) { filesTemplate := ` -- hugo.toml -- diff --git a/related/inverted_index.go b/related/inverted_index.go index 7e171cf539e..9197a61351c 100644 --- a/related/inverted_index.go +++ b/related/inverted_index.go @@ -582,6 +582,9 @@ func DecodeConfig(m maps.Params) (Config, error) { } } for i := range c.Indices { + // Lower case name. + c.Indices[i].Name = strings.ToLower(c.Indices[i].Name) + icfg := c.Indices[i] if icfg.Type == "" { c.Indices[i].Type = TypeBasic diff --git a/related/inverted_index_test.go b/related/inverted_index_test.go index f1d8e11a14a..568486d1f4b 100644 --- a/related/inverted_index_test.go +++ b/related/inverted_index_test.go @@ -21,6 +21,7 @@ import ( "time" qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/config" ) type testDoc struct { @@ -249,6 +250,50 @@ func TestToKeywordsToLower(t *testing.T) { }) } +func TestDecodeConfig(t *testing.T) { + c := qt.New(t) + + configToml := ` +[related] + includeNewer = true + threshold = 32 + toLower = false + [[related.indices]] + applyFilter = false + cardinalityThreshold = 0 + name = 'KeyworDs' + pattern = '' + toLower = false + type = 'basic' + weight = 100 + [[related.indices]] + applyFilter = true + cardinalityThreshold = 32 + name = 'date' + pattern = '' + toLower = false + type = 'basic' + weight = 10 + [[related.indices]] + applyFilter = false + cardinalityThreshold = 0 + name = 'tags' + pattern = '' + toLower = false + type = 'fragments' + weight = 80 +` + + m, err := config.FromConfigString(configToml, "toml") + c.Assert(err, qt.IsNil) + conf, err := DecodeConfig(m.GetParams("related")) + + c.Assert(err, qt.IsNil) + c.Assert(conf.IncludeNewer, qt.IsTrue) + first := conf.Indices[0] + c.Assert(first.Name, qt.Equals, "keywords") +} + func TestToKeywordsAnySlice(t *testing.T) { c := qt.New(t) var config IndexConfig diff --git a/resources/page/page.go b/resources/page/page.go index 40e15dc6f58..afb31bb3fce 100644 --- a/resources/page/page.go +++ b/resources/page/page.go @@ -180,6 +180,11 @@ type PageFragment interface { resource.ResourceNameTitleProvider } +type PageMetaResource interface { + PageMetaProvider + resource.Resource +} + // PageMetaProvider provides page metadata, typically provided via front matter. type PageMetaProvider interface { // The 4 page dates @@ -251,6 +256,68 @@ type PageMetaProvider interface { Weight() int } +// PageMetaValueNamedValue returns a named metadata value from a PageMetaResource. +// This is currently only used to generate keywords for related content. +// If nameLower is not one of the metadata interface methods, we +// look in Params. +func PageMetaValueNamedValue(p PageMetaResource, nameLower string) (any, bool, error) { + var ( + v any + err error + ) + + switch nameLower { + case "kind": + v = p.Kind() + case "bundletype": + v = p.BundleType() + case "mediatype": + v = p.MediaType() + case "section": + v = p.Section() + case "lang": + v = p.Lang() + case "aliases": + v = p.Aliases() + case "name": + v = p.Name() + case "keywords": + v = p.Keywords() + case "description": + v = p.Description() + case "title": + v = p.Title() + case "linktitle": + v = p.LinkTitle() + case "slug": + v = p.Slug() + case "date": + v = p.Date() + case "publishdate": + v = p.PublishDate() + case "expirydate": + v = p.ExpiryDate() + case "lastmod": + v = p.Lastmod() + case "draft": + v = p.Draft() + case "type": + v = p.Type() + case "layout": + v = p.Layout() + case "weight": + v = p.Weight() + default: + // Try params. + v, err = resource.Param(p, nil, nameLower) + if v == nil { + return nil, false, nil + } + } + + return v, err == nil, err +} + // PageMetaInternalProvider provides internal page metadata. type PageMetaInternalProvider interface { // This is for internal use only.