Compare commits

..

67 Commits

Author SHA1 Message Date
hugoreleaser 629f84e8ed releaser: Bump versions for release of 0.124.0
[ci skip]
2024-03-16 15:44:32 +00:00
Bjørn Erik Pedersen 76ef3f42fa docs: Regen CLI docs 2024-03-16 15:54:33 +01:00
Bjørn Erik Pedersen 0ccb6cdc04 docs: Regen docshelper 2024-03-16 15:54:02 +01:00
Bjørn Erik Pedersen 1f1c62e6c7 Add segments config + --renderSegments flag
Named segments can be defined in `hugo.toml`.

* Eeach segment consists of zero or more `exclude` filters and zero or more `include` filters.
* Eeach filter consists of one or more field Glob matchers.
* Eeach filter in a section (`exclude` or `include`) is ORed together, each matcher in a filter is ANDed together.

The current list of fields that can be filtered are:

* path as defined in https://gohugo.io/methods/page/path/
* kind
* lang
* output (output format, e.g. html).

It is recommended to put coarse grained filters (e.g. for language and output format) in the excludes section, e.g.:

```toml
[segments.segment1]
  [[segments.segment1.excludes]]
    lang = "n*"
  [[segments.segment1.excludes]]
    no     = "en"
    output = "rss"
  [[segments.segment1.includes]]
    term = "{home,term,taxonomy}"
  [[segments.segment1.includes]]
    path = "{/docs,/docs/**}"
```

By default, Hugo will render all segments, but you can enable filters by setting the `renderSegments` option or `--renderSegments` flag, e.g:

```
hugo --renderSegments segment1,segment2
```

For segment `segment1` in the configuration above, this will:

* Skip rendering of all languages matching `n*`, e.g. `no`.
* Skip rendering of the output format `rss` for the `en` language.
* It will render all pages of kind `home`, `term` or `taxonomy`
* It will render the `/docs` section and all pages below.

Fixes #10106
2024-03-16 15:53:26 +01:00
Bjørn Erik Pedersen f1d755965f Fix .Parent when there are overlapping regular pages inbetween
Fixes #12263
2024-03-16 14:48:04 +01:00
Bjørn Erik Pedersen 558f74f009 hugolib: Remove Site.HomeAbsURL
It's not in use and after #12266 it's also not corret to use on its own (use .Site.Home.Permalink).
2024-03-16 12:49:40 +01:00
Bjørn Erik Pedersen ba03114aa9 deps: Upgrade github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.1.0 => v0.2.0 2024-03-16 11:50:11 +01:00
Joe Mooring 3935faa417 hugolib: Fix sitemap index with monolingual site
Fixes #12266
2024-03-16 11:49:00 +01:00
Joe Mooring d4d49e0f0e hugolib: Deprecate site methods Author, Authors, and Social
Closes #12228
2024-03-15 17:26:45 +01:00
Christian Oliff 78178d0c2a all: Typo fixes 2024-03-15 17:25:52 +01:00
Bjørn Erik Pedersen 68d92ef9dd Fix translationKey handling for term pages
Fixes #12261
2024-03-15 16:54:18 +01:00
Bjørn Erik Pedersen b40f3c7df6 Fix intersect and similar for term entry page collections
Fixes #12254
2024-03-15 16:45:53 +01:00
Bjørn Erik Pedersen 57206e7274 Upgrade to Go 1.22.1
Closes #12250
2024-03-15 16:40:36 +01:00
dependabot[bot] b1f8676347 build(deps): bump github.com/evanw/esbuild from 0.20.1 to 0.20.2
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.20.1 to 0.20.2.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.20.1...v0.20.2)

---
updated-dependencies:
- dependency-name: github.com/evanw/esbuild
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-15 15:43:18 +01:00
Bjørn Erik Pedersen 07b2e535be Fix server rebuilds when adding sub sections especially on Windows
This commit also optimizes for the case where change events for both file (e.g. `_index.md`) and the container directory comes in the same event batch.

While testing this on Windows 11 (ARM64), I notice that Windows behaves a little oddly when dumping a folder of files into the content tree; it works (at least after this commit), but it seems like the event batching behaves differently compared to other OSes (even older Win versions).

A related tip would be to try starting the server with polling, to see if that improves the situation, e.g.:

```
hugo server --poll 700ms
```

