Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e290504261 |
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 (1–4 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{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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: 
|
||||
{{ .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-"<>&\">text-"<>&</a>",
|
||||
"img src=\"/destination-%22%3C%3E\" alt=\"alt-"<>&\" title=\"title-"<>&\">",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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 -}}
|
||||
|
||||
@@ -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 := `
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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_",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 --
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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("*/")
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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++ {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 --
|
||||
|
||||
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 62 KiB |
@@ -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),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
|
||||
@@ -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]|")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ type StringBool struct {
|
||||
}
|
||||
|
||||
type page interface {
|
||||
IsBranch() bool
|
||||
IsNode() bool
|
||||
}
|
||||
|
||||
type site interface {
|
||||
|
||||
@@ -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,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">
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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'll lose this staring contest 🦉 <a href="https://t.co/eJh4f2zncC">pic.twitter.com/eJh4f2zncC</a></p>— 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'll lose this staring contest 🦉 <a href="https://t.co/eJh4f2zncC">pic.twitter.com/eJh4f2zncC</a></p>— 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'll lose this staring contest 🦉 <a href=\"https://t.co/eJh4f2zncC\">pic.twitter.com/eJh4f2zncC</a></p>— 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'll lose this staring contest 🦉 <a href=\"https://t.co/eJh4f2zncC\">pic.twitter.com/eJh4f2zncC</a></p>— 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
|
||||
|
||||
@@ -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
|
||||
|
||||