mirror of
https://github.com/gohugoio/hugo.git
synced 2026-09-01 19:22:38 +00:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 06bc2c16c0 | |||
| d51adca26a | |||
| a1307700dd | |||
| 9425b939c6 | |||
| bd50c9c7e7 | |||
| a8e0ca9254 | |||
| 559a029de7 | |||
| 184b10ed45 | |||
| 5bad0d523e | |||
| a3d9548469 | |||
| 9e344bbe49 | |||
| 1ec0e2ebec | |||
| a3574f6f70 | |||
| 989454a52d | |||
| 1e91e46520 | |||
| 8a57d0f15f | |||
| b76c50ac18 | |||
| 1cdd17882c | |||
| e2fb0b0e80 | |||
| 88aea56683 | |||
| f4c11571bb | |||
| 54075acc29 | |||
| 8b52303e31 | |||
| 3d45d30a41 | |||
| 095157cd65 | |||
| 29cf87444f | |||
| 1b4dd436dd | |||
| 4414ef73f3 | |||
| a133393eda | |||
| 9197debbfe |
@@ -4,7 +4,7 @@ parameters:
|
||||
defaults: &defaults
|
||||
resource_class: large
|
||||
docker:
|
||||
- image: bepsays/ci-hugoreleaser:1.22500.20100
|
||||
- image: bepsays/ci-hugoreleaser:1.22500.20300
|
||||
environment: &buildenv
|
||||
GOMODCACHE: /root/project/gomodcache
|
||||
version: 2
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
environment:
|
||||
<<: [*buildenv]
|
||||
docker:
|
||||
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22500.20100
|
||||
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22500.20300
|
||||
steps:
|
||||
- *restore-cache
|
||||
- &attach-workspace
|
||||
|
||||
+1
-1
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/alecthomas/chroma/v2/formatters/html"
|
||||
"github.com/alecthomas/chroma/v2/styles"
|
||||
"github.com/bep/simplecobra"
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
"github.com/gohugoio/hugo/docshelper"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
@@ -36,7 +37,6 @@ import (
|
||||
"github.com/gohugoio/hugo/parser"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/cobra/doc"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func newGenCommand() *genCommand {
|
||||
|
||||
@@ -1166,7 +1166,6 @@ func chmodFilter(dst, src os.FileInfo) bool {
|
||||
}
|
||||
|
||||
func cleanErrorLog(content string) string {
|
||||
content = strings.ReplaceAll(content, "\n", " ")
|
||||
content = logReplacer.Replace(content)
|
||||
content = logDuplicateTemplateExecuteRe.ReplaceAllString(content, "")
|
||||
content = logDuplicateTemplateParseRe.ReplaceAllString(content, "")
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2025 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// 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 hdebug
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/htesting"
|
||||
)
|
||||
|
||||
// Printf is a debug print function that should be removed before committing code to the repository.
|
||||
func Printf(format string, args ...any) {
|
||||
panicIfRealCI()
|
||||
if len(args) == 1 && !strings.Contains(format, "%") {
|
||||
format = format + ": %v"
|
||||
}
|
||||
if !strings.HasSuffix(format, "\n") {
|
||||
format = format + "\n"
|
||||
}
|
||||
fmt.Printf(format, args...)
|
||||
}
|
||||
|
||||
func AssertNotNil(a ...any) {
|
||||
panicIfRealCI()
|
||||
for _, v := range a {
|
||||
if types.IsNil(v) {
|
||||
panic("hdebug.AssertNotNil: value is nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Panicf(format string, args ...any) {
|
||||
panicIfRealCI()
|
||||
// fmt.Println(stack())
|
||||
if len(args) == 1 && !strings.Contains(format, "%") {
|
||||
format = format + ": %v"
|
||||
}
|
||||
if !strings.HasSuffix(format, "\n") {
|
||||
format = format + "\n"
|
||||
}
|
||||
panic(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func panicIfRealCI() {
|
||||
if htesting.IsRealCI() {
|
||||
panic("This debug statement should be removed before committing code!")
|
||||
}
|
||||
}
|
||||
@@ -110,11 +110,11 @@ func (fe *fileError) UpdateContent(r io.Reader, linematcher LineMatcherFn) FileE
|
||||
|
||||
fe.errorContext = ectx
|
||||
|
||||
if ectx.Position.LineNumber > 0 {
|
||||
if ectx.Position.LineNumber > 0 && ectx.Position.LineNumber > fe.position.LineNumber {
|
||||
fe.position.LineNumber = ectx.Position.LineNumber
|
||||
}
|
||||
|
||||
if ectx.Position.ColumnNumber > 0 {
|
||||
if ectx.Position.ColumnNumber > 0 && ectx.Position.ColumnNumber > fe.position.ColumnNumber {
|
||||
fe.position.ColumnNumber = ectx.Position.ColumnNumber
|
||||
}
|
||||
|
||||
@@ -177,6 +177,7 @@ func NewFileErrorFromName(err error, name string) FileError {
|
||||
// Filetype is used to determine the Chroma lexer to use.
|
||||
fileType, pos := extractFileTypePos(err)
|
||||
pos.Filename = name
|
||||
|
||||
if fileType == "" {
|
||||
_, fileType = paths.FileAndExtNoDelimiter(filepath.Clean(name))
|
||||
}
|
||||
@@ -234,7 +235,9 @@ func NewFileErrorFromFile(err error, filename string, fs afero.Fs, linematcher L
|
||||
return NewFileErrorFromName(err, realFilename)
|
||||
}
|
||||
defer f.Close()
|
||||
return NewFileErrorFromName(err, realFilename).UpdateContent(f, linematcher)
|
||||
fe := NewFileErrorFromName(err, realFilename)
|
||||
fe = fe.UpdateContent(f, linematcher)
|
||||
return fe
|
||||
}
|
||||
|
||||
func openFile(filename string, fs afero.Fs) (afero.File, string, error) {
|
||||
@@ -321,13 +324,9 @@ func extractFileTypePos(err error) (string, text.Position) {
|
||||
}
|
||||
|
||||
// Look in the error message for the line number.
|
||||
for _, handle := range lineNumberExtractors {
|
||||
lno, col := handle(err)
|
||||
if lno > 0 {
|
||||
pos.ColumnNumber = col
|
||||
pos.LineNumber = lno
|
||||
break
|
||||
}
|
||||
if lno, col := commonLineNumberExtractor(err); lno > 0 {
|
||||
pos.ColumnNumber = col
|
||||
pos.LineNumber = lno
|
||||
}
|
||||
|
||||
if fileType == "" && pos.Filename != "" {
|
||||
|
||||
@@ -19,17 +19,27 @@ import (
|
||||
)
|
||||
|
||||
var lineNumberExtractors = []lineNumberExtractor{
|
||||
// YAML parse errors.
|
||||
newLineNumberErrHandlerFromRegexp(`\[(\d+):(\d+)\]`),
|
||||
|
||||
// Template/shortcode parse errors
|
||||
newLineNumberErrHandlerFromRegexp(`:(\d+):(\d*):`),
|
||||
newLineNumberErrHandlerFromRegexp(`:(\d+):`),
|
||||
|
||||
// YAML parse errors
|
||||
newLineNumberErrHandlerFromRegexp(`line (\d+):`),
|
||||
|
||||
// i18n bundle errors
|
||||
newLineNumberErrHandlerFromRegexp(`\((\d+),\s(\d*)`),
|
||||
}
|
||||
|
||||
func commonLineNumberExtractor(e error) (int, int) {
|
||||
for _, handler := range lineNumberExtractors {
|
||||
lno, col := handler(e)
|
||||
if lno > 0 {
|
||||
return lno, col
|
||||
}
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
type lineNumberExtractor func(e error) (int, int)
|
||||
|
||||
func newLineNumberErrHandlerFromRegexp(expression string) lineNumberExtractor {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2025 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// 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 herrors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestCommonLineNumberExtractor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c := qt.New(t)
|
||||
|
||||
lno, col := commonLineNumberExtractor(errors.New("[4:9] value is not allowed in this context"))
|
||||
c.Assert(lno, qt.Equals, 4)
|
||||
c.Assert(col, qt.Equals, 9)
|
||||
}
|
||||
@@ -19,5 +19,5 @@ var CurrentVersion = Version{
|
||||
Major: 0,
|
||||
Minor: 152,
|
||||
PatchLevel: 0,
|
||||
Suffix: "-DEV",
|
||||
Suffix: "",
|
||||
}
|
||||
|
||||
+43
-4
@@ -14,6 +14,7 @@
|
||||
package maps
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -40,6 +41,14 @@ func (p Params) GetNested(indices ...string) any {
|
||||
// SetParams overwrites values in dst with values in src for common or new keys.
|
||||
// This is done recursively.
|
||||
func SetParams(dst, src Params) {
|
||||
setParams(dst, src, 0)
|
||||
}
|
||||
|
||||
func setParams(dst, src Params, depth int) {
|
||||
const maxDepth = 1000
|
||||
if depth > maxDepth {
|
||||
panic(errors.New("max depth exceeded"))
|
||||
}
|
||||
for k, v := range src {
|
||||
vv, found := dst[k]
|
||||
if !found {
|
||||
@@ -48,7 +57,7 @@ func SetParams(dst, src Params) {
|
||||
switch vvv := vv.(type) {
|
||||
case Params:
|
||||
if pv, ok := v.(Params); ok {
|
||||
SetParams(vvv, pv)
|
||||
setParams(vvv, pv, depth+1)
|
||||
} else {
|
||||
dst[k] = v
|
||||
}
|
||||
@@ -101,7 +110,6 @@ func (p Params) merge(ps ParamsMergeStrategy, pp Params) {
|
||||
noUpdate = noUpdate || (ps != "" && ps == ParamsMergeStrategyShallow)
|
||||
|
||||
for k, v := range pp {
|
||||
|
||||
if k == MergeStrategyKey {
|
||||
continue
|
||||
}
|
||||
@@ -266,9 +274,17 @@ func CleanConfigStringMapString(m map[string]string) map[string]string {
|
||||
// CleanConfigStringMap is the same as CleanConfigStringMapString but for
|
||||
// map[string]any.
|
||||
func CleanConfigStringMap(m map[string]any) map[string]any {
|
||||
return doCleanConfigStringMap(m, 0)
|
||||
}
|
||||
|
||||
func doCleanConfigStringMap(m map[string]any, depth int) map[string]any {
|
||||
if len(m) == 0 {
|
||||
return m
|
||||
}
|
||||
const maxDepth = 1000
|
||||
if depth > maxDepth {
|
||||
panic(errors.New("max depth exceeded"))
|
||||
}
|
||||
if _, found := m[MergeStrategyKey]; !found {
|
||||
return m
|
||||
}
|
||||
@@ -280,9 +296,9 @@ func CleanConfigStringMap(m map[string]any) map[string]any {
|
||||
}
|
||||
switch v2 := v.(type) {
|
||||
case map[string]any:
|
||||
m2[k] = CleanConfigStringMap(v2)
|
||||
m2[k] = doCleanConfigStringMap(v2, depth+1)
|
||||
case Params:
|
||||
var p Params = CleanConfigStringMap(v2)
|
||||
var p Params = doCleanConfigStringMap(v2, depth+1)
|
||||
m2[k] = p
|
||||
case map[string]string:
|
||||
m2[k] = CleanConfigStringMapString(v2)
|
||||
@@ -344,6 +360,29 @@ func PrepareParams(m Params) {
|
||||
}
|
||||
}
|
||||
|
||||
// CloneParamsDeep does a deep clone of the given Params,
|
||||
// meaning that any nested Params will be cloned as well.
|
||||
func CloneParamsDeep(m Params) Params {
|
||||
return cloneParamsDeep(m, 0)
|
||||
}
|
||||
|
||||
func cloneParamsDeep(m Params, depth int) Params {
|
||||
const maxDepth = 1000
|
||||
if depth > maxDepth {
|
||||
panic(errors.New("max depth exceeded"))
|
||||
}
|
||||
m2 := make(Params)
|
||||
for k, v := range m {
|
||||
switch vv := v.(type) {
|
||||
case Params:
|
||||
m2[k] = cloneParamsDeep(vv, depth+1)
|
||||
default:
|
||||
m2[k] = v
|
||||
}
|
||||
}
|
||||
return m2
|
||||
}
|
||||
|
||||
// PrepareParamsClone is like PrepareParams, but it does not modify the input.
|
||||
func PrepareParamsClone(m Params) Params {
|
||||
m2 := make(Params)
|
||||
|
||||
@@ -55,8 +55,6 @@ import (
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
"github.com/gohugoio/hugo/resources/page/pagemeta"
|
||||
"github.com/spf13/afero"
|
||||
|
||||
xmaps "golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
// InternalConfig is the internal configuration for Hugo, not read from any user provided config file.
|
||||
@@ -1054,6 +1052,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
mergedConfig := config.New()
|
||||
var differentRootKeys []string
|
||||
switch x := v.(type) {
|
||||
case nil:
|
||||
case maps.Params:
|
||||
_, found := x["params"]
|
||||
if !found {
|
||||
@@ -1070,6 +1069,14 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
// baseURL configure don the language level is a multihost setup.
|
||||
isMultihost = true
|
||||
}
|
||||
|
||||
if p, ok := vv.(maps.Params); ok {
|
||||
// With the introduction of YAML anchor and alias support, language config entries
|
||||
// may be contain shared references.
|
||||
// This also break potential cycles.
|
||||
vv = maps.CloneParamsDeep(p)
|
||||
}
|
||||
|
||||
mergedConfig.Set(kk, vv)
|
||||
rootv := cfg.Get(kk)
|
||||
if rootv != nil && cfg.IsSet(kk) {
|
||||
@@ -1080,7 +1087,8 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
differentRootKeys = append(differentRootKeys, kk)
|
||||
|
||||
// Use the language value as base.
|
||||
mergedConfigEntry := xmaps.Clone(vvv)
|
||||
// Note that this is already cloned above.
|
||||
mergedConfigEntry := vvv
|
||||
// Merge in the root value.
|
||||
maps.MergeParams(mergedConfigEntry, rootv.(maps.Params))
|
||||
|
||||
@@ -1129,6 +1137,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
|
||||
langConfigMap[k] = clone
|
||||
case maps.ParamsMergeStrategy:
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown type in languages config: %T", v))
|
||||
|
||||
|
||||
@@ -40,7 +40,13 @@ import (
|
||||
//lint:ignore ST1005 end user message.
|
||||
var ErrNoConfigFile = errors.New("Unable to locate config file or config directory. Perhaps you need to create a new site.\n Run `hugo help new` for details.\n")
|
||||
|
||||
func LoadConfig(d ConfigSourceDescriptor) (*Configs, error) {
|
||||
func LoadConfig(d ConfigSourceDescriptor) (configs *Configs, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("failed to load config: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
if len(d.Environ) == 0 && !hugo.IsRunningAsTest() {
|
||||
d.Environ = os.Environ()
|
||||
}
|
||||
@@ -59,7 +65,7 @@ func LoadConfig(d ConfigSourceDescriptor) (*Configs, error) {
|
||||
return nil, fmt.Errorf("failed to load config: %w", err)
|
||||
}
|
||||
|
||||
configs, err := fromLoadConfigResult(d.Fs, d.Logger, res)
|
||||
configs, err = fromLoadConfigResult(d.Fs, d.Logger, res)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create config from result: %w", err)
|
||||
}
|
||||
@@ -93,7 +99,7 @@ func LoadConfig(d ConfigSourceDescriptor) (*Configs, error) {
|
||||
|
||||
loggers.SetGlobalLogger(d.Logger)
|
||||
|
||||
return configs, nil
|
||||
return
|
||||
}
|
||||
|
||||
// ConfigSourceDescriptor describes where to find the config (e.g. config.toml etc.).
|
||||
@@ -538,11 +544,12 @@ func (l configLoader) loadConfig(configName string) (string, error) {
|
||||
return filename, nil
|
||||
}
|
||||
|
||||
func (l configLoader) deleteMergeStrategies() {
|
||||
func (l configLoader) deleteMergeStrategies() (err error) {
|
||||
l.cfg.WalkParams(func(params ...maps.KeyParams) bool {
|
||||
params[len(params)-1].Params.DeleteMergeStrategy()
|
||||
return false
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (l configLoader) wrapFileError(err error, filename string) error {
|
||||
|
||||
@@ -14,11 +14,13 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
xmaps "golang.org/x/exp/maps"
|
||||
xmaps "maps"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
|
||||
@@ -237,12 +239,16 @@ func (c *defaultConfigProvider) Merge(k string, v any) {
|
||||
func (c *defaultConfigProvider) Keys() []string {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return xmaps.Keys(c.root)
|
||||
return slices.Collect(xmaps.Keys(c.root))
|
||||
}
|
||||
|
||||
func (c *defaultConfigProvider) WalkParams(walkFn func(params ...maps.KeyParams) bool) {
|
||||
var walk func(params ...maps.KeyParams)
|
||||
walk = func(params ...maps.KeyParams) {
|
||||
maxDepth := 1000
|
||||
var walk func(depth int, params ...maps.KeyParams)
|
||||
walk = func(depth int, params ...maps.KeyParams) {
|
||||
if depth > maxDepth {
|
||||
panic(errors.New("max depth exceeded"))
|
||||
}
|
||||
if walkFn(params...) {
|
||||
return
|
||||
}
|
||||
@@ -253,11 +259,11 @@ func (c *defaultConfigProvider) WalkParams(walkFn func(params ...maps.KeyParams)
|
||||
paramsplus1 := make([]maps.KeyParams, i+1)
|
||||
copy(paramsplus1, params)
|
||||
paramsplus1[i] = maps.KeyParams{Key: k, Params: p2}
|
||||
walk(paramsplus1...)
|
||||
walk(depth+1, paramsplus1...)
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(maps.KeyParams{Key: "", Params: c.root})
|
||||
walk(0, maps.KeyParams{Key: "", Params: c.root})
|
||||
}
|
||||
|
||||
func (c *defaultConfigProvider) determineMergeStrategy(params ...maps.KeyParams) maps.ParamsMergeStrategy {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
{{ define "main" }}
|
||||
{{ .Content }}
|
||||
{{ range site.RegularPages }}
|
||||
<h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
|
||||
{{ .Summary }}
|
||||
<section>
|
||||
<h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
|
||||
{{ .Summary }}
|
||||
</section>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ .Content }}
|
||||
{{ range .Pages }}
|
||||
<h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
|
||||
{{ .Summary }}
|
||||
<section>
|
||||
<h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
|
||||
{{ .Summary }}
|
||||
</section>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
@@ -2,6 +2,7 @@ module github.com/gohugoio/hugo
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69
|
||||
github.com/JohannesKaufmann/html-to-markdown/v2 v2.4.0
|
||||
github.com/alecthomas/chroma/v2 v2.20.0
|
||||
github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.2
|
||||
@@ -26,17 +27,17 @@ require (
|
||||
github.com/clbanning/mxj/v2 v2.7.0
|
||||
github.com/disintegration/gift v1.2.1
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/evanw/esbuild v0.25.10
|
||||
github.com/evanw/esbuild v0.25.11
|
||||
github.com/fatih/color v1.18.0
|
||||
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.133.0
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/gobuffalo/flect v1.0.3
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e
|
||||
github.com/gohugoio/hashstructure v0.5.0
|
||||
github.com/goccy/go-yaml v1.18.0
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20251018145728-cfcc22d823c6
|
||||
github.com/gohugoio/hashstructure v0.6.0
|
||||
github.com/gohugoio/httpcache v0.8.0
|
||||
github.com/gohugoio/hugo-goldmark-extensions/extras v0.5.0
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.3.1
|
||||
@@ -67,22 +68,20 @@ require (
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/fsync v0.10.1
|
||||
github.com/spf13/pflag v1.0.7
|
||||
github.com/tdewolff/minify/v2 v2.24.3
|
||||
github.com/tdewolff/parse/v2 v2.8.3
|
||||
github.com/tdewolff/minify/v2 v2.24.4
|
||||
github.com/tdewolff/parse/v2 v2.8.4
|
||||
github.com/tetratelabs/wazero v1.9.0
|
||||
github.com/yuin/goldmark v1.7.13
|
||||
github.com/yuin/goldmark-emoji v1.0.6
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
gocloud.dev v0.43.0
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b
|
||||
golang.org/x/image v0.30.0
|
||||
golang.org/x/mod v0.28.0
|
||||
golang.org/x/net v0.44.0
|
||||
golang.org/x/image v0.32.0
|
||||
golang.org/x/mod v0.29.0
|
||||
golang.org/x/net v0.46.0
|
||||
golang.org/x/sync v0.17.0
|
||||
golang.org/x/text v0.29.0
|
||||
golang.org/x/tools v0.37.0
|
||||
golang.org/x/text v0.30.0
|
||||
golang.org/x/tools v0.38.0
|
||||
google.golang.org/api v0.251.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
rsc.io/qr v0.2.0
|
||||
)
|
||||
|
||||
@@ -106,7 +105,6 @@ require (
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
|
||||
github.com/JohannesKaufmann/dom v0.2.0 // indirect
|
||||
github.com/JohannesKaufmann/html-to-markdown/v2 v2.4.0 // indirect
|
||||
github.com/aws/aws-sdk-go v1.55.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.17 // indirect
|
||||
@@ -178,9 +176,9 @@ require (
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
golang.org/x/crypto v0.42.0 // indirect
|
||||
golang.org/x/crypto v0.43.0 // indirect
|
||||
golang.org/x/oauth2 v0.31.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/time v0.13.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||
google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 // indirect
|
||||
@@ -188,6 +186,7 @@ require (
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // indirect
|
||||
google.golang.org/grpc v1.75.1 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
software.sslmate.com/src/go-pkcs12 v0.2.0 // indirect
|
||||
|
||||
@@ -80,8 +80,9 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJ
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69 h1:+tu3HOoMXB7RXEINRVIpxJCT+KdYiI7LAEAUrOw3dIU=
|
||||
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69/go.mod h1:L1AbZdiDllfyYH5l5OkAaZtk7VkWe89bPJFmnDBNHxg=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw=
|
||||
@@ -225,8 +226,8 @@ github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJP
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
|
||||
github.com/evanw/esbuild v0.25.10 h1:8cl6FntLWO4AbqXWqMWgYrvdm8lLSFm5HjU/HY2N27E=
|
||||
github.com/evanw/esbuild v0.25.10/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
|
||||
github.com/evanw/esbuild v0.25.11 h1:NGtezc+xk+Mti4fgWaoD3dncZNCzcTA+r0BxMV3Koyw=
|
||||
github.com/evanw/esbuild v0.25.11/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
@@ -242,8 +243,6 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
|
||||
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
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=
|
||||
@@ -266,10 +265,12 @@ github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4
|
||||
github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e h1:QArsSubW7eDh8APMXkByjQWvuljwPGAGQpJEFn0F0wY=
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e/go.mod h1:3Ltoo9Banwq0gOtcOwxuHG6omk+AwsQPADyw2vQYOJQ=
|
||||
github.com/gohugoio/hashstructure v0.5.0 h1:G2fjSBU36RdwEJBWJ+919ERvOVqAg9tfcYp47K9swqg=
|
||||
github.com/gohugoio/hashstructure v0.5.0/go.mod h1:Ser0TniXuu/eauYmrwM4o64EBvySxNzITEOLlm4igec=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20251018145728-cfcc22d823c6 h1:pxlAea9eRwuAnt/zKbGqlFO2ZszpIe24YpOVLf+N+4I=
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20251018145728-cfcc22d823c6/go.mod h1:m5hu1im5Qc7LDycVLvee6MPobJiRLBYHklypFJR0/aE=
|
||||
github.com/gohugoio/hashstructure v0.6.0 h1:7wMB/2CfXoThFYhdWRGv3u3rUM761Cq29CxUW+NltUg=
|
||||
github.com/gohugoio/hashstructure v0.6.0/go.mod h1:lapVLk9XidheHG1IQ4ZSbyYrXcaILU1ZEP/+vno5rBQ=
|
||||
github.com/gohugoio/httpcache v0.8.0 h1:hNdsmGSELztetYCsPVgjA960zSa4dfEqqF/SficorCU=
|
||||
github.com/gohugoio/httpcache v0.8.0/go.mod h1:fMlPrdY/vVJhAriLZnrF5QpN3BNAcoBClgAyQd+lGFI=
|
||||
github.com/gohugoio/hugo-goldmark-extensions/extras v0.5.0 h1:dco+7YiOryRoPOMXwwaf+kktZSCtlFtreNdiJbETvYE=
|
||||
@@ -467,7 +468,6 @@ 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/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
|
||||
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
@@ -481,6 +481,10 @@ github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/sanity-io/litter v1.5.8 h1:uM/2lKrWdGbRXDrIq08Lh9XtVYoeGtcQxk9rtQ7+rYg=
|
||||
github.com/sanity-io/litter v1.5.8/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
|
||||
github.com/sebdah/goldie/v2 v2.7.1 h1:PkBHymaYdtvEkZV7TmyqKxdmn5/Vcj+8TpATWZjnG5E=
|
||||
github.com/sebdah/goldie/v2 v2.7.1/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/shogo82148/go-shuffle v0.0.0-20180218125048-27e6095f230d/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc=
|
||||
github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
@@ -509,10 +513,10 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tdewolff/minify/v2 v2.24.3 h1:BaKgWSFLKbKDiUskbeRgbe2n5d1Ci1x3cN/eXna8zOA=
|
||||
github.com/tdewolff/minify/v2 v2.24.3/go.mod h1:1JrCtoZXaDbqioQZfk3Jdmr0GPJKiU7c1Apmb+7tCeE=
|
||||
github.com/tdewolff/parse/v2 v2.8.3 h1:5VbvtJ83cfb289A1HzRA9sf02iT8YyUwN84ezjkdY1I=
|
||||
github.com/tdewolff/parse/v2 v2.8.3/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo=
|
||||
github.com/tdewolff/minify/v2 v2.24.4 h1:pQyr6eWDa+RXtAoZg+6wurh0jB9ojqw/qc5LlU7/z6c=
|
||||
github.com/tdewolff/minify/v2 v2.24.4/go.mod h1:iD9Qn7/brhKY9d0KLKMkZrqS8/bqxSxRKruBi7V6m+w=
|
||||
github.com/tdewolff/parse/v2 v2.8.4 h1:A6slgBLGGDPBMGA28KQZfHpaKffuNvhOe7zSag+x/rw=
|
||||
github.com/tdewolff/parse/v2 v2.8.4/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo=
|
||||
github.com/tdewolff/test v1.0.11 h1:FdLbwQVHxqG16SlkGveC0JVyrJN62COWTRyUFzfbtBE=
|
||||
github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
|
||||
@@ -576,8 +580,8 @@ golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -588,13 +592,11 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0=
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
|
||||
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.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.30.0 h1:jD5RhkmVAnjqaCUXfbGBrn3lpxbknfN9w2UhHHU+5B4=
|
||||
golang.org/x/image v0.30.0/go.mod h1:SAEUTxCCMWSrJcCy/4HwavEsfZZJlYxeHLc6tTiAe/c=
|
||||
golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
|
||||
golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
|
||||
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=
|
||||
@@ -621,8 +623,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -661,8 +663,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -738,8 +740,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@@ -758,8 +760,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
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=
|
||||
@@ -817,8 +819,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -936,7 +938,6 @@ gopkg.in/neurosnap/sentences.v1 v1.0.6/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3J
|
||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2025 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// 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_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
)
|
||||
|
||||
func TestMountRestrictTheme(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["taxonomy", "term", "rss"]
|
||||
theme = "mytheme"
|
||||
[[module.mounts]]
|
||||
source = '../file2.txt'
|
||||
target = 'assets/file2.txt'
|
||||
-- themes/mytheme/hugo.toml --
|
||||
[[module.mounts]]
|
||||
source = '../../file1.txt'
|
||||
target = 'assets/file1.txt'
|
||||
-- file1.txt --
|
||||
file1
|
||||
-- file2.txt --
|
||||
file2
|
||||
-- layouts/all.html --
|
||||
All.
|
||||
`
|
||||
b, err := hugolib.TestE(t, files)
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
b.Assert(err.Error(), qt.Contains, "mount source must be a local path for modules/themes")
|
||||
}
|
||||
@@ -172,3 +172,26 @@ func TestTargetPathHTMLRedirectAlias(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAliasNIssue14053(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "http://example.com"
|
||||
-- layouts/all.html --
|
||||
All.
|
||||
-- content/page.md --
|
||||
---
|
||||
title: "Page"
|
||||
aliases:
|
||||
- n
|
||||
- y
|
||||
- no
|
||||
- yes
|
||||
---
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertPublishDir("n/index.html", "yes/index.html", "no/index.html", "yes/index.html")
|
||||
}
|
||||
|
||||
@@ -1570,3 +1570,82 @@ title: "P1 us"
|
||||
`
|
||||
Test(t, files)
|
||||
}
|
||||
|
||||
func TestConfigYAMLAnchorsMerge(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.yaml --
|
||||
definitions:
|
||||
params: ¶ms
|
||||
p1: p1alias
|
||||
|
||||
theme: "mytheme"
|
||||
defaultContentLanguage: en
|
||||
defaultContentLanguageInSubdir: true
|
||||
|
||||
params:
|
||||
<<: *params
|
||||
|
||||
languages:
|
||||
en:
|
||||
weight: 1
|
||||
no:
|
||||
weight: 2
|
||||
params:
|
||||
<<: *params
|
||||
p2: p2no
|
||||
sv:
|
||||
weight: 3
|
||||
params: *params
|
||||
|
||||
-- layouts/all.html --
|
||||
Params: {{ site.Params }}|
|
||||
-- themes/mytheme/hugo.yaml --
|
||||
definitions:
|
||||
params: ¶ms
|
||||
p1: p1aliastheme
|
||||
p2: p2aliastheme
|
||||
|
||||
params: *params
|
||||
|
||||
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/en/index.html", "Params: map[p1:p1alias p2:p2aliastheme]|")
|
||||
b.AssertFileContent("public/no/index.html", "Params: map[p1:p1alias p2:p2no]|")
|
||||
b.AssertFileContent("public/sv/index.html", "Params: map[p1:p1alias p2:p2aliastheme]|")
|
||||
}
|
||||
|
||||
func TestConfigYAMLAnchorsCyclicReference(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.yaml --
|
||||
definitions:
|
||||
params: ¶ms
|
||||
p1: p1alias
|
||||
|
||||
params:
|
||||
p3: *params
|
||||
|
||||
languages:
|
||||
en:
|
||||
weight: 1
|
||||
sv:
|
||||
weight: 2
|
||||
params: *params
|
||||
|
||||
-- layouts/all.html --
|
||||
Params: {{ site.Params }}|
|
||||
|
||||
`
|
||||
|
||||
for range 4 {
|
||||
b := Test(t, files)
|
||||
b.AssertFileContent("public/index.html", "Params: map[p3:map[p1:p1alias]]|")
|
||||
b.AssertFileContent("public/sv/index.html", "Params: map[p1:p1alias p3:map[p1:p1alias]]|")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ Strings: {{ printf "%T" .Params.strings }} {{ range .Params.strings }}Strings: {
|
||||
|
||||
b.Build()
|
||||
|
||||
b.AssertFileContent("public/post/one/index.html", "Ints: []interface {} Int: 1 (int)|Int: 2 (int)|Int: 3 (int)|")
|
||||
b.AssertFileContent("public/post/one/index.html", "Mixed: []interface {} Mixed: 1 (string)|Mixed: 2 (int)|Mixed: 3 (int)|")
|
||||
b.AssertFileContent("public/post/one/index.html", "Ints: []interface {} Int: 1 (uint64)|Int: 2 (uint64)|Int: 3 (uint64)|")
|
||||
b.AssertFileContent("public/post/one/index.html", "Mixed: []interface {} Mixed: 1 (string)|Mixed: 2 (uint64)|Mixed: 3 (uint64)|")
|
||||
b.AssertFileContent("public/post/one/index.html", "Strings: []string Strings: 1 (string)|Strings: 2 (string)|Strings: 3 (string)|")
|
||||
}
|
||||
|
||||
@@ -476,7 +476,7 @@ line 5
|
||||
errors := herrors.UnwrapFileErrorsWithErrorContext(err)
|
||||
|
||||
b.Assert(errors, qt.HasLen, 3)
|
||||
b.Assert(errors[0].Error(), qt.Contains, filepath.FromSlash(`"/content/_index.md:1:1": "/layouts/_default/_markup/render-heading.html:2:5": execute of template failed`))
|
||||
b.Assert(errors[0].Error(), qt.Contains, filepath.FromSlash(`"/content/_index.md:2:5": "/layouts/_default/_markup/render-heading.html:2:5": execute of template failed`))
|
||||
}
|
||||
|
||||
func TestErrorRenderHookCodeblock(t *testing.T) {
|
||||
@@ -642,3 +642,35 @@ Home.
|
||||
b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`/layouts/index.html:2:3`))
|
||||
b.Assert(err.Error(), qt.Contains, `can't evaluate field ThisDoesNotExist`)
|
||||
}
|
||||
|
||||
func TestErrorFrontmatterYAMLSyntax(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
-- content/_index.md --
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
line1: 'value1'
|
||||
x
|
||||
line2: 'value2'
|
||||
line3: 'value3'
|
||||
---
|
||||
`
|
||||
|
||||
b, err := TestE(t, files)
|
||||
|
||||
b.Assert(err, qt.Not(qt.IsNil))
|
||||
b.Assert(err.Error(), qt.Contains, "[2:1] non-map value is specified")
|
||||
fe := herrors.UnwrapFileError(err)
|
||||
b.Assert(fe, qt.Not(qt.IsNil))
|
||||
pos := fe.Position()
|
||||
b.Assert(pos.Filename, qt.Contains, filepath.FromSlash("content/_index.md"))
|
||||
b.Assert(fe.ErrorContext(), qt.Not(qt.IsNil))
|
||||
b.Assert(pos.LineNumber, qt.Equals, 8)
|
||||
b.Assert(pos.ColumnNumber, qt.Equals, 1)
|
||||
}
|
||||
|
||||
+13
-16
@@ -285,23 +285,20 @@ func (c *contentParseInfo) parseFrontMatter(it pageparser.Item, iter *pageparser
|
||||
var err error
|
||||
c.frontMatter, err = metadecoders.Default.UnmarshalToMap(it.Val(source), f)
|
||||
if err != nil {
|
||||
if fe, ok := err.(herrors.FileError); ok {
|
||||
pos := fe.Position()
|
||||
|
||||
// Offset the starting position of front matter.
|
||||
offset := iter.LineNumber(source) - 1
|
||||
if f == metadecoders.YAML {
|
||||
offset -= 1
|
||||
}
|
||||
pos.LineNumber += offset
|
||||
|
||||
fe.UpdatePosition(pos)
|
||||
fe.SetFilename("") // It will be set later.
|
||||
|
||||
return fe
|
||||
} else {
|
||||
return err
|
||||
fe := herrors.UnwrapFileError(err)
|
||||
if fe == nil {
|
||||
fe = herrors.NewFileError(err)
|
||||
}
|
||||
pos := fe.Position()
|
||||
|
||||
// Offset the starting position of front matter.
|
||||
offset := iter.LineNumber(source) - 1
|
||||
|
||||
pos.LineNumber += offset
|
||||
|
||||
fe.UpdatePosition(pos)
|
||||
fe.SetFilename("") // It will be set later.
|
||||
return fe
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -21,11 +21,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
xmaps "maps"
|
||||
|
||||
"github.com/bep/logg"
|
||||
"github.com/gobuffalo/flect"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
"github.com/gohugoio/hugo/markup/converter"
|
||||
xmaps "golang.org/x/exp/maps"
|
||||
|
||||
"github.com/gohugoio/hugo/source"
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ func (c *pagesCollector) Collect() (collectErr error) {
|
||||
Handle: func(ctx context.Context, fi hugofs.FileMetaInfo) error {
|
||||
numPages, numResources, err := c.m.AddFi(fi, c.buildConfig)
|
||||
if err != nil {
|
||||
return hugofs.AddFileInfoToError(err, fi, c.fs)
|
||||
return hugofs.AddFileInfoToError(err, fi, c.h.SourceFs)
|
||||
}
|
||||
numFilesProcessedTotal.Add(1)
|
||||
numPagesProcessedTotal.Add(numPages)
|
||||
|
||||
+4
-2
@@ -23,6 +23,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -48,9 +49,10 @@ import (
|
||||
"github.com/gohugoio/hugo/modules"
|
||||
"github.com/gohugoio/hugo/resources"
|
||||
|
||||
xmaps "maps"
|
||||
|
||||
"github.com/gohugoio/hugo/tpl/tplimpl"
|
||||
"github.com/gohugoio/hugo/tpl/tplimplinit"
|
||||
xmaps "golang.org/x/exp/maps"
|
||||
|
||||
// Loads the template funcs namespaces.
|
||||
|
||||
@@ -999,7 +1001,7 @@ func (w *WhatChanged) Changes() []identity.Identity {
|
||||
if w == nil || w.ids == nil {
|
||||
return nil
|
||||
}
|
||||
return xmaps.Keys(w.ids)
|
||||
return slices.Collect(xmaps.Keys(w.ids))
|
||||
}
|
||||
|
||||
func (w *WhatChanged) Drain() []identity.Identity {
|
||||
|
||||
+4
-2
@@ -1,7 +1,9 @@
|
||||
# Release env.
|
||||
# These will be replaced by script before release.
|
||||
HUGORELEASER_TAG=v0.151.0
|
||||
HUGORELEASER_COMMITISH=c70ab27ceb841fc9404eab5d2c985ff7595034b7
|
||||
HUGORELEASER_TAG=v0.151.2
|
||||
HUGORELEASER_COMMITISH=a3574f6f70c9b272e3de53cf976018ee3ff16fd1
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ package i18n_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
)
|
||||
|
||||
@@ -126,3 +127,41 @@ title: home_es
|
||||
b.AssertFileContent("public/es/index.html", `home_es_gato`)
|
||||
b.AssertFileContent("public/fr/index.html", `home_fr_gato`)
|
||||
}
|
||||
|
||||
// See issue #14061
|
||||
func TestI18nReservedKeyMap(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- config.toml --
|
||||
-- i18n/en.toml --
|
||||
[description]
|
||||
other = 'This is a description from i18n.'
|
||||
-- layouts/all.html --
|
||||
description: {{ T "description" }}|
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/index.html", `description: This is a description from i18n.|`)
|
||||
}
|
||||
|
||||
// See issue #14061
|
||||
func TestI18nReservedKeyScalar(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','page','rss','section','sitemap','taxonomy','term']
|
||||
-- i18n/en.toml --
|
||||
a = 'a translated'
|
||||
description = 'description translated'
|
||||
b = 'b translated'
|
||||
|
||||
`
|
||||
|
||||
b, err := hugolib.TestE(t, files)
|
||||
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
b.Assert(err.Error(), qt.Contains, "failed to load translations: reserved keys [description] mixed with unreserved keys [a b]: see the lang.Translate documentation for a list of reserved keys")
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/parser/metadecoders"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"golang.org/x/text/language"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/gohugoio/go-i18n/v2/i18n"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
@@ -53,8 +53,8 @@ func (tp *TranslationProvider) NewResource(dst *deps.Deps) error {
|
||||
bundle := i18n.NewBundle(defaultLangTag)
|
||||
|
||||
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
|
||||
bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal)
|
||||
bundle.RegisterUnmarshalFunc("yml", yaml.Unmarshal)
|
||||
bundle.RegisterUnmarshalFunc("yaml", metadecoders.UnmarshalYaml)
|
||||
bundle.RegisterUnmarshalFunc("yml", metadecoders.UnmarshalYaml)
|
||||
bundle.RegisterUnmarshalFunc("json", json.Unmarshal)
|
||||
|
||||
w := hugofs.NewWalkway(
|
||||
@@ -114,7 +114,11 @@ func addTranslationFile(bundle *i18n.Bundle, r *source.File) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errWithFileContext(fmt.Errorf("failed to load translations: %w", err), r)
|
||||
var guidance string
|
||||
if strings.Contains(err.Error(), "mixed with unreserved keys") {
|
||||
guidance = ": see the lang.Translate documentation for a list of reserved keys"
|
||||
}
|
||||
return errWithFileContext(fmt.Errorf("failed to load translations: %w%s", err, guidance), r)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -69,7 +69,7 @@ func (r *htmlRenderer) renderBlockquote(w util.BufWriter, src []byte, node ast.N
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
text := strings.TrimSpace(ctx.PopRenderedString())
|
||||
text := ctx.PopRenderedString()
|
||||
|
||||
ordinal := ctx.GetAndIncrementOrdinal(ast.KindBlockquote)
|
||||
|
||||
@@ -90,7 +90,7 @@ func (r *htmlRenderer) renderBlockquote(w util.BufWriter, src []byte, node ast.N
|
||||
// tag if the first line of the blockquote content does not have a
|
||||
// closing p tag. At some point we might want to move this to the
|
||||
// parser.
|
||||
before, after, found := strings.Cut(text, "\n")
|
||||
before, after, found := strings.Cut(strings.TrimSpace(text), "\n")
|
||||
if found {
|
||||
if strings.HasSuffix(before, "</p>") {
|
||||
text = after
|
||||
@@ -173,7 +173,7 @@ func (c *blockquoteContext) Text() hstring.HTML {
|
||||
var blockQuoteAlertRe = regexp.MustCompile(`^<p>\[!([a-zA-Z]+)\](-|\+)?[^\S\r\n]?([^\n]*)\n?`)
|
||||
|
||||
func resolveBlockQuoteAlert(s string) blockQuoteAlert {
|
||||
m := blockQuoteAlertRe.FindStringSubmatch(s)
|
||||
m := blockQuoteAlertRe.FindStringSubmatch(strings.TrimSpace(s))
|
||||
if len(m) == 4 {
|
||||
title := strings.TrimSpace(m[3])
|
||||
title = strings.TrimSuffix(title, "</p>")
|
||||
|
||||
@@ -15,6 +15,7 @@ package blockquotes_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
@@ -76,7 +77,7 @@ title: "p1"
|
||||
"Blockquote Alert: |<p>This is a note with some whitespace after the alert type.</p>|alert|",
|
||||
"Blockquote Alert: |<p>This is a tip.</p>",
|
||||
"Blockquote Alert: |<p>This is a caution with some whitespace before the alert type.</p>|alert|",
|
||||
"Blockquote: |<p>A regular blockquote.</p>|regular|",
|
||||
"Blockquote: |<p>A regular blockquote.</p>\n|regular|",
|
||||
"Blockquote Alert Attributes: |<p>This is a tip with attributes.</p>|map[class:foo bar id:baz]|",
|
||||
filepath.FromSlash("/content/p1.md:19:3"),
|
||||
"Blockquote Alert Page: |<p>This is a tip with attributes.</p>|p1|p1|",
|
||||
@@ -248,6 +249,37 @@ title: home
|
||||
"AlertType: fourteen|AlertTitle: title|Text: <p><img src=\"a.jpg\" alt=\"alt\"></p>|",
|
||||
"AlertType: fifteen|AlertTitle: <em>title</em>|Text: |",
|
||||
"AlertType: sixteen|AlertTitle: <em>title</em>|Text: <p>line one</p>|",
|
||||
"AlertType: |AlertTitle: |Text: <p>seventeen</p>|",
|
||||
"AlertType: |AlertTitle: |Text: <p>seventeen</p>\n|",
|
||||
)
|
||||
}
|
||||
|
||||
// Issue14046
|
||||
func TestBlockquoteDefaultOutput(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
|
||||
-- content/p1.md --
|
||||
---
|
||||
title: p1
|
||||
---
|
||||
> foo
|
||||
-- layouts/page.html --
|
||||
|{{ .Content }}|
|
||||
-- xxx --
|
||||
<blockquote>
|
||||
{{ .Text }}
|
||||
</blockquote>
|
||||
`
|
||||
|
||||
want := "|<blockquote>\n<p>foo</p>\n</blockquote>\n|"
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
b.AssertFileContent("public/p1/index.html", want) // fail
|
||||
|
||||
files = strings.ReplaceAll(files, "xxx", "layouts/_markup/render-blockquote.html")
|
||||
|
||||
b = hugolib.Test(t, files)
|
||||
b.AssertFileContent("public/p1/index.html", want)
|
||||
}
|
||||
|
||||
+4
-1
@@ -710,7 +710,10 @@ func (c *collector) normalizeMounts(owner *moduleAdapter, mounts []Mount) ([]Mou
|
||||
mnt.Target = filepath.Clean(mnt.Target)
|
||||
var sourceDir string
|
||||
|
||||
if owner.projectMod && filepath.IsAbs(mnt.Source) {
|
||||
if !owner.projectMod && !filepath.IsLocal(mnt.Source) {
|
||||
return nil, fmt.Errorf("%s: %q: mount source must be a local path for modules/themes", errMsg, mnt.Source)
|
||||
}
|
||||
if filepath.IsAbs(mnt.Source) {
|
||||
// Abs paths in the main project is allowed.
|
||||
sourceDir = mnt.Source
|
||||
} else {
|
||||
|
||||
+1
-1
@@ -16,6 +16,7 @@ package navigation
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"slices"
|
||||
"sort"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
@@ -25,7 +26,6 @@ import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
"slices"
|
||||
)
|
||||
|
||||
var smc = newMenuCache()
|
||||
|
||||
@@ -22,8 +22,6 @@ import (
|
||||
|
||||
toml "github.com/pelletier/go-toml/v2"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
xml "github.com/clbanning/mxj/v2"
|
||||
)
|
||||
|
||||
@@ -39,7 +37,7 @@ func InterfaceToConfig(in any, format metadecoders.Format, w io.Writer) error {
|
||||
|
||||
switch format {
|
||||
case metadecoders.YAML:
|
||||
b, err := yaml.Marshal(in)
|
||||
b, err := metadecoders.MarshalYAML(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+115
-78
@@ -1,4 +1,4 @@
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2025 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -22,16 +22,17 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/niklasfasching/go-org/org"
|
||||
|
||||
xml "github.com/clbanning/mxj/v2"
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
toml "github.com/pelletier/go-toml/v2"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cast"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Decoder provides some configuration options for the decoders.
|
||||
@@ -153,6 +154,117 @@ func (d Decoder) Unmarshal(data []byte, f Format) (any, error) {
|
||||
return v, err
|
||||
}
|
||||
|
||||
// UnmarshalYaml unmarshals data in YAML format into v.
|
||||
func UnmarshalYaml(data []byte, v any) error {
|
||||
if err := yaml.Unmarshal(data, v); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateAliasLimitForCollections(v, calculateCollectionAliasLimit(len(data))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// The Billion Laughs YAML example is about 500 bytes in size,
|
||||
// but even halving that when converted to JSON would produce a file of about 4 MB in size,
|
||||
// which, when repeated enough times, could be disruptive.
|
||||
// For large data files where every row shares a common map via aliases,
|
||||
// a large number of aliases could make sense.
|
||||
// The primary goal here is to catch the small but malicious files.
|
||||
func calculateCollectionAliasLimit(sizeInBytes int) int {
|
||||
sizeInKB := sizeInBytes / 1024
|
||||
if sizeInKB == 0 {
|
||||
sizeInKB = 1
|
||||
}
|
||||
if sizeInKB < 2 {
|
||||
// This should allow at most "thousand laughs",
|
||||
// which should be plenty of room for legitimate uses.
|
||||
return 100
|
||||
}
|
||||
|
||||
// The numbers below are somewhat arbitrary, but should provide
|
||||
// a reasonable trade-off between safety and usability.
|
||||
if sizeInKB < 10 {
|
||||
return 5000
|
||||
}
|
||||
return 10000
|
||||
}
|
||||
|
||||
// Used in benchmarks.
|
||||
func unmarshalYamlNoValidation(data []byte, v any) error {
|
||||
if err := yaml.Unmarshal(data, v); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// See https://github.com/goccy/go-yaml/issues/461
|
||||
// While it's true that yaml.Unmarshal isn't vulnerable to the Billion Laughs attack,
|
||||
// we can easily get a delayed laughter when we try to render this very big structure later,
|
||||
// e.g. via RenderString.
|
||||
func validateAliasLimitForCollections(v any, limit int) error {
|
||||
if limit <= 0 {
|
||||
limit = 1000
|
||||
}
|
||||
|
||||
collectionRefCounts := make(map[uintptr]int)
|
||||
|
||||
checkCollectionRef := func(v *any) error {
|
||||
// Conversion of a Pointer to a uintptr (but not back to Pointer) is considered safe.
|
||||
// See https://pkg.go.dev/unsafe#pkg-functions
|
||||
ptr := uintptr(unsafe.Pointer(v))
|
||||
if ptr == 0 {
|
||||
return nil
|
||||
}
|
||||
collectionRefCounts[ptr]++
|
||||
if collectionRefCounts[ptr] > limit {
|
||||
return fmt.Errorf("too many YAML aliases for non-scalar nodes")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var validate func(v any) error
|
||||
validate = func(v any) error {
|
||||
switch vv := v.(type) {
|
||||
case *map[string]any:
|
||||
if err := checkCollectionRef(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, vvv := range *vv {
|
||||
if err := validate(vvv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case map[string]any:
|
||||
if err := checkCollectionRef(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, vvv := range vv {
|
||||
if err := validate(vvv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case []any:
|
||||
if err := checkCollectionRef(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, vvv := range vv {
|
||||
if err := validate(vvv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case *any:
|
||||
return validate(*vv)
|
||||
default:
|
||||
// ok
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return validate(v)
|
||||
}
|
||||
|
||||
// UnmarshalTo unmarshals data in format f into v.
|
||||
func (d Decoder) UnmarshalTo(data []byte, f Format, v any) error {
|
||||
var err error
|
||||
@@ -196,35 +308,7 @@ func (d Decoder) UnmarshalTo(data []byte, f Format, v any) error {
|
||||
case TOML:
|
||||
err = toml.Unmarshal(data, v)
|
||||
case YAML:
|
||||
err = yaml.Unmarshal(data, v)
|
||||
if err != nil {
|
||||
return toFileError(f, data, fmt.Errorf("failed to unmarshal YAML: %w", err))
|
||||
}
|
||||
|
||||
// To support boolean keys, the YAML package unmarshals maps to
|
||||
// map[interface{}]interface{}. Here we recurse through the result
|
||||
// and change all maps to map[string]interface{} like we would've
|
||||
// gotten from `json`.
|
||||
var ptr any
|
||||
switch vv := v.(type) {
|
||||
case *map[string]any:
|
||||
ptr = *vv
|
||||
case *any:
|
||||
ptr = *vv
|
||||
default:
|
||||
// Not a map.
|
||||
}
|
||||
|
||||
if ptr != nil {
|
||||
if mm, changed := stringifyMapKeys(ptr); changed {
|
||||
switch vv := v.(type) {
|
||||
case *map[string]any:
|
||||
*vv = mm.(map[string]any)
|
||||
case *any:
|
||||
*vv = mm
|
||||
}
|
||||
}
|
||||
}
|
||||
return UnmarshalYaml(data, v)
|
||||
case CSV:
|
||||
return d.unmarshalCSV(data, v)
|
||||
|
||||
@@ -331,50 +415,3 @@ func (d Decoder) unmarshalORG(data []byte, v any) error {
|
||||
func toFileError(f Format, data []byte, err error) error {
|
||||
return herrors.NewFileErrorFromName(err, fmt.Sprintf("_stream.%s", f)).UpdateContent(bytes.NewReader(data), nil)
|
||||
}
|
||||
|
||||
// stringifyMapKeys recurses into in and changes all instances of
|
||||
// map[interface{}]interface{} to map[string]interface{}. This is useful to
|
||||
// work around the impedance mismatch between JSON and YAML unmarshaling that's
|
||||
// described here: https://github.com/go-yaml/yaml/issues/139
|
||||
//
|
||||
// Inspired by https://github.com/stripe/stripe-mock, MIT licensed
|
||||
func stringifyMapKeys(in any) (any, bool) {
|
||||
switch in := in.(type) {
|
||||
case []any:
|
||||
for i, v := range in {
|
||||
if vv, replaced := stringifyMapKeys(v); replaced {
|
||||
in[i] = vv
|
||||
}
|
||||
}
|
||||
case map[string]any:
|
||||
for k, v := range in {
|
||||
if vv, changed := stringifyMapKeys(v); changed {
|
||||
in[k] = vv
|
||||
}
|
||||
}
|
||||
case map[any]any:
|
||||
res := make(map[string]any)
|
||||
var (
|
||||
ok bool
|
||||
err error
|
||||
)
|
||||
for k, v := range in {
|
||||
var ks string
|
||||
|
||||
if ks, ok = k.(string); !ok {
|
||||
ks, err = cast.ToStringE(k)
|
||||
if err != nil {
|
||||
ks = fmt.Sprintf("%v", k)
|
||||
}
|
||||
}
|
||||
if vv, replaced := stringifyMapKeys(v); replaced {
|
||||
res[ks] = vv
|
||||
} else {
|
||||
res[ks] = v
|
||||
}
|
||||
}
|
||||
return res, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
package metadecoders
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
@@ -91,8 +90,8 @@ func TestUnmarshalToMap(t *testing.T) {
|
||||
{`a = "b"`, TOML, expect},
|
||||
{`a: "b"`, YAML, expect},
|
||||
// Make sure we get all string keys, even for YAML
|
||||
{"a: Easy!\nb:\n c: 2\n d: [3, 4]", YAML, map[string]any{"a": "Easy!", "b": map[string]any{"c": 2, "d": []any{3, 4}}}},
|
||||
{"a:\n true: 1\n false: 2", YAML, map[string]any{"a": map[string]any{"true": 1, "false": 2}}},
|
||||
{"a: Easy!\nb:\n c: 2\n d: [3, 4]", YAML, map[string]any{"a": "Easy!", "b": map[string]any{"c": uint64(2), "d": []any{uint64(3), uint64(4)}}}},
|
||||
{"a:\n true: 1\n false: 2", YAML, map[string]any{"a": map[string]any{"true": uint64(1), "false": uint64(2)}}},
|
||||
{`{ "a": "b" }`, JSON, expect},
|
||||
{`<root><a>b</a></root>`, XML, expect},
|
||||
{`#+a: b`, ORG, expect},
|
||||
@@ -140,7 +139,7 @@ func TestUnmarshalToInterface(t *testing.T) {
|
||||
{[]byte(`a: "b"`), YAML, expect},
|
||||
{[]byte(`<root><a>b</a></root>`), XML, expect},
|
||||
{[]byte(`a,b,c`), CSV, [][]string{{"a", "b", "c"}}},
|
||||
{[]byte("a: Easy!\nb:\n c: 2\n d: [3, 4]"), YAML, map[string]any{"a": "Easy!", "b": map[string]any{"c": 2, "d": []any{3, 4}}}},
|
||||
{[]byte("a: Easy!\nb:\n c: 2\n d: [3, 4]"), YAML, map[string]any{"a": "Easy!", "b": map[string]any{"c": uint64(2), "d": []any{uint64(3), uint64(4)}}}},
|
||||
// errors
|
||||
{[]byte(`a = "`), TOML, false},
|
||||
} {
|
||||
@@ -173,7 +172,7 @@ func TestUnmarshalStringTo(t *testing.T) {
|
||||
{"32", int64(1234), int64(32)},
|
||||
{"32", int(1234), int(32)},
|
||||
{"3.14159", float64(1), float64(3.14159)},
|
||||
{"[3,7,9]", []any{}, []any{3, 7, 9}},
|
||||
{"[3,7,9]", []any{}, []any{uint64(3), uint64(7), uint64(9)}},
|
||||
{"[3.1,7.2,9.3]", []any{}, []any{3.1, 7.2, 9.3}},
|
||||
} {
|
||||
msg := qt.Commentf("%d: %T", i, test.to)
|
||||
@@ -188,126 +187,18 @@ func TestUnmarshalStringTo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringifyYAMLMapKeys(t *testing.T) {
|
||||
cases := []struct {
|
||||
input any
|
||||
want any
|
||||
replaced bool
|
||||
}{
|
||||
{
|
||||
map[any]any{"a": 1, "b": 2},
|
||||
map[string]any{"a": 1, "b": 2},
|
||||
true,
|
||||
},
|
||||
{
|
||||
map[any]any{"a": []any{1, map[any]any{"b": 2}}},
|
||||
map[string]any{"a": []any{1, map[string]any{"b": 2}}},
|
||||
true,
|
||||
},
|
||||
{
|
||||
map[any]any{true: 1, "b": false},
|
||||
map[string]any{"true": 1, "b": false},
|
||||
true,
|
||||
},
|
||||
{
|
||||
map[any]any{1: "a", 2: "b"},
|
||||
map[string]any{"1": "a", "2": "b"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
map[any]any{"a": map[any]any{"b": 1}},
|
||||
map[string]any{"a": map[string]any{"b": 1}},
|
||||
true,
|
||||
},
|
||||
{
|
||||
map[string]any{"a": map[string]any{"b": 1}},
|
||||
map[string]any{"a": map[string]any{"b": 1}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
[]any{map[any]any{1: "a", 2: "b"}},
|
||||
[]any{map[string]any{"1": "a", "2": "b"}},
|
||||
false,
|
||||
},
|
||||
}
|
||||
func TestCalculateAliasLimit(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
for i, c := range cases {
|
||||
res, replaced := stringifyMapKeys(c.input)
|
||||
const kb = 1024
|
||||
|
||||
if c.replaced != replaced {
|
||||
t.Fatalf("[%d] Replaced mismatch: %t", i, replaced)
|
||||
}
|
||||
if !c.replaced {
|
||||
res = c.input
|
||||
}
|
||||
if !reflect.DeepEqual(res, c.want) {
|
||||
t.Errorf("[%d] given %q\nwant: %q\n got: %q", i, c.input, c.want, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStringifyMapKeysStringsOnlyInterfaceMaps(b *testing.B) {
|
||||
maps := make([]map[any]any, b.N)
|
||||
for i := 0; i < b.N; i++ {
|
||||
maps[i] = map[any]any{
|
||||
"a": map[any]any{
|
||||
"b": 32,
|
||||
"c": 43,
|
||||
"d": map[any]any{
|
||||
"b": 32,
|
||||
"c": 43,
|
||||
},
|
||||
},
|
||||
"b": []any{"a", "b"},
|
||||
"c": "d",
|
||||
}
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
stringifyMapKeys(maps[i])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStringifyMapKeysStringsOnlyStringMaps(b *testing.B) {
|
||||
m := map[string]any{
|
||||
"a": map[string]any{
|
||||
"b": 32,
|
||||
"c": 43,
|
||||
"d": map[string]any{
|
||||
"b": 32,
|
||||
"c": 43,
|
||||
},
|
||||
},
|
||||
"b": []any{"a", "b"},
|
||||
"c": "d",
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
stringifyMapKeys(m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStringifyMapKeysIntegers(b *testing.B) {
|
||||
maps := make([]map[any]any, b.N)
|
||||
for i := 0; i < b.N; i++ {
|
||||
maps[i] = map[any]any{
|
||||
1: map[any]any{
|
||||
4: 32,
|
||||
5: 43,
|
||||
6: map[any]any{
|
||||
7: 32,
|
||||
8: 43,
|
||||
},
|
||||
},
|
||||
2: []any{"a", "b"},
|
||||
3: "d",
|
||||
}
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
stringifyMapKeys(maps[i])
|
||||
}
|
||||
c.Assert(calculateCollectionAliasLimit(0), qt.Equals, 100)
|
||||
c.Assert(calculateCollectionAliasLimit(500), qt.Equals, 100)
|
||||
c.Assert(calculateCollectionAliasLimit(1*kb), qt.Equals, 100)
|
||||
c.Assert(calculateCollectionAliasLimit(2*kb), qt.Equals, 5000)
|
||||
c.Assert(calculateCollectionAliasLimit(8*kb), qt.Equals, 5000)
|
||||
c.Assert(calculateCollectionAliasLimit(12*kb), qt.Equals, 10000)
|
||||
c.Assert(calculateCollectionAliasLimit(10000*kb), qt.Equals, 10000)
|
||||
}
|
||||
|
||||
func BenchmarkDecodeYAMLToMap(b *testing.B) {
|
||||
@@ -332,3 +223,61 @@ c: "d"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalBillionLaughs(b *testing.B) {
|
||||
yamlBillionLaughs := []byte(`
|
||||
a: &a [_, _, _, _, _, _, _, _, _, _, _, _, _, _, _]
|
||||
b: &b [*a, *a, *a, *a, *a, *a, *a, *a, *a, *a]
|
||||
c: &c [*b, *b, *b, *b, *b, *b, *b, *b, *b, *b]
|
||||
d: &d [*c, *c, *c, *c, *c, *c, *c, *c, *c, *c]
|
||||
e: &e [*d, *d, *d, *d, *d, *d, *d, *d, *d, *d]
|
||||
f: &f [*e, *e, *e, *e, *e, *e, *e, *e, *e, *e]
|
||||
g: &g [*f, *f, *f, *f, *f, *f, *f, *f, *f, *f]
|
||||
h: &h [*g, *g, *g, *g, *g, *g, *g, *g, *g, *g]
|
||||
i: &i [*h, *h, *h, *h, *h, *h, *h, *h, *h, *h]
|
||||
`)
|
||||
|
||||
yamlFrontMatter := []byte(`
|
||||
title: mysect
|
||||
tags: [tag1, tag2]
|
||||
params:
|
||||
color: blue
|
||||
`)
|
||||
|
||||
yamlTests := []struct {
|
||||
Title string
|
||||
Content []byte
|
||||
IsExpectedToFailValidation bool
|
||||
}{
|
||||
{"Billion Laughs", yamlBillionLaughs, true},
|
||||
{"YAML Front Matter", yamlFrontMatter, false},
|
||||
}
|
||||
|
||||
for _, tt := range yamlTests {
|
||||
|
||||
b.Run(tt.Title+" no validation", func(b *testing.B) {
|
||||
for range b.N {
|
||||
var v any
|
||||
if err := unmarshalYamlNoValidation(tt.Content, &v); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run(tt.Title+" with validation", func(b *testing.B) {
|
||||
for range b.N {
|
||||
var v any
|
||||
err := UnmarshalYaml(tt.Content, &v)
|
||||
if tt.IsExpectedToFailValidation {
|
||||
if err == nil {
|
||||
b.Fatal("expected to fail validation but did not")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2025 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// 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 metadecoders
|
||||
|
||||
import yaml "github.com/goccy/go-yaml"
|
||||
|
||||
var yamlEncodeOptions = []yaml.EncodeOption{
|
||||
yaml.UseSingleQuote(true),
|
||||
// This prevents excessively large objects, see https://github.com/goccy/go-yaml/issues/461
|
||||
yaml.WithSmartAnchor(),
|
||||
}
|
||||
|
||||
// MarshalYAML marshals the given value to YAML.
|
||||
var MarshalYAML = func(v any) ([]byte, error) {
|
||||
return yaml.MarshalWithOptions(v, yamlEncodeOptions...)
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
"unique"
|
||||
)
|
||||
|
||||
const eof = -1
|
||||
@@ -78,7 +79,7 @@ func newPageLexer(input []byte, stateStart stateFunc, cfg Config) *pageLexer {
|
||||
lexerShortcodeState: lexerShortcodeState{
|
||||
currLeftDelimItem: tLeftDelimScNoMarkup,
|
||||
currRightDelimItem: tRightDelimScNoMarkup,
|
||||
openShortcodes: make(map[string]bool),
|
||||
openShortcodes: make(map[unique.Handle[string]]bool),
|
||||
},
|
||||
items: make([]Item, 0, 5),
|
||||
}
|
||||
|
||||
@@ -13,15 +13,17 @@
|
||||
|
||||
package pageparser
|
||||
|
||||
import "unique"
|
||||
|
||||
type lexerShortcodeState struct {
|
||||
currLeftDelimItem ItemType
|
||||
currRightDelimItem ItemType
|
||||
isInline bool
|
||||
currShortcodeName string // is only set when a shortcode is in opened state
|
||||
closingState int // > 0 = on its way to be closed
|
||||
elementStepNum int // step number in element
|
||||
paramElements int // number of elements (name + value = 2) found first
|
||||
openShortcodes map[string]bool // set of shortcodes in open state
|
||||
currShortcodeName string // is only set when a shortcode is in opened state
|
||||
closingState int // > 0 = on its way to be closed
|
||||
elementStepNum int // step number in element
|
||||
paramElements int // number of elements (name + value = 2) found first
|
||||
openShortcodes map[unique.Handle[string]]bool // set of shortcodes in open state
|
||||
}
|
||||
|
||||
// Shortcode syntax
|
||||
@@ -254,16 +256,17 @@ Loop:
|
||||
}
|
||||
default:
|
||||
l.backup()
|
||||
word := string(l.input[l.start:l.pos])
|
||||
if l.closingState > 0 && !l.openShortcodes[word] {
|
||||
return l.errorf("closing tag for shortcode '%s' does not match start tag", word)
|
||||
} else if l.closingState > 0 {
|
||||
word := unique.Make(string(l.input[l.start:l.pos]))
|
||||
if l.closingState > 0 {
|
||||
if !l.openShortcodes[word] {
|
||||
return l.errorf("closing tag for shortcode '%s' does not match start tag", word.Value())
|
||||
}
|
||||
l.openShortcodes[word] = false
|
||||
lookForEnd = true
|
||||
}
|
||||
|
||||
l.closingState = 0
|
||||
l.currShortcodeName = word
|
||||
l.currShortcodeName = word.Value()
|
||||
l.openShortcodes[word] = true
|
||||
l.elementStepNum++
|
||||
if l.isInline {
|
||||
|
||||
@@ -142,6 +142,12 @@ var shortCodeLexerTests = []lexerTest{
|
||||
tstLeftNoMD, tstSC2, tstRightNoMD,
|
||||
tstLeftNoMD, tstSCClose, tstSC1, tstRightNoMD, tstEOF,
|
||||
}, nil},
|
||||
{"nested same", `{{< sc1 >}}{{< sc1 >}}{{< /sc1 >}}{{< /sc1 >}}`, []typeText{
|
||||
tstLeftNoMD, tstSC1, tstRightNoMD,
|
||||
tstLeftNoMD, tstSC1, tstRightNoMD,
|
||||
tstLeftNoMD, tstSCClose, tstSC1, tstRightNoMD,
|
||||
tstLeftNoMD, tstSCClose, tstSC1, tstRightNoMD, tstEOF,
|
||||
}, nil},
|
||||
{"nested complex", `{{< sc1 >}}ab{{% sc2 param1 %}}cd{{< sc3 >}}ef{{< /sc3 >}}gh{{% /sc2 %}}ij{{< /sc1 >}}kl`, []typeText{
|
||||
tstLeftNoMD, tstSC1, tstRightNoMD,
|
||||
nti(tText, "ab"),
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
xmaps "golang.org/x/exp/maps"
|
||||
xmaps "maps"
|
||||
|
||||
"github.com/gohugoio/hugo/common/collections"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
|
||||
@@ -20,7 +20,7 @@ stderr 'invalid style: __invalid_style__'
|
||||
hugo gen chromastyles --style monokai
|
||||
! stdout '{ }'
|
||||
hugo gen chromastyles --omitEmpty --style monokai --logLevel info
|
||||
stderr 'INFO deprecated: --omitEmpty was deprecated'
|
||||
stderr 'deprecated: --omitEmpty was deprecated'
|
||||
|
||||
# Disable class comments.
|
||||
hugo gen chromastyles --style monokai
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
hugo --quiet
|
||||
ls public/files
|
||||
stdout 'f1.txt'
|
||||
stdout 'f2.txt'
|
||||
|
||||
grep 'enf1' public/files/f1.txt # en mount is last.
|
||||
grep 'frf2' public/files/f2.txt
|
||||
|
||||
-- hugo.toml --
|
||||
disableKinds = ["taxonomy", "term"]
|
||||
defaultContentLanguage = "en"
|
||||
defaultContentLanguageInSubdir = true
|
||||
|
||||
[languages]
|
||||
[languages.en]
|
||||
languageName = "English"
|
||||
weight = 1
|
||||
[languages.fr]
|
||||
languageName = "Français"
|
||||
weight = 2
|
||||
[module]
|
||||
[[module.mounts]]
|
||||
source = "static/fr"
|
||||
target = "static"
|
||||
lang = "fr"
|
||||
[[module.mounts]]
|
||||
source = "static/en"
|
||||
target = "static"
|
||||
lang = "en"
|
||||
|
||||
-- static/en/files/f1.txt --
|
||||
enf1
|
||||
-- static/fr/files/f1.txt --
|
||||
frf1
|
||||
-- static/fr/files/f2.txt --
|
||||
frf2
|
||||
-- layouts/home.html --
|
||||
Home.
|
||||
|
||||
@@ -8,7 +8,7 @@ waitServer
|
||||
httpget ${HUGOTEST_BASEURL_0}p1/ 'Title: P1'
|
||||
|
||||
replace $WORK/content/p1/index.md 'title:' 'titlecolon'
|
||||
httpget ${HUGOTEST_BASEURL_0}p1/ 'failed'
|
||||
httpget ${HUGOTEST_BASEURL_0}p1/ 'Error'
|
||||
|
||||
replace $WORK/content/p1/index.md 'titlecolon' 'title:'
|
||||
httpget ${HUGOTEST_BASEURL_0}p1/ 'Title: P1'
|
||||
|
||||
@@ -5,9 +5,14 @@ hugo server --renderToMemory &
|
||||
|
||||
waitServer
|
||||
|
||||
# Assert home page.
|
||||
httpget $HUGOTEST_BASEURL_0 'Title: Hugo Server Test' $HUGOTEST_BASEURL_0
|
||||
httpget $HUGOTEST_BASEURL_1 'Title: Hugo Serveur Test' $HUGOTEST_BASEURL_1
|
||||
|
||||
# Assert static mount.
|
||||
httpget ${HUGOTEST_BASEURL_0}mystatic/mytext.txt 'en_mytext'
|
||||
httpget ${HUGOTEST_BASEURL_1}mystatic/mytext.txt 'fr_mytext'
|
||||
|
||||
stopServer
|
||||
! stderr .
|
||||
|
||||
@@ -15,6 +20,16 @@ stopServer
|
||||
title = "Hugo Server Test"
|
||||
baseURL = "https://example.org/"
|
||||
disableKinds = ["taxonomy", "term", "sitemap"]
|
||||
|
||||
[[module.mounts]]
|
||||
source = 'static/en'
|
||||
target = 'static'
|
||||
lang = 'en'
|
||||
[[module.mounts]]
|
||||
source = 'static/fr'
|
||||
target = 'static'
|
||||
lang = 'fr'
|
||||
|
||||
[languages]
|
||||
[languages.en]
|
||||
baseURL = "https://en.example.org/"
|
||||
@@ -28,5 +43,9 @@ languageName = "Français"
|
||||
weight = 2
|
||||
-- layouts/index.html --
|
||||
Title: {{ .Title }}|BaseURL: {{ site.BaseURL }}|
|
||||
-- static/en/mystatic/mytext.txt --
|
||||
en_mytext
|
||||
-- static/fr/mystatic/mytext.txt --
|
||||
fr_mytext
|
||||
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ hugo server --renderToMemory --renderStaticToDisk &
|
||||
|
||||
waitServer
|
||||
|
||||
httpget $HUGOTEST_BASEURL_0 'Title: Hugo Server Test' $HUGOTEST_BASEURL_0
|
||||
httpget ${HUGOTEST_BASEURL_0}mystatic.txt 'This is a static file.'
|
||||
httpget ${HUGOTEST_BASEURL_0}en/ 'Title: Hugo Server Test' $HUGOTEST_BASEURL_0
|
||||
httpget ${HUGOTEST_BASEURL_0}mystatic.txt 'This is a static file in English.'
|
||||
|
||||
! exists public/index.html
|
||||
exists public/mystatic.txt
|
||||
@@ -18,8 +18,32 @@ stopServer
|
||||
title = "Hugo Server Test"
|
||||
baseURL = "https://example.org/"
|
||||
disableKinds = ["taxonomy", "term", "sitemap"]
|
||||
-- static/mystatic.txt --
|
||||
This is a static file.
|
||||
defaultContentLanguage = "en"
|
||||
defaultContentLanguageInSubdir = true
|
||||
[[module.mounts]]
|
||||
source = 'static/en'
|
||||
target = 'static'
|
||||
lang = 'en'
|
||||
[[module.mounts]]
|
||||
source = 'static/fr'
|
||||
target = 'static'
|
||||
lang = 'fr'
|
||||
|
||||
|
||||
[languages]
|
||||
[languages.en]
|
||||
languageName = "English"
|
||||
title = "Hugo Server Test"
|
||||
weight = 1
|
||||
[languages.fr]
|
||||
title = "Hugo Serveur Test"
|
||||
languageName = "Français"
|
||||
weight = 2
|
||||
|
||||
-- static/en/mystatic.txt --
|
||||
This is a static file in English.
|
||||
-- static/en/mystatic.txt --
|
||||
This is a static file in English.
|
||||
-- layouts/index.html --
|
||||
Title: {{ .Title }}|BaseURL: {{ site.BaseURL }}|
|
||||
|
||||
|
||||
@@ -19,8 +19,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
gyaml "github.com/ghodss/yaml"
|
||||
|
||||
kopenapi3 "github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/gohugoio/hugo/cache/dynacache"
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
@@ -80,7 +78,7 @@ func (ns *Namespace) Unmarshal(r resource.UnmarshableResource) (*OpenAPIDocument
|
||||
s := &kopenapi3.T{}
|
||||
switch f {
|
||||
case metadecoders.YAML:
|
||||
err = gyaml.Unmarshal(b, s)
|
||||
err = metadecoders.UnmarshalYaml(b, s)
|
||||
default:
|
||||
err = metadecoders.Default.UnmarshalTo(b, f, s)
|
||||
}
|
||||
|
||||
@@ -111,7 +111,11 @@ func (ns *Namespace) Truncate(s any, options ...any) (template.HTML, error) {
|
||||
if unicode.IsSpace(r) {
|
||||
lastWordIndex = lastNonSpace
|
||||
} else if unicode.In(r, unicode.Han, unicode.Hangul, unicode.Hiragana, unicode.Katakana) {
|
||||
lastWordIndex = i
|
||||
lastWordIndex = lastNonSpace
|
||||
lastNonSpace = i + utf8.RuneLen(r)
|
||||
if currentLen <= length {
|
||||
lastWordIndex = lastNonSpace
|
||||
}
|
||||
} else {
|
||||
lastNonSpace = i + utf8.RuneLen(r)
|
||||
}
|
||||
|
||||
@@ -41,6 +41,19 @@ func TestTruncate(t *testing.T) {
|
||||
{10, template.HTML("<p>IamanextremelylongwordthatjustgoesonandonandonjusttoannoyyoualmostasifIwaswritteninGermanActuallyIbettheresagermanwordforthis</p>"), nil, template.HTML("<p>Iamanextre …</p>"), false},
|
||||
{13, template.HTML("With <a href=\"/markdown\">Markdown</a> inside."), nil, template.HTML("With <a href=\"/markdown\">Markdown …</a>"), false},
|
||||
{14, "Hello中国 Good 好的", nil, template.HTML("Hello中国 Good 好 …"), false},
|
||||
{12, "", "日本語でHugoを使います", template.HTML("日本語でHugoを使いま"), false},
|
||||
{12, "", "日本語で Hugo を使います", template.HTML("日本語で Hugo を使"), false},
|
||||
{8, "", "日本語でHugoを使います", template.HTML("日本語でHugo"), false},
|
||||
{10, "", "日本語で Hugo を使います", template.HTML("日本語で Hugo"), false},
|
||||
{9, "", "日本語で Hugo を使います", template.HTML("日本語で Hugo"), false},
|
||||
{7, "", "日本語でHugoを使います", template.HTML("日本語で"), false},
|
||||
{7, "", "日本語で Hugo を使います", template.HTML("日本語で"), false},
|
||||
{6, "", "日本語でHugoを使います", template.HTML("日本語で"), false},
|
||||
{6, "", "日本語で Hugo を使います", template.HTML("日本語で"), false},
|
||||
{5, "", "日本語でHugoを使います", template.HTML("日本語で"), false},
|
||||
{5, "", "日本語で Hugo を使います", template.HTML("日本語で"), false},
|
||||
{4, "", "日本語でHugoを使います", template.HTML("日本語で"), false},
|
||||
{4, "", "日本語で Hugo を使います", template.HTML("日本語で"), false},
|
||||
{15, "", template.HTML("A <br> tag that's not closed"), template.HTML("A <br> tag that's"), false},
|
||||
{14, template.HTML("<p>Hello中国 Good 好的</p>"), nil, template.HTML("<p>Hello中国 Good 好 …</p>"), false},
|
||||
{2, template.HTML("<p>P1</p><p>P2</p>"), nil, template.HTML("<p>P1 …</p>"), false},
|
||||
|
||||
@@ -201,3 +201,73 @@ a = "b"
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
})
|
||||
}
|
||||
|
||||
func TestRemarshaBillionLaughs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
yamlBillionLaughs := `
|
||||
a: &a [_, _, _, _, _, _, _, _, _, _, _, _, _, _, _]
|
||||
b: &b [*a, *a, *a, *a, *a, *a, *a, *a, *a, *a]
|
||||
c: &c [*b, *b, *b, *b, *b, *b, *b, *b, *b, *b]
|
||||
d: &d [*c, *c, *c, *c, *c, *c, *c, *c, *c, *c]
|
||||
e: &e [*d, *d, *d, *d, *d, *d, *d, *d, *d, *d]
|
||||
f: &f [*e, *e, *e, *e, *e, *e, *e, *e, *e, *e]
|
||||
g: &g [*f, *f, *f, *f, *f, *f, *f, *f, *f, *f]
|
||||
h: &h [*g, *g, *g, *g, *g, *g, *g, *g, *g, *g]
|
||||
i: &i [*h, *h, *h, *h, *h, *h, *h, *h, *h, *h]
|
||||
`
|
||||
|
||||
yamlMillionLaughs := `
|
||||
a: &a [_, _, _, _, _, _, _, _, _, _, _, _, _, _, _]
|
||||
b: &b [*a, *a, *a, *a, *a, *a, *a, *a, *a, *a]
|
||||
c: &c [*b, *b, *b, *b, *b, *b, *b, *b, *b, *b]
|
||||
d: &d [*c, *c, *c, *c, *c, *c, *c, *c, *c, *c]
|
||||
e: &e [*d, *d, *d, *d, *d, *d, *d, *d, *d, *d]
|
||||
f: &f [*e, *e, *e, *e, *e, *e, *e, *e, *e, *e]
|
||||
`
|
||||
|
||||
yamlTenThousandLaughs := `
|
||||
a: &a [_, _, _, _, _, _, _, _, _, _, _, _, _, _, _]
|
||||
b: &b [*a, *a, *a, *a, *a, *a, *a, *a, *a, *a]
|
||||
c: &c [*b, *b, *b, *b, *b, *b, *b, *b, *b, *b]
|
||||
d: &d [*c, *c, *c, *c, *c, *c, *c, *c, *c, *c]
|
||||
|
||||
`
|
||||
|
||||
yamlThousandLaughs := `
|
||||
a: &a [_, _, _, _, _, _, _, _, _, _, _, _, _, _, _]
|
||||
b: &b [*a, *a, *a, *a, *a, *a, *a, *a, *a, *a]
|
||||
c: &c [*b, *b, *b, *b, *b, *b, *b, *b, *b, *b]
|
||||
|
||||
`
|
||||
|
||||
b := hugolib.NewIntegrationTestBuilder(
|
||||
hugolib.IntegrationTestConfig{T: t},
|
||||
).Build()
|
||||
|
||||
ns := transform.New(b.H.Deps)
|
||||
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
data string
|
||||
}{
|
||||
{"10k", yamlTenThousandLaughs},
|
||||
{"1M", yamlMillionLaughs},
|
||||
{"1B", yamlBillionLaughs},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
_, err := ns.Remarshal("json", test.data)
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
})
|
||||
}
|
||||
|
||||
// Thousand laughs should be ok.
|
||||
// It produces about 29KB of JSON,
|
||||
// which is still a large output for such a large input,
|
||||
// but there may be use cases for this.
|
||||
_, err := ns.Remarshal("json", yamlThousandLaughs)
|
||||
c := qt.New(t)
|
||||
c.Assert(err, qt.IsNil)
|
||||
}
|
||||
|
||||
@@ -591,3 +591,31 @@ outputs: ["html", "markdown"]
|
||||
// There are some white space differences, so we cannot do an exact match.
|
||||
b.AssertFileContent("public/p1/index.md", markdown)
|
||||
}
|
||||
|
||||
// See https://github.com/goccy/go-yaml/issues/461
|
||||
func TestUnmarshalExcessiveYAMLStructureShouldFail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
|
||||
-- assets/ddos.yaml --
|
||||
a: &a [_, _, _, _, _, _, _, _, _, _, _, _, _, _, _]
|
||||
b: &b [*a, *a, *a, *a, *a, *a, *a, *a, *a, *a]
|
||||
c: &c [*b, *b, *b, *b, *b, *b, *b, *b, *b, *b]
|
||||
d: &d [*c, *c, *c, *c, *c, *c, *c, *c, *c, *c]
|
||||
e: &e [*d, *d, *d, *d, *d, *d, *d, *d, *d, *d]
|
||||
f: &f [*e, *e, *e, *e, *e, *e, *e, *e, *e, *e]
|
||||
g: &g [*f, *f, *f, *f, *f, *f, *f, *f, *f, *f]
|
||||
h: &h [*g, *g, *g, *g, *g, *g, *g, *g, *g, *g]
|
||||
i: &i [*h, *h, *h, *h, *h, *h, *h, *h, *h, *h]
|
||||
-- layouts/home.html --
|
||||
{{ $m := resources.Get "ddos.yaml" | transform.Unmarshal }}
|
||||
{{ printf "Length: %d" (len $m) }}
|
||||
`
|
||||
|
||||
b, err := hugolib.TestE(t, files)
|
||||
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
b.Assert(err.Error(), qt.Contains, "too many YAML aliases for non-scalar nodes")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user