summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2024-01-30 11:43:20 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2024-01-30 19:43:01 +0100
commit252d0cf52ad39743ef97b5748c743ee3108115e0 (patch)
treeca7bd2daeb3df307511657b49d6273e858362d1d
parentec22bb31a89883db5ca95404cda4f74344fd3762 (diff)
Create default link and image render hooksfeat/defaulthooks-11933
Fixes #11933
-rwxr-xr-xcheck_gofmt.sh2
-rw-r--r--common/paths/path.go5
-rw-r--r--common/types/types.go5
-rw-r--r--config/allconfig/allconfig.go13
-rw-r--r--hugolib/content_render_hooks_test.go72
-rw-r--r--hugolib/page__per_output.go15
-rw-r--r--hugolib/pagecollections.go2
-rw-r--r--hugolib/pagecollections_test.go8
-rw-r--r--magefile.go41
-rw-r--r--markup/goldmark/goldmark_config/config.go31
-rw-r--r--tpl/template_info.go4
-rw-r--r--tpl/tplimpl/embedded/templates/_default/_markup/render-image.html15
-rw-r--r--tpl/tplimpl/embedded/templates/_default/_markup/render-link.html26
-rw-r--r--tpl/tplimpl/template.go25
-rw-r--r--tpl/tplimpl/template_errors.go7
15 files changed, 229 insertions, 42 deletions
diff --git a/check_gofmt.sh b/check_gofmt.sh
new file mode 100755
index 000000000..c77517d3f
--- /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 da99b16ac..906270cae 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 11683c196..801c511a0 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 5788e792b..c12551f56 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 93b0e1621..36d1e626f 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|<p><a href=\"/nn/p2/\">P2</a\n></p>", "<img alt=\"Pixel\" src=\"/nn/p1/pixel.nn.png\">")
+ b.AssertFileContent("public/en/p1/index.html",
+ "p1 en|<p><a href=\"/en/p2/\">P2</a\n></p>", "<img alt=\"Pixel\" src=\"/nn/p1/pixel.nn.png\">")
+ })
+
+ 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|<p><a href=\"p2\">P2</a>", "<img src=\"pixel.png\" alt=\"Pixel\">")
+ })
+}
diff --git a/hugolib/page__per_output.go b/hugolib/page__per_output.go
index 3d86cdece..a387b294e 100644
--- a/hugolib/page__per_output.go
+++ b/hugolib/page__per_output.go
@@ -469,6 +469,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 8e05ad7e6..505b10bd7 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 8fd4f0739..eaa80a109 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 c8542dad7..d4c7b65de 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 1c393e3f4..9a14bf9b8 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 b27debf1f..fd126d80f 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 000000000..c90ababd2
--- /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 -}}
+<img
+ {{- range $k, $v := $attributes -}}
+ {{- if $v -}}
+ {{- printf " %s=%q" $k $v | safeHTMLAttr -}}
+ {{- end -}}
+ {{- end -}}>
+{{- /**/ -}}
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 000000000..bd64b204b
--- /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 -}}
+<a
+ {{- range $k, $v := $attributes -}}
+ {{- if $v -}}
+ {{- printf " %s=%q" $k $v | safeHTMLAttr -}}
+ {{- end -}}
+ {{- end -}}
+ >{{ .Text | safeHTML }}</a>
+{{- /**/ -}}
diff --git a/tpl/tplimpl/template.go b/tpl/tplimpl/template.go
index a8ba6815d..63dc29662 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 34e73a07a..a9d259220 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
}