From 5b7cb258ec26d7de690099f5dc39935b8d728155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Tue, 30 Jan 2024 11:43:20 +0100 Subject: [PATCH] Create default link and image render hooks Fixes #11933 --- check_gofmt.sh | 2 + common/paths/path.go | 5 ++ common/types/types.go | 5 ++ config/allconfig/allconfig.go | 13 ++++ hugolib/content_render_hooks_test.go | 72 +++++++++++++++++++ hugolib/page__per_output.go | 15 ++++ hugolib/pagecollections.go | 2 +- hugolib/pagecollections_test.go | 8 +++ magefile.go | 41 +++-------- markup/goldmark/goldmark_config/config.go | 31 +++++++- tpl/template_info.go | 4 ++ .../_default/_markup/render-image.html | 15 ++++ .../_default/_markup/render-link.html | 26 +++++++ tpl/tplimpl/template.go | 25 +++++-- tpl/tplimpl/template_errors.go | 7 +- 15 files changed, 229 insertions(+), 42 deletions(-) create mode 100755 check_gofmt.sh create mode 100644 tpl/tplimpl/embedded/templates/_default/_markup/render-image.html create mode 100644 tpl/tplimpl/embedded/templates/_default/_markup/render-link.html diff --git a/check_gofmt.sh b/check_gofmt.sh new file mode 100755 index 00000000000..c77517d3ff3 --- /dev/null +++ b/check_gofmt.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +diff <(gofmt -d .) <(printf '') \ No newline at end of file diff --git a/common/paths/path.go b/common/paths/path.go index da99b16ac3c..906270cae38 100644 --- a/common/paths/path.go +++ b/common/paths/path.go @@ -387,6 +387,11 @@ func ToSlashTrimLeading(s string) string { return strings.TrimPrefix(filepath.ToSlash(s), "/") } +// ToSlashTrimTrailing is just a filepath.ToSlash with an added / suffix trimmer. +func ToSlashTrimTrailing(s string) string { + return strings.TrimSuffix(filepath.ToSlash(s), "/") +} + // ToSlashPreserveLeading converts the path given to a forward slash separated path // and preserves the leading slash if present trimming any trailing slash. func ToSlashPreserveLeading(s string) string { diff --git a/common/types/types.go b/common/types/types.go index 11683c196bc..801c511a0fd 100644 --- a/common/types/types.go +++ b/common/types/types.go @@ -107,3 +107,8 @@ type LowHigh struct { // This is only used for debugging purposes. var InvocationCounter atomic.Int64 + +// NewTrue returns a pointer to b. +func NewBool(b bool) *bool { + return &b +} diff --git a/config/allconfig/allconfig.go b/config/allconfig/allconfig.go index 26e40230537..742a281504c 100644 --- a/config/allconfig/allconfig.go +++ b/config/allconfig/allconfig.go @@ -31,6 +31,7 @@ import ( "github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/common/paths" + "github.com/gohugoio/hugo/common/types" "github.com/gohugoio/hugo/common/urls" "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/config/privacy" @@ -899,6 +900,18 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon return nil, err } + // Adjust Goldmark config defaults for multilingual, single-host sites. + if len(languagesConfig) > 1 && !isMultiHost && !clone.Markup.Goldmark.DuplicateResourceFiles { + if !clone.Markup.Goldmark.DuplicateResourceFiles { + if clone.Markup.Goldmark.RenderHooks.Link.EnableDefault == nil { + clone.Markup.Goldmark.RenderHooks.Link.EnableDefault = types.NewBool(true) + } + if clone.Markup.Goldmark.RenderHooks.Image.EnableDefault == nil { + clone.Markup.Goldmark.RenderHooks.Image.EnableDefault = types.NewBool(true) + } + } + } + langConfigMap[k] = clone case maps.ParamsMergeStrategy: default: diff --git a/hugolib/content_render_hooks_test.go b/hugolib/content_render_hooks_test.go index 93b0e1621af..36d1e626f6c 100644 --- a/hugolib/content_render_hooks_test.go +++ b/hugolib/content_render_hooks_test.go @@ -14,6 +14,7 @@ package hugolib import ( + "strings" "testing" ) @@ -169,3 +170,74 @@ Self Fragments: [d e f] P1 Fragments: [b c z] `) } + +func TestDefaultRenderHooksMultilingual(t *testing.T) { + files := ` +-- hugo.toml -- +baseURL = "https://example.org" +disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT"] +defaultContentLanguage = "nn" +defaultContentLanguageInSubdir = true +[markup] +[markup.goldmark] +duplicateResourceFiles = false +[markup.goldmark.renderhooks] +[markup.goldmark.renderhooks.link] +#enableDefault = false +[markup.goldmark.renderhooks.image] +#enableDefault = false +[languages] +[languages.en] +weight = 1 +[languages.nn] +weight = 2 +-- content/p1/index.md -- +--- +title: "p1" +--- +[P2](p2) +![Pixel](pixel.png) +-- content/p2/index.md -- +--- +title: "p2" +--- +[P1](p1) +![Pixel](pixel.jpg) +-- content/p1/index.en.md -- +--- +title: "p1 en" +--- +[P2](p2) +![Pixel](pixel.png) +-- content/p2/index.en.md -- +--- +title: "p2 en" +--- +[P1](p1) +![Pixel](pixel.png) + +-- content/p1/pixel.nn.png -- +iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== +-- content/p2/pixel.png -- +iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== +-- layouts/_default/single.html -- +{{ .Title }}|{{ .Content }}|$ + +` + + t.Run("Default multilingual", func(t *testing.T) { + b := Test(t, files) + + b.AssertFileContent("public/nn/p1/index.html", + "p1|

