Compare commits

...

16 Commits

Author SHA1 Message Date
Bjørn Erik Pedersen 70423f0c0c Filter out duplicate content resource files
We do a slight normalisation of the content paths (lower case, replacing " " with "-") and remove andy language identifier before inserting them into the content tree.

This means that, given that that the default content language is `en`:

```
index.md
index.html
Foo Bar.txt
foo-bar.txt
foo-bar.en.txt
Foo-Bar.txt
```

The bundle above will be reduced to one content file with one resource (`foo-bar.txt`).

Before this commit, what version of the `foo-bar.txt` you ended up with was undeterministic. No  we pick the first determined by sort order.

Note that the sort order is stable, but we recommend avoiding situations like the above.

Closes #11946
2024-01-31 09:18:22 +01:00
Bjørn Erik Pedersen 5b7cb258ec Create default link and image render hooks
Fixes #11933
2024-01-30 20:12:19 +01:00
Bjørn Erik Pedersen 80595bbe3e Fix recent regression .Resources.Get for resources with spaces in filename
Fixes #11944
2024-01-30 20:12:03 +01:00
Bjørn Erik Pedersen afee781f03 Emit a warning that can be turned off when overwriting built-in .Params values
Fixes #11941
2024-01-30 20:12:03 +01:00
Bjørn Erik Pedersen 4e84f57efb Add warnidf template function
Also rename config `ignoreErrors` => `ignoreLogs`

But the old still works.

Closes #9189
2024-01-30 20:12:03 +01:00
Bjørn Erik Pedersen f31a6db797 Add path, kind and lang to content front matter
Note that none of these can be set via cascade (you will get an error)

Fixes #11544
2024-01-30 20:12:03 +01:00
Lars Lehtonen ec22bb31a8 hugofs/glob: Fix dropped test error 2024-01-28 23:31:10 +01:00
Bjørn Erik Pedersen a795acbcd8 all: Run gofumpt -l -w . 2024-01-28 23:14:09 +01:00
Bjørn Erik Pedersen 982d9513e7 testing: Simplify some integration tests 2024-01-28 22:17:22 +01:00
Bjørn Erik Pedersen 6dedb4efc7 Add the [params] concept to front matter
This is deliberately very simple, but should not break anything. We need to introduce this in baby steps, but this should allow us to introduce this in the documentation.

Note that the `params` section's key/values will be added to `.Params` last. This means that you can have different values for "Hugo's summary" and the custom ".Params.summary" if you want to.

Updates #11055
2024-01-28 21:38:40 +01:00
Bjørn Erik Pedersen 292626e679 tpl/data: Deprecate data.GetJSON and data.GetCSV 2024-01-28 16:37:36 +01:00
razonyang 60d954c785 modules: Print required Hugo version for incompatible modules 2024-01-28 15:23:21 +01:00
Bjørn Erik Pedersen 63e0a92894 hugolib: Remove unused test image 2024-01-28 15:14:53 +01:00
Kandula Naveen ce7daa6156 navigation: Improve menu cache 2024-01-28 15:11:57 +01:00
Bjørn Erik Pedersen 2a0329423c testing: Rename integration_test.go to PACKAGE_integration_test.go
Primary motivation making them easier to find in the code editor.
2024-01-28 11:41:59 +01:00
Bjørn Erik Pedersen 50dc327d1a Port some integration tests to new test setup
The method I'm currently using (if other want to help) is:

* Add fmt.Println(b.DumpTxtar()) after the Build step
* Add that to a files var and pass that to Test(t, files) or similar
* Then, if possible, try to reduce the files/content down to what's needed in test.

