Compare commits

..

1 Commits

Author SHA1 Message Date
hugoreleaser e290504261 releaser: Prepare repository for 0.163.0-DEV
[ci skip]
2026-05-26 14:16:29 +00:00
94 changed files with 494 additions and 1406 deletions
+5 -2
View File
@@ -16,8 +16,8 @@ jobs:
test:
strategy:
matrix:
go-version: [1.26.x]
os: [ubuntu-latest, windows-latest]
go-version: [1.25.x, 1.26.x]
os: [ubuntu-latest, windows-latest] # macos disabled for now because of disk space issues.
runs-on: ${{ matrix.os }}
steps:
- if: matrix.os == 'ubuntu-latest'
@@ -39,6 +39,9 @@ jobs:
go-version: ${{ matrix.go-version }}
check-latest: true
cache: true
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Install Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
+1 -1
View File
@@ -105,7 +105,7 @@ Install Hugo from a [prebuilt binary][], package manager, or package repository.
To build Hugo from source you must install:
1. [Git][]
1. [Go][] version 1.26.0 or later
1. [Go][] version 1.25.0 or later
### Standard edition
+3 -1
View File
@@ -103,7 +103,9 @@ func (c *Inspector) MethodsFromTypes(include []reflect.Type, exclude []reflect.T
}
for _, t := range include {
for m := range t.Methods() {
for i := range t.NumMethod() {
m := t.Method(i)
if excludes[m.Name] || seen[m.Name] {
continue
}
+3 -31
View File
@@ -263,39 +263,11 @@ func (c *convertCommand) convertContents(format metadecoders.Format) error {
site := c.h.Sites[0]
workingDir := c.h.Sites[0].Deps.Conf.WorkingDir() + string(filepath.Separator)
isConvertible := func(p page.Page) bool {
// Skip pages not backed by a content file.
if p.File() == nil {
return false
}
// Skip content adapters.
if p.File().IsContentAdapter() {
return false
}
// Skip content files provided by modules, including vendored modules.
if !p.File().FileInfo().Meta().IsProject {
return false
}
// Skip content files in project mounts outside the working directory.
if !strings.HasPrefix(p.File().Filename(), workingDir) {
return false
}
return true
}
seen := make(map[string]bool)
var pagesBackedByFile page.Pages
for _, p := range c.h.Pages() {
if !isConvertible(p) {
for _, p := range site.AllPages() {
if p.File() == nil {
continue
}
filename := p.File().Filename()
if seen[filename] {
continue
}
seen[filename] = true
pagesBackedByFile = append(pagesBackedByFile, p)
}
@@ -306,7 +278,7 @@ func (c *convertCommand) convertContents(format metadecoders.Format) error {
}
site.Log.Println("processing", len(pagesBackedByFile), "content files")
for _, p := range pagesBackedByFile {
for _, p := range site.AllPages() {
if err := c.convertAndSavePage(p, site, format); err != nil {
return err
}
+2 -2
View File
@@ -367,7 +367,7 @@ func (c *hugoBuilder) newWatcher(pollIntervalStr string, dirList ...string) (*wa
case changes := <-c.r.changesFromBuild:
unlock, err := h.LockBuild()
if err != nil {
c.r.logger.Errorf("Failed to acquire a build lock: %s", err)
c.r.logger.Errorln("Failed to acquire a build lock: %s", err)
return
}
c.changeDetector.PrepareNew()
@@ -387,7 +387,7 @@ func (c *hugoBuilder) newWatcher(pollIntervalStr string, dirList ...string) (*wa
case evs := <-watcher.Events:
unlock, err := h.LockBuild()
if err != nil {
c.r.logger.Errorf("Failed to acquire a build lock: %s", err)
c.r.logger.Errorln("Failed to acquire a build lock: %s", err)
return
}
c.handleEvents(watcher, staticSyncer, evs, configSet)
+9
View File
@@ -62,6 +62,15 @@ func TestXxHashFromString(t *testing.T) {
c.Assert(got, qt.Equals, uint64(7148569436472236994))
}
func TestHashNilMapVsEmptyMap(t *testing.T) {
c := qt.New(t)
var m1 map[string]any = nil
m2 := map[string]any{}
c.Assert(HashString(m1), qt.Equals, HashString(m2))
}
func TestXxHashFromStringHexEncoded(t *testing.T) {
c := qt.New(t)
s := "The quick brown fox jumps over the lazy dog"
+1 -1
View File
@@ -1,7 +1,7 @@
package hiter
// Common iterator functions.
// Some of these are based on this discsussion: https://github.com/golang/go/issues/61898
// Some of these are are based on this discsussion: https://github.com/golang/go/issues/61898
import "iter"
+7 -3
View File
@@ -14,6 +14,7 @@
package hstore
import (
"reflect"
"sync"
"testing"
@@ -68,8 +69,9 @@ func TestScratchAddSlice(t *testing.T) {
sl := scratch.Get("intSlice")
expected := []int{1, 2, 3}
c.Assert(sl, qt.DeepEquals, expected)
if !reflect.DeepEqual(expected, sl) {
t.Errorf("Slice difference, go %q expected %q", sl, expected)
}
_, err = scratch.Add("intSlice", []int{4, 5})
c.Assert(err, qt.IsNil)
@@ -77,7 +79,9 @@ func TestScratchAddSlice(t *testing.T) {
sl = scratch.Get("intSlice")
expected = []int{1, 2, 3, 4, 5}
c.Assert(sl, qt.DeepEquals, expected)
if !reflect.DeepEqual(expected, sl) {
t.Errorf("Slice difference, go %q expected %q", sl, expected)
}
}
// https://github.com/gohugoio/hugo/issues/5275
+2 -2
View File
@@ -20,6 +20,6 @@ import "github.com/gohugoio/hugo/common/version"
var CurrentVersion = version.Version{
Major: 0,
Minor: 163,
PatchLevel: 1,
Suffix: "",
PatchLevel: 0,
Suffix: "-DEV",
}
+5
View File
@@ -134,6 +134,11 @@ func (l LowHigh[S]) Value(source S) S {
// This is only used for debugging purposes.
var InvocationCounter atomic.Int64
// NewTrue returns a pointer to b.
func NewBool(b bool) *bool {
return &b
}
// WeightProvider provides a weight.
type WeightProvider interface {
Weight() int
+14 -34
View File
@@ -9,6 +9,7 @@ import (
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/config/allconfig"
"github.com/gohugoio/hugo/hugolib"
gc "github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
"github.com/gohugoio/hugo/media"
)
@@ -359,46 +360,25 @@ weight = 3
// Issue 13535
// We changed enablement of the embedded link and image render hooks from
// booleans to enums in v0.148.0. This should throw error with v0.163.0 and later.
// booleans to enums in v0.148.0.
func TestLegacyEmbeddedRenderHookEnablement(t *testing.T) {
files := `
-- hugo.toml --
[markup.goldmark.renderHooks.image]
#KEY_VALUE_IMAGE
#KEY_VALUE
[markup.goldmark.renderHooks.link]
#KEY_VALUE_LINK
#KEY_VALUE
`
f := strings.ReplaceAll(files, "#KEY_VALUE", "enableDefault = false")
b := hugolib.Test(t, f)
c := b.H.Configs.Base.Markup.Goldmark.RenderHooks
b.Assert(c.Link.UseEmbedded, qt.Equals, gc.RenderHookUseEmbeddedNever)
b.Assert(c.Image.UseEmbedded, qt.Equals, gc.RenderHookUseEmbeddedNever)
replacer := strings.NewReplacer(
"#KEY_VALUE_IMAGE", "enableDefault = false",
"#KEY_VALUE_LINK", "",
)
f := replacer.Replace(files)
b, _ := hugolib.TestE(t, f)
b.AssertLogContains("ERROR deprecated")
replacer = strings.NewReplacer(
"#KEY_VALUE_IMAGE", "enableDefault = true",
"#KEY_VALUE_LINK", "",
)
f = replacer.Replace(files)
b, _ = hugolib.TestE(t, f)
b.AssertLogContains("ERROR deprecated")
replacer = strings.NewReplacer(
"#KEY_VALUE_IMAGE", "",
"#KEY_VALUE_LINK", "enableDefault = false",
)
f = replacer.Replace(files)
b, _ = hugolib.TestE(t, f)
b.AssertLogContains("ERROR deprecated")
replacer = strings.NewReplacer(
"#KEY_VALUE_IMAGE", "",
"#KEY_VALUE_LINK", "enableDefault = true",
)
f = replacer.Replace(files)
b, _ = hugolib.TestE(t, f)
b.AssertLogContains("ERROR deprecated")
f = strings.ReplaceAll(files, "#KEY_VALUE", "enableDefault = true")
b = hugolib.Test(t, f)
c = b.H.Configs.Base.Markup.Goldmark.RenderHooks
b.Assert(c.Link.UseEmbedded, qt.Equals, gc.RenderHookUseEmbeddedFallback)
b.Assert(c.Image.UseEmbedded, qt.Equals, gc.RenderHookUseEmbeddedFallback)
}
+1 -12
View File
@@ -24,7 +24,6 @@ import (
"github.com/gohugoio/hugo/cache/httpcache"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstrings"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
@@ -88,18 +87,8 @@ var allDecoderSetups = map[string]decodeWeight{
"imaging": {
key: "imaging",
decode: func(d decodeWeight, p decodeConfig) error {
m := p.p.GetStringMap(d.key)
if _, found := m["quality"]; found {
hugo.DeprecateWithLogger("project config key imaging.quality", "Set the quality per format instead with imaging.jpeg.quality, imaging.webp.quality and/or imaging.avif.quality.", "v0.163.0", p.logger.Logger())
}
if _, found := m["compression"]; found {
hugo.DeprecateWithLogger("project config key imaging.compression", "Set the compression type per format instead with imaging.webp.compression and/or imaging.avif.compression.", "v0.163.0", p.logger.Logger())
}
if _, found := m["hint"]; found {
hugo.DeprecateWithLogger("project config key imaging.hint", "Set the hint per format instead with imaging.webp.hint and/or imaging.avif.hint.", "v0.163.0", p.logger.Logger())
}
var err error
p.c.Imaging, err = images.DecodeConfig(m)
p.c.Imaging, err = images.DecodeConfig(p.p.GetStringMap(d.key))
return err
},
},
+1 -1
View File
@@ -19,7 +19,7 @@ import (
"github.com/gohugoio/hugo/docshelper"
)
// This is just a helper used to create some JSON used in the Hugo docs.
// This is is just some helpers used to create some JSON used in the Hugo docs.
func init() {
docsProvider := func() docshelper.DocProvider {
cfg := config.New()
+3 -91
View File
@@ -18,11 +18,8 @@ import (
"encoding/json"
"errors"
"fmt"
"net/netip"
"net/url"
"reflect"
"slices"
"strconv"
"strings"
"github.com/gohugoio/hugo/common/herrors"
@@ -192,102 +189,17 @@ func (c Config) CheckAllowedGetEnv(name string) error {
return nil
}
func (c Config) CheckAllowedHTTPURL(u string) error {
deny := func(name string) error {
func (c Config) CheckAllowedHTTPURL(url string) error {
if !c.HTTP.URLs.Accept(url) {
return &AccessDeniedError{
name: name,
name: url,
path: "security.http.urls",
policies: c.ToTOML(),
}
}
if !c.HTTP.URLs.Accept(u) {
return deny(u)
}
// A host can be written as an integer/hex/octal IPv4 literal
// (e.g. http://2130706433/ == http://127.0.0.1/) that has no dot and
// thus slips past IP-literal deny rules. Re-check the canonical form so
// the policy treats every encoding of the same address alike.
if canon, ok := canonicalIPv4URL(u); ok && !c.HTTP.URLs.Accept(canon) {
return deny(u)
}
return nil
}
// canonicalIPv4URL rewrites an integer/hex/octal IPv4 host in rawURL to its
// canonical dotted-decimal form (inet_aton semantics), returning ok=false when
// the host is a normal name or already dotted-decimal.
func canonicalIPv4URL(rawURL string) (string, bool) {
u, err := url.Parse(rawURL)
if err != nil {
return "", false
}
host := u.Hostname()
ip, ok := parseInetAtonIPv4(host)
if !ok || ip.String() == host {
return "", false
}
if port := u.Port(); port != "" {
u.Host = ip.String() + ":" + port
} else {
u.Host = ip.String()
}
return u.String(), true
}
// parseInetAtonIPv4 parses the inet_aton IPv4 forms (14 dot-separated parts,
// each decimal, octal "0..." or hex "0x..."), e.g. "2130706433", "0x7f.0.0.1".
func parseInetAtonIPv4(host string) (netip.Addr, bool) {
if host == "" {
return netip.Addr{}, false
}
parts := strings.Split(host, ".")
if len(parts) > 4 {
return netip.Addr{}, false
}
vals := make([]uint64, len(parts))
for i, p := range parts {
v, ok := parseCInt(p)
if !ok {
return netip.Addr{}, false
}
vals[i] = v
}
maxLast := []uint64{0xffffffff, 0xffffff, 0xffff, 0xff}[len(parts)-1]
var n uint64
for i, v := range vals {
if i == len(parts)-1 {
if v > maxLast {
return netip.Addr{}, false
}
n |= v
} else {
if v > 0xff {
return netip.Addr{}, false
}
n |= v << (8 * (3 - i))
}
}
return netip.AddrFrom4([4]byte{byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n)}), true
}
func parseCInt(s string) (uint64, bool) {
base := 10
switch {
case len(s) >= 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X'):
base, s = 16, s[2:]
case len(s) >= 2 && s[0] == '0':
base, s = 8, s[1:]
}
if s == "" {
return 0, false
}
v, err := strconv.ParseUint(s, base, 64)
if err != nil {
return 0, false
}
return v, true
}
func (c Config) CheckAllowedHTTPMethod(method string) error {
if !c.HTTP.Methods.Accept(method) {
return &AccessDeniedError{
-35
View File
@@ -298,41 +298,6 @@ func TestCheckAllowedHTTPURLDigitHostnameIssue14837(t *testing.T) {
}
}
// Integer/hex/octal IPv4 encodings must be denied just like their dotted-decimal
// literals; digit-leading hostnames must still be allowed. See issue 14856.
func TestCheckAllowedHTTPURLIntegerIPEncodings(t *testing.T) {
t.Parallel()
c := qt.New(t)
pc, err := DecodeConfig(config.New())
c.Assert(err, qt.IsNil)
for _, u := range []string{
"http://2130706433/", // 127.0.0.1 decimal
"http://2130706433:9777/x", // 127.0.0.1 decimal, port
"http://2852039166/", // 169.254.169.254 (cloud metadata)
"http://0x7f000001/", // 127.0.0.1 hex
"http://017700000001/", // 127.0.0.1 octal
"http://0x7f.0.0.1/", // 127.0.0.1 dotted hex
"http://0177.0.0.1/", // 127.0.0.1 dotted octal
"http://127.1/", // 127.0.0.1 short form
"http://0/", // 0.0.0.0
"http://0xa9fea9fe/", // 169.254.169.254 hex
} {
err := pc.CheckAllowedHTTPURL(u)
c.Assert(err, qt.IsNotNil, qt.Commentf(u))
}
for _, u := range []string{
"https://1password.com/",
"https://37signals.com/foo",
"https://3com.com/",
"https://0x.tools/",
} {
c.Assert(pc.CheckAllowedHTTPURL(u), qt.IsNil, qt.Commentf(u))
}
}
func TestCheckAllowedContent(t *testing.T) {
t.Parallel()
c := qt.New(t)
-18
View File
@@ -435,28 +435,10 @@ type DepsCfg struct {
// Build triggered by the IntegrationTest framework.
IsIntegrationTest bool
// TestCfg holds configuration used only in tests.
// It is a programming error to set this when IsIntegrationTest is not set,
// and doing so will panic.
TestCfg TestConfig
// ChangesFromBuild for changes passed back to the server/watch process.
ChangesFromBuild chan []identity.Identity
}
// TestConfig holds configuration used only in tests.
// See DepsCfg.TestCfg.
type TestConfig struct {
// WarpcMemory, if set, overrides the memory limit in MiB for the WASM based
// image processors (WebP and AVIF). Used to provoke memory allocation failures.
WarpcMemory int
}
// IsZero reports whether c holds no test configuration.
func (c TestConfig) IsZero() bool {
return c == TestConfig{}
}
// BuildState are state used during a build.
type BuildState struct {
counter uint64
+12 -12
View File
@@ -12,18 +12,18 @@ require (
github.com/bep/goat v0.5.0
github.com/bep/godartsass/v2 v2.5.0
github.com/bep/golibsass v1.2.0
github.com/bep/golocales v0.2.0
github.com/bep/golocales v0.1.0
github.com/bep/goportabletext v0.2.0
github.com/bep/helpers v0.12.0
github.com/bep/imagemeta v0.17.2
github.com/bep/lazycache v0.8.1
github.com/bep/logg v0.4.0
github.com/bep/mclib v1.20401.20400
github.com/bep/overlayfs v0.11.0
github.com/bep/overlayfs v0.10.0
github.com/bep/simplecobra v0.7.0
github.com/bep/textandbinarywriter v0.1.0
github.com/bep/tmc v0.6.0
github.com/bits-and-blooms/bitset v1.24.5
github.com/bits-and-blooms/bitset v1.24.4
github.com/cespare/xxhash/v2 v2.3.0
github.com/clbanning/mxj/v2 v2.7.0
github.com/dustin/go-humanize v1.0.1
@@ -32,7 +32,7 @@ require (
github.com/fortytw2/leaktest v1.3.0
github.com/frankban/quicktest v1.14.6
github.com/fsnotify/fsnotify v1.9.0
github.com/getkin/kin-openapi v0.139.0
github.com/getkin/kin-openapi v0.138.0
github.com/gobuffalo/flect v1.0.3
github.com/gobwas/glob v0.2.3
github.com/goccy/go-yaml v1.19.2
@@ -60,7 +60,7 @@ require (
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
github.com/pelletier/go-toml/v2 v2.3.1
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
github.com/rogpeppe/go-internal v1.15.0
github.com/rogpeppe/go-internal v1.14.1
github.com/spf13/afero v1.15.0
github.com/spf13/cast v1.10.0
github.com/spf13/cobra v1.10.2
@@ -68,16 +68,16 @@ require (
github.com/spf13/pflag v1.0.10
github.com/tdewolff/minify/v2 v2.24.13
github.com/tdewolff/parse/v2 v2.8.12
github.com/tetratelabs/wazero v1.12.0
github.com/tetratelabs/wazero v1.11.1-0.20260521072212-475a1f8f0dc3
github.com/yuin/goldmark v1.8.2
github.com/yuin/goldmark-emoji v1.0.6
go.uber.org/automaxprocs v1.5.3
gocloud.dev v0.45.0
golang.org/x/image v0.42.0
golang.org/x/image v0.41.0
golang.org/x/mod v0.36.0
golang.org/x/net v0.55.0
golang.org/x/sync v0.21.0
golang.org/x/text v0.38.0
golang.org/x/sync v0.20.0
golang.org/x/text v0.37.0
golang.org/x/tools v0.45.0
google.golang.org/api v0.276.0
rsc.io/qr v0.2.0
@@ -154,8 +154,8 @@ require (
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/oasdiff/yaml v0.1.0 // indirect
github.com/oasdiff/yaml3 v0.0.13 // indirect
github.com/oasdiff/yaml v0.0.9 // indirect
github.com/oasdiff/yaml3 v0.0.12 // indirect
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
github.com/olekukonko/errors v1.2.0 // indirect
github.com/olekukonko/ll v0.1.6 // indirect
@@ -191,4 +191,4 @@ require (
software.sslmate.com/src/go-pkcs12 v0.7.0 // indirect
)
go 1.26.0
go 1.25.0
+22 -22
View File
@@ -158,8 +158,8 @@ github.com/bep/godartsass/v2 v2.5.0 h1:tKRvwVdyjCIr48qgtLa4gHEdtRkPF8H1OeEhJAEv7
github.com/bep/godartsass/v2 v2.5.0/go.mod h1:rjsi1YSXAl/UbsGL85RLDEjRKdIKUlMQHr6ChUNYOFU=
github.com/bep/golibsass v1.2.0 h1:nyZUkKP/0psr8nT6GR2cnmt99xS93Ji82ZD9AgOK6VI=
github.com/bep/golibsass v1.2.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA=
github.com/bep/golocales v0.2.0 h1:4H1H5UPw3ainpj5zykeEfiMRQngyaIC/t+I4Dvn+fvE=
github.com/bep/golocales v0.2.0/go.mod h1:Hl78nje8mNL3LzLeJvYN9NsIZgyFJGrGfvgO9r1+mwE=
github.com/bep/golocales v0.1.0 h1:rjWf1S4basIje+G+je5WMW8G+yzaoz4gEDFolrFVdvA=
github.com/bep/golocales v0.1.0/go.mod h1:Hl78nje8mNL3LzLeJvYN9NsIZgyFJGrGfvgO9r1+mwE=
github.com/bep/goportabletext v0.2.0 h1:CZ9f8jADBWqHwBymQiJJPCTSV/tHSA+PYzlUf86Yze0=
github.com/bep/goportabletext v0.2.0/go.mod h1:xDeA5+qcgKzJq6Q6XjAiBKtxLD3Yn7f6XP4joD3J3qU=
github.com/bep/helpers v0.12.0 h1:tD6V2DQW0B+FUynF2etR/106S/TO9akm+vA/Hk24GxY=
@@ -174,16 +174,14 @@ github.com/bep/mclib v1.20401.20400 h1:silTOMNlNI7yHBb+HxEE0THIVFVWo/0I4SCH69Fxt
github.com/bep/mclib v1.20401.20400/go.mod h1:v5Hh3EIinPn7epigP28uf9JCkZlYzBS2vEOPe2wrHzM=
github.com/bep/overlayfs v0.10.0 h1:wS3eQ6bRsLX+4AAmwGjvoFSAQoeheamxofFiJ2SthSE=
github.com/bep/overlayfs v0.10.0/go.mod h1:ouu4nu6fFJaL0sPzNICzxYsBeWwrjiTdFZdK4lI3tro=
github.com/bep/overlayfs v0.11.0 h1:aymHDGC0CHpvn0XvTfgpK6skCp16oMi+tdUF32l6pPs=
github.com/bep/overlayfs v0.11.0/go.mod h1:L+ggdoKm+Y7Xb4a1osd+/LOPG4qsY62snqRqJH5Mspc=
github.com/bep/simplecobra v0.7.0 h1:kG8ZPwEc1o96hlIVGXcrrvwC8RornBqvMD3+pS0Z7y0=
github.com/bep/simplecobra v0.7.0/go.mod h1:PDXvBWH1ZMX05DRQ25ub/C6kKUuq+jROPgjbVz8wO1g=
github.com/bep/textandbinarywriter v0.1.0 h1:KXmXsRN2Uhwhm1G3e/snM8+5SPQBJrCEpIosdIBR3po=
github.com/bep/textandbinarywriter v0.1.0/go.mod h1:dAcHveajlWWU7PXhp6Dn4PIAYDg2H13Huif9xMS2w8w=
github.com/bep/tmc v0.6.0 h1:5zWy4L+3gS+Kk8czzLC4g7ETaC3wkX9ZtTRdAdL8V4s=
github.com/bep/tmc v0.6.0/go.mod h1:SNHxc3o2WSNMAYqJcAO0rxFY+pbhZzMwjIHe5xaAue0=
github.com/bits-and-blooms/bitset v1.24.5 h1:654xBVHc23gJMAgOTkPNoCVfiRxuIOAUnAZFtopqJ4w=
github.com/bits-and-blooms/bitset v1.24.5/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -240,8 +238,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/getkin/kin-openapi v0.139.0 h1:pBFXcZJFwz9J1X64jzxlOoNgFm+TF7kNrs9+HJVN6Ic=
github.com/getkin/kin-openapi v0.139.0/go.mod h1:NGxPfE4PwS/TRXEbyx2RrxDFPZvxcWw31Tw8XXjPZLs=
github.com/getkin/kin-openapi v0.138.0 h1:ebfE0JAmF6AqHrNBy1KO3Fs68K9tPs48HalvLPo7Rv4=
github.com/getkin/kin-openapi v0.138.0/go.mod h1:vUYWaKyMqj7PfTybelXtLuLN9tReS12vxnzMRK+z2GY=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -424,10 +422,10 @@ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/niklasfasching/go-org v1.9.1 h1:/3s4uTPOF06pImGa2Yvlp24yKXZoTYM+nsIlMzfpg/0=
github.com/niklasfasching/go-org v1.9.1/go.mod h1:ZAGFFkWvUQcpazmi/8nHqwvARpr1xpb+Es67oUGX/48=
github.com/oasdiff/yaml v0.1.0 h1:0bqZjfKc/8S9urj4JuwepX41WX9EoA6ifhU3SV06cXg=
github.com/oasdiff/yaml v0.1.0/go.mod h1:kOlRmMdL2X3vucLCEQO5u61SU22RysnfXvcttrZA1O0=
github.com/oasdiff/yaml3 v0.0.13 h1:06svmvOHOVBqF81+sY2EUScvUI/iS/vl2VIeUUxZQwg=
github.com/oasdiff/yaml3 v0.0.13/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
github.com/oasdiff/yaml v0.0.9 h1:zQOvd2UKoozsSsAknnWoDJlSK4lC0mpmjfDsfqNwX48=
github.com/oasdiff/yaml v0.0.9/go.mod h1:8lvhgJG4xiKPj3HN5lDow4jZHPlx1i7dIwzkdAo6oAM=
github.com/oasdiff/yaml3 v0.0.12 h1:75urAtPeDg2/iDEWwzNrLOWxI9N/dCh81nTTJtokt2M=
github.com/oasdiff/yaml3 v0.0.12/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo=
@@ -458,8 +456,8 @@ github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.15.0 h1:D0RCU5rMAp+SpgkiNdrjfJ+LX4J1M32V2NeCY7EJ6hc=
github.com/rogpeppe/go-internal v1.15.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
@@ -504,8 +502,10 @@ github.com/tdewolff/parse/v2 v2.8.12/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLB
github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk=
github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU=
github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0=
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
github.com/tetratelabs/wazero v1.11.1-0.20260521072212-475a1f8f0dc3 h1:0Jpp+tPkvALC9hcZUYOj/6yWYvUIV/kKoxRDj0a6zk4=
github.com/tetratelabs/wazero v1.11.1-0.20260521072212-475a1f8f0dc3/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0=
@@ -571,8 +571,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.42.0 h1:1gSs6ehNWXLbkHBIPcWztk3D/6aIA/8hauiAYtlodVY=
golang.org/x/image v0.42.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
golang.org/x/image v0.41.0 h1:8wS72eGJMJaBxK6okTzd4WaXumUlTVlb753MlsSvTCo=
golang.org/x/image v0.41.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -650,8 +650,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -697,8 +697,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"github.com/gohugoio/hugo/docshelper"
)
// This is just a helper used to create some JSON used in the Hugo docs.
// This is is just some helpers used to create some JSON used in the Hugo docs.
func init() {
docsProvider := func() docshelper.DocProvider {
var chromaLexers []any
+2 -2
View File
@@ -200,8 +200,8 @@ func structTypes(v reflect.Value, m map[reflect.Type]struct{}) {
}
case reflect.Struct:
m[v.Type()] = struct{}{}
for _, field := range v.Fields() {
structTypes(field, m)
for i := range v.NumField() {
structTypes(v.Field(i), m)
}
}
}
-18
View File
@@ -46,7 +46,6 @@ func NewComponentFs(opts ComponentFsOptions) *componentFs {
var (
_ FilesystemUnwrapper = (*componentFs)(nil)
_ ReadDirWithContextDir = (*componentFsDir)(nil)
_ afero.Lstater = (*componentFs)(nil)
)
// componentFs is a filesystem that holds one of the Hugo components, e.g. content, layouts etc.
@@ -308,23 +307,6 @@ func (fs *componentFs) Stat(name string) (os.FileInfo, error) {
return fim, nil
}
func (fs *componentFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {
if lstater, ok := fs.Fs.(afero.Lstater); ok {
fi, b, err := lstater.LstatIfPossible(name)
if err != nil {
return nil, false, err
}
fim, ok := fs.applyMeta(fi, name)
if !ok {
return nil, false, os.ErrNotExist
}
return fim, b, nil
}
fi, err := fs.Stat(name)
return fi, false, err
}
func (fs *componentFs) applyMeta(fi FileNameIsDir, name string) (FileMetaInfo, bool) {
if runtime.GOOS == "darwin" {
name = norm.NFC.String(name)
-10
View File
@@ -244,8 +244,6 @@ func WrapFilesystem(container, content afero.Fs) afero.Fs {
return filesystemsWrapper{Fs: container, content: content}
}
var _ afero.Lstater = (*filesystemsWrapper)(nil)
type filesystemsWrapper struct {
afero.Fs
content afero.Fs
@@ -255,14 +253,6 @@ func (w filesystemsWrapper) UnwrapFilesystem() afero.Fs {
return w.content
}
func (w filesystemsWrapper) LstatIfPossible(name string) (os.FileInfo, bool, error) {
if lstater, ok := w.Fs.(afero.Lstater); ok {
return lstater.LstatIfPossible(name)
}
fi, err := w.Fs.Stat(name)
return fi, false, err
}
type ReadDirWithContextDir interface {
ReadDirWithContext(context context.Context, count int) ([]iofs.DirEntry, context.Context, error)
}
-52
View File
@@ -1,52 +0,0 @@
// Copyright 2026 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.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hugofs
import (
"os"
"github.com/spf13/afero"
)
// NewDropSymlinksFs returns an afero.Fs wrapper that treats symlinks as non-existing files.
func NewDropSymlinksFs(base afero.Fs) *DropSymlinksFs {
return &DropSymlinksFs{base}
}
// DropSymlinksFs is an afero.Fs wrapper that treats symlinks as non-existing files.
type DropSymlinksFs struct {
afero.Fs
}
func (fs *DropSymlinksFs) Open(name string) (afero.File, error) {
if _, err := fs.Stat(name); err != nil {
return nil, err
}
f, err := fs.Fs.Open(name)
if err != nil {
return nil, err
}
return f, nil
}
func (fs *DropSymlinksFs) Stat(name string) (os.FileInfo, error) {
fi, err := LstatIfPossible(fs.Fs, name)
if err != nil {
return nil, err
}
if fi.Mode()&os.ModeSymlink != 0 {
return nil, os.ErrNotExist
}
return fi, nil
}
+1 -1
View File
@@ -488,7 +488,7 @@ func (m *pageMap) forEachResourceInPage(
prefix := paths.AddTrailingSlash(ps.Path())
isBranch := ps.IsBranch()
isBranch := ps.IsNode()
rwr := &doctree.NodeShiftTreeWalker[contentNode]{
Tree: m.treeResources,
+1 -1
View File
@@ -303,7 +303,7 @@ func (h helperContentNode) isBranchNode(n contentNode) bool {
case *pageMetaSource:
return nn.pathInfo.IsBranchBundle()
case *pageState:
return nn.IsBranch()
return nn.IsNode()
case contentNodeSampleProvider:
return h.isBranchNode(nn.sample())
default:
+9 -9
View File
@@ -239,9 +239,9 @@ defaultContentLanguageInSubdir = true
duplicateResourceFiles = false
[markup.goldmark.renderhooks]
[markup.goldmark.renderhooks.link]
#useEmbedded = 'never'
#enableDefault = false
[markup.goldmark.renderhooks.image]
#useEmbedded = 'never'
#enableDefault = false
[languages]
[languages.en]
weight = 1
@@ -291,7 +291,7 @@ iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAA
})
t.Run("Disabled", func(t *testing.T) {
b := Test(t, strings.ReplaceAll(files, "#useEmbedded = 'never'", "useEmbedded = 'never'"))
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\">")
@@ -304,9 +304,9 @@ func TestRenderHooksDefaultEscape(t *testing.T) {
[markup.goldmark.extensions.typographer]
disable = true
[markup.goldmark.renderHooks.image]
useEmbedded = 'WHEN'
enableDefault = ENABLE
[markup.goldmark.renderHooks.link]
useEmbedded = 'WHEN'
enableDefault = ENABLE
[markup.goldmark.parser]
wrapStandAloneImageWithinParagraph = false
[markup.goldmark.parser.attribute]
@@ -326,13 +326,13 @@ Image: ![alt-"<>&](/destination-"<> 'title-"<>&')
{{ .Content }}
`
for _, when := range []string{"auto", "never", "always", "fallback"} {
t.Run(fmt.Sprint(when), func(t *testing.T) {
for _, enabled := range []bool{true, false} {
t.Run(fmt.Sprint(enabled), func(t *testing.T) {
t.Parallel()
b := Test(t, strings.ReplaceAll(files, "WHEN", fmt.Sprint(when)))
b := Test(t, strings.ReplaceAll(files, "ENABLE", fmt.Sprint(enabled)))
// The escaping is slightly different between the two.
if when == "always" || when == "fallback" {
if enabled {
b.AssertFileContent("public/index.html",
"Link: <a href=\"/destination-%22%3C%3E\" title=\"title-&#34;&lt;&gt;&amp;\">text-&quot;&lt;&gt;&amp;</a>",
"img src=\"/destination-%22%3C%3E\" alt=\"alt-&#34;&lt;&gt;&amp;\" title=\"title-&#34;&lt;&gt;&amp;\">",
+3 -22
View File
@@ -429,16 +429,6 @@ func (s *IntegrationTestBuilder) negate(match string) (string, bool) {
return match, negate
}
// AssertFileContentStartsWith asserts that the content of the given file starts with s.
func (s *IntegrationTestBuilder) AssertFileContentStartsWith(filename, prefix string) {
s.Helper()
content := strings.TrimSpace(s.FileContent(filename))
cm := qt.Commentf("File: %s Expect:\n%s Got:\n%s\nWith Space Visuals:\n%s", filename, prefix, content, util.VisualizeSpaces([]byte(content)))
var negate bool
prefix, negate = s.negate(prefix)
s.Assert(strings.HasPrefix(content, prefix), qt.Equals, !negate, cm)
}
func (s *IntegrationTestBuilder) AssertFileContent(filename string, matches ...string) {
s.Helper()
content := strings.TrimSpace(s.FileContent(filename))
@@ -559,12 +549,7 @@ func (s *IntegrationTestBuilder) AssertPublishDir(matches ...string) {
func (s *IntegrationTestBuilder) AssertFs(fs afero.Fs, matches ...string) {
s.Helper()
var buff bytes.Buffer
if err := s.printAndCheckFs(fs, "", &buff); err != nil {
// E.g. public not created, treat that as an empty dir.
if !errors.Is(err, os.ErrNotExist) {
s.Fatal(err)
}
}
s.Assert(s.printAndCheckFs(fs, "", &buff), qt.IsNil)
printFsLines := strings.Split(buff.String(), "\n")
sort.Strings(printFsLines)
content := strings.TrimSpace((strings.Join(printFsLines, "\n")))
@@ -594,7 +579,7 @@ func (s *IntegrationTestBuilder) printAndCheckFs(fs afero.Fs, path string, w io.
return afero.Walk(fs, path, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
return fmt.Errorf("error: path %q: %s", path, err)
}
path = filepath.ToSlash(path)
if path == "" {
@@ -962,7 +947,7 @@ func (s *IntegrationTestBuilder) initBuilder() error {
// In the full setup, this channel is created in the commands package.
changesFromBuild := make(chan []identity.Identity, 10)
depsCfg := deps.DepsCfg{Configs: res, Fs: fs, LogLevel: logger.Level(), StdErr: logger.StdErr(), ChangesFromBuild: changesFromBuild, IsIntegrationTest: true, TestCfg: deps.TestConfig{WarpcMemory: s.Cfg.WarpcMemory}}
depsCfg := deps.DepsCfg{Configs: res, Fs: fs, LogLevel: logger.Level(), StdErr: logger.StdErr(), ChangesFromBuild: changesFromBuild, IsIntegrationTest: true}
sites, err := NewHugoSites(depsCfg)
if err != nil {
initErr = err
@@ -1241,8 +1226,4 @@ type IntegrationTestConfig struct {
// The config to pass to Build.
BuildCfg BuildCfg
// WarpcMemory, if set, overrides the memory limit in MiB for the WASM based
// image processors (WebP and AVIF). Used to provoke memory allocation failures.
WarpcMemory int
}
+1 -6
View File
@@ -469,12 +469,7 @@ func (m *pageMeta) Name() string {
}
func (m *pageMeta) IsNode() bool {
hugo.Deprecate(".Page.IsNode", "Use .Page.IsBranch or not .Page.IsPage instead.", "v0.163.0")
return m.IsBranch()
}
func (m *pageMeta) IsBranch() bool {
return kinds.IsBranch(m.Kind())
return !m.IsPage()
}
func (m *pageMeta) IsPage() bool {
+1 -1
View File
@@ -48,7 +48,7 @@ func newPageOutput(
var paginatorProvider page.PaginatorProvider
var pag *pagePaginator
if render && ps.IsBranch() {
if render && ps.IsNode() {
pag = newPagePaginator(ps)
paginatorProvider = pag
} else {
+5 -42
View File
@@ -1994,57 +1994,20 @@ func content(c resource.ContentProvider) string {
return ccs
}
// See issue 11574.
func TestPageIsBranch(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ["taxonomy", "term", "rss", "sitemap", "robotsTXT", "404"]
-- content/_index.md --
-- content/sect/_index.md --
-- content/sect/p1.md --
-- layouts/all.html --
{{ .Kind }}|IsBranch={{ .IsBranch }}|IsPage={{ .IsPage }}
`
b := Test(t, files)
b.AssertFileContent("public/index.html", "home|IsBranch=true|IsPage=false")
b.AssertFileContent("public/sect/index.html", "section|IsBranch=true|IsPage=false")
b.AssertFileContent("public/sect/p1/index.html", "page|IsBranch=false|IsPage=true")
}
// See issue 11574.
func TestPageIsNodeDeprecated(t *testing.T) {
files := `
-- hugo.toml --
disableKinds = ["taxonomy", "term", "rss", "sitemap", "robotsTXT", "404"]
-- content/_index.md --
-- layouts/all.html --
{{ .IsNode }}
`
b := Test(t, files, TestOptInfo())
b.AssertLogContains(".Page.IsNode was deprecated")
}
func BenchmarkIsTranslatedOneLanguage(b *testing.B) {
// Set it reasonably high to get a balance between cached and uncached calls to IsTranslated.
const numPages = 3000
var files strings.Builder
files.WriteString(`
files := `
-- hugo.toml --
disableKinds = ["taxonomy", "term"]
`)
`
for i := range numPages {
files.WriteString(fmt.Sprintf(`
-- content/sect/p%d.md --`, i))
files += fmt.Sprintf(`
-- content/sect/p%d.md --`, i)
}
bb := Test(b, files.String(), TestOptSkipRender())
bb := Test(b, files, TestOptSkipRender())
p := bb.H.Sites[0].RegularPages()
b.ResetTimer()
+2 -1
View File
@@ -17,6 +17,7 @@ import (
"context"
"fmt"
"io"
"path/filepath"
"github.com/gohugoio/hugo/common/hashing"
"github.com/gohugoio/hugo/common/hmaps"
@@ -345,7 +346,7 @@ func (p *PagesFromTemplate) Execute(ctx context.Context) (BuildInfo, error) {
}
defer f.Close()
tmpl, err := p.TemplateStore.TextParse(p.GoTmplFi.Meta().PathInfo.Path(), helpers.ReaderToString(f))
tmpl, err := p.TemplateStore.TextParse(filepath.ToSlash(p.GoTmplFi.Meta().Filename), helpers.ReaderToString(f))
if err != nil {
return BuildInfo{}, err
}
@@ -14,7 +14,6 @@
package pagesfromdata_test
import (
"bytes"
"fmt"
"strings"
"testing"
@@ -924,25 +923,3 @@ iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAA
b.AssertFileContent("public/p1/index.html", "Resized: 1x1|")
}
// See https://github.com/gohugoio/hugo/issues/14999
func TestContentAdapterTemplateMetricsRelativePath(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
templateMetrics = true
-- layouts/all.html --
{{ .Title }}
-- content/docs/_content.gotmpl --
{{ .AddPage (dict "path" "/docs/p1" "title" "P1") }}
`
b := hugolib.Test(t, files)
var buf bytes.Buffer
b.H.Metrics.WriteMetrics(&buf)
got := buf.String()
b.Assert(got, qt.Contains, "/docs/_content.gotmpl")
b.Assert(got, qt.Not(qt.Contains), "content/docs/_content.gotmpl")
}
+2 -2
View File
@@ -69,9 +69,9 @@ func TestRSSCanonifyURLsSubDir(t *testing.T) {
baseURL = 'https://example.org/subdir'
disableKinds = ['section','sitemap','taxonomy','term']
[markup.goldmark.renderHooks.image]
useEmbedded = 'fallback'
enableDefault = true
[markup.goldmark.renderHooks.link]
useEmbedded = 'fallback'
enableDefault = true
-- layouts/_markup/render-image.html --
{{- $u := urls.Parse .Destination -}}
{{- $src := $u.String | relURL -}}
+17 -29
View File
@@ -44,35 +44,17 @@ type SegmentFilter interface {
ShouldExcludeFine(SegmentQuery) bool
}
type segmentPredicate struct {
include predicate.PR[SegmentQuery]
exclude predicate.PR[SegmentQuery]
}
type segmentFilter struct {
segments []segmentPredicate
exclude predicate.PR[SegmentQuery]
include predicate.PR[SegmentQuery]
}
// ShouldExcludeCoarse skips a whole site or output format only if every
// segment excludes it; a single segment that doesn't is enough to keep it.
func (f segmentFilter) ShouldExcludeCoarse(q SegmentQuery) bool {
for _, s := range f.segments {
if !s.exclude(q).OK() {
return false
}
}
return true
return f.exclude(q).OK()
}
// ShouldExcludeFine renders the query if any segment includes it and does not
// exclude it.
func (f segmentFilter) ShouldExcludeFine(q SegmentQuery) bool {
for _, s := range f.segments {
if s.include(q).OK() && !s.exclude(q).OK() {
return false
}
}
return true
return f.exclude(q).OK() || !f.include(q).OK()
}
type segmentsBuilder struct {
@@ -222,17 +204,23 @@ func (s *segmentsBuilder) build() (SegmentFilter, error) {
if err != nil {
return nil, err
}
if include == nil {
include = matchAll
if sf.include == nil {
sf.include = include
} else {
sf.include = sf.include.Or(include)
}
if exclude == nil {
exclude = matchNothing
if sf.exclude == nil {
sf.exclude = exclude
} else {
sf.exclude = sf.exclude.Or(exclude)
}
sf.segments = append(sf.segments, segmentPredicate{include: include, exclude: exclude})
}
if len(sf.segments) == 0 {
sf.segments = append(sf.segments, segmentPredicate{include: matchAll, exclude: matchNothing})
if sf.exclude == nil {
sf.exclude = matchNothing
}
if sf.include == nil {
sf.include = matchAll
}
return sf, nil
@@ -14,7 +14,6 @@
package segments_test
import (
"strings"
"testing"
qt "github.com/frankban/quicktest"
@@ -77,64 +76,6 @@ tags: ["tag1", "tag2"]
b.AssertFileExists("public/no/index.xml", false)
}
// See issue 15024.
func TestSegmentsMultiple(t *testing.T) {
filesTemplate := `
-- hugo.toml --
renderSegments = SEGMENTS
disableKinds = ["home", "taxonomy", "term", "page"]
[outputs]
section = ['html', 'json']
[segments]
[segments.excludeallkinds]
[[segments.excludeallkinds.excludes]]
kind = "**"
[segments.blog]
[[segments.blog.includes]]
path = "{/blog,/blog/**}"
[[segments.blog.excludes]]
output = 'json'
[segments.news]
[[segments.news.includes]]
path = "{/news,/news/**}"
-- layouts/all.html --
{{ .Kind }}: {{ .Title }}|{{ .RelPermalink }}|
-- layouts/all.json --
{{ .Kind }}: {{ .Title }}|{{ .RelPermalink }}|
-- content/blog/_index.md --
-- content/blog/page1.md --
---
title: "Blog Page 1"
tags: ["tag1", "tag2"]
---
-- content/news/_index.md --
-- content/news/page1.md --
---
title: "News Page 1"
tags: ["tag1", "tag2"]
---
`
files := strings.ReplaceAll(filesTemplate, "SEGMENTS", `["excludeallkinds", "blog", "news"]`)
b := hugolib.Test(t, files, hugolib.TestOptInfo())
b.AssertPublishDir(`
blog/index.html
! blog/index.json
news/index.html
news/index.json
`)
files = strings.ReplaceAll(filesTemplate, "SEGMENTS", `["excludeallkinds"]`)
b = hugolib.Test(t, files, hugolib.TestOptInfo())
b.AssertPublishDir(`
! json
! html
`)
}
// See issue 14939.
func TestRenderSegmentsMergesHugoStatsJSON(t *testing.T) {
files := `
+2 -11
View File
@@ -197,10 +197,6 @@ func (s *Site) Debug() {
// NewHugoSites creates HugoSites from the given config.
func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
if !cfg.TestCfg.IsZero() && !cfg.IsIntegrationTest {
panic("DepsCfg.TestCfg must only be set in integration tests")
}
conf := cfg.Configs.GetFirstLanguageConfig()
rolesSorted := cfg.Configs.Base.Roles.Config.Sorted
versionsSorted := cfg.Configs.Base.Versions.Config.Sorted
@@ -261,11 +257,6 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
compilationCacheDir := filepath.Join(conf.Dirs().CacheDir, "_warpc")
imageWasmMemory := 384 // 384 MiB (4096 MiB Max)
if m := cfg.TestCfg.WarpcMemory; m > 0 {
imageWasmMemory = m
}
firstSiteDeps := &deps.Deps{
Fs: cfg.Fs,
Log: logger,
@@ -289,7 +280,7 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
warpc.Options{
CompilationCacheDir: compilationCacheDir,
PoolSize: poolSizeWebP,
Memory: imageWasmMemory,
Memory: 384, // 384 MiB (4096 MiB Max)
Infof: logger.InfoCommand("webp").Logf,
Warnf: logger.WarnCommand("webp").Logf,
},
@@ -297,7 +288,7 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
warpc.Options{
CompilationCacheDir: compilationCacheDir,
PoolSize: poolSizeAvif,
Memory: imageWasmMemory,
Memory: 384, // 384 MiB (4096 MiB Max)
Infof: logger.InfoCommand("avif").Logf,
Warnf: logger.WarnCommand("avif").Logf,
},
+1 -4
View File
@@ -42,10 +42,7 @@ Doc2
b := Test(t, files)
b.AssertFileContentStartsWith("public/sitemap.xml", `<?xml`) // Issue 14977
b.AssertFileContent("public/sitemap.xml",
"<loc>https://example.com/sect/doc1/</loc>", "doc2",
)
b.AssertFileContent("public/sitemap.xml", " <loc>https://example.com/sect/doc1/</loc>", "doc2")
}
func TestSitemapMultilingual(t *testing.T) {
+2 -3
View File
@@ -1,8 +1,7 @@
# Release env.
# These will be replaced by script before release.
HUGORELEASER_TAG=v0.163.0
HUGORELEASER_COMMITISH=4a9485336a3ff2cea07ab88e2a17ec34d5baaa6e
HUGORELEASER_TAG=v0.162.0
HUGORELEASER_COMMITISH=076dfe13d0f789e3d9586b192f8f7f3329c26990
+3 -2
View File
@@ -16,7 +16,6 @@ package esbuild
import (
"encoding/json"
"fmt"
"maps"
"path/filepath"
"sort"
"strings"
@@ -302,7 +301,9 @@ OUTER:
loaders = make(map[string]api.Loader)
// Add default CSS file loaders.
// May be overridden by opts.Loaders.
maps.Copy(loaders, extensionToLoaderMapCSS)
for ext, loader := range extensionToLoaderMapCSS {
loaders[ext] = loader
}
}
if opts.Loaders != nil {
if loaders == nil {
+1 -1
View File
@@ -159,7 +159,7 @@ func (d *AvifCodec) Decode(r io.Reader) (image.Image, error) {
return nil, fmt.Errorf("decoded AVIF frame count %d does not match frame durations %d", frameCount, len(out.Data.Params.FrameDurations))
}
frames := make([]image.Image, frameCount)
for i := range frames {
for i := 0; i < len(frames); i++ {
frameBytes := destination.Bytes()[i*frameSize : (i+1)*frameSize]
if isHDR {
// NRGBA64 for HDR - libavif returns non-premultiplied alpha.
-49
View File
@@ -66,52 +66,3 @@ gif:{{ $gif.RelPermalink }}
AssertLoopCount(0).
AssertFrameDurations(durations)
}
// See issue 14987.
func TestAvifEncodeHintSubsampling(t *testing.T) {
files := `
-- hugo.toml --
-- assets/logo.png --
sourcefilename: ../../resources/testdata/gohugoio24.png
-- layouts/home.html --
{{ $img := resources.Get "logo.png" }}
default:{{ ($img.Process "avif photo").RelPermalink }}|
photo:{{ ($img.Process "avif photo").RelPermalink }}|
text:{{ ($img.Process "avif text").RelPermalink }}|
`
b := hugolib.Test(t, files)
b.AssertFileContent("public/index.html",
`
default:/logo_hu_72e150ce03376cfd.avif|
photo:/logo_hu_72e150ce03376cfd.avif|
text:/logo_hu_9de8f282912bc925.avif|
`)
}
// See issue 14985.
func TestAvifEncodeOutOfMemory(t *testing.T) {
files := `
-- assets/gopher.png --
sourcefilename: ../../resources/testdata/bw-gopher.png
-- layouts/home.html --
{{ $img := resources.Get "gopher.png" }}
{{ $r := try ($img.Resize "3000x3000 avif") }}
{{ with $r.Err }}BigErr: {{ . }}|{{ else }}BigOK|{{ end }}
{{ $small := $img.Resize "32x32 avif" }}
SmallAfter: {{ $small.RelPermalink }}|
`
b := hugolib.Test(t, files, hugolib.TestOptWithConfig(func(c *hugolib.IntegrationTestConfig) {
c.WarpcMemory = 8
}))
// The big resize must fail gracefully (caught by try) ...
b.AssertFileContent("public/index.html",
"BigErr:",
"out of memory allocating",
"for blob data",
// ... and the dispatcher must still process images afterwards.
"SmallAfter: /gopher_",
)
}
+2 -58
View File
@@ -50,7 +50,6 @@ typedef struct
float quality; // between 1 and 100.
char compression[32]; // "lossy" or "lossless"
int encoderSpeed; // 1 (slowest, best) to 10 (fastest). 0 means use default.
char hint[64]; // drawing, icon, photo, picture, or text. Selects chroma subsampling.
} InputOptions;
typedef struct
@@ -166,12 +165,6 @@ InputMessage parse_input_message(const char *line)
strncpy(msg.data.options.compression, compression_str, sizeof(msg.data.options.compression) - 1);
msg.data.options.compression[sizeof(msg.data.options.compression) - 1] = '\0';
}
const char *hint_str = json_object_get_string(options_object, "hint");
if (hint_str != NULL)
{
strncpy(msg.data.options.hint, hint_str, sizeof(msg.data.options.hint) - 1);
msg.data.options.hint[sizeof(msg.data.options.hint) - 1] = '\0';
}
}
}
@@ -250,36 +243,6 @@ void write_output_message(const OutputMessage *msg)
json_value_free(root_value);
}
// drain_bytes discards n bytes from stream. Used to keep the protocol aligned
// after an error that prevents the blob from being consumed normally.
static void drain_bytes(FILE *stream, size_t n)
{
uint8_t buf[4096];
while (n > 0)
{
size_t want = n < sizeof(buf) ? n : sizeof(buf);
size_t got = fread(buf, 1, want, stream);
if (got == 0)
{
break;
}
n -= got;
}
}
// avifFormatForHint maps a content hint to a chroma subsampling format.
// Photographic content tolerates 4:2:0, which roughly halves the encoder's
// memory footprint and the output size. Sharp-edged content (text, icons, line
// art) keeps full 4:4:4 chroma. See issue 14987.
static avifPixelFormat avifFormatForHint(const char *hint)
{
if (strcmp(hint, "drawing") == 0 || strcmp(hint, "icon") == 0 || strcmp(hint, "text") == 0)
{
return AVIF_PIXEL_FORMAT_YUV444;
}
return AVIF_PIXEL_FORMAT_YUV420; // photo, picture, and the default.
}
void handle_commands(FILE *stream)
{
@@ -315,15 +278,7 @@ void handle_commands(FILE *stream)
blob_data = malloc((size_t)blob_size);
if (blob_data == NULL)
{
// Out of memory. Drain the blob from the input stream so the next
// command stays aligned, then report the error to the client instead
// of leaving the stream corrupted with no response.
drain_bytes(stream, (size_t)blob_size);
OutputMessage err_output = {0};
err_output.header = input.header;
snprintf(err_output.header.err, sizeof(err_output.header.err),
"out of memory allocating %u bytes for blob data", blob_size);
write_output_message(&err_output);
fprintf(stderr, "[%d] Error allocating memory for blob data\n", blob_id);
goto cleanup;
}
read_bytes = fread(blob_data, 1, (size_t)blob_size, stream);
@@ -640,15 +595,8 @@ void handle_commands(FILE *stream)
goto cleanup;
}
// Pick chroma subsampling from the content hint. Lossless keeps 4:4:4,
// since subsampling discards chroma and would defeat it.
avifPixelFormat yuvFormat = avifFormatForHint(input.data.options.hint);
if (strcmp(compression, "lossless") == 0) {
yuvFormat = AVIF_PIXEL_FORMAT_YUV444;
}
// Create image with the target bit depth for encoding.
avifImage *image = avifImageCreate(width, height, depth, yuvFormat);
avifImage *image = avifImageCreate(width, height, depth, AVIF_PIXEL_FORMAT_YUV444);
if (!image) {
snprintf(output.header.err, sizeof(output.header.err), "encodeNRGBA: Failed to create avifImage");
write_output_message(&output);
@@ -712,8 +660,6 @@ void handle_commands(FILE *stream)
if (quality < 1) quality = 1;
if (quality > 100) quality = 100;
int avif_quality = (int)((quality - 1.0) / 99.0 * 100.0);
// Keep lossy strictly below lossless so quality 100 stays lossy. See issue 14981.
if (avif_quality >= AVIF_QUALITY_LOSSLESS) avif_quality = AVIF_QUALITY_LOSSLESS - 1;
encoder->quality = avif_quality;
encoder->qualityAlpha = avif_quality;
}
@@ -838,8 +784,6 @@ void handle_commands(FILE *stream)
if (quality < 1) quality = 1;
if (quality > 100) quality = 100;
int avif_quality = (int)((quality - 1.0) / 99.0 * 100.0);
// Keep lossy strictly below lossless so quality 100 stays lossy. See issue 14981.
if (avif_quality >= AVIF_QUALITY_LOSSLESS) avif_quality = AVIF_QUALITY_LOSSLESS - 1;
encoder->quality = avif_quality;
}
encoder->qualityAlpha = AVIF_QUALITY_LOSSLESS;
+1 -26
View File
@@ -524,23 +524,6 @@ void write_output_message(const OutputMessage *msg)
json_value_free(root_value);
}
// drain_bytes discards n bytes from stream. Used to keep the protocol aligned
// after an error that prevents the blob from being consumed normally.
static void drain_bytes(FILE *stream, size_t n)
{
uint8_t buf[4096];
while (n > 0)
{
size_t want = n < sizeof(buf) ? n : sizeof(buf);
size_t got = fread(buf, 1, want, stream);
if (got == 0)
{
break;
}
n -= got;
}
}
void handle_commands(FILE *stream)
{
@@ -576,15 +559,7 @@ void handle_commands(FILE *stream)
blob_data = malloc((size_t)blob_size);
if (blob_data == NULL)
{
// Out of memory. Drain the blob from the input stream so the next
// command stays aligned, then report the error to the client instead
// of leaving the stream corrupted with no response.
drain_bytes(stream, (size_t)blob_size);
OutputMessage err_output = {0};
err_output.header = input.header;
snprintf(err_output.header.err, sizeof(err_output.header.err),
"out of memory allocating %u bytes for blob data", blob_size);
write_output_message(&err_output);
fprintf(stderr, "[%d] Error allocating memory for blob data\n", blob_id);
goto cleanup;
}
read_bytes = fread(blob_data, 1, (size_t)blob_size, stream);
+1 -5
View File
@@ -419,11 +419,7 @@ func (d *dispatcher[Q, R]) pendingCall(id uint32) *call[Q, R] {
defer d.mu.Unlock()
c, ok := d.pending[id]
if !ok {
// The WASM module wrote a response for an ID we never sent. This means it
// broke the RPC protocol, e.g. a corrupted stream after an error path that
// failed to drain its input or write a response. This is a bug in the
// module and should be reported.
panic(fmt.Errorf("received response for unknown call ID %d: WASM module violated the RPC protocol", id))
panic(fmt.Errorf("call with ID %d not found", id))
}
return c
}
Binary file not shown.
Binary file not shown.
-27
View File
@@ -121,33 +121,6 @@ sourcefilename: ../../resources/testdata/giphy.gif
b.ImageHelper("public/anim_hu_58eb49733894e7ce.gif").AssertFormat("gif").AssertIsAnimated(true).AssertLoopCount(0).AssertFrameDurations(animFrameDurations)
}
// See issue 14985.
func TestWebPEncodeOutOfMemory(t *testing.T) {
files := `
-- assets/gopher.png --
sourcefilename: ../../resources/testdata/bw-gopher.png
-- layouts/home.html --
{{ $img := resources.Get "gopher.png" }}
{{ $r := try ($img.Resize "3000x3000 webp") }}
{{ with $r.Err }}BigErr: {{ . }}|{{ else }}BigOK|{{ end }}
{{ $small := $img.Resize "32x32 webp" }}
SmallAfter: {{ $small.RelPermalink }}|
`
b := hugolib.Test(t, files, hugolib.TestOptWithConfig(func(c *hugolib.IntegrationTestConfig) {
c.WarpcMemory = 8
}))
// The big resize must fail gracefully (caught by try) ...
b.AssertFileContent("public/index.html",
"BigErr:",
"out of memory allocating",
"for blob data",
// ... and the dispatcher must still process images afterwards.
"SmallAfter: /gopher_",
)
}
func BenchmarkWebp(b *testing.B) {
files := `
-- content/p1/sunrise.webp --
-10
View File
@@ -32,13 +32,3 @@ HugoReload.prototype.reload = function (path, options) {
};
LiveReload.addPlugin(HugoReload);
// Disconnect LiveReload before the page is captured for a cross-document view
// transition. With Speculation Rules prerendering in Chromium, an active
// LiveReload WebSocket on the outgoing page causes a flash during the
// transition. LiveReload reconnects automatically on the next page load.
addEventListener('pageswap', function () {
if (window.LiveReload) {
window.LiveReload.shutDown();
}
});
+6 -6
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -519,8 +519,8 @@ func TestImageAltApostrophesWithTypographer(t *testing.T) {
-- hugo.toml --
[markup.goldmark.extensions.typographer]
disable = false
[markup.goldmark.renderHooks.image]
useEmbedded = 'always'
[markup.goldmark.renderHooks.image]
enableDefault = true
-- content/p1.md --
---
title: "p1"
+28 -21
View File
@@ -22,7 +22,6 @@ import (
"os"
"path/filepath"
"runtime"
"slices"
"strings"
"github.com/gohugoio/hugo/common/hashing"
@@ -208,19 +207,6 @@ func Pack(sourceFs, assetsWithDuplicatesPreservedFs afero.Fs, mods modules.Modul
devDependenciesKey: moduleDevDeps,
}
if err := sourceFs.MkdirAll(files.FolderPackagesHugoAutoGen, 0o777); err != nil {
return err
}
if err := writeJSON(sourceFs, workspacePackageJSON, autoGenPkg); err != nil {
return err
}
// 5. Ensure root package.json references the workspace.
if err := ensureWorkspaceRef(sourceFs, workspacePath); err != nil {
return err
}
// 6. Write metadata for the final on-disk package state.
metaFile := packageMeta{
Sum: PackageFilesSum(sourceFs, mods),
DependencySources: dependencySources{
@@ -228,7 +214,19 @@ func Pack(sourceFs, assetsWithDuplicatesPreservedFs afero.Fs, mods modules.Modul
DevDependencies: moduleDevDepsComments,
},
}
return writeJSON(sourceFs, workspacePackageMetaJSON, metaFile)
if err := sourceFs.MkdirAll(files.FolderPackagesHugoAutoGen, 0o777); err != nil {
return err
}
if err := writeJSON(sourceFs, workspacePackageJSON, autoGenPkg); err != nil {
return err
}
if err := writeJSON(sourceFs, workspacePackageMetaJSON, metaFile); err != nil {
return err
}
// 5. Ensure root package.json references the workspace.
return ensureWorkspaceRef(sourceFs, workspacePath)
}
// ensureWorkspaceRef adds workspacePath to the "workspaces" array in root
@@ -256,23 +254,23 @@ func ensureWorkspaceRef(fsys afero.Fs, workspacePath string) error {
wsVal, hasWS := pkg["workspaces"]
switch v := wsVal.(type) {
case []any:
case []interface{}:
// Array form: ["pkg-a", "pkg-b", ...]
if slices.Contains(toStringSlice(v), workspacePath) {
if containsString(toStringSlice(v), workspacePath) {
if runtime.GOOS == "windows" {
return afero.WriteFile(fsys, packageJSONName, data, 0o666)
}
return nil
}
// Fall through to byte-based insertion into the existing array below.
case map[string]any:
case map[string]interface{}:
// Object form: { "workspaces": { "packages": [...] } }
packagesVal, ok := v["packages"]
if !ok {
return fmt.Errorf("npm pack: unsupported workspaces object; missing \"packages\" field")
}
packagesSlice := toStringSlice(packagesVal)
if slices.Contains(packagesSlice, workspacePath) {
if containsString(packagesSlice, workspacePath) {
if runtime.GOOS == "windows" {
return afero.WriteFile(fsys, packageJSONName, data, 0o666)
}
@@ -280,7 +278,7 @@ func ensureWorkspaceRef(fsys afero.Fs, workspacePath string) error {
}
// Append the new workspace path to the packages slice.
newPkgs := make([]any, 0, len(packagesSlice)+1)
newPkgs := make([]interface{}, 0, len(packagesSlice)+1)
for _, s := range packagesSlice {
newPkgs = append(newPkgs, s)
}
@@ -350,7 +348,7 @@ func ensureWorkspaceRef(fsys afero.Fs, workspacePath string) error {
}
func detectIndent(data []byte) string {
for line := range bytes.SplitSeq(data, []byte("\n")) {
for _, line := range bytes.Split(data, []byte("\n")) {
trimmed := bytes.TrimLeft(line, " \t")
if len(trimmed) < len(line) && len(trimmed) > 0 && trimmed[0] == '"' {
return string(line[:len(line)-len(trimmed)])
@@ -386,6 +384,15 @@ func toStringSlice(v any) []string {
return nil
}
func containsString(ss []string, s string) bool {
for _, v := range ss {
if v == s {
return true
}
}
return false
}
// resolveProjectWorkspaces resolves workspace patterns from the project's
// package source, skipping the hugoautogen workspace.
func resolveProjectWorkspaces(sourceFs afero.Fs, workspacesSource map[string]any, skipPath string) []string {
@@ -21,7 +21,6 @@ import (
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/modules"
"github.com/gohugoio/hugo/modules/npm"
"github.com/spf13/afero"
)
func getPackageBuilderTestFiles() string {
@@ -50,7 +49,7 @@ PACKAGE_CONTENT
"version": "0.1.1",
"dependencies": {
"react-dom": "1.1.1",
"tailwindcss": "1.2.0",
"tailwindcss": "1.2.0",
"@babel/cli": "7.8.4",
"@babel/core": "7.9.0",
"@babel/preset-env": "7.9.5"
@@ -71,25 +70,9 @@ func TestPackageBuilder(t *testing.T) {
files := getPackageBuilderTestFiles()
b := hugolib.Test(t, files)
fs := b.H.Fs.WorkingDirReadOnly
sourceFs := b.H.BaseFs.ProjectSourceFs
assetsFs := b.H.BaseFs.AssetsWithDuplicatesPreserved.Fs
mods := b.H.Configs.Modules
sum := npm.PackageFilesSum(fs, b.H.AllModules())
b.Assert(sum, qt.Equals, "528bb6507993888c")
b.Assert(npm.Pack(sourceFs, assetsFs, mods), qt.IsNil)
b.Assert(npm.NpmPackNeedsUpdate(sourceFs, mods), qt.IsFalse)
meta1, err := afero.ReadFile(sourceFs, "packages/hugoautogen/hugo_packagemeta.json")
b.Assert(err, qt.IsNil)
b.Assert(npm.Pack(sourceFs, assetsFs, mods), qt.IsNil)
b.Assert(npm.NpmPackNeedsUpdate(sourceFs, mods), qt.IsFalse)
meta2, err := afero.ReadFile(sourceFs, "packages/hugoautogen/hugo_packagemeta.json")
b.Assert(err, qt.IsNil)
b.Assert(string(meta2), qt.Equals, string(meta1))
b.Assert(sum, qt.Equals, "ce880d142ad9a16a")
}
func BenchmarkPackageFilesSum(b *testing.B) {
@@ -100,6 +83,6 @@ func BenchmarkPackageFilesSum(b *testing.B) {
for b.Loop() {
sum := npm.PackageFilesSum(fs, modules.Modules{})
bb.Assert(sum, qt.Equals, "528bb6507993888c")
bb.Assert(sum, qt.Equals, "ce880d142ad9a16a")
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"github.com/gohugoio/hugo/docshelper"
)
// This is just a helper used to create some JSON used in the Hugo docs.
// This is is just some helpers used to create some JSON used in the Hugo docs.
func init() {
docsProvider := func() docshelper.DocProvider {
return docshelper.DocProvider{
+1 -1
View File
@@ -33,7 +33,7 @@ var (
rightDelimScNoMarkup = []byte(">}}")
leftDelimScWithMarkup = []byte("{{%")
rightDelimScWithMarkup = []byte("%}}")
leftComment = []byte("/*") // comments in this context are used to mark shortcodes as "not really a shortcode"
leftComment = []byte("/*") // comments in this context us used to to mark shortcodes as "not really a shortcode"
rightComment = []byte("*/")
)
+1 -1
View File
@@ -331,7 +331,7 @@ type SearchOpts struct {
// The indices to search in.
Indices []string
// Fragments holds a list of special keywords that is used
// Fragments holds a a list of special keywords that is used
// for indices configured as type "fragments".
// This will match the fragment identifiers of the documents.
Fragments []string
+4 -5
View File
@@ -156,18 +156,17 @@ Len related: {{ site.RegularPages.Related . | len }}
`)
createContent := func(n int) string {
var base strings.Builder
base.WriteString(`---
base := `---
title: "Page %d"
keywords: ['k%d']
---
`)
`
for range 32 {
base.WriteString(fmt.Sprintf("\n## Title %d", rand.Intn(100)))
base += fmt.Sprintf("\n## Title %d", rand.Intn(100))
}
return fmt.Sprintf(base.String(), n, rand.Intn(32))
return fmt.Sprintf(base, n, rand.Intn(32))
}
for i := 1; i < 100; i++ {
-1
View File
@@ -144,7 +144,6 @@ func (d *Codec) EncodeTo(conf ImageConfig, w io.Writer, img image.Image) error {
"compression": conf.Compression,
"quality": conf.Quality,
"encoderSpeed": conf.EncoderSpeed,
"hint": conf.Hint,
}
return d.avif.Encode(w, img, opts)
case WEBP:
+120 -253
View File
@@ -84,13 +84,6 @@ var (
// Increment to mark all processed images as stale. Only use when absolutely needed.
// See the finer grained smartCropVersionNumber.
mainImageVersionNumber = 1
// Increment a format's version number to mark all processed images targeting
// that format as stale, e.g. after a change to its encoder. This is finer
// grained than mainImageVersionNumber, which invalidates every format.
formatVersionNumbers = map[Format]int{
AVIF: 1,
}
)
var anchorPositions = map[string]gift.Anchor{
@@ -111,7 +104,7 @@ var compressionMethods = map[string]bool{
"lossless": true,
}
// These encoding hints are used by Webp (preset) and Avif (chroma subsampling).
// These encoding hints are currently only relevant for Webp.
var hints = map[string]bool{
"picture": true,
"photo": true,
@@ -169,6 +162,7 @@ func ImageFormatFromMediaSubType(sub string) (Format, ImageResourceType) {
}
const (
defaultJPEGQuality = 75
defaultResampleFilter = "box"
defaultBgColor = "#ffffff"
defaultHint = "photo"
@@ -178,6 +172,33 @@ const (
defaultAvifEncoderSpeed = 10
)
var (
defaultImaging = map[string]any{
"resampleFilter": defaultResampleFilter,
"bgColor": defaultBgColor,
"hint": defaultHint,
"quality": defaultJPEGQuality,
"compression": defaultCompression,
"webp": map[string]any{
"useSharpYuv": defaultWebpUseSharpYuv,
"method": defaultWebpMethod,
},
"avif": map[string]any{
"encoderSpeed": defaultAvifEncoderSpeed,
},
}
defaultImageConfig *config.ConfigNamespace[ImagingConfig, ImagingConfigInternal]
)
func init() {
var err error
defaultImageConfig, err = DecodeConfig(defaultImaging)
if err != nil {
panic(err)
}
}
func DecodeConfig(in map[string]any) (*config.ConfigNamespace[ImagingConfig, ImagingConfigInternal], error) {
if in == nil {
in = make(map[string]any)
@@ -188,21 +209,20 @@ func DecodeConfig(in map[string]any) (*config.ConfigNamespace[ImagingConfig, Ima
if err != nil {
return ImagingConfigInternal{}, nil, err
}
// Merge in the defaults.
hmaps.MergeShallow(m, defaultImaging)
i := ImagingConfigInternal{
Imaging: ImagingConfig{
BgColor: defaultBgColor,
ResampleFilter: defaultResampleFilter,
Webp: WebpConfig{
UseSharpYuv: defaultWebpUseSharpYuv,
Method: defaultWebpMethod,
},
Avif: AvifConfig{
EncoderSpeed: defaultAvifEncoderSpeed,
},
},
// Deep merge webp defaults.
if webp, ok := m["webp"].(map[string]any); ok {
hmaps.MergeShallow(webp, defaultImaging["webp"].(map[string]any))
}
// Deep merge avif defaults.
if avif, ok := m["avif"].(map[string]any); ok {
hmaps.MergeShallow(avif, defaultImaging["avif"].(map[string]any))
}
var i ImagingConfigInternal
if err := mapstructure.Decode(m, &i.Imaging); err != nil {
return i, nil, err
}
@@ -219,7 +239,7 @@ func DecodeConfig(in map[string]any) (*config.ConfigNamespace[ImagingConfig, Ima
if i.Imaging.Anchor != "" {
anchor, found := anchorPositions[i.Imaging.Anchor]
if !found {
return i, nil, fmt.Errorf("invalid anchor value %q in imaging config", i.Imaging.Anchor)
return i, nil, fmt.Errorf("invalid anchor value %q in imaging config", i.Anchor)
}
i.Anchor = anchor
}
@@ -243,7 +263,7 @@ func DecodeConfig(in map[string]any) (*config.ConfigNamespace[ImagingConfig, Ima
func DecodeImageConfig(options []string, defaults *config.ConfigNamespace[ImagingConfig, ImagingConfigInternal], sourceFormat Format) (ImageConfig, error) {
var (
c ImageConfig = newImageConfig()
c ImageConfig = GetDefaultImageConfig(defaults)
err error
)
@@ -330,18 +350,43 @@ func DecodeImageConfig(options []string, defaults *config.ConfigNamespace[Imagin
}
}
if err := c.init(defaults.Config, sourceFormat); err != nil {
return c, err
if c.Action != "" && c.Filter == nil {
c.Filter = defaults.Config.ResampleFilter
}
if c.Hint == "" {
c.Hint = defaults.Config.Imaging.Webp.Hint
}
if c.Action != "" && c.Anchor == -1 {
c.Anchor = defaults.Config.Anchor
}
// default to the source format
if c.TargetFormat == 0 {
c.TargetFormat = sourceFormat
}
if c.Quality <= 0 && c.TargetFormat.RequiresDefaultQuality() {
// We need a quality setting for all JPEGs and WEBPs,
// unless the user explicitly set quality.
c.Quality = defaults.Config.Imaging.Quality
}
if c.Compression == "" {
c.Compression = defaults.Config.Imaging.Compression
}
if c.BgColor == nil && c.TargetFormat != sourceFormat {
if sourceFormat.SupportsTransparency() && !c.TargetFormat.SupportsTransparency() {
c.BgColor = defaults.Config.BgColor
}
}
if mainImageVersionNumber > 0 {
options = append(options, strconv.Itoa(mainImageVersionNumber))
}
if v := formatVersionNumbers[c.TargetFormat]; v > 0 {
options = append(options, "tfv"+strconv.Itoa(v))
}
usesSmartCrop := c.Anchor == SmartCropAnchor && (c.Action == ActionCrop || c.Action == ActionFill)
if smartCropVersionNumber > 0 && usesSmartCrop {
options = append(options, strconv.Itoa(smartCropVersionNumber))
@@ -357,7 +402,6 @@ type ImageConfig struct {
// This defines the output format of the output image. It defaults to the source format.
TargetFormat Format
// Optional image processing action to perform ("resize", "crop", "fit", "fill"). When empty, the operation may still perform e.g. format conversion.
Action string
// If set, this will be used as the key in filenames etc.
@@ -380,11 +424,8 @@ type ImageConfig struct {
// transparency.
BgColor color.Color
Filter gift.Resampling
Anchor gift.Anchor
// Hint about what type of picture this is. Used to optimize encoding
// when target is webp (preset) or avif (chroma subsampling).
// when target is set to webp.
Hint string
Compression string
@@ -398,54 +439,16 @@ type ImageConfig struct {
Width int
Height int
Filter gift.Resampling
Anchor gift.Anchor
}
func (c *ImageConfig) init(defaults ImagingConfigInternal, sourceFormat Format) error {
if c.Action != "" && c.Anchor == -1 {
c.Anchor = defaults.Anchor
}
if c.TargetFormat == 0 {
c.TargetFormat = sourceFormat
}
if c.Filter == nil {
c.Filter = defaults.ResampleFilter
}
if c.Anchor == -1 {
c.Anchor = defaults.Anchor
}
if c.Hint == "" {
c.Hint = defaults.Imaging.hintFor(c.TargetFormat)
}
if c.Quality == 0 {
c.Quality = defaults.Imaging.qualityFor(c.TargetFormat)
}
if c.Compression == "" {
c.Compression = defaults.Imaging.compressionFor(c.TargetFormat)
}
if c.TargetFormat == AVIF {
c.EncoderSpeed = defaults.Imaging.Avif.EncoderSpeed
}
if c.TargetFormat == WEBP {
c.Method = defaults.Imaging.Webp.Method
c.UseSharpYuv = defaults.Imaging.Webp.UseSharpYuv
}
if c.BgColor == nil && c.TargetFormat != sourceFormat {
if sourceFormat.SupportsTransparency() && !c.TargetFormat.SupportsTransparency() {
c.BgColor = defaults.BgColor
}
}
return nil
}
func (c ImageConfig) Reanchor(a gift.Anchor) ImageConfig {
c.Anchor = a
c.Key = hashing.HashStringHex(c.Key, "reanchor", a)
return c
func (cfg ImageConfig) Reanchor(a gift.Anchor) ImageConfig {
cfg.Anchor = a
cfg.Key = hashing.HashStringHex(cfg.Key, "reanchor", a)
return cfg
}
type ImagingConfigInternal struct {
@@ -466,14 +469,14 @@ func (i *ImagingConfigInternal) Compile(externalCfg *ImagingConfig) error {
if externalCfg.Anchor != "" {
anchor, found := anchorPositions[externalCfg.Anchor]
if !found {
return fmt.Errorf("invalid anchor value %q in imaging config", externalCfg.Anchor)
return fmt.Errorf("invalid anchor value %q in imaging config", i.Anchor)
}
i.Anchor = anchor
}
filter, found := imageFilters[externalCfg.ResampleFilter]
if !found {
return fmt.Errorf("%q is not a valid resample filter", externalCfg.ResampleFilter)
return fmt.Errorf("%q is not a valid resample filter", filter)
}
i.ResampleFilter = filter
@@ -483,24 +486,19 @@ func (i *ImagingConfigInternal) Compile(externalCfg *ImagingConfig) error {
// ImagingConfig contains default image processing configuration. This will be fetched
// from site (or language) config.
type ImagingConfig struct {
// Default image quality setting (1-100). Used as the fallback for JPEG,
// WebP and AVIF when no per-format quality is set. When left unset, JPEG
// and WebP default to 75 and AVIF to 60 (its scale differs perceptually).
// Deprecated in v0.163.0: set the quality per format instead, see
// imaging.jpeg.quality, imaging.webp.quality and imaging.avif.quality.
Quality int `json:"-"`
// Default image quality setting (1-100). Only used for JPEG and WebP images.
Quality int
// Compression method to use.
// One of "lossy" or "lossless".
// Note that lossless is currently only supported for WebP and AVIF.
// Deprecated in v0.163.0: set the compression method per format instead, see imaging.webp.compression and imaging.avif.compression.
Compression string `json:"-"`
Compression string
// Resample filter to use in resize operations.
ResampleFilter string
// Hint about what type of image this is.
// Used when encoding to Webp (preset) and Avif (chroma subsampling).
// Currently only used when encoding to Webp.
// Default is "photo".
// Valid values are "picture", "photo", "drawing", "icon", or "text".
// Moved to WebpConfig in v0.155.0, but kept here for backwards compatibility.
@@ -514,44 +512,10 @@ type ImagingConfig struct {
Exif ExifConfig
Meta MetaConfig
Jpeg JpegConfig
Webp WebpConfig
Avif AvifConfig
}
func (cfg *ImagingConfig) qualityFor(f Format) int {
switch f {
case JPEG:
return cfg.Jpeg.Quality
case WEBP:
return cfg.Webp.Quality
case AVIF:
return cfg.Avif.Quality
}
return 0
}
func (cfg *ImagingConfig) compressionFor(f Format) string {
switch f {
case WEBP:
return cfg.Webp.Compression
case AVIF:
return cfg.Avif.Compression
}
return ""
}
// hintFor returns the configured hint for the given target format.
func (cfg *ImagingConfig) hintFor(f Format) string {
switch f {
case WEBP:
return cfg.Webp.Hint
case AVIF:
return cfg.Avif.Hint
}
return ""
}
var validMetaSources = map[string]bool{
"exif": true,
"iptc": true,
@@ -559,23 +523,15 @@ var validMetaSources = map[string]bool{
}
func (cfg *ImagingConfig) init() error {
if cfg.Quality < 1 || cfg.Quality > 100 {
return errors.New("image quality must be a number between 1 and 100")
}
cfg.BgColor = strings.ToLower(strings.TrimPrefix(cfg.BgColor, "#"))
cfg.Anchor = strings.ToLower(cfg.Anchor)
cfg.ResampleFilter = strings.ToLower(cfg.ResampleFilter)
cfg.Hint = strings.ToLower(cfg.Hint)
cfg.Compression = strings.ToLower(cfg.Compression)
if err := cfg.Jpeg.init(cfg); err != nil {
return fmt.Errorf("invalid jpeg config: %w", err)
}
if err := cfg.Webp.init(cfg); err != nil {
return fmt.Errorf("invalid webp config: %w", err)
}
if err := cfg.Avif.init(cfg); err != nil {
return fmt.Errorf("invalid avif config: %w", err)
}
if cfg.Quality < 0 || cfg.Quality > 100 {
return fmt.Errorf("imaging.quality must be between 1 and 100 inclusive, got %d", cfg.Quality)
}
if cfg.Anchor == "" {
cfg.Anchor = smartCropIdentifier
@@ -609,6 +565,33 @@ func (cfg *ImagingConfig) init() error {
}
}
// WebP config with backwards compatibility for root-level Hint.
cfg.Webp.Hint = strings.ToLower(cfg.Webp.Hint)
if cfg.Webp.Hint == "" {
// Fall back to root-level hint for backwards compatibility.
if cfg.Hint != "" {
cfg.Webp.Hint = cfg.Hint
} else {
cfg.Webp.Hint = defaultHint
}
}
if cfg.Webp.Hint != "" && !hints[cfg.Webp.Hint] {
return fmt.Errorf("invalid webp hint %q; must be one of picture, photo, drawing, icon, or text", cfg.Webp.Hint)
}
if cfg.Webp.Method == 0 {
cfg.Webp.Method = defaultWebpMethod
}
if cfg.Webp.Method < 0 || cfg.Webp.Method > 6 {
return fmt.Errorf("webp method must be between 0 and 6, got %d", cfg.Webp.Method)
}
if cfg.Avif.EncoderSpeed == 0 {
cfg.Avif.EncoderSpeed = defaultAvifEncoderSpeed
}
if cfg.Avif.EncoderSpeed < 1 || cfg.Avif.EncoderSpeed > 10 {
return fmt.Errorf("avif encoderSpeed must be between 1 and 10, got %d", cfg.Avif.EncoderSpeed)
}
return nil
}
@@ -648,39 +631,8 @@ type MetaConfig struct {
Sources []string
}
// JpegConfig holds JPEG-specific encoding configuration.
type JpegConfig struct {
// Quality setting (1-100). Falls back to the global imaging.quality if unset.
Quality int
}
func (c *JpegConfig) init(ic *ImagingConfig) error {
if c.Quality == 0 {
c.Quality = ic.Quality
}
if c.Quality == 0 {
c.Quality = 75
}
if c.Quality < 1 || c.Quality > 100 {
return fmt.Errorf("imaging.jpeg.quality must be between 1 and 100 inclusive, got %d", c.Quality)
}
return nil
}
// AvifConfig holds AVIF-specific encoding configuration.
type AvifConfig struct {
// Quality setting (1-100). Falls back to the global imaging.quality if unset.
Quality int
// Compression method to use.
// One of "lossy" or "lossless".
Compression string
// Hint about what type of image this is. Used for chroma subsampling.
// Valid values are "picture", "photo", "drawing", "icon", or "text".
// Default is "photo".
Hint string
// Encoder quality/speed trade-off, 1 (slowest, best quality / smallest
// files) to 10 (fastest). Default is 10 — fast enough for incremental
// builds with quality indistinguishable from slower settings at typical
@@ -688,59 +640,12 @@ type AvifConfig struct {
// build time.
// We recommend sticking with the default of 10 unless you have a specific reason to change it,
// and to stay above 5 to avoid very long build times and timeouts.
// 0 is treated as unset and falls back to the default.
EncoderSpeed int
}
func (c *AvifConfig) init(ic *ImagingConfig) error {
if c.Hint == "" {
c.Hint = ic.Hint
}
if c.Compression == "" {
c.Compression = ic.Compression
}
if c.Quality == 0 {
c.Quality = ic.Quality
}
if c.Hint == "" {
c.Hint = defaultHint
}
if c.Compression == "" {
c.Compression = defaultCompression
}
if c.Quality == 0 {
c.Quality = 60
}
c.Hint = strings.ToLower(c.Hint)
c.Compression = strings.ToLower(c.Compression)
if c.Hint != "" && !hints[c.Hint] {
return fmt.Errorf("imaging.avif.hint must be one of picture, photo, drawing, icon, or text, got %q", c.Hint)
}
if c.EncoderSpeed < 1 || c.EncoderSpeed > 10 {
return fmt.Errorf("imaging.avif.encoderSpeed must be between 1 and 10, got %d", c.EncoderSpeed)
}
if c.Compression != "" && !compressionMethods[c.Compression] {
return fmt.Errorf("imaging.avif.compression must be one of lossy or lossless, got %q", c.Compression)
}
if c.Quality < 1 || c.Quality > 100 {
return fmt.Errorf("imaging.avif.quality must be between 1 and 100 inclusive, got %d", c.Quality)
}
return nil
}
// WebpConfig holds WebP-specific encoding configuration.
type WebpConfig struct {
// Quality setting (1-100). Falls back to the global imaging.quality if unset.
// Only relevant for lossy encoding.
Quality int
// Compression method to use.
// One of "lossy" or "lossless".
Compression string
// Hint about what type of image this is.
// Valid values are "picture", "photo", "drawing", "icon", or "text".
// Default is "photo".
@@ -754,41 +659,3 @@ type WebpConfig struct {
// Default is 2.
Method int
}
func (c *WebpConfig) init(ic *ImagingConfig) error {
if c.Hint == "" {
c.Hint = ic.Hint
}
if c.Compression == "" {
c.Compression = ic.Compression
}
if c.Quality == 0 {
c.Quality = ic.Quality
}
if c.Hint == "" {
c.Hint = defaultHint
}
if c.Compression == "" {
c.Compression = defaultCompression
}
if c.Quality == 0 {
c.Quality = 75
}
c.Hint = strings.ToLower(c.Hint)
c.Compression = strings.ToLower(c.Compression)
if c.Hint != "" && !hints[c.Hint] {
return fmt.Errorf("imaging.webp.hint must be one of picture, photo, drawing, icon, or text, got %q", c.Hint)
}
if c.Method < 0 || c.Method > 6 {
return fmt.Errorf("imaging.webp.method must be between 0 and 6, got %d", c.Method)
}
if c.Compression != "" && !compressionMethods[c.Compression] {
return fmt.Errorf("imaging.webp.compression must be one of lossy or lossless, got %q", c.Compression)
}
if c.Quality < 1 || c.Quality > 100 {
return fmt.Errorf("imaging.webp.quality must be between 1 and 100 inclusive, got %d", c.Quality)
}
return nil
}
+23 -55
View File
@@ -95,6 +95,13 @@ func TestDecodeConfig(t *testing.T) {
})
c.Assert(err, qt.ErrorMatches, ".*encoderSpeed must be between.*")
// AVIF: minimum is 1; 0 is treated as unset and falls back to the default.
imagingConfig, err = DecodeConfig(map[string]any{
"avif": map[string]any{"encoderSpeed": 0},
})
c.Assert(err, qt.IsNil)
c.Assert(imagingConfig.Config.Imaging.Avif.EncoderSpeed, qt.Equals, defaultAvifEncoderSpeed)
imagingConfig, err = DecodeConfig(map[string]any{
"avif": map[string]any{"encoderSpeed": 1},
})
@@ -102,58 +109,21 @@ func TestDecodeConfig(t *testing.T) {
c.Assert(imagingConfig.Config.Imaging.Avif.EncoderSpeed, qt.Equals, 1)
}
func TestImageConfigPerFormat(t *testing.T) {
c := qt.New(t)
cfg, err := DecodeConfig(map[string]any{
"quality": 80,
"compression": "lossy",
"hint": "text",
"jpeg": map[string]any{"quality": 80},
"webp": map[string]any{"quality": 70, "hint": "picture", "compression": "lossless"},
"avif": map[string]any{"quality": 55},
})
c.Assert(err, qt.IsNil)
conf := func(opts ...string) ImageConfig {
conf, err := DecodeImageConfig(append([]string{"resize", "100x"}, opts...), cfg, JPEG)
c.Assert(err, qt.IsNil)
return conf
}
c.Assert(conf("jpg").Quality, qt.Equals, 80)
c.Assert(conf("avif").Quality, qt.Equals, 55)
c.Assert(conf("webp").Quality, qt.Equals, 70)
c.Assert(conf("webp", "q33").Quality, qt.Equals, 33)
c.Assert(conf("avif", "q33").Quality, qt.Equals, 33)
c.Assert(conf("webp").Hint, qt.Equals, "picture")
c.Assert(conf("avif").Hint, qt.Equals, "text")
c.Assert(conf("jpeg").Hint, qt.Equals, "")
c.Assert(conf("webp").Compression, qt.Equals, "lossless")
c.Assert(conf("avif").Compression, qt.Equals, "lossy")
c.Assert(conf("jpeg").Compression, qt.Equals, "")
}
func TestDecodeImageConfig(t *testing.T) {
c := qt.New(t)
for i, this := range []struct {
action string
in string
expect any
}{
{"resize", "300x400", newTestImageConfig("resize", 300, 400, 75, 0, "box", "smart", "")},
{"resize", "300x400 #fff", newTestImageConfig("resize", 300, 400, 75, 0, "box", "smart", "fff")},
{"resize", "100x200 bottomRight", newTestImageConfig("resize", 100, 200, 75, 0, "box", "BottomRight", "")},
{"resize", "10x20 topleft Lanczos", newTestImageConfig("resize", 10, 20, 75, 0, "Lanczos", "topleft", "")},
{"resize", "linear left 10x r180", newTestImageConfig("resize", 10, 0, 75, 180, "linear", "left", "")},
{"resize", "x20 riGht Cosine q95", newTestImageConfig("resize", 0, 20, 95, 0, "cosine", "right", "")},
{"crop", "300x400", newTestImageConfig("crop", 300, 400, 75, 0, "box", "smart", "")},
{"fill", "300x400", newTestImageConfig("fill", 300, 400, 75, 0, "box", "smart", "")},
{"fit", "300x400", newTestImageConfig("fit", 300, 400, 75, 0, "box", "smart", "")},
{"resize", "300x400", newImageConfig("resize", 300, 400, 75, 0, "box", "smart", "")},
{"resize", "300x400 #fff", newImageConfig("resize", 300, 400, 75, 0, "box", "smart", "fff")},
{"resize", "100x200 bottomRight", newImageConfig("resize", 100, 200, 75, 0, "box", "BottomRight", "")},
{"resize", "10x20 topleft Lanczos", newImageConfig("resize", 10, 20, 75, 0, "Lanczos", "topleft", "")},
{"resize", "linear left 10x r180", newImageConfig("resize", 10, 0, 75, 180, "linear", "left", "")},
{"resize", "x20 riGht Cosine q95", newImageConfig("resize", 0, 20, 95, 0, "cosine", "right", "")},
{"crop", "300x400", newImageConfig("crop", 300, 400, 75, 0, "box", "smart", "")},
{"fill", "300x400", newImageConfig("fill", 300, 400, 75, 0, "box", "smart", "")},
{"fit", "300x400", newImageConfig("fit", 300, 400, 75, 0, "box", "smart", "")},
{"resize", "", false},
{"resize", "foo", false},
@@ -168,7 +138,7 @@ func TestDecodeImageConfig(t *testing.T) {
t.Fatal(err)
}
options := append([]string{this.action}, strings.Fields(this.in)...)
result, err := DecodeImageConfig(options, cfg, WEBP)
result, err := DecodeImageConfig(options, cfg, PNG)
if b, ok := this.expect.(bool); ok && !b {
if err == nil {
t.Errorf("[%d] parseImageConfig didn't return an expected error", i)
@@ -180,25 +150,23 @@ func TestDecodeImageConfig(t *testing.T) {
expect := this.expect.(ImageConfig)
result.Key = ""
c.Assert(fmt.Sprint(result), qt.Equals, fmt.Sprint(expect))
if fmt.Sprint(result) != fmt.Sprint(expect) {
t.Fatalf("[%d] got\n%v\n but expected\n%v", i, result, expect)
}
}
}
}
func newTestImageConfig(action string, width, height, quality, rotate int, filter, anchor, bgColor string) ImageConfig {
var c ImageConfig = newImageConfig()
func newImageConfig(action string, width, height, quality, rotate int, filter, anchor, bgColor string) ImageConfig {
var c ImageConfig = GetDefaultImageConfig(nil)
c.Action = action
c.TargetFormat = WEBP
c.Hint = defaultHint
c.Compression = defaultCompression
c.TargetFormat = PNG
c.Width = width
c.Height = height
c.Quality = quality
c.Rotate = rotate
c.BgColor, _ = hexStringToColorGo(bgColor)
c.Anchor = SmartCropAnchor
c.Method = defaultWebpMethod
if filter != "" {
filter = strings.ToLower(filter)
+17 -2
View File
@@ -342,9 +342,18 @@ func (p *ImageProcessor) doFilter(src image.Image, targetFormat Format, filters
return dst, nil
}
func newImageConfig() ImageConfig {
func GetDefaultImageConfig(defaults *config.ConfigNamespace[ImagingConfig, ImagingConfigInternal]) ImageConfig {
if defaults == nil {
defaults = defaultImageConfig
}
return ImageConfig{
Anchor: -1, // The real values start at 0.
Anchor: -1, // The real values start at 0.
Hint: defaults.Config.Imaging.Webp.Hint,
Quality: defaults.Config.Imaging.Quality,
Compression: defaults.Config.Imaging.Compression,
UseSharpYuv: defaults.Config.Imaging.Webp.UseSharpYuv,
Method: defaults.Config.Imaging.Webp.Method,
EncoderSpeed: defaults.Config.Imaging.Avif.EncoderSpeed,
}
}
@@ -396,6 +405,12 @@ func (f Format) ToImageMetaImageFormatFormat() imagemeta.ImageFormat {
}
}
// RequiresDefaultQuality returns if the default quality needs to be applied to
// images of this format.
func (f Format) RequiresDefaultQuality() bool {
return f == JPEG || f == WEBP
}
// SupportsTransparency reports whether it supports transparency in any form.
func (f Format) SupportsTransparency() bool {
return f != JPEG
@@ -370,10 +370,6 @@ sourcefilename: ../testdata/sunset.jpg
sourcefilename: ../testdata/giphy.avif
-- assets/fuzzycircle.webp --
sourcefilename: ../testdata/webp/fuzzy-cirlcle-transparent-32.webp
-- assets/gohugoio8.png --
sourcefilename: ../testdata/gohugoio8.png
-- assets/gohugoio24.png --
sourcefilename: ../testdata/gohugoio24.png
-- layouts/home.html --
Home.
{{ $sunset := resources.Get "sunset.jpg" }}
@@ -382,8 +378,6 @@ Home.
{{ $webpAnim := resources.Get "anim.webp" }}
{{ $giphy := resources.Get "giphy.avif" }}
{{ $fuzzyCircle := resources.Get "fuzzycircle.webp" }}
{{ $gohugoio8 := resources.Get "gohugoio8.png" }}
{{ $gohugoio24 := resources.Get "gohugoio24.png" }}
{{ template "process" (dict "spec" "r1" "img" $dock) }}
{{ template "process" (dict "spec" "q50" "img" $dock) }}
@@ -392,8 +386,8 @@ Home.
{{ template "process" (dict "spec" "gif" "img" $giphy) }}
{{ template "process" (dict "spec" "crop 300x300 smart avif" "img" $fuzzyCircle) }}
{{ template "process" (dict "spec" "crop 300x300 smart #ff9999 avif" "img" $fuzzyCircle) }}
{{ template "process" (dict "spec" "avif q79" "img" $gohugoio8) }}
{{ template "process" (dict "spec" "avif q80" "img" $gohugoio24) }}
@@ -16,7 +16,6 @@ package images_test
import (
"testing"
"github.com/bep/logg"
"github.com/gohugoio/hugo/htesting"
"github.com/gohugoio/hugo/hugolib"
)
@@ -105,30 +104,6 @@ CropSmart: {{ .Width }}x{{ .Height }}|
b.AssertFileContent("public/index.html", "Original: 900x562|CropTopLeft: 900x561|CropSmart: 900x561|")
}
func TestImagingGlobalsDeprecated(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
[imaging]
quality = 70
hint = "picture"
compression = "lossless"
-- layouts/home.html --
Home.
`
b := hugolib.Test(t, files, hugolib.TestOptWithConfig(func(cfg *hugolib.IntegrationTestConfig) {
cfg.LogLevel = logg.LevelInfo
}))
b.AssertLogContains(
"project config key imaging.quality was deprecated in Hugo v0.163.0",
"project config key imaging.hint was deprecated in Hugo v0.163.0",
"project config key imaging.compression was deprecated in Hugo v0.163.0",
)
}
func BenchmarkImageResize(b *testing.B) {
files := `
-- content/p1/sunrise.jpg --
Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 62 KiB

+4 -2
View File
@@ -61,7 +61,8 @@ func (b *Builder) AddSourceRoot(root string) {
// CompilerOptions holds compilerOptions for jsonconfig.json.
type CompilerOptions struct {
Paths map[string][]string `json:"paths"`
BaseURL string `json:"baseUrl"`
Paths map[string][]string `json:"paths"`
}
// Config holds the data for jsconfig.json.
@@ -72,7 +73,8 @@ type Config struct {
func newJSConfig() *Config {
return &Config{
CompilerOptions: CompilerOptions{
Paths: make(map[string][]string),
BaseURL: ".",
Paths: make(map[string][]string),
},
}
}
+1 -6
View File
@@ -14,7 +14,6 @@
package jsconfig
import (
"encoding/json"
"path/filepath"
"testing"
@@ -29,12 +28,8 @@ func TestJsConfigBuilder(t *testing.T) {
b.AddSourceRoot("/d/assets")
conf := b.Build("/a/b")
c.Assert(conf.CompilerOptions.BaseURL, qt.Equals, ".")
c.Assert(conf.CompilerOptions.Paths["*"], qt.DeepEquals, []string{filepath.FromSlash("../../c/assets/*"), filepath.FromSlash("../../d/assets/*")})
// baseUrl is deprecated in TypeScript and not needed when paths is set; see issue 14991.
data, err := json.Marshal(conf)
c.Assert(err, qt.IsNil)
c.Assert(string(data), qt.Not(qt.Contains), "baseUrl")
c.Assert(NewBuilder().Build("/a/b"), qt.IsNil)
}
+2 -7
View File
@@ -221,15 +221,10 @@ type PageMetaProvider interface {
// The title used for links.
LinkTitle() string
// IsNode returns whether this is a branch node (e.g. a section).
//
// Deprecated: Use IsBranch or "not IsPage" instead.
// IsNode returns whether this is an item of one of the list types in Hugo,
// i.e. not a regular content
IsNode() bool
// IsBranch returns whether this is a branch node, i.e. a node that
// can have descendants (home, section, taxonomy or term).
IsBranch() bool
// IsPage returns whether this is a regular content
IsPage() bool
@@ -80,7 +80,6 @@ func generateMarshalJSON(c *codegen.Inspector) error {
"github.com/gohugoio/hugo/resources/page",
// Exclusion regexps. Matches method names.
`\bPage\b`,
`\bIsNode\b`,
)
fmt.Fprintf(f, `%s
+6 -3
View File
@@ -26,6 +26,7 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
lastmod := p.Lastmod()
publishDate := p.PublishDate()
expiryDate := p.ExpiryDate()
aliases := p.Aliases()
bundleType := p.BundleType()
description := p.Description()
draft := p.Draft()
@@ -35,10 +36,10 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
layout := p.Layout()
linkTitle := p.LinkTitle()
isNode := p.IsNode()
isBranch := p.IsBranch()
isPage := p.IsPage()
path := p.Path()
slug := p.Slug()
lang := p.Lang()
isSection := p.IsSection()
section := p.Section()
sitemap := p.Sitemap()
@@ -50,6 +51,7 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
Lastmod time.Time
PublishDate time.Time
ExpiryDate time.Time
Aliases []string
BundleType string
Description string
Draft bool
@@ -59,10 +61,10 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
Layout string
LinkTitle string
IsNode bool
IsBranch bool
IsPage bool
Path string
Slug string
Lang string
IsSection bool
Section string
Sitemap config.SitemapConfig
@@ -73,6 +75,7 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
Lastmod: lastmod,
PublishDate: publishDate,
ExpiryDate: expiryDate,
Aliases: aliases,
BundleType: bundleType,
Description: description,
Draft: draft,
@@ -82,10 +85,10 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
Layout: layout,
LinkTitle: linkTitle,
IsNode: isNode,
IsBranch: isBranch,
IsPage: isPage,
Path: path,
Slug: slug,
Lang: lang,
IsSection: isSection,
Section: section,
Sitemap: sitemap,
-4
View File
@@ -227,10 +227,6 @@ func (p *nopPage) IsNode() bool {
return false
}
func (p *nopPage) IsBranch() bool {
return false
}
func (p *nopPage) IsPage() bool {
return false
}
+2 -2
View File
@@ -395,7 +395,7 @@ func (p *PageConfigEarly) CompileForPagesFromDataPre(basePath string, logger log
// Note that NormalizePathStringBasic will make sure that we don't preserve the unnormalized path.
// We do that when we create pages from the file system; mostly for backward compatibility,
// but also because people tend to use the filename to name their resources (with spaces and all),
// but also because people tend to use use the filename to name their resources (with spaces and all),
// and this isn't relevant when creating resources from an API where it's easy to add textual meta data.
p.Path = paths.NormalizePathStringBasic(p.Path)
@@ -499,7 +499,7 @@ func (rc *ResourceConfig) Compile(basePath string, fim hugofs.FileMetaInfo, conf
// Note that NormalizePathStringBasic will make sure that we don't preserve the unnormalized path.
// We do that when we create resources from the file system; mostly for backward compatibility,
// but also because people tend to use the filename to name their resources (with spaces and all),
// but also because people tend to use use the filename to name their resources (with spaces and all),
// and this isn't relevant when creating resources from an API where it's easy to add textual meta data.
rc.Path = paths.NormalizePathStringBasic(path.Join(basePath, rc.Path))
rc.PathInfo = conf.PathParser().Parse(files.ComponentFolderContent, rc.Path)
-4
View File
@@ -281,10 +281,6 @@ func (p *testPage) IsNode() bool {
panic("testpage: not implemented")
}
func (p *testPage) IsBranch() bool {
panic("testpage: not implemented")
}
func (p *testPage) IsPage() bool {
panic("testpage: not implemented")
}
+4 -3
View File
@@ -31,8 +31,8 @@ func structToMap(s any) map[string]any {
m := make(map[string]any)
t := reflect.TypeOf(s)
for method := range t.Methods() {
for i := range t.NumMethod() {
method := t.Method(i)
if method.PkgPath != "" {
continue
}
@@ -41,7 +41,8 @@ func structToMap(s any) map[string]any {
}
}
for field := range t.Fields() {
for i := range t.NumField() {
field := t.Field(i)
if field.PkgPath != "" {
continue
}
+33 -104
View File
@@ -9,121 +9,50 @@ stdout 'to use TOML for the front matter'
hugo convert toYAML -h
stdout 'to use YAML for the front matter'
cd project
# toJSON
hugo convert toJSON -o output/json
stdout 'processing 6 content files'
! stderr .
grep '^{' output/json/content/json.fr.md
grep '^{' output/json/content/toml.en.md
grep '^{' output/json/content/yaml.md
grep '^{' output/json/content/bundle/index.en.md
grep '^{' output/json/content/bundle/index.fr.md
grep '^{' output/json/internal/internal-mount.md
exists output/json/content/bundle/data.txt
exists output/json/content/bundle/nested/asset.dat
exists output/json/content/_content.gotmpl
! exists output/json/external
# toTOML
hugo convert toTOML -o output/toml
stdout 'processing 6 content files'
! stderr .
grep '^\+\+\+' output/toml/content/json.fr.md
grep '^\+\+\+' output/toml/content/toml.en.md
grep '^\+\+\+' output/toml/content/yaml.md
grep '^\+\+\+' output/toml/content/bundle/index.en.md
grep '^\+\+\+' output/toml/content/bundle/index.fr.md
grep '^\+\+\+' output/toml/internal/internal-mount.md
exists output/toml/content/bundle/data.txt
exists output/toml/content/bundle/nested/asset.dat
exists output/toml/content/_content.gotmpl
! exists output/toml/external
# toYAML
hugo convert toYAML -o output/yaml
stdout 'processing 6 content files'
! stderr .
grep '^---' output/yaml/content/json.fr.md
grep '^---' output/yaml/content/toml.en.md
grep '^---' output/yaml/content/yaml.md
grep '^---' output/yaml/content/bundle/index.en.md
grep '^---' output/yaml/content/bundle/index.fr.md
grep '^---' output/yaml/internal/internal-mount.md
exists output/yaml/content/bundle/data.txt
exists output/yaml/content/bundle/nested/asset.dat
exists output/yaml/content/_content.gotmpl
! exists output/yaml/external
hugo convert toJSON -o myjsoncontent
stdout 'processing 4 content files'
grep '^{' myjsoncontent/content/mytoml.md
grep '^{' myjsoncontent/content/myjson.md
grep '^{' myjsoncontent/content/myyaml.md
grep '^{' myjsoncontent/content/bundle/index.md
exists myjsoncontent/content/bundle/data.txt
exists myjsoncontent/content/bundle/nested/asset.dat
hugo convert toYAML -o myyamlcontent
stdout 'processing 4 content files'
exists myyamlcontent/content/bundle/data.txt
exists myyamlcontent/content/bundle/nested/asset.dat
hugo convert toTOML -o mytomlcontent
stdout 'processing 4 content files'
exists mytomlcontent/content/bundle/data.txt
exists mytomlcontent/content/bundle/nested/asset.dat
-- project/hugo.toml --
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
defaultContentLanguage = 'en'
defaultContentLanguageInSubdir = true
[languages]
[languages.en]
weight = 1
[languages.fr]
weight = 2
[[module.mounts]]
source = 'content'
target = 'content'
[[module.mounts]]
source = 'internal'
target = 'content'
[module.mounts.sites.matrix]
languages = ['en']
[[module.mounts]]
source = '../external'
target = 'content'
[module.mounts.sites.matrix]
languages = ['en']
-- project/layouts/page.html --
{{ .Title }}
-- project/content/_content.gotmpl --
{{ $content := dict "mediaType" "text/markdown" "value" "Content created by content adapter" }}
{{ .AddPage (dict "path" "created-by-content-adapter" "title" "Created by content adapter" "content" $content) }}
{{ .EnableAllDimensions }}
-- project/content/json.fr.md --
-- hugo.toml --
baseURL = "http://example.org/"
-- content/mytoml.md --
+++
title = "TOML"
+++
TOML content
-- content/myjson.md --
{
"title": "JSON"
}
JSON content
-- project/content/toml.en.md --
+++
title = 'TOML'
+++
TOML content
-- project/content/yaml.md --
-- content/myyaml.md --
---
title: YAML
sites:
matrix:
languages: ['**']
---
YAML content
-- project/content/bundle/index.en.md --
-- content/bundle/index.md --
---
title: Bundle EN
title: Bundle
---
Bundle EN content
-- project/content/bundle/index.fr.md --
---
title: Bundle FR
---
Bundle FR content
-- project/content/bundle/data.txt --
Bundle content
-- content/bundle/data.txt --
bundle resource
-- project/content/bundle/nested/asset.dat --
-- content/bundle/nested/asset.dat --
nested resource
-- project/internal/internal-mount.md --
---
title: Internal Mount
---
Internal mount content
-- external/external-mount.md --
---
title: External Mount
---
External mount content
@@ -5,9 +5,6 @@ cmp packages/hugoautogen/package.json golden1/packages/hugoautogen/package.json
cmp package.json golden1/package.json
cmp packages/hugoautogen/hugo_packagemeta.json golden1/packages/hugoautogen/hugo_packagemeta.json
hugo mod graph
! stderr 'WARN npm dependencies are out of sync'
-- hugo.toml --
baseURL = "https://example.org/"
[module]
@@ -47,7 +44,7 @@ go 1.20
}
-- golden1/packages/hugoautogen/hugo_packagemeta.json --
{
"sum": "98594123fa88aedf",
"sum": "dd3590c300b0bebb",
"dependencySources": {
"dependencies": {
"count-days-in-month": "github.com/gohugoio/hugoTestModsNPMNested/a",
+1 -19
View File
@@ -3,7 +3,6 @@
ln ./rootfile.txt ./themes/mytheme/assets/modassetsymlink.txt
ln ./rootfile.txt ./themes/mytheme/static/modstaticsymlink.txt
ln ./README.md ./content/pagesymlink.md
ln ./rootdir ./assets/myassets/symlinkdir
hugo
@@ -28,30 +27,13 @@ Read me.
-- layouts/all.html --
{{ with resources.Get "modassetok.txt"}}OK {{ .Publish }}{{ else }}FAIL{{ end }}
{{ with resources.Get "modassetsymlink.txt"}}FAIL {{ .Publish }}{{ else }}OK{{ end }}
{{ with resources.GetMatch "modassetsymlink.txt"}}FAIL {{ .Publish }}{{ else }}OK{{ end }}
{{ with resources.GetMatch "myassets/symlinkdir/**"}}FAIL {{ .Publish }}{{ else }}OK{{ end }}
Page: {{ .RelPermalink }}|{{ .Content }}|
{{/* os template package. */}}
{{ $symFilePath := "content/pagesymlink.md" }}
{{ with os.ReadDir "assets/myassets/symlinkdir" }}FAIL {{ len . }}{{ else }}OK{{ end }}
{{ with os.Stat $symFilePath }}FAIL{{ else }}OK{{ end }}
{{ with os.ReadFile $symFilePath }}FAIL{{ else }}OK{{ end }}
{{ with os.FileExists $symFilePath }}FAIL{{ else }}OK{{ end }}
-- content/pageok.md --
-- themes/mytheme/assets/modassetok.txt --
Content.
-- themes/mytheme/static/modstatictok.txt --
Content.
-- rootfile.txt --
Root Content.
-- assets/myassets/myfile.txt --
My file.
-- rootdir/rootdirfile1.txt --
Rootdirfile1 content.
-- rootdir/rootdirfile2.txt --
Rootdirfile2 content.
Roo Content.
+10
View File
@@ -157,7 +157,17 @@ func (ns *Namespace) Delimit(ctx context.Context, l, sep any, last ...any) (stri
// Dictionary creates a new map from the given parameters by
// treating values as key-value pairs. The number of values must be even.
// The keys can be string slices, which will create the needed nested structure.
// If no values are provided, nil will be returned.
func (ns *Namespace) Dictionary(values ...any) (map[string]any, error) {
if len(values) == 0 {
// A common construct is to do
// {{ $opts := dict }}
// And then conditionally assign it if some condition is set.
// The only difference between this and an empty map is that this cannot be written to,
// which is not something we do in Hugo (or: If we do, that's a bug).
// This saves us ~48 bytes in memory allocation on 64-bit architectures.
return nil, nil
}
if len(values)%2 != 0 {
return nil, errors.New("invalid dictionary call")
}
@@ -653,3 +653,21 @@ All.
}
})
}
func TestEmptyDictShouldBeNil(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
-- layouts/home.html --
{{ $d := dict }}
{{ printf "dict: %T %t %d" $d (eq $d nil) (len $d) }}
{{ range $d }}FAIL{{ end }}
index: {{ index $d "foo" }}|
{{ $d2 := dict "foo" "bar" }}
{{ $d3 := merge $d $d2 }}
{{ printf "d3: %v" $d3 }}|
`
hugolib.Test(t, files).AssertFileContent("public/index.html", "dict: map[string]interface {} true 0", "! FAIL", "index: |", "d3: map[foo:bar]|")
}
+2 -2
View File
@@ -214,8 +214,8 @@ func (t *TemplateFuncsNamespace) toJSON(ctx context.Context) ([]byte, error) {
return nil, nil
}
ctxType := reflect.TypeOf(tctx)
for method := range ctxType.Methods() {
method := method
for i := range ctxType.NumMethod() {
method := ctxType.Method(i)
if ignoreFuncs[method.Name] {
continue
}
+8 -15
View File
@@ -24,25 +24,24 @@ import (
"github.com/bep/overlayfs"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/afero"
"github.com/spf13/cast"
)
// New returns a new instance of the os-namespaced template functions.
func New(d *deps.Deps) *Namespace {
var readFileFs, workFs *hugofs.DropSymlinksFs
var readFileFs, workFs afero.Fs
// The docshelper script does not have or need all the dependencies set up.
if d.PathSpec != nil {
readFileFs = hugofs.NewDropSymlinksFs(overlayfs.New(overlayfs.Options{
readFileFs = overlayfs.New(overlayfs.Options{
Fss: []afero.Fs{
d.PathSpec.BaseFs.Work,
d.PathSpec.BaseFs.Content.Fs,
},
}))
})
// See #9599
workFs = hugofs.NewDropSymlinksFs(d.PathSpec.BaseFs.WorkDir)
workFs = d.PathSpec.BaseFs.WorkDir
}
return &Namespace{
@@ -54,8 +53,8 @@ func New(d *deps.Deps) *Namespace {
// Namespace provides template functions for the "os" namespace.
type Namespace struct {
readFileFs *hugofs.DropSymlinksFs
workFs *hugofs.DropSymlinksFs
readFileFs afero.Fs
workFs afero.Fs
deps *deps.Deps
}
@@ -119,9 +118,6 @@ func (ns *Namespace) ReadDir(i any) ([]_os.FileInfo, error) {
list, err := afero.ReadDir(ns.workFs, path)
if err != nil {
if herrors.IsNotExist(err) {
return nil, nil
}
return nil, fmt.Errorf("failed to read directory %q: %s", path, err)
}
@@ -136,7 +132,7 @@ func (ns *Namespace) FileExists(i any) (bool, error) {
}
if path == "" {
return false, nil
return false, errors.New("fileExists needs a path to a file")
}
status, err := afero.Exists(ns.readFileFs, path)
@@ -155,14 +151,11 @@ func (ns *Namespace) Stat(i any) (_os.FileInfo, error) {
}
if path == "" {
return nil, nil
return nil, errors.New("fileStat needs a path to a file")
}
r, err := ns.readFileFs.Stat(path)
if err != nil {
if herrors.IsNotExist(err) {
return nil, nil
}
return nil, err
}
+8 -4
View File
@@ -64,16 +64,21 @@ func TestFileExists(t *testing.T) {
for _, test := range []struct {
filename string
expect bool
expect any
}{
{filepath.FromSlash("/f/f1.txt"), true},
{filepath.FromSlash("f/f1.txt"), true},
{filepath.FromSlash("../f2.txt"), false},
{"b", false},
{"", false},
{"", nil},
} {
result, err := ns.FileExists(test.filename)
if test.expect == nil {
c.Assert(err, qt.Not(qt.IsNil))
continue
}
c.Assert(err, qt.IsNil)
c.Assert(result, qt.Equals, test.expect)
}
@@ -96,8 +101,7 @@ func TestStat(t *testing.T) {
result, err := ns.Stat(test.filename)
if test.expect == nil {
b.Assert(err, qt.IsNil)
b.Assert(result, qt.IsNil)
b.Assert(err, qt.Not(qt.IsNil))
continue
}
+1 -1
View File
@@ -95,7 +95,7 @@ type StringBool struct {
}
type page interface {
IsBranch() bool
IsNode() bool
}
type site interface {
-53
View File
@@ -1,53 +0,0 @@
// Copyright 2017 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.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package time_test
import (
"testing"
"github.com/gohugoio/hugo/hugolib"
)
// See issue 14948.
func TestTimeFormatMonthAbbreviationsEnGB(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
locale = 'en-GB'
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
-- layouts/home.html --
{{ $dates := slice
"2025-01-17T10:42:00-08:00"
"2025-02-17T10:42:00-08:00"
"2025-03-17T10:42:00-07:00"
"2025-04-17T10:42:00-07:00"
"2025-05-17T10:42:00-07:00"
"2025-06-17T10:42:00-07:00"
"2025-07-17T10:42:00-07:00"
"2025-08-17T10:42:00-07:00"
"2025-09-17T10:42:00-07:00"
"2025-10-17T10:42:00-07:00"
"2025-11-17T10:42:00-08:00"
"2025-12-17T10:42:00-08:00"
}}
{{- range $dates }}
{{- time.Format "Jan" . }}|
{{- end }}
`
b := hugolib.Test(t, files)
b.AssertFileContent("public/index.html", "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sept|Oct|Nov|Dec|")
}
+1 -1
View File
@@ -1,4 +1,4 @@
{{/* gotmplfmt-ignore-all */ -}}
{{/* gotmplfmt-ignore-all */}}
{{ printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
+31 -23
View File
@@ -27,7 +27,7 @@ func TestEmbeddedLinkRenderHook(t *testing.T) {
-- hugo.toml --
disableKinds = ['rss','sitemap','taxonomy','term']
[markup.goldmark.renderHooks.link]
useEmbedded = 'always'
enableDefault = true
-- layouts/list.html --
{{ .Content }}
-- layouts/single.html --
@@ -153,7 +153,7 @@ wrapStandAloneImageWithinParagraph = false
[markup.goldmark.parser.attribute]
block = false
[markup.goldmark.renderHooks.image]
useEmbedded = 'always'
enableDefault = true
-- content/p1/index.md --
![]()
@@ -253,29 +253,37 @@ custom image render hook: {{ .Text }}|{{ .Destination }}
id string // the test id
isMultilingual bool // whether the site is multilingual single-host
hasCustomHooks bool // whether the site has custom link and image render hooks
keyValuePair string // the useEmbedded key-value pair
keyValuePair string // the enableDefault (deprecated in v0.148.0) or useEmbedded key-value pair
want string // the expected content of public/s1/p1/index.html
}{
{"01", false, false, "", wantGoldmark}, // monolingual
{"02", false, false, "useEmbedded = 'always'", wantEmbedded}, // monolingual, useEmbedded = 'always'
{"03", false, false, "useEmbedded = 'auto'", wantGoldmark}, // monolingual, useEmbedded = 'auto'
{"04", false, false, "useEmbedded = 'fallback'", wantEmbedded}, // monolingual, useEmbedded = 'fallback'
{"05", false, false, "useEmbedded = 'never'", wantGoldmark}, // monolingual, useEmbedded = 'never'
{"06", false, true, "", wantCustom}, // monolingual, with custom hooks
{"07", false, true, "useEmbedded = 'always'", wantEmbedded}, // monolingual, with custom hooks, useEmbedded = 'always'
{"08", false, true, "useEmbedded = 'auto'", wantCustom}, // monolingual, with custom hooks, useEmbedded = 'auto'
{"09", false, true, "useEmbedded = 'fallback'", wantCustom}, // monolingual, with custom hooks, useEmbedded = 'fallback'
{"10", false, true, "useEmbedded = 'never'", wantCustom}, // monolingual, with custom hooks, useEmbedded = 'never'
{"11", true, false, "", wantEmbedded}, // multilingual
{"12", true, false, "useEmbedded = 'always'", wantEmbedded}, // multilingual, useEmbedded = 'always'
{"13", true, false, "useEmbedded = 'auto'", wantEmbedded}, // multilingual, useEmbedded = 'auto'
{"14", true, false, "useEmbedded = 'fallback'", wantEmbedded}, // multilingual, useEmbedded = 'fallback'
{"15", true, false, "useEmbedded = 'never'", wantGoldmark}, // multilingual, useEmbedded = 'never'
{"16", true, true, "", wantCustom}, // multilingual, with custom hooks
{"17", true, true, "useEmbedded = 'always'", wantEmbedded}, // multilingual, with custom hooks, useEmbedded = 'always'
{"18", true, true, "useEmbedded = 'auto'", wantCustom}, // multilingual, with custom hooks, useEmbedded = 'auto'
{"19", true, true, "useEmbedded = 'fallback'", wantCustom}, // multilingual, with custom hooks, useEmbedded = 'fallback'
{"20", true, true, "useEmbedded = 'never'", wantCustom}, // multilingual, with custom hooks, useEmbedded = 'never'
{"02", false, false, "enableDefault = false", wantGoldmark}, // monolingual, enableDefault = false
{"03", false, false, "enableDefault = true", wantEmbedded}, // monolingual, enableDefault = true
{"04", false, false, "useEmbedded = 'always'", wantEmbedded}, // monolingual, useEmbedded = 'always'
{"05", false, false, "useEmbedded = 'auto'", wantGoldmark}, // monolingual, useEmbedded = 'auto'
{"06", false, false, "useEmbedded = 'fallback'", wantEmbedded}, // monolingual, useEmbedded = 'fallback'
{"07", false, false, "useEmbedded = 'never'", wantGoldmark}, // monolingual, useEmbedded = 'never'
{"08", false, true, "", wantCustom}, // monolingual, with custom hooks
{"09", false, true, "enableDefault = false", wantCustom}, // monolingual, with custom hooks, enableDefault = false
{"10", false, true, "enableDefault = true", wantCustom}, // monolingual, with custom hooks, enableDefault = true
{"11", false, true, "useEmbedded = 'always'", wantEmbedded}, // monolingual, with custom hooks, useEmbedded = 'always'
{"12", false, true, "useEmbedded = 'auto'", wantCustom}, // monolingual, with custom hooks, useEmbedded = 'auto'
{"13", false, true, "useEmbedded = 'fallback'", wantCustom}, // monolingual, with custom hooks, useEmbedded = 'fallback'
{"14", false, true, "useEmbedded = 'never'", wantCustom}, // monolingual, with custom hooks, useEmbedded = 'never'
{"15", true, false, "", wantEmbedded}, // multilingual
{"16", true, false, "enableDefault = false", wantGoldmark}, // multilingual, enableDefault = false
{"17", true, false, "enableDefault = true", wantEmbedded}, // multilingual, enableDefault = true
{"18", true, false, "useEmbedded = 'always'", wantEmbedded}, // multilingual, useEmbedded = 'always'
{"19", true, false, "useEmbedded = 'auto'", wantEmbedded}, // multilingual, useEmbedded = 'auto'
{"20", true, false, "useEmbedded = 'fallback'", wantEmbedded}, // multilingual, useEmbedded = 'fallback'
{"21", true, false, "useEmbedded = 'never'", wantGoldmark}, // multilingual, useEmbedded = 'never'
{"22", true, true, "", wantCustom}, // multilingual, with custom hooks
{"23", true, true, "enableDefault = false", wantCustom}, // multilingual, with custom hooks, enableDefault = false
{"24", true, true, "enableDefault = true", wantCustom}, // multilingual, with custom hooks, enableDefault = true
{"25", true, true, "useEmbedded = 'always'", wantEmbedded}, // multilingual, with custom hooks, useEmbedded = 'always'
{"26", true, true, "useEmbedded = 'auto'", wantCustom}, // multilingual, with custom hooks, useEmbedded = 'auto'
{"27", true, true, "useEmbedded = 'fallback'", wantCustom}, // multilingual, with custom hooks, useEmbedded = 'fallback'
{"28", true, true, "useEmbedded = 'never'", wantCustom}, // multilingual, with custom hooks, useEmbedded = 'never'
}
for _, tt := range tests {
@@ -305,7 +313,7 @@ func TestRenderHookMultilingual(t *testing.T) {
baseURL = 'https://example.org/'
defaultContentLanguage = 'en'
[markup.goldmark.renderHooks.image]
useEmbedded = 'never'
enableDefault = false
[languages.en]
weight = 1
[languages.tr]
+3 -3
View File
@@ -510,12 +510,12 @@ title: p2
b := hugolib.Test(t, files)
// Test x shortcode
want := `<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Owl bet you&#39;ll lose this staring contest 🦉 <a href="https://t.co/eJh4f2zncC">pic.twitter.com/eJh4f2zncC</a></p>&mdash; San Diego Zoo Wildlife Alliance (@sandiegozoo) <a href="https://x.com/sandiegozoo/status/1453110110599868418?ref_src=twsrc%5Etfw">October 26, 2021</a></blockquote>
<script async src="https://platform.x.com/widgets.js" charset="utf-8"></script>`
want := `<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Owl bet you&#39;ll lose this staring contest 🦉 <a href="https://t.co/eJh4f2zncC">pic.twitter.com/eJh4f2zncC</a></p>&mdash; San Diego Zoo Wildlife Alliance (@sandiegozoo) <a href="https://twitter.com/sandiegozoo/status/1453110110599868418?ref_src=twsrc%5Etfw">October 26, 2021</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>`
b.AssertFileContent("public/p1/index.html", want)
// Test x_simple shortcodes
wantSimple := "<style type=\"text/css\">\n .twitter-tweet {\n font:\n 14px/1.45 -apple-system,\n BlinkMacSystemFont,\n \"Segoe UI\",\n Roboto,\n Oxygen-Sans,\n Ubuntu,\n Cantarell,\n \"Helvetica Neue\",\n sans-serif;\n border-left: 4px solid #2b7bb9;\n padding-left: 1.5em;\n color: #555;\n }\n .twitter-tweet a {\n color: #2b7bb9;\n text-decoration: none;\n }\n blockquote.twitter-tweet a:hover,\n blockquote.twitter-tweet a:focus {\n text-decoration: underline;\n }\n </style><blockquote class=\"twitter-tweet\"><p lang=\"en\" dir=\"ltr\">Owl bet you&#39;ll lose this staring contest 🦉 <a href=\"https://t.co/eJh4f2zncC\">pic.twitter.com/eJh4f2zncC</a></p>&mdash; San Diego Zoo Wildlife Alliance (@sandiegozoo) <a href=\"https://x.com/sandiegozoo/status/1453110110599868418?ref_src=twsrc%5Etfw\">October 26, 2021</a></blockquote>\n--"
wantSimple := "<style type=\"text/css\">\n .twitter-tweet {\n font:\n 14px/1.45 -apple-system,\n BlinkMacSystemFont,\n \"Segoe UI\",\n Roboto,\n Oxygen-Sans,\n Ubuntu,\n Cantarell,\n \"Helvetica Neue\",\n sans-serif;\n border-left: 4px solid #2b7bb9;\n padding-left: 1.5em;\n color: #555;\n }\n .twitter-tweet a {\n color: #2b7bb9;\n text-decoration: none;\n }\n blockquote.twitter-tweet a:hover,\n blockquote.twitter-tweet a:focus {\n text-decoration: underline;\n }\n </style><blockquote class=\"twitter-tweet\"><p lang=\"en\" dir=\"ltr\">Owl bet you&#39;ll lose this staring contest 🦉 <a href=\"https://t.co/eJh4f2zncC\">pic.twitter.com/eJh4f2zncC</a></p>&mdash; San Diego Zoo Wildlife Alliance (@sandiegozoo) <a href=\"https://twitter.com/sandiegozoo/status/1453110110599868418?ref_src=twsrc%5Etfw\">October 26, 2021</a></blockquote>\n--"
b.AssertFileContent("public/p2/index.html", wantSimple)
// Test privacy.x.simple
+1 -1
View File
@@ -58,7 +58,7 @@ func NewEmpty() Chain {
}
// Implements contentTransformer
// Content is read from the from-buffer and rewritten to the to-buffer.
// Content is read from the from-buffer and rewritten to to the to-buffer.
type fromToBuffer struct {
from *bytes.Buffer
to *bytes.Buffer