P2

", "\"Pixel\"") + b.AssertFileContent("public/en/p1/index.html", + "p1 en|

P2

", "\"Pixel\"") + }) + + t.Run("Disabled", func(t *testing.T) { + b := Test(t, strings.ReplaceAll(files, "#enableDefault = false", "enableDefault = false")) + + b.AssertFileContent("public/nn/p1/index.html", + "p1|

P2", "\"Pixel\"") + }) +} diff --git a/hugolib/page__per_output.go b/hugolib/page__per_output.go index e0026ed1fbd..7220c86fe43 100644 --- a/hugolib/page__per_output.go +++ b/hugolib/page__per_output.go @@ -470,6 +470,21 @@ func (pco *pageContentOutput) initRenderHooks() error { if err != nil { panic(err) } + if found { + if isitp, ok := templ.(tpl.IsInternalTemplateProvider); ok && isitp.IsInternalTemplate() { + renderHookConfig := pco.po.p.s.conf.Markup.Goldmark.RenderHooks + switch templ.Name() { + case "_default/_markup/render-link.html": + if !renderHookConfig.Link.IsEnableDefault() { + return nil, false + } + case "_default/_markup/render-image.html": + if !renderHookConfig.Image.IsEnableDefault() { + return nil, false + } + } + } + } return templ, found } diff --git a/hugolib/pagecollections.go b/hugolib/pagecollections.go index 8e05ad7e60d..505b10bd772 100644 --- a/hugolib/pagecollections.go +++ b/hugolib/pagecollections.go @@ -56,7 +56,7 @@ func (c *pageFinder) getPageRef(context page.Page, ref string) (page.Page, error } func (c *pageFinder) getPage(context page.Page, ref string) (page.Page, error) { - n, err := c.getContentNode(context, false, filepath.ToSlash(ref)) + n, err := c.getContentNode(context, false, paths.ToSlashTrimTrailing(ref)) if err != nil { return nil, err } diff --git a/hugolib/pagecollections_test.go b/hugolib/pagecollections_test.go index 8fd4f07395c..eaa80a10995 100644 --- a/hugolib/pagecollections_test.go +++ b/hugolib/pagecollections_test.go @@ -413,6 +413,10 @@ title: p2 func TestPageGetPageVariations(t *testing.T) { files := ` -- hugo.toml -- +-- content/s1/_index.md -- +--- +title: s1 section +--- -- content/s1/p1/index.md -- --- title: p1 @@ -430,6 +434,8 @@ title: p3 title: p2_root --- -- layouts/index.html -- +/s1: {{ with .GetPage "/s1" }}{{ .Title }}{{ end }}| +/s1/: {{ with .GetPage "/s1/" }}{{ .Title }}{{ end }}| /s1/p2.md: {{ with .GetPage "/s1/p2.md" }}{{ .Title }}{{ end }}| /s1/p2: {{ with .GetPage "/s1/p2" }}{{ .Title }}{{ end }}| /s1/p1/index.md: {{ with .GetPage "/s1/p1/index.md" }}{{ .Title }}{{ end }}| @@ -444,6 +450,8 @@ p1/index.md: {{ with .GetPage "p1/index.md" }}{{ .Title }}{{ end }}| b := Test(t, files) b.AssertFileContent("public/index.html", ` +/s1: s1 section| +/s1/: s1 section| /s1/p2.md: p2| /s1/p2: p2| /s1/p1/index.md: p1| diff --git a/magefile.go b/magefile.go index c8542dad7c4..d4c7b65de43 100644 --- a/magefile.go +++ b/magefile.go @@ -185,42 +185,15 @@ func TestRace() error { // Run gofmt linter func Fmt() error { - if !isGoLatest() { + if !isGoLatest() && !isUnix() { return nil } - pkgs, err := hugoPackages() + s, err := sh.Output("./check_gofmt.sh") if err != nil { - return err - } - failed := false - first := true - for _, pkg := range pkgs { - files, err := filepath.Glob(filepath.Join(pkg, "*.go")) - if err != nil { - return nil - } - for _, f := range files { - // gofmt doesn't exit with non-zero when it finds unformatted code - // so we have to explicitly look for output, and if we find any, we - // should fail this target. - s, err := sh.Output("gofmt", "-l", f) - if err != nil { - fmt.Printf("ERROR: running gofmt on %q: %v\n", f, err) - failed = true - } - if s != "" { - if first { - fmt.Println("The following files are not gofmt'ed:") - first = false - } - failed = true - fmt.Println(s) - } - } - } - if failed { - return errors.New("improperly formatted go files") + fmt.Println(s) + return fmt.Errorf("gofmt needs to be run: %s", err) } + return nil } @@ -332,6 +305,10 @@ func isGoLatest() bool { return strings.Contains(runtime.Version(), "1.21") } +func isUnix() bool { + return runtime.GOOS != "windows" +} + func isCI() bool { return os.Getenv("CI") != "" } diff --git a/markup/goldmark/goldmark_config/config.go b/markup/goldmark/goldmark_config/config.go index 1c393e3f44e..9a14bf9b884 100644 --- a/markup/goldmark/goldmark_config/config.go +++ b/markup/goldmark/goldmark_config/config.go @@ -73,10 +73,39 @@ var Default = Config{ // Config configures Goldmark. type Config struct { - DuplicateResourceFiles bool Renderer Renderer Parser Parser Extensions Extensions + DuplicateResourceFiles bool + RenderHooks RenderHooks +} + +// RenderHooks contains configuration for Goldmark render hooks. +type RenderHooks struct { + Image ImageRenderHook + Link LinkRenderHook +} + +// ImageRenderHook contains configuration for the image render hook. +type ImageRenderHook struct { + // Enable the default image render hook. + // We need to know if it is set or not, hence the pointer. + EnableDefault *bool +} + +func (h ImageRenderHook) IsEnableDefault() bool { + return h.EnableDefault != nil && *h.EnableDefault +} + +// LinkRenderHook contains configuration for the link render hook. +type LinkRenderHook struct { + // Disable the default image render hook. + // We need to know if it is set or not, hence the pointer. + EnableDefault *bool +} + +func (h LinkRenderHook) IsEnableDefault() bool { + return h.EnableDefault != nil && *h.EnableDefault } type Extensions struct { diff --git a/tpl/template_info.go b/tpl/template_info.go index b27debf1fcf..fd126d80f64 100644 --- a/tpl/template_info.go +++ b/tpl/template_info.go @@ -25,6 +25,10 @@ type FileInfo interface { Filename() string } +type IsInternalTemplateProvider interface { + IsInternalTemplate() bool +} + type ParseInfo struct { // Set for shortcode templates with any {{ .Inner }} IsInner bool diff --git a/tpl/tplimpl/embedded/templates/_default/_markup/render-image.html b/tpl/tplimpl/embedded/templates/_default/_markup/render-image.html new file mode 100644 index 00000000000..c90ababd224 --- /dev/null +++ b/tpl/tplimpl/embedded/templates/_default/_markup/render-image.html @@ -0,0 +1,15 @@ +{{- $u := urls.Parse .Destination -}} +{{- $src := $u.String -}} +{{- if not $u.IsAbs -}} + {{- with or (.Page.Resources.Get $u.Path) (resources.Get $u.Path) -}} + {{- $src = .RelPermalink -}} + {{- end -}} +{{- end -}} +{{- $attributes := dict "alt" .Text "src" $src "title" .Title -}} + +{{- /**/ -}} diff --git a/tpl/tplimpl/embedded/templates/_default/_markup/render-link.html b/tpl/tplimpl/embedded/templates/_default/_markup/render-link.html new file mode 100644 index 00000000000..bd64b204b35 --- /dev/null +++ b/tpl/tplimpl/embedded/templates/_default/_markup/render-link.html @@ -0,0 +1,26 @@ +{{- $u := urls.Parse .Destination -}} +{{- $href := $u.String -}} +{{- if not $u.IsAbs -}} + {{- with or + ($.Page.GetPage $u.Path) + ($.Page.Resources.Get $u.Path) + (resources.Get $u.Path) + -}} + {{- $href = .RelPermalink -}} + {{- with $u.RawQuery -}} + {{- $href = printf "%s?%s" $href . -}} + {{- end -}} + {{- with $u.Fragment -}} + {{- $href = printf "%s#%s" $href . -}} + {{- end -}} + {{- end -}} +{{- end -}} +{{- $attributes := dict "href" $href "title" .Title -}} +{{ .Text | safeHTML }} +{{- /**/ -}} diff --git a/tpl/tplimpl/template.go b/tpl/tplimpl/template.go index a8ba6815d55..63dc29662e4 100644 --- a/tpl/tplimpl/template.go +++ b/tpl/tplimpl/template.go @@ -55,6 +55,7 @@ const ( shortcodesPathPrefix = "shortcodes/" internalPathPrefix = "_internal/" + embeddedPathPrefix = "_embedded/" baseFileBase = "baseof" ) @@ -517,11 +518,19 @@ func (t *templateHandler) findLayout(d layouts.LayoutDescriptor, f output.Format func (t *templateHandler) newTemplateInfo(name, tpl string) templateInfo { var isText bool + var isEmbedded bool + + if strings.HasPrefix(name, embeddedPathPrefix) { + isEmbedded = true + name = strings.TrimPrefix(name, embeddedPathPrefix) + } + name, isText = t.nameIsText(name) return templateInfo{ - name: name, - isText: isText, - template: tpl, + name: name, + isText: isText, + isEmbedded: isEmbedded, + template: tpl, } } @@ -772,7 +781,7 @@ func (t *templateHandler) loadEmbedded() error { } if _, found := t.Lookup(templateName); !found { - if err := t.AddTemplate(templateName, templ); err != nil { + if err := t.AddTemplate(embeddedPathPrefix+templateName, templ); err != nil { return err } } @@ -781,7 +790,7 @@ func (t *templateHandler) loadEmbedded() error { // TODO(bep) avoid reparsing these aliases for _, alias := range aliases { alias = internalPathPrefix + alias - if err := t.AddTemplate(alias, templ); err != nil { + if err := t.AddTemplate(embeddedPathPrefix+alias, templ); err != nil { return err } } @@ -1026,6 +1035,8 @@ func (t *templateNamespace) parse(info templateInfo) (*templateState, error) { return ts, nil } +var _ tpl.IsInternalTemplateProvider = (*templateState)(nil) + type templateState struct { tpl.Template @@ -1037,6 +1048,10 @@ type templateState struct { baseInfo templateInfo // Set when a base template is used. } +func (t *templateState) IsInternalTemplate() bool { + return t.info.isEmbedded +} + func (t *templateState) GetIdentity() identity.Identity { return t.id } diff --git a/tpl/tplimpl/template_errors.go b/tpl/tplimpl/template_errors.go index 34e73a07a1f..a9d25922005 100644 --- a/tpl/tplimpl/template_errors.go +++ b/tpl/tplimpl/template_errors.go @@ -24,9 +24,10 @@ import ( var _ identity.Identity = (*templateInfo)(nil) type templateInfo struct { - name string - template string - isText bool // HTML or plain text template. + name string + template string + isText bool // HTML or plain text template. + isEmbedded bool meta *hugofs.FileMeta }