Note that if the test is small, it's probably faster just to manually re-create the test.
2024-01-28 11:29:23 +01:00
173 changed files with 1414 additions and 1350 deletions
+1 -3
View File
@@ -15,6 +15,7 @@
package filecache
import (
"errors"
"fmt"
"path"
"path/filepath"
@@ -24,8 +25,6 @@ import (
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/config"
"errors"
"github.com/mitchellh/mapstructure"
"github.com/spf13/afero"
)
@@ -225,7 +224,6 @@ func DecodeConfig(fs afero.Fs, bcfg config.BaseConfig, m map[string]any) (Config
// Resolves :resourceDir => /myproject/resources etc., :cacheDir => ...
func resolveDirPlaceholder(fs afero.Fs, bcfg config.BaseConfig, placeholder string) (cacheDir string, isResource bool, err error) {
switch strings.ToLower(placeholder) {
case ":resourcedir":
return "", true, nil
-2
View File
@@ -60,7 +60,6 @@ func (c *Cache) Prune(force bool) (int, error) {
counter := 0
err := afero.Walk(c.Fs, "", func(name string, info os.FileInfo, err error) error {
if info == nil {
return nil
}
@@ -69,7 +68,6 @@ func (c *Cache) Prune(force bool) (int, error) {
if info.IsDir() {
f, err := c.Fs.Open(name)
if err != nil {
// This cache dir may not exist.
return nil
Executable
+2
View File
@@ -0,0 +1,2 @@
#!/usr/bin/env bash
diff <(gofmt -d .) <(printf '')
-1
View File
@@ -461,7 +461,6 @@ func collectMethodsRecursive(pkg string, f []*ast.Field) []string {
pkg,
tt.Methods.List)...)
}
} else {
// Embedded, but in a different file/package. Return the
// package.Name and deal with that later.
+7 -5
View File
@@ -55,7 +55,7 @@ func TestAppend(t *testing.T) {
[]any{&tstSlicerIn1{"c"}},
testSlicerInterfaces{&tstSlicerIn1{"a"}, &tstSlicerIn1{"b"}, &tstSlicerIn1{"c"}},
},
//https://github.com/gohugoio/hugo/issues/5361
// https://github.com/gohugoio/hugo/issues/5361
{
[]string{"a", "b"},
[]any{tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}}},
@@ -102,14 +102,16 @@ func TestAppendToMultiDimensionalSlice(t *testing.T) {
from []any
expected any
}{
{[][]string{{"a", "b"}},
{
[][]string{{"a", "b"}},
[]any{[]string{"c", "d"}},
[][]string{
{"a", "b"},
{"c", "d"},
},
},
{[][]string{{"a", "b"}},
{
[][]string{{"a", "b"}},
[]any{[]string{"c", "d"}, []string{"e", "f"}},
[][]string{
{"a", "b"},
@@ -117,7 +119,8 @@ func TestAppendToMultiDimensionalSlice(t *testing.T) {
{"e", "f"},
},
},
{[][]string{{"a", "b"}},
{
[][]string{{"a", "b"}},
[]any{[]int{1, 2}},
false,
},
@@ -130,7 +133,6 @@ func TestAppendToMultiDimensionalSlice(t *testing.T) {
c.Assert(result, qt.DeepEquals, test.expected)
}
}
}
func TestAppendShouldMakeACopyOfTheInputSlice(t *testing.T) {
-1
View File
@@ -73,7 +73,6 @@ func StringSliceToInterfaceSlice(ss []string) []any {
result[i] = s
}
return result
}
type SortedStringSlice []string
-1
View File
@@ -135,5 +135,4 @@ func TestSortedStringSlice(t *testing.T) {
c.Assert(s.Count("b"), qt.Equals, 3)
c.Assert(s.Count("z"), qt.Equals, 0)
c.Assert(s.Count("a"), qt.Equals, 1)
}
+3 -1
View File
@@ -13,12 +13,14 @@
package constants
// Error IDs.
// Error/Warning IDs.
// Do not change these values.
const (
// IDs for remote errors in tpl/data.
ErrRemoteGetJSON = "error-remote-getjson"
ErrRemoteGetCSV = "error-remote-getcsv"
WarnFrontMatterParamsOverrides = "warning-frontmatter-params-overrides"
)
// Field/method names with special meaning.
+2 -4
View File
@@ -19,11 +19,10 @@ import (
"errors"
"fmt"
"io"
"regexp"
"strings"
"os"
"os/exec"
"regexp"
"strings"
"github.com/cli/safeexec"
"github.com/gohugoio/hugo/config"
@@ -142,7 +141,6 @@ func (e *Exec) New(name string, arg ...any) (Runner, error) {
}
return cm.command(arg...)
}
// Npx is a convenience method to create a Runner running npx --no-install <name> <args.
@@ -47,12 +47,7 @@ defaultContentLanguage = 'it'
{{ end }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/index.html", `
month: _gennaio_ weekday: _lunedì_
-4
View File
@@ -53,7 +53,6 @@ func TestTimeFormatter(t *testing.T) {
c.Assert(f.Format(june06, ":time_long"), qt.Equals, "02:09:37 UTC")
c.Assert(f.Format(june06, ":time_medium"), qt.Equals, "02:09:37")
c.Assert(f.Format(june06, ":time_short"), qt.Equals, "02:09")
})
c.Run("Custom layouts English", func(c *qt.C) {
@@ -68,7 +67,6 @@ func TestTimeFormatter(t *testing.T) {
c.Assert(f.Format(june06, ":time_long"), qt.Equals, "2:09:37 am UTC")
c.Assert(f.Format(june06, ":time_medium"), qt.Equals, "2:09:37 am")
c.Assert(f.Format(june06, ":time_short"), qt.Equals, "2:09 am")
})
c.Run("English", func(c *qt.C) {
@@ -107,9 +105,7 @@ func TestTimeFormatter(t *testing.T) {
c.Assert(tr.MonthWide(date.Month()), qt.Equals, monthWideNorway)
c.Assert(f.Format(date, "January"), qt.Equals, monthWideNorway)
}
})
}
func BenchmarkTimeFormatter(b *testing.B) {
+13 -2
View File
@@ -179,9 +179,9 @@ type Logger interface {
Debugln(v ...any)
Error() logg.LevelLogger
Errorf(format string, v ...any)
Erroridf(id, format string, v ...any)
Errorln(v ...any)
Errors() string
Errorsf(id, format string, v ...any)
Info() logg.LevelLogger
InfoCommand(command string) logg.LevelLogger
Infof(format string, v ...any)
@@ -197,6 +197,7 @@ type Logger interface {
Warn() logg.LevelLogger
WarnCommand(command string) logg.LevelLogger
Warnf(format string, v ...any)
Warnidf(id, format string, v ...any)
Warnln(v ...any)
Deprecatef(fail bool, format string, v ...any)
Trace(s logg.StringFunc)
@@ -321,10 +322,20 @@ func (l *logAdapter) Errors() string {
return l.errors.String()
}
func (l *logAdapter) Errorsf(id, format string, v ...any) {
func (l *logAdapter) Erroridf(id, format string, v ...any) {
format += l.idfInfoStatement("error", id, format)
l.errorl.WithField(FieldNameStatementID, id).Logf(format, v...)
}
func (l *logAdapter) Warnidf(id, format string, v ...any) {
format += l.idfInfoStatement("warning", id, format)
l.warnl.WithField(FieldNameStatementID, id).Logf(format, v...)
}
func (l *logAdapter) idfInfoStatement(what, id, format string) string {
return fmt.Sprintf("\nYou can suppress this %s by adding the following to your site configuration:\nignoreLogs = ['%s']", what, id)
}
func (l *logAdapter) Trace(s logg.StringFunc) {
l.tracel.Log(s)
}
-1
View File
@@ -192,5 +192,4 @@ func TestLookupEqualFold(t *testing.T) {
v, found = LookupEqualFold(m2, "b")
c.Assert(found, qt.IsTrue)
c.Assert(v, qt.Equals, "bv")
}
-1
View File
@@ -154,7 +154,6 @@ func TestParamsSetAndMerge(t *testing.T) {
"a": "av",
"c": "cv",
})
}
func TestParamsIsZero(t *testing.T) {
+5
View File
@@ -387,6 +387,11 @@ func ToSlashTrimLeading(s string) string {
return strings.TrimPrefix(filepath.ToSlash(s), "/")
}
// ToSlashTrimTrailing is just a filepath.ToSlash with an added / suffix trimmer.
func ToSlashTrimTrailing(s string) string {
return strings.TrimSuffix(filepath.ToSlash(s), "/")
}
// ToSlashPreserveLeading converts the path given to a forward slash separated path
// and preserves the leading slash if present trimming any trailing slash.
func ToSlashPreserveLeading(s string) string {
-1
View File
@@ -163,7 +163,6 @@ func Uglify(in string) string {
// If ParseRequestURI fails, the input is just converted to OS specific slashes and returned.
func UrlToFilename(s string) (string, bool) {
u, err := url.ParseRequestURI(s)
if err != nil {
return filepath.FromSlash(s), false
}
-4
View File
@@ -57,7 +57,6 @@ line 3`
c := qt.New(t)
c.Assert(collected, qt.DeepEquals, []string{"line 1\n", "line 2\n", "\n", "line 3"})
}
func BenchmarkVisitLinesAfter(b *testing.B) {
@@ -68,9 +67,6 @@ func BenchmarkVisitLinesAfter(b *testing.B) {
for i := 0; i < b.N; i++ {
VisitLinesAfter(lines, func(s string) {
})
}
}
-1
View File
@@ -80,7 +80,6 @@ func ToStringSlicePreserveStringE(v any) ([]string, error) {
default:
return nil, fmt.Errorf("failed to convert %T to a string slice", v)
}
}
// TypeToString converts v to a string if it's a valid string type.
-1
View File
@@ -45,5 +45,4 @@ func TestToDuration(t *testing.T) {
c.Assert(ToDuration("200"), qt.Equals, 200*time.Millisecond)
c.Assert(ToDuration("4m"), qt.Equals, 4*time.Minute)
c.Assert(ToDuration("asdfadf"), qt.Equals, time.Duration(0))
}
+5
View File
@@ -107,3 +107,8 @@ type LowHigh struct {
// This is only used for debugging purposes.
var InvocationCounter atomic.Int64
// NewTrue returns a pointer to b.
func NewBool(b bool) *bool {
return &b
}
-1
View File
@@ -79,5 +79,4 @@ func BenchmarkStringSort(b *testing.B) {
})
}
})
}
+21 -8
View File
@@ -31,6 +31,7 @@ import (
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/common/urls"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/privacy"
@@ -206,7 +207,7 @@ func (c Config) cloneForLang() *Config {
x.DisableKinds = copyStringSlice(x.DisableKinds)
x.DisableLanguages = copyStringSlice(x.DisableLanguages)
x.MainSections = copyStringSlice(x.MainSections)
x.IgnoreErrors = copyStringSlice(x.IgnoreErrors)
x.IgnoreLogs = copyStringSlice(x.IgnoreLogs)
x.IgnoreFiles = copyStringSlice(x.IgnoreFiles)
x.Theme = copyStringSlice(x.Theme)
@@ -299,9 +300,9 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
}
}
ignoredErrors := make(map[string]bool)
for _, err := range c.IgnoreErrors {
ignoredErrors[strings.ToLower(err)] = true
ignoredLogIDs := make(map[string]bool)
for _, err := range c.IgnoreLogs {
ignoredLogIDs[strings.ToLower(err)] = true
}
baseURL, err := urls.NewBaseURLFromString(c.BaseURL)
@@ -357,7 +358,7 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
BaseURLLiveReload: baseURL,
DisabledKinds: disabledKinds,
DisabledLanguages: disabledLangs,
IgnoredErrors: ignoredErrors,
IgnoredLogs: ignoredLogIDs,
KindOutputFormats: kindOutputFormats,
CreateTitle: helpers.GetTitleFunc(c.TitleCaseStyle),
IsUglyURLSection: isUglyURL,
@@ -394,7 +395,7 @@ type ConfigCompiled struct {
KindOutputFormats map[string]output.Formats
DisabledKinds map[string]bool
DisabledLanguages map[string]bool
IgnoredErrors map[string]bool
IgnoredLogs map[string]bool
CreateTitle func(s string) string
IsUglyURLSection func(section string) bool
IgnoreFile func(filename string) bool
@@ -501,8 +502,8 @@ type RootConfig struct {
// Enable to disable the build lock file.
NoBuildLock bool
// A list of error IDs to ignore.
IgnoreErrors []string
// A list of log IDs to ignore.
IgnoreLogs []string
// A list of regexps that match paths to ignore.
// Deprecated: Use the settings on module imports.
@@ -899,6 +900,18 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
return nil, err
}
// Adjust Goldmark config defaults for multilingual, single-host sites.
if len(languagesConfig) > 1 && !isMultiHost && !clone.Markup.Goldmark.DuplicateResourceFiles {
if !clone.Markup.Goldmark.DuplicateResourceFiles {
if clone.Markup.Goldmark.RenderHooks.Link.EnableDefault == nil {
clone.Markup.Goldmark.RenderHooks.Link.EnableDefault = types.NewBool(true)
}
if clone.Markup.Goldmark.RenderHooks.Image.EnableDefault == nil {
clone.Markup.Goldmark.RenderHooks.Image.EnableDefault = types.NewBool(true)
}
}
}
langConfigMap[k] = clone
case maps.ParamsMergeStrategy:
default:
+2 -2
View File
@@ -89,8 +89,8 @@ func (c ConfigLanguage) IsLangDisabled(lang string) bool {
return c.config.C.DisabledLanguages[lang]
}
func (c ConfigLanguage) IgnoredErrors() map[string]bool {
return c.config.C.IgnoredErrors
func (c ConfigLanguage) IgnoredLogs() map[string]bool {
return c.config.C.IgnoredLogs
}
func (c ConfigLanguage) NoBuildLock() bool {
+1
View File
@@ -141,6 +141,7 @@ func (l configLoader) applyConfigAliases() error {
{Key: "indexes", Value: "taxonomies"},
{Key: "logI18nWarnings", Value: "printI18nWarnings"},
{Key: "logPathWarnings", Value: "printPathWarnings"},
{Key: "ignoreErrors", Value: "ignoreLogs"},
}
for _, alias := range aliases {
+1 -1
View File
@@ -50,7 +50,7 @@ weight = 3
title = "Svenska"
weight = 4
`
if err := os.WriteFile(configFilename, []byte(config), 0666); err != nil {
if err := os.WriteFile(configFilename, []byte(config), 0o666); err != nil {
b.Fatal(err)
}
d := ConfigSourceDescriptor{
-1
View File
@@ -208,7 +208,6 @@ func LoadConfigFromDir(sourceFs afero.Fs, configDir, environment string) (Provid
}
return cfg, dirnames, nil
}
var keyAliases maps.KeyRenamer
+1 -1
View File
@@ -67,7 +67,7 @@ type AllProvider interface {
NewContentEditor() string
Timeout() time.Duration
StaticDirs() []string
IgnoredErrors() map[string]bool
IgnoredLogs() map[string]bool
WorkingDir() string
EnableEmoji() bool
}
-1
View File
@@ -370,7 +370,6 @@ func (c *defaultConfigProvider) SetDefaultMergeStrategy() {
}
return false
})
}
func (c *defaultConfigProvider) getNestedKeyAndMap(key string, create bool) (string, maps.Params) {
-1
View File
@@ -49,7 +49,6 @@ func GetMemoryLimit() uint64 {
if v := stringToGibabyte(mem); v > 0 {
return v
}
}
// There is a FreeMemory function, but as the kernel in most situations
-1
View File
@@ -43,5 +43,4 @@ func TestWhitelist(t *testing.T) {
c.Assert(w.Accept("bar"), qt.IsTrue)
c.Assert(w.Accept("mbar"), qt.IsFalse)
})
}
+1 -3
View File
@@ -14,11 +14,10 @@
package deploy
import (
"errors"
"fmt"
"regexp"
"errors"
"github.com/gobwas/glob"
"github.com/gohugoio/hugo/config"
hglob "github.com/gohugoio/hugo/hugofs/glob"
@@ -132,7 +131,6 @@ var DefaultConfig = DeployConfig{
// DecodeConfig creates a config from a given Hugo configuration.
func DecodeConfig(cfg config.Provider) (DeployConfig, error) {
dcfg := DefaultConfig
if !cfg.IsSet(deploymentConfigKey) {
+1 -1
View File
@@ -48,7 +48,7 @@ require (
github.com/marekm4/color-extractor v1.2.1
github.com/mattn/go-isatty v0.0.20
github.com/mitchellh/hashstructure v1.1.0
github.com/mitchellh/mapstructure v1.5.0
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c
github.com/muesli/smartcrop v0.3.0
github.com/niklasfasching/go-org v1.7.0
github.com/olekukonko/tablewriter v0.0.5
+2
View File
@@ -359,6 +359,8 @@ github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9km
github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE=
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/montanaflynn/stats v0.6.3/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
-2
View File
@@ -20,12 +20,10 @@ import (
)
func TestExtractMinorVersionFromGoTag(t *testing.T) {
c := qt.New(t)
c.Assert(extractMinorVersionFromGoTag("go1.17"), qt.Equals, 17)
c.Assert(extractMinorVersionFromGoTag("go1.16.7"), qt.Equals, 16)
c.Assert(extractMinorVersionFromGoTag("go1.17beta1"), qt.Equals, 17)
c.Assert(extractMinorVersionFromGoTag("asdfadf"), qt.Equals, -1)
}
+2 -2
View File
@@ -147,13 +147,13 @@ func (f *componentFsDir) ReadDir(count int) ([]iofs.DirEntry, error) {
})
if f.fs.opts.Component == files.ComponentFolderContent {
// Finally filter out any duplicate content files, e.g. page.md and page.html.
// Finally filter out any duplicate content or resource files, e.g. page.md and page.html.
n := 0
seen := map[hstrings.Tuple]bool{}
for _, fi := range fis {
fim := fi.(FileMetaInfo)
pi := fim.Meta().PathInfo
keep := fim.IsDir() || !pi.IsContent()
keep := fim.IsDir()
if !keep {
baseLang := hstrings.Tuple{First: pi.Base(), Second: fim.Meta().Lang}
+1 -3
View File
@@ -33,9 +33,7 @@ type DuplicatesReporter interface {
ReportDuplicates() string
}
var (
_ FilesystemUnwrapper = (*createCountingFs)(nil)
)
var _ FilesystemUnwrapper = (*createCountingFs)(nil)
func NewCreateCountingFs(fs afero.Fs) afero.Fs {
return &createCountingFs{Fs: fs, fileCount: make(map[string]int)}
-1
View File
@@ -75,7 +75,6 @@ func TestGetGlob(t *testing.T) {
}
func BenchmarkGetGlob(b *testing.B) {
runBench := func(name string, cache *globCache, search string) {
b.Run(name, func(b *testing.B) {
g, err := GetGlob("**/foo")
+2 -2
View File
@@ -41,8 +41,8 @@ Data: {{ len .Data }}|
IntegrationTestConfig{
T: t,
TxtarString: files,
//LogLevel: logg.LevelTrace,
//Verbose: true,
// LogLevel: logg.LevelTrace,
// Verbose: true,
},
).Build()
Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

+3 -4
View File
@@ -157,7 +157,7 @@ module github.com/bep/mymod
tempDir := os.TempDir()
cacheDir := filepath.Join(tempDir, "hugocache")
if err := os.MkdirAll(cacheDir, 0777); err != nil {
if err := os.MkdirAll(cacheDir, 0o777); err != nil {
return nil, err
}
cfg.Set("cacheDir", cacheDir)
@@ -168,11 +168,11 @@ module github.com/bep/mymod
fs := afero.NewOsFs()
if err := afero.WriteFile(fs, filepath.Join(tempDir, "hugo.toml"), []byte(configToml), 0644); err != nil {
if err := afero.WriteFile(fs, filepath.Join(tempDir, "hugo.toml"), []byte(configToml), 0o644); err != nil {
return nil, err
}
if err := afero.WriteFile(fs, filepath.Join(tempDir, "go.mod"), []byte(goMod), 0644); err != nil {
if err := afero.WriteFile(fs, filepath.Join(tempDir, "go.mod"), []byte(goMod), 0o644); err != nil {
return nil, err
}
@@ -181,5 +181,4 @@ module github.com/bep/mymod
return nil, err
}
return conf.Base, err
}
+16 -99
View File
@@ -48,15 +48,8 @@ title = "English Title"
[languages.en.params.comments]
title = "English Comments Title"
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
enSite := b.H.Sites[0]
b.Assert(enSite.Title(), qt.Equals, "English Title")
@@ -97,14 +90,8 @@ weight = 2
[languages.sv.params]
myparam = "svParamValue"
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
enSite := b.H.Sites[0]
svSite := b.H.Sites[1]
@@ -157,12 +144,7 @@ baseURL = "https://example.com"
[internal]
running = true
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.Assert(b.H.Conf.Running(), qt.Equals, false)
})
@@ -236,12 +218,7 @@ p1: {{ .Site.Params.p1 }}|
p2: {{ .Site.Params.p2 }}|
sub: {{ .Site.Params.sub }}|
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/en/index.html", `
title: English Title|
@@ -987,12 +964,7 @@ params:
mainSections: {{ site.Params.mainSections }}
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/index.html", `
mainSections: []
@@ -1062,12 +1034,7 @@ Ein "Zitat" auf Deutsch.
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/index.html", "p1: p1base", "<p>A &ldquo;quote&rdquo; in English.</p>")
b.AssertFileContent("public/de/index.html", "p1: p1de", "<p>Ein &laquo;Zitat&raquo; auf Deutsch.</p>")
@@ -1129,12 +1096,7 @@ HTACCESS.
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/.htaccess", "HTACCESS")
}
@@ -1150,12 +1112,7 @@ LanguageCode: {{ .Site.LanguageCode }}|{{ site.Language.LanguageCode }}|
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/index.html", "LanguageCode: en-US|en-US|")
}
@@ -1181,12 +1138,7 @@ Home.
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/index.html", "Home.")
@@ -1214,12 +1166,7 @@ Foo: {{ site.Params.foo }}|
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/index.html", "Foo: |")
})
@@ -1295,12 +1242,7 @@ Home.
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.Assert(b.H.Configs.Base.Module.Mounts, qt.HasLen, 7)
b.Assert(b.H.Configs.LanguageConfigSlice[0].Module.Mounts, qt.HasLen, 7)
@@ -1321,12 +1263,7 @@ Foo.
-- layouts/index.html --
Home.
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/en/index.html", "Home.")
b.AssertFileContent("public/en/foo/bar.txt", "Foo.")
@@ -1354,12 +1291,7 @@ Foo.
-- layouts/index.html --
Home.
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/en/index.html", "Home.")
b.AssertFileContent("public/en/foo/bar.txt", "Foo.")
@@ -1387,12 +1319,7 @@ Foo.
-- layouts/index.html --
Home.
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/index.html", "Home.")
b.AssertFileContent("public/foo/bar.txt", "Foo.")
@@ -1417,12 +1344,7 @@ Home.
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.Assert(len(b.H.Sites), qt.Equals, 1)
}
@@ -1557,12 +1479,7 @@ List.
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileExists("public/index.html", true)
b.AssertFileExists("public/categories/c1/index.html", true)
+1 -6
View File
@@ -38,12 +38,7 @@ c = "c1"
-- layouts/index.html --
Params: {{ site.Params}}
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/index.html", `
Params: map[a:acp1 b:bc1 c:c1 d:dcp1]
+4 -2
View File
@@ -187,7 +187,7 @@ func (m *pageMap) AddFi(fi hugofs.FileMetaInfo) error {
if pi.IsContent() {
// Create the page now as we need it at assemembly time.
// The other resources are created if needed.
pageResource, err := m.s.h.newPage(
pageResource, pi, err := m.s.h.newPage(
&pageMeta{
f: source.NewFileInfo(fim),
pathInfo: pi,
@@ -197,6 +197,8 @@ func (m *pageMap) AddFi(fi hugofs.FileMetaInfo) error {
if err != nil {
return err
}
key = pi.Base()
rs = &resourceSource{r: pageResource}
} else {
rs = &resourceSource{path: pi, opener: r, fi: fim}
@@ -226,7 +228,7 @@ func (m *pageMap) AddFi(fi hugofs.FileMetaInfo) error {
},
))
// A content file.
p, err := m.s.h.newPage(
p, pi, err := m.s.h.newPage(
&pageMeta{
f: source.NewFileInfo(fi),
pathInfo: pi,
+53 -38
View File
@@ -43,6 +43,7 @@ import (
"github.com/gohugoio/hugo/resources/kinds"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/page/pagemeta"
"github.com/gohugoio/hugo/resources/resource"
)
@@ -97,7 +98,6 @@ type pageMap struct {
cacheContentRendered *dynacache.Partition[string, *resources.StaleValue[contentSummary]]
cacheContentPlain *dynacache.Partition[string, *resources.StaleValue[contentPlainPlainWords]]
contentTableOfContents *dynacache.Partition[string, *resources.StaleValue[contentTableOfContents]]
cacheContentSource *dynacache.Partition[string, *resources.StaleValue[[]byte]]
cfg contentMapConfig
}
@@ -147,7 +147,6 @@ func (t *pageTrees) collectIdentities(key string) []identity.Identity {
// collectIdentitiesSurrounding collects all identities surrounding the given key.
func (t *pageTrees) collectIdentitiesSurrounding(key string, maxSamplesPerTree int) []identity.Identity {
// TODO1 test language coverage from this.
ids := t.collectIdentitiesSurroundingIn(key, maxSamplesPerTree, t.treePages)
ids = append(ids, t.collectIdentitiesSurroundingIn(key, maxSamplesPerTree, t.treeResources)...)
return ids
@@ -483,7 +482,7 @@ func (m *pageMap) getOrCreateResourcesForPage(ps *pageState) resource.Resources
return nil, err
}
if translationKey := ps.m.translationKey; translationKey != "" {
if translationKey := ps.m.pageConfig.TranslationKey; translationKey != "" {
// This this should not be a very common case.
// Merge in resources from the other languages.
translatedPages, _ := m.s.h.translationKeyPages.Get(translationKey)
@@ -539,9 +538,9 @@ func (m *pageMap) getOrCreateResourcesForPage(ps *pageState) resource.Resources
sort.SliceStable(res, lessFunc)
if len(ps.m.resourcesMetadata) > 0 {
if len(ps.m.pageConfig.Resources) > 0 {
for i, r := range res {
res[i] = resources.CloneWithMetadataIfNeeded(ps.m.resourcesMetadata, r)
res[i] = resources.CloneWithMetadataIfNeeded(ps.m.pageConfig.Resources, r)
}
sort.SliceStable(res, lessFunc)
}
@@ -819,7 +818,6 @@ func newPageMap(i int, s *Site, mcache *dynacache.Cache, pageTrees *pageTrees) *
cacheContentRendered: dynacache.GetOrCreatePartition[string, *resources.StaleValue[contentSummary]](mcache, fmt.Sprintf("/cont/ren/%d", i), dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
cacheContentPlain: dynacache.GetOrCreatePartition[string, *resources.StaleValue[contentPlainPlainWords]](mcache, fmt.Sprintf("/cont/pla/%d", i), dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
contentTableOfContents: dynacache.GetOrCreatePartition[string, *resources.StaleValue[contentTableOfContents]](mcache, fmt.Sprintf("/cont/toc/%d", i), dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
cacheContentSource: dynacache.GetOrCreatePartition[string, *resources.StaleValue[[]byte]](mcache, fmt.Sprintf("/cont/src/%d", i), dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
cfg: contentMapConfig{
lang: s.Lang(),
@@ -1215,7 +1213,7 @@ func (sa *sitePagesAssembler) applyAggregates() error {
// Home page gets it's cascade from the site config.
cascade = sa.conf.Cascade.Config
if pageBundle.m.cascade == nil {
if pageBundle.m.pageConfig.Cascade == nil {
// Pass the site cascade downwards.
pw.WalkContext.Data().Insert(keyPage, cascade)
}
@@ -1227,12 +1225,12 @@ func (sa *sitePagesAssembler) applyAggregates() error {
}
if (pageBundle.IsHome() || pageBundle.IsSection()) && pageBundle.m.setMetaPostCount > 0 {
oldDates := pageBundle.m.dates
oldDates := pageBundle.m.pageConfig.Dates
// We need to wait until after the walk to determine if any of the dates have changed.
pw.WalkContext.AddPostHook(
func() error {
if oldDates != pageBundle.m.dates {
if oldDates != pageBundle.m.pageConfig.Dates {
sa.assembleChanges.Add(pageBundle)
}
return nil
@@ -1251,11 +1249,12 @@ func (sa *sitePagesAssembler) applyAggregates() error {
const eventName = "dates"
if n.isContentNodeBranch() {
if pageBundle.m.cascade != nil {
if pageBundle.m.pageConfig.Cascade != nil {
// Pass it down.
pw.WalkContext.Data().Insert(keyPage, pageBundle.m.cascade)
pw.WalkContext.Data().Insert(keyPage, pageBundle.m.pageConfig.Cascade)
}
wasZeroDates := resource.IsZeroDates(pageBundle.m.dates)
wasZeroDates := pageBundle.m.pageConfig.Dates.IsAllDatesZero()
if wasZeroDates || pageBundle.IsHome() {
pw.WalkContext.AddEventListener(eventName, keyPage, func(e *doctree.Event[contentNodeI]) {
sp, ok := e.Source.(*pageState)
@@ -1264,15 +1263,15 @@ func (sa *sitePagesAssembler) applyAggregates() error {
}
if wasZeroDates {
pageBundle.m.dates.UpdateDateAndLastmodIfAfter(sp.m.dates)
pageBundle.m.pageConfig.Dates.UpdateDateAndLastmodIfAfter(sp.m.pageConfig.Dates)
}
if pageBundle.IsHome() {
if pageBundle.m.dates.Lastmod().After(pageBundle.s.lastmod) {
pageBundle.s.lastmod = pageBundle.m.dates.Lastmod()
if pageBundle.m.pageConfig.Dates.Lastmod.After(pageBundle.s.lastmod) {
pageBundle.s.lastmod = pageBundle.m.pageConfig.Dates.Lastmod
}
if sp.m.dates.Lastmod().After(pageBundle.s.lastmod) {
pageBundle.s.lastmod = sp.m.dates.Lastmod()
if sp.m.pageConfig.Dates.Lastmod.After(pageBundle.s.lastmod) {
pageBundle.s.lastmod = sp.m.pageConfig.Dates.Lastmod
}
}
})
@@ -1351,9 +1350,9 @@ func (sa *sitePagesAssembler) applyAggregatesToTaxonomiesAndTerms() error {
p := n.(*pageState)
if p.Kind() != kinds.KindTerm {
// The other kinds were handled in applyAggregates.
if p.m.cascade != nil {
if p.m.pageConfig.Cascade != nil {
// Pass it down.
pw.WalkContext.Data().Insert(s, p.m.cascade)
pw.WalkContext.Data().Insert(s, p.m.pageConfig.Cascade)
}
}
@@ -1388,14 +1387,14 @@ func (sa *sitePagesAssembler) applyAggregatesToTaxonomiesAndTerms() error {
// Send the date info up the tree.
pw.WalkContext.SendEvent(&doctree.Event[contentNodeI]{Source: n, Path: s, Name: eventName})
if resource.IsZeroDates(p.m.dates) {
if p.m.pageConfig.Dates.IsAllDatesZero() {
pw.WalkContext.AddEventListener(eventName, s, func(e *doctree.Event[contentNodeI]) {
sp, ok := e.Source.(*pageState)
if !ok {
return
}
p.m.dates.UpdateDateAndLastmodIfAfter(sp.m.dates)
p.m.pageConfig.Dates.UpdateDateAndLastmodIfAfter(sp.m.pageConfig.Dates)
})
}
@@ -1443,8 +1442,8 @@ func (sa *sitePagesAssembler) assembleTermsAndTranslations() error {
// This is a little out of place, but is conveniently put here.
// Check if translationKey is set by user.
// This is to support the manual way of setting the translationKey in front matter.
if ps.m.translationKey != "" {
sa.s.h.translationKeyPages.Append(ps.m.translationKey, ps)
if ps.m.pageConfig.TranslationKey != "" {
sa.s.h.translationKeyPages.Append(ps.m.pageConfig.TranslationKey, ps)
}
if sa.pageMap.cfg.taxonomyTermDisabled {
@@ -1477,9 +1476,13 @@ func (sa *sitePagesAssembler) assembleTermsAndTranslations() error {
singular: viewName.singular,
s: sa.Site,
pathInfo: pi,
kind: kinds.KindTerm,
pageMetaParams: pageMetaParams{
pageConfig: &pagemeta.PageConfig{
Kind: kinds.KindTerm,
},
},
}
n, err := sa.h.newPage(m)
n, pi, err := sa.h.newPage(m)
if err != nil {
return false, err
}
@@ -1524,7 +1527,7 @@ func (sa *sitePagesAssembler) assembleResources() error {
targetPaths := ps.targetPaths()
baseTarget := targetPaths.SubResourceBaseTarget
duplicateResourceFiles := true
if ps.s.ContentSpec.Converters.IsGoldmark(ps.m.markup) {
if ps.s.ContentSpec.Converters.IsGoldmark(ps.m.pageConfig.Markup) {
duplicateResourceFiles = ps.s.ContentSpec.Converters.GetMarkupConfig().Goldmark.DuplicateResourceFiles
}
@@ -1545,7 +1548,7 @@ func (sa *sitePagesAssembler) assembleResources() error {
return false, nil
}
relPathOriginal := rs.path.PathRel(ps.m.pathInfo)
relPathOriginal := rs.path.Unmormalized().PathRel(ps.m.pathInfo.Unmormalized())
relPath := rs.path.BaseRel(ps.m.pathInfo)
var targetBasePaths []string
@@ -1566,7 +1569,7 @@ func (sa *sitePagesAssembler) assembleResources() error {
BasePathTargetPath: baseTarget,
Name: relPath,
NameOriginal: relPathOriginal,
LazyPublish: !ps.m.buildConfig.PublishResources,
LazyPublish: !ps.m.pageConfig.Build.PublishResources,
}
r, err := ps.m.s.ResourceSpec.NewResource(rd)
if err != nil {
@@ -1631,7 +1634,7 @@ func (sa *sitePagesAssembler) removeShouldNotBuild() error {
case kinds.KindHome, kinds.KindSection, kinds.KindTaxonomy:
// We need to keep these for the structure, but disable
// them so they don't get listed/rendered.
(&p.m.buildConfig).Disable()
(&p.m.pageConfig.Build).Disable()
default:
keys = append(keys, key)
}
@@ -1673,13 +1676,17 @@ func (sa *sitePagesAssembler) addStandalonePages() error {
}
m := &pageMeta{
s: s,
pathInfo: s.Conf.PathParser().Parse(files.ComponentFolderContent, key+f.MediaType.FirstSuffix.FullSuffix),
kind: kind,
s: s,
pathInfo: s.Conf.PathParser().Parse(files.ComponentFolderContent, key+f.MediaType.FirstSuffix.FullSuffix),
pageMetaParams: pageMetaParams{
pageConfig: &pagemeta.PageConfig{
Kind: kind,
},
},
standaloneOutputFormat: f,
}
p, _ := s.h.newPage(m)
p, _, _ := s.h.newPage(m)
tree.InsertIntoValuesDimension(key, p)
}
@@ -1756,7 +1763,7 @@ func (sa *sitePagesAssembler) addMissingRootSections() error {
pathInfo: pth,
}
ps, err := sa.h.newPage(m)
ps, pth, err := sa.h.newPage(m)
if err != nil {
return false, err
}
@@ -1781,9 +1788,13 @@ func (sa *sitePagesAssembler) addMissingRootSections() error {
m := &pageMeta{
s: sa.Site,
pathInfo: p,
kind: kinds.KindHome,
pageMetaParams: pageMetaParams{
pageConfig: &pagemeta.PageConfig{
Kind: kinds.KindHome,
},
},
}
n, err := sa.h.newPage(m)
n, p, err := sa.h.newPage(m)
if err != nil {
return err
}
@@ -1810,10 +1821,14 @@ func (sa *sitePagesAssembler) addMissingTaxonomies() error {
m := &pageMeta{
s: sa.Site,
pathInfo: sa.Conf.PathParser().Parse(files.ComponentFolderContent, key+"/_index.md"),
kind: kinds.KindTaxonomy,
pageMetaParams: pageMetaParams{
pageConfig: &pagemeta.PageConfig{
Kind: kinds.KindTaxonomy,
},
},
singular: viewName.singular,
}
p, _ := sa.h.newPage(m)
p, _, _ := sa.h.newPage(m)
tree.InsertIntoValuesDimension(key, p)
}
}
+46
View File
@@ -280,3 +280,49 @@ P1: {{ $p1.Title }}|{{ $p1.Params.foo }}|{{ $p1.File.Filename }}|
filepath.FromSlash("P1: P1 md|md|/content/p1.md|"),
)
}
// Issue #11944
func TestBundleResourcesGetWithSpacesInFilename(t *testing.T) {
files := `
-- hugo.toml --
baseURL = "https://example.com"
disableKinds = ["taxonomy", "term"]
-- content/bundle/index.md --
-- content/bundle/data with Spaces.txt --
Data.
-- layouts/index.html --
{{ $bundle := site.GetPage "bundle" }}
{{ $r := $bundle.Resources.Get "data with Spaces.txt" }}
R: {{ with $r }}{{ .Content }}{{ end }}|
`
b := Test(t, files)
b.AssertFileContent("public/index.html", "R: Data.")
}
// Issue #11946.
func TestBundleResourcesGetDuplicateSortOrder(t *testing.T) {
files := `
-- hugo.toml --
baseURL = "https://example.com"
-- content/bundle/index.md --
-- content/bundle/data-1.txt --
data-1.txt
-- content/bundle/data 1.txt --
data 1.txt
-- content/bundle/Data 1.txt --
Data 1.txt
-- content/bundle/Data-1.txt --
Data-1.txt
-- layouts/index.html --
{{ $bundle := site.GetPage "bundle" }}
{{ $r := $bundle.Resources.Get "data-1.txt" }}
R: {{ with $r }}{{ .Content }}{{ end }}|Len: {{ len $bundle.Resources }}|$
`
for i := 0; i < 3; i++ {
b := Test(t, files)
b.AssertFileContent("public/index.html", "R: Data 1.txt|", "Len: 1|")
}
}
+74 -12
View File
@@ -14,6 +14,7 @@
package hugolib
import (
"strings"
"testing"
)
@@ -63,12 +64,7 @@ outputs: ["rss"]
---
P3. [I'm an inline-style link](https://www.example.org)
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/index.html", `
P1: <p>P1. html-link: https://www.gohugo.io|</p>
@@ -163,12 +159,7 @@ P1 Fragments: {{ .Fragments.Identifiers }}|
{{ .Content}}
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/p1/index.html", `
Self Fragments: [b c z]
@@ -179,3 +170,74 @@ Self Fragments: [d e f]
P1 Fragments: [b c z]
`)
}
func TestDefaultRenderHooksMultilingual(t *testing.T) {
files := `
-- hugo.toml --
baseURL = "https://example.org"
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT"]
defaultContentLanguage = "nn"
defaultContentLanguageInSubdir = true
[markup]
[markup.goldmark]
duplicateResourceFiles = false
[markup.goldmark.renderhooks]
[markup.goldmark.renderhooks.link]
#enableDefault = false
[markup.goldmark.renderhooks.image]
#enableDefault = false
[languages]
[languages.en]
weight = 1
[languages.nn]
weight = 2
-- content/p1/index.md --
---
title: "p1"
---
[P2](p2)
![Pixel](pixel.png)
-- content/p2/index.md --
---
title: "p2"
---
[P1](p1)
![Pixel](pixel.jpg)
-- content/p1/index.en.md --
---
title: "p1 en"
---
[P2](p2)
![Pixel](pixel.png)
-- content/p2/index.en.md --
---
title: "p2 en"
---
[P1](p1)
![Pixel](pixel.png)
-- content/p1/pixel.nn.png --
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
-- content/p2/pixel.png --
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
-- layouts/_default/single.html --
{{ .Title }}|{{ .Content }}|$
`
t.Run("Default multilingual", func(t *testing.T) {
b := Test(t, files)
b.AssertFileContent("public/nn/p1/index.html",
"p1|<p><a href=\"/nn/p2/\">P2</a\n></p>", "<img alt=\"Pixel\" src=\"/nn/p1/pixel.nn.png\">")
b.AssertFileContent("public/en/p1/index.html",
"p1 en|<p><a href=\"/en/p2/\">P2</a\n></p>", "<img alt=\"Pixel\" src=\"/nn/p1/pixel.nn.png\">")
})
t.Run("Disabled", func(t *testing.T) {
b := Test(t, strings.ReplaceAll(files, "#enableDefault = false", "enableDefault = false"))
b.AssertFileContent("public/nn/p1/index.html",
"p1|<p><a href=\"p2\">P2</a>", "<img src=\"pixel.png\" alt=\"Pixel\">")
})
}
+1 -8
View File
@@ -18,7 +18,6 @@ import (
)
func TestData(t *testing.T) {
t.Run("with theme", func(t *testing.T) {
t.Parallel()
@@ -43,14 +42,8 @@ b: {{ site.Data.b.v1 }}|
cd: {{ site.Data.c.d.v1 }}|
d: {{ site.Data.d.v1 }}|
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/index.html", "a: a_v1|\nb: b_v1|\ncd: c_d_v1|\nd: d_v1_theme|")
})
}
+1 -6
View File
@@ -255,12 +255,7 @@ mydata.date: {{ site.Data.mydata.date }}
Full time: {{ $p1Date | time.Format ":time_full" }}
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/index.html", `
Future talks: 2
+1 -7
View File
@@ -76,12 +76,7 @@ Foo: {{< param foo >}}
-- layouts/index.html --
Content: {{ .Content }}|
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/index.html", `
<figure>
@@ -94,6 +89,5 @@ Foo: bar
`)
})
}
+30 -47
View File
@@ -657,30 +657,24 @@ min_version = 0.55.0
func TestMountsProject(t *testing.T) {
t.Parallel()
config := `
files := `
-- config.toml --
baseURL="https://example.org"
[module]
[[module.mounts]]
source="mycontent"
target="content"
`
b := newTestSitesBuilder(t).
WithConfigFile("toml", config).
WithSourceFile(filepath.Join("mycontent", "mypage.md"), `
-- layouts/_default/single.html --
Permalink: {{ .Permalink }}|
-- mycontent/mypage.md --
---
title: "My Page"
---
`
b := Test(t, files)
`)
b.Build(BuildCfg{})
// helpers.PrintFs(b.H.Fs.Source, "public", os.Stdout)
b.AssertFileContent("public/mypage/index.html", "Permalink: https://example.org/mypage/")
b.AssertFileContent("public/mypage/index.html", "Permalink: https://example.org/mypage/|")
}
// https://github.com/gohugoio/hugo/issues/6684
@@ -706,25 +700,20 @@ Home: {{ .Title }}|{{ .Content }}|
func TestSiteWithGoModButNoModules(t *testing.T) {
t.Parallel()
c := qt.New(t)
// We need to use the OS fs for this.
workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-no-mod")
c.Assert(err, qt.IsNil)
tempDir := t.TempDir()
cfg := config.New()
cfg.Set("workingDir", workDir)
cfg.Set("publishDir", "public")
fs := hugofs.NewFromOld(hugofs.Os, cfg)
files := `
-- hugo.toml --
baseURL = "https://example.org"
-- go.mod --
defer clean()
`
b := newTestSitesBuilder(t)
b.Fs = fs
b := Test(t, files, TestOptWithConfig(func(cfg *IntegrationTestConfig) {
cfg.WorkingDir = tempDir
}))
b.WithWorkingDir(workDir).WithViper(cfg)
b.WithSourceFile("go.mod", "")
b.Build(BuildCfg{})
b.Build()
}
// https://github.com/gohugoio/hugo/issues/6622
@@ -783,7 +772,9 @@ P1: {{ $p1.Title }}|{{ $p1.RelPermalink }}|Filename: {{ $p1.File.Filename }}
// Issue 9426
func TestMountSameSource(t *testing.T) {
config := `baseURL = 'https://example.org/'
files := `
-- hugo.toml --
baseURL = 'https://example.org/'
languageCode = 'en-us'
title = 'Hugo GitHub Issue #9426'
@@ -800,18 +791,15 @@ target = "content/resources-a"
[[module.mounts]]
source = "extra-content"
target = "content/resources-b"
-- layouts/_default/single.html --
Single
-- content/p1.md --
-- extra-content/_index.md --
-- extra-content/subdir/_index.md --
-- extra-content/subdir/about.md --
"
`
b := newTestSitesBuilder(t).WithConfigFile("toml", config)
b.WithContent("p1.md", "")
b.WithSourceFile(
"extra-content/_index.md", "",
"extra-content/subdir/_index.md", "",
"extra-content/subdir/about.md", "",
)
b.Build(BuildCfg{})
b := Test(t, files)
b.AssertFileContent("public/resources-a/subdir/about/index.html", "Single")
b.AssertFileContent("public/resources-b/subdir/about/index.html", "Single")
@@ -836,12 +824,7 @@ message: Hugo Rocks
{{ site.Data.extra.test.message }}
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/index.html", "Hugo Rocks")
}
+3
View File
@@ -26,6 +26,7 @@ import (
"github.com/gohugoio/hugo/config/allconfig"
"github.com/gohugoio/hugo/hugofs/glob"
"github.com/gohugoio/hugo/hugolib/doctree"
"github.com/gohugoio/hugo/resources"
"github.com/fsnotify/fsnotify"
@@ -72,6 +73,8 @@ type HugoSites struct {
// Cache for page listings.
cachePages *dynacache.Partition[string, page.Pages]
// Cache for content sources.
cacheContentSource *dynacache.Partition[string, *resources.StaleValue[[]byte]]
// Before Hugo 0.122.0 we managed all translations in a map using a translationKey
// that could be overridden in front matter.
+28 -2
View File
@@ -57,6 +57,13 @@ func TestOptDebug() TestOpt {
}
}
// TestOptWarn will enable warn logging in integration tests.
func TestOptWarn() TestOpt {
return func(c *IntegrationTestConfig) {
c.LogLevel = logg.LevelWarn
}
}
// TestOptWithNFDOnDarwin will normalize the Unicode filenames to NFD on Darwin.
func TestOptWithNFDOnDarwin() TestOpt {
return func(c *IntegrationTestConfig) {
@@ -80,6 +87,15 @@ func Test(t testing.TB, files string, opts ...TestOpt) *IntegrationTestBuilder {
return NewIntegrationTestBuilder(cfg).Build()
}
// TestE is the same as Test, but returns an error instead of failing the test.
func TestE(t testing.TB, files string, opts ...TestOpt) (*IntegrationTestBuilder, error) {
cfg := IntegrationTestConfig{T: t, TxtarString: files}
for _, o := range opts {
o(&cfg)
}
return NewIntegrationTestBuilder(cfg).BuildE()
}
// TestRunning is a convenience method to create a new IntegrationTestBuilder from some files with Running set to true and run a build.
// Deprecated: Use Test with TestOptRunning instead.
func TestRunning(t testing.TB, files string, opts ...TestOpt) *IntegrationTestBuilder {
@@ -90,6 +106,7 @@ func TestRunning(t testing.TB, files string, opts ...TestOpt) *IntegrationTestBu
return NewIntegrationTestBuilder(cfg).Build()
}
// In most cases you should not use this function directly, but the Test or TestRunning function.
func NewIntegrationTestBuilder(conf IntegrationTestConfig) *IntegrationTestBuilder {
// Code fences.
conf.TxtarString = strings.ReplaceAll(conf.TxtarString, "§§§", "```")
@@ -171,9 +188,18 @@ func (b *lockingBuffer) Write(p []byte) (n int, err error) {
return
}
func (s *IntegrationTestBuilder) AssertLogContains(text string) {
func (s *IntegrationTestBuilder) AssertLogContains(els ...string) {
s.Helper()
s.Assert(s.logBuff.String(), qt.Contains, text)
for _, el := range els {
s.Assert(s.logBuff.String(), qt.Contains, el)
}
}
func (s *IntegrationTestBuilder) AssertLogNotContains(els ...string) {
s.Helper()
for _, el := range els {
s.Assert(s.logBuff.String(), qt.Not(qt.Contains), el)
}
}
func (s *IntegrationTestBuilder) AssertLogMatches(expression string) {
+1 -3
View File
@@ -26,7 +26,7 @@ import (
func TestI18n(t *testing.T) {
c := qt.New(t)
//https://github.com/gohugoio/hugo/issues/7804
// https://github.com/gohugoio/hugo/issues/7804
c.Run("pt-br should be case insensitive", func(c *qt.C) {
b := newTestSitesBuilder(c)
langCode := func() string {
@@ -76,12 +76,10 @@ name = "foo-a"
menus := b.H.Sites[0].Menus()
c.Assert(menus, qt.HasLen, 1)
})
}
func TestLanguageNumberFormatting(t *testing.T) {
b := newTestSitesBuilder(t)
b.WithConfigFile("toml", `
baseURL = "https://example.org"
+3 -18
View File
@@ -571,12 +571,7 @@ Page IsAncestor Self: {{ $page.IsAncestor $page }}
Page IsDescendant Self: {{ $page.IsDescendant $page}}
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/tests/index.html", `
Tests|/tests/|IsMenuCurrent = true|HasMenuCurrent = false
@@ -609,12 +604,7 @@ Menu Item: {{ $i }}: {{ .Pre }}{{ .Name }}{{ .Post }}|{{ .URL }}|
{{ end }}
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/index.html", `
Menu Item: 0: <span>Home</span>|/|
@@ -640,12 +630,7 @@ Menu Item: {{ $i }}|{{ .URL }}|
{{ end }}
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/index.html", `
Menu Item: 0|/foo/posts|
+17 -16
View File
@@ -1,4 +1,4 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
// Copyright 2024 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.
@@ -27,6 +27,7 @@ import (
"github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/output/layouts"
"github.com/gohugoio/hugo/related"
"github.com/spf13/afero"
"github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/markup/tableofcontents"
@@ -197,7 +198,7 @@ func (p *pageHeadingsFiltered) page() page.Page {
// For internal use by the related content feature.
func (p *pageState) ApplyFilterToHeadings(ctx context.Context, fn func(*tableofcontents.Heading) bool) related.Document {
r, err := p.content.contentToC(ctx, p.pageOutput.pco)
r, err := p.m.content.contentToC(ctx, p.pageOutput.pco)
if err != nil {
panic(err)
}
@@ -313,14 +314,14 @@ func (p *pageState) Pages() page.Pages {
// RawContent returns the un-rendered source content without
// any leading front matter.
func (p *pageState) RawContent() string {
if p.content.parseInfo.itemsStep2 == nil {
if p.m.content.pi.itemsStep2 == nil {
return ""
}
start := p.content.parseInfo.posMainContent
start := p.m.content.pi.posMainContent
if start == -1 {
start = 0
}
source, err := p.content.contentSource()
source, err := p.m.content.pi.contentSource(p.m.content)
if err != nil {
panic(err)
}
@@ -332,11 +333,11 @@ func (p *pageState) Resources() resource.Resources {
}
func (p *pageState) HasShortcode(name string) bool {
if p.content.shortcodeState == nil {
if p.m.content.shortcodeState == nil {
return false
}
return p.content.shortcodeState.hasName(name)
return p.m.content.shortcodeState.hasName(name)
}
func (p *pageState) Site() page.Site {
@@ -355,8 +356,8 @@ func (p *pageState) IsTranslated() bool {
// TranslationKey returns the key used to identify a translation of this content.
func (p *pageState) TranslationKey() string {
if p.m.translationKey != "" {
return p.m.translationKey
if p.m.pageConfig.TranslationKey != "" {
return p.m.pageConfig.TranslationKey
}
return p.Path()
}
@@ -365,9 +366,9 @@ func (p *pageState) TranslationKey() string {
func (p *pageState) AllTranslations() page.Pages {
key := p.Path() + "/" + "translations-all"
pages, err := p.s.pageMap.getOrCreatePagesFromCache(key, func(string) (page.Pages, error) {
if p.m.translationKey != "" {
if p.m.pageConfig.TranslationKey != "" {
// translationKey set by user.
pas, _ := p.s.h.translationKeyPages.Get(p.m.translationKey)
pas, _ := p.s.h.translationKeyPages.Get(p.m.pageConfig.TranslationKey)
pasc := make(page.Pages, len(pas))
copy(pasc, pas)
page.SortByLanguage(pasc)
@@ -534,7 +535,7 @@ var defaultRenderStringOpts = renderStringOpts{
Markup: "", // Will inherit the page's value when not set.
}
func (p *pageMeta) wrapError(err error) error {
func (p *pageMeta) wrapError(err error, sourceFs afero.Fs) error {
if err == nil {
panic("wrapError with nil")
}
@@ -544,18 +545,18 @@ func (p *pageMeta) wrapError(err error) error {
return fmt.Errorf("%q: %w", p.Path(), err)
}
return hugofs.AddFileInfoToError(err, p.File().FileInfo(), p.s.SourceSpec.Fs.Source)
return hugofs.AddFileInfoToError(err, p.File().FileInfo(), sourceFs)
}
// wrapError adds some more context to the given error if possible/needed
func (p *pageState) wrapError(err error) error {
return p.m.wrapError(err)
return p.m.wrapError(err, p.s.h.SourceFs)
}
func (p *pageState) getContentConverter() converter.Converter {
var err error
p.contentConverterInit.Do(func() {
markup := p.m.markup
markup := p.m.pageConfig.Markup
if markup == "html" {
// Only used for shortcode inner content.
markup = "markdown"
@@ -612,7 +613,7 @@ func (p *pageState) posFromInput(input []byte, offset int) text.Position {
}
func (p *pageState) posOffset(offset int) text.Position {
return p.posFromInput(p.content.mustSource(), offset)
return p.posFromInput(p.m.content.mustSource(), offset)
}
// shiftToOutputFormat is serialized. The output format idx refers to the
-3
View File
@@ -91,9 +91,6 @@ type pageCommon struct {
layoutDescriptor layouts.LayoutDescriptor
layoutDescriptorInit sync.Once
// The source and the parsed page content.
content *cachedContent
// Set if feature enabled and this is in a Git repo.
gitInfo source.GitInfo
codeowners []string
+121 -59
View File
@@ -20,6 +20,7 @@ import (
"fmt"
"html/template"
"io"
"strconv"
"strings"
"unicode/utf8"
@@ -53,9 +54,8 @@ type pageContentReplacement struct {
source pageparser.Item
}
func newCachedContent(m *pageMeta, pid uint64) (*cachedContent, error) {
func (m *pageMeta) parseFrontMatter(h *HugoSites, pid uint64, sourceKey string) (*contentParseInfo, error) {
var openSource hugio.OpenReadSeekCloser
var filename string
if m.f != nil {
meta := m.f.FileInfo().Meta()
openSource = func() (hugio.ReadSeekCloser, error) {
@@ -65,6 +65,44 @@ func newCachedContent(m *pageMeta, pid uint64) (*cachedContent, error) {
}
return r, nil
}
}
if sourceKey == "" {
sourceKey = strconv.Itoa(int(pid))
}
pi := &contentParseInfo{
h: h,
pid: pid,
sourceKey: sourceKey,
openSource: openSource,
}
source, err := pi.contentSource(m)
if err != nil {
return nil, err
}
items, err := pageparser.ParseBytes(
source,
pageparser.Config{},
)
if err != nil {
return nil, err
}
pi.itemsStep1 = items
if err := pi.mapFrontMatter(source); err != nil {
return nil, err
}
return pi, nil
}
func (m *pageMeta) newCachedContent(h *HugoSites, pi *contentParseInfo) (*cachedContent, error) {
var filename string
if m.f != nil {
filename = m.f.Filename()
}
@@ -72,15 +110,11 @@ func newCachedContent(m *pageMeta, pid uint64) (*cachedContent, error) {
pm: m.s.pageMap,
StaleInfo: m,
shortcodeState: newShortcodeHandler(filename, m.s),
parseInfo: &contentParseInfo{
pid: pid,
},
cacheBaseKey: m.pathInfo.PathNoLang(),
openSource: openSource,
enableEmoji: m.s.conf.EnableEmoji,
pi: pi,
enableEmoji: m.s.conf.EnableEmoji,
}
source, err := c.contentSource()
source, err := c.pi.contentSource(m)
if err != nil {
return nil, err
}
@@ -95,23 +129,25 @@ func newCachedContent(m *pageMeta, pid uint64) (*cachedContent, error) {
type cachedContent struct {
pm *pageMap
cacheBaseKey string
// The source bytes.
openSource hugio.OpenReadSeekCloser
resource.StaleInfo
shortcodeState *shortcodeHandler
// Parsed content.
parseInfo *contentParseInfo
pi *contentParseInfo
enableEmoji bool
}
type contentParseInfo struct {
pid uint64
h *HugoSites
pid uint64
sourceKey string
// The source bytes.
openSource hugio.OpenReadSeekCloser
frontMatter map[string]any
// Whether the parsed content contains a summary separator.
@@ -190,25 +226,15 @@ func (pi *contentParseInfo) contentToRender(ctx context.Context, source []byte,
}
func (c *cachedContent) IsZero() bool {
return len(c.parseInfo.itemsStep2) == 0
return len(c.pi.itemsStep2) == 0
}
func (c *cachedContent) parseContentFile(source []byte) error {
if source == nil || c.openSource == nil {
if source == nil || c.pi.openSource == nil {
return nil
}
items, err := pageparser.ParseBytes(
source,
pageparser.Config{},
)
if err != nil {
return err
}
c.parseInfo.itemsStep1 = items
return c.parseInfo.mapItems(source, c.shortcodeState)
return c.pi.mapItemsAfterFrontMatter(source, c.shortcodeState)
}
func (c *contentParseInfo) parseFrontMatter(it pageparser.Item, iter *pageparser.Iterator, source []byte) error {
@@ -242,7 +268,49 @@ func (c *contentParseInfo) parseFrontMatter(it pageparser.Item, iter *pageparser
return nil
}
func (rn *contentParseInfo) mapItems(
func (rn *contentParseInfo) failMap(source []byte, err error, i pageparser.Item) error {
if fe, ok := err.(herrors.FileError); ok {
return fe
}
pos := posFromInput("", source, i.Pos())
return herrors.NewFileErrorFromPos(err, pos)
}
func (rn *contentParseInfo) mapFrontMatter(source []byte) error {
if len(rn.itemsStep1) == 0 {
return nil
}
iter := pageparser.NewIterator(rn.itemsStep1)
Loop:
for {
it := iter.Next()
switch {
case it.IsFrontMatter():
if err := rn.parseFrontMatter(it, iter, source); err != nil {
return err
}
next := iter.Peek()
if !next.IsDone() {
rn.posMainContent = next.Pos()
}
// Done.
break Loop
case it.IsEOF():
break Loop
case it.IsError():
return rn.failMap(source, it.Err, it)
default:
}
}
return nil
}
func (rn *contentParseInfo) mapItemsAfterFrontMatter(
source []byte,
s *shortcodeHandler,
) error {
@@ -273,13 +341,7 @@ Loop:
switch {
case it.Type == pageparser.TypeIgnore:
case it.IsFrontMatter():
if err := rn.parseFrontMatter(it, iter, source); err != nil {
return err
}
next := iter.Peek()
if !next.IsDone() {
rn.posMainContent = next.Pos()
}
// Ignore.
case it.Type == pageparser.TypeLeadSummaryDivider:
posBody := -1
f := func(item pageparser.Item) bool {
@@ -347,16 +409,16 @@ Loop:
}
func (c *cachedContent) mustSource() []byte {
source, err := c.contentSource()
source, err := c.pi.contentSource(c)
if err != nil {
panic(err)
}
return source
}
func (c *cachedContent) contentSource() ([]byte, error) {
key := c.cacheBaseKey
v, err := c.pm.cacheContentSource.GetOrCreate(key, func(string) (*resources.StaleValue[[]byte], error) {
func (c *contentParseInfo) contentSource(s resource.StaleInfo) ([]byte, error) {
key := c.sourceKey
v, err := c.h.cacheContentSource.GetOrCreate(key, func(string) (*resources.StaleValue[[]byte], error) {
b, err := c.readSourceAll()
if err != nil {
return nil, err
@@ -365,7 +427,7 @@ func (c *cachedContent) contentSource() ([]byte, error) {
return &resources.StaleValue[[]byte]{
Value: b,
IsStaleFunc: func() bool {
return c.IsStale()
return s.IsStale()
},
}, nil
})
@@ -376,7 +438,7 @@ func (c *cachedContent) contentSource() ([]byte, error) {
return v.Value, nil
}
func (c *cachedContent) readSourceAll() ([]byte, error) {
func (c *contentParseInfo) readSourceAll() ([]byte, error) {
if c.openSource == nil {
return []byte{}, nil
}
@@ -424,7 +486,7 @@ type contentPlainPlainWords struct {
func (c *cachedContent) contentRendered(ctx context.Context, cp *pageContentOutput) (contentSummary, error) {
ctx = tpl.Context.DependencyScope.Set(ctx, pageDependencyScopeGlobal)
key := c.cacheBaseKey + "/" + cp.po.f.Name
key := c.pi.sourceKey + "/" + cp.po.f.Name
versionv := cp.contentRenderedVersion
v, err := c.pm.cacheContentRendered.GetOrCreate(key, func(string) (*resources.StaleValue[contentSummary], error) {
@@ -447,7 +509,7 @@ func (c *cachedContent) contentRendered(ctx context.Context, cp *pageContentOutp
},
}
if len(c.parseInfo.itemsStep2) == 0 {
if len(c.pi.itemsStep2) == 0 {
// Nothing to do.
return rs, nil
}
@@ -501,8 +563,8 @@ func (c *cachedContent) contentRendered(ctx context.Context, cp *pageContentOutp
var result contentSummary // hasVariants bool
if c.parseInfo.hasSummaryDivider {
isHTML := cp.po.p.m.markup == "html"
if c.pi.hasSummaryDivider {
isHTML := cp.po.p.m.pageConfig.Markup == "html"
if isHTML {
// Use the summary sections as provided by the user.
i := bytes.Index(b, internalSummaryDividerPre)
@@ -510,7 +572,7 @@ func (c *cachedContent) contentRendered(ctx context.Context, cp *pageContentOutp
b = b[i+len(internalSummaryDividerPre):]
} else {
summary, content, err := splitUserDefinedSummaryAndContent(cp.po.p.m.markup, b)
summary, content, err := splitUserDefinedSummaryAndContent(cp.po.p.m.pageConfig.Markup, b)
if err != nil {
cp.po.p.s.Log.Errorf("Failed to set user defined summary for page %q: %s", cp.po.p.pathOrTitle(), err)
} else {
@@ -518,7 +580,7 @@ func (c *cachedContent) contentRendered(ctx context.Context, cp *pageContentOutp
result.summary = helpers.BytesToHTML(summary)
}
}
result.summaryTruncated = c.parseInfo.summaryTruncated
result.summaryTruncated = c.pi.summaryTruncated
}
result.content = helpers.BytesToHTML(b)
rs.Value = result
@@ -543,11 +605,11 @@ func (c *cachedContent) mustContentToC(ctx context.Context, cp *pageContentOutpu
var setGetContentCallbackInContext = hcontext.NewContextDispatcher[func(*pageContentOutput, contentTableOfContents)]("contentCallback")
func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (contentTableOfContents, error) {
key := c.cacheBaseKey + "/" + cp.po.f.Name
key := c.pi.sourceKey + "/" + cp.po.f.Name
versionv := cp.contentRenderedVersion
v, err := c.pm.contentTableOfContents.GetOrCreate(key, func(string) (*resources.StaleValue[contentTableOfContents], error) {
source, err := c.contentSource()
source, err := c.pi.contentSource(c)
if err != nil {
return nil, err
}
@@ -572,7 +634,7 @@ func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (
}
if p.s.conf.Internal.Watch {
for _, s := range cp2.po.p.content.shortcodeState.shortcodes {
for _, s := range cp2.po.p.m.content.shortcodeState.shortcodes {
for _, templ := range s.templs {
cp.trackDependency(templ.(identity.IdentityProvider))
}
@@ -580,7 +642,7 @@ func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (
}
// Transfer shortcode names so HasShortcode works for shortcodes from included pages.
cp.po.p.content.shortcodeState.transferNames(cp2.po.p.content.shortcodeState)
cp.po.p.m.content.shortcodeState.transferNames(cp2.po.p.m.content.shortcodeState)
if cp2.po.p.pageOutputTemplateVariationsState.Load() > 0 {
cp.po.p.pageOutputTemplateVariationsState.Add(1)
}
@@ -589,7 +651,7 @@ func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (
ctx = setGetContentCallbackInContext.Set(ctx, ctxCallback)
var hasVariants bool
ct.contentToRender, hasVariants, err = c.parseInfo.contentToRender(ctx, source, ct.contentPlaceholders)
ct.contentToRender, hasVariants, err = c.pi.contentToRender(ctx, source, ct.contentPlaceholders)
if err != nil {
return nil, err
}
@@ -598,7 +660,7 @@ func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (
p.pageOutputTemplateVariationsState.Add(1)
}
isHTML := cp.po.p.m.markup == "html"
isHTML := cp.po.p.m.pageConfig.Markup == "html"
if !isHTML {
createAndSetToC := func(tocProvider converter.TableOfContentsProvider) {
@@ -661,7 +723,7 @@ func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (
}
func (c *cachedContent) contentPlain(ctx context.Context, cp *pageContentOutput) (contentPlainPlainWords, error) {
key := c.cacheBaseKey + "/" + cp.po.f.Name
key := c.pi.sourceKey + "/" + cp.po.f.Name
versionv := cp.contentRenderedVersion
@@ -681,7 +743,7 @@ func (c *cachedContent) contentPlain(ctx context.Context, cp *pageContentOutput)
result.plain = tpl.StripHTML(string(rendered.content))
result.plainWords = strings.Fields(result.plain)
isCJKLanguage := cp.po.p.m.isCJKLanguage
isCJKLanguage := cp.po.p.m.pageConfig.IsCJKLanguage
if isCJKLanguage {
result.wordCount = 0
@@ -711,8 +773,8 @@ func (c *cachedContent) contentPlain(ctx context.Context, cp *pageContentOutput)
if rendered.summary != "" {
result.summary = rendered.summary
result.summaryTruncated = rendered.summaryTruncated
} else if cp.po.p.m.summary != "" {
b, err := cp.po.contentRenderer.ParseAndRenderContent(ctx, []byte(cp.po.p.m.summary), false)
} else if cp.po.p.m.pageConfig.Summary != "" {
b, err := cp.po.contentRenderer.ParseAndRenderContent(ctx, []byte(cp.po.p.m.pageConfig.Summary), false)
if err != nil {
return nil, err
}
+194 -175
View File
@@ -1,4 +1,4 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
// Copyright 2024 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.
@@ -31,6 +31,7 @@ import (
"github.com/gohugoio/hugo/source"
"github.com/gohugoio/hugo/common/constants"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
@@ -48,13 +49,11 @@ import (
var cjkRe = regexp.MustCompile(`\p{Han}|\p{Hangul}|\p{Hiragana}|\p{Katakana}`)
type pageMeta struct {
kind string // Page kind.
term string // Set for kind == KindTerm.
singular string // Set for kind == KindTerm and kind == KindTaxonomy.
resource.Staler
pageMetaParams
pageMetaFrontMatter
// Set for standalone pages, e.g. robotsTXT.
@@ -66,13 +65,15 @@ type pageMeta struct {
pathInfo *paths.Path // Always set. This the canonical path to the Page.
f *source.File
content *cachedContent // The source and the parsed page content.
s *Site // The site this page belongs to.
}
// Prepare for a rebuild of the data passed in from front matter.
func (m *pageMeta) setMetaPostPrepareRebuild() {
params := xmaps.Clone[map[string]any](m.paramsOriginal)
m.pageMetaParams.params = params
m.pageMetaParams.pageConfig.Params = params
m.pageMetaFrontMatter = pageMetaFrontMatter{}
}
@@ -80,48 +81,28 @@ type pageMetaParams struct {
setMetaPostCount int
setMetaPostCascadeChanged bool
params map[string]any // Params contains configuration defined in the params section of page frontmatter.
cascade map[page.PageMatcher]maps.Params // cascade contains default configuration to be cascaded downwards.
pageConfig *pagemeta.PageConfig
// These are only set in watch mode.
datesOriginal pageMetaDates
datesOriginal pagemeta.Dates
paramsOriginal map[string]any // contains the original params as defined in the front matter.
cascadeOriginal map[page.PageMatcher]maps.Params // contains the original cascade as defined in the front matter.
}
// From page front matter.
type pageMetaFrontMatter struct {
draft bool // Only published when running with -D flag
title string
linkTitle string
summary string
weight int
markup string
contentType string // type in front matter.
isCJKLanguage bool // whether the content is in a CJK language.
layout string
aliases []string
description string
keywords []string
translationKey string // maps to translation(s) of this page.
buildConfig pagemeta.BuildConfig
configuredOutputFormats output.Formats // outputs defiend in front matter.
pageMetaDates // The 4 front matter dates that Hugo cares about.
resourcesMetadata []map[string]any // Raw front matter metadata that is going to be assigned to the page resources.
sitemap config.SitemapConfig // Sitemap overrides from front matter.
urlPaths pagemeta.URLPath
configuredOutputFormats output.Formats // outputs defiend in front matter.
}
func (m *pageMetaParams) init(preserveOringal bool) {
if preserveOringal {
m.paramsOriginal = xmaps.Clone[maps.Params](m.params)
m.cascadeOriginal = xmaps.Clone[map[page.PageMatcher]maps.Params](m.cascade)
m.paramsOriginal = xmaps.Clone[maps.Params](m.pageConfig.Params)
m.cascadeOriginal = xmaps.Clone[map[page.PageMatcher]maps.Params](m.pageConfig.Cascade)
}
}
func (p *pageMeta) Aliases() []string {
return p.aliases
return p.pageConfig.Aliases
}
func (p *pageMeta) Author() page.Author {
@@ -150,8 +131,24 @@ func (p *pageMeta) BundleType() string {
}
}
func (p *pageMeta) Date() time.Time {
return p.pageConfig.Date
}
func (p *pageMeta) PublishDate() time.Time {
return p.pageConfig.PublishDate
}
func (p *pageMeta) Lastmod() time.Time {
return p.pageConfig.Lastmod
}
func (p *pageMeta) ExpiryDate() time.Time {
return p.pageConfig.ExpiryDate
}
func (p *pageMeta) Description() string {
return p.description
return p.pageConfig.Description
}
func (p *pageMeta) Lang() string {
@@ -159,7 +156,7 @@ func (p *pageMeta) Lang() string {
}
func (p *pageMeta) Draft() bool {
return p.draft
return p.pageConfig.Draft
}
func (p *pageMeta) File() *source.File {
@@ -171,20 +168,20 @@ func (p *pageMeta) IsHome() bool {
}
func (p *pageMeta) Keywords() []string {
return p.keywords
return p.pageConfig.Keywords
}
func (p *pageMeta) Kind() string {
return p.kind
return p.pageConfig.Kind
}
func (p *pageMeta) Layout() string {
return p.layout
return p.pageConfig.Layout
}
func (p *pageMeta) LinkTitle() string {
if p.linkTitle != "" {
return p.linkTitle
if p.pageConfig.LinkTitle != "" {
return p.pageConfig.LinkTitle
}
return p.Title()
@@ -194,7 +191,7 @@ func (p *pageMeta) Name() string {
if p.resourcePath != "" {
return p.resourcePath
}
if p.kind == kinds.KindTerm {
if p.pageConfig.Kind == kinds.KindTerm {
return p.pathInfo.Unmormalized().BaseNameNoIdentifier()
}
return p.Title()
@@ -218,7 +215,7 @@ func (p *pageMeta) Param(key any) (any, error) {
}
func (p *pageMeta) Params() maps.Params {
return p.params
return p.pageConfig.Params
}
func (p *pageMeta) Path() string {
@@ -248,18 +245,18 @@ func (p *pageMeta) Section() string {
}
func (p *pageMeta) Sitemap() config.SitemapConfig {
return p.sitemap
return p.pageConfig.Sitemap
}
func (p *pageMeta) Title() string {
return p.title
return p.pageConfig.Title
}
const defaultContentType = "page"
func (p *pageMeta) Type() string {
if p.contentType != "" {
return p.contentType
if p.pageConfig.Type != "" {
return p.pageConfig.Type
}
if sect := p.Section(); sect != "" {
@@ -270,36 +267,56 @@ func (p *pageMeta) Type() string {
}
func (p *pageMeta) Weight() int {
return p.weight
return p.pageConfig.Weight
}
func (ps *pageState) setMetaPre() error {
pm := ps.m
p := ps
frontmatter := p.content.parseInfo.frontMatter
watching := p.s.watching()
func (p *pageMeta) setMetaPre(pi *contentParseInfo, conf config.AllProvider) error {
frontmatter := pi.frontMatter
if frontmatter != nil {
pcfg := p.pageConfig
if pcfg == nil {
panic("pageConfig not set")
}
// Needed for case insensitive fetching of params values
maps.PrepareParams(frontmatter)
pm.pageMetaParams.params = frontmatter
if p.IsNode() {
// Check for any cascade define on itself.
if cv, found := frontmatter["cascade"]; found {
var err error
cascade, err := page.DecodeCascade(cv)
if err != nil {
return err
}
pm.pageMetaParams.cascade = cascade
pcfg.Params = frontmatter
// Check for any cascade define on itself.
if cv, found := frontmatter["cascade"]; found {
var err error
cascade, err := page.DecodeCascade(cv)
if err != nil {
return err
}
pcfg.Cascade = cascade
}
// Look for path, lang and kind, all of which values we need early on.
if v, found := frontmatter["path"]; found {
pcfg.Path = paths.ToSlashPreserveLeading(cast.ToString(v))
pcfg.Params["path"] = pcfg.Path
}
if v, found := frontmatter["lang"]; found {
lang := strings.ToLower(cast.ToString(v))
if _, ok := conf.PathParser().LanguageIndex[lang]; ok {
pcfg.Lang = lang
pcfg.Params["lang"] = pcfg.Lang
}
}
} else if pm.pageMetaParams.params == nil {
pm.pageMetaParams.params = make(maps.Params)
if v, found := frontmatter["kind"]; found {
s := cast.ToString(v)
if s != "" {
pcfg.Kind = kinds.GetKindMain(s)
if pcfg.Kind == "" {
return fmt.Errorf("unknown kind %q in front matter", s)
}
pcfg.Params["kind"] = pcfg.Kind
}
}
} else if p.pageMetaParams.pageConfig.Params == nil {
p.pageConfig.Params = make(maps.Params)
}
pm.pageMetaParams.init(watching)
p.pageMetaParams.init(conf.Watching())
return nil
}
@@ -308,18 +325,18 @@ func (ps *pageState) setMetaPost(cascade map[page.PageMatcher]maps.Params) error
ps.m.setMetaPostCount++
var cascadeHashPre uint64
if ps.m.setMetaPostCount > 1 {
cascadeHashPre = identity.HashUint64(ps.m.cascade)
ps.m.cascade = xmaps.Clone[map[page.PageMatcher]maps.Params](ps.m.cascadeOriginal)
cascadeHashPre = identity.HashUint64(ps.m.pageConfig.Cascade)
ps.m.pageConfig.Cascade = xmaps.Clone[map[page.PageMatcher]maps.Params](ps.m.cascadeOriginal)
}
// Apply cascades first so they can be overriden later.
if cascade != nil {
if ps.m.cascade != nil {
if ps.m.pageConfig.Cascade != nil {
for k, v := range cascade {
vv, found := ps.m.cascade[k]
vv, found := ps.m.pageConfig.Cascade[k]
if !found {
ps.m.cascade[k] = v
ps.m.pageConfig.Cascade[k] = v
} else {
// Merge
for ck, cv := range v {
@@ -329,21 +346,21 @@ func (ps *pageState) setMetaPost(cascade map[page.PageMatcher]maps.Params) error
}
}
}
cascade = ps.m.cascade
cascade = ps.m.pageConfig.Cascade
} else {
ps.m.cascade = cascade
ps.m.pageConfig.Cascade = cascade
}
}
if cascade == nil {
cascade = ps.m.cascade
cascade = ps.m.pageConfig.Cascade
}
if ps.m.setMetaPostCount > 1 {
ps.m.setMetaPostCascadeChanged = cascadeHashPre != identity.HashUint64(ps.m.cascade)
ps.m.setMetaPostCascadeChanged = cascadeHashPre != identity.HashUint64(ps.m.pageConfig.Cascade)
if !ps.m.setMetaPostCascadeChanged {
// No changes, restore any value that may be changed by aggregation.
ps.m.dates = ps.m.datesOriginal.dates
ps.m.pageConfig.Dates = ps.m.datesOriginal
return nil
}
ps.m.setMetaPostPrepareRebuild()
@@ -356,8 +373,8 @@ func (ps *pageState) setMetaPost(cascade map[page.PageMatcher]maps.Params) error
continue
}
for kk, vv := range v {
if _, found := ps.m.params[kk]; !found {
ps.m.params[kk] = vv
if _, found := ps.m.pageConfig.Params[kk]; !found {
ps.m.pageConfig.Params[kk] = vv
}
}
}
@@ -371,7 +388,7 @@ func (ps *pageState) setMetaPost(cascade map[page.PageMatcher]maps.Params) error
}
// Store away any original values that may be changed from aggregation.
ps.m.datesOriginal = ps.m.pageMetaDates
ps.m.datesOriginal = ps.m.pageConfig.Dates
return nil
}
@@ -392,13 +409,8 @@ func (p *pageState) setMetaPostParams() error {
gitAuthorDate = p.gitInfo.AuthorDate
}
pm.pageMetaDates = pageMetaDates{}
pm.urlPaths = pagemeta.URLPath{}
descriptor := &pagemeta.FrontMatterDescriptor{
Params: pm.params,
Dates: &pm.pageMetaDates.dates,
PageURLs: &pm.urlPaths,
PageConfig: pm.pageConfig,
BaseFilename: contentBaseName,
ModTime: mtime,
GitAuthorDate: gitAuthorDate,
@@ -413,17 +425,39 @@ func (p *pageState) setMetaPostParams() error {
p.s.Log.Errorf("Failed to handle dates for page %q: %s", p.pathOrTitle(), err)
}
pm.buildConfig, err = pagemeta.DecodeBuildConfig(pm.params["_build"])
var buildConfig any
if v, ok := pm.pageConfig.Params["_build"]; ok {
buildConfig = v
} else {
buildConfig = pm.pageConfig.Params["build"]
}
pm.pageConfig.Build, err = pagemeta.DecodeBuildConfig(buildConfig)
if err != nil {
return err
}
var sitemapSet bool
pcfg := pm.pageConfig
params := pcfg.Params
var draft, published, isCJKLanguage *bool
for k, v := range pm.params {
var userParams map[string]any
for k, v := range pcfg.Params {
loki := strings.ToLower(k)
if loki == "params" {
vv, err := maps.ToStringMapE(v)
if err != nil {
return err
}
userParams = vv
delete(pcfg.Params, k)
continue
}
if loki == "published" { // Intentionally undocumented
vv, err := cast.ToBoolE(v)
if err == nil {
@@ -439,43 +473,43 @@ func (p *pageState) setMetaPostParams() error {
switch loki {
case "title":
pm.title = cast.ToString(v)
pm.params[loki] = pm.title
pcfg.Title = cast.ToString(v)
params[loki] = pcfg.Title
case "linktitle":
pm.linkTitle = cast.ToString(v)
pm.params[loki] = pm.linkTitle
pcfg.LinkTitle = cast.ToString(v)
params[loki] = pcfg.LinkTitle
case "summary":
pm.summary = cast.ToString(v)
pm.params[loki] = pm.summary
pcfg.Summary = cast.ToString(v)
params[loki] = pcfg.Summary
case "description":
pm.description = cast.ToString(v)
pm.params[loki] = pm.description
pcfg.Description = cast.ToString(v)
params[loki] = pcfg.Description
case "slug":
// Don't start or end with a -
pm.urlPaths.Slug = strings.Trim(cast.ToString(v), "-")
pm.params[loki] = pm.Slug()
pcfg.Slug = strings.Trim(cast.ToString(v), "-")
params[loki] = pm.Slug()
case "url":
url := cast.ToString(v)
if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
return fmt.Errorf("URLs with protocol (http*) not supported: %q. In page %q", url, p.pathOrTitle())
}
pm.urlPaths.URL = url
pm.params[loki] = url
pcfg.URL = url
params[loki] = url
case "type":
pm.contentType = cast.ToString(v)
pm.params[loki] = pm.contentType
pcfg.Type = cast.ToString(v)
params[loki] = pcfg.Type
case "keywords":
pm.keywords = cast.ToStringSlice(v)
pm.params[loki] = pm.keywords
pcfg.Keywords = cast.ToStringSlice(v)
params[loki] = pcfg.Keywords
case "headless":
// Legacy setting for leaf bundles.
// This is since Hugo 0.63 handled in a more general way for all
// pages.
isHeadless := cast.ToBool(v)
pm.params[loki] = isHeadless
params[loki] = isHeadless
if p.File().TranslationBaseName() == "index" && isHeadless {
pm.buildConfig.List = pagemeta.Never
pm.buildConfig.Render = pagemeta.Never
pm.pageConfig.Build.List = pagemeta.Never
pm.pageConfig.Build.Render = pagemeta.Never
}
case "outputs":
o := cast.ToStringSlice(v)
@@ -490,43 +524,42 @@ func (p *pageState) setMetaPostParams() error {
p.s.Log.Errorf("Failed to resolve output formats: %s", err)
} else {
pm.configuredOutputFormats = outFormats
pm.params[loki] = outFormats
params[loki] = outFormats
}
}
case "draft":
draft = new(bool)
*draft = cast.ToBool(v)
case "layout":
pm.layout = cast.ToString(v)
pm.params[loki] = pm.layout
pcfg.Layout = cast.ToString(v)
params[loki] = pcfg.Layout
case "markup":
pm.markup = cast.ToString(v)
pm.params[loki] = pm.markup
pcfg.Markup = cast.ToString(v)
params[loki] = pcfg.Markup
case "weight":
pm.weight = cast.ToInt(v)
pm.params[loki] = pm.weight
pcfg.Weight = cast.ToInt(v)
params[loki] = pcfg.Weight
case "aliases":
pm.aliases = cast.ToStringSlice(v)
for i, alias := range pm.aliases {
pcfg.Aliases = cast.ToStringSlice(v)
for i, alias := range pcfg.Aliases {
if strings.HasPrefix(alias, "http://") || strings.HasPrefix(alias, "https://") {
return fmt.Errorf("http* aliases not supported: %q", alias)
}
pm.aliases[i] = filepath.ToSlash(alias)
pcfg.Aliases[i] = filepath.ToSlash(alias)
}
pm.params[loki] = pm.aliases
params[loki] = pcfg.Aliases
case "sitemap":
p.m.sitemap, err = config.DecodeSitemap(p.s.conf.Sitemap, maps.ToStringMap(v))
pcfg.Sitemap, err = config.DecodeSitemap(p.s.conf.Sitemap, maps.ToStringMap(v))
if err != nil {
return fmt.Errorf("failed to decode sitemap config in front matter: %s", err)
}
pm.params[loki] = p.m.sitemap
sitemapSet = true
case "iscjklanguage":
isCJKLanguage = new(bool)
*isCJKLanguage = cast.ToBool(v)
case "translationkey":
pm.translationKey = cast.ToString(v)
pm.params[loki] = pm.translationKey
pcfg.TranslationKey = cast.ToString(v)
params[loki] = pcfg.TranslationKey
case "resources":
var resources []map[string]any
handled := true
@@ -552,8 +585,7 @@ func (p *pageState) setMetaPostParams() error {
}
if handled {
pm.params[loki] = resources
pm.resourcesMetadata = resources
pcfg.Resources = resources
break
}
fallthrough
@@ -575,47 +607,54 @@ func (p *pageState) setMetaPostParams() error {
for i, u := range vv {
a[i] = cast.ToString(u)
}
pm.params[loki] = a
params[loki] = a
} else {
pm.params[loki] = vv
params[loki] = vv
}
} else {
pm.params[loki] = []string{}
params[loki] = []string{}
}
default:
pm.params[loki] = vv
params[loki] = vv
}
}
}
if !sitemapSet {
pm.sitemap = p.s.conf.Sitemap
for k, v := range userParams {
if _, found := params[k]; found {
p.s.Log.Warnidf(constants.WarnFrontMatterParamsOverrides, "Hugo front matter key %q is overridden in params section.", k)
}
params[strings.ToLower(k)] = v
}
pm.markup = p.s.ContentSpec.ResolveMarkup(pm.markup)
if !sitemapSet {
pcfg.Sitemap = p.s.conf.Sitemap
}
pcfg.Markup = p.s.ContentSpec.ResolveMarkup(pcfg.Markup)
if draft != nil && published != nil {
pm.draft = *draft
pcfg.Draft = *draft
p.m.s.Log.Warnf("page %q has both draft and published settings in its frontmatter. Using draft.", p.File().Filename())
} else if draft != nil {
pm.draft = *draft
pcfg.Draft = *draft
} else if published != nil {
pm.draft = !*published
pcfg.Draft = !*published
}
pm.params["draft"] = pm.draft
params["draft"] = pcfg.Draft
if isCJKLanguage != nil {
pm.isCJKLanguage = *isCJKLanguage
} else if p.s.conf.HasCJKLanguage && p.content.openSource != nil {
if cjkRe.Match(p.content.mustSource()) {
pm.isCJKLanguage = true
pcfg.IsCJKLanguage = *isCJKLanguage
} else if p.s.conf.HasCJKLanguage && p.m.content.pi.openSource != nil {
if cjkRe.Match(p.m.content.mustSource()) {
pcfg.IsCJKLanguage = true
} else {
pm.isCJKLanguage = false
pcfg.IsCJKLanguage = false
}
}
pm.params["iscjklanguage"] = p.m.isCJKLanguage
params["iscjklanguage"] = pcfg.IsCJKLanguage
return nil
}
@@ -628,7 +667,7 @@ func (p *pageMeta) shouldList(global bool) bool {
return false
}
switch p.buildConfig.List {
switch p.pageConfig.Build.List {
case pagemeta.Always:
return true
case pagemeta.Never:
@@ -652,56 +691,56 @@ func (p *pageMeta) shouldBeCheckedForMenuDefinitions() bool {
return false
}
return p.kind == kinds.KindHome || p.kind == kinds.KindSection || p.kind == kinds.KindPage
return p.pageConfig.Kind == kinds.KindHome || p.pageConfig.Kind == kinds.KindSection || p.pageConfig.Kind == kinds.KindPage
}
func (p *pageMeta) noRender() bool {
return p.buildConfig.Render != pagemeta.Always
return p.pageConfig.Build.Render != pagemeta.Always
}
func (p *pageMeta) noLink() bool {
return p.buildConfig.Render == pagemeta.Never
return p.pageConfig.Build.Render == pagemeta.Never
}
func (p *pageMeta) applyDefaultValues() error {
if p.buildConfig.IsZero() {
p.buildConfig, _ = pagemeta.DecodeBuildConfig(nil)
if p.pageConfig.Build.IsZero() {
p.pageConfig.Build, _ = pagemeta.DecodeBuildConfig(nil)
}
if !p.s.conf.IsKindEnabled(p.Kind()) {
(&p.buildConfig).Disable()
(&p.pageConfig.Build).Disable()
}
if p.markup == "" {
if p.pageConfig.Markup == "" {
if p.File() != nil {
// Fall back to file extension
p.markup = p.s.ContentSpec.ResolveMarkup(p.File().Ext())
p.pageConfig.Markup = p.s.ContentSpec.ResolveMarkup(p.File().Ext())
}
if p.markup == "" {
p.markup = "markdown"
if p.pageConfig.Markup == "" {
p.pageConfig.Markup = "markdown"
}
}
if p.title == "" && p.f == nil {
if p.pageConfig.Title == "" && p.f == nil {
switch p.Kind() {
case kinds.KindHome:
p.title = p.s.Title()
p.pageConfig.Title = p.s.Title()
case kinds.KindSection:
sectionName := p.pathInfo.Unmormalized().BaseNameNoIdentifier()
if p.s.conf.PluralizeListTitles {
sectionName = flect.Pluralize(sectionName)
}
p.title = p.s.conf.C.CreateTitle(sectionName)
p.pageConfig.Title = p.s.conf.C.CreateTitle(sectionName)
case kinds.KindTerm:
if p.term != "" {
p.title = p.s.conf.C.CreateTitle(p.term)
p.pageConfig.Title = p.s.conf.C.CreateTitle(p.term)
} else {
panic("term not set")
}
case kinds.KindTaxonomy:
p.title = strings.Replace(p.s.conf.C.CreateTitle(p.pathInfo.Unmormalized().BaseNameNoIdentifier()), "-", " ", -1)
p.pageConfig.Title = strings.Replace(p.s.conf.C.CreateTitle(p.pathInfo.Unmormalized().BaseNameNoIdentifier()), "-", " ", -1)
case kinds.KindStatus404:
p.title = "404 Page not found"
p.pageConfig.Title = "404 Page not found"
}
}
@@ -752,7 +791,7 @@ func (m *pageMeta) outputFormats() output.Formats {
}
func (p *pageMeta) Slug() string {
return p.urlPaths.Slug
return p.pageConfig.Slug
}
func getParam(m resource.ResourceParamsProvider, key string, stringToLower bool) any {
@@ -790,26 +829,6 @@ func getParamToLower(m resource.ResourceParamsProvider, key string) any {
return getParam(m, key, true)
}
type pageMetaDates struct {
dates resource.Dates
}
func (d *pageMetaDates) Date() time.Time {
return d.dates.Date()
}
func (d *pageMetaDates) Lastmod() time.Time {
return d.dates.Lastmod()
}
func (d *pageMetaDates) PublishDate() time.Time {
return d.dates.PublishDate()
}
func (d *pageMetaDates) ExpiryDate() time.Time {
return d.dates.ExpiryDate()
}
func (ps *pageState) initLazyProviders() error {
ps.init.Add(func(ctx context.Context) (any, error) {
pp, err := newPagePaths(ps)
+85 -27
View File
@@ -15,45 +15,106 @@ package hugolib
import (
"fmt"
"path/filepath"
"sync"
"sync/atomic"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/lazy"
"github.com/gohugoio/hugo/resources/kinds"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/page/pagemeta"
)
var pageIDCounter atomic.Uint64
func (h *HugoSites) newPage(m *pageMeta) (*pageState, error) {
if m.pathInfo == nil {
func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) {
m.Staler = &resources.AtomicStaler{}
if m.pageConfig == nil {
m.pageMetaParams = pageMetaParams{
pageConfig: &pagemeta.PageConfig{
Params: maps.Params{},
},
}
}
var sourceKey string
if m.f != nil {
sourceKey = filepath.ToSlash(m.f.Filename())
}
pid := pageIDCounter.Add(1)
pi, err := m.parseFrontMatter(h, pid, sourceKey)
if err != nil {
return nil, nil, err
}
if err := m.setMetaPre(pi, h.Conf); err != nil {
return nil, nil, m.wrapError(err, h.BaseFs.SourceFs)
}
pcfg := m.pageConfig
if pcfg.Path != "" {
s := m.pageConfig.Path
if !paths.HasExt(s) {
var (
isBranch bool
ext string = "md"
)
if pcfg.Kind != "" {
isBranch = kinds.IsBranch(pcfg.Kind)
} else if m.pathInfo != nil {
isBranch = m.pathInfo.IsBranchBundle()
if m.pathInfo.Ext() != "" {
ext = m.pathInfo.Ext()
}
} else if m.f != nil {
pi := m.f.FileInfo().Meta().PathInfo
isBranch = pi.IsBranchBundle()
if pi.Ext() != "" {
ext = pi.Ext()
}
}
if isBranch {
s += "/_index." + ext
} else {
s += "/index." + ext
}
}
m.pathInfo = h.Conf.PathParser().Parse(files.ComponentFolderContent, s)
} else if m.pathInfo == nil {
if m.f != nil {
m.pathInfo = m.f.FileInfo().Meta().PathInfo
}
if m.pathInfo == nil {
panic(fmt.Sprintf("missing pathInfo in %v", m))
}
}
m.Staler = &resources.AtomicStaler{}
ps, err := func() (*pageState, error) {
if m.s == nil {
// Identify the Site/language to associate this Page with.
var lang string
if m.f != nil {
if pcfg.Lang != "" {
lang = pcfg.Lang
} else if m.f != nil {
meta := m.f.FileInfo().Meta()
lang = meta.Lang
m.s = h.Sites[meta.LangIndex]
} else {
lang = m.pathInfo.Lang()
}
if lang == "" {
lang = h.Conf.DefaultContentLanguage()
}
var found bool
for _, ss := range h.Sites {
if ss.Lang() == lang {
@@ -62,51 +123,49 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, error) {
break
}
}
if !found {
return nil, fmt.Errorf("no site found for language %q", lang)
}
}
// Identify Page Kind.
if m.kind == "" {
m.kind = kinds.KindSection
if m.pageConfig.Kind == "" {
m.pageConfig.Kind = kinds.KindSection
if m.pathInfo.Base() == "/" {
m.kind = kinds.KindHome
m.pageConfig.Kind = kinds.KindHome
} else if m.pathInfo.IsBranchBundle() {
// A section, taxonomy or term.
tc := m.s.pageMap.cfg.getTaxonomyConfig(m.Path())
if !tc.IsZero() {
// Either a taxonomy or a term.
if tc.pluralTreeKey == m.Path() {
m.kind = kinds.KindTaxonomy
m.pageConfig.Kind = kinds.KindTaxonomy
} else {
m.kind = kinds.KindTerm
m.pageConfig.Kind = kinds.KindTerm
}
}
} else if m.f != nil {
m.kind = kinds.KindPage
m.pageConfig.Kind = kinds.KindPage
}
}
if m.kind == kinds.KindPage && !m.s.conf.IsKindEnabled(m.kind) {
if m.pageConfig.Kind == kinds.KindPage && !m.s.conf.IsKindEnabled(m.pageConfig.Kind) {
return nil, nil
}
pid := pageIDCounter.Add(1)
// Parse page content.
cachedContent, err := newCachedContent(m, pid)
if err != nil {
return nil, m.wrapError(err)
}
var dependencyManager identity.Manager = identity.NopManager
if m.s.conf.Internal.Watch {
dependencyManager = identity.NewManager(m.Path())
}
// Parse the rest of the page content.
m.content, err = m.newCachedContent(h, pi)
if err != nil {
return nil, m.wrapError(err, h.SourceFs)
}
ps := &pageState{
pid: pid,
pageOutput: nopPageOutput,
@@ -115,7 +174,6 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, error) {
Staler: m,
dependencyManager: dependencyManager,
pageCommon: &pageCommon{
content: cachedContent,
FileProvider: m,
AuthorProvider: m,
Scratcher: maps.NewScratcher(),
@@ -168,10 +226,6 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, error) {
ps.ShortcodeInfoProvider = ps
ps.AlternativeOutputFormatsProvider = ps
if err := ps.setMetaPre(); err != nil {
return nil, ps.wrapError(err)
}
if err := ps.initLazyProviders(); err != nil {
return nil, ps.wrapError(err)
}
@@ -182,5 +236,9 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, error) {
m.MarkStale()
}
return ps, err
if ps == nil {
return nil, nil, err
}
return ps, ps.PathInfo(), err
}
+1 -1
View File
@@ -127,7 +127,7 @@ func createTargetPathDescriptor(p *pageState) (page.TargetPathDescriptor, error)
Section: pageInfoCurrentSection,
UglyURLs: s.h.Conf.IsUglyURLs(p.Section()),
ForcePrefix: s.h.Conf.IsMultihost() || alwaysInSubDir,
URL: pm.urlPaths.URL,
URL: pm.pageConfig.URL,
}
if pm.Slug() != "" {
+29 -13
View File
@@ -104,12 +104,12 @@ func (pco *pageContentOutput) Reset() {
}
func (pco *pageContentOutput) Fragments(ctx context.Context) *tableofcontents.Fragments {
return pco.po.p.content.mustContentToC(ctx, pco).tableOfContents
return pco.po.p.m.content.mustContentToC(ctx, pco).tableOfContents
}
func (pco *pageContentOutput) RenderShortcodes(ctx context.Context) (template.HTML, error) {
content := pco.po.p.content
source, err := content.contentSource()
content := pco.po.p.m.content
source, err := content.pi.contentSource(content)
if err != nil {
return "", err
}
@@ -125,7 +125,7 @@ func (pco *pageContentOutput) RenderShortcodes(ctx context.Context) (template.HT
insertPlaceholders = true
}
c := make([]byte, 0, len(source)+(len(source)/10))
for _, it := range content.parseInfo.itemsStep2 {
for _, it := range content.pi.itemsStep2 {
switch v := it.(type) {
case pageparser.Item:
c = append(c, source[v.Pos():v.Pos()+len(v.Val(source))]...)
@@ -169,12 +169,12 @@ func (pco *pageContentOutput) RenderShortcodes(ctx context.Context) (template.HT
}
func (pco *pageContentOutput) Content(ctx context.Context) (any, error) {
r, err := pco.po.p.content.contentRendered(ctx, pco)
r, err := pco.po.p.m.content.contentRendered(ctx, pco)
return r.content, err
}
func (pco *pageContentOutput) TableOfContents(ctx context.Context) template.HTML {
return pco.po.p.content.mustContentToC(ctx, pco).tableOfContentsHTML
return pco.po.p.m.content.mustContentToC(ctx, pco).tableOfContentsHTML
}
func (p *pageContentOutput) Len(ctx context.Context) int {
@@ -182,7 +182,7 @@ func (p *pageContentOutput) Len(ctx context.Context) int {
}
func (pco *pageContentOutput) mustContentRendered(ctx context.Context) contentSummary {
r, err := pco.po.p.content.contentRendered(ctx, pco)
r, err := pco.po.p.m.content.contentRendered(ctx, pco)
if err != nil {
pco.fail(err)
}
@@ -190,7 +190,7 @@ func (pco *pageContentOutput) mustContentRendered(ctx context.Context) contentSu
}
func (pco *pageContentOutput) mustContentPlain(ctx context.Context) contentPlainPlainWords {
r, err := pco.po.p.content.contentPlain(ctx, pco)
r, err := pco.po.p.m.content.contentPlain(ctx, pco)
if err != nil {
pco.fail(err)
}
@@ -270,7 +270,7 @@ func (pco *pageContentOutput) RenderString(ctx context.Context, args ...any) (te
}
conv := pco.po.p.getContentConverter()
if opts.Markup != "" && opts.Markup != pco.po.p.m.markup {
if opts.Markup != "" && opts.Markup != pco.po.p.m.pageConfig.Markup {
var err error
conv, err = pco.po.p.m.newContentConverter(pco.po.p, opts.Markup)
if err != nil {
@@ -281,6 +281,7 @@ func (pco *pageContentOutput) RenderString(ctx context.Context, args ...any) (te
var rendered []byte
parseInfo := &contentParseInfo{
h: pco.po.p.s.h,
pid: pco.po.p.pid,
}
@@ -293,7 +294,7 @@ func (pco *pageContentOutput) RenderString(ctx context.Context, args ...any) (te
}
s := newShortcodeHandler(pco.po.p.pathOrTitle(), pco.po.p.s)
if err := parseInfo.mapItems(contentToRenderb, s); err != nil {
if err := parseInfo.mapItemsAfterFrontMatter(contentToRenderb, s); err != nil {
return "", err
}
@@ -320,7 +321,7 @@ func (pco *pageContentOutput) RenderString(ctx context.Context, args ...any) (te
tokenHandler := func(ctx context.Context, token string) ([]byte, error) {
if token == tocShortcodePlaceholder {
toc, err := pco.po.p.content.contentToC(ctx, pco)
toc, err := pco.po.p.m.content.contentToC(ctx, pco)
if err != nil {
return nil, err
}
@@ -350,7 +351,7 @@ func (pco *pageContentOutput) RenderString(ctx context.Context, args ...any) (te
}
// We need a consolidated view in $page.HasShortcode
pco.po.p.content.shortcodeState.transferNames(s)
pco.po.p.m.content.shortcodeState.transferNames(s)
} else {
c, err := pco.renderContentWithConverter(ctx, conv, []byte(contentToRender), false)
@@ -411,7 +412,7 @@ func (pco *pageContentOutput) initRenderHooks() error {
var renderCacheMu sync.Mutex
resolvePosition := func(ctx any) text.Position {
source := pco.po.p.content.mustSource()
source := pco.po.p.m.content.mustSource()
var offset int
switch v := ctx.(type) {
@@ -469,6 +470,21 @@ func (pco *pageContentOutput) initRenderHooks() error {
if err != nil {
panic(err)
}
if found {
if isitp, ok := templ.(tpl.IsInternalTemplateProvider); ok && isitp.IsInternalTemplate() {
renderHookConfig := pco.po.p.s.conf.Markup.Goldmark.RenderHooks
switch templ.Name() {
case "_default/_markup/render-link.html":
if !renderHookConfig.Link.IsEnableDefault() {
return nil, false
}
case "_default/_markup/render-image.html":
if !renderHookConfig.Image.IsEnableDefault() {
return nil, false
}
}
}
}
return templ, found
}
+1 -6
View File
@@ -698,12 +698,7 @@ title: "empty"
|{{ .RawContent }}|
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/basic/index.html", "|**basic**|")
b.AssertFileContent("public/empty/index.html", "! title")
+1 -1
View File
@@ -56,7 +56,7 @@ func (c *pageFinder) getPageRef(context page.Page, ref string) (page.Page, error
}
func (c *pageFinder) getPage(context page.Page, ref string) (page.Page, error) {
n, err := c.getContentNode(context, false, filepath.ToSlash(ref))
n, err := c.getContentNode(context, false, paths.ToSlashTrimTrailing(ref))
if err != nil {
return nil, err
}
+8
View File
@@ -413,6 +413,10 @@ title: p2
func TestPageGetPageVariations(t *testing.T) {
files := `
-- hugo.toml --
-- content/s1/_index.md --
---
title: s1 section
---
-- content/s1/p1/index.md --
---
title: p1
@@ -430,6 +434,8 @@ title: p3
title: p2_root
---
-- layouts/index.html --
/s1: {{ with .GetPage "/s1" }}{{ .Title }}{{ end }}|
/s1/: {{ with .GetPage "/s1/" }}{{ .Title }}{{ end }}|
/s1/p2.md: {{ with .GetPage "/s1/p2.md" }}{{ .Title }}{{ end }}|
/s1/p2: {{ with .GetPage "/s1/p2" }}{{ .Title }}{{ end }}|
/s1/p1/index.md: {{ with .GetPage "/s1/p1/index.md" }}{{ .Title }}{{ end }}|
@@ -444,6 +450,8 @@ p1/index.md: {{ with .GetPage "p1/index.md" }}{{ .Title }}{{ end }}|
b := Test(t, files)
b.AssertFileContent("public/index.html", `
/s1: s1 section|
/s1/: s1 section|
/s1/p2.md: p2|
/s1/p2: p2|
/s1/p1/index.md: p1|
+1 -6
View File
@@ -153,12 +153,7 @@ Len: {{ len $empty }}: Type: {{ printf "%T" $empty }}
{{ $pag := .Paginate $pgs }}
Len Pag: {{ len $pag.Pages }}
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/index.html", "Len: 0", "Len Pag: 0")
}
+183
View File
@@ -0,0 +1,183 @@
// Copyright 2024 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 hugolib
import (
"testing"
qt "github.com/frankban/quicktest"
)
func TestFrontMatterParamsInItsOwnSection(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "https://example.org/"
-- content/_index.md --
+++
title = "Home"
[[cascade]]
background = 'yosemite.jpg'
[cascade.params]
a = "home-a"
b = "home-b"
[cascade._target]
kind = 'page'
+++
-- content/p1.md --
---
title: "P1"
summary: "frontmatter.summary"
params:
a: "p1-a"
summary: "params.summary"
---
-- layouts/_default/single.html --
Params: {{ range $k, $v := .Params }}{{ $k }}: {{ $v }}|{{ end }}$
Summary: {{ .Summary }}|
`
b := Test(t, files)
b.AssertFileContent("public/p1/index.html",
"Params: a: p1-a|b: home-b|background: yosemite.jpg|draft: false|iscjklanguage: false|summary: params.summary|title: P1|$",
"Summary: frontmatter.summary|",
)
}
func TestFrontMatterParamsKindPath(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "https://example.org/"
disableKinds = ["taxonomy", "term"]
-- content/p1.md --
---
title: "P1"
date: 2019-08-07
path: "/a/b/c"
slug: "s1"
---
-- content/mysection.md --
---
title: "My Section"
kind: "section"
date: 2022-08-07
path: "/a/b"
---
-- layouts/index.html --
RegularPages: {{ range site.RegularPages }}{{ .Path }}|{{ .RelPermalink }}|{{ .Title }}|{{ .Date.Format "2006-02-01" }}| Slug: {{ .Params.slug }}|{{ end }}$
Sections: {{ range site.Sections }}{{ .Path }}|{{ .RelPermalink }}|{{ .Title }}|{{ .Date.Format "2006-02-01" }}| Slug: {{ .Params.slug }}|{{ end }}$
{{ $ab := site.GetPage "a/b" }}
a/b pages: {{ range $ab.RegularPages }}{{ .Path }}|{{ .RelPermalink }}|{{ end }}$
`
b := Test(t, files)
b.AssertFileContent("public/index.html",
"RegularPages: /a/b/c|/a/b/s1/|P1|2019-07-08| Slug: s1|$",
"Sections: /a|/a/|As",
"a/b pages: /a/b/c|/a/b/s1/|$",
)
}
func TestFrontMatterParamsLang(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "https://example.org/"
disableKinds = ["taxonomy", "term"]
defaultContentLanguage = "en"
defaultContentLanguageInSubdir = true
[languages]
[languages.en]
weight = 1
[languages.nn]
weight = 2
-- content/p1.md --
---
title: "P1 nn"
lang: "nn"
---
-- content/p2.md --
---
title: "P2"
---
-- layouts/index.html --
RegularPages: {{ range site.RegularPages }}{{ .Path }}|{{ .RelPermalink }}|{{ .Title }}|{{ end }}$
`
b := Test(t, files)
b.AssertFileContent("public/en/index.html",
"RegularPages: /p2|/en/p2/|P2|$",
)
b.AssertFileContent("public/nn/index.html",
"RegularPages: /p1|/nn/p1/|P1 nn|$",
)
}
func TestFrontMatterTitleOverrideWarn(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "https://example.org/"
disableKinds = ["taxonomy", "term"]
-- content/p1.md --
---
title: "My title"
params:
title: "My title from params"
---
`
b := Test(t, files, TestOptWarn())
b.AssertLogContains("ARN Hugo front matter key \"title\" is overridden in params section", "You can suppress this warning")
}
func TestFrontMatterParamsLangNoCascade(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "https://example.org/"
disableKinds = ["taxonomy", "term"]
defaultContentLanguage = "en"
defaultContentLanguageInSubdir = true
[languages]
[languages.en]
weight = 1
[languages.nn]
weight = 2
-- content/_index.md --
+++
[[cascade]]
background = 'yosemite.jpg'
lang = 'nn'
+++
`
b, err := TestE(t, files)
b.Assert(err, qt.IsNotNil)
}
+2 -12
View File
@@ -67,12 +67,7 @@ HasShortcode not found: {{ .HasShortcode "notfound" }}|
Content: {{ .Content }}|
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/p1/index.html",
"Fragments: [p1-h1 p2-h1 p2-h2 p2-h3 p2-withmarkdown p3-h1 p3-h2 p3-withmarkdown]|",
@@ -118,12 +113,7 @@ JSON: {{ .Content }}
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/p1/index.html", "Myshort HTML")
b.AssertFileContent("public/p1/index.json", "Myshort JSON")
+2 -12
View File
@@ -172,12 +172,7 @@ Has other: {{ .HasShortcode "other" }}
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/index.html",
`
@@ -213,12 +208,7 @@ title: "P1"
{{ .Content }}
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/p1/index.html", `TableOfContents`)
}
+2 -2
View File
@@ -315,7 +315,7 @@ func prepareShortcode(
isRenderString bool,
) (shortcodeRenderer, error) {
toParseErr := func(err error) error {
source := p.content.mustSource()
source := p.m.content.mustSource()
return p.parseError(fmt.Errorf("failed to render shortcode %q: %w", sc.name, err), source, sc.pos)
}
@@ -443,7 +443,7 @@ func doRenderShortcode(
// unchanged.
// 2 If inner does not have a newline, strip the wrapping <p> block and
// the newline.
switch p.m.markup {
switch p.m.pageConfig.Markup {
case "", "markdown":
if match, _ := regexp.MatchString(innerNewlineRegexp, inner); !match {
cleaner, err := regexp.Compile(innerCleanupRegexp)
+4 -24
View File
@@ -916,12 +916,7 @@ title: "p1"
{{ .Content }}
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/p1/index.html", `
<x
@@ -957,12 +952,7 @@ title: "p1"
{{ .Content }}
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/p1/index.html", "<ol>\n<li>\n<p>List 1</p>\n<ol>\n<li>Item Mark1 1</li>\n<li>Item Mark1 2</li>\n<li>Item Mark2 1</li>\n<li>Item Mark2 2\n<ol>\n<li>Item Mark2 2-1</li>\n</ol>\n</li>\n<li>Item Mark2 3</li>\n</ol>\n</li>\n</ol>")
}
@@ -987,12 +977,7 @@ echo "foo";
{{ .Content }}
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/p1/index.html", "<pre><code>echo &quot;foo&quot;;\n</code></pre>")
}
@@ -1023,12 +1008,7 @@ title: "p1"
{{ .Content }}
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/p1/index.html", `
<pre><code> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">line 1<span class="p">;</span>
+3 -1
View File
@@ -40,6 +40,7 @@ import (
"github.com/gohugoio/hugo/navigation"
"github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/publisher"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/page/pagemeta"
"github.com/gohugoio/hugo/resources/page/siteidentities"
@@ -123,7 +124,7 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
Stdout: cfg.LogOut,
Stderr: cfg.LogOut,
StoreErrors: conf.Running(),
SuppressStatements: conf.IgnoredErrors(),
SuppressStatements: conf.IgnoredLogs(),
}
logger = loggers.New(logOpts)
}
@@ -281,6 +282,7 @@ func newHugoSites(cfg deps.DepsCfg, d *deps.Deps, pageTrees *pageTrees, sites []
page.Pages](d.MemCache, "/pags/all",
dynacache.OptionsPartition{Weight: 10, ClearWhen: dynacache.ClearOnRebuild},
),
cacheContentSource: dynacache.GetOrCreatePartition[string, *resources.StaleValue[[]byte]](d.MemCache, "/cont/src", dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
translationKeyPages: maps.NewSliceCache[page.Page](),
currentSite: sites[0],
skipRebuildForFilenames: make(map[string]bool),
+1 -1
View File
@@ -129,7 +129,7 @@ func pageRenderer(
continue
}
if p.m.buildConfig.PublishResources {
if p.m.pageConfig.Build.PublishResources {
if err := p.renderResources(); err != nil {
s.SendError(p.errorf(err, "failed to render page resources"))
continue
+3 -18
View File
@@ -436,12 +436,7 @@ MainSections Site method: {{ site.MainSections }}|
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/index.html", `
MainSections Params: [a b]|
@@ -469,12 +464,7 @@ MainSections Site method: {{ site.MainSections }}|
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/index.html", `
MainSections Params: [a b]|
@@ -497,12 +487,7 @@ MainSections Site method: {{ site.MainSections }}|
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/index.html", `
MainSections Params: [mysect]|
+1 -6
View File
@@ -128,12 +128,7 @@ SectionsEntries: {{ .SectionsEntries }}
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/withfile/index.html", "SectionsEntries: [withfile]")
b.AssertFileContent("public/withoutfile/index.html", "SectionsEntries: [withoutfile]")
+3 -18
View File
@@ -39,12 +39,7 @@ title: doc2
Doc2
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/sitemap.xml", " <loc>https://example.com/sect/doc1/</loc>", "doc2")
}
@@ -81,12 +76,7 @@ title: doc2
Doc2
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/sitemap.xml", "<loc>https://example.com/en/sitemap.xml</loc>", "<loc>https://example.com/nn/sitemap.xml</loc>")
b.AssertFileContent("public/en/sitemap.xml", " <loc>https://example.com/sect/doc1/</loc>", "doc2")
@@ -109,12 +99,7 @@ outputs: [ "html", "amp" ]
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
// Should link to the HTML version.
b.AssertFileContent("public/sitemap.xml", " <loc>https://example.com/blog/html-amp/</loc>")
+1 -6
View File
@@ -730,12 +730,7 @@ tags_weight: 40
---
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := Test(t, files)
b.AssertFileContent("public/index.html", `:/p1/|/p3/|/p2/|:`)
}
@@ -44,12 +44,7 @@ l1: {{ i18n "l1" }}|l2: {{ i18n "l2" }}|l3: {{ i18n "l3" }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/index.html", `
l1: l1main|l2: l2main|l3: l3theme
@@ -92,12 +87,7 @@ i18n: {{ i18n "a" . }}|
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/index.html", `
i18n: Reading time: 3|
@@ -131,12 +121,7 @@ title: home_es
---
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/es/index.html", `home_es_gato`)
b.AssertFileContent("public/fr/index.html", `home_fr_gato`)
-3
View File
@@ -23,7 +23,6 @@ import (
)
func TestCollator(t *testing.T) {
c := qt.New(t)
var wg sync.WaitGroup
@@ -43,7 +42,6 @@ func TestCollator(t *testing.T) {
}()
}
wg.Wait()
}
func BenchmarkCollator(b *testing.B) {
@@ -75,5 +73,4 @@ func BenchmarkCollator(b *testing.B) {
}
})
})
}
-1
View File
@@ -234,5 +234,4 @@ func TestResetError(t *testing.T) {
r = true
_, err = i.Do(context.Background())
c.Assert(err, qt.IsNil)
}
+9 -32
View File
@@ -185,42 +185,15 @@ func TestRace() error {
// Run gofmt linter
func Fmt() error {
if !isGoLatest() {
if !isGoLatest() && !isUnix() {
return nil
}
pkgs, err := hugoPackages()
s, err := sh.Output("./check_gofmt.sh")
if err != nil {
return err
}
failed := false
first := true
for _, pkg := range pkgs {
files, err := filepath.Glob(filepath.Join(pkg, "*.go"))
if err != nil {
return nil
}
for _, f := range files {
// gofmt doesn't exit with non-zero when it finds unformatted code
// so we have to explicitly look for output, and if we find any, we
// should fail this target.
s, err := sh.Output("gofmt", "-l", f)
if err != nil {
fmt.Printf("ERROR: running gofmt on %q: %v\n", f, err)
failed = true
}
if s != "" {
if first {
fmt.Println("The following files are not gofmt'ed:")
first = false
}
failed = true
fmt.Println(s)
}
}
}
if failed {
return errors.New("improperly formatted go files")
fmt.Println(s)
return fmt.Errorf("gofmt needs to be run: %s", err)
}
return nil
}
@@ -332,6 +305,10 @@ func isGoLatest() bool {
return strings.Contains(runtime.Version(), "1.21")
}
func isUnix() bool {
return runtime.GOOS != "windows"
}
func isCI() bool {
return os.Getenv("CI") != ""
}
@@ -87,13 +87,7 @@ echo "l8";
§§§
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: false,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html", `
Goat SVG:<svg class='diagram'
@@ -152,13 +146,7 @@ fmt.Println("Hello, World!");
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: false,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html",
"Inner: |<span class=\"line\"><span class=\"cl\"><span class=\"nx\">fmt</span><span class=\"p\">.</span><span class=\"nf\">Println</span><span class=\"p\">(</span><span class=\"s\">&#34;Hello, World!&#34;</span><span class=\"p\">);</span></span></span>|",
@@ -188,13 +176,7 @@ title: "p1"
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: false,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html", `
# Issue 9627: For the Position in code blocks we try to match the .Inner with the original source. This isn't always possible.
@@ -223,13 +205,7 @@ echo "p1";
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: false,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html", "|echo \"p1\";|")
}
@@ -257,12 +233,7 @@ Position: {{ .Position | safeHTML }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html", filepath.FromSlash("Position: \"/content/p1.md:7:1\""))
}
@@ -290,12 +261,7 @@ Hello, World!
Attributes: {{ .Attributes }}|Type: {{ .Type }}|
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html", "<h2 id=\"issue-10118\">Issue 10118</h2>\nAttributes: map[foo:bar]|Type: |")
}
+30 -1
View File
@@ -73,10 +73,39 @@ var Default = Config{
// Config configures Goldmark.
type Config struct {
DuplicateResourceFiles bool
Renderer Renderer
Parser Parser
Extensions Extensions
DuplicateResourceFiles bool
RenderHooks RenderHooks
}
// RenderHooks contains configuration for Goldmark render hooks.
type RenderHooks struct {
Image ImageRenderHook
Link LinkRenderHook
}
// ImageRenderHook contains configuration for the image render hook.
type ImageRenderHook struct {
// Enable the default image render hook.
// We need to know if it is set or not, hence the pointer.
EnableDefault *bool
}
func (h ImageRenderHook) IsEnableDefault() bool {
return h.EnableDefault != nil && *h.EnableDefault
}
// LinkRenderHook contains configuration for the link render hook.
type LinkRenderHook struct {
// Disable the default image render hook.
// We need to know if it is set or not, hence the pointer.
EnableDefault *bool
}
func (h LinkRenderHook) IsEnableDefault() bool {
return h.EnableDefault != nil && *h.EnableDefault
}
type Extensions struct {
@@ -50,13 +50,7 @@ foo
{{ .Content }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: false,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html", `
<h2 class="a" id="heading">
@@ -85,13 +79,7 @@ title: "p1"
>{{ .Text | safeHTML }}</h{{ .Level }}>
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: false,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html", `
<h2 data-foo="bar" id="heading">Heading</h2>
@@ -111,13 +99,7 @@ title: "p1"
{{ .Content }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: false,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html", `
class="a &lt; b"
@@ -141,13 +123,7 @@ safeHTML: |{{- range $k, $v := .Attributes -}}{{ $k }}: {{ $v | safeHTML }}|{{ e
{{ .Content }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: false,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html", `
plain: |class: Smith &amp; Wesson|id: heading-attribute-which-needs-escaping|
@@ -178,13 +154,7 @@ title: "p1"
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: false,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html",
"<h2 id=\"hello-testhttpsexamplecom\">\n Hello <a href=\"https://example.com\">Test</a>\n\n <a class=\"anchor\" href=\"#hello-testhttpsexamplecom\">#</a>\n</h2>",
@@ -252,12 +222,7 @@ LINE8
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html",
"<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"line\"><span class=\"cl\">LINE1\n</span></span></code></pre></div>",
@@ -397,7 +362,6 @@ FENCE
runBenchmark(files, b)
})
}
// Iisse #8959
@@ -406,7 +370,6 @@ func TestHookInfiniteRecursion(t *testing.T) {
for _, renderFunc := range []string{"markdownify", ".Page.RenderString"} {
t.Run(renderFunc, func(t *testing.T) {
files := `
-- config.toml --
-- layouts/_default/_markup/render-link.html --
@@ -436,11 +399,8 @@ a@b.com
b.Assert(err, qt.IsNotNil)
b.Assert(err.Error(), qt.Contains, "text is already rendered, repeating it may cause infinite recursion")
})
}
}
// Issue 9594
@@ -460,12 +420,7 @@ title: "p1"
{{ .Content }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html", `
<img src="b.jpg" alt="&quot;a&quot;">
@@ -476,7 +431,6 @@ func TestLinkifyProtocol(t *testing.T) {
t.Parallel()
runTest := func(protocol string, withHook bool) *hugolib.IntegrationTestBuilder {
files := `
-- config.toml --
[markup.goldmark]
@@ -507,7 +461,6 @@ Link https procol: https://www.example.org
TxtarString: files,
},
).Build()
}
for _, withHook := range []bool{false, true} {
@@ -564,12 +517,7 @@ a <!-- b --> c
{{ .Content }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContentExact("public/p1/index.html",
// Issue 9650
@@ -621,12 +569,7 @@ sc3_begin|{{ .Inner }}|sc3_end
{{ .Content }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContentExact("public/p1/index.html",
// Issue #7332
@@ -657,12 +600,7 @@ title: "p1"
{{ .Content }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContentExact("public/p1/index.html", "<p>:x:</p>")
}
@@ -680,12 +618,7 @@ title: "p1"
{{ .Content }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContentExact("public/p1/index.html", "<p>:x:</p>")
}
@@ -45,13 +45,7 @@ This is an inline image: ![Inline Image](/inline.jpg). Some more text.
<img src="{{ .Destination | safeURL }}" alt="{{ .Text }}|{{ .Ordinal }}" />
{{ end }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: false,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html",
"This is an inline image: \n\t<img src=\"/inline.jpg\" alt=\"Inline Image|0\" />\n. Some more text.</p>",
@@ -70,13 +64,7 @@ This is an inline image: ![Inline Image](/inline.jpg). Some more text.
<img src="{{ .Destination | safeURL }}" alt="{{ .Text }}" />
{{ end }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: false,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html",
"This is an inline image: \n\t<img src=\"/inline.jpg\" alt=\"Inline Image\" />\n. Some more text.</p>",
@@ -86,26 +74,14 @@ This is an inline image: ![Inline Image](/inline.jpg). Some more text.
t.Run("No Hook, no wrap", func(t *testing.T) {
files := strings.ReplaceAll(filesTemplate, "CONFIG_VALUE", "false")
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: false,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html", "<p>This is an inline image: <img src=\"/inline.jpg\" alt=\"Inline Image\">. Some more text.</p>\n<img src=\"/block.jpg\" alt=\"Block Image\" class=\"b\">")
})
t.Run("No Hook, wrap", func(t *testing.T) {
files := strings.ReplaceAll(filesTemplate, "CONFIG_VALUE", "true")
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: false,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html", "<p class=\"b\"><img src=\"/block.jpg\" alt=\"Block Image\"></p>")
})
-2
View File
@@ -71,7 +71,5 @@ func (t *Transformer) Transform(doc *ast.Document, reader text.Reader, pctx pars
}
return ast.WalkContinue, nil
})
}
@@ -56,7 +56,7 @@ func (a *attrParser) Continue(node ast.Node, reader text.Reader, pc parser.Conte
func (a *attrParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) {
if attrs, ok := parser.ParseAttributes(reader); ok {
// add attributes
var node = &attributesBlock{
node := &attributesBlock{
BaseBlock: ast.BaseBlock{},
}
for _, attr := range attrs {
@@ -95,7 +95,7 @@ func (a *attributesBlock) Kind() ast.NodeKind {
type transformer struct{}
func (a *transformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
var attributes = make([]ast.Node, 0, 500)
attributes := make([]ast.Node, 0, 500)
ast.Walk(node, func(node ast.Node, entering bool) (ast.WalkStatus, error) {
if entering && node.Kind() == kindAttributesBlock {
// Attributes for fenced code blocks are handled in their own extension,
@@ -67,13 +67,7 @@ HighlightCodeBlock: Wrapped:{{ $result.Wrapped }}|Inner:{{ $result.Inner }}
{{ .Content }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: false,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html",
"Inline:<code class=\"code-inline language-emacs\"><span class=\"p\">(</span><span class=\"nf\">message</span> <span class=\"s\">&#34;this highlight shortcode&#34;</span><span class=\"p\">)</span></code>:End.",
@@ -103,13 +97,7 @@ xəx := 0
{{ .Content }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: false,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/index.html", `
<span class="nx">xəx</span>
+2 -1
View File
@@ -13,7 +13,8 @@ import (
func ExternallyRenderContent(
cfg converter.ProviderConfig,
ctx converter.DocumentContext,
content []byte, binaryName string, args []string) ([]byte, error) {
content []byte, binaryName string, args []string,
) ([]byte, error) {
logger := cfg.Logger
if strings.Contains(binaryName, "/") {
-2
View File
@@ -83,7 +83,5 @@ func TestConfig(t *testing.T) {
c.Assert(err, qt.IsNil)
c.Assert(conf.Goldmark.Extensions.Typographer.Disable, qt.Equals, false)
c.Assert(conf.Goldmark.Extensions.Typographer.Ellipsis, qt.Equals, "&hellip;")
})
}
+1 -2
View File
@@ -26,8 +26,7 @@ import (
// Provider is the package entry point.
var Provider converter.ProviderProvider = provider{}
type provider struct {
}
type provider struct{}
func (p provider) New(cfg converter.ProviderConfig) (converter.Provider, error) {
return converter.NewProvider("pandoc", func(ctx converter.DocumentContext) (converter.Converter, error) {
+1 -2
View File
@@ -30,8 +30,7 @@ import (
// Provider is the package entry point.
var Provider converter.ProviderProvider = provider{}
type provider struct {
}
type provider struct{}
func (p provider) New(cfg converter.ProviderConfig) (converter.Provider, error) {
return converter.NewProvider("rst", func(ctx converter.DocumentContext) (converter.Converter, error) {
@@ -186,7 +186,6 @@ func TestTocMisc(t *testing.T) {
}
func BenchmarkToc(b *testing.B) {
newTocs := func(n int) []*Fragments {
var tocs []*Fragments
for i := 0; i < n; i++ {
@@ -216,5 +215,4 @@ func BenchmarkToc(b *testing.B) {
toc.ToHTML(1, -1, false)
}
})
}
+43 -45
View File
@@ -51,58 +51,56 @@ type BuiltinTypes struct {
OctetType Type
}
var (
Builtin = BuiltinTypes{
CalendarType: Type{Type: "text/calendar"},
CSSType: Type{Type: "text/css"},
SCSSType: Type{Type: "text/x-scss"},
SASSType: Type{Type: "text/x-sass"},
CSVType: Type{Type: "text/csv"},
HTMLType: Type{Type: "text/html"},
JavascriptType: Type{Type: "text/javascript"},
TypeScriptType: Type{Type: "text/typescript"},
TSXType: Type{Type: "text/tsx"},
JSXType: Type{Type: "text/jsx"},
var Builtin = BuiltinTypes{
CalendarType: Type{Type: "text/calendar"},
CSSType: Type{Type: "text/css"},
SCSSType: Type{Type: "text/x-scss"},
SASSType: Type{Type: "text/x-sass"},
CSVType: Type{Type: "text/csv"},
HTMLType: Type{Type: "text/html"},
JavascriptType: Type{Type: "text/javascript"},
TypeScriptType: Type{Type: "text/typescript"},
TSXType: Type{Type: "text/tsx"},
JSXType: Type{Type: "text/jsx"},
JSONType: Type{Type: "application/json"},
WebAppManifestType: Type{Type: "application/manifest+json"},
RSSType: Type{Type: "application/rss+xml"},
XMLType: Type{Type: "application/xml"},
SVGType: Type{Type: "image/svg+xml"},
TextType: Type{Type: "text/plain"},
TOMLType: Type{Type: "application/toml"},
YAMLType: Type{Type: "application/yaml"},
JSONType: Type{Type: "application/json"},
WebAppManifestType: Type{Type: "application/manifest+json"},
RSSType: Type{Type: "application/rss+xml"},
XMLType: Type{Type: "application/xml"},
SVGType: Type{Type: "image/svg+xml"},
TextType: Type{Type: "text/plain"},
TOMLType: Type{Type: "application/toml"},
YAMLType: Type{Type: "application/yaml"},
// Common image types
PNGType: Type{Type: "image/png"},
JPEGType: Type{Type: "image/jpeg"},
GIFType: Type{Type: "image/gif"},
TIFFType: Type{Type: "image/tiff"},
BMPType: Type{Type: "image/bmp"},
WEBPType: Type{Type: "image/webp"},
// Common image types
PNGType: Type{Type: "image/png"},
JPEGType: Type{Type: "image/jpeg"},
GIFType: Type{Type: "image/gif"},
TIFFType: Type{Type: "image/tiff"},
BMPType: Type{Type: "image/bmp"},
WEBPType: Type{Type: "image/webp"},
// Common font types
TrueTypeFontType: Type{Type: "font/ttf"},
OpenTypeFontType: Type{Type: "font/otf"},
// Common font types
TrueTypeFontType: Type{Type: "font/ttf"},
OpenTypeFontType: Type{Type: "font/otf"},
// Common document types
PDFType: Type{Type: "application/pdf"},
MarkdownType: Type{Type: "text/markdown"},
// Common document types
PDFType: Type{Type: "application/pdf"},
MarkdownType: Type{Type: "text/markdown"},
// Common video types
AVIType: Type{Type: "video/x-msvideo"},
MPEGType: Type{Type: "video/mpeg"},
MP4Type: Type{Type: "video/mp4"},
OGGType: Type{Type: "video/ogg"},
WEBMType: Type{Type: "video/webm"},
GPPType: Type{Type: "video/3gpp"},
// Common video types
AVIType: Type{Type: "video/x-msvideo"},
MPEGType: Type{Type: "video/mpeg"},
MP4Type: Type{Type: "video/mp4"},
OGGType: Type{Type: "video/ogg"},
WEBMType: Type{Type: "video/webm"},
GPPType: Type{Type: "video/3gpp"},
// Web assembly.
WasmType: Type{Type: "application/wasm"},
// Web assembly.
WasmType: Type{Type: "application/wasm"},
OctetType: Type{Type: "application/octet-stream"},
}
)
OctetType: Type{Type: "application/octet-stream"},
}
var defaultMediaTypesConfig = map[string]any{
"text/calendar": map[string]any{"suffixes": []string{"ics"}},
+2 -3
View File
@@ -184,7 +184,6 @@ func TestDecodeConfigDecimalIsNowPrecision(t *testing.T) {
conf := testconfig.GetTestConfigs(nil, v).Base.Minify
c.Assert(conf.Tdewolff.CSS.Precision, qt.Equals, 3)
}
// Issue 9456
@@ -209,7 +208,7 @@ func TestDecodeConfigKeepWhitespace(t *testing.T) {
KeepDocumentTags: true,
KeepEndTags: false,
KeepQuotes: false,
KeepWhitespace: false},
KeepWhitespace: false,
},
)
}
+7 -7
View File
@@ -18,6 +18,7 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"os"
@@ -46,8 +47,6 @@ import (
"github.com/gohugoio/hugo/common/hugio"
"errors"
"github.com/spf13/afero"
)
@@ -199,7 +198,7 @@ func (c *Client) Vendor() error {
if err := c.rmVendorDir(vendorDir); err != nil {
return err
}
if err := c.fs.MkdirAll(vendorDir, 0755); err != nil {
if err := c.fs.MkdirAll(vendorDir, 0o755); err != nil {
return err
}
@@ -260,7 +259,7 @@ func (c *Client) Vendor() error {
} else {
targetDir := filepath.Dir(targetFilename)
if err := c.fs.MkdirAll(targetDir, 0755); err != nil {
if err := c.fs.MkdirAll(targetDir, 0o755); err != nil {
return fmt.Errorf("failed to make target dir: %w", err)
}
@@ -303,7 +302,7 @@ func (c *Client) Vendor() error {
}
if modulesContent.Len() > 0 {
if err := afero.WriteFile(c.fs, filepath.Join(vendorDir, vendorModulesFilename), modulesContent.Bytes(), 0666); err != nil {
if err := afero.WriteFile(c.fs, filepath.Join(vendorDir, vendorModulesFilename), modulesContent.Bytes(), 0o666); err != nil {
return err
}
}
@@ -558,7 +557,7 @@ func (c *Client) rewriteGoMod(name string, isGoMod map[string]bool) error {
return err
}
if data != nil {
if err := afero.WriteFile(c.fs, filepath.Join(c.ccfg.WorkingDir, name), data, 0666); err != nil {
if err := afero.WriteFile(c.fs, filepath.Join(c.ccfg.WorkingDir, name), data, 0o666); err != nil {
return err
}
}
@@ -636,7 +635,8 @@ func (c *Client) rmVendorDir(vendorDir string) error {
func (c *Client) runGo(
ctx context.Context,
stdout io.Writer,
args ...string) error {
args ...string,
) error {
if c.goBinaryStatus != 0 {
return nil
}
+1 -1
View File
@@ -126,7 +126,7 @@ func (m ModulesConfig) HasConfigFile() bool {
func (m *ModulesConfig) setActiveMods(logger loggers.Logger) error {
for _, mod := range m.AllModules {
if !mod.Config().HugoVersion.IsValid() {
logger.Warnf(`Module %q is not compatible with this Hugo version; run "hugo mod graph" for more information.`, mod.Path())
logger.Warnf(`Module %q is not compatible with this Hugo version: %s; run "hugo mod graph" for more information.`, mod.Path(), mod.Config().HugoVersion)
}
}

Some files were not shown because too many files have changed in this diff Show More