mirror of
https://github.com/gohugoio/hugo.git
synced 2026-09-02 03:32:38 +00:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 629f84e8ed | |||
| 76ef3f42fa | |||
| 0ccb6cdc04 | |||
| 1f1c62e6c7 | |||
| f1d755965f | |||
| 558f74f009 | |||
| ba03114aa9 | |||
| 3935faa417 | |||
| d4d49e0f0e | |||
| 78178d0c2a | |||
| 68d92ef9dd | |||
| b40f3c7df6 | |||
| 57206e7274 | |||
| b1f8676347 | |||
| 07b2e535be | |||
| f038a51b3e | |||
| b4bff6190c | |||
| d2cebee273 | |||
| be914ff34d | |||
| 48a0fea87a | |||
| 9ca1de09dd | |||
| e62675002e | |||
| 9668759ad8 | |||
| 9e9b1f110c | |||
| dc6a292133 | |||
| 4f92f949ea | |||
| d24ffdde5b | |||
| ca31b95f30 | |||
| 5fed9c591b | |||
| 134e7d1d3d | |||
| 1f48b717c7 | |||
| ada3fceea8 | |||
| 05e23bd55c | |||
| 7afac3f1ad | |||
| a4b17470a8 | |||
| 4d5e173cf8 | |||
| 4271b6be0f | |||
| 0567a3e6f1 | |||
| 632ad74fc5 | |||
| b1de03f398 | |||
| 312735366b | |||
| 2b2f2b75ef | |||
| 0d6e593ffb | |||
| 7023cf0f07 | |||
| 9dfa9e70e6 | |||
| 3f217fd664 | |||
| 92684f9a26 | |||
| fce8d82b79 | |||
| 4a502f7eb4 | |||
| be1dbba0f7 | |||
| 1007bcdf49 | |||
| 1904ba40e8 | |||
| 6a8b25e291 | |||
| a322282e70 | |||
| 6bc0d745a5 | |||
| 218690328c | |||
| 673c634131 | |||
| 21a41003c4 | |||
| 1736ef7459 | |||
| d4be1643a0 | |||
| f27e578718 | |||
| d310595a2b | |||
| 049dd1d7e0 | |||
| 189b72331e | |||
| b2b7bfdd3a | |||
| 80e69344da | |||
| 7508a16470 |
@@ -4,7 +4,7 @@ parameters:
|
||||
defaults: &defaults
|
||||
resource_class: large
|
||||
docker:
|
||||
- image: bepsays/ci-hugoreleaser:1.22200.20000
|
||||
- image: bepsays/ci-hugoreleaser:1.22200.20100
|
||||
environment: &buildenv
|
||||
GOMODCACHE: /root/project/gomodcache
|
||||
version: 2
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
environment:
|
||||
<<: [*buildenv]
|
||||
docker:
|
||||
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22200.20000
|
||||
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22200.20100
|
||||
steps:
|
||||
- *restore-cache
|
||||
- &attach-workspace
|
||||
|
||||
@@ -521,6 +521,7 @@ func applyLocalFlagsBuildConfig(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.Flags().StringP("cacheDir", "", "", "filesystem path to cache directory")
|
||||
_ = cmd.Flags().SetAnnotation("cacheDir", cobra.BashCompSubdirsInDir, []string{})
|
||||
cmd.Flags().StringP("contentDir", "c", "", "filesystem path to content directory")
|
||||
cmd.Flags().StringSliceP("renderSegments", "", []string{}, "named segments to render (configured in the segments config)")
|
||||
_ = cmd.Flags().SetAnnotation("theme", cobra.BashCompSubdirsInDir, []string{"themes"})
|
||||
}
|
||||
|
||||
|
||||
+9
-10
@@ -512,9 +512,9 @@ func (c *serverCommand) Init(cd *simplecobra.Commandeer) error {
|
||||
cmd.Long = `Hugo provides its own webserver which builds and serves the site.
|
||||
While hugo server is high performance, it is a webserver with limited options.
|
||||
|
||||
'hugo server' will by default write and server files from disk, but you can
|
||||
render to memory by using the '--renderToMemory' flag. This can be faster
|
||||
in some cases, but it will consume more memory.
|
||||
The ` + "`" + `hugo server` + "`" + ` command will by default write and serve files from disk, but
|
||||
you can render to memory by using the ` + "`" + `--renderToMemory` + "`" + ` flag. This can be
|
||||
faster in some cases, but it will consume more memory.
|
||||
|
||||
By default hugo will also watch your files for any changes you make and
|
||||
automatically rebuild the site. It will then live reload any open browser pages
|
||||
@@ -617,9 +617,9 @@ func (c *serverCommand) setBaseURLsInConfig() error {
|
||||
}
|
||||
return c.withConfE(func(conf *commonConfig) error {
|
||||
for i, language := range conf.configs.Languages {
|
||||
isMultiHost := conf.configs.IsMultihost
|
||||
isMultihost := conf.configs.IsMultihost
|
||||
var serverPort int
|
||||
if isMultiHost {
|
||||
if isMultihost {
|
||||
serverPort = c.serverPorts[i].p
|
||||
} else {
|
||||
serverPort = c.serverPorts[0].p
|
||||
@@ -737,9 +737,9 @@ func (c *serverCommand) createServerPorts(cd *simplecobra.Commandeer) error {
|
||||
flags := cd.CobraCommand.Flags()
|
||||
var cerr error
|
||||
c.withConf(func(conf *commonConfig) {
|
||||
isMultiHost := conf.configs.IsMultihost
|
||||
isMultihost := conf.configs.IsMultihost
|
||||
c.serverPorts = make([]serverPortListener, 1)
|
||||
if isMultiHost {
|
||||
if isMultihost {
|
||||
if !c.serverAppend {
|
||||
cerr = errors.New("--appendPort=false not supported when in multihost mode")
|
||||
return
|
||||
@@ -852,7 +852,7 @@ func (c *serverCommand) serve() error {
|
||||
h *hugolib.HugoSites
|
||||
)
|
||||
err := c.withConfE(func(conf *commonConfig) error {
|
||||
isMultiHost := conf.configs.IsMultihost
|
||||
isMultihost := conf.configs.IsMultihost
|
||||
var err error
|
||||
h, err = c.r.HugFromConfig(conf)
|
||||
if err != nil {
|
||||
@@ -862,7 +862,7 @@ func (c *serverCommand) serve() error {
|
||||
// We need the server to share the same logger as the Hugo build (for error counts etc.)
|
||||
c.r.logger = h.Log
|
||||
|
||||
if isMultiHost {
|
||||
if isMultihost {
|
||||
for _, l := range conf.configs.ConfigLangs() {
|
||||
baseURLs = append(baseURLs, l.BaseURL())
|
||||
roots = append(roots, l.Language().Lang)
|
||||
@@ -1005,7 +1005,6 @@ func (c *serverCommand) serve() error {
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
c.r.Println("Error:", err)
|
||||
}
|
||||
|
||||
@@ -111,11 +111,29 @@ func (i HugoInfo) Deps() []*Dependency {
|
||||
return i.deps
|
||||
}
|
||||
|
||||
// Deprecated: Use hugo.IsMultihost instead.
|
||||
func (i HugoInfo) IsMultiHost() bool {
|
||||
Deprecate("hugo.IsMultiHost", "Use hugo.IsMultihost instead.", "v0.124.0")
|
||||
return i.conf.IsMultihost()
|
||||
}
|
||||
|
||||
// IsMultihost reports whether each configured language has a unique baseURL.
|
||||
func (i HugoInfo) IsMultihost() bool {
|
||||
return i.conf.IsMultihost()
|
||||
}
|
||||
|
||||
// IsMultilingual reports whether there are two or more configured languages.
|
||||
func (i HugoInfo) IsMultilingual() bool {
|
||||
return i.conf.IsMultilingual()
|
||||
}
|
||||
|
||||
// ConfigProvider represents the config options that are relevant for HugoInfo.
|
||||
type ConfigProvider interface {
|
||||
Environment() string
|
||||
Running() bool
|
||||
WorkingDir() string
|
||||
IsMultihost() bool
|
||||
IsMultilingual() bool
|
||||
}
|
||||
|
||||
// NewInfo creates a new Hugo Info object.
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
// 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 hugo_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
)
|
||||
|
||||
func TestIsMultilingualAndIsMultihost(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
|
||||
defaultContentLanguageInSubdir = true
|
||||
[languages.de]
|
||||
baseURL = 'https://de.example.org/'
|
||||
[languages.en]
|
||||
baseURL = 'https://en.example.org/'
|
||||
-- content/_index.md --
|
||||
---
|
||||
title: home
|
||||
---
|
||||
-- layouts/index.html --
|
||||
multilingual={{ hugo.IsMultilingual }}
|
||||
multihost={{ hugo.IsMultihost }}
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/de/index.html",
|
||||
"multilingual=true",
|
||||
"multihost=true",
|
||||
)
|
||||
b.AssertFileContent("public/en/index.html",
|
||||
"multilingual=true",
|
||||
"multihost=true",
|
||||
)
|
||||
|
||||
files = strings.ReplaceAll(files, "baseURL = 'https://de.example.org/'", "")
|
||||
files = strings.ReplaceAll(files, "baseURL = 'https://en.example.org/'", "")
|
||||
|
||||
b = hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/de/index.html",
|
||||
"multilingual=true",
|
||||
"multihost=false",
|
||||
)
|
||||
b.AssertFileContent("public/en/index.html",
|
||||
"multilingual=true",
|
||||
"multihost=false",
|
||||
)
|
||||
|
||||
files = strings.ReplaceAll(files, "[languages.de]", "")
|
||||
files = strings.ReplaceAll(files, "[languages.en]", "")
|
||||
|
||||
b = hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/en/index.html",
|
||||
"multilingual=false",
|
||||
"multihost=false",
|
||||
)
|
||||
}
|
||||
@@ -65,9 +65,11 @@ func TestDeprecationLogLevelFromVersion(t *testing.T) {
|
||||
}
|
||||
|
||||
type testConfig struct {
|
||||
environment string
|
||||
running bool
|
||||
workingDir string
|
||||
environment string
|
||||
running bool
|
||||
workingDir string
|
||||
multihost bool
|
||||
multilingual bool
|
||||
}
|
||||
|
||||
func (c testConfig) Environment() string {
|
||||
@@ -81,3 +83,11 @@ func (c testConfig) Running() bool {
|
||||
func (c testConfig) WorkingDir() string {
|
||||
return c.workingDir
|
||||
}
|
||||
|
||||
func (c testConfig) IsMultihost() bool {
|
||||
return c.multihost
|
||||
}
|
||||
|
||||
func (c testConfig) IsMultilingual() bool {
|
||||
return c.multilingual
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ package hugo
|
||||
// This should be the only one.
|
||||
var CurrentVersion = Version{
|
||||
Major: 0,
|
||||
Minor: 123,
|
||||
PatchLevel: 3,
|
||||
Minor: 124,
|
||||
PatchLevel: 0,
|
||||
Suffix: "",
|
||||
}
|
||||
|
||||
@@ -239,7 +239,7 @@ const (
|
||||
// E.g. /blog/my-post.md
|
||||
PathTypeContentSingle
|
||||
|
||||
// All bewlow are bundled content files.
|
||||
// All below are bundled content files.
|
||||
|
||||
// Leaf bundles, e.g. /blog/my-post/index.md
|
||||
PathTypeLeaf
|
||||
@@ -313,7 +313,7 @@ func (p *Path) norm(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
// IdentifierBase satifies identity.Identity.
|
||||
// IdentifierBase satisfies identity.Identity.
|
||||
func (p *Path) IdentifierBase() string {
|
||||
return p.Base()
|
||||
}
|
||||
@@ -368,7 +368,7 @@ func (p *Path) Name() string {
|
||||
return p.s
|
||||
}
|
||||
|
||||
// Name returns the last element of path withhout any extension.
|
||||
// Name returns the last element of path without any extension.
|
||||
func (p *Path) NameNoExt() string {
|
||||
if i := p.identifierIndex(0); i != -1 {
|
||||
return p.s[p.posContainerHigh : p.identifiers[i].Low-1]
|
||||
@@ -376,7 +376,7 @@ func (p *Path) NameNoExt() string {
|
||||
return p.s[p.posContainerHigh:]
|
||||
}
|
||||
|
||||
// Name returns the last element of path withhout any language identifier.
|
||||
// Name returns the last element of path without any language identifier.
|
||||
func (p *Path) NameNoLang() string {
|
||||
i := p.identifierIndex(p.posIdentifierLanguage)
|
||||
if i == -1 {
|
||||
@@ -386,7 +386,7 @@ func (p *Path) NameNoLang() string {
|
||||
return p.s[p.posContainerHigh:p.identifiers[i].Low-1] + p.s[p.identifiers[i].High:]
|
||||
}
|
||||
|
||||
// BaseNameNoIdentifier returns the logcical base name for a resource without any idenifier (e.g. no extension).
|
||||
// BaseNameNoIdentifier returns the logical base name for a resource without any identifier (e.g. no extension).
|
||||
// For bundles this will be the containing directory's name, e.g. "blog".
|
||||
func (p *Path) BaseNameNoIdentifier() string {
|
||||
if p.IsBundle() {
|
||||
@@ -395,7 +395,7 @@ func (p *Path) BaseNameNoIdentifier() string {
|
||||
return p.NameNoIdentifier()
|
||||
}
|
||||
|
||||
// NameNoIdentifier returns the last element of path withhout any identifier (e.g. no extension).
|
||||
// NameNoIdentifier returns the last element of path without any identifier (e.g. no extension).
|
||||
func (p *Path) NameNoIdentifier() string {
|
||||
if len(p.identifiers) > 0 {
|
||||
return p.s[p.posContainerHigh : p.identifiers[len(p.identifiers)-1].Low-1]
|
||||
@@ -435,7 +435,7 @@ func (p *Path) PathNoIdentifier() string {
|
||||
return p.base(false, false)
|
||||
}
|
||||
|
||||
// PathRel returns the path relativeto the given owner.
|
||||
// PathRel returns the path relative to the given owner.
|
||||
func (p *Path) PathRel(owner *Path) string {
|
||||
ob := owner.Base()
|
||||
if !strings.HasSuffix(ob, "/") {
|
||||
|
||||
@@ -99,6 +99,14 @@ type Unwrapper interface {
|
||||
Unwrapv() any
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying value of v if it implements Unwrapper, otherwise v is returned.
|
||||
func Unwrapv(v any) any {
|
||||
if u, ok := v.(Unwrapper); ok {
|
||||
return u.Unwrapv()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// LowHigh is typically used to represent a slice boundary.
|
||||
type LowHigh struct {
|
||||
Low int
|
||||
|
||||
@@ -39,6 +39,7 @@ import (
|
||||
"github.com/gohugoio/hugo/config/services"
|
||||
"github.com/gohugoio/hugo/deploy/deployconfig"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugolib/segments"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
"github.com/gohugoio/hugo/markup/markup_config"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
@@ -103,9 +104,11 @@ type Config struct {
|
||||
RootConfig
|
||||
|
||||
// Author information.
|
||||
// Deprecated: Use taxonomies instead.
|
||||
Author map[string]any
|
||||
|
||||
// Social links.
|
||||
// Deprecated: Use .Site.Params instead.
|
||||
Social map[string]string
|
||||
|
||||
// The build configuration section contains build-related configuration options.
|
||||
@@ -137,6 +140,9 @@ type Config struct {
|
||||
// a slice of page matcher and params to apply to those pages.
|
||||
Cascade *config.ConfigNamespace[[]page.PageMatcherParamsConfig, map[page.PageMatcher]maps.Params] `mapstructure:"-"`
|
||||
|
||||
// The segments defines segments for the site. Used for partial/segmented builds.
|
||||
Segments *config.ConfigNamespace[map[string]segments.SegmentConfig, segments.Segments] `mapstructure:"-"`
|
||||
|
||||
// Menu configuration.
|
||||
// <docsmeta>{"refs": ["config:languages:menus"] }</docsmeta>
|
||||
Menus *config.ConfigNamespace[map[string]navigation.MenuConfig, navigation.Menus] `mapstructure:"-"`
|
||||
@@ -364,6 +370,7 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
|
||||
CreateTitle: helpers.GetTitleFunc(c.TitleCaseStyle),
|
||||
IsUglyURLSection: isUglyURL,
|
||||
IgnoreFile: ignoreFile,
|
||||
SegmentFilter: c.Segments.Config.Get(func(s string) { logger.Warnf("Render segment %q not found in configuration", s) }, c.RootConfig.RenderSegments...),
|
||||
MainSections: c.MainSections,
|
||||
Clock: clock,
|
||||
transientErr: transientErr,
|
||||
@@ -400,6 +407,7 @@ type ConfigCompiled struct {
|
||||
CreateTitle func(s string) string
|
||||
IsUglyURLSection func(section string) bool
|
||||
IgnoreFile func(filename string) bool
|
||||
SegmentFilter segments.SegmentFilter
|
||||
MainSections []string
|
||||
Clock time.Time
|
||||
|
||||
@@ -472,6 +480,10 @@ type RootConfig struct {
|
||||
// A list of languages to disable.
|
||||
DisableLanguages []string
|
||||
|
||||
// The named segments to render.
|
||||
// This needs to match the name of the segment in the segments configuration.
|
||||
RenderSegments []string
|
||||
|
||||
// Disable the injection of the Hugo generator tag on the home page.
|
||||
DisableHugoGeneratorInject bool
|
||||
|
||||
@@ -671,6 +683,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 {
|
||||
@@ -819,7 +838,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
langConfigMap := make(map[string]*Config)
|
||||
|
||||
languagesConfig := cfg.GetStringMap("languages")
|
||||
var isMultiHost bool
|
||||
var isMultihost bool
|
||||
|
||||
if err := all.CompileConfig(logger); err != nil {
|
||||
return nil, err
|
||||
@@ -856,7 +875,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
}
|
||||
if kk == "baseurl" {
|
||||
// baseURL configure don the language level is a multihost setup.
|
||||
isMultiHost = true
|
||||
isMultihost = true
|
||||
}
|
||||
mergedConfig.Set(kk, vv)
|
||||
rootv := cfg.Get(kk)
|
||||
@@ -906,7 +925,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
}
|
||||
|
||||
// Adjust Goldmark config defaults for multilingual, single-host sites.
|
||||
if len(languagesConfig) > 1 && !isMultiHost && !clone.Markup.Goldmark.DuplicateResourceFiles {
|
||||
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)
|
||||
@@ -936,7 +955,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
Base: all,
|
||||
LanguageConfigMap: langConfigMap,
|
||||
LoadingInfo: res,
|
||||
IsMultihost: isMultiHost,
|
||||
IsMultihost: isMultihost,
|
||||
}
|
||||
|
||||
return cm, nil
|
||||
@@ -969,7 +988,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"
|
||||
@@ -26,11 +25,13 @@ import (
|
||||
"github.com/gohugoio/hugo/config/security"
|
||||
"github.com/gohugoio/hugo/config/services"
|
||||
"github.com/gohugoio/hugo/deploy/deployconfig"
|
||||
"github.com/gohugoio/hugo/hugolib/segments"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
"github.com/gohugoio/hugo/markup/markup_config"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/minifiers"
|
||||
"github.com/gohugoio/hugo/modules"
|
||||
|
||||
"github.com/gohugoio/hugo/navigation"
|
||||
"github.com/gohugoio/hugo/output"
|
||||
"github.com/gohugoio/hugo/related"
|
||||
@@ -43,11 +44,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 {
|
||||
@@ -122,6 +122,14 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
return err
|
||||
},
|
||||
},
|
||||
"segments": {
|
||||
key: "segments",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.Segments, err = segments.DecodeSegments(p.p.GetStringMap(d.key))
|
||||
return err
|
||||
},
|
||||
},
|
||||
"server": {
|
||||
key: "server",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
@@ -293,7 +301,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
|
||||
},
|
||||
},
|
||||
|
||||
@@ -52,7 +52,7 @@ func (c ConfigLanguage) LanguagePrefix() string {
|
||||
return c.Language().Lang
|
||||
}
|
||||
|
||||
if !c.IsMultiLingual() || c.DefaultContentLanguage() == c.Language().Lang {
|
||||
if !c.IsMultilingual() || c.DefaultContentLanguage() == c.Language().Lang {
|
||||
return ""
|
||||
}
|
||||
return c.Language().Lang
|
||||
@@ -78,7 +78,7 @@ func (c ConfigLanguage) FastRenderMode() bool {
|
||||
return c.config.Internal.FastRenderMode
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) IsMultiLingual() bool {
|
||||
func (c ConfigLanguage) IsMultilingual() bool {
|
||||
return len(c.m.Languages) > 1
|
||||
}
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@ func (l configLoader) applyDefaultConfig() error {
|
||||
"menus": maps.Params{},
|
||||
"disableLiveReload": false,
|
||||
"pluralizeListTitles": true,
|
||||
"CapitalizeListTitles": true,
|
||||
"capitalizeListTitles": true,
|
||||
"forceSyncStatic": false,
|
||||
"footnoteAnchorPrefix": "",
|
||||
"footnoteReturnLinkContents": "",
|
||||
|
||||
@@ -35,7 +35,7 @@ type AllProvider interface {
|
||||
PathParser() *paths.PathParser
|
||||
Environment() string
|
||||
IsMultihost() bool
|
||||
IsMultiLingual() bool
|
||||
IsMultilingual() bool
|
||||
NoBuildLock() bool
|
||||
BaseConfig() BaseConfig
|
||||
Dirs() CommonDirs
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
For a given taxonomy, renders a list of terms assigned to the page.
|
||||
|
||||
@context {page} page The current page.
|
||||
@context {string} taxonomy The taxonony.
|
||||
@context {string} taxonomy The taxonomy.
|
||||
|
||||
@example: {{ partial "terms.html" (dict "taxonomy" "tags" "page" .) }}
|
||||
*/}}
|
||||
|
||||
Vendored
+1
-1
@@ -361,7 +361,7 @@ type BuildState struct {
|
||||
|
||||
mu sync.Mutex // protects state below.
|
||||
|
||||
// A set of ilenames in /public that
|
||||
// A set of filenames in /public that
|
||||
// contains a post-processing prefix.
|
||||
filenamesWithPostPrefix map[string]bool
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ hugo [flags]
|
||||
--printPathWarnings print warnings on duplicate target paths etc.
|
||||
--printUnusedTemplates print warnings on unused templates.
|
||||
--quiet build in quiet mode
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
--renderToMemory render to memory (mostly useful when running the server)
|
||||
-s, --source string filesystem path to read files relative from
|
||||
--templateMetrics display metrics about template executions
|
||||
|
||||
@@ -18,13 +18,14 @@ hugo config [command] [flags]
|
||||
### Options
|
||||
|
||||
```
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
--format string preferred file format (toml, yaml or json) (default "toml")
|
||||
-h, --help help for config
|
||||
--lang string the language to display config for. Defaults to the first language defined.
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
--format string preferred file format (toml, yaml or json) (default "toml")
|
||||
-h, --help help for config
|
||||
--lang string the language to display config for. Defaults to the first language defined.
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -14,11 +14,12 @@ hugo config mounts [flags] [args]
|
||||
### Options
|
||||
|
||||
```
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for mounts
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for mounts
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -18,13 +18,14 @@ hugo mod clean [flags] [args]
|
||||
### Options
|
||||
|
||||
```
|
||||
--all clean entire module cache
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for clean
|
||||
--pattern string pattern matching module paths to clean (all if not set), e.g. "**hugo*"
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
--all clean entire module cache
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for clean
|
||||
--pattern string pattern matching module paths to clean (all if not set), e.g. "**hugo*"
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -20,12 +20,13 @@ hugo mod graph [flags] [args]
|
||||
### Options
|
||||
|
||||
```
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
--clean delete module cache for dependencies that fail verification
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for graph
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
--clean delete module cache for dependencies that fail verification
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for graph
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -25,11 +25,12 @@ hugo mod init [flags] [args]
|
||||
### Options
|
||||
|
||||
```
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for init
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for init
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -28,11 +28,12 @@ hugo mod npm pack [flags] [args]
|
||||
### Options
|
||||
|
||||
```
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for pack
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for pack
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -14,11 +14,12 @@ hugo mod tidy [flags] [args]
|
||||
### Options
|
||||
|
||||
```
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for tidy
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for tidy
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -20,11 +20,12 @@ hugo mod vendor [flags] [args]
|
||||
### Options
|
||||
|
||||
```
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for vendor
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for vendor
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -18,12 +18,13 @@ hugo mod verify [flags] [args]
|
||||
### Options
|
||||
|
||||
```
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
--clean delete module cache for dependencies that fail verification
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for verify
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
--clean delete module cache for dependencies that fail verification
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for verify
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -25,14 +25,15 @@ hugo new content [path] [flags]
|
||||
### Options
|
||||
|
||||
```
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
--editor string edit new content with this editor, if provided
|
||||
-f, --force overwrite file if it already exists
|
||||
-h, --help help for content
|
||||
-k, --kind string content type to create
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
--editor string edit new content with this editor, if provided
|
||||
-f, --force overwrite file if it already exists
|
||||
-h, --help help for content
|
||||
-k, --kind string content type to create
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -12,9 +12,9 @@ A high performance webserver
|
||||
Hugo provides its own webserver which builds and serves the site.
|
||||
While hugo server is high performance, it is a webserver with limited options.
|
||||
|
||||
'hugo server' will by default write and server files from disk, but you can
|
||||
render to memory by using the '--renderToMemory' flag. This can be faster
|
||||
in some cases, but it will consume more memory.
|
||||
The `hugo server` command will by default write and serve files from disk, but
|
||||
you can render to memory by using the `--renderToMemory` flag. This can be
|
||||
faster in some cases, but it will consume more memory.
|
||||
|
||||
By default hugo will also watch your files for any changes you make and
|
||||
automatically rebuild the site. It will then live reload any open browser pages
|
||||
@@ -28,49 +28,50 @@ hugo server [command] [flags]
|
||||
### Options
|
||||
|
||||
```
|
||||
--appendPort append port to baseURL (default true)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--bind string interface to which the server will bind (default "127.0.0.1")
|
||||
-D, --buildDrafts include content marked as draft
|
||||
-E, --buildExpired include expired content
|
||||
-F, --buildFuture include content with publishdate in the future
|
||||
--cacheDir string filesystem path to cache directory
|
||||
--cleanDestinationDir remove files from destination not found in static directories
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
--disableBrowserError do not show build errors in the browser
|
||||
--disableFastRender enables full re-renders on changes
|
||||
--disableKinds strings disable different kind of pages (home, RSS etc.)
|
||||
--disableLiveReload watch without enabling live browser reload on rebuild
|
||||
--enableGitInfo add Git revision, date, author, and CODEOWNERS info to the pages
|
||||
--forceSyncStatic copy all files when static is changed.
|
||||
--gc enable to run some cleanup tasks (remove unused cache files) after the build
|
||||
-h, --help help for server
|
||||
--ignoreCache ignores the cache directory
|
||||
-l, --layoutDir string filesystem path to layout directory
|
||||
--liveReloadPort int port for live reloading (i.e. 443 in HTTPS proxy situations) (default -1)
|
||||
--minify minify any supported output format (HTML, XML etc.)
|
||||
--navigateToChanged navigate to changed content file on live browser reload
|
||||
--noBuildLock don't create .hugo_build.lock file
|
||||
--noChmod don't sync permission mode of files
|
||||
--noHTTPCache prevent HTTP caching
|
||||
--noTimes don't sync modification time of files
|
||||
--panicOnWarning panic on first WARNING log
|
||||
--poll string set this to a poll interval, e.g --poll 700ms, to use a poll based approach to watch for file system changes
|
||||
-p, --port int port on which the server will listen (default 1313)
|
||||
--pprof enable the pprof server (port 8080)
|
||||
--printI18nWarnings print missing translations
|
||||
--printMemoryUsage print memory usage to screen at intervals
|
||||
--printPathWarnings print warnings on duplicate target paths etc.
|
||||
--printUnusedTemplates print warnings on unused templates.
|
||||
--renderStaticToDisk serve static files from disk and dynamic files from memory
|
||||
--templateMetrics display metrics about template executions
|
||||
--templateMetricsHints calculate some improvement hints when combined with --templateMetrics
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
--tlsAuto generate and use locally-trusted certificates.
|
||||
--tlsCertFile string path to TLS certificate file
|
||||
--tlsKeyFile string path to TLS key file
|
||||
--trace file write trace to file (not useful in general)
|
||||
-w, --watch watch filesystem for changes and recreate as needed (default true)
|
||||
--appendPort append port to baseURL (default true)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--bind string interface to which the server will bind (default "127.0.0.1")
|
||||
-D, --buildDrafts include content marked as draft
|
||||
-E, --buildExpired include expired content
|
||||
-F, --buildFuture include content with publishdate in the future
|
||||
--cacheDir string filesystem path to cache directory
|
||||
--cleanDestinationDir remove files from destination not found in static directories
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
--disableBrowserError do not show build errors in the browser
|
||||
--disableFastRender enables full re-renders on changes
|
||||
--disableKinds strings disable different kind of pages (home, RSS etc.)
|
||||
--disableLiveReload watch without enabling live browser reload on rebuild
|
||||
--enableGitInfo add Git revision, date, author, and CODEOWNERS info to the pages
|
||||
--forceSyncStatic copy all files when static is changed.
|
||||
--gc enable to run some cleanup tasks (remove unused cache files) after the build
|
||||
-h, --help help for server
|
||||
--ignoreCache ignores the cache directory
|
||||
-l, --layoutDir string filesystem path to layout directory
|
||||
--liveReloadPort int port for live reloading (i.e. 443 in HTTPS proxy situations) (default -1)
|
||||
--minify minify any supported output format (HTML, XML etc.)
|
||||
--navigateToChanged navigate to changed content file on live browser reload
|
||||
--noBuildLock don't create .hugo_build.lock file
|
||||
--noChmod don't sync permission mode of files
|
||||
--noHTTPCache prevent HTTP caching
|
||||
--noTimes don't sync modification time of files
|
||||
--panicOnWarning panic on first WARNING log
|
||||
--poll string set this to a poll interval, e.g --poll 700ms, to use a poll based approach to watch for file system changes
|
||||
-p, --port int port on which the server will listen (default 1313)
|
||||
--pprof enable the pprof server (port 8080)
|
||||
--printI18nWarnings print missing translations
|
||||
--printMemoryUsage print memory usage to screen at intervals
|
||||
--printPathWarnings print warnings on duplicate target paths etc.
|
||||
--printUnusedTemplates print warnings on unused templates.
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
--renderStaticToDisk serve static files from disk and dynamic files from memory
|
||||
--templateMetrics display metrics about template executions
|
||||
--templateMetricsHints calculate some improvement hints when combined with --templateMetrics
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
--tlsAuto generate and use locally-trusted certificates.
|
||||
--tlsCertFile string path to TLS certificate file
|
||||
--tlsKeyFile string path to TLS key file
|
||||
--trace file write trace to file (not useful in general)
|
||||
-w, --watch watch filesystem for changes and recreate as needed (default true)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -207,6 +207,10 @@ chroma:
|
||||
- Aliases:
|
||||
- dax
|
||||
Name: Dax
|
||||
- Aliases:
|
||||
- desktop
|
||||
- desktop_entry
|
||||
Name: Desktop file
|
||||
- Aliases:
|
||||
- diff
|
||||
- udiff
|
||||
@@ -443,6 +447,10 @@ chroma:
|
||||
- Aliases:
|
||||
- mason
|
||||
Name: Mason
|
||||
- Aliases:
|
||||
- materialize
|
||||
- mzsql
|
||||
Name: Materialize SQL dialect
|
||||
- Aliases:
|
||||
- mathematica
|
||||
- mma
|
||||
@@ -493,6 +501,9 @@ chroma:
|
||||
- Aliases:
|
||||
- natural
|
||||
Name: Natural
|
||||
- Aliases:
|
||||
- ndisasm
|
||||
Name: NDISASM
|
||||
- Aliases:
|
||||
- newspeak
|
||||
Name: Newspeak
|
||||
@@ -607,6 +618,9 @@ chroma:
|
||||
- Aliases:
|
||||
- prolog
|
||||
Name: Prolog
|
||||
- Aliases:
|
||||
- promela
|
||||
Name: Promela
|
||||
- Aliases:
|
||||
- promql
|
||||
Name: PromQL
|
||||
@@ -673,6 +687,9 @@ chroma:
|
||||
- Aliases:
|
||||
- registry
|
||||
Name: reg
|
||||
- Aliases:
|
||||
- rego
|
||||
Name: Rego
|
||||
- Aliases:
|
||||
- rst
|
||||
- rest
|
||||
@@ -682,6 +699,9 @@ chroma:
|
||||
- rexx
|
||||
- arexx
|
||||
Name: Rexx
|
||||
- Aliases:
|
||||
- spec
|
||||
Name: RPMSpec
|
||||
- Aliases:
|
||||
- rb
|
||||
- ruby
|
||||
@@ -946,6 +966,7 @@ config:
|
||||
dir: :cacheDir/modules
|
||||
maxAge: -1
|
||||
canonifyURLs: false
|
||||
capitalizeListTitles: true
|
||||
cascade: []
|
||||
cleanDestinationDir: false
|
||||
contentDir: content
|
||||
@@ -1585,6 +1606,7 @@ config:
|
||||
toLower: false
|
||||
relativeURLs: false
|
||||
removePathAccents: false
|
||||
renderSegments: null
|
||||
resourceDir: resources
|
||||
sectionPagesMenu: ""
|
||||
security:
|
||||
@@ -1607,6 +1629,7 @@ config:
|
||||
- (?i)GET|POST
|
||||
urls:
|
||||
- .*
|
||||
segments: {}
|
||||
server:
|
||||
headers: null
|
||||
redirects:
|
||||
@@ -1704,6 +1727,8 @@ config_helpers:
|
||||
_merge: none
|
||||
security:
|
||||
_merge: none
|
||||
segments:
|
||||
_merge: none
|
||||
server:
|
||||
_merge: none
|
||||
services:
|
||||
@@ -2965,6 +2990,21 @@ tpl:
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
IsMultiHost:
|
||||
Aliases: null
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
IsMultihost:
|
||||
Aliases: null
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
IsMultilingual:
|
||||
Aliases: null
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
IsProduction:
|
||||
Aliases: null
|
||||
Args: null
|
||||
|
||||
@@ -2,7 +2,7 @@ module github.com/gohugoio/hugo
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69
|
||||
github.com/alecthomas/chroma/v2 v2.12.0
|
||||
github.com/alecthomas/chroma/v2 v2.13.0
|
||||
github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.32.6
|
||||
@@ -18,14 +18,14 @@ 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
|
||||
github.com/cli/safeexec v1.0.1
|
||||
github.com/disintegration/gift v1.2.1
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/evanw/esbuild v0.20.1
|
||||
github.com/evanw/esbuild v0.20.2
|
||||
github.com/fatih/color v1.16.0
|
||||
github.com/fortytw2/leaktest v1.3.0
|
||||
github.com/frankban/quicktest v1.14.6
|
||||
@@ -35,7 +35,7 @@ require (
|
||||
github.com/gobuffalo/flect v1.0.2
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.1.0
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.2.0
|
||||
github.com/gohugoio/locales v0.14.0
|
||||
github.com/gohugoio/localescompressed v1.0.1
|
||||
github.com/gohugoio/testmodBuilder/mods v0.0.0-20190520184928-c56af20f2e95
|
||||
@@ -62,9 +62,9 @@ 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/minify/v2 v2.20.19
|
||||
github.com/tdewolff/parse/v2 v2.7.12
|
||||
github.com/yuin/goldmark v1.7.0
|
||||
github.com/yuin/goldmark-emoji v1.0.2
|
||||
@@ -72,11 +72,11 @@ require (
|
||||
gocloud.dev v0.36.0
|
||||
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326
|
||||
golang.org/x/image v0.15.0
|
||||
golang.org/x/mod v0.15.0
|
||||
golang.org/x/net v0.21.0
|
||||
golang.org/x/mod v0.16.0
|
||||
golang.org/x/net v0.22.0
|
||||
golang.org/x/sync v0.6.0
|
||||
golang.org/x/text v0.14.0
|
||||
golang.org/x/tools v0.18.0
|
||||
golang.org/x/tools v0.19.0
|
||||
google.golang.org/api v0.152.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
@@ -114,7 +114,7 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 // indirect
|
||||
github.com/aws/smithy-go v1.19.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
|
||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.8 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.1.0 // indirect
|
||||
@@ -142,9 +142,9 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/oauth2 v0.15.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
|
||||
@@ -66,10 +66,10 @@ github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69/go.mod h1:L1AbZd
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
|
||||
github.com/alecthomas/chroma/v2 v2.12.0 h1:Wh8qLEgMMsN7mgyG8/qIpegky2Hvzr4By6gEF7cmWgw=
|
||||
github.com/alecthomas/chroma/v2 v2.12.0/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw=
|
||||
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
|
||||
github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
|
||||
github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI=
|
||||
github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c h1:651/eoCRnQ7YtSjAnSzRucrJz+3iGEFt+ysraELS81M=
|
||||
github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aws/aws-sdk-go v1.50.7 h1:odKb+uneeGgF2jgAerKjFzpljiyZxleV4SHB7oBK+YA=
|
||||
@@ -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=
|
||||
@@ -168,8 +168,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc=
|
||||
github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI=
|
||||
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
@@ -179,8 +179,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanw/esbuild v0.20.1 h1:ueyMIL19umCcJTSxiBH/QmPipgGt8hEDM24pdfowgEc=
|
||||
github.com/evanw/esbuild v0.20.1/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
|
||||
github.com/evanw/esbuild v0.20.2 h1:E4Y0iJsothpUCq7y0D+ERfqpJmPWrZpNybJA3x3I4p8=
|
||||
github.com/evanw/esbuild v0.20.2/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||
@@ -211,8 +211,8 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e h1:QArsSubW7eDh8APMXkByjQWvuljwPGAGQpJEFn0F0wY=
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e/go.mod h1:3Ltoo9Banwq0gOtcOwxuHG6omk+AwsQPADyw2vQYOJQ=
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.1.0 h1:oFQ3f1M3Ook6amHmbqVu/uBRrQ6yjMDFkIv4HQr0f1Y=
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.1.0/go.mod h1:g9CCh+Ci2IMbPUrVJuXbBTrA+rIIx5+hDQ4EXYaQDoM=
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.2.0 h1:PCtO5l++psZf48yen2LxQ3JiOXxaRC6v0594NeHvGZg=
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.2.0/go.mod h1:g9CCh+Ci2IMbPUrVJuXbBTrA+rIIx5+hDQ4EXYaQDoM=
|
||||
github.com/gohugoio/locales v0.14.0 h1:Q0gpsZwfv7ATHMbcTNepFd59H7GoykzWJIxi113XGDc=
|
||||
github.com/gohugoio/locales v0.14.0/go.mod h1:ip8cCAv/cnmVLzzXtiTpPwgJ4xhKZranqNqtoIu0b/4=
|
||||
github.com/gohugoio/localescompressed v1.0.1 h1:KTYMi8fCWYLswFyJAeOtuk/EkXR/KPTHHNN9OS+RTxo=
|
||||
@@ -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=
|
||||
@@ -427,8 +427,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tdewolff/minify/v2 v2.20.17 h1:zGqEDhspr3XjSrQI/56vw9IdAhLAaKTLXWnDBsxNVt8=
|
||||
github.com/tdewolff/minify/v2 v2.20.17/go.mod h1:ulkFoeAVWMLEyjuDz1ZIWOA31g5aWOawCFRp9R/MudM=
|
||||
github.com/tdewolff/minify/v2 v2.20.19 h1:tX0SR0LUrIqGoLjXnkIzRSIbKJ7PaNnSENLD4CyH6Xo=
|
||||
github.com/tdewolff/minify/v2 v2.20.19/go.mod h1:ulkFoeAVWMLEyjuDz1ZIWOA31g5aWOawCFRp9R/MudM=
|
||||
github.com/tdewolff/parse/v2 v2.7.12 h1:tgavkHc2ZDEQVKy1oWxwIyh5bP4F5fEh/JmBwPP/3LQ=
|
||||
github.com/tdewolff/parse/v2 v2.7.12/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
|
||||
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
@@ -466,8 +466,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -508,8 +508,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -545,8 +545,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -615,8 +615,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -685,8 +685,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
|
||||
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
|
||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -42,7 +42,7 @@ var LanguageDirsMerger overlayfs.DirsMerger = func(lofi, bofi []fs.DirEntry) []f
|
||||
|
||||
// AppendDirsMerger merges two directories keeping all regular files
|
||||
// with the first slice as the base.
|
||||
// Duplicate directories in the secnond slice will be ignored.
|
||||
// Duplicate directories in the second slice will be ignored.
|
||||
// This strategy is used for the i18n and data fs where we need all entries.
|
||||
var AppendDirsMerger overlayfs.DirsMerger = func(lofi, bofi []fs.DirEntry) []fs.DirEntry {
|
||||
for _, fi1 := range bofi {
|
||||
|
||||
+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 {
|
||||
|
||||
+108
-112
@@ -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.
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
@@ -43,7 +44,7 @@ var _ ReverseLookupProvder = (*RootMappingFs)(nil)
|
||||
func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
|
||||
rootMapToReal := radix.New()
|
||||
realMapToRoot := radix.New()
|
||||
var virtualRoots []RootMapping
|
||||
id := fmt.Sprintf("rfs-%d", rootMappingFsCounter.Add(1))
|
||||
|
||||
addMapping := func(key string, rm RootMapping, to *radix.Tree) {
|
||||
var mappings []RootMapping
|
||||
@@ -77,45 +78,49 @@ func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
|
||||
rm.Meta = NewFileMeta()
|
||||
}
|
||||
|
||||
if rm.FromBase == "" {
|
||||
panic(" rm.FromBase is empty")
|
||||
}
|
||||
|
||||
rm.Meta.Component = rm.FromBase
|
||||
rm.Meta.Module = rm.Module
|
||||
rm.Meta.ModuleOrdinal = rm.ModuleOrdinal
|
||||
rm.Meta.IsProject = rm.IsProject
|
||||
rm.Meta.BaseDir = rm.ToBase
|
||||
|
||||
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
|
||||
},
|
||||
))
|
||||
|
||||
@@ -129,19 +134,9 @@ func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if rm.FromBase == "" {
|
||||
panic(" rm.FromBase is empty")
|
||||
}
|
||||
|
||||
// Extract "blog" from "content/blog"
|
||||
rm.path = strings.TrimPrefix(strings.TrimPrefix(rm.From, rm.FromBase), filepathSeparator)
|
||||
|
||||
rm.Meta.SourceRoot = fi.(MetaProvider).Meta().Filename
|
||||
rm.Meta.BaseDir = rm.ToBase
|
||||
rm.Meta.Module = rm.Module
|
||||
rm.Meta.ModuleOrdinal = rm.ModuleOrdinal
|
||||
rm.Meta.Component = rm.FromBase
|
||||
rm.Meta.IsProject = rm.IsProject
|
||||
|
||||
meta := rm.Meta.Copy()
|
||||
|
||||
@@ -160,12 +155,10 @@ func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
|
||||
|
||||
addMapping(rev, rm, realMapToRoot)
|
||||
|
||||
virtualRoots = append(virtualRoots, rm)
|
||||
}
|
||||
|
||||
rootMapToReal.Insert(filepathSeparator, virtualRoots)
|
||||
|
||||
rfs := &RootMappingFs{
|
||||
id: id,
|
||||
Fs: fs,
|
||||
rootMapToReal: rootMapToReal,
|
||||
realMapToRoot: realMapToRoot,
|
||||
@@ -237,11 +230,14 @@ var _ FilesystemUnwrapper = (*RootMappingFs)(nil)
|
||||
// is directories only, and they will be returned in Readdir and Readdirnames
|
||||
// in the order given.
|
||||
type RootMappingFs struct {
|
||||
id string
|
||||
afero.Fs
|
||||
rootMapToReal *radix.Tree
|
||||
realMapToRoot *radix.Tree
|
||||
}
|
||||
|
||||
var rootMappingFsCounter atomic.Int32
|
||||
|
||||
func (fs *RootMappingFs) Mounts(base string) ([]FileMetaInfo, error) {
|
||||
base = filepathSeparator + fs.cleanName(base)
|
||||
roots := fs.getRootsWithPrefix(base)
|
||||
@@ -252,13 +248,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,17 +263,16 @@ 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)
|
||||
}
|
||||
|
||||
return fss, nil
|
||||
}
|
||||
|
||||
func (fs *RootMappingFs) Key() string {
|
||||
return fs.id
|
||||
}
|
||||
|
||||
func (fs *RootMappingFs) UnwrapFilesystem() afero.Fs {
|
||||
return fs.Fs
|
||||
}
|
||||
@@ -337,13 +330,18 @@ func (c ComponentPath) ComponentPathJoined() string {
|
||||
}
|
||||
|
||||
type ReverseLookupProvder interface {
|
||||
ReverseLookup(filename string, checkExists bool) ([]ComponentPath, error)
|
||||
ReverseLookup(filename string) ([]ComponentPath, error)
|
||||
ReverseLookupComponent(component, filename string) ([]ComponentPath, error)
|
||||
}
|
||||
|
||||
// func (fs *RootMappingFs) ReverseStat(filename string) ([]FileMetaInfo, error)
|
||||
func (fs *RootMappingFs) ReverseLookup(in string, checkExists bool) ([]ComponentPath, error) {
|
||||
in = fs.cleanName(in)
|
||||
key := filepathSeparator + in
|
||||
func (fs *RootMappingFs) ReverseLookup(filename string) ([]ComponentPath, error) {
|
||||
return fs.ReverseLookupComponent("", filename)
|
||||
}
|
||||
|
||||
func (fs *RootMappingFs) ReverseLookupComponent(component, filename string) ([]ComponentPath, error) {
|
||||
filename = fs.cleanName(filename)
|
||||
key := filepathSeparator + filename
|
||||
|
||||
s, roots := fs.getRootsReverse(key)
|
||||
|
||||
@@ -357,19 +355,21 @@ func (fs *RootMappingFs) ReverseLookup(in string, checkExists bool) ([]Component
|
||||
dir, name := filepath.Split(base)
|
||||
|
||||
for _, first := range roots {
|
||||
if first.Meta.Rename != nil {
|
||||
name = first.Meta.Rename(name, true)
|
||||
if component != "" && first.FromBase != component {
|
||||
continue
|
||||
}
|
||||
|
||||
// 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 {
|
||||
var filename string
|
||||
if first.Meta.Rename != 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)
|
||||
}
|
||||
|
||||
cps = append(cps, ComponentPath{
|
||||
@@ -412,6 +412,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
|
||||
}
|
||||
@@ -474,6 +475,11 @@ func (fs *RootMappingFs) newUnionFile(fis ...FileMetaInfo) (afero.File, error) {
|
||||
return fis[0].Meta().Open()
|
||||
}
|
||||
|
||||
if !fis[0].IsDir() {
|
||||
// Pick the last file mount.
|
||||
return fis[len(fis)-1].Meta().Open()
|
||||
}
|
||||
|
||||
openers := make([]func() (afero.File, error), len(fis))
|
||||
for i := len(fis) - 1; i >= 0; i-- {
|
||||
fi := fis[i]
|
||||
@@ -562,9 +568,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)
|
||||
}
|
||||
@@ -609,25 +617,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
|
||||
@@ -653,6 +652,28 @@ func (rfs *RootMappingFs) collectDirEntries(prefix string) ([]iofs.DirEntry, err
|
||||
}
|
||||
|
||||
func (fs *RootMappingFs) doStat(name string) ([]FileMetaInfo, error) {
|
||||
fis, err := fs.doDoStat(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Sanity check. Check that all is either file or directories.
|
||||
var isDir, isFile bool
|
||||
for _, fi := range fis {
|
||||
if fi.IsDir() {
|
||||
isDir = true
|
||||
} else {
|
||||
isFile = true
|
||||
}
|
||||
}
|
||||
if isDir && isFile {
|
||||
// For now.
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
return fis, nil
|
||||
}
|
||||
|
||||
func (fs *RootMappingFs) doDoStat(name string) ([]FileMetaInfo, error) {
|
||||
name = fs.cleanName(name)
|
||||
key := filepathSeparator + name
|
||||
|
||||
@@ -693,48 +714,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
|
||||
}
|
||||
|
||||
@@ -745,24 +738,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)
|
||||
@@ -826,7 +822,7 @@ func (f *rootMappingDir) ReadDir(count int) ([]iofs.DirEntry, error) {
|
||||
return f.fs.collectDirEntries(f.name)
|
||||
}
|
||||
|
||||
// Sentinal error to signal that a file is a directory.
|
||||
// Sentinel error to signal that a file is a directory.
|
||||
var errIsDir = errors.New("isDir")
|
||||
|
||||
func (f *rootMappingDir) Stat() (iofs.FileInfo, error) {
|
||||
|
||||
@@ -276,15 +276,20 @@ func TestRootMappingFsMount(t *testing.T) {
|
||||
|
||||
// Test ReverseLookup.
|
||||
// Single file mounts.
|
||||
cps, err := rfs.ReverseLookup(filepath.FromSlash("singlefiles/no.txt"), true)
|
||||
cps, err := rfs.ReverseLookup(filepath.FromSlash("singlefiles/no.txt"))
|
||||
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"))
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(cps, qt.DeepEquals, []ComponentPath{
|
||||
{Component: "content", Path: "singles/p1.md", Lang: "sv"},
|
||||
})
|
||||
|
||||
// File inside directory mount.
|
||||
cps, err = rfs.ReverseLookup(filepath.FromSlash("mynoblogcontent/test.txt"), true)
|
||||
cps, err = rfs.ReverseLookup(filepath.FromSlash("mynoblogcontent/test.txt"))
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(cps, qt.DeepEquals, []ComponentPath{
|
||||
{Component: "content", Path: "blog/test.txt", Lang: "no"},
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+40
-21
@@ -508,12 +508,12 @@ func TestLoadConfigFromThemeDir(t *testing.T) {
|
||||
theme = "test-theme"
|
||||
|
||||
[params]
|
||||
m1 = "mv1"
|
||||
m1 = "mv1"
|
||||
`
|
||||
|
||||
themeConfig := `
|
||||
[params]
|
||||
t1 = "tv1"
|
||||
t1 = "tv1"
|
||||
t2 = "tv2"
|
||||
`
|
||||
|
||||
@@ -885,9 +885,9 @@ ThisIsAParam: {{ site.Params.thisIsAParam }}
|
||||
).BuildE()
|
||||
|
||||
b.Assert(err, qt.IsNil)
|
||||
b.AssertFileContent("public/index.html", `
|
||||
b.AssertFileContent("public/index.html", `
|
||||
MyParam: enParamValue
|
||||
ThisIsAParam: thisIsAParamValue
|
||||
ThisIsAParam: thisIsAParamValue
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -919,7 +919,7 @@ title: "My Swedish Section"
|
||||
-- layouts/index.html --
|
||||
LanguageCode: {{ eq site.LanguageCode site.Language.LanguageCode }}|{{ site.Language.LanguageCode }}|
|
||||
{{ range $i, $e := (slice site .Site) }}
|
||||
{{ $i }}|AllPages: {{ len .AllPages }}|Sections: {{ if .Sections }}true{{ end }}| Author: {{ .Authors }}|BuildDrafts: {{ .BuildDrafts }}|IsMultiLingual: {{ .IsMultiLingual }}|Param: {{ .Language.Params.myparam }}|Language string: {{ .Language }}|Languages: {{ .Languages }}
|
||||
{{ $i }}|AllPages: {{ len .AllPages }}|Sections: {{ if .Sections }}true{{ end }}| Author: {{ .Authors }}|BuildDrafts: {{ .BuildDrafts }}|IsMultilingual: {{ .IsMultiLingual }}|Param: {{ .Language.Params.myparam }}|Language string: {{ .Language }}|Languages: {{ .Languages }}
|
||||
{{ end }}
|
||||
|
||||
|
||||
@@ -939,9 +939,9 @@ LanguageCode: {{ eq site.LanguageCode site.Language.LanguageCode }}|{{ site.Lang
|
||||
b.AssertFileContent("public/index.html", `
|
||||
AllPages: 4|
|
||||
Sections: true|
|
||||
Param: enParamValue
|
||||
Param: enParamValue
|
||||
IsMultiLingual: true
|
||||
Param: enParamValue
|
||||
Param: enParamValue
|
||||
IsMultilingual: true
|
||||
LanguageCode: true|en-US|
|
||||
`)
|
||||
|
||||
@@ -1062,7 +1062,7 @@ Home
|
||||
).BuildE()
|
||||
|
||||
b.Assert(err, qt.IsNil)
|
||||
b.AssertFileContent("public/index.html", `
|
||||
b.AssertFileContent("public/index.html", `
|
||||
Home
|
||||
`)
|
||||
|
||||
@@ -1095,7 +1095,7 @@ HTML.
|
||||
HTACCESS.
|
||||
|
||||
|
||||
|
||||
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
@@ -1111,7 +1111,7 @@ languageCode = "en-US"
|
||||
-- layouts/index.html --
|
||||
LanguageCode: {{ .Site.LanguageCode }}|{{ site.Language.LanguageCode }}|
|
||||
|
||||
|
||||
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
@@ -1137,7 +1137,7 @@ suffixes = ["bar"]
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
|
||||
|
||||
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
@@ -1164,8 +1164,8 @@ func TestConfigMiscPanics(t *testing.T) {
|
||||
params:
|
||||
-- layouts/index.html --
|
||||
Foo: {{ site.Params.foo }}|
|
||||
|
||||
|
||||
|
||||
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
@@ -1188,8 +1188,8 @@ defaultContentLanguage = "en"
|
||||
weight = 1
|
||||
-- layouts/index.html --
|
||||
Foo: {{ site.Params.foo }}|
|
||||
|
||||
|
||||
|
||||
|
||||
`
|
||||
b, err := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
@@ -1215,8 +1215,8 @@ languageCode = "en"
|
||||
languageName = "English"
|
||||
weight = 1
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
`
|
||||
b, err := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
@@ -1241,7 +1241,7 @@ contentDir = "mycontent"
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
|
||||
|
||||
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
@@ -1343,7 +1343,7 @@ disabled = true
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
|
||||
|
||||
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
@@ -1438,7 +1438,7 @@ home = ["html"]
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "page", "section"]
|
||||
|
||||
|
||||
`
|
||||
|
||||
runVariant(t, files, nil)
|
||||
@@ -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}
|
||||
|
||||
+46
-38
@@ -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())
|
||||
@@ -488,7 +485,7 @@ func (m *pageMap) forEachResourceInPage(
|
||||
rw.Handle = func(resourceKey string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
|
||||
if isBranch {
|
||||
ownerKey, _ := m.treePages.LongestPrefixAll(resourceKey)
|
||||
if ownerKey != keyPage {
|
||||
if ownerKey != keyPage && path.Dir(ownerKey) != path.Dir(resourceKey) {
|
||||
// Stop walking downwards, someone else owns this resource.
|
||||
rw.SkipPrefix(ownerKey + "/")
|
||||
return false, nil
|
||||
@@ -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)
|
||||
@@ -1503,13 +1500,6 @@ func (sa *sitePagesAssembler) assembleTermsAndTranslations() error {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// 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.pageConfig.TranslationKey != "" {
|
||||
sa.s.h.translationKeyPages.Append(ps.m.pageConfig.TranslationKey, ps)
|
||||
}
|
||||
|
||||
if sa.pageMap.cfg.taxonomyTermDisabled {
|
||||
return false, nil
|
||||
}
|
||||
@@ -1552,8 +1542,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 == "" {
|
||||
@@ -1588,6 +1580,13 @@ func (sa *sitePagesAssembler) assembleResources() error {
|
||||
Handle: func(s string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
|
||||
ps := n.(*pageState)
|
||||
|
||||
// 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.pageConfig.TranslationKey != "" {
|
||||
sa.s.h.translationKeyPages.Append(ps.m.pageConfig.TranslationKey, ps)
|
||||
}
|
||||
|
||||
// Prepare resources for this page.
|
||||
ps.shiftToOutputFormat(true, 0)
|
||||
targetPaths := ps.targetPaths()
|
||||
@@ -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
|
||||
@@ -1764,7 +1764,7 @@ func (sa *sitePagesAssembler) addStandalonePages() error {
|
||||
|
||||
if s.conf.EnableRobotsTXT {
|
||||
if m.i == 0 || s.Conf.IsMultihost() {
|
||||
addStandalone("/robots", kinds.KindRobotsTXT, output.RobotsTxtFormat)
|
||||
addStandalone("/_robots", kinds.KindRobotsTXT, output.RobotsTxtFormat)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1777,11 +1777,11 @@ func (sa *sitePagesAssembler) addStandalonePages() error {
|
||||
}
|
||||
|
||||
if sitemapEnabled {
|
||||
addStandalone("/sitemap", kinds.KindSitemap, output.SitemapFormat)
|
||||
skipSitemapIndex := s.Conf.IsMultihost() || !(s.Conf.DefaultContentLanguageInSubdir() || s.Conf.IsMultiLingual())
|
||||
addStandalone("/_sitemap", kinds.KindSitemap, output.SitemapFormat)
|
||||
skipSitemapIndex := s.Conf.IsMultihost() || !(s.Conf.DefaultContentLanguageInSubdir() || s.Conf.IsMultilingual())
|
||||
|
||||
if !skipSitemapIndex {
|
||||
addStandalone("/sitemapindex", kinds.KindSitemapIndex, output.SitemapIndexFormat)
|
||||
addStandalone("/_sitemapindex", kinds.KindSitemapIndex, output.SitemapIndexFormat)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1814,6 +1814,14 @@ func (sa *sitePagesAssembler) addMissingRootSections() error {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
switch ps.Kind() {
|
||||
case kinds.KindPage, kinds.KindSection:
|
||||
// OK
|
||||
default:
|
||||
// Skip taxonomy nodes etc.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
p := ps.m.pathInfo
|
||||
section := p.Section()
|
||||
if section == "" || seen[section] {
|
||||
|
||||
@@ -326,3 +326,35 @@ R: {{ with $r }}{{ .Content }}{{ end }}|Len: {{ len $bundle.Resources }}|$
|
||||
b.AssertFileContent("public/index.html", "R: Data 1.txt|", "Len: 1|")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBundleResourcesNoPublishedIssue12198(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','rss','sitemap','taxonomy','term']
|
||||
-- content/s1/p1.md --
|
||||
---
|
||||
title: p1
|
||||
---
|
||||
-- content/s1/foo.txt --
|
||||
foo.txt
|
||||
-- content/s1/p1.txt --
|
||||
p1.txt
|
||||
-- content/s1/p1-foo.txt --
|
||||
p1-foo.txt
|
||||
-- layouts/_default/list.html --
|
||||
{{.Title }}|
|
||||
-- layouts/_default/single.html --
|
||||
{{.Title }}|
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
b.Build()
|
||||
|
||||
b.AssertFileExists("public/s1/index.html", true)
|
||||
b.AssertFileExists("public/s1/foo.txt", true)
|
||||
b.AssertFileExists("public/s1/p1.txt", true) // failing test
|
||||
b.AssertFileExists("public/s1/p1-foo.txt", true) // failing test
|
||||
b.AssertFileExists("public/s1/p1/index.html", true)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -265,6 +265,9 @@ type SourceFilesystem struct {
|
||||
// This is a virtual composite filesystem. It expects path relative to a context.
|
||||
Fs afero.Fs
|
||||
|
||||
// The source filesystem (usually the OS filesystem).
|
||||
SourceFs afero.Fs
|
||||
|
||||
// When syncing a source folder to the target (e.g. /public), this may
|
||||
// be set to publish into a subfolder. This is used for static syncing
|
||||
// in multihost mode.
|
||||
@@ -320,10 +323,10 @@ func (s SourceFilesystems) IsContent(filename string) bool {
|
||||
}
|
||||
|
||||
// ResolvePaths resolves the given filename to a list of paths in the filesystems.
|
||||
func (s *SourceFilesystems) ResolvePaths(filename string, checkExists bool) []hugofs.ComponentPath {
|
||||
func (s *SourceFilesystems) ResolvePaths(filename string) []hugofs.ComponentPath {
|
||||
var cpss []hugofs.ComponentPath
|
||||
for _, rfs := range s.RootFss {
|
||||
cps, err := rfs.ReverseLookup(filename, checkExists)
|
||||
cps, err := rfs.ReverseLookup(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -362,7 +365,17 @@ func (d *SourceFilesystem) ReverseLookup(filename string, checkExists bool) ([]h
|
||||
var cps []hugofs.ComponentPath
|
||||
hugofs.WalkFilesystems(d.Fs, func(fs afero.Fs) bool {
|
||||
if rfs, ok := fs.(hugofs.ReverseLookupProvder); ok {
|
||||
if c, err := rfs.ReverseLookup(filename, checkExists); err == nil {
|
||||
if c, err := rfs.ReverseLookupComponent(d.Name, filename); err == nil {
|
||||
if checkExists {
|
||||
n := 0
|
||||
for _, cp := range c {
|
||||
if _, err := d.Fs.Stat(filepath.FromSlash(cp.Path)); err == nil {
|
||||
c[n] = cp
|
||||
n++
|
||||
}
|
||||
}
|
||||
c = c[:n]
|
||||
}
|
||||
cps = append(cps, c...)
|
||||
}
|
||||
}
|
||||
@@ -379,11 +392,12 @@ func (d *SourceFilesystem) mounts() []hugofs.FileMetaInfo {
|
||||
if err == nil {
|
||||
m = append(m, mounts...)
|
||||
}
|
||||
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// Filter out any mounts not belonging to this filesystem.
|
||||
// TODO(bep) I think this is superflous.
|
||||
n := 0
|
||||
for _, mm := range m {
|
||||
if mm.Meta().Component == d.Name {
|
||||
@@ -392,6 +406,7 @@ func (d *SourceFilesystem) mounts() []hugofs.FileMetaInfo {
|
||||
}
|
||||
}
|
||||
m = m[:n]
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -428,10 +443,8 @@ func (d *SourceFilesystem) RealDirs(from string) []string {
|
||||
if !m.IsDir() {
|
||||
continue
|
||||
}
|
||||
meta := m.Meta()
|
||||
_, err := d.Fs.Stat(from)
|
||||
if err == nil {
|
||||
dirname := filepath.Join(meta.Filename, from)
|
||||
dirname := filepath.Join(m.Meta().Filename, from)
|
||||
if _, err := d.SourceFs.Stat(dirname); err == nil {
|
||||
dirnames = append(dirnames, dirname)
|
||||
}
|
||||
}
|
||||
@@ -519,8 +532,9 @@ func newSourceFilesystemsBuilder(p *paths.Paths, logger loggers.Logger, b *BaseF
|
||||
|
||||
func (b *sourceFilesystemsBuilder) newSourceFilesystem(name string, fs afero.Fs) *SourceFilesystem {
|
||||
return &SourceFilesystem{
|
||||
Name: name,
|
||||
Fs: fs,
|
||||
Name: name,
|
||||
Fs: fs,
|
||||
SourceFs: b.sourceFs,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -314,7 +315,7 @@ func TestStaticFs(t *testing.T) {
|
||||
checkFileContent(sfs, "f2.txt", c, "Hugo Themes Still Rocks!")
|
||||
}
|
||||
|
||||
func TestStaticFsMultiHost(t *testing.T) {
|
||||
func TestStaticFsMultihost(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
v := config.New()
|
||||
workDir := "mywork"
|
||||
@@ -478,6 +479,70 @@ Home.
|
||||
_ = stat("blog/b1.md")
|
||||
}
|
||||
|
||||
func TestReverseLookupShouldOnlyConsiderFilesInCurrentComponent(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com/"
|
||||
[module]
|
||||
[[module.mounts]]
|
||||
source = "files/layouts"
|
||||
target = "layouts"
|
||||
[[module.mounts]]
|
||||
source = "files/layouts/assets"
|
||||
target = "assets"
|
||||
-- files/layouts/l1.txt --
|
||||
l1
|
||||
-- files/layouts/assets/l2.txt --
|
||||
l2
|
||||
`
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
assetsFs := b.H.Assets
|
||||
|
||||
for _, checkExists := range []bool{false, true} {
|
||||
cps, err := assetsFs.ReverseLookup(filepath.FromSlash("files/layouts/assets/l2.txt"), checkExists)
|
||||
b.Assert(err, qt.IsNil)
|
||||
b.Assert(cps, qt.HasLen, 1)
|
||||
cps, err = assetsFs.ReverseLookup(filepath.FromSlash("files/layouts/l2.txt"), checkExists)
|
||||
b.Assert(err, qt.IsNil)
|
||||
b.Assert(cps, qt.HasLen, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssetsIssue12175(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com/"
|
||||
[module]
|
||||
[[module.mounts]]
|
||||
source = "node_modules/@foo/core/assets"
|
||||
target = "assets"
|
||||
[[module.mounts]]
|
||||
source = "assets"
|
||||
target = "assets"
|
||||
-- node_modules/@foo/core/assets/js/app.js --
|
||||
JS.
|
||||
-- node_modules/@foo/core/assets/scss/app.scss --
|
||||
body { color: red; }
|
||||
-- assets/scss/app.scss --
|
||||
body { color: blue; }
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
SCSS: {{ with resources.Get "scss/app.scss" }}{{ .RelPermalink }}|{{ .Content }}{{ end }}|
|
||||
# Note that the pattern below will match 2 resources, which doesn't make much sense,
|
||||
# but is how the current (and also < v0.123.0) merge logic works, and for most practical purposes, it doesn't matter.
|
||||
SCSS Match: {{ with resources.Match "**.scss" }}{{ . | len }}|{{ range .}}{{ .RelPermalink }}|{{ end }}{{ end }}|
|
||||
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/index.html", `
|
||||
SCSS: /scss/app.scss|body { color: blue; }|
|
||||
SCSS Match: 2|
|
||||
`)
|
||||
}
|
||||
|
||||
func TestStaticComposite(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
@@ -513,6 +578,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)
|
||||
|
||||
@@ -269,7 +269,7 @@ func (h *HugoSites) pickOneAndLogTheRest(errors []error) error {
|
||||
return errors[i]
|
||||
}
|
||||
|
||||
func (h *HugoSites) isMultiLingual() bool {
|
||||
func (h *HugoSites) isMultilingual() bool {
|
||||
return len(h.Sites) > 1
|
||||
}
|
||||
|
||||
@@ -410,6 +410,10 @@ type BuildCfg struct {
|
||||
|
||||
// shouldRender returns whether this output format should be rendered or not.
|
||||
func (cfg *BuildCfg) shouldRender(p *pageState) bool {
|
||||
if p.skipRender() {
|
||||
return false
|
||||
}
|
||||
|
||||
if !p.renderOnce {
|
||||
return true
|
||||
}
|
||||
|
||||
+23
-32
@@ -28,16 +28,16 @@ import (
|
||||
"github.com/bep/logg"
|
||||
"github.com/gohugoio/hugo/cache/dynacache"
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
"github.com/gohugoio/hugo/hugofs/glob"
|
||||
"github.com/gohugoio/hugo/hugolib/segments"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/output"
|
||||
"github.com/gohugoio/hugo/publisher"
|
||||
"github.com/gohugoio/hugo/source"
|
||||
"github.com/gohugoio/hugo/tpl"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/para"
|
||||
@@ -318,9 +318,20 @@ func (h *HugoSites) render(l logg.LevelLogger, config *BuildCfg) error {
|
||||
|
||||
i := 0
|
||||
for _, s := range h.Sites {
|
||||
segmentFilter := s.conf.C.SegmentFilter
|
||||
if segmentFilter.ShouldExcludeCoarse(segments.SegmentMatcherFields{Lang: s.language.Lang}) {
|
||||
l.Logf("skip language %q not matching segments set in --renderSegments", s.language.Lang)
|
||||
continue
|
||||
}
|
||||
|
||||
siteRenderContext.languageIdx = s.languagei
|
||||
h.currentSite = s
|
||||
for siteOutIdx, renderFormat := range s.renderFormats {
|
||||
if segmentFilter.ShouldExcludeCoarse(segments.SegmentMatcherFields{Output: renderFormat.Name, Lang: s.language.Lang}) {
|
||||
l.Logf("skip output format %q for language %q not matching segments set in --renderSegments", renderFormat.Name, s.language.Lang)
|
||||
continue
|
||||
}
|
||||
|
||||
siteRenderContext.outIdx = siteOutIdx
|
||||
siteRenderContext.sitesOutIdx = i
|
||||
i++
|
||||
@@ -595,8 +606,10 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
return sb.String()
|
||||
}))
|
||||
|
||||
// For a list of events for the different OSes, see the test output in https://github.com/bep/fsnotifyeventlister/.
|
||||
events = h.fileEventsFilter(events)
|
||||
events = h.fileEventsTranslate(events)
|
||||
eventInfos := h.fileEventsApplyInfo(events)
|
||||
|
||||
logger := h.Log
|
||||
|
||||
@@ -631,36 +644,12 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
addedContentPaths []*paths.Path
|
||||
)
|
||||
|
||||
for _, ev := range events {
|
||||
removed := false
|
||||
added := false
|
||||
|
||||
if ev.Op&fsnotify.Remove == fsnotify.Remove {
|
||||
removed = true
|
||||
}
|
||||
|
||||
fi, statErr := h.Fs.Source.Stat(ev.Name)
|
||||
|
||||
// Some editors (Vim) sometimes issue only a Rename operation when writing an existing file
|
||||
// Sometimes a rename operation means that file has been renamed other times it means
|
||||
// it's been updated.
|
||||
if ev.Op.Has(fsnotify.Rename) {
|
||||
// If the file is still on disk, it's only been updated, if it's not, it's been moved
|
||||
if statErr != nil {
|
||||
removed = true
|
||||
}
|
||||
}
|
||||
if ev.Op.Has(fsnotify.Create) {
|
||||
added = true
|
||||
}
|
||||
|
||||
isChangedDir := statErr == nil && fi.IsDir()
|
||||
|
||||
cpss := h.BaseFs.ResolvePaths(ev.Name, !removed)
|
||||
for _, ev := range eventInfos {
|
||||
cpss := h.BaseFs.ResolvePaths(ev.Name)
|
||||
pss := make([]*paths.Path, len(cpss))
|
||||
for i, cps := range cpss {
|
||||
p := cps.Path
|
||||
if removed && !paths.HasExt(p) {
|
||||
if ev.removed && !paths.HasExt(p) {
|
||||
// Assume this is a renamed/removed directory.
|
||||
// For deletes, we walk up the tree to find the container (e.g. branch bundle),
|
||||
// so we will catch this even if it is a file without extension.
|
||||
@@ -671,7 +660,7 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
}
|
||||
|
||||
pss[i] = h.Configs.ContentPathParser.Parse(cps.Component, p)
|
||||
if added && !isChangedDir && cps.Component == files.ComponentFolderContent {
|
||||
if ev.added && !ev.isChangedDir && cps.Component == files.ComponentFolderContent {
|
||||
addedContentPaths = append(addedContentPaths, pss[i])
|
||||
}
|
||||
|
||||
@@ -683,9 +672,9 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
}
|
||||
}
|
||||
|
||||
if removed {
|
||||
if ev.removed {
|
||||
changedPaths.deleted = append(changedPaths.deleted, pss...)
|
||||
} else if isChangedDir {
|
||||
} else if ev.isChangedDir {
|
||||
changedPaths.changedDirs = append(changedPaths.changedDirs, pss...)
|
||||
} else {
|
||||
changedPaths.changedFiles = append(changedPaths.changedFiles, pss...)
|
||||
@@ -792,6 +781,8 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
// It's hard to determine the exact change set of this,
|
||||
// so be very coarse grained for now.
|
||||
changes = append(changes, identity.GenghisKhan)
|
||||
case files.ComponentFolderArchetypes:
|
||||
// Ignore for now.
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown component: %q", pathInfo.Component()))
|
||||
}
|
||||
|
||||
@@ -66,12 +66,12 @@ robots|{{ site.Language.Lang }}
|
||||
404|{{ site.Language.Lang }}
|
||||
|
||||
|
||||
|
||||
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.Assert(b.H.Conf.IsMultiLingual(), qt.Equals, true)
|
||||
b.Assert(b.H.Conf.IsMultilingual(), qt.Equals, true)
|
||||
b.Assert(b.H.Conf.IsMultihost(), qt.Equals, true)
|
||||
|
||||
// helpers.PrintFs(b.H.Fs.PublishDir, "", os.Stdout)
|
||||
@@ -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
|
||||
}
|
||||
@@ -182,6 +183,13 @@ type lockingBuffer struct {
|
||||
bytes.Buffer
|
||||
}
|
||||
|
||||
func (b *lockingBuffer) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
b.Lock()
|
||||
n, err = b.Buffer.ReadFrom(r)
|
||||
b.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (b *lockingBuffer) Write(p []byte) (n int, err error) {
|
||||
b.Lock()
|
||||
n, err = b.Buffer.Write(p)
|
||||
@@ -192,21 +200,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 +289,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 +312,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 +377,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 +388,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 +400,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 +417,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 +663,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 +681,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
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/hugolib/doctree"
|
||||
"github.com/gohugoio/hugo/hugolib/segments"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/output"
|
||||
@@ -36,6 +37,7 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
|
||||
"github.com/gohugoio/hugo/source"
|
||||
|
||||
@@ -151,6 +153,19 @@ func (p *pageState) reusePageOutputContent() bool {
|
||||
return p.pageOutputTemplateVariationsState.Load() == 1
|
||||
}
|
||||
|
||||
func (p *pageState) skipRender() bool {
|
||||
b := p.s.conf.C.SegmentFilter.ShouldExcludeFine(
|
||||
segments.SegmentMatcherFields{
|
||||
Path: p.Path(),
|
||||
Kind: p.Kind(),
|
||||
Lang: p.Lang(),
|
||||
Output: p.pageOutput.f.Name,
|
||||
},
|
||||
)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (po *pageState) isRenderedAny() bool {
|
||||
for _, o := range po.pageOutputs {
|
||||
if o.isRendered() {
|
||||
@@ -731,3 +746,9 @@ func (p pageWithWeight0) Weight0() int {
|
||||
func (p pageWithWeight0) page() page.Page {
|
||||
return p.pageState
|
||||
}
|
||||
|
||||
var _ types.Unwrapper = (*pageWithWeight0)(nil)
|
||||
|
||||
func (p pageWithWeight0) Unwrapv() any {
|
||||
return p.pageState
|
||||
}
|
||||
|
||||
@@ -106,9 +106,9 @@ func (p *pageMeta) Aliases() []string {
|
||||
return p.pageConfig.Aliases
|
||||
}
|
||||
|
||||
// Deprecated: use taxonomies.
|
||||
// Deprecated: Use taxonomies instead.
|
||||
func (p *pageMeta) Author() page.Author {
|
||||
hugo.Deprecate(".Author", "Use taxonomies.", "v0.98.0")
|
||||
hugo.Deprecate(".Page.Author", "Use taxonomies instead.", "v0.98.0")
|
||||
authors := p.Authors()
|
||||
|
||||
for _, author := range authors {
|
||||
@@ -117,9 +117,9 @@ func (p *pageMeta) Author() page.Author {
|
||||
return page.Author{}
|
||||
}
|
||||
|
||||
// Deprecated: use taxonomies.
|
||||
// Deprecated: Use taxonomies instead.
|
||||
func (p *pageMeta) Authors() page.AuthorList {
|
||||
hugo.Deprecate(".Author", "Use taxonomies.", "v0.112.0")
|
||||
hugo.Deprecate(".Page.Authors", "Use taxonomies instead.", "v0.112.0")
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -145,9 +145,11 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) {
|
||||
// Either a taxonomy or a term.
|
||||
if tc.pluralTreeKey == m.Path() {
|
||||
m.pageConfig.Kind = kinds.KindTaxonomy
|
||||
m.singular = tc.singular
|
||||
} else {
|
||||
m.pageConfig.Kind = kinds.KindTerm
|
||||
m.term = m.pathInfo.Unnormalized().BaseNameNoIdentifier()
|
||||
m.singular = tc.singular
|
||||
}
|
||||
}
|
||||
} else if m.f != nil {
|
||||
|
||||
@@ -124,11 +124,16 @@ func (pt pageTree) Parent() page.Page {
|
||||
return pt.p.s.home
|
||||
}
|
||||
|
||||
_, n := pt.p.s.pageMap.treePages.LongestPrefix(dir, true, nil)
|
||||
if n != nil {
|
||||
return n.(page.Page)
|
||||
for {
|
||||
_, n := pt.p.s.pageMap.treePages.LongestPrefix(dir, true, nil)
|
||||
if n == nil {
|
||||
return pt.p.s.home
|
||||
}
|
||||
if pt.p.m.bundled || n.isContentNodeBranch() {
|
||||
return n.(page.Page)
|
||||
}
|
||||
dir = paths.Dir(dir)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pt pageTree) Ancestors() page.Pages {
|
||||
|
||||
+48
-3
@@ -578,7 +578,7 @@ date: 2012-01-12
|
||||
b.Assert(s.getPageOldVersion("/with-index-no-date").Date().IsZero(), qt.Equals, true)
|
||||
checkDate(s.getPageOldVersion("/with-index-date"), 2018)
|
||||
|
||||
b.Assert(s.Site().LastChange().Year(), qt.Equals, 2018)
|
||||
b.Assert(s.Site().Lastmod().Year(), qt.Equals, 2018)
|
||||
}
|
||||
|
||||
func TestCreateNewPage(t *testing.T) {
|
||||
@@ -709,7 +709,7 @@ func TestPageWithShortCodeInSummary(t *testing.T) {
|
||||
assertFunc := func(t *testing.T, ext string, pages page.Pages) {
|
||||
p := pages[0]
|
||||
checkPageTitle(t, p, "Simple")
|
||||
checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Next Line. <figure><img src=\"/not/real\"/> </figure> . More text here.</p><p>Some more text</p>"))
|
||||
checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Next Line. <figure><img src=\"/not/real\"> </figure> . More text here.</p><p>Some more text</p>"))
|
||||
checkPageSummary(t, p, "Summary Next Line. . More text here. Some more text")
|
||||
checkPageType(t, p, "page")
|
||||
}
|
||||
@@ -773,7 +773,7 @@ func TestSummaryManualSplit(t *testing.T) {
|
||||
title: Simple
|
||||
---
|
||||
This is **summary**.
|
||||
<!--more-->
|
||||
<!--more-->
|
||||
This is **content**.
|
||||
-- layouts/_default/single.html --
|
||||
Summary: {{ .Summary }}|Truncated: {{ .Truncated }}|
|
||||
@@ -1350,6 +1350,51 @@ AllTranslations: {{ range .AllTranslations }}{{ .Language.Lang }}|{{ end }}|
|
||||
)
|
||||
}
|
||||
|
||||
func TestTranslationKeyTermPages(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','rss','section','sitemap','taxonomy']
|
||||
defaultContentLanguage = 'en'
|
||||
defaultContentLanguageInSubdir = true
|
||||
[languages.en]
|
||||
weight = 1
|
||||
[languages.pt]
|
||||
weight = 2
|
||||
[taxonomies]
|
||||
category = 'categories'
|
||||
-- layouts/_default/list.html --
|
||||
{{ .IsTranslated }}|{{ range .Translations }}{{ .RelPermalink }}|{{ end }}
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}|
|
||||
-- content/p1.en.md --
|
||||
---
|
||||
title: p1 (en)
|
||||
categories: [music]
|
||||
---
|
||||
-- content/p1.pt.md --
|
||||
---
|
||||
title: p1 (pt)
|
||||
categories: [música]
|
||||
---
|
||||
-- content/categories/music/_index.en.md --
|
||||
---
|
||||
title: music
|
||||
translationKey: foo
|
||||
---
|
||||
-- content/categories/música/_index.pt.md --
|
||||
---
|
||||
title: música
|
||||
translationKey: foo
|
||||
---
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
b.AssertFileContent("public/en/categories/music/index.html", "true|/pt/categories/m%C3%BAsica/|")
|
||||
b.AssertFileContent("public/pt/categories/música/index.html", "true|/en/categories/music/|")
|
||||
}
|
||||
|
||||
// Issue #11540.
|
||||
func TestTranslationKeyResourceSharing(t *testing.T) {
|
||||
files := `
|
||||
|
||||
@@ -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/|$")
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ func (c *pagesCollector) Collect() (collectErr error) {
|
||||
// We always start from a directory.
|
||||
collectErr = c.collectDir(id.p, id.isDir, func(fim hugofs.FileMetaInfo) bool {
|
||||
if id.delete || id.isDir {
|
||||
if id.isDir {
|
||||
if id.isDir && fim.Meta().PathInfo.IsLeafBundle() {
|
||||
return strings.HasPrefix(fim.Meta().PathInfo.Path(), paths.AddTrailingSlash(id.p.Path()))
|
||||
}
|
||||
|
||||
|
||||
@@ -1472,3 +1472,21 @@ all: {{ $ab.RelPermalink }}
|
||||
b.AddFiles("assets/common/c3.css", "c3").Build()
|
||||
b.AssertFileContent("public/ab.css", "abc1c2 editedc3")
|
||||
}
|
||||
|
||||
func TestRebuildEditArchetypeFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableLiveReload = true
|
||||
-- archetypes/default.md --
|
||||
---
|
||||
title: "Default"
|
||||
---
|
||||
`
|
||||
|
||||
b := TestRunning(t, files)
|
||||
// Just make sure that it doesn't panic.
|
||||
b.EditFileReplaceAll("archetypes/default.md", "Default", "Default Edited").Build()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
// 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 segments
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/predicate"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
hglob "github.com/gohugoio/hugo/hugofs/glob"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// Segments is a collection of named segments.
|
||||
type Segments struct {
|
||||
s map[string]excludeInclude
|
||||
}
|
||||
|
||||
type excludeInclude struct {
|
||||
exclude predicate.P[SegmentMatcherFields]
|
||||
include predicate.P[SegmentMatcherFields]
|
||||
}
|
||||
|
||||
// ShouldExcludeCoarse returns whether the given fields should be excluded.
|
||||
// This is used for the coarser grained checks, e.g. language and output format.
|
||||
// Note that ShouldExcludeCoarse(fields) == ShouldExcludeFine(fields) may
|
||||
// not always be true, but ShouldExcludeCoarse(fields) == true == ShouldExcludeFine(fields)
|
||||
// will always be truthful.
|
||||
func (e excludeInclude) ShouldExcludeCoarse(fields SegmentMatcherFields) bool {
|
||||
return e.exclude != nil && e.exclude(fields)
|
||||
}
|
||||
|
||||
// ShouldExcludeFine returns whether the given fields should be excluded.
|
||||
// This is used for the finer grained checks, e.g. on invididual pages.
|
||||
func (e excludeInclude) ShouldExcludeFine(fields SegmentMatcherFields) bool {
|
||||
if e.exclude != nil && e.exclude(fields) {
|
||||
return true
|
||||
}
|
||||
return e.include != nil && !e.include(fields)
|
||||
}
|
||||
|
||||
type SegmentFilter interface {
|
||||
// ShouldExcludeCoarse returns whether the given fields should be excluded on a coarse level.
|
||||
ShouldExcludeCoarse(SegmentMatcherFields) bool
|
||||
|
||||
// ShouldExcludeFine returns whether the given fields should be excluded on a fine level.
|
||||
ShouldExcludeFine(SegmentMatcherFields) bool
|
||||
}
|
||||
|
||||
type segmentFilter struct {
|
||||
coarse predicate.P[SegmentMatcherFields]
|
||||
fine predicate.P[SegmentMatcherFields]
|
||||
}
|
||||
|
||||
func (f segmentFilter) ShouldExcludeCoarse(field SegmentMatcherFields) bool {
|
||||
return f.coarse(field)
|
||||
}
|
||||
|
||||
func (f segmentFilter) ShouldExcludeFine(fields SegmentMatcherFields) bool {
|
||||
return f.fine(fields)
|
||||
}
|
||||
|
||||
var (
|
||||
matchAll = func(SegmentMatcherFields) bool { return true }
|
||||
matchNothing = func(SegmentMatcherFields) bool { return false }
|
||||
)
|
||||
|
||||
// Get returns a SegmentFilter for the given segments.
|
||||
func (sms Segments) Get(onNotFound func(s string), ss ...string) SegmentFilter {
|
||||
if ss == nil {
|
||||
return segmentFilter{coarse: matchNothing, fine: matchNothing}
|
||||
}
|
||||
var sf segmentFilter
|
||||
for _, s := range ss {
|
||||
if seg, ok := sms.s[s]; ok {
|
||||
if sf.coarse == nil {
|
||||
sf.coarse = seg.ShouldExcludeCoarse
|
||||
} else {
|
||||
sf.coarse = sf.coarse.Or(seg.ShouldExcludeCoarse)
|
||||
}
|
||||
if sf.fine == nil {
|
||||
sf.fine = seg.ShouldExcludeFine
|
||||
} else {
|
||||
sf.fine = sf.fine.Or(seg.ShouldExcludeFine)
|
||||
}
|
||||
} else if onNotFound != nil {
|
||||
onNotFound(s)
|
||||
}
|
||||
}
|
||||
|
||||
if sf.coarse == nil {
|
||||
sf.coarse = matchAll
|
||||
}
|
||||
if sf.fine == nil {
|
||||
sf.fine = matchAll
|
||||
}
|
||||
|
||||
return sf
|
||||
}
|
||||
|
||||
type SegmentConfig struct {
|
||||
Excludes []SegmentMatcherFields
|
||||
Includes []SegmentMatcherFields
|
||||
}
|
||||
|
||||
// SegmentMatcherFields is a matcher for a segment include or exclude.
|
||||
// All of these are Glob patterns.
|
||||
type SegmentMatcherFields struct {
|
||||
Kind string
|
||||
Path string
|
||||
Lang string
|
||||
Output string
|
||||
}
|
||||
|
||||
func getGlob(s string) (glob.Glob, error) {
|
||||
if s == "" {
|
||||
return nil, nil
|
||||
}
|
||||
g, err := hglob.GetGlob(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compile Glob %q: %w", s, err)
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func compileSegments(f []SegmentMatcherFields) (predicate.P[SegmentMatcherFields], error) {
|
||||
if f == nil {
|
||||
return func(SegmentMatcherFields) bool { return false }, nil
|
||||
}
|
||||
var (
|
||||
result predicate.P[SegmentMatcherFields]
|
||||
section predicate.P[SegmentMatcherFields]
|
||||
)
|
||||
|
||||
addToSection := func(matcherFields SegmentMatcherFields, f func(fields SegmentMatcherFields) string) error {
|
||||
s1 := f(matcherFields)
|
||||
g, err := getGlob(s1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
matcher := func(fields SegmentMatcherFields) bool {
|
||||
s2 := f(fields)
|
||||
if s2 == "" {
|
||||
return false
|
||||
}
|
||||
return g.Match(s2)
|
||||
}
|
||||
if section == nil {
|
||||
section = matcher
|
||||
} else {
|
||||
section = section.And(matcher)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, fields := range f {
|
||||
if fields.Kind != "" {
|
||||
if err := addToSection(fields, func(fields SegmentMatcherFields) string { return fields.Kind }); err != nil {
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
if fields.Path != "" {
|
||||
if err := addToSection(fields, func(fields SegmentMatcherFields) string { return fields.Path }); err != nil {
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
if fields.Lang != "" {
|
||||
if err := addToSection(fields, func(fields SegmentMatcherFields) string { return fields.Lang }); err != nil {
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
if fields.Output != "" {
|
||||
if err := addToSection(fields, func(fields SegmentMatcherFields) string { return fields.Output }); err != nil {
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
result = section
|
||||
} else {
|
||||
result = result.Or(section)
|
||||
}
|
||||
section = nil
|
||||
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func DecodeSegments(in map[string]any) (*config.ConfigNamespace[map[string]SegmentConfig, Segments], error) {
|
||||
buildConfig := func(in any) (Segments, any, error) {
|
||||
sms := Segments{
|
||||
s: map[string]excludeInclude{},
|
||||
}
|
||||
m, err := maps.ToStringMapE(in)
|
||||
if err != nil {
|
||||
return sms, nil, err
|
||||
}
|
||||
if m == nil {
|
||||
m = map[string]any{}
|
||||
}
|
||||
m = maps.CleanConfigStringMap(m)
|
||||
|
||||
var scfgm map[string]SegmentConfig
|
||||
if err := mapstructure.Decode(m, &scfgm); err != nil {
|
||||
return sms, nil, err
|
||||
}
|
||||
|
||||
for k, v := range scfgm {
|
||||
var (
|
||||
include predicate.P[SegmentMatcherFields]
|
||||
exclude predicate.P[SegmentMatcherFields]
|
||||
err error
|
||||
)
|
||||
if v.Excludes != nil {
|
||||
exclude, err = compileSegments(v.Excludes)
|
||||
if err != nil {
|
||||
return sms, nil, err
|
||||
}
|
||||
}
|
||||
if v.Includes != nil {
|
||||
include, err = compileSegments(v.Includes)
|
||||
if err != nil {
|
||||
return sms, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
ei := excludeInclude{
|
||||
exclude: exclude,
|
||||
include: include,
|
||||
}
|
||||
sms.s[k] = ei
|
||||
|
||||
}
|
||||
|
||||
return sms, nil, nil
|
||||
}
|
||||
|
||||
ns, err := config.DecodeNamespace[map[string]SegmentConfig](in, buildConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode segments: %w", err)
|
||||
}
|
||||
return ns, nil
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// 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 segments_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
)
|
||||
|
||||
func TestSegments(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.org/"
|
||||
renderSegments = ["docs"]
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
[languages.no]
|
||||
weight = 2
|
||||
[languages.nb]
|
||||
weight = 3
|
||||
[segments]
|
||||
[segments.docs]
|
||||
[[segments.docs.includes]]
|
||||
kind = "{home,taxonomy,term}"
|
||||
[[segments.docs.includes]]
|
||||
path = "{/docs,/docs/**}"
|
||||
[[segments.docs.excludes]]
|
||||
path = "/blog/**"
|
||||
[[segments.docs.excludes]]
|
||||
lang = "n*"
|
||||
output = "rss"
|
||||
[[segments.docs.excludes]]
|
||||
output = "json"
|
||||
-- layouts/_default/single.html --
|
||||
Single: {{ .Title }}|{{ .RelPermalink }}|
|
||||
-- layouts/_default/list.html --
|
||||
List: {{ .Title }}|{{ .RelPermalink }}|
|
||||
-- content/docs/_index.md --
|
||||
-- content/docs/section1/_index.md --
|
||||
-- content/docs/section1/page1.md --
|
||||
---
|
||||
title: "Docs Page 1"
|
||||
tags: ["tag1", "tag2"]
|
||||
---
|
||||
-- content/blog/_index.md --
|
||||
-- content/blog/section1/page1.md --
|
||||
---
|
||||
title: "Blog Page 1"
|
||||
tags: ["tag1", "tag2"]
|
||||
---
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
b.Assert(b.H.Configs.Base.RootConfig.RenderSegments, qt.DeepEquals, []string{"docs"})
|
||||
|
||||
b.AssertFileContent("public/docs/section1/page1/index.html", "Docs Page 1")
|
||||
b.AssertFileExists("public/blog/section1/page1/index.html", false)
|
||||
b.AssertFileExists("public/index.html", true)
|
||||
b.AssertFileExists("public/index.xml", true)
|
||||
b.AssertFileExists("public/no/index.html", true)
|
||||
b.AssertFileExists("public/no/index.xml", false)
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package segments
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestCompileSegments(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
c.Run("excludes", func(c *qt.C) {
|
||||
fields := []SegmentMatcherFields{
|
||||
{
|
||||
Lang: "n*",
|
||||
Output: "rss",
|
||||
},
|
||||
}
|
||||
|
||||
match, err := compileSegments(fields)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
check := func() {
|
||||
c.Assert(match, qt.IsNotNil)
|
||||
c.Assert(match(SegmentMatcherFields{Lang: "no"}), qt.Equals, false)
|
||||
c.Assert(match(SegmentMatcherFields{Lang: "no", Kind: "page"}), qt.Equals, false)
|
||||
c.Assert(match(SegmentMatcherFields{Lang: "no", Output: "rss"}), qt.Equals, true)
|
||||
c.Assert(match(SegmentMatcherFields{Lang: "no", Output: "html"}), qt.Equals, false)
|
||||
c.Assert(match(SegmentMatcherFields{Kind: "page"}), qt.Equals, false)
|
||||
c.Assert(match(SegmentMatcherFields{Lang: "no", Output: "rss", Kind: "page"}), qt.Equals, true)
|
||||
}
|
||||
|
||||
check()
|
||||
|
||||
fields = []SegmentMatcherFields{
|
||||
{
|
||||
Path: "/blog/**",
|
||||
},
|
||||
{
|
||||
Lang: "n*",
|
||||
Output: "rss",
|
||||
},
|
||||
}
|
||||
|
||||
match, err = compileSegments(fields)
|
||||
c.Assert(err, qt.IsNil)
|
||||
check()
|
||||
c.Assert(match(SegmentMatcherFields{Path: "/blog/foo"}), qt.Equals, true)
|
||||
})
|
||||
|
||||
c.Run("includes", func(c *qt.C) {
|
||||
fields := []SegmentMatcherFields{
|
||||
{
|
||||
Path: "/docs/**",
|
||||
},
|
||||
{
|
||||
Lang: "no",
|
||||
Output: "rss",
|
||||
},
|
||||
}
|
||||
|
||||
match, err := compileSegments(fields)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(match, qt.IsNotNil)
|
||||
c.Assert(match(SegmentMatcherFields{Lang: "no"}), qt.Equals, false)
|
||||
c.Assert(match(SegmentMatcherFields{Kind: "page"}), qt.Equals, false)
|
||||
c.Assert(match(SegmentMatcherFields{Kind: "page", Path: "/blog/foo"}), qt.Equals, false)
|
||||
c.Assert(match(SegmentMatcherFields{Lang: "en"}), qt.Equals, false)
|
||||
c.Assert(match(SegmentMatcherFields{Lang: "no", Output: "rss"}), qt.Equals, true)
|
||||
c.Assert(match(SegmentMatcherFields{Lang: "no", Output: "html"}), qt.Equals, false)
|
||||
c.Assert(match(SegmentMatcherFields{Kind: "page", Path: "/docs/foo"}), qt.Equals, true)
|
||||
})
|
||||
|
||||
c.Run("includes variant1", func(c *qt.C) {
|
||||
c.Skip()
|
||||
|
||||
fields := []SegmentMatcherFields{
|
||||
{
|
||||
Kind: "home",
|
||||
},
|
||||
{
|
||||
Path: "{/docs,/docs/**}",
|
||||
},
|
||||
}
|
||||
|
||||
match, err := compileSegments(fields)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(match, qt.IsNotNil)
|
||||
c.Assert(match(SegmentMatcherFields{Path: "/blog/foo"}), qt.Equals, false)
|
||||
c.Assert(match(SegmentMatcherFields{Kind: "page", Path: "/docs/foo"}), qt.Equals, true)
|
||||
c.Assert(match(SegmentMatcherFields{Kind: "home", Path: "/"}), qt.Equals, true)
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkSegmentsMatch(b *testing.B) {
|
||||
fields := []SegmentMatcherFields{
|
||||
{
|
||||
Path: "/docs/**",
|
||||
},
|
||||
{
|
||||
Lang: "no",
|
||||
Output: "rss",
|
||||
},
|
||||
}
|
||||
|
||||
match, err := compileSegments(fields)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
match(SegmentMatcherFields{Lang: "no", Output: "rss"})
|
||||
}
|
||||
}
|
||||
+74
-12
@@ -19,6 +19,7 @@ import (
|
||||
"io"
|
||||
"mime"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
@@ -297,7 +298,6 @@ func (s *siteRefLinker) refLink(ref string, source any, relative bool, outputFor
|
||||
ref = filepath.ToSlash(ref)
|
||||
|
||||
refURL, err = url.Parse(ref)
|
||||
|
||||
if err != nil {
|
||||
return s.notFoundURL, err
|
||||
}
|
||||
@@ -427,6 +427,73 @@ func (h *HugoSites) fileEventsFilter(events []fsnotify.Event) []fsnotify.Event {
|
||||
return events[:n]
|
||||
}
|
||||
|
||||
type fileEventInfo struct {
|
||||
fsnotify.Event
|
||||
fi os.FileInfo
|
||||
added bool
|
||||
removed bool
|
||||
isChangedDir bool
|
||||
}
|
||||
|
||||
func (h *HugoSites) fileEventsApplyInfo(events []fsnotify.Event) []fileEventInfo {
|
||||
var infos []fileEventInfo
|
||||
for _, ev := range events {
|
||||
removed := false
|
||||
added := false
|
||||
|
||||
if ev.Op&fsnotify.Remove == fsnotify.Remove {
|
||||
removed = true
|
||||
}
|
||||
|
||||
fi, statErr := h.Fs.Source.Stat(ev.Name)
|
||||
|
||||
// Some editors (Vim) sometimes issue only a Rename operation when writing an existing file
|
||||
// Sometimes a rename operation means that file has been renamed other times it means
|
||||
// it's been updated.
|
||||
if ev.Op.Has(fsnotify.Rename) {
|
||||
// If the file is still on disk, it's only been updated, if it's not, it's been moved
|
||||
if statErr != nil {
|
||||
removed = true
|
||||
}
|
||||
}
|
||||
if ev.Op.Has(fsnotify.Create) {
|
||||
added = true
|
||||
}
|
||||
|
||||
isChangedDir := statErr == nil && fi.IsDir()
|
||||
|
||||
infos = append(infos, fileEventInfo{
|
||||
Event: ev,
|
||||
fi: fi,
|
||||
added: added,
|
||||
removed: removed,
|
||||
isChangedDir: isChangedDir,
|
||||
})
|
||||
}
|
||||
|
||||
n := 0
|
||||
|
||||
for _, ev := range infos {
|
||||
// Remove any directories that's also represented by a file.
|
||||
keep := true
|
||||
if ev.isChangedDir {
|
||||
for _, ev2 := range infos {
|
||||
if ev2.fi != nil && !ev2.fi.IsDir() && filepath.Dir(ev2.Name) == ev.Name {
|
||||
keep = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if keep {
|
||||
infos[n] = ev
|
||||
n++
|
||||
}
|
||||
}
|
||||
infos = infos[:n]
|
||||
|
||||
return infos
|
||||
}
|
||||
|
||||
func (h *HugoSites) fileEventsTranslate(events []fsnotify.Event) []fsnotify.Event {
|
||||
eventMap := make(map[string][]fsnotify.Event)
|
||||
|
||||
@@ -531,18 +598,13 @@ func (h *HugoSites) fileEventsContentPaths(p []pathChange) []pathChange {
|
||||
return keepers
|
||||
}
|
||||
|
||||
// HomeAbsURL is a convenience method giving the absolute URL to the home page.
|
||||
func (s *Site) HomeAbsURL() string {
|
||||
base := ""
|
||||
if len(s.conf.Languages) > 1 {
|
||||
base = s.Language().Lang
|
||||
}
|
||||
return s.AbsURL(base, false)
|
||||
}
|
||||
|
||||
// SitemapAbsURL is a convenience method giving the absolute URL to the sitemap.
|
||||
func (s *Site) SitemapAbsURL() string {
|
||||
p := s.HomeAbsURL()
|
||||
base := ""
|
||||
if len(s.conf.Languages) > 1 || s.Conf.DefaultContentLanguageInSubdir() {
|
||||
base = s.Language().Lang
|
||||
}
|
||||
p := s.AbsURL(base, false)
|
||||
if !strings.HasSuffix(p, "/") {
|
||||
p += "/"
|
||||
}
|
||||
@@ -681,7 +743,7 @@ func (s *Site) getLanguagePermalinkLang(alwaysInSubDir bool) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
if s.h.Conf.IsMultiLingual() && alwaysInSubDir {
|
||||
if s.h.Conf.IsMultilingual() && alwaysInSubDir {
|
||||
return s.Language().Lang
|
||||
}
|
||||
|
||||
|
||||
+20
-8
@@ -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{
|
||||
@@ -357,8 +361,7 @@ func newHugoSites(cfg deps.DepsCfg, d *deps.Deps, pageTrees *pageTrees, sites []
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// Returns true if we're running in a server.
|
||||
// Deprecated: use hugo.IsServer instead
|
||||
// Deprecated: Use hugo.IsServer instead.
|
||||
func (s *Site) IsServer() bool {
|
||||
hugo.Deprecate(".Site.IsServer", "Use hugo.IsServer instead.", "v0.120.0")
|
||||
return s.conf.Internal.Running
|
||||
@@ -378,8 +381,9 @@ func (s *Site) Copyright() string {
|
||||
return s.conf.Copyright
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Home.OutputFormats.Get "rss" instead.
|
||||
func (s *Site) RSSLink() template.URL {
|
||||
hugo.Deprecate("Site.RSSLink", "Use the Output Format's Permalink method instead, e.g. .OutputFormats.Get \"RSS\".Permalink", "v0.114.0")
|
||||
hugo.Deprecate(".Site.RSSLink", "Use the Output Format's Permalink method instead, e.g. .OutputFormats.Get \"RSS\".Permalink", "v0.114.0")
|
||||
rssOutputFormat := s.home.OutputFormats().Get("rss")
|
||||
return template.URL(rssOutputFormat.Permalink())
|
||||
}
|
||||
@@ -427,9 +431,9 @@ func (s *Site) BaseURL() string {
|
||||
return s.conf.C.BaseURL.WithPath
|
||||
}
|
||||
|
||||
// Returns the last modification date of the content.
|
||||
// Deprecated: Use .Lastmod instead.
|
||||
// Deprecated: Use .Site.Lastmod instead.
|
||||
func (s *Site) LastChange() time.Time {
|
||||
hugo.Deprecate(".Site.LastChange", "Use .Site.Lastmod instead.", "v0.123.0")
|
||||
return s.lastmod
|
||||
}
|
||||
|
||||
@@ -443,25 +447,31 @@ func (s *Site) Params() maps.Params {
|
||||
return s.conf.Params
|
||||
}
|
||||
|
||||
// Deprecated: Use taxonomies instead.
|
||||
func (s *Site) Author() map[string]any {
|
||||
hugo.Deprecate(".Site.Author", "Use taxonomies instead.", "v0.124.0")
|
||||
return s.conf.Author
|
||||
}
|
||||
|
||||
// Deprecated: Use taxonomies instead.
|
||||
func (s *Site) Authors() page.AuthorList {
|
||||
hugo.Deprecate(".Site.Authors", "Use taxonomies instead.", "v0.124.0")
|
||||
return page.AuthorList{}
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Params instead.
|
||||
func (s *Site) Social() map[string]string {
|
||||
hugo.Deprecate(".Site.Social", "Use .Site.Params instead.", "v0.124.0")
|
||||
return s.conf.Social
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Config.Services.Disqus.Shortname instead
|
||||
// Deprecated: Use .Site.Config.Services.Disqus.Shortname instead.
|
||||
func (s *Site) DisqusShortname() string {
|
||||
hugo.Deprecate(".Site.DisqusShortname", "Use .Site.Config.Services.Disqus.Shortname instead.", "v0.120.0")
|
||||
return s.Config().Services.Disqus.Shortname
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Config.Services.GoogleAnalytics.ID instead
|
||||
// Deprecated: Use .Site.Config.Services.GoogleAnalytics.ID instead.
|
||||
func (s *Site) GoogleAnalytics() string {
|
||||
hugo.Deprecate(".Site.GoogleAnalytics", "Use .Site.Config.Services.GoogleAnalytics.ID instead.", "v0.120.0")
|
||||
return s.Config().Services.GoogleAnalytics.ID
|
||||
@@ -480,8 +490,10 @@ func (s *Site) BuildDrafts() bool {
|
||||
return s.conf.BuildDrafts
|
||||
}
|
||||
|
||||
// Deprecated: Use hugo.IsMultilingual instead.
|
||||
func (s *Site) IsMultiLingual() bool {
|
||||
return s.h.isMultiLingual()
|
||||
hugo.Deprecate(".Site.IsMultiLingual", "Use hugo.IsMultilingual instead.", "v0.124.0")
|
||||
return s.h.isMultilingual()
|
||||
}
|
||||
|
||||
func (s *Site) LanguagePrefix() string {
|
||||
|
||||
@@ -271,7 +271,7 @@ func (s *Site) renderAliases() error {
|
||||
p := n.(*pageState)
|
||||
|
||||
// We cannot alias a page that's not rendered.
|
||||
if p.m.noLink() {
|
||||
if p.m.noLink() || p.skipRender() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@ func (s *Site) renderAliases() error {
|
||||
// renderMainLanguageRedirect creates a redirect to the main language home,
|
||||
// depending on if it lives in sub folder (e.g. /en) or not.
|
||||
func (s *Site) renderMainLanguageRedirect() error {
|
||||
if s.h.Conf.IsMultihost() || !(s.h.Conf.DefaultContentLanguageInSubdir() || s.h.Conf.IsMultiLingual()) {
|
||||
if s.h.Conf.IsMultihost() || !(s.h.Conf.DefaultContentLanguageInSubdir() || s.h.Conf.IsMultilingual()) {
|
||||
// No need for a redirect
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -398,3 +398,26 @@ Kind: {{ .Kind }}|RelPermalink: {{ .RelPermalink }}|SectionsPath: {{ .SectionsPa
|
||||
b.AssertFileContent("public/a/b/c/mybundle/index.html", "Kind: page|RelPermalink: /a/b/c/mybundle/|SectionsPath: /a/b/c|SectionsEntries: [a b c]|Len: 3")
|
||||
b.AssertFileContent("public/index.html", "Kind: home|RelPermalink: /|SectionsPath: /|SectionsEntries: []|Len: 0")
|
||||
}
|
||||
|
||||
func TestParentWithPageOverlap(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com/"
|
||||
-- content/docs/_index.md --
|
||||
-- content/docs/logs/_index.md --
|
||||
-- content/docs/logs/sdk.md --
|
||||
-- content/docs/logs/sdk_exporters/stdout.md --
|
||||
-- layouts/_default/list.html --
|
||||
{{ .RelPermalink }}|{{ with .Parent}}{{ .RelPermalink }}{{ end }}|
|
||||
-- layouts/_default/single.html --
|
||||
{{ .RelPermalink }}|{{ with .Parent}}{{ .RelPermalink }}{{ end }}|
|
||||
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/index.html", "/||")
|
||||
b.AssertFileContent("public/docs/index.html", "/docs/|/|")
|
||||
b.AssertFileContent("public/docs/logs/index.html", "/docs/logs/|/docs/|")
|
||||
b.AssertFileContent("public/docs/logs/sdk/index.html", "/docs/logs/sdk/|/docs/logs/|")
|
||||
b.AssertFileContent("public/docs/logs/sdk_exporters/stdout/index.html", "/docs/logs/sdk_exporters/stdout/|/docs/logs/|")
|
||||
}
|
||||
|
||||
+78
-1
@@ -15,6 +15,7 @@ package hugolib
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/config"
|
||||
@@ -127,7 +128,7 @@ func TestParseSitemap(t *testing.T) {
|
||||
func TestSitemapShouldNotUseListXML(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableKinds = ["term", "taxonomy"]
|
||||
@@ -148,3 +149,79 @@ Home.
|
||||
|
||||
b.AssertFileContent("public/sitemap.xml", "https://example.com/en/sitemap.xml")
|
||||
}
|
||||
|
||||
func TestSitemapAndContentBundleNamedSitemap(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','rss','section','taxonomy','term']
|
||||
-- layouts/_default/single.html --
|
||||
layouts/_default/single.html
|
||||
-- layouts/sitemap/single.html --
|
||||
layouts/sitemap/single.html
|
||||
-- content/sitemap/index.md --
|
||||
---
|
||||
title: My sitemap
|
||||
type: sitemap
|
||||
---
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileExists("public/sitemap.xml", true)
|
||||
}
|
||||
|
||||
// Issue 12266
|
||||
func TestSitemapIssue12266(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = 'https://example.org/'
|
||||
disableKinds = ['rss','taxonomy','term']
|
||||
defaultContentLanguage = 'en'
|
||||
defaultContentLanguageInSubdir = true
|
||||
[languages.de]
|
||||
[languages.en]
|
||||
`
|
||||
|
||||
// Test A: multilingual with defaultContentLanguageInSubdir = true
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/sitemap.xml",
|
||||
"<loc>https://example.org/de/sitemap.xml</loc>",
|
||||
"<loc>https://example.org/en/sitemap.xml</loc>",
|
||||
)
|
||||
b.AssertFileContent("public/de/sitemap.xml", "<loc>https://example.org/de/</loc>")
|
||||
b.AssertFileContent("public/en/sitemap.xml", "<loc>https://example.org/en/</loc>")
|
||||
|
||||
// Test B: multilingual with defaultContentLanguageInSubdir = false
|
||||
files = strings.ReplaceAll(files, "defaultContentLanguageInSubdir = true", "defaultContentLanguageInSubdir = false")
|
||||
|
||||
b = Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/sitemap.xml",
|
||||
"<loc>https://example.org/de/sitemap.xml</loc>",
|
||||
"<loc>https://example.org/en/sitemap.xml</loc>",
|
||||
)
|
||||
b.AssertFileContent("public/de/sitemap.xml", "<loc>https://example.org/de/</loc>")
|
||||
b.AssertFileContent("public/en/sitemap.xml", "<loc>https://example.org/</loc>")
|
||||
|
||||
// Test C: monolingual with defaultContentLanguageInSubdir = false
|
||||
files = strings.ReplaceAll(files, "[languages.de]", "")
|
||||
files = strings.ReplaceAll(files, "[languages.en]", "")
|
||||
|
||||
b = Test(t, files)
|
||||
|
||||
b.AssertFileExists("public/en/sitemap.xml", false)
|
||||
b.AssertFileContent("public/sitemap.xml", "<loc>https://example.org/</loc>")
|
||||
|
||||
// Test D: monolingual with defaultContentLanguageInSubdir = true
|
||||
files = strings.ReplaceAll(files, "defaultContentLanguageInSubdir = false", "defaultContentLanguageInSubdir = true")
|
||||
|
||||
b = Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/sitemap.xml", "<loc>https://example.org/en/sitemap.xml</loc>")
|
||||
b.AssertFileContent("public/en/sitemap.xml", "<loc>https://example.org/en/</loc>")
|
||||
}
|
||||
|
||||
@@ -885,3 +885,88 @@ 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|")
|
||||
}
|
||||
|
||||
func TestTaxonomyLookupIssue12193(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['page','rss','section','sitemap']
|
||||
[taxonomies]
|
||||
author = 'authors'
|
||||
-- layouts/_default/list.html --
|
||||
{{ .Title }}|
|
||||
-- layouts/_default/author.terms.html --
|
||||
layouts/_default/author.terms.html
|
||||
-- content/authors/_index.md --
|
||||
---
|
||||
title: Authors Page
|
||||
---
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileExists("public/index.html", true)
|
||||
b.AssertFileExists("public/authors/index.html", true)
|
||||
b.AssertFileContent("public/authors/index.html", "layouts/_default/author.terms.html") // failing test
|
||||
}
|
||||
|
||||
func TestTaxonomyNestedEmptySectionsIssue12188(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['rss','sitemap']
|
||||
defaultContentLanguage = 'en'
|
||||
defaultContentLanguageInSubdir = true
|
||||
[languages.en]
|
||||
weight = 1
|
||||
[languages.ja]
|
||||
weight = 2
|
||||
[taxonomies]
|
||||
's1/category' = 's1/category'
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}|
|
||||
-- layouts/_default/list.html --
|
||||
{{ .Title }}|
|
||||
-- content/s1/p1.en.md --
|
||||
---
|
||||
title: p1
|
||||
---
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileExists("public/en/s1/index.html", true)
|
||||
b.AssertFileExists("public/en/s1/p1/index.html", true)
|
||||
b.AssertFileExists("public/en/s1/category/index.html", true)
|
||||
|
||||
b.AssertFileExists("public/ja/s1/index.html", false) // failing test
|
||||
b.AssertFileExists("public/ja/s1/category/index.html", true)
|
||||
}
|
||||
|
||||
+7
-2
@@ -1,7 +1,12 @@
|
||||
# Release env.
|
||||
# These will be replaced by script before release.
|
||||
HUGORELEASER_TAG=v0.123.2
|
||||
HUGORELEASER_COMMITISH=929b91fe75cb0d041f22b4707700dfc117115ad4
|
||||
HUGORELEASER_TAG=v0.123.8
|
||||
HUGORELEASER_COMMITISH=5fed9c591b694f314e5939548e11cc3dcb79a79c
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -355,7 +355,7 @@ func (im *identityManager) String() string {
|
||||
}
|
||||
|
||||
func (im *identityManager) forEeachIdentity(fn func(id Identity) bool) bool {
|
||||
// The absense of a lock here is debliberate. This is currently opnly used on server reloads
|
||||
// The absence of a lock here is deliberate. This is currently only used on server reloads
|
||||
// in a single-threaded context.
|
||||
for id := range im.ids {
|
||||
if fn(id) {
|
||||
|
||||
+1
-1
@@ -134,7 +134,7 @@ var commonTestScriptsParam = testscript.Params{
|
||||
fmt.Fprintf(ts.Stdout(), "%s %04o %s %s\n", fi.Mode(), fi.Mode().Perm(), fi.ModTime().Format(time.RFC3339Nano), fi.Name())
|
||||
}
|
||||
},
|
||||
// append appends to a file with a leaading newline.
|
||||
// append appends to a file with a leading newline.
|
||||
"append": func(ts *testscript.TestScript, neg bool, args []string) {
|
||||
if len(args) < 2 {
|
||||
ts.Fatalf("usage: append FILE TEXT")
|
||||
|
||||
@@ -147,7 +147,7 @@ type codeBlockContext struct {
|
||||
ordinal int
|
||||
|
||||
// This is only used in error situations and is expensive to create,
|
||||
// to deleay creation until needed.
|
||||
// to delay creation until needed.
|
||||
pos htext.Position
|
||||
posInit sync.Once
|
||||
createPos func() htext.Position
|
||||
|
||||
@@ -18,14 +18,14 @@ import (
|
||||
"bytes"
|
||||
|
||||
"github.com/gohugoio/hugo-goldmark-extensions/passthrough"
|
||||
"github.com/yuin/goldmark/util"
|
||||
|
||||
"github.com/gohugoio/hugo/markup/goldmark/codeblocks"
|
||||
"github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
|
||||
"github.com/gohugoio/hugo/markup/goldmark/images"
|
||||
"github.com/gohugoio/hugo/markup/goldmark/internal/extensions/attributes"
|
||||
"github.com/gohugoio/hugo/markup/goldmark/internal/render"
|
||||
|
||||
"github.com/gohugoio/hugo/markup/converter"
|
||||
"github.com/gohugoio/hugo/markup/tableofcontents"
|
||||
"github.com/yuin/goldmark"
|
||||
emoji "github.com/yuin/goldmark-emoji"
|
||||
"github.com/yuin/goldmark/ast"
|
||||
@@ -34,6 +34,9 @@ import (
|
||||
"github.com/yuin/goldmark/renderer"
|
||||
"github.com/yuin/goldmark/renderer/html"
|
||||
"github.com/yuin/goldmark/text"
|
||||
|
||||
"github.com/gohugoio/hugo/markup/converter"
|
||||
"github.com/gohugoio/hugo/markup/tableofcontents"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -91,10 +94,17 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
|
||||
rendererOptions = append(rendererOptions, html.WithUnsafe())
|
||||
}
|
||||
|
||||
tocRendererOptions := make([]renderer.Option, len(rendererOptions))
|
||||
if rendererOptions != nil {
|
||||
copy(tocRendererOptions, rendererOptions)
|
||||
}
|
||||
tocRendererOptions = append(tocRendererOptions,
|
||||
renderer.WithNodeRenderers(util.Prioritized(extension.NewStrikethroughHTMLRenderer(), 500)),
|
||||
renderer.WithNodeRenderers(util.Prioritized(emoji.NewHTMLRenderer(), 200)))
|
||||
var (
|
||||
extensions = []goldmark.Extender{
|
||||
newLinks(cfg),
|
||||
newTocExtension(rendererOptions),
|
||||
newTocExtension(tocRendererOptions),
|
||||
}
|
||||
parserOptions []parser.Option
|
||||
)
|
||||
@@ -174,9 +184,11 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
|
||||
}
|
||||
}
|
||||
|
||||
extensions = append(extensions, passthrough.NewPassthroughWithDelimiters(
|
||||
inlineDelimiters,
|
||||
blockDelimiters,
|
||||
extensions = append(extensions, passthrough.New(
|
||||
passthrough.Config{
|
||||
InlineDelimiters: inlineDelimiters,
|
||||
BlockDelimiters: blockDelimiters,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@ package goldmark
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
strikethroughAst "github.com/yuin/goldmark/extension/ast"
|
||||
|
||||
emojiAst "github.com/yuin/goldmark-emoji/ast"
|
||||
|
||||
"github.com/gohugoio/hugo/markup/tableofcontents"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
@@ -86,7 +90,9 @@ func (t *tocTransformer) Transform(n *ast.Document, reader text.Reader, pc parse
|
||||
ast.KindCodeSpan,
|
||||
ast.KindLink,
|
||||
ast.KindImage,
|
||||
ast.KindEmphasis:
|
||||
ast.KindEmphasis,
|
||||
strikethroughAst.KindStrikethrough,
|
||||
emojiAst.KindEmoji:
|
||||
err := t.r.Render(&headingText, reader.Source(), n)
|
||||
if err != nil {
|
||||
return s, err
|
||||
|
||||
@@ -253,14 +253,12 @@ title: p7 (emoji)
|
||||
`)
|
||||
|
||||
// strikethrough
|
||||
// TODO failing test: Issue #8087
|
||||
// b.AssertFileContent("public/p6/index.html", `
|
||||
// <li><a href="#ome-deleted-text">Some <del>deleted</del> text</a></li>
|
||||
// `)
|
||||
b.AssertFileContent("public/p6/index.html", `
|
||||
<li><a href="#some-deleted-text">Some <del>deleted</del> text</a></li>
|
||||
`)
|
||||
|
||||
// emoji
|
||||
// TODO failing test: Issue #12022
|
||||
// b.AssertFileContent("public/p7/index.html", `
|
||||
// <li><a href="#a-snake-emoji">A 🐍 emoji</a></li>
|
||||
// `)
|
||||
b.AssertFileContent("public/p7/index.html", `
|
||||
<li><a href="#a-snake-emoji">A 🐍 emoji</a></li>
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ func New(astAttributes []ast.Attribute, ownerType AttributesOwnerType) *Attribut
|
||||
case []byte:
|
||||
// Note that we don't do any HTML escaping here.
|
||||
// We used to do that, but that changed in #9558.
|
||||
// Noww it's up to the templates to decide.
|
||||
// Now it's up to the templates to decide.
|
||||
vv = string(vvv)
|
||||
default:
|
||||
panic(fmt.Sprintf("not implemented: %T", vvv))
|
||||
@@ -175,7 +175,7 @@ func (a *AttributesHolder) OptionsSlice() []Attribute {
|
||||
|
||||
// RenderASTAttributes writes the AST attributes to the given as attributes to an HTML element.
|
||||
// This is used by the default HTML renderers, e.g. for headings etc. where no hook template could be found.
|
||||
// This performs HTML esacaping of string attributes.
|
||||
// This performs HTML escaping of string attributes.
|
||||
func RenderASTAttributes(w hugio.FlexiWriter, attributes ...ast.Attribute) {
|
||||
for _, attr := range attributes {
|
||||
|
||||
|
||||
+3
-3
@@ -86,7 +86,7 @@ func ApplyProjectConfigDefaults(mod Module, cfgs ...config.AllProvider) error {
|
||||
|
||||
first := cfgs[0]
|
||||
dirsBase := first.DirsBase()
|
||||
isMultiHost := first.IsMultihost()
|
||||
isMultihost := first.IsMultihost()
|
||||
|
||||
for i, cfg := range cfgs {
|
||||
dirs := cfg.Dirs()
|
||||
@@ -113,7 +113,7 @@ func ApplyProjectConfigDefaults(mod Module, cfgs ...config.AllProvider) error {
|
||||
dir = dirs.AssetDir
|
||||
case files.ComponentFolderStatic:
|
||||
// For static dirs, we only care about the language in multihost setups.
|
||||
dropLang = !isMultiHost
|
||||
dropLang = !isMultihost
|
||||
}
|
||||
|
||||
var perLang bool
|
||||
@@ -270,7 +270,7 @@ type Config struct {
|
||||
|
||||
// When enabled, we will pick the vendored module closest to the module
|
||||
// using it.
|
||||
// The default behaviour is to pick the first.
|
||||
// The default behavior is to pick the first.
|
||||
// Note that there can still be only one dependency of a given module path,
|
||||
// so once it is in use it cannot be redefined.
|
||||
VendorClosest bool
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
|
||||
const commitPrefix = "releaser:"
|
||||
|
||||
// New initialises a ReleaseHandler.
|
||||
// New initializes a ReleaseHandler.
|
||||
func New(skipPush, try bool, step int) (*ReleaseHandler, error) {
|
||||
if step < 1 || step > 2 {
|
||||
return nil, fmt.Errorf("step must be 1 or 2")
|
||||
|
||||
+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
|
||||
|
||||
@@ -91,15 +91,19 @@ func (f textFilter) Draw(dst draw.Image, src image.Image, options *gift.Options)
|
||||
y := f.y
|
||||
d.Dot = fixed.P(f.x, f.y)
|
||||
|
||||
// Draw text and break line at max width
|
||||
parts := strings.Fields(f.text)
|
||||
for _, str := range parts {
|
||||
strWith := font.MeasureString(face, str)
|
||||
if (d.Dot.X.Ceil() + strWith.Ceil()) >= maxWidth {
|
||||
y = y + fontHeight + f.linespacing
|
||||
d.Dot = fixed.P(f.x, y)
|
||||
// Draw text line by line, breaking each line at the maximum width.
|
||||
f.text = strings.ReplaceAll(f.text, "\r", "")
|
||||
for _, line := range strings.Split(f.text, "\n") {
|
||||
for _, str := range strings.Fields(line) {
|
||||
strWidth := font.MeasureString(face, str)
|
||||
if (d.Dot.X.Ceil() + strWidth.Ceil()) >= maxWidth {
|
||||
y = y + fontHeight + f.linespacing
|
||||
d.Dot = fixed.P(f.x, y)
|
||||
}
|
||||
d.DrawString(str + " ")
|
||||
}
|
||||
d.DrawString(str + " ")
|
||||
y = y + fontHeight + f.linespacing
|
||||
d.Dot = fixed.P(f.x, y)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,9 +52,9 @@ type AlternativeOutputFormatsProvider interface {
|
||||
|
||||
// AuthorProvider provides author information.
|
||||
type AuthorProvider interface {
|
||||
// Deprecated.
|
||||
// Deprecated: Use taxonomies instead.
|
||||
Author() Author
|
||||
// Deprecated.
|
||||
// Deprecated: Use taxonomies instead.
|
||||
Authors() AuthorList
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,11 @@
|
||||
package page
|
||||
|
||||
// AuthorList is a list of all authors and their metadata.
|
||||
// Deprecated: Use taxonomies instead.
|
||||
type AuthorList map[string]Author
|
||||
|
||||
// Author contains details about the author of a page.
|
||||
// Deprecated: Use taxonomies instead.
|
||||
type Author struct {
|
||||
GivenName string
|
||||
FamilyName string
|
||||
@@ -41,4 +43,5 @@ type Author struct {
|
||||
// - youtube
|
||||
// - linkedin
|
||||
// - skype
|
||||
// Deprecated: Use taxonomies instead.
|
||||
type AuthorSocial map[string]string
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -157,9 +157,11 @@ func TestDecodeCascadeConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
type testConfig struct {
|
||||
environment string
|
||||
running bool
|
||||
workingDir string
|
||||
environment string
|
||||
running bool
|
||||
workingDir string
|
||||
multihost bool
|
||||
multilingual bool
|
||||
}
|
||||
|
||||
func (c testConfig) Environment() string {
|
||||
@@ -174,6 +176,14 @@ func (c testConfig) WorkingDir() string {
|
||||
return c.workingDir
|
||||
}
|
||||
|
||||
func (c testConfig) IsMultihost() bool {
|
||||
return c.multihost
|
||||
}
|
||||
|
||||
func (c testConfig) IsMultilingual() bool {
|
||||
return c.multilingual
|
||||
}
|
||||
|
||||
func TestIsGlobWithExtension(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
|
||||
@@ -79,10 +79,12 @@ func (p *nopPage) RSSLink() template.URL {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Deprecated: Use taxonomies instead.
|
||||
func (p *nopPage) Author() Author {
|
||||
return Author{}
|
||||
}
|
||||
|
||||
// Deprecated: Use taxonomies instead.
|
||||
func (p *nopPage) Authors() AuthorList {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -421,18 +421,22 @@ func (f *frontmatterFieldHandlers) newDateFieldHandler(key string, setter func(d
|
||||
return false, nil
|
||||
}
|
||||
|
||||
date, err := htime.ToTimeInDefaultLocationE(v, d.Location)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
var date time.Time
|
||||
if vt, ok := v.(time.Time); ok && vt.Location() == d.Location {
|
||||
date = vt
|
||||
} else {
|
||||
var err error
|
||||
date, err = htime.ToTimeInDefaultLocationE(v, d.Location)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
d.PageConfig.Params[key] = date
|
||||
}
|
||||
|
||||
// We map several date keys to one, so, for example,
|
||||
// "expirydate", "unpublishdate" will all set .ExpiryDate (first found).
|
||||
setter(d, date)
|
||||
|
||||
// This is the params key as set in front matter.
|
||||
d.PageConfig.Params[key] = date
|
||||
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// 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 pagemeta_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
)
|
||||
|
||||
func TestLastModEq(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
timeZone = "Europe/London"
|
||||
-- content/p1.md --
|
||||
---
|
||||
title: p1
|
||||
date: 2024-03-13T06:00:00
|
||||
---
|
||||
-- layouts/_default/single.html --
|
||||
Date: {{ .Date }}
|
||||
Lastmod: {{ .Lastmod }}
|
||||
Eq: {{ eq .Date .Lastmod }}
|
||||
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/p1/index.html", `
|
||||
Date: 2024-03-13 06:00:00 +0000 GMT
|
||||
Lastmod: 2024-03-13 06:00:00 +0000 GMT
|
||||
Eq: true
|
||||
`)
|
||||
}
|
||||
+26
-18
@@ -54,8 +54,7 @@ type Site interface {
|
||||
// A shortcut to the home
|
||||
Home() Page
|
||||
|
||||
// Returns true if we're running in a server.
|
||||
// Deprecated: use hugo.IsServer instead
|
||||
// Deprecated: Use hugo.IsServer instead.
|
||||
IsServer() bool
|
||||
|
||||
// Returns the server port.
|
||||
@@ -64,7 +63,6 @@ type Site interface {
|
||||
// Returns the configured title for this Site.
|
||||
Title() string
|
||||
|
||||
// Returns the configured language code for this Site.
|
||||
// Deprecated: Use .Language.LanguageCode instead.
|
||||
LanguageCode() string
|
||||
|
||||
@@ -86,7 +84,6 @@ type Site interface {
|
||||
// Returns a taxonomy map.
|
||||
Taxonomies() TaxonomyList
|
||||
|
||||
// Returns the last modification date of the content.
|
||||
// Deprecated: Use .Lastmod instead.
|
||||
LastChange() time.Time
|
||||
|
||||
@@ -111,13 +108,13 @@ type Site interface {
|
||||
// Returns the site config.
|
||||
Config() SiteConfig
|
||||
|
||||
// Author is deprecated and will be removed in a future release.
|
||||
// Deprecated: Use taxonomies instead.
|
||||
Author() map[string]interface{}
|
||||
|
||||
// Authors is deprecated and will be removed in a future release.
|
||||
// Deprecated: Use taxonomies instead.
|
||||
Authors() AuthorList
|
||||
|
||||
// Returns the social links for this site.
|
||||
// Deprecated: Use .Site.Params instead.
|
||||
Social() map[string]string
|
||||
|
||||
// Deprecated: Use Config().Services.GoogleAnalytics instead.
|
||||
@@ -129,13 +126,13 @@ type Site interface {
|
||||
// BuildDrafts is deprecated and will be removed in a future release.
|
||||
BuildDrafts() bool
|
||||
|
||||
// IsMultiLingual reports whether this site is configured with more than one language.
|
||||
// Deprecated: Use hugo.IsMultilingual instead.
|
||||
IsMultiLingual() bool
|
||||
|
||||
// LanguagePrefix returns the language prefix for this site.
|
||||
LanguagePrefix() string
|
||||
|
||||
// Deprecated. Use site.Home.OutputFormats.Get "rss" instead.
|
||||
// Deprecated: Use .Site.Home.OutputFormats.Get "rss" instead.
|
||||
RSSLink() template.URL
|
||||
}
|
||||
|
||||
@@ -168,19 +165,22 @@ func (s *siteWrapper) Key() string {
|
||||
return s.s.Language().Lang
|
||||
}
|
||||
|
||||
// // Deprecated: Use .Site.Params instead.
|
||||
func (s *siteWrapper) Social() map[string]string {
|
||||
return s.s.Social()
|
||||
}
|
||||
|
||||
// Deprecated: Use taxonomies instead.
|
||||
func (s *siteWrapper) Author() map[string]interface{} {
|
||||
return s.s.Author()
|
||||
}
|
||||
|
||||
// Deprecated: Use taxonomies instead.
|
||||
func (s *siteWrapper) Authors() AuthorList {
|
||||
return AuthorList{}
|
||||
return s.s.Authors()
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Config.Services.GoogleAnalytics.ID instead
|
||||
// Deprecated: Use .Site.Config.Services.GoogleAnalytics.ID instead.
|
||||
func (s *siteWrapper) GoogleAnalytics() string {
|
||||
return s.s.GoogleAnalytics()
|
||||
}
|
||||
@@ -217,7 +217,7 @@ func (s *siteWrapper) Home() Page {
|
||||
return s.s.Home()
|
||||
}
|
||||
|
||||
// Deprecated: use hugo.IsServer instead
|
||||
// Deprecated: Use hugo.IsServer instead.
|
||||
func (s *siteWrapper) IsServer() bool {
|
||||
return s.s.IsServer()
|
||||
}
|
||||
@@ -262,9 +262,9 @@ func (s *siteWrapper) Taxonomies() TaxonomyList {
|
||||
return s.s.Taxonomies()
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Lastmod instead.
|
||||
func (s *siteWrapper) LastChange() time.Time {
|
||||
hugo.Deprecate(".Site.LastChange", "Use .Site.Lastmod instead.", "v0.123.0")
|
||||
return s.s.Lastmod()
|
||||
return s.s.LastChange()
|
||||
}
|
||||
|
||||
func (s *siteWrapper) Lastmod() time.Time {
|
||||
@@ -295,11 +295,12 @@ func (s *siteWrapper) BuildDrafts() bool {
|
||||
return s.s.BuildDrafts()
|
||||
}
|
||||
|
||||
// Deprecated: Use hugo.IsMultilingual instead.
|
||||
func (s *siteWrapper) IsMultiLingual() bool {
|
||||
return s.s.IsMultiLingual()
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Config.Services.Disqus.Shortname instead
|
||||
// Deprecated: Use .Site.Config.Services.Disqus.Shortname instead.
|
||||
func (s *siteWrapper) DisqusShortname() string {
|
||||
return s.s.DisqusShortname()
|
||||
}
|
||||
@@ -308,6 +309,7 @@ func (s *siteWrapper) LanguagePrefix() string {
|
||||
return s.s.LanguagePrefix()
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Home.OutputFormats.Get "rss" instead.
|
||||
func (s *siteWrapper) RSSLink() template.URL {
|
||||
return s.s.RSSLink()
|
||||
}
|
||||
@@ -322,14 +324,17 @@ type testSite struct {
|
||||
l *langs.Language
|
||||
}
|
||||
|
||||
// Deprecated: Use taxonomies instead.
|
||||
func (s testSite) Author() map[string]interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deprecated: Use taxonomies instead.
|
||||
func (s testSite) Authors() AuthorList {
|
||||
return AuthorList{}
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Params instead.
|
||||
func (s testSite) Social() map[string]string {
|
||||
return make(map[string]string)
|
||||
}
|
||||
@@ -342,6 +347,7 @@ func (t testSite) ServerPort() int {
|
||||
return 1313
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Lastmod instead.
|
||||
func (testSite) LastChange() (t time.Time) {
|
||||
return
|
||||
}
|
||||
@@ -386,7 +392,7 @@ func (t testSite) Languages() langs.Languages {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Config.Services.GoogleAnalytics.ID instead
|
||||
// Deprecated: Use .Site.Config.Services.GoogleAnalytics.ID instead.
|
||||
func (t testSite) GoogleAnalytics() string {
|
||||
return ""
|
||||
}
|
||||
@@ -395,7 +401,7 @@ func (t testSite) MainSections() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deprecated: use hugo.IsServer instead
|
||||
// Deprecated: Use hugo.IsServer instead.
|
||||
func (t testSite) IsServer() bool {
|
||||
return false
|
||||
}
|
||||
@@ -444,7 +450,7 @@ func (s testSite) Config() SiteConfig {
|
||||
return SiteConfig{}
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Config.Services.Disqus.Shortname instead
|
||||
// Deprecated: Use .Site.Config.Services.Disqus.Shortname instead.
|
||||
func (testSite) DisqusShortname() string {
|
||||
return ""
|
||||
}
|
||||
@@ -453,6 +459,7 @@ func (s testSite) BuildDrafts() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Deprecated: Use hugo.IsMultilingual instead.
|
||||
func (s testSite) IsMultiLingual() bool {
|
||||
return false
|
||||
}
|
||||
@@ -461,6 +468,7 @@ func (s testSite) Param(key any) (any, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Home.OutputFormats.Get "rss" instead.
|
||||
func (s testSite) RSSLink() template.URL {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package page
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/compare"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
@@ -58,11 +59,11 @@ type OrderedTaxonomyEntry struct {
|
||||
|
||||
// Get the weighted pages for the given key.
|
||||
func (i Taxonomy) Get(key string) WeightedPages {
|
||||
return i[key]
|
||||
return i[strings.ToLower(key)]
|
||||
}
|
||||
|
||||
// Count the weighted pages for the given key.
|
||||
func (i Taxonomy) Count(key string) int { return len(i[key]) }
|
||||
func (i Taxonomy) Count(key string) int { return len(i[strings.ToLower(key)]) }
|
||||
|
||||
// TaxonomyArray returns an ordered taxonomy with a non defined order.
|
||||
func (i Taxonomy) TaxonomyArray() OrderedTaxonomy {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// 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 page_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
)
|
||||
|
||||
func TestTaxonomiesGetAndCount(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['rss','sitemap']
|
||||
[taxonomies]
|
||||
author = 'authors'
|
||||
-- layouts/_default/home.html --
|
||||
John Smith count: {{ site.Taxonomies.authors.Count "John Smith" }}
|
||||
Robert Jones count: {{ (site.Taxonomies.authors.Get "Robert Jones").Pages.Len }}
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}|
|
||||
-- layouts/_default/list.html --
|
||||
{{ .Title }}|
|
||||
-- content/p1.md --
|
||||
---
|
||||
title: p1
|
||||
authors: [John Smith,Robert Jones]
|
||||
---
|
||||
-- content/p2.md --
|
||||
---
|
||||
title: p2
|
||||
authors: [John Smith]
|
||||
---
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/index.html",
|
||||
"John Smith count: 2",
|
||||
"Robert Jones count: 1",
|
||||
)
|
||||
}
|
||||
@@ -127,10 +127,12 @@ func (p *testPage) AlternativeOutputFormats() OutputFormats {
|
||||
panic("testpage: not implemented")
|
||||
}
|
||||
|
||||
// Deprecated: Use taxonomies instead.
|
||||
func (p *testPage) Author() Author {
|
||||
return Author{}
|
||||
}
|
||||
|
||||
// Deprecated: Use taxonomies instead.
|
||||
func (p *testPage) Authors() AuthorList {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -62,21 +62,22 @@ func (r Resources) Get(name any) Resource {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
namestr = strings.ToLower(namestr)
|
||||
|
||||
namestr = paths.AddLeadingSlash(namestr)
|
||||
|
||||
// First check the Name.
|
||||
// Note that this can be modified by the user in the front matter,
|
||||
// also, it does not contain any language code.
|
||||
for _, resource := range r {
|
||||
if strings.EqualFold(namestr, resource.Name()) {
|
||||
if strings.EqualFold(namestr, paths.AddLeadingSlash(resource.Name())) {
|
||||
return 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, paths.AddLeadingSlash(nop.NameNormalized())) {
|
||||
return resource
|
||||
}
|
||||
}
|
||||
@@ -93,23 +94,21 @@ func (r Resources) GetMatch(pattern any) Resource {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
patternstr = paths.NormalizePathStringBasic(patternstr)
|
||||
|
||||
g, err := glob.GetGlob(patternstr)
|
||||
g, err := glob.GetGlob(paths.AddLeadingSlash(patternstr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, resource := range r {
|
||||
if g.Match(paths.NormalizePathStringBasic(resource.Name())) {
|
||||
if g.Match(paths.AddLeadingSlash(resource.Name())) {
|
||||
return resource
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, check the original name.
|
||||
// Finally, check the normalized 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(paths.AddLeadingSlash(nop.NameNormalized())) {
|
||||
return resource
|
||||
}
|
||||
}
|
||||
@@ -133,22 +132,22 @@ func (r Resources) Match(pattern any) Resources {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
g, err := glob.GetGlob(patternstr)
|
||||
g, err := glob.GetGlob(paths.AddLeadingSlash(patternstr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var matches Resources
|
||||
for _, resource := range r {
|
||||
if g.Match(strings.ToLower(resource.Name())) {
|
||||
if g.Match(paths.AddLeadingSlash(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(paths.AddLeadingSlash(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
|
||||
}
|
||||
|
||||
|
||||
@@ -128,13 +128,16 @@ func (c *Client) match(name, pattern string, matchFunc func(r resource.Resource)
|
||||
|
||||
handle := func(info hugofs.FileMetaInfo) (bool, error) {
|
||||
meta := info.Meta()
|
||||
|
||||
r, err := c.rs.NewResource(resources.ResourceSourceDescriptor{
|
||||
LazyPublish: true,
|
||||
OpenReadSeekCloser: func() (hugio.ReadSeekCloser, error) {
|
||||
return meta.Open()
|
||||
},
|
||||
GroupIdentity: meta.PathInfo,
|
||||
TargetPath: meta.PathInfo.PathNoLang(),
|
||||
NameNormalized: meta.PathInfo.Path(),
|
||||
NameOriginal: meta.PathInfo.Unnormalized().Path(),
|
||||
GroupIdentity: meta.PathInfo,
|
||||
TargetPath: meta.PathInfo.Unnormalized().Path(),
|
||||
})
|
||||
if err != nil {
|
||||
return true, err
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package dartsass integrates with the Dass Sass Embedded protocol to transpile
|
||||
// Package dartsass integrates with the Dart Sass Embedded protocol to transpile
|
||||
// SCSS/SASS.
|
||||
package dartsass
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
|
||||
"github.com/bep/logg"
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/htesting"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass"
|
||||
)
|
||||
@@ -525,3 +526,39 @@ T1: {{ $r.Content }}
|
||||
b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:13:0: number`)
|
||||
b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:14:0: number`)
|
||||
}
|
||||
|
||||
// Note: This test is more or less duplicated in both of the SCSS packages (libsass and dartsass).
|
||||
func TestBootstrap(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !dartsass.Supports() {
|
||||
t.Skip()
|
||||
}
|
||||
if !htesting.IsCI() {
|
||||
t.Skip("skip (slow) test in non-CI environment")
|
||||
}
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["term", "taxonomy", "section", "page"]
|
||||
[module]
|
||||
[[module.imports]]
|
||||
path="github.com/gohugoio/hugo-mod-bootstrap-scss/v5"
|
||||
-- go.mod --
|
||||
module github.com/gohugoio/tests/testHugoModules
|
||||
-- assets/scss/main.scss --
|
||||
@import "bootstrap/bootstrap";
|
||||
-- layouts/index.html --
|
||||
{{ $cssOpts := (dict "transpiler" "dartsass" ) }}
|
||||
{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts }}
|
||||
Styles: {{ $r.RelPermalink }}
|
||||
`
|
||||
|
||||
b := hugolib.NewIntegrationTestBuilder(
|
||||
hugolib.IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
NeedsOsFS: true,
|
||||
}).Build()
|
||||
|
||||
b.AssertFileContent("public/index.html", "Styles: /scss/main.css")
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ func (t importResolver) CanonicalizeURL(url string) (string, error) {
|
||||
var pathDir string
|
||||
if isURL {
|
||||
var found bool
|
||||
prevDir, found = t.c.sfs.MakePathRelative(filepath.Dir(filePath), false)
|
||||
prevDir, found = t.c.sfs.MakePathRelative(filepath.Dir(filePath), true)
|
||||
|
||||
if !found {
|
||||
// Not a member of this filesystem, let Dart Sass handle it.
|
||||
@@ -181,7 +181,7 @@ func (t importResolver) CanonicalizeURL(url string) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Not found, let Dart Dass handle it
|
||||
// Not found, let Dart Sass handle it
|
||||
return "", nil
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
|
||||
"github.com/gohugoio/hugo/htesting"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/scss"
|
||||
)
|
||||
@@ -290,3 +291,39 @@ T1: {{ $r.Content }}
|
||||
|
||||
b.AssertFileContent("public/index.html", `T1: body body{background:url(images/hero.jpg) no-repeat center/cover;font-family:Hugo's New Roman}p{color:blue;font-size:var 24px}b{color:green}`)
|
||||
}
|
||||
|
||||
// Note: This test is more or less duplicated in both of the SCSS packages (libsass and dartsass).
|
||||
func TestBootstrap(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !scss.Supports() {
|
||||
t.Skip()
|
||||
}
|
||||
if !htesting.IsCI() {
|
||||
t.Skip("skip (slow) test in non-CI environment")
|
||||
}
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["term", "taxonomy", "section", "page"]
|
||||
[module]
|
||||
[[module.imports]]
|
||||
path="github.com/gohugoio/hugo-mod-bootstrap-scss/v5"
|
||||
-- go.mod --
|
||||
module github.com/gohugoio/tests/testHugoModules
|
||||
-- assets/scss/main.scss --
|
||||
@import "bootstrap/bootstrap";
|
||||
-- layouts/index.html --
|
||||
{{ $cssOpts := (dict "transpiler" "libsass" ) }}
|
||||
{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts }}
|
||||
Styles: {{ $r.RelPermalink }}
|
||||
`
|
||||
|
||||
b := hugolib.NewIntegrationTestBuilder(
|
||||
hugolib.IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
NeedsOsFS: true,
|
||||
}).Build()
|
||||
|
||||
b.AssertFileContent("public/index.html", "Styles: /scss/main.css")
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user