mirror of
https://github.com/gohugoio/hugo.git
synced 2026-09-03 04:02:38 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c87da23d82 | |||
| 92684f9a26 | |||
| fce8d82b79 | |||
| 4a502f7eb4 | |||
| be1dbba0f7 | |||
| 1007bcdf49 | |||
| 1904ba40e8 | |||
| 6a8b25e291 | |||
| a322282e70 | |||
| 6bc0d745a5 | |||
| 218690328c | |||
| 673c634131 | |||
| 21a41003c4 | |||
| 1736ef7459 | |||
| d4be1643a0 | |||
| f27e578718 | |||
| d310595a2b | |||
| 049dd1d7e0 | |||
| 189b72331e | |||
| b2b7bfdd3a |
@@ -671,6 +671,13 @@ type Configs struct {
|
||||
configLangs []config.AllProvider
|
||||
}
|
||||
|
||||
func (c *Configs) Validate(logger loggers.Logger) error {
|
||||
for p := range c.Base.Cascade.Config {
|
||||
page.CheckCascadePattern(logger, p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// transientErr returns the last transient error found during config compilation.
|
||||
func (c *Configs) transientErr() error {
|
||||
for _, l := range c.LanguageConfigSlice {
|
||||
@@ -969,7 +976,7 @@ func decodeConfigFromParams(fs afero.Fs, logger loggers.Logger, bcfg config.Base
|
||||
})
|
||||
|
||||
for _, v := range decoderSetups {
|
||||
p := decodeConfig{p: p, c: target, fs: fs, logger: logger, bcfg: bcfg}
|
||||
p := decodeConfig{p: p, c: target, fs: fs, bcfg: bcfg}
|
||||
if err := v.decode(v, p); err != nil {
|
||||
return fmt.Errorf("failed to decode %q: %w", v.key, err)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/cache/filecache"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
@@ -43,11 +42,10 @@ import (
|
||||
)
|
||||
|
||||
type decodeConfig struct {
|
||||
p config.Provider
|
||||
c *Config
|
||||
fs afero.Fs
|
||||
logger loggers.Logger
|
||||
bcfg config.BaseConfig
|
||||
p config.Provider
|
||||
c *Config
|
||||
fs afero.Fs
|
||||
bcfg config.BaseConfig
|
||||
}
|
||||
|
||||
type decodeWeight struct {
|
||||
@@ -293,7 +291,7 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
key: "cascade",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.Cascade, err = page.DecodeCascadeConfig(p.logger, p.p.Get(d.key))
|
||||
p.c.Cascade, err = page.DecodeCascadeConfig(nil, p.p.Get(d.key))
|
||||
return err
|
||||
},
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@ require (
|
||||
github.com/bep/lazycache v0.4.0
|
||||
github.com/bep/logg v0.4.0
|
||||
github.com/bep/mclib v1.20400.20402
|
||||
github.com/bep/overlayfs v0.9.1
|
||||
github.com/bep/overlayfs v0.9.2
|
||||
github.com/bep/simplecobra v0.4.0
|
||||
github.com/bep/tmc v0.5.1
|
||||
github.com/clbanning/mxj/v2 v2.7.0
|
||||
@@ -62,7 +62,7 @@ require (
|
||||
github.com/spf13/afero v1.11.0
|
||||
github.com/spf13/cast v1.6.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/fsync v0.10.0
|
||||
github.com/spf13/fsync v0.10.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/tdewolff/minify/v2 v2.20.17
|
||||
github.com/tdewolff/parse/v2 v2.7.12
|
||||
|
||||
@@ -138,8 +138,8 @@ github.com/bep/logg v0.4.0 h1:luAo5mO4ZkhA5M1iDVDqDqnBBnlHjmtZF6VAyTp+nCQ=
|
||||
github.com/bep/logg v0.4.0/go.mod h1:Ccp9yP3wbR1mm++Kpxet91hAZBEQgmWgFgnXX3GkIV0=
|
||||
github.com/bep/mclib v1.20400.20402 h1:olpCE2WSPpOAbFE1R4hnftSEmQ34+xzy2HRzd0m69rA=
|
||||
github.com/bep/mclib v1.20400.20402/go.mod h1:pkrk9Kyfqg34Uj6XlDq9tdEFJBiL1FvCoCgVKRzw1EY=
|
||||
github.com/bep/overlayfs v0.9.1 h1:SL54SV8A3zRkmQ+83Jj4TLE88jadHd5d1L4NpfmqJJs=
|
||||
github.com/bep/overlayfs v0.9.1/go.mod h1:aYY9W7aXQsGcA7V9x/pzeR8LjEgIxbtisZm8Q7zPz40=
|
||||
github.com/bep/overlayfs v0.9.2 h1:qJEmFInsW12L7WW7dOTUhnMfyk/fN9OCDEO5Gr8HSDs=
|
||||
github.com/bep/overlayfs v0.9.2/go.mod h1:aYY9W7aXQsGcA7V9x/pzeR8LjEgIxbtisZm8Q7zPz40=
|
||||
github.com/bep/simplecobra v0.4.0 h1:ufX/6WcOtEVJdCd7hsztTWURlZkOaWYOD+zCqrM8qUE=
|
||||
github.com/bep/simplecobra v0.4.0/go.mod h1:evSM6iQqRwqpV7W4H4DlYFfe9mZ0x6Hj5GEOnIV7dI4=
|
||||
github.com/bep/tmc v0.5.1 h1:CsQnSC6MsomH64gw0cT5f+EwQDcvZz4AazKunFwTpuI=
|
||||
@@ -410,8 +410,8 @@ github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/fsync v0.10.0 h1:j+zUMN41zWj3sEqueD4mAsPDQwyOvMeJCcrawdmbqXk=
|
||||
github.com/spf13/fsync v0.10.0/go.mod h1:y+B41vYq5i6Boa3Z+BVoPbDeOvxVkNU5OBXhoT8i4TQ=
|
||||
github.com/spf13/fsync v0.10.1 h1:JRnB7G72b+gIBaBcpn5ibJSd7ww1iEahXSX2B8G6dSE=
|
||||
github.com/spf13/fsync v0.10.1/go.mod h1:y+B41vYq5i6Boa3Z+BVoPbDeOvxVkNU5OBXhoT8i4TQ=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
||||
+3
-1
@@ -73,7 +73,9 @@ type FileMeta struct {
|
||||
InclusionFilter *glob.FilenameFilter
|
||||
|
||||
// Rename the name part of the file (not the directory).
|
||||
Rename func(name string, toFrom bool) string
|
||||
// Returns the new name and a boolean indicating if the file
|
||||
// should be included.
|
||||
Rename func(name string, toFrom bool) (string, bool)
|
||||
}
|
||||
|
||||
func (m *FileMeta) Copy() *FileMeta {
|
||||
|
||||
+57
-98
@@ -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.
|
||||
@@ -43,7 +43,6 @@ var _ ReverseLookupProvder = (*RootMappingFs)(nil)
|
||||
func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
|
||||
rootMapToReal := radix.New()
|
||||
realMapToRoot := radix.New()
|
||||
var virtualRoots []RootMapping
|
||||
|
||||
addMapping := func(key string, rm RootMapping, to *radix.Tree) {
|
||||
var mappings []RootMapping
|
||||
@@ -80,42 +79,36 @@ func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
|
||||
if !fi.IsDir() {
|
||||
// We do allow single file mounts.
|
||||
// However, the file system logic will be much simpler with just directories.
|
||||
// So, convert this mount into a directory mount with a nameTo filter and renamer.
|
||||
// So, convert this mount into a directory mount with a renamer,
|
||||
// which will tell the caller if name should be included.
|
||||
dirFrom, nameFrom := filepath.Split(rm.From)
|
||||
dirTo, nameTo := filepath.Split(rm.To)
|
||||
dirFrom, dirTo = strings.TrimSuffix(dirFrom, filepathSeparator), strings.TrimSuffix(dirTo, filepathSeparator)
|
||||
rm.From = dirFrom
|
||||
|
||||
fi, err = fs.Stat(rm.To)
|
||||
if err != nil {
|
||||
if herrors.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rm.fiSingleFile = NewFileMetaInfo(fi, rm.Meta.Copy())
|
||||
singleFileMeta := rm.Meta.Copy()
|
||||
singleFileMeta.Name = nameFrom
|
||||
rm.fiSingleFile = NewFileMetaInfo(fi, singleFileMeta)
|
||||
rm.To = dirTo
|
||||
|
||||
rm.Meta.Rename = func(name string, toFrom bool) string {
|
||||
rm.Meta.Rename = func(name string, toFrom bool) (string, bool) {
|
||||
if toFrom {
|
||||
if name == nameTo {
|
||||
return nameFrom
|
||||
return nameFrom, true
|
||||
}
|
||||
return name
|
||||
return "", false
|
||||
}
|
||||
|
||||
if name == nameFrom {
|
||||
return nameTo
|
||||
return nameTo, true
|
||||
}
|
||||
|
||||
return name
|
||||
return "", false
|
||||
}
|
||||
nameToFilename := filepathSeparator + nameTo
|
||||
|
||||
rm.Meta.InclusionFilter = rm.Meta.InclusionFilter.Append(glob.NewFilenameFilterForInclusionFunc(
|
||||
func(filename string) bool {
|
||||
return strings.HasPrefix(nameToFilename, filename)
|
||||
return nameToFilename == filename
|
||||
},
|
||||
))
|
||||
|
||||
@@ -160,11 +153,8 @@ func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
|
||||
|
||||
addMapping(rev, rm, realMapToRoot)
|
||||
|
||||
virtualRoots = append(virtualRoots, rm)
|
||||
}
|
||||
|
||||
rootMapToReal.Insert(filepathSeparator, virtualRoots)
|
||||
|
||||
rfs := &RootMappingFs{
|
||||
Fs: fs,
|
||||
rootMapToReal: rootMapToReal,
|
||||
@@ -252,13 +242,11 @@ func (fs *RootMappingFs) Mounts(base string) ([]FileMetaInfo, error) {
|
||||
|
||||
fss := make([]FileMetaInfo, len(roots))
|
||||
for i, r := range roots {
|
||||
|
||||
if r.fiSingleFile != nil {
|
||||
// A single file mount.
|
||||
fss[i] = r.fiSingleFile
|
||||
continue
|
||||
}
|
||||
|
||||
bfs := NewBasePathFs(fs.Fs, r.To)
|
||||
fs := bfs
|
||||
if r.Meta.InclusionFilter != nil {
|
||||
@@ -269,11 +257,6 @@ func (fs *RootMappingFs) Mounts(base string) ([]FileMetaInfo, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("RootMappingFs.Dirs: %w", err)
|
||||
}
|
||||
|
||||
if !fi.IsDir() {
|
||||
fi.(FileMetaInfo).Meta().Merge(r.Meta)
|
||||
}
|
||||
|
||||
fss[i] = fi.(FileMetaInfo)
|
||||
}
|
||||
|
||||
@@ -365,19 +348,26 @@ func (fs *RootMappingFs) ReverseLookupComponent(component, filename string, chec
|
||||
if component != "" && first.FromBase != component {
|
||||
continue
|
||||
}
|
||||
|
||||
var filename string
|
||||
if first.Meta.Rename != nil {
|
||||
name = first.Meta.Rename(name, true)
|
||||
}
|
||||
|
||||
// Now we know that this file _could_ be in this fs.
|
||||
filename := filepathSeparator + filepath.Join(first.path, dir, name)
|
||||
|
||||
if checkExists {
|
||||
// Confirm that it exists.
|
||||
_, err := fs.Stat(first.FromBase + filename)
|
||||
if err != nil {
|
||||
// Single file mount.
|
||||
if newname, ok := first.Meta.Rename(name, true); ok {
|
||||
filename = filepathSeparator + filepath.Join(first.path, dir, newname)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// Now we know that this file _could_ be in this fs.
|
||||
filename = filepathSeparator + filepath.Join(first.path, dir, name)
|
||||
|
||||
if checkExists {
|
||||
// Confirm that it exists.
|
||||
_, err := fs.Stat(first.FromBase + filename)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cps = append(cps, ComponentPath{
|
||||
@@ -420,6 +410,7 @@ func (fs *RootMappingFs) getRoots(key string) (string, []RootMapping) {
|
||||
for {
|
||||
var found bool
|
||||
ss, vv, found := tree.LongestPrefix(key)
|
||||
|
||||
if !found || (levels < 2 && ss == key) {
|
||||
break
|
||||
}
|
||||
@@ -570,9 +561,11 @@ func (rfs *RootMappingFs) collectDirEntries(prefix string) ([]iofs.DirEntry, err
|
||||
}
|
||||
fi = newDirNameOnlyFileInfo(name, meta, opener)
|
||||
} else if rm.Meta.Rename != nil {
|
||||
if n := rm.Meta.Rename(fi.Name(), true); n != fi.Name() {
|
||||
fi.(MetaProvider).Meta().Name = n
|
||||
n, ok := rm.Meta.Rename(fi.Name(), true)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
fi.(MetaProvider).Meta().Name = n
|
||||
}
|
||||
fis = append(fis, fi)
|
||||
}
|
||||
@@ -617,25 +610,16 @@ func (rfs *RootMappingFs) collectDirEntries(prefix string) ([]iofs.DirEntry, err
|
||||
|
||||
rms := v.([]RootMapping)
|
||||
for _, rm := range rms {
|
||||
if !rm.fi.IsDir() {
|
||||
// A single file mount
|
||||
fis = append(fis, rm.fi)
|
||||
continue
|
||||
}
|
||||
name := filepath.Base(rm.From)
|
||||
if seen[name] {
|
||||
continue
|
||||
}
|
||||
seen[name] = true
|
||||
|
||||
opener := func() (afero.File, error) {
|
||||
return rfs.Open(rm.From)
|
||||
}
|
||||
|
||||
fi := newDirNameOnlyFileInfo(name, rm.Meta, opener)
|
||||
|
||||
fis = append(fis, fi)
|
||||
|
||||
}
|
||||
|
||||
return false
|
||||
@@ -701,48 +685,20 @@ func (fs *RootMappingFs) doStat(name string) ([]FileMetaInfo, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fileCount := 0
|
||||
var wasFiltered bool
|
||||
for _, root := range roots {
|
||||
meta := root.fi.Meta()
|
||||
if !meta.InclusionFilter.Match(strings.TrimPrefix(meta.Filename, meta.SourceRoot), root.fi.IsDir()) {
|
||||
wasFiltered = true
|
||||
continue
|
||||
}
|
||||
|
||||
if !root.fi.IsDir() {
|
||||
fileCount++
|
||||
}
|
||||
if fileCount > 1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if fileCount == 0 {
|
||||
if wasFiltered {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
// Dir only.
|
||||
return []FileMetaInfo{newDirNameOnlyFileInfo(name, roots[0].Meta, fs.virtualDirOpener(name))}, nil
|
||||
}
|
||||
|
||||
if fileCount > 1 {
|
||||
// Not supported by this filesystem.
|
||||
return nil, fmt.Errorf("found multiple files with name %q, use .Readdir or the source filesystem directly", name)
|
||||
}
|
||||
|
||||
return []FileMetaInfo{roots[0].fi}, nil
|
||||
return []FileMetaInfo{newDirNameOnlyFileInfo(name, roots[0].Meta, fs.virtualDirOpener(name))}, nil
|
||||
}
|
||||
|
||||
func (fs *RootMappingFs) statRoot(root RootMapping, filename string) (FileMetaInfo, error) {
|
||||
dir, name := filepath.Split(filename)
|
||||
if root.Meta.Rename != nil {
|
||||
if n := root.Meta.Rename(name, false); n != name {
|
||||
filename = filepath.Join(dir, n)
|
||||
n, ok := root.Meta.Rename(name, false)
|
||||
if !ok {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
filename = filepath.Join(dir, n)
|
||||
}
|
||||
|
||||
if !root.Meta.InclusionFilter.Match(root.trimFrom(filename), root.fi.IsDir()) {
|
||||
if !root.Meta.InclusionFilter.Match(root.trimFrom(filename), true) {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
@@ -753,24 +709,27 @@ func (fs *RootMappingFs) statRoot(root RootMapping, filename string) (FileMetaIn
|
||||
}
|
||||
|
||||
var opener func() (afero.File, error)
|
||||
if fi.IsDir() {
|
||||
// Make sure metadata gets applied in ReadDir.
|
||||
opener = fs.realDirOpener(filename, root.Meta)
|
||||
} else {
|
||||
if root.Meta.Rename != nil {
|
||||
if n := root.Meta.Rename(fi.Name(), true); n != fi.Name() {
|
||||
meta := fi.(MetaProvider).Meta()
|
||||
|
||||
meta.Name = n
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if !fi.IsDir() {
|
||||
// Open the file directly.
|
||||
// Opens the real file directly.
|
||||
opener = func() (afero.File, error) {
|
||||
return fs.Fs.Open(filename)
|
||||
}
|
||||
|
||||
} else if root.Meta.Rename != nil {
|
||||
// A single file mount where we have mounted the containing directory.
|
||||
n, ok := root.Meta.Rename(fi.Name(), true)
|
||||
if !ok {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
meta := fi.(MetaProvider).Meta()
|
||||
meta.Name = n
|
||||
// Opens the real file directly.
|
||||
opener = func() (afero.File, error) {
|
||||
return fs.Fs.Open(filename)
|
||||
}
|
||||
} else {
|
||||
// Make sure metadata gets applied in ReadDir.
|
||||
opener = fs.realDirOpener(filename, root.Meta)
|
||||
}
|
||||
|
||||
fim := decorateFileInfo(fi, opener, "", root.Meta)
|
||||
|
||||
@@ -280,6 +280,11 @@ func TestRootMappingFsMount(t *testing.T) {
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(cps, qt.DeepEquals, []ComponentPath{
|
||||
{Component: "content", Path: "singles/p1.md", Lang: "no"},
|
||||
})
|
||||
|
||||
cps, err = rfs.ReverseLookup(filepath.FromSlash("singlefiles/sv.txt"), true)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(cps, qt.DeepEquals, []ComponentPath{
|
||||
{Component: "content", Path: "singles/p1.md", Lang: "sv"},
|
||||
})
|
||||
|
||||
|
||||
@@ -674,6 +674,8 @@ S1|p1:|p2:p2|
|
||||
|
||||
// Issue 11977.
|
||||
func TestCascadeExtensionInPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.org"
|
||||
@@ -700,3 +702,93 @@ title: "Post 1"
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
b.AssertLogContains(`cascade target path "/posts/post-1.de.md" looks like a path with an extension; since Hugo v0.123.0 this will not match anything, see https://gohugo.io/methods/page/path/`)
|
||||
}
|
||||
|
||||
func TestCascadeExtensionInPathIgnore(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.org"
|
||||
ignoreLogs = ['cascade-pattern-with-extension']
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
[languages.de]
|
||||
-- content/_index.de.md --
|
||||
+++
|
||||
[[cascade]]
|
||||
[cascade.params]
|
||||
foo = 'bar'
|
||||
[cascade._target]
|
||||
path = '/posts/post-1.de.md'
|
||||
+++
|
||||
-- content/posts/post-1.de.md --
|
||||
---
|
||||
title: "Post 1"
|
||||
---
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}|{{ .Params.foo }}$
|
||||
`
|
||||
b := Test(t, files)
|
||||
b.AssertLogNotContains(`looks like a path with an extension`)
|
||||
}
|
||||
|
||||
func TestCascadConfigExtensionInPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.org"
|
||||
[[cascade]]
|
||||
[cascade.params]
|
||||
foo = 'bar'
|
||||
[cascade._target]
|
||||
path = '/p1.md'
|
||||
`
|
||||
b, err := TestE(t, files)
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
b.AssertLogContains(`looks like a path with an extension`)
|
||||
}
|
||||
|
||||
func TestCascadConfigExtensionInPathIgnore(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.org"
|
||||
ignoreLogs = ['cascade-pattern-with-extension']
|
||||
[[cascade]]
|
||||
[cascade.params]
|
||||
foo = 'bar'
|
||||
[cascade._target]
|
||||
path = '/p1.md'
|
||||
`
|
||||
b := Test(t, files)
|
||||
b.AssertLogNotContains(`looks like a path with an extension`)
|
||||
}
|
||||
|
||||
func TestCascadeIssue12172(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['rss','sitemap','taxonomy','term']
|
||||
[[cascade]]
|
||||
headless = true
|
||||
[cascade._target]
|
||||
path = '/s1**'
|
||||
-- content/s1/p1.md --
|
||||
---
|
||||
title: p1
|
||||
---
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}|
|
||||
-- layouts/_default/list.html --
|
||||
{{ .Title }}|
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileExists("public/index.html", true)
|
||||
b.AssertFileExists("public/s1/index.html", false)
|
||||
b.AssertFileExists("public/s1/p1/index.html", false)
|
||||
}
|
||||
|
||||
@@ -1539,3 +1539,22 @@ List.
|
||||
b.AssertLogContains("WARN DEPRECATED: Kind \"taxonomyterm\" used in disableKinds is deprecated, use \"taxonomy\" instead.\n")
|
||||
b.AssertLogContains("WARN DEPRECATED: Kind \"taxonomyterm\" used in outputs configuration is deprecated, use \"taxonomy\" instead.\n")
|
||||
}
|
||||
|
||||
func TestDisableKindsIssue12144(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["page"]
|
||||
defaultContentLanguage = "pt-br"
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
-- content/custom/index.pt-br.md --
|
||||
---
|
||||
title: "P1 pt"
|
||||
---
|
||||
-- content/custom/index.en-us.md --
|
||||
---
|
||||
title: "P1 us"
|
||||
---
|
||||
`
|
||||
Test(t, files)
|
||||
}
|
||||
|
||||
@@ -193,6 +193,10 @@ func (m *pageMap) AddFi(fi hugofs.FileMetaInfo) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pageResource == nil {
|
||||
// Disabled page.
|
||||
return nil
|
||||
}
|
||||
key = pi.Base()
|
||||
|
||||
rs = &resourceSource{r: pageResource}
|
||||
|
||||
+26
-26
@@ -348,31 +348,28 @@ func (m *pageMap) getPagesInSection(q pageMapQueryPagesInSection) page.Pages {
|
||||
w := &doctree.NodeShiftTreeWalker[contentNodeI]{
|
||||
Tree: m.treePages,
|
||||
Prefix: prefix,
|
||||
Handle: func(key string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
|
||||
if q.Recursive {
|
||||
if p, ok := n.(*pageState); ok && include(p) {
|
||||
pas = append(pas, p)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// We store both leafs and branches in the same tree, so for non-recursive walks,
|
||||
// we need to walk until the end, but can skip
|
||||
// any not belonging to child branches.
|
||||
if otherBranch != "" && strings.HasPrefix(key, otherBranch) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
w.Handle = func(key string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
|
||||
if q.Recursive {
|
||||
if p, ok := n.(*pageState); ok && include(p) {
|
||||
pas = append(pas, p)
|
||||
}
|
||||
|
||||
if n.isContentNodeBranch() {
|
||||
otherBranch = key + "/"
|
||||
}
|
||||
|
||||
return false, nil
|
||||
},
|
||||
}
|
||||
|
||||
if p, ok := n.(*pageState); ok && include(p) {
|
||||
pas = append(pas, p)
|
||||
}
|
||||
|
||||
if n.isContentNodeBranch() {
|
||||
currentBranch := key + "/"
|
||||
if otherBranch == "" || otherBranch != currentBranch {
|
||||
w.SkipPrefix(currentBranch)
|
||||
}
|
||||
otherBranch = currentBranch
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
err := w.Walk(context.Background())
|
||||
@@ -542,7 +539,7 @@ func (m *pageMap) getOrCreateResourcesForPage(ps *pageState) resource.Resources
|
||||
for _, r := range res2 {
|
||||
var found bool
|
||||
for _, r2 := range res {
|
||||
if r2.Name() == r.Name() {
|
||||
if r2.(resource.NameNormalizedProvider).NameNormalized() == r.(resource.NameNormalizedProvider).NameNormalized() {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
@@ -688,13 +685,13 @@ func (s *contentNodeShifter) Delete(n contentNodeI, dimension doctree.Dimension)
|
||||
}
|
||||
return wasDeleted, isEmpty
|
||||
case *resourceSource:
|
||||
if lidx > 0 {
|
||||
if lidx != v.LangIndex() {
|
||||
return false, false
|
||||
}
|
||||
resource.MarkStale(v)
|
||||
return true, true
|
||||
case *pageState:
|
||||
if lidx > 0 {
|
||||
if lidx != v.s.languagei {
|
||||
return false, false
|
||||
}
|
||||
resource.MarkStale(v)
|
||||
@@ -1552,8 +1549,10 @@ func (sa *sitePagesAssembler) assembleTermsAndTranslations() error {
|
||||
}
|
||||
pages.InsertIntoValuesDimension(pi.Base(), n)
|
||||
term = pages.Get(pi.Base())
|
||||
} else if term.(*pageState).m.term != v {
|
||||
term.(*pageState).m.term = v
|
||||
} else {
|
||||
m := term.(*pageState).m
|
||||
m.term = v
|
||||
m.singular = viewName.singular
|
||||
}
|
||||
|
||||
if s == "" {
|
||||
@@ -1633,7 +1632,7 @@ func (sa *sitePagesAssembler) assembleResources() error {
|
||||
TargetBasePaths: targetBasePaths,
|
||||
BasePathRelPermalink: targetPaths.SubResourceBaseLink,
|
||||
BasePathTargetPath: baseTarget,
|
||||
Name: relPath,
|
||||
NameNormalized: relPath,
|
||||
NameOriginal: relPathOriginal,
|
||||
LazyPublish: !ps.m.pageConfig.Build.PublishResources,
|
||||
}
|
||||
@@ -1715,6 +1714,7 @@ func (sa *sitePagesAssembler) removeShouldNotBuild() error {
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sa.pageMap.DeletePageAndResourcesBelow(keys...)
|
||||
|
||||
return nil
|
||||
|
||||
@@ -64,3 +64,21 @@ v1: {{ site.Data.MyFolder.MyData.v1 }}|
|
||||
|
||||
b.AssertFileContent("public/index.html", "v1: my_v1|")
|
||||
}
|
||||
|
||||
// Issue #12133
|
||||
func TestDataNoAssets(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
|
||||
-- assets/data/foo.toml --
|
||||
content = "I am assets/data/foo.toml"
|
||||
-- layouts/index.html --
|
||||
|{{ site.Data.foo.content }}|
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/index.html", "||")
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2018 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.
|
||||
|
||||
@@ -234,6 +234,7 @@ foo
|
||||
b := hugolib.Test(t, files)
|
||||
bfs := b.H.BaseFs
|
||||
watchFilenames := bfs.WatchFilenames()
|
||||
// []string{"/hugo_stats.json", "/content", "/content2", "/themes/t1/layouts", "/themes/t1/layouts/_default", "/themes/t1/static"}
|
||||
b.Assert(watchFilenames, qt.HasLen, 6)
|
||||
}
|
||||
|
||||
@@ -543,6 +544,30 @@ files/f2.txt false
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMountIssue12141(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["taxonomy", "term"]
|
||||
[module]
|
||||
[[module.mounts]]
|
||||
source = "myfiles"
|
||||
target = "static"
|
||||
[[module.mounts]]
|
||||
source = "myfiles/f1.txt"
|
||||
target = "static/f2.txt"
|
||||
-- myfiles/f1.txt --
|
||||
f1
|
||||
`
|
||||
b := hugolib.Test(t, files)
|
||||
fs := b.H.BaseFs.StaticFs("")
|
||||
|
||||
b.AssertFs(fs, `
|
||||
. true
|
||||
f1.txt false
|
||||
f2.txt false
|
||||
`)
|
||||
}
|
||||
|
||||
func checkFileCount(fs afero.Fs, dirname string, c *qt.C, expected int) {
|
||||
c.Helper()
|
||||
count, names, err := countFilesAndGetFilenames(fs, dirname)
|
||||
|
||||
@@ -162,6 +162,52 @@ title: "Mybundle fr"
|
||||
b.AssertFileContent("public/fr/section/mybundle/styles.min.css", ".body{color:french}")
|
||||
}
|
||||
|
||||
func TestResourcePerLanguageIssue12163(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
defaultContentLanguage = 'de'
|
||||
disableKinds = ['rss','sitemap','taxonomy','term']
|
||||
|
||||
[languages.de]
|
||||
baseURL = 'https://de.example.org/'
|
||||
contentDir = 'content/de'
|
||||
weight = 1
|
||||
|
||||
[languages.en]
|
||||
baseURL = 'https://en.example.org/'
|
||||
contentDir = 'content/en'
|
||||
weight = 2
|
||||
-- content/de/mybundle/index.md --
|
||||
---
|
||||
title: mybundle-de
|
||||
---
|
||||
-- content/de/mybundle/pixel.png --
|
||||
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
|
||||
-- content/en/mybundle/index.md --
|
||||
---
|
||||
title: mybundle-en
|
||||
---
|
||||
-- layouts/_default/single.html --
|
||||
{{ with .Resources.Get "pixel.png" }}
|
||||
{{ with .Resize "2x2" }}
|
||||
{{ .RelPermalink }}|
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileExists("public/de/mybundle/index.html", true)
|
||||
b.AssertFileExists("public/en/mybundle/index.html", true)
|
||||
|
||||
b.AssertFileExists("public/de/mybundle/pixel.png", true)
|
||||
b.AssertFileExists("public/en/mybundle/pixel.png", true)
|
||||
|
||||
b.AssertFileExists("public/de/mybundle/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_2x2_resize_box_3.png", true)
|
||||
// failing test below
|
||||
b.AssertFileExists("public/en/mybundle/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_2x2_resize_box_3.png", true)
|
||||
}
|
||||
|
||||
func TestMultihostResourceOneBaseURLWithSuPath(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
|
||||
@@ -169,10 +169,11 @@ type IntegrationTestBuilder struct {
|
||||
renamedFiles []string
|
||||
renamedDirs []string
|
||||
|
||||
buildCount int
|
||||
GCCount int
|
||||
counters *buildCounters
|
||||
logBuff lockingBuffer
|
||||
buildCount int
|
||||
GCCount int
|
||||
counters *buildCounters
|
||||
logBuff lockingBuffer
|
||||
lastBuildLog string
|
||||
|
||||
builderInit sync.Once
|
||||
}
|
||||
@@ -192,21 +193,21 @@ func (b *lockingBuffer) Write(p []byte) (n int, err error) {
|
||||
func (s *IntegrationTestBuilder) AssertLogContains(els ...string) {
|
||||
s.Helper()
|
||||
for _, el := range els {
|
||||
s.Assert(s.logBuff.String(), qt.Contains, el)
|
||||
s.Assert(s.lastBuildLog, 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)
|
||||
s.Assert(s.lastBuildLog, qt.Not(qt.Contains), el)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) AssertLogMatches(expression string) {
|
||||
s.Helper()
|
||||
re := regexp.MustCompile(expression)
|
||||
s.Assert(re.MatchString(s.logBuff.String()), qt.IsTrue, qt.Commentf(s.logBuff.String()))
|
||||
s.Assert(re.MatchString(s.lastBuildLog), qt.IsTrue, qt.Commentf(s.lastBuildLog))
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) AssertBuildCountData(count int) {
|
||||
@@ -281,7 +282,7 @@ func (s *IntegrationTestBuilder) AssertPublishDir(matches ...string) {
|
||||
func (s *IntegrationTestBuilder) AssertFs(fs afero.Fs, matches ...string) {
|
||||
s.Helper()
|
||||
var buff bytes.Buffer
|
||||
helpers.PrintFs(fs, "", &buff)
|
||||
s.Assert(s.printAndCheckFs(fs, "", &buff), qt.IsNil)
|
||||
printFsLines := strings.Split(buff.String(), "\n")
|
||||
sort.Strings(printFsLines)
|
||||
content := strings.TrimSpace((strings.Join(printFsLines, "\n")))
|
||||
@@ -304,6 +305,34 @@ func (s *IntegrationTestBuilder) AssertFs(fs afero.Fs, matches ...string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) printAndCheckFs(fs afero.Fs, path string, w io.Writer) error {
|
||||
if fs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return afero.Walk(fs, path, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("error: path %q: %s", path, err)
|
||||
}
|
||||
path = filepath.ToSlash(path)
|
||||
if path == "" {
|
||||
path = "."
|
||||
}
|
||||
if !info.IsDir() {
|
||||
f, err := fs.Open(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error: path %q: %s", path, err)
|
||||
}
|
||||
defer f.Close()
|
||||
// This will panic if the file is a directory.
|
||||
var buf [1]byte
|
||||
io.ReadFull(f, buf[:])
|
||||
}
|
||||
fmt.Fprintln(w, path, info.IsDir())
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) AssertFileExists(filename string, b bool) {
|
||||
checker := qt.IsNil
|
||||
if !b {
|
||||
@@ -341,7 +370,7 @@ func (s *IntegrationTestBuilder) Build() *IntegrationTestBuilder {
|
||||
s.Helper()
|
||||
_, err := s.BuildE()
|
||||
if s.Cfg.Verbose || err != nil {
|
||||
fmt.Println(s.logBuff.String())
|
||||
fmt.Println(s.lastBuildLog)
|
||||
if s.H != nil && err == nil {
|
||||
for _, s := range s.H.Sites {
|
||||
m := s.pageMap
|
||||
@@ -352,7 +381,7 @@ func (s *IntegrationTestBuilder) Build() *IntegrationTestBuilder {
|
||||
}
|
||||
}
|
||||
} else if s.Cfg.LogLevel <= logg.LevelDebug {
|
||||
fmt.Println(s.logBuff.String())
|
||||
fmt.Println(s.lastBuildLog)
|
||||
}
|
||||
s.Assert(err, qt.IsNil)
|
||||
if s.Cfg.RunGC {
|
||||
@@ -364,7 +393,7 @@ func (s *IntegrationTestBuilder) Build() *IntegrationTestBuilder {
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) LogString() string {
|
||||
return s.logBuff.String()
|
||||
return s.lastBuildLog
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) BuildE() (*IntegrationTestBuilder, error) {
|
||||
@@ -381,6 +410,7 @@ func (s *IntegrationTestBuilder) Init() *IntegrationTestBuilder {
|
||||
if err := s.initBuilder(); err != nil {
|
||||
s.Fatalf("Failed to init builder: %s", err)
|
||||
}
|
||||
s.lastBuildLog = s.logBuff.String()
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -626,10 +656,11 @@ func (s *IntegrationTestBuilder) build(cfg BuildCfg) error {
|
||||
s.Helper()
|
||||
defer func() {
|
||||
s.reset()
|
||||
s.lastBuildLog = s.logBuff.String()
|
||||
s.logBuff.Reset()
|
||||
}()
|
||||
|
||||
changeEvents := s.changeEvents()
|
||||
s.logBuff.Reset()
|
||||
s.counters = &buildCounters{}
|
||||
cfg.testCounters = s.counters
|
||||
|
||||
@@ -643,10 +674,6 @@ func (s *IntegrationTestBuilder) build(cfg BuildCfg) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logErrorCount := s.H.NumLogErrors()
|
||||
if logErrorCount > 0 {
|
||||
return fmt.Errorf("logged %d error(s): %s", logErrorCount, s.logBuff.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -523,7 +523,7 @@ params:
|
||||
// pages.
|
||||
isHeadless := cast.ToBool(v)
|
||||
params[loki] = isHeadless
|
||||
if p.File().TranslationBaseName() == "index" && isHeadless {
|
||||
if isHeadless {
|
||||
pm.pageConfig.Build.List = pagemeta.Never
|
||||
pm.pageConfig.Build.Render = pagemeta.Never
|
||||
}
|
||||
|
||||
@@ -11,13 +11,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package hugolib_test
|
||||
package hugolib
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
)
|
||||
|
||||
// Issue 9793
|
||||
@@ -43,7 +41,7 @@ tags: 'tag-a'
|
||||
{{ .Title }}
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/section-1/index.html", "Section-1s")
|
||||
b.AssertFileContent("public/tags/index.html", "Tags")
|
||||
@@ -51,9 +49,34 @@ tags: 'tag-a'
|
||||
|
||||
files = strings.Replace(files, "true", "false", -1)
|
||||
|
||||
b = hugolib.Test(t, files)
|
||||
b = Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/section-1/index.html", "section-1")
|
||||
b.AssertFileContent("public/tags/index.html", "tags")
|
||||
b.AssertFileContent("public/tags/tag-a/index.html", "tag-a")
|
||||
}
|
||||
|
||||
func TestDraftNonDefaultContentLanguage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
defaultContentLanguage = "en"
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
[languages.nn]
|
||||
weight = 2
|
||||
-- content/p1.md --
|
||||
-- content/p2.nn.md --
|
||||
---
|
||||
title: "p2"
|
||||
draft: true
|
||||
---
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileExists("public/nn/p2/index.html", false)
|
||||
}
|
||||
|
||||
@@ -871,7 +871,7 @@ RegularPages: {{ range .RegularPages }}{{ .RelPermalink }}|File LogicalName: {{
|
||||
b := Test(t, files)
|
||||
|
||||
// Note that the sort order gives us the most specific data file for the en language (the data.en.json).
|
||||
b.AssertFileContent("public/mysection/mybundle/index.html", `Single:|/mysection/mybundle|File LogicalName: index.md||/mysection/mybundle/|page|Resources: data.json: Data JSON.|foo/p1.html: |p1.html: |p1.md: |data.txt: Data en txt.|$`)
|
||||
b.AssertFileContent("public/mysection/mybundle/index.html", `Single:|/mysection/mybundle|File LogicalName: index.md||/mysection/mybundle/|page|Resources: data.en.txt: Data en txt.|data.json: Data JSON.|foo/p1.html: |p1.html: |p1.md: |$`)
|
||||
b.AssertFileContent("public/mysection/index.html",
|
||||
"List: |/mysection|File LogicalName: _index.md|/mysection/|section|Resources: sectiondata.json: Secion data JSON.|sectiondata.txt: Section data TXT.|$",
|
||||
"RegularPages: /mysection/foo/p2/|File LogicalName: p2.md|/mysection/mybundle/|File LogicalName: index.md|/mysection/p2/|File LogicalName: p2.md|$")
|
||||
@@ -886,12 +886,12 @@ baseURL = "https://example.com"
|
||||
F1.
|
||||
-- layouts/_default/single.html --
|
||||
GetMatch: {{ with .Resources.GetMatch "f1.en.*" }}{{ .Name }}: {{ .Content }}|{{ end }}
|
||||
Match: {{ range .Resources.Match "f1.en.*" }}{{ .Name }}: {{ .Content }}|{{ end }}
|
||||
Match: {{ range .Resources.Match "f1.En.*" }}{{ .Name }}: {{ .Content }}|{{ end }}
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/mybundle/index.html", "GetMatch: f1.txt: F1.|", "Match: f1.txt: F1.|")
|
||||
b.AssertFileContent("public/mybundle/index.html", "GetMatch: f1.en.txt: F1.|", "Match: f1.en.txt: F1.|")
|
||||
}
|
||||
|
||||
func TestBundleResourcesWhenLanguageVariantIsDraft(t *testing.T) {
|
||||
@@ -917,5 +917,5 @@ GetMatch: {{ with .Resources.GetMatch "f1.*" }}{{ .Name }}: {{ .Content }}|{{ en
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/mybundle/index.html", "GetMatch: f1.txt: F1.|")
|
||||
b.AssertFileContent("public/mybundle/index.html", "GetMatch: f1.en.txt: F1.|")
|
||||
}
|
||||
|
||||
@@ -663,3 +663,33 @@ RegularPagesRecursive: {{ range .RegularPagesRecursive }}{{ .Kind }}:{{ .RelPerm
|
||||
|
||||
b.AssertFileContent("public/index.html", `RegularPagesRecursive: page:/p1/|page:/post/p2/||End.`)
|
||||
}
|
||||
|
||||
// Issue #12169.
|
||||
func TestPagesSimilarSectionNames(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
-- content/draftsection/_index.md --
|
||||
---
|
||||
draft: true
|
||||
---
|
||||
-- content/draftsection/sub/_index.md --got
|
||||
-- content/draftsection/sub/d1.md --
|
||||
-- content/s1/_index.md --
|
||||
-- content/s1/p1.md --
|
||||
-- content/s1-foo/_index.md --
|
||||
-- content/s1-foo/p2.md --
|
||||
-- content/s1-foo/s2/_index.md --
|
||||
-- content/s1-foo/s2/p3.md --
|
||||
-- content/s1-foo/s2-foo/_index.md --
|
||||
-- content/s1-foo/s2-foo/p4.md --
|
||||
-- layouts/_default/list.html --
|
||||
{{ .RelPermalink }}: Pages: {{ range .Pages }}{{ .RelPermalink }}|{{ end }}$
|
||||
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/index.html", "/: Pages: /s1-foo/|/s1/|$")
|
||||
b.AssertFileContent("public/s1/index.html", "/s1/: Pages: /s1/p1/|$")
|
||||
b.AssertFileContent("public/s1-foo/index.html", "/s1-foo/: Pages: /s1-foo/p2/|/s1-foo/s2-foo/|/s1-foo/s2/|$")
|
||||
b.AssertFileContent("public/s1-foo/s2/index.html", "/s1-foo/s2/: Pages: /s1-foo/s2/p3/|$")
|
||||
}
|
||||
|
||||
@@ -127,6 +127,7 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
|
||||
SuppressStatements: conf.IgnoredLogs(),
|
||||
}
|
||||
logger = loggers.New(logOpts)
|
||||
|
||||
}
|
||||
|
||||
memCache := dynacache.New(dynacache.Options{Running: conf.Running(), Log: logger})
|
||||
@@ -145,6 +146,9 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
|
||||
}
|
||||
|
||||
confm := cfg.Configs
|
||||
if err := confm.Validate(logger); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var sites []*Site
|
||||
|
||||
ns := &contentNodeShifter{
|
||||
|
||||
@@ -885,3 +885,29 @@ build:
|
||||
b.AssertFileExists("public/tags/a/index.html", false)
|
||||
b.AssertFileContent("public/index.html", "|0|")
|
||||
}
|
||||
|
||||
func TestTaxonomiesTermLookup(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
[taxonomies]
|
||||
tag = "tags"
|
||||
-- content/_index.md --
|
||||
---
|
||||
title: "Home"
|
||||
tags: ["a", "b"]
|
||||
---
|
||||
-- layouts/taxonomy/tag.html --
|
||||
Tag: {{ .Title }}|
|
||||
-- content/tags/a/_index.md --
|
||||
---
|
||||
title: tag-a-title-override
|
||||
---
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/tags/a/index.html", "Tag: tag-a-title-override|")
|
||||
}
|
||||
|
||||
+5
-2
@@ -1,7 +1,10 @@
|
||||
# Release env.
|
||||
# These will be replaced by script before release.
|
||||
HUGORELEASER_TAG=v0.123.3
|
||||
HUGORELEASER_COMMITISH=a75a659f6fc0cb3a52b2b2ba666a81f79a459376
|
||||
HUGORELEASER_TAG=v0.123.6
|
||||
HUGORELEASER_COMMITISH=92684f9a26838a46d1a81e3c250fef5207bcb735
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+4
-4
@@ -48,10 +48,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
_ images.ImageResource = (*imageResource)(nil)
|
||||
_ resource.Source = (*imageResource)(nil)
|
||||
_ resource.Cloner = (*imageResource)(nil)
|
||||
_ resource.NameOriginalProvider = (*imageResource)(nil)
|
||||
_ images.ImageResource = (*imageResource)(nil)
|
||||
_ resource.Source = (*imageResource)(nil)
|
||||
_ resource.Cloner = (*imageResource)(nil)
|
||||
_ resource.NameNormalizedProvider = (*imageResource)(nil)
|
||||
)
|
||||
|
||||
// imageResource represents an image resource.
|
||||
|
||||
@@ -39,7 +39,16 @@ func (c *ImageCache) getOrCreate(
|
||||
) (*resourceAdapter, error) {
|
||||
relTarget := parent.relTargetPathFromConfig(conf)
|
||||
relTargetPath := relTarget.TargetPath()
|
||||
memKey := dynacache.CleanKey(relTargetPath)
|
||||
memKey := relTargetPath
|
||||
|
||||
// For multihost sites, we duplicate language versions of the same resource,
|
||||
// so we need to include the language in the key.
|
||||
// Note that we don't need to include the language in the file cache key,
|
||||
// as the hash will take care of any different content.
|
||||
if c.pathSpec.Cfg.IsMultihost() {
|
||||
memKey = c.pathSpec.Lang() + memKey
|
||||
}
|
||||
memKey = dynacache.CleanKey(memKey)
|
||||
|
||||
v, err := c.mcache.GetOrCreate(memKey, func(key string) (*resourceAdapter, error) {
|
||||
var img *imageResource
|
||||
|
||||
@@ -98,6 +98,12 @@ func isGlobWithExtension(s string) bool {
|
||||
return strings.Count(last, ".") > 0
|
||||
}
|
||||
|
||||
func CheckCascadePattern(logger loggers.Logger, m PageMatcher) {
|
||||
if logger != nil && isGlobWithExtension(m.Path) {
|
||||
logger.Erroridf("cascade-pattern-with-extension", "cascade target path %q looks like a path with an extension; since Hugo v0.123.0 this will not match anything, see https://gohugo.io/methods/page/path/", m.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func DecodeCascadeConfig(logger loggers.Logger, in any) (*config.ConfigNamespace[[]PageMatcherParamsConfig, map[PageMatcher]maps.Params], error) {
|
||||
buildConfig := func(in any) (map[PageMatcher]maps.Params, any, error) {
|
||||
cascade := make(map[PageMatcher]maps.Params)
|
||||
@@ -127,9 +133,7 @@ func DecodeCascadeConfig(logger loggers.Logger, in any) (*config.ConfigNamespace
|
||||
|
||||
for _, cfg := range cfgs {
|
||||
m := cfg.Target
|
||||
if isGlobWithExtension(m.Path) {
|
||||
logger.Erroridf("cascade-pattern-with-extension", "cascade target path %q looks like a path with an extension; since Hugo v0.123.0 this will not match anything, see https://gohugo.io/methods/page/path/", m.Path)
|
||||
}
|
||||
CheckCascadePattern(logger, m)
|
||||
c, found := cascade[m]
|
||||
if found {
|
||||
// Merge
|
||||
|
||||
@@ -59,8 +59,8 @@ type ResourceSourceDescriptor struct {
|
||||
// The canonical source path.
|
||||
Path *paths.Path
|
||||
|
||||
// The name of the resource.
|
||||
Name string
|
||||
// The normalized name of the resource.
|
||||
NameNormalized string
|
||||
|
||||
// The name of the resource as it was read from the source.
|
||||
NameOriginal string
|
||||
@@ -135,12 +135,12 @@ func (fd *ResourceSourceDescriptor) init(r *Spec) error {
|
||||
fd.TargetBasePaths[i] = dir
|
||||
}
|
||||
|
||||
if fd.Name == "" {
|
||||
fd.Name = fd.TargetPath
|
||||
if fd.NameNormalized == "" {
|
||||
fd.NameNormalized = fd.TargetPath
|
||||
}
|
||||
|
||||
if fd.NameOriginal == "" {
|
||||
fd.NameOriginal = fd.Name
|
||||
fd.NameOriginal = fd.NameNormalized
|
||||
}
|
||||
|
||||
mediaType := fd.MediaType
|
||||
@@ -228,7 +228,7 @@ type baseResourceResource interface {
|
||||
|
||||
type baseResourceInternal interface {
|
||||
resource.Source
|
||||
resource.NameOriginalProvider
|
||||
resource.NameNormalizedProvider
|
||||
|
||||
fileInfo
|
||||
mediaTypeAssigner
|
||||
@@ -457,8 +457,8 @@ func (l *genericResource) Name() string {
|
||||
return l.name
|
||||
}
|
||||
|
||||
func (l *genericResource) NameOriginal() string {
|
||||
return l.sd.NameOriginal
|
||||
func (l *genericResource) NameNormalized() string {
|
||||
return l.sd.NameNormalized
|
||||
}
|
||||
|
||||
func (l *genericResource) Params() maps.Params {
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/hugofs/glob"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
@@ -73,10 +72,10 @@ func (r Resources) Get(name any) Resource {
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, check the original name.
|
||||
// Finally, check the normalized name.
|
||||
for _, resource := range r {
|
||||
if nop, ok := resource.(NameOriginalProvider); ok {
|
||||
if strings.EqualFold(namestr, nop.NameOriginal()) {
|
||||
if nop, ok := resource.(NameNormalizedProvider); ok {
|
||||
if strings.EqualFold(namestr, nop.NameNormalized()) {
|
||||
return resource
|
||||
}
|
||||
}
|
||||
@@ -93,23 +92,21 @@ func (r Resources) GetMatch(pattern any) Resource {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
patternstr = paths.NormalizePathStringBasic(patternstr)
|
||||
|
||||
g, err := glob.GetGlob(patternstr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, resource := range r {
|
||||
if g.Match(paths.NormalizePathStringBasic(resource.Name())) {
|
||||
if g.Match(resource.Name()) {
|
||||
return resource
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, check the original name.
|
||||
for _, resource := range r {
|
||||
if nop, ok := resource.(NameOriginalProvider); ok {
|
||||
if g.Match(paths.NormalizePathStringBasic(nop.NameOriginal())) {
|
||||
if nop, ok := resource.(NameNormalizedProvider); ok {
|
||||
if g.Match(nop.NameNormalized()) {
|
||||
return resource
|
||||
}
|
||||
}
|
||||
@@ -140,15 +137,15 @@ func (r Resources) Match(pattern any) Resources {
|
||||
|
||||
var matches Resources
|
||||
for _, resource := range r {
|
||||
if g.Match(strings.ToLower(resource.Name())) {
|
||||
if g.Match(resource.Name()) {
|
||||
matches = append(matches, resource)
|
||||
}
|
||||
}
|
||||
if len(matches) == 0 {
|
||||
// Fall back to the original name.
|
||||
// Fall back to the normalized name.
|
||||
for _, resource := range r {
|
||||
if nop, ok := resource.(NameOriginalProvider); ok {
|
||||
if g.Match(strings.ToLower(nop.NameOriginal())) {
|
||||
if nop, ok := resource.(NameNormalizedProvider); ok {
|
||||
if g.Match(nop.NameNormalized()) {
|
||||
matches = append(matches, resource)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,20 +127,16 @@ type ResourceNameTitleProvider interface {
|
||||
// So, for the image "/some/path/sunset.jpg" this will be "sunset.jpg".
|
||||
// The value returned by this method will be used in the GetByPrefix and ByPrefix methods
|
||||
// on Resources.
|
||||
// Note that for bundled content resources with language code in the filename, this will
|
||||
// be the name without the language code.
|
||||
Name() string
|
||||
|
||||
// Title returns the title if set in front matter. For content pages, this will be the expected value.
|
||||
Title() string
|
||||
}
|
||||
|
||||
type NameOriginalProvider interface {
|
||||
// NameOriginal is the original name of this resource.
|
||||
// Note that for bundled content resources with language code in the filename, this will
|
||||
// be the name with the language code.
|
||||
type NameNormalizedProvider interface {
|
||||
// NameNormalized is the normalized name of this resource.
|
||||
// For internal use (for now).
|
||||
NameOriginal() string
|
||||
NameNormalized() string
|
||||
}
|
||||
|
||||
type ResourceParamsProvider interface {
|
||||
@@ -166,6 +162,8 @@ type ResourcesLanguageMerger interface {
|
||||
|
||||
// Identifier identifies a resource.
|
||||
type Identifier interface {
|
||||
// Key is is mostly for internal use and should be considered opaque.
|
||||
// This value may change between Hugo versions.
|
||||
Key() string
|
||||
}
|
||||
|
||||
|
||||
@@ -28,10 +28,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
_ mediaTypeAssigner = (*genericResource)(nil)
|
||||
_ mediaTypeAssigner = (*imageResource)(nil)
|
||||
_ resource.Staler = (*genericResource)(nil)
|
||||
_ resource.NameOriginalProvider = (*genericResource)(nil)
|
||||
_ mediaTypeAssigner = (*genericResource)(nil)
|
||||
_ mediaTypeAssigner = (*imageResource)(nil)
|
||||
_ resource.Staler = (*genericResource)(nil)
|
||||
_ resource.NameNormalizedProvider = (*genericResource)(nil)
|
||||
)
|
||||
|
||||
// metaAssigner allows updating metadata in resources that supports it.
|
||||
|
||||
@@ -170,8 +170,8 @@ func (r *Spec) NewResource(rd ResourceSourceDescriptor) (resource.Resource, erro
|
||||
spec: r,
|
||||
sd: rd,
|
||||
params: make(map[string]any),
|
||||
name: rd.Name,
|
||||
title: rd.Name,
|
||||
name: rd.NameOriginal,
|
||||
title: rd.NameOriginal,
|
||||
}
|
||||
|
||||
if rd.MediaType.MainType == "image" {
|
||||
|
||||
@@ -117,8 +117,8 @@ func fetchResourceForSpec(spec *resources.Spec, c *qt.C, name string, targetPath
|
||||
targetPath := name
|
||||
base := "/a/"
|
||||
r, err := spec.NewResource(resources.ResourceSourceDescriptor{
|
||||
LazyPublish: true,
|
||||
Name: name, TargetPath: targetPath, BasePathRelPermalink: base, BasePathTargetPath: base, OpenReadSeekCloser: open,
|
||||
LazyPublish: true,
|
||||
NameNormalized: name, TargetPath: targetPath, BasePathRelPermalink: base, BasePathTargetPath: base, OpenReadSeekCloser: open,
|
||||
GroupIdentity: identity.Anonymous,
|
||||
})
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
@@ -55,7 +55,7 @@ var (
|
||||
_ resource.WithResourceMetaProvider = (*resourceAdapter)(nil)
|
||||
_ identity.DependencyManagerProvider = (*resourceAdapter)(nil)
|
||||
_ identity.IdentityGroupProvider = (*resourceAdapter)(nil)
|
||||
_ resource.NameOriginalProvider = (*resourceAdapter)(nil)
|
||||
_ resource.NameNormalizedProvider = (*resourceAdapter)(nil)
|
||||
)
|
||||
|
||||
// These are transformations that need special support in Hugo that may not
|
||||
@@ -282,9 +282,9 @@ func (r *resourceAdapter) Name() string {
|
||||
return r.metaProvider.Name()
|
||||
}
|
||||
|
||||
func (r *resourceAdapter) NameOriginal() string {
|
||||
func (r *resourceAdapter) NameNormalized() string {
|
||||
r.init(false, false)
|
||||
return r.target.(resource.NameOriginalProvider).NameOriginal()
|
||||
return r.target.(resource.NameNormalizedProvider).NameNormalized()
|
||||
}
|
||||
|
||||
func (r *resourceAdapter) Params() maps.Params {
|
||||
|
||||
Reference in New Issue
Block a user