Fixes #12230
2024-03-15 15:42:56 +01:00
Joe Mooring f038a51b3e tpl/tplimpl: Remove deprecated method from sitemapindex.xml 2024-03-14 21:03:19 +01:00
dependabot[bot] b4bff6190c build(deps): bump golang.org/x/tools from 0.18.0 to 0.19.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.18.0 to 0.19.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.18.0...v0.19.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-14 11:16:50 +01:00
dependabot[bot] d2cebee273 build(deps): bump github.com/tdewolff/minify/v2 from 2.20.17 to 2.20.19
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.20.17 to 2.20.19.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.20.17...v2.20.19)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-14 11:16:07 +01:00
Paul Jolly be914ff34d deps: Upgrade github.com/alecthomas/chroma/v2 to v2.13.0
Closes #11862
2024-03-14 11:04:41 +01:00
Joe Mooring 48a0fea87a tpl/tplimpl: Modify figure shortcode to look for page resource
Closes #12244
Closes #12245
2024-03-14 10:21:40 +02:00
Bjørn Erik Pedersen 9ca1de09dd Fix panic when changing archetype files when servere is running
Fixes #12195
2024-03-13 19:35:28 +02:00
dependabot[bot] e62675002e build(deps): bump golang.org/x/mod from 0.15.0 to 0.16.0
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.15.0 to 0.16.0.
- [Commits](https://github.com/golang/mod/compare/v0.15.0...v0.16.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-13 19:23:42 +02:00
Bjørn Erik Pedersen 9668759ad8 Fix front matter date location when value gets inherited from other dates
Fixes #12236
2024-03-13 19:00:50 +02:00
Bjørn Erik Pedersen 9e9b1f110c Fix Name for nested resourced fetched in resources.ByName and similar
Fixes #12214
2024-03-13 18:59:50 +02:00
Joe Mooring dc6a292133 common/hugo: Rename IsMultiHost and IsMultiLingual
hugo.IsMultiHost => hugo.IsMultihost
hugo.IsMultiLingual => hugo.IsMultilingual

Closes #12232
2024-03-13 17:34:42 +02:00
Joe Mooring 4f92f949ea hugolib: Deprecate .Site.MultiLingual in favor of hugo.IsMultiLingual
Closes #12224
2024-03-10 10:47:51 +02:00
Joe Mooring d24ffdde5b snap: Transition to from core20 to core22
Closes #12219
2024-03-09 08:47:53 -08:00
hugoreleaser ca31b95f30 releaser: Prepare repository for 0.124.0-DEV
[ci skip]
2024-03-07 13:28:40 +00:00
hugoreleaser 5fed9c591b releaser: Bump versions for release of 0.123.8
[ci skip]
2024-03-07 13:14:42 +00:00
Jonas Zeiger 134e7d1d3d markup/goldmark: TOC: render strikethrough, emojis
Configure the TOC (TableOfContents, toc.go) goldmark renderer to always
enable the Strikethrough and Emoji extensions. This allows handling
ast.KindStrikethrough and ast.KindEmoji AST nodes when rendering the TOC.

Fixes #7169
Fixes #11783
Fixes #12022
2024-03-07 14:08:29 +01:00
razonyang 1f48b717c7 Add hugo.IsMultiHost 2024-03-07 14:04:41 +01:00
Andreas Deininger ada3fceea8 docs: Fix hyphens and grammar in synopsis of command 'hugo server' 2024-03-07 14:03:49 +01:00
Joe Mooring 05e23bd55c resources/images: Retain newlines with text overlays
Closes #12206
2024-03-07 09:52:30 +01:00
Bjørn Erik Pedersen 7afac3f1ad Don't auto-create empty sections for nested taxonomies
Fixes #12188
2024-03-07 09:50:50 +01:00
Bjørn Erik Pedersen a4b17470a8 Fix resource name in resources.ByType
Fixes #12190
2024-03-07 09:50:50 +01:00
Bjørn Erik Pedersen 4d5e173cf8 Fix global resource isn't published when using an uncommon code construct
Fixes #12190
2024-03-07 09:50:50 +01:00
Bjørn Erik Pedersen 4271b6be0f Fix section page resource not published if resource filename partially matches content file name
Fixes #12198
2024-03-07 09:50:50 +01:00
Bjørn Erik Pedersen 0567a3e6f1 Fix taxonomy kind template lookup issue
Fixes #12193
2024-03-07 09:50:50 +01:00
Joe Mooring 632ad74fc5 tpl/tplimpl: Honor markdown attributes in embedded image render hook
Fixes #12203
2024-03-07 08:21:58 +01:00
hugoreleaser b1de03f398 releaser: Prepare repository for 0.124.0-DEV
[ci skip]
2024-03-01 16:30:33 +00:00
hugoreleaser 312735366b releaser: Bump versions for release of 0.123.7
[ci skip]
2024-03-01 16:16:06 +00:00
Bjørn Erik Pedersen 2b2f2b75ef hugofs: Fix vertical mount merge issue
Fixes #12175
2024-03-01 17:10:13 +01:00
Bjørn Erik Pedersen 0d6e593ffb Fix and add integration test for the Bootstrap SCSS module for both Dart Sass and Libsass
This fixes the reverse filesystem lookup (absolute filename to path relative to the composite filesystem).

The old logic had some assumptions about the locality of the actual files that didn't work in more complex scenarios.

This commit now also adds the popular Bootstrap SCSS Hugo module to the CI build (both for libsass and dartsass transpiler), so we can hopefully avoid similar future breakage.

Fixes #12178
2024-03-01 14:18:52 +01:00
Bjørn Erik Pedersen 7023cf0f07 Fix resources.GetMatch, resources.Match, and resources.ByType to they don't normalize permalinks
Fixes #12182
2024-03-01 13:34:05 +01:00
Bjørn Erik Pedersen 9dfa9e70e6 Make sure that sitemaps gets generated even if there is a content bundle with the same path
Fixes #12183
2024-03-01 13:34:05 +01:00
Joe Mooring 3f217fd664 resources/page: Make Taxonomy.Get and Taxonomy.Count case-insensitive
Closes #12177
2024-03-01 12:09:00 +01:00
hugoreleaser 92684f9a26 releaser: Bump versions for release of 0.123.6
[ci skip]
2024-02-28 18:29:40 +00:00
Bjørn Erik Pedersen fce8d82b79 Fix panic when cascading headless from site config to section that does not have an _index.md file
Fixes #12172
2024-02-28 19:26:55 +01:00
Bjørn Erik Pedersen 4a502f7eb4 Fix assets vs data issue
And possibly some other related file mount issues.

Fixes #12133
2024-02-28 19:26:55 +01:00
Bjørn Erik Pedersen be1dbba0f7 Fix draft for non-default content when content in default language does not exist
Fixes #12132
2024-02-28 19:26:55 +01:00
hugoreleaser 1007bcdf49 releaser: Prepare repository for 0.124.0-DEV
[ci skip]
2024-02-28 16:01:41 +00:00
hugoreleaser 1904ba40e8 releaser: Bump versions for release of 0.123.5
[ci skip]
2024-02-28 15:47:09 +00:00
Bjørn Erik Pedersen 6a8b25e291 Fix .Page.Pages with similary named sections
Fixes #12169
2024-02-28 16:43:19 +01:00
Bjørn Erik Pedersen a322282e70 Fix single mount rename panic
Fixes #12141
2024-02-28 13:38:12 +01:00
Bjørn Erik Pedersen 6bc0d745a5 Fix multihost processed image not copied to non-default content languages
Fixes #12163
2024-02-27 10:43:23 +01:00
dependabot[bot] 218690328c build(deps): bump github.com/bep/overlayfs from 0.9.1 to 0.9.2
Bumps [github.com/bep/overlayfs](https://github.com/bep/overlayfs) from 0.9.1 to 0.9.2.
- [Release notes](https://github.com/bep/overlayfs/releases)
- [Commits](https://github.com/bep/overlayfs/compare/v0.9.1...v0.9.2)

---
updated-dependencies:
- dependency-name: github.com/bep/overlayfs
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-27 09:59:15 +01:00
hugoreleaser 673c634131 releaser: Prepare repository for 0.124.0-DEV
[ci skip]
2024-02-26 16:46:48 +00:00
hugoreleaser 21a41003c4 releaser: Bump versions for release of 0.123.4
[ci skip]
2024-02-26 16:33:05 +00:00
Bjørn Erik Pedersen 1736ef7459 Fix cascade-pattern-with-extension for cascade in site config
Also clean up the log handling in the integration tester, most notably lost logs during the config loading.

Fixes #12151
2024-02-26 17:29:37 +01:00
dependabot[bot] d4be1643a0 build(deps): bump github.com/spf13/fsync from 0.10.0 to 0.10.1
Bumps [github.com/spf13/fsync](https://github.com/spf13/fsync) from 0.10.0 to 0.10.1.
- [Release notes](https://github.com/spf13/fsync/releases)
- [Commits](https://github.com/spf13/fsync/compare/v0.10.0...v0.10.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/fsync
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-26 17:08:34 +01:00
Bjørn Erik Pedersen f27e578718 Fix term template lookup when its backed by a content file
Closes #12146
2024-02-25 18:28:16 +01:00
Bjørn Erik Pedersen d310595a2b resource: Revert the normalization of Resource.Name
Which means that .Name now returns the same as it did in 0.122.0.

Closes #12142
2024-02-25 14:57:29 +01:00
Bjørn Erik Pedersen 049dd1d7e0 Fix panic for disableKinds page for content resources
Fixes #12144
2024-02-25 11:38:52 +01:00
Bjørn Erik Pedersen 189b72331e tocss: Fix the import resolving from absolute to relative assets paths
Fixes #12137
2024-02-24 16:41:18 +01:00
Bjørn Erik Pedersen b2b7bfdd3a Maek Resoyrce.Key as (mostly) internal 2024-02-24 15:17:15 +01:00
Joe Mooring 80e69344da config/allconfig: Fix typo
Closes #12134
2024-02-23 12:40:15 -08:00
hugoreleaser 7508a16470 releaser: Prepare repository for 0.124.0-DEV
[ci skip]
2024-02-23 17:22:38 +00:00
125 changed files with 2627 additions and 731 deletions
+2 -2
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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)
}
+18
View File
@@ -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.
+77
View File
@@ -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",
)
}
+13 -3
View File
@@ -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
}
+2 -2
View File
@@ -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: "",
}
+7 -7
View File
@@ -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, "/") {
+8
View File
@@ -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
+24 -5
View File
@@ -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)
}
+15 -7
View File
@@ -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
},
},
+2 -2
View File
@@ -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
}
+1 -1
View File
@@ -189,7 +189,7 @@ func (l configLoader) applyDefaultConfig() error {
"menus": maps.Params{},
"disableLiveReload": false,
"pluralizeListTitles": true,
"CapitalizeListTitles": true,
"capitalizeListTitles": true,
"forceSyncStatic": false,
"footnoteAnchorPrefix": "",
"footnoteReturnLinkContents": "",
+1 -1
View File
@@ -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" .) }}
*/}}
+1 -1
View File
@@ -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
}
+1
View File
@@ -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
+8 -7
View File
@@ -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
+8 -7
View File
@@ -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
+7 -6
View File
@@ -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
+6 -5
View File
@@ -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
+6 -5
View File
@@ -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
+6 -5
View File
@@ -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
+7 -6
View File
@@ -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
+9 -8
View File
@@ -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
+47 -46
View File
@@ -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
+40
View File
@@ -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
+12 -12
View File
@@ -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
+26 -26
View File
@@ -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=
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -1,4 +1,4 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -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) {
+7 -2
View File
@@ -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"},
+92
View File
@@ -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
View File
@@ -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)
}
+4
View File
@@ -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
View File
@@ -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] {
+32
View File
@@ -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)
}
+18
View File
@@ -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", "||")
}
+25 -11
View File
@@ -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,
}
}
+90 -1
View File
@@ -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)
+5 -1
View File
@@ -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
View File
@@ -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()))
}
+48 -2
View File
@@ -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 --
+50 -16
View File
@@ -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
}
+21
View File
@@ -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
}
+5 -5
View File
@@ -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
}
+28 -5
View File
@@ -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)
}
+2
View File
@@ -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 {
+9 -4
View File
@@ -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
View File
@@ -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 := `
+4 -4
View File
@@ -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.|")
}
+30
View File
@@ -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/|$")
}
+1 -1
View File
@@ -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()))
}
+18
View File
@@ -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()
}
+257
View File
@@ -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)
}
+115
View File
@@ -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
View File
@@ -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
View File
@@ -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 {
+2 -2
View File
@@ -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
}
+23
View File
@@ -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
View File
@@ -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>")
}
+85
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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")
+1 -1
View File
@@ -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 -6
View File
@@ -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,
},
))
}
+7 -1
View File
@@ -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
+6 -8
View File
@@ -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 &#x1f40d; emoji</a></li>
// `)
b.AssertFileContent("public/p7/index.html", `
<li><a href="#a-snake-emoji">A &#x1f40d; emoji</a></li>
`)
}
+2 -2
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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.
+10 -1
View File
@@ -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
+12 -8
View File
@@ -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)
}
}
+2 -2
View File
@@ -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
}
+3
View File
@@ -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
+7 -3
View File
@@ -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
+13 -3
View File
@@ -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)
+2
View File
@@ -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
}
+10 -6
View File
@@ -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 &#43;0000 GMT
Lastmod: 2024-03-13 06:00:00 &#43;0000 GMT
Eq: true
`)
}
+26 -18
View File
@@ -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 ""
}
+3 -2
View File
@@ -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",
)
}
+2
View File
@@ -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
}
+8 -8
View File
@@ -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 {
+16 -17
View File
@@ -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)
}
}
+5 -7
View File
@@ -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
+4 -4
View File
@@ -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.
+2 -2
View File
@@ -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&#39;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