Compare commits

...

61 Commits

Author SHA1 Message Date
hugoreleaser 10352335e0 releaser: Prepare repository for 0.156.0-DEV
[ci skip]
2026-01-30 10:32:56 +00:00
hugoreleaser f23576f2fb releaser: Bump versions for release of 0.155.1
[ci skip]
2026-01-30 10:10:48 +00:00
Bjørn Erik Pedersen b5d43cdc17 Fix image DecodeConfig regression of WebP images from file cache
Fixes #14453
2026-01-29 22:53:13 +01:00
Bjørn Erik Pedersen 6ef8017f60 Remove go vet from check.sh
Too slow.
2026-01-29 15:45:21 +01:00
Bjørn Erik Pedersen d67925f5a1 Add ./check.sh script
Running `mage check` takes too long time for user (and agent) iterations. That will eventualy run on CI.

This is a faster version that:

* Checks gofmt.
* Runs go vet.
* Runs staticcheck.
* Runs unit tests without the race flag.

It assumes that Go is installed, but will check if staticcheck is available and install it if not with go install.

It takes an optional argument to specify packages to check, otherwise it checks all packages.

This commit also enable local runs of the image golden tests.
2026-01-29 14:24:54 +01:00
Bjørn Erik Pedersen 48dd4f469a Update AGENTS.md with debug printing note
Added note about debug printing and CI build failure.
2026-01-29 11:30:50 +01:00
Joe Mooring b1e1eede50 resources/images: Fix WebP useSharpYuv being ignored
Closes #14449
2026-01-29 10:50:37 +01:00
Joe Mooring f522a7282b tpl/tplimpl: Remove failing Twitter tests
In v0.141.0 we deprecated:
- privacy.twitter.simple in favor of privacy.x.simple
- privacy.twitter.disable in favor of privacy.x.disable

Use of those settings is now causing the integration test to fail.
2026-01-28 23:21:35 -08:00
hugoreleaser 6ac72dd3bf releaser: Prepare repository for 0.156.0-DEV
[ci skip]
2026-01-28 16:50:40 +00:00
hugoreleaser 74c0bbfc6a releaser: Bump versions for release of 0.155.0
[ci skip]
2026-01-28 16:28:23 +00:00
Bjørn Erik Pedersen e66a33d369 Revert "release: Support alpha, beta, and RC releases"
This reverts commit 663075920a.

Closes #14448
2026-01-28 16:54:57 +01:00
Jayson Wang 8a979d54d1 Fix data race when clearing cache in cachebusters 2026-01-28 16:50:57 +01:00
Bjørn Erik Pedersen fd49df8f7a resources/images: Fix comment for Quality field in ImageConfig 2026-01-28 08:41:23 +01:00
Bjørn Erik Pedersen c7b35c8704 Fix panic reported in discourse
Fixes #14441
2026-01-27 22:48:46 +01:00
Bjørn Erik Pedersen 5916b61be9 Remove disableDate and disableLatLong from MetaConfig
Since the meta config is new (and exif config is deprecated), remove
these options from MetaConfig. The fields filter controls which metadata
is extracted, including Date and GPS fields.

The existing exif config options are preserved for backward compatibility.

Closes #14437

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 21:47:32 +01:00
Bjørn Erik Pedersen 7eafef2269 internal/warpc: Make webp C defaults match the Go defaults
To avoid confusion.
2026-01-27 18:31:45 +01:00
Bjørn Erik Pedersen 00c4228f61 testscripts: Move server tests to own folder
See #14439
2026-01-27 18:27:47 +01:00
Bjørn Erik Pedersen 5f5b2f378f testing: Skip some slow tests when not running in CI
Fixes #14438
2026-01-27 15:38:49 +01:00
Joe Mooring b3ea2a5fab misc: Update image processing description in README.md 2026-01-27 05:01:15 -08:00
Joe Mooring 67b543549b docs: Update docs.yaml 2026-01-27 10:05:34 +01:00
Bjørn Erik Pedersen ef7a22a2ff magefile: Skip commands test when running mage check locally
They're flaky for some reason.
2026-01-27 09:05:01 +01:00
Bjørn Erik Pedersen 8dfcece805 Fix recently introduced partial rendering bug
Fixes #14433
2026-01-26 22:45:16 +01:00
Bjørn Erik Pedersen 9a6bfe2667 Remove -p 2 parallelism limit for local test runs
Only limit parallelism in CI where memory is constrained.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 21:40:52 +01:00
dependabot[bot] 7d45614c6d build(deps): bump golang.org/x/tools from 0.40.0 to 0.41.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.40.0 to 0.41.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.40.0...v0.41.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-26 11:01:34 +01:00
dependabot[bot] 7f12c0900d build(deps): bump github.com/olekukonko/tablewriter from 1.1.2 to 1.1.3
Bumps [github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter) from 1.1.2 to 1.1.3.
- [Release notes](https://github.com/olekukonko/tablewriter/releases)
- [Commits](https://github.com/olekukonko/tablewriter/compare/v1.1.2...v1.1.3)

---
updated-dependencies:
- dependency-name: github.com/olekukonko/tablewriter
  dependency-version: 1.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-26 11:00:53 +01:00
dependabot[bot] dab2c332dc build(deps): bump github.com/alecthomas/chroma/v2 from 2.23.0 to 2.23.1
Bumps [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) from 2.23.0 to 2.23.1.
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.23.0...v2.23.1)

---
updated-dependencies:
- dependency-name: github.com/alecthomas/chroma/v2
  dependency-version: 2.23.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-26 10:59:39 +01:00
Bjørn Erik Pedersen 5a64551a07 Make docs helper maxAge JSON output user friendly 2026-01-25 13:58:28 +01:00
Bjørn Erik Pedersen 0fc63fbf57 Add AGENTS.md and CLAUDE.md 2026-01-25 13:35:28 +01:00
dependabot[bot] 576d55d009 build(deps): bump github.com/aws/aws-sdk-go-v2/service/cloudfront
Bumps [github.com/aws/aws-sdk-go-v2/service/cloudfront](https://github.com/aws/aws-sdk-go-v2) from 1.58.3 to 1.59.0.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/changelog-template.json)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.58.3...service/s3/v1.59.0)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/service/cloudfront
  dependency-version: 1.59.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-24 20:06:28 +01:00
dependabot[bot] b9400b8a95 build(deps): bump golang.org/x/image from 0.34.0 to 0.35.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.34.0 to 0.35.0.
- [Commits](https://github.com/golang/image/compare/v0.34.0...v0.35.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-24 18:11:13 +01:00
dependabot[bot] ce0c7f42c1 build(deps): bump golang.org/x/mod from 0.31.0 to 0.32.0
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.31.0 to 0.32.0.
- [Commits](https://github.com/golang/mod/compare/v0.31.0...v0.32.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-24 17:00:01 +01:00
Simon Heimlicher f472dd48a7 tpl: Fix partial decorator panic when partial returns falsy
When {{ with partial "foo" }} returns a falsy value (false, nil, ""),
the with block is skipped but _popPartialDecorator was only called
inside the with block. This left an orphan entry on the decorator
stack, causing "partial decorator ID mismatch" panic on subsequent
partial calls.

Add _popPartialDecorator call to the else branch to ensure the
decorator stack is always balanced regardless of the partial's
return value.

Fixes #14419
2026-01-24 16:59:18 +01:00
Bjørn Erik Pedersen 50973e7272 Add == and != operators to range predicates
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 16:51:57 +01:00
Simon Heimlicher 48566b6f37 resources: Fix race condition in test helper
The fetchResourceForSpec function was reusing a single ReadSeekCloser
across multiple goroutines, causing a race condition when
TestImageTransformConcurrent runs with imageProcWorkers > 1.

This was exposed by commit e569dd59a ("Misc webp performance work")
which increased imageProcWorkers from 1 to 2.

Fix by creating a new reader on each call to the open function.

Fixes race condition causing segfault on Apple Silicon.

Co-authored-by: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
2026-01-24 15:41:29 +01:00
Bjørn Erik Pedersen b441472b69 Add modulequeries file cache for module version queries
This adds a new file cache named 'modulequeries' with a 24h maxAge
that caches JSON responses from 'go mod download -json' when querying
module versions with constraints (e.g., version = "<v3.0.0").

On subsequent builds, if the cached JSON exists and the module
directory it references is non-empty, the cached result is used
directly, skipping the costly VCS interaction entirely.

Fixes #14417
2026-01-24 14:25:34 +01:00
Bjørn Erik Pedersen ac2c3fb20f Allow v1,v2 etc. style version names while still supporting full semver in queries
Fixes #14414
2026-01-23 18:53:32 +01:00
Bjørn Erik Pedersen ee91c707ee Make Page.Aliases more useful in multidimensional setups (note)
There are 2 fundamental changes related to alias handling in this PR:

1. the `Page.Aliases`` method returns a ready-to-use list of aliases, not the raw input data. This means that it's prefixed with correct dimension values (e.g. language code) and output format paths, and ready to use in e.g. Netlify's _redirects file.
2. We only render aliases for output formats that is both defined as `IsHTML` and `Permalinkable`.

See #14402
2026-01-23 18:52:35 +01:00
Bjørn Erik Pedersen 11f7f39913 Fix cascade draft panic
Fixes #14409
Fixes #14412

Co-authored-by: Joe Mooring <joe.mooring@veriphor.com>
2026-01-23 16:08:29 +01:00
Bjørn Erik Pedersen 192e3c453e Add range matchers for site matrix vector store filtering
Fixes #14359
2026-01-22 12:03:02 +01:00
Bjørn Erik Pedersen e569dd59a0 Misc webp performance work
*- Dynamic pool sizing for WebP and Katex based on CPU count
- WebP decode: use RGB (3 bytes) for opaque images instead of RGBA (4 bytes)
- Add new imaging.config with new encoder method option; this is default 4 (what we had before), if have tested with value 2 for a 30% speedup on my MacBook, but that does sacrifice quality in some cases (quality/speed trade-off 0-6)
- Track hasAlpha in decode response to enable RGB optimization
- Scale image processing workers (1->2) on high-core machines
- Add WebP benchmark test

On my MacBook Pro M1 Pro, I now get these numbers compared to Hugo v0.152.0:

```
goos: darwin
goarch: arm64
pkg: github.com/gohugoio/hugo/internal/warpc
cpu: Apple M1 Pro
        │ cmp152.bench │         fix-webpbench.bench         │
        │    sec/op    │    sec/op     vs base               │
Webp-10   162.6m ± 32%   145.3m ± 21%  -10.60% (p=0.026 n=6)

        │ cmp152.bench │          fix-webpbench.bench          │
        │     B/op     │     B/op       vs base                │
Webp-10   41.69Mi ± 0%   123.79Mi ± 0%  +196.92% (p=0.002 n=6)

        │ cmp152.bench │         fix-webpbench.bench         │
        │  allocs/op   │  allocs/op    vs base               │
Webp-10    9.963k ± 3%   18.797k ± 2%  +88.67% (p=0.002 n=6)
```

For general image resize benchmarks compared to Hugo v0.152.0 (note that that's not the result of this branch):

```bash
goos: darwin
goarch: arm64
pkg: github.com/gohugoio/hugo/resources/images
cpu: Apple M1 Pro
               │ cmp152.bench │         fix-webpbench.bench         │
               │    sec/op    │    sec/op     vs base               │
ImageResize-10   84.12m ± ∞ ¹   40.70m ± ∞ ¹  -51.61% (p=0.029 n=4)
¹ need >= 6 samples for confidence interval at level 0.95

               │ cmp152.bench  │         fix-webpbench.bench         │
               │     B/op      │     B/op       vs base              │
ImageResize-10   32.37Mi ± ∞ ¹   29.25Mi ± ∞ ¹  -9.64% (p=0.029 n=4)
¹ need >= 6 samples for confidence interval at level 0.95

               │ cmp152.bench │        fix-webpbench.bench         │
               │  allocs/op   │  allocs/op    vs base              │
ImageResize-10   11.34k ± ∞ ¹   12.03k ± ∞ ¹  +6.07% (p=0.029 n=4)
¹ need >= 6 samples for confidence interval at level 0.95
```

Closes #14370
2026-01-21 10:44:22 +01:00
Joe Mooring 5ba03bf657 hugolib: Fix multilingual alias generation
Closes #14388
2026-01-21 10:34:32 +01:00
Bjørn Erik Pedersen c1b2e58bfb Fix file mount specifity issue within the same module
Fixes #14405
2026-01-20 13:15:33 +01:00
Bjørn Erik Pedersen 77214117ae deps: Upgrade github.com/gohugoio/gift v0.1.0 => v0.2.0 2026-01-20 09:33:30 +01:00
Bjørn Erik Pedersen 1a94731669 Move from github.com/disintegration/gift to github.com/gohugoio/gift
With some performance improvements upstream, there's improvements in memory usage. Note that the benchmark below is full site builds with image processing:

```
goos: darwin
goarch: arm64
pkg: github.com/gohugoio/hugo/resources/images
cpu: Apple M1 Pro
               │ benchcmp.bench │  feat-forkgift-14397.bench   │
               │     sec/op     │   sec/op     vs base         │
ImageResize-10      74.60m ± 4%   73.75m ± 4%  ~ (p=0.093 n=6)

               │ benchcmp.bench │     feat-forkgift-14397.bench      │
               │      B/op      │     B/op      vs base              │
ImageResize-10     32.38Mi ± 1%   29.31Mi ± 1%  -9.49% (p=0.002 n=6)

               │ benchcmp.bench │     feat-forkgift-14397.bench     │
               │   allocs/op    │  allocs/op   vs base              │
ImageResize-10      12.53k ± 1%   12.05k ± 1%  -3.82% (p=0.002 n=6)
```

Closes #14397
2026-01-19 22:18:45 +01:00
dependabot[bot] 18784711b5 build(deps): bump github.com/alecthomas/chroma/v2 from 2.22.0 to 2.23.0
Bumps [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) from 2.22.0 to 2.23.0.
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.22.0...v2.23.0)

---
updated-dependencies:
- dependency-name: github.com/alecthomas/chroma/v2
  dependency-version: 2.23.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 11:12:44 +01:00
Shravani Totre 2c611091fd warpc: Fix typed nil return in Start
Fixes a panic in Close() when initialization fails by ensuring Start returns a nil interface instead of a typed nil.

See #14372
2026-01-18 14:54:31 +01:00
Joe Mooring 8e9e04e395 resources/images: Stabilize order of valid sources in error message
Closes #14390
2026-01-18 10:27:40 +01:00
Joe Mooring 32334d098e hugolib: Fix relative alias generation
Closes #14381
2026-01-17 10:33:44 +01:00
Bjørn Erik Pedersen 8e2e60dd45 Add XMP and IPTC image metadata support
Rename resources/images/exif to resources/images/meta and extend
metadata support to include XMP and IPTC fields. Deprecate .Exif
in favor of .Meta. Update imagemeta dependency and add configuration
options for metadata extraction.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Closes #13146
2026-01-16 20:06:44 +01:00
Bjørn Erik Pedersen 2bc54df622 output: Add TestCanonical integration test 2026-01-16 19:08:54 +01:00
Bjørn Erik Pedersen 608ed09a0a Rename common/maps to common/hmaps (#14384)
Go's stdlid now has a maps package, which is very useful. We have been using imports on the form `xmaps "maps"`, but auto import tools doesn't handle this, which is annoying.

I have been thinking about this for some time, but have been holding back because of all the import changes. However, now is a good time to do this, with very little unmerged code.
2026-01-16 11:47:46 +01:00
dependabot[bot] 94f1ede3aa build(deps): bump golang.org/x/net from 0.48.0 to 0.49.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.48.0 to 0.49.0.
- [Commits](https://github.com/golang/net/compare/v0.48.0...v0.49.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-15 11:54:51 +01:00
dependabot[bot] ab374e3d22 build(deps): bump github.com/bep/lazycache from 0.8.0 to 0.8.1
Bumps [github.com/bep/lazycache](https://github.com/bep/lazycache) from 0.8.0 to 0.8.1.
- [Release notes](https://github.com/bep/lazycache/releases)
- [Commits](https://github.com/bep/lazycache/compare/v0.8.0...v0.8.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-14 17:23:07 +01:00
Joe Mooring 1a9133075c deps: Upgrade github.com/alecthomas/chroma v2.21.1 => v2.22.0
Closes #14368
2026-01-13 23:03:16 +01:00
Bjørn Erik Pedersen d36a8f5601 Decode webp.ImageConfig natively
This has 2 main benefits:

1. When you only need width/height, we don't need to spin up a WASM runtime.
2. When all processed images are cached in the file cache, we don't need to spin up a WASM runtime.

The above is only really visible for smaller sites, but it's annoying to potentially have a site building in 15ms take e.g. 200ms because of WASM startup time.

Fixes #14371
2026-01-13 17:58:34 +01:00
hugoreleaser 487b8e3af3 releaser: Prepare repository for 0.155.0-DEV
[ci skip]
2026-01-11 21:14:41 +00:00
hugoreleaser a6f99cca22 releaser: Bump versions for release of 0.154.5
[ci skip]
2026-01-11 20:53:23 +00:00
Bjørn Erik Pedersen a775488304 Fix some default site redirect woes
* The fix for #14357 yesterday sadly had the assumption that default language/version/role always was the first site in the sites matrix. This is common, but not always true.
* Also, that fix forgot to add a redirect the other way when `defaultContentLanguageInSubdir` was disabled, e.g. from `/en/` to `/`.
* This commit also renames config option `DisableDefaultDimensionRedirect` to `DisableDefaultSiteRedirect`. This is stricly a breaking change, but it's only been out for a day, and the old name didn't make much sense.

Fixes #14361
2026-01-11 21:50:10 +01:00
Rohan Hasabe 66ba63cd5f hugolib: Fix newly created shortcodes not found during server rebuild
When a new shortcode file was added during `hugo server`, the shortcode
was detected but not properly registered in the template cache. This caused
subsequent content edits using the shortcode to fail with "template for
shortcode not found" until the server was restarted.

The issue was that when a shortcode was added, only a glob identity for
dependent content was added to the changes list, but not the shortcode
file's own pathInfo. This meant RefreshFiles filtered it out and never
inserted it into the shortcodesByName cache.

The fix adds the shortcode file's pathInfo to changes (in addition to the
glob for dependent content), and corrects the glob pattern from
`shortcodes/...` to `/_shortcodes/...` to match the actual path format.

Fixes #14207
2026-01-11 19:33:00 +01:00
Joe Mooring eb06a3cd0c tpl/tplimpl: Remove trailing slash from void elements 2026-01-11 15:17:19 +01:00
hugoreleaser a798e3e724 releaser: Prepare repository for 0.155.0-DEV
[ci skip]
2026-01-10 15:48:05 +00:00
223 changed files with 4347 additions and 1712 deletions
+6 -5
View File
@@ -106,7 +106,10 @@ jobs:
run: go install honnef.co/go/tools/cmd/staticcheck@latest
- if: matrix.os == 'ubuntu-latest'
name: Run staticcheck
run: staticcheck ./...
run: |
export STATICCHECK_CACHE="${{ runner.temp }}/staticcheck"
staticcheck ./...
rm -rf ${{ runner.temp }}/staticcheck
- if: matrix.os != 'windows-latest'
name: Check
run: |
@@ -118,16 +121,14 @@ jobs:
# See issue #11052. We limit the build to regular test (no -race flag) on Windows for now.
name: Test
run: |
mage -v test;
mage -v test
env:
HUGO_BUILD_TAGS: extended,withdeploy
- name: Build tags
run: |
go install -tags extended
- if: matrix.os == 'ubuntu-latest'
name: Build for dragonfly
run: |
go install
go clean -i -cache
env:
GOARCH: amd64
GOOS: dragonfly
+2 -1
View File
@@ -3,4 +3,5 @@
imports.*
dist/
public/
.DS_Store
.DS_Store
cache/filecache/_gen/
+1
View File
@@ -0,0 +1 @@
tpl/tplimpl/embedded/templates/**
+14
View File
@@ -0,0 +1,14 @@
* Brevity is good.
* Assume that the maintainers and readers of the code you write are Go experts:
* Don't use comments to explain the obvious.
* Use self-explanatory variable and function names.
* Use short variable names when the context is clear.
* If you need to add temporary debug printing, use `hdebug.Printf`.[^1]
* Never export symbols that's not needed outside of the package.
* Avoid global state at (almost) all cost.
* This is a project with a long history; assume that a similiar problem has been solved before, look hard for helper functions before creating new ones.
* Use `./check.sh ./somepackage/...` when iterating.
* Use `./check.sh` when you're done.
[^1]: CI build fail if you forget to remove the debug printing.
+1
View File
@@ -0,0 +1 @@
@AGENTS.md
+1 -1
View File
@@ -77,7 +77,7 @@ To make the contribution process as seamless as possible, we ask for the followi
* Run `go fmt`.
* Add documentation if you are adding new features or changing functionality. The docs site lives in `/docs`.
* Squash your commits into a single commit. `git rebase -i`. Its okay to force update your pull request with `git push -f`.
* Ensure that `mage check` succeeds. [Travis CI](https://travis-ci.org/gohugoio/hugo) (Windows, Linux and macOS) will fail the build if `mage check` fails.
* Ensure that `./check.sh` succeeds. Note that some tests are skipped when running locally, some because they are slow. To run these locally, do `CI_LOCAL=true ./check.sh ./somepackage/...`.
* Follow the **Git Commit Message Guidelines** below.
## AI Assistance Notice
+1 -1
View File
@@ -52,7 +52,7 @@ Use Hugo's embedded web server during development to instantly see changes to co
Hugo's fast asset pipelines include:
- Image processing &ndash; Convert, resize, crop, rotate, adjust colors, apply filters, overlay text and images, and extract EXIF data
- Image processing &ndash; Convert, resize, crop, rotate, adjust colors, apply filters, overlay text and images, and extract metadata
- JavaScript bundling &ndash; Transpile TypeScript and JSX to JavaScript, bundle, tree shake, minify, create source maps, and perform SRI hashing.
- Sass processing &ndash; Transpile Sass to CSS, bundle, tree shake, minify, create source maps, perform SRI hashing, and integrate with PostCSS
- Tailwind CSS processing &ndash; Compile Tailwind CSS utility classes into standard CSS, bundle, tree shake, optimize, minify, perform SRI hashing, and integrate with PostCSS
+55 -11
View File
@@ -16,6 +16,7 @@ package filecache
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"path/filepath"
@@ -54,7 +55,11 @@ type Cache struct {
nlocker *lockTracker
isResourceDir bool
baseDir string
initOnce sync.Once
isInited bool
initErr error
}
@@ -109,9 +114,14 @@ func (l *lockedFile) Close() error {
}
func (c *Cache) init() error {
if c == nil {
panic("cache is nil")
}
c.initOnce.Do(func() {
c.isInited = true
// Create the base dir if it does not exist.
if err := c.Fs.MkdirAll("", 0o777); err != nil && !os.IsExist(err) {
err = fmt.Errorf("failled to create base cache directory: %s", err)
c.initErr = err
}
})
@@ -286,8 +296,32 @@ func (c *Cache) GetOrCreateBytes(id string, create func() ([]byte, error)) (Item
return info, b, nil
}
// SetBytes sets the file content with the given id in the cache.
func (c *Cache) SetBytes(id string, data []byte) error {
if err := c.init(); err != nil {
return err
}
id = cleanID(id)
c.nlocker.Lock(id)
defer c.nlocker.Unlock(id)
if c.maxAge == 0 {
// No caching.
return nil
}
return c.writeReader(id, bytes.NewReader(data))
}
// GetBytes gets the file content with the given id from the cache, nil if none found.
func (c *Cache) GetBytes(id string) (ItemInfo, []byte, error) {
func (c *Cache) GetBytes(id string) ([]byte, error) {
_, b, err := c.GetItemBytes(id)
return b, err
}
// GetItemBytes gets the ItemInfo and file content with the given id from the cache, nil if none found.
func (c *Cache) GetItemBytes(id string) (ItemInfo, []byte, error) {
if err := c.init(); err != nil {
return ItemInfo{}, nil, err
}
@@ -417,6 +451,17 @@ func (c *Cache) GetString(id string) string {
// Caches is a named set of caches.
type Caches map[string]*Cache
func (f Caches) SetResourceFs(fs afero.Fs) {
for _, c := range f {
if c.isResourceDir {
if c.isInited {
panic("cannot set resource fs after init")
}
c.Fs = hugofs.NewBasePathFs(fs, c.baseDir)
}
}
}
// Get gets a named cache, nil if none found.
func (f Caches) Get(name string) *Cache {
return f[strings.ToLower(name)]
@@ -424,24 +469,19 @@ func (f Caches) Get(name string) *Cache {
// NewCaches creates a new set of file caches from the given
// configuration.
func NewCaches(p *helpers.PathSpec) (Caches, error) {
dcfg := p.Cfg.GetConfigSection("caches").(Configs)
fs := p.Fs.Source
func NewCaches(dcfg Configs, sourceFs afero.Fs) (Caches, error) {
// dcfg := p.Cfg.GetConfigSection("caches").(Configs)
fs := sourceFs
m := make(Caches)
for k, v := range dcfg {
var cfs afero.Fs
if v.IsResourceDir {
cfs = p.BaseFs.ResourcesCache
cfs = nil // Set later. TODO(bep) this needs to be cleanded up.
} else {
cfs = fs
}
if cfs == nil {
panic("nil fs")
}
baseDir := v.DirCompiled
bfs := hugofs.NewBasePathFs(cfs, baseDir)
@@ -451,7 +491,11 @@ func NewCaches(p *helpers.PathSpec) (Caches, error) {
pruneAllRootDir = "pkg"
}
m[k] = NewCache(bfs, v.MaxAge, pruneAllRootDir)
c := NewCache(bfs, v.MaxAge, pruneAllRootDir)
c.isResourceDir = v.IsResourceDir
c.baseDir = baseDir
m[k] = c
}
return m, nil
+42 -9
View File
@@ -15,6 +15,7 @@
package filecache
import (
"encoding/json"
"errors"
"fmt"
"path"
@@ -22,7 +23,7 @@ import (
"strings"
"time"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/config"
"github.com/mitchellh/mapstructure"
@@ -40,13 +41,14 @@ var defaultCacheConfig = FileCacheConfig{
}
const (
CacheKeyGetJSON = "getjson"
CacheKeyGetCSV = "getcsv"
CacheKeyImages = "images"
CacheKeyAssets = "assets"
CacheKeyModules = "modules"
CacheKeyGetResource = "getresource"
CacheKeyMisc = "misc"
CacheKeyGetJSON = "getjson"
CacheKeyGetCSV = "getcsv"
CacheKeyImages = "images"
CacheKeyAssets = "assets"
CacheKeyModules = "modules"
CacheKeyModuleQueries = "modulequeries"
CacheKeyGetResource = "getresource"
CacheKeyMisc = "misc"
)
type Configs map[string]FileCacheConfig
@@ -68,6 +70,10 @@ var defaultCacheConfigs = Configs{
MaxAge: -1,
Dir: ":cacheDir/modules",
},
CacheKeyModuleQueries: {
MaxAge: 24 * time.Hour,
Dir: ":cacheDir/modules",
},
CacheKeyGetJSON: defaultCacheConfig,
CacheKeyGetCSV: defaultCacheConfig,
CacheKeyImages: {
@@ -107,6 +113,23 @@ type FileCacheConfig struct {
IsResourceDir bool `json:"-"`
}
// MarshalJSON marshals FileCacheConfig to JSON with MaxAge as a human-readable string.
func (c FileCacheConfig) MarshalJSON() ([]byte, error) {
var maxAge any
if c.MaxAge == -1 {
maxAge = -1
} else {
maxAge = strings.TrimSuffix(c.MaxAge.String(), "0m0s")
}
return json.Marshal(&struct {
MaxAge any `json:"maxAge"`
Dir string `json:"dir"`
}{
MaxAge: maxAge,
Dir: c.Dir,
})
}
// GetJSONCache gets the file cache for getJSON.
func (f Caches) GetJSONCache() *Cache {
return f[CacheKeyGetJSON]
@@ -127,6 +150,16 @@ func (f Caches) ModulesCache() *Cache {
return f[CacheKeyModules]
}
// ModuleQueriesCache gets the file cache for Hugo Module version queries.
// Returns nil if not found.
func (f Caches) ModuleQueriesCache() *Cache {
c, ok := f[CacheKeyModuleQueries]
if !ok {
panic("module queries cache not set")
}
return c
}
// AssetsCache gets the file cache for assets (processed resources, SCSS etc.).
func (f Caches) AssetsCache() *Cache {
return f[CacheKeyAssets]
@@ -154,7 +187,7 @@ func DecodeConfig(fs afero.Fs, bcfg config.BaseConfig, m map[string]any) (Config
_, isOsFs := fs.(*afero.OsFs)
for k, v := range m {
if _, ok := v.(maps.Params); !ok {
if _, ok := v.(hmaps.Params); !ok {
continue
}
cc := defaultCacheConfig
+30 -3
View File
@@ -14,6 +14,7 @@
package filecache_test
import (
"encoding/json"
"path/filepath"
"runtime"
"testing"
@@ -59,7 +60,7 @@ dir = "/path/to/c4"
c.Assert(err, qt.IsNil)
fs := afero.NewMemMapFs()
decoded := testconfig.GetTestConfigs(fs, cfg).Base.Caches
c.Assert(len(decoded), qt.Equals, 7)
c.Assert(len(decoded), qt.Equals, 8)
c2 := decoded["getcsv"]
c.Assert(c2.MaxAge.String(), qt.Equals, "11h0m0s")
@@ -106,7 +107,7 @@ dir = "/path/to/c4"
c.Assert(err, qt.IsNil)
fs := afero.NewMemMapFs()
decoded := testconfig.GetTestConfigs(fs, cfg).Base.Caches
c.Assert(len(decoded), qt.Equals, 7)
c.Assert(len(decoded), qt.Equals, 8)
for _, v := range decoded {
c.Assert(v.MaxAge, qt.Equals, time.Duration(0))
@@ -129,7 +130,7 @@ func TestDecodeConfigDefault(t *testing.T) {
fs := afero.NewMemMapFs()
decoded := testconfig.GetTestConfigs(fs, cfg).Base.Caches
c.Assert(len(decoded), qt.Equals, 7)
c.Assert(len(decoded), qt.Equals, 8)
imgConfig := decoded[filecache.CacheKeyImages]
jsonConfig := decoded[filecache.CacheKeyGetJSON]
@@ -144,3 +145,29 @@ func TestDecodeConfigDefault(t *testing.T) {
c.Assert(imgConfig.IsResourceDir, qt.Equals, true)
c.Assert(jsonConfig.IsResourceDir, qt.Equals, false)
}
func TestFileCacheConfigMarshalJSON(t *testing.T) {
c := qt.New(t)
cfg := config.New()
cfg.Set("cacheDir", "/cache")
cfg.Set("workingDir", "/my/project")
fs := afero.NewMemMapFs()
decoded := testconfig.GetTestConfigs(fs, cfg).Base.Caches
moduleQueriesConfig := decoded[filecache.CacheKeyModuleQueries]
c.Assert(moduleQueriesConfig.MaxAge, qt.Equals, 24*time.Hour)
b, err := json.Marshal(moduleQueriesConfig)
c.Assert(err, qt.IsNil)
c.Assert(string(b), qt.Contains, `"maxAge":"24h"`)
c.Assert(string(b), qt.Not(qt.Contains), "86400000000000")
c.Assert(string(b), qt.Not(qt.Contains), "8.64e")
moduleQueriesConfig.MaxAge = -1
b, err = json.Marshal(moduleQueriesConfig)
c.Assert(err, qt.IsNil)
c.Assert(string(b), qt.Contains, `"maxAge":-1`)
}
+7 -3
View File
@@ -55,9 +55,12 @@ dir = ":resourceDir/_gen"
for _, name := range []string{filecache.CacheKeyGetCSV, filecache.CacheKeyGetJSON, filecache.CacheKeyAssets, filecache.CacheKeyImages} {
msg := qt.Commentf("cache: %s", name)
p := newPathsSpec(t, afero.NewMemMapFs(), configStr)
caches, err := filecache.NewCaches(p)
fs := afero.NewMemMapFs()
p := newPathsSpec(t, fs, configStr)
fileCachConfig := p.Cfg.GetConfigSection("caches").(filecache.Configs)
caches, err := filecache.NewCaches(fileCachConfig, fs)
c.Assert(err, qt.IsNil)
caches.SetResourceFs(fs)
cache := caches[name]
for i := range 10 {
id := fmt.Sprintf("i%d", i)
@@ -84,8 +87,9 @@ dir = ":resourceDir/_gen"
}
}
caches, err = filecache.NewCaches(p)
caches, err = filecache.NewCaches(fileCachConfig, fs)
c.Assert(err, qt.IsNil)
caches.SetResourceFs(fs)
cache = caches[name]
// Touch one and then prune.
cache.GetOrCreateBytes("i5", func() ([]byte, error) {
+10 -4
View File
@@ -18,6 +18,8 @@ import (
"fmt"
"io"
"strings"
"github.com/gohugoio/hugo/htesting"
"sync"
"testing"
"time"
@@ -78,9 +80,11 @@ dir = ":cacheDir/c"
configStr = strings.Replace(configStr, "\\", winPathSep, -1)
p := newPathsSpec(t, osfs, configStr)
fileCachConfig := p.Cfg.GetConfigSection("caches").(filecache.Configs)
caches, err := filecache.NewCaches(p)
caches, err := filecache.NewCaches(fileCachConfig, p.Fs.Source)
c.Assert(err, qt.IsNil)
caches.SetResourceFs(p.SourceFs)
cache := caches.Get("GetJSON")
c.Assert(cache, qt.Not(qt.IsNil))
@@ -149,7 +153,7 @@ dir = ":cacheDir/c"
r.Close()
c.Assert(string(b), qt.Equals, "Hugo is great!")
info, b, err = caches.ImageCache().GetBytes("mykey")
info, b, err = caches.ImageCache().GetItemBytes("mykey")
c.Assert(err, qt.IsNil)
c.Assert(info.Name, qt.Equals, "mykey")
c.Assert(string(b), qt.Equals, "Hugo is great!")
@@ -158,6 +162,7 @@ dir = ":cacheDir/c"
}
func TestFileCacheConcurrent(t *testing.T) {
htesting.SkipSlowTestUnlessCI(t)
t.Parallel()
c := qt.New(t)
@@ -179,9 +184,10 @@ dir = "/cache/c"
`
p := newPathsSpec(t, afero.NewMemMapFs(), configStr)
caches, err := filecache.NewCaches(p)
fileCachConfig := p.Cfg.GetConfigSection("caches").(filecache.Configs)
caches, err := filecache.NewCaches(fileCachConfig, p.Fs.Source)
c.Assert(err, qt.IsNil)
caches.SetResourceFs(p.Fs.Source)
const cacheName = "getjson"
Executable
+82
View File
@@ -0,0 +1,82 @@
#!/bin/bash
set -e
# Default to all packages if none specified
PACKAGES="${1:-./...}"
echo "==> Checking packages: $PACKAGES"
# Timing arrays
declare -a STEP_NAMES
declare -a STEP_TIMES
time_step() {
local name="$1"
shift
local start=$(date +%s.%N)
"$@"
local end=$(date +%s.%N)
local elapsed=$(echo "$end - $start" | bc)
STEP_NAMES+=("$name")
STEP_TIMES+=("$elapsed")
}
# Check gofmt
run_gofmt() {
echo "==> Running gofmt..."
# Convert package pattern to path (e.g., ./hugolib/... -> ./hugolib)
local path="${PACKAGES%/...}"
GOFMT_OUTPUT=$(gofmt -l "$path" 2>&1) || true
if [ -n "$GOFMT_OUTPUT" ]; then
echo "gofmt found issues in:"
echo "$GOFMT_OUTPUT"
exit 1
fi
echo " OK"
}
# Run staticcheck
run_staticcheck() {
# Check if staticcheck is installed, install if not
if ! command -v staticcheck &> /dev/null; then
echo "==> Installing staticcheck..."
go install honnef.co/go/tools/cmd/staticcheck@latest
fi
echo "==> Running staticcheck..."
staticcheck $PACKAGES
echo " OK"
}
# Run tests
run_tests() {
echo "==> Running tests..."
local output
if ! output=$(go test $PACKAGES 2>&1); then
echo "$output"
exit 1
fi
echo " OK"
}
# Run all steps with timing
TOTAL_START=$(date +%s.%N)
time_step "gofmt" run_gofmt
time_step "staticcheck" run_staticcheck
time_step "tests" run_tests
TOTAL_END=$(date +%s.%N)
TOTAL_ELAPSED=$(echo "$TOTAL_END - $TOTAL_START" | bc)
# Print timing summary
echo ""
echo "==> All checks passed!"
echo ""
echo "Timing summary:"
echo "---------------"
for i in "${!STEP_NAMES[@]}"; do
printf " %-15s %6.2fs\n" "${STEP_NAMES[$i]}" "${STEP_TIMES[$i]}"
done
echo "---------------"
printf " %-15s %6.2fs\n" "Total" "$TOTAL_ELAPSED"
+2 -2
View File
@@ -23,7 +23,7 @@ import (
"time"
"github.com/bep/simplecobra"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/config/allconfig"
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
"github.com/gohugoio/hugo/modules"
@@ -95,7 +95,7 @@ func (c *configCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, arg
if err := json.Unmarshal(buf.Bytes(), &m); err != nil {
return err
}
maps.ConvertFloat64WithNoDecimalsToInt(m)
hmaps.ConvertFloat64WithNoDecimalsToInt(m)
switch format {
case "yaml":
return parser.InterfaceToConfig(m, metadecoders.YAML, os.Stdout)
+2 -2
View File
@@ -31,11 +31,11 @@ import (
"github.com/bep/simplecobra"
"github.com/fsnotify/fsnotify"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstrings"
"github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/terminal"
"github.com/gohugoio/hugo/common/types"
@@ -1077,7 +1077,7 @@ func (c *hugoBuilder) loadConfig(cd *simplecobra.Commandeer, running bool) error
}
cfg.Set("environment", c.r.environment)
cfg.Set("internal", maps.Params{
cfg.Set("internal", hmaps.Params{
"running": running,
"watch": watch,
"verbose": c.r.isVerbose(),
+3 -3
View File
@@ -29,9 +29,9 @@ import (
"unicode"
"github.com/bep/simplecobra"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/parser"
@@ -190,7 +190,7 @@ func (c *importCommand) createSiteFromJekyll(jekyllRoot, targetDir string, jekyl
}
func (c *importCommand) convertJekyllContent(m any, content string) (string, error) {
metadata, _ := maps.ToStringMapE(m)
metadata, _ := hmaps.ToStringMapE(m)
lines := strings.Split(content, "\n")
var resultLines []string
@@ -246,7 +246,7 @@ func (c *importCommand) convertJekyllContent(m any, content string) (string, err
}
func (c *importCommand) convertJekyllMetaData(m any, postName string, postDate time.Time, draft bool) (any, error) {
metadata, err := maps.ToStringMapE(m)
metadata, err := hmaps.ToStringMapE(m)
if err != nil {
return nil, err
}
+1 -3
View File
@@ -28,14 +28,13 @@ func newReleaseCommand() simplecobra.Commander {
step int
skipPush bool
try bool
version string
)
return &simpleCommand{
name: "release",
short: "Release a new version of Hugo",
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
rel, err := releaser.New(skipPush, try, step, version)
rel, err := releaser.New(skipPush, try, step)
if err != nil {
return err
}
@@ -48,7 +47,6 @@ func newReleaseCommand() simplecobra.Commander {
cmd.PersistentFlags().BoolVarP(&skipPush, "skip-push", "", false, "skip pushing to remote")
cmd.PersistentFlags().BoolVarP(&try, "try", "", false, "no changes")
cmd.PersistentFlags().IntVarP(&step, "step", "", 0, "step to run (1: set new version 2: prepare next dev version)")
cmd.PersistentFlags().StringVarP(&version, "version", "", "", "version to release (derived from branch name if not set)")
_ = cmd.RegisterFlagCompletionFunc("step", cobra.FixedCompletions([]string{"1", "2"}, cobra.ShellCompDirectiveNoFileComp))
},
}
+5 -3
View File
@@ -87,11 +87,13 @@ func XXHashFromString(s string) (uint64, error) {
return h.Sum64(), nil
}
// XxHashFromStringHexEncoded calculates the xxHash for the given string
// XxHashFromStringHexEncoded calculates the xxHash for the given strings
// and returns the hash as a hex encoded string.
func XxHashFromStringHexEncoded(f string) string {
func XxHashFromStringHexEncoded(s ...string) string {
h := xxhash.New()
h.WriteString(f)
for _, f := range s {
h.WriteString(f)
}
hash := h.Sum(nil)
return hex.EncodeToString(hash)
}
+3 -3
View File
@@ -27,8 +27,8 @@ import (
"sync"
"github.com/bep/logg"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/security"
)
@@ -102,7 +102,7 @@ func New(cfg security.Config, workingDir string, log loggers.Logger) *Exec {
workingDir: workingDir,
infol: log.InfoCommand("exec"),
baseEnviron: baseEnviron,
newNPXRunnerCache: maps.NewCache[string, func(arg ...any) (Runner, error)](),
newNPXRunnerCache: hmaps.NewCache[string, func(arg ...any) (Runner, error)](),
}
}
@@ -121,7 +121,7 @@ type Exec struct {
// os.Environ filtered by the Exec.OsEnviron whitelist filter.
baseEnviron []string
newNPXRunnerCache *maps.Cache[string, func(arg ...any) (Runner, error)]
newNPXRunnerCache *hmaps.Cache[string, func(arg ...any) (Runner, error)]
npxInit sync.Once
npxAvailable bool
}
@@ -1,4 +1,4 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
// Copyright 2026 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.
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package maps
package hmaps
import (
"sync"
@@ -1,4 +1,4 @@
// Copyright 2025 The Hugo Authors. All rights reserved.
// Copyright 2026 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.
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package maps
package hmaps
import (
"testing"
+2 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2025 The Hugo Authors. All rights reserved.
// Copyright 2026 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.
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package maps
package hmaps
import (
"iter"
@@ -1,4 +1,4 @@
// Copyright 2025 The Hugo Authors. All rights reserved.
// Copyright 2026 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.
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package maps
package hmaps
import (
"testing"
+2 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
// Copyright 2026 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.
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package maps
package hmaps
import (
"fmt"
@@ -1,4 +1,4 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
// Copyright 2026 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.
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package maps
package hmaps
import (
"fmt"
@@ -1,4 +1,4 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
// Copyright 2026 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.
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package maps
package hmaps
import (
"slices"
@@ -1,4 +1,4 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
// Copyright 2026 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.
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package maps
package hmaps
import (
"testing"
@@ -1,4 +1,4 @@
// Copyright 2025 The Hugo Authors. All rights reserved.
// Copyright 2026 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.
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package maps
package hmaps
import (
"fmt"
@@ -1,4 +1,4 @@
// Copyright 2025 The Hugo Authors. All rights reserved.
// Copyright 2026 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.
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package maps
package hmaps
import (
"testing"
@@ -1,4 +1,4 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
// Copyright 2026 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.
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package maps
package hmaps
import (
"errors"
@@ -1,4 +1,4 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
// Copyright 2026 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.
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package maps
package hmaps
import (
"testing"
+2 -2
View File
@@ -20,8 +20,8 @@ import (
"sync"
"time"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/types"
)
@@ -355,7 +355,7 @@ func IsNil(v reflect.Value) bool {
var contextInterface = reflect.TypeOf((*context.Context)(nil)).Elem()
var isContextCache = maps.NewCache[reflect.Type, bool]()
var isContextCache = hmaps.NewCache[reflect.Type, bool]()
type k string
+3 -3
View File
@@ -19,7 +19,7 @@ import "github.com/gohugoio/hugo/common/version"
// This should be the only one.
var CurrentVersion = version.Version{
Major: 0,
Minor: 154,
PatchLevel: 4,
Suffix: "",
Minor: 156,
PatchLevel: 0,
Suffix: "-DEV",
}
+3 -3
View File
@@ -21,7 +21,7 @@ import (
"strings"
"sync"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
@@ -54,12 +54,12 @@ type PathParser struct {
// Below gets created on demand.
initOnce sync.Once
sitesMatrixCache *maps.Cache[string, sitesmatrix.VectorStore] // Maps language index to sites matrix vector store.
sitesMatrixCache *hmaps.Cache[string, sitesmatrix.VectorStore] // Maps language index to sites matrix vector store.
}
func (pp *PathParser) init() {
pp.initOnce.Do(func() {
pp.sitesMatrixCache = maps.NewCache[string, sitesmatrix.VectorStore]()
pp.sitesMatrixCache = hmaps.NewCache[string, sitesmatrix.VectorStore]()
})
}
+111 -2
View File
@@ -153,6 +153,35 @@ func (p PR[T]) FilterCopy(s []T) []T {
return result
}
const (
rangeOpNone = iota
rangeOpLT
rangeOpLTE
rangeOpGT
rangeOpGTE
rangeOpEQ
rangeOpNE
)
func cutRangeOp(s string) (op int, rest string) {
switch {
case strings.HasPrefix(s, ">= "):
return rangeOpGTE, s[3:]
case strings.HasPrefix(s, "<= "):
return rangeOpLTE, s[3:]
case strings.HasPrefix(s, "== "):
return rangeOpEQ, s[3:]
case strings.HasPrefix(s, "!= "):
return rangeOpNE, s[3:]
case strings.HasPrefix(s, "> "):
return rangeOpGT, s[2:]
case strings.HasPrefix(s, "< "):
return rangeOpLT, s[2:]
default:
return rangeOpNone, s
}
}
// NewStringPredicateFromGlobs creates a string predicate from the given glob patterns.
// A glob pattern starting with "!" is a negation pattern which will be ANDed with the rest.
func NewStringPredicateFromGlobs(patterns []string, getGlob func(pattern string) (glob.Glob, error)) (P[string], error) {
@@ -187,6 +216,86 @@ func NewStringPredicateFromGlobs(patterns []string, getGlob func(pattern string)
return p.BoolFunc(), nil
}
type IndexMatcher interface {
IndexMatch(match P[string]) (iter.Seq[int], error)
// NewIndexStringPredicateFromGlobsAndRanges creates an IndexString predicate from the given glob patterns and range patterns.
// A glob pattern starting with "!" is a negation pattern which will be ANDed with the rest.
// A range pattern is one of "> value", ">= value", "< value" or "<= value".
func NewIndexStringPredicateFromGlobsAndRanges(patterns []string, getIndex func(s string) int, getGlob func(pattern string) (glob.Glob, error)) (P[IndexString], error) {
var p PR[IndexString]
for _, pattern := range patterns {
pattern = strings.TrimSpace(pattern)
if pattern == "" {
continue
}
negate := strings.HasPrefix(pattern, hglob.NegationPrefix)
if negate {
pattern = pattern[2:]
g, err := getGlob(pattern)
if err != nil {
return nil, err
}
p = p.And(func(s IndexString) Match {
return BoolMatch(!g.Match(s.String))
})
} else {
// This can be either a glob or a value prefixed with one of >, >=, < or <=.
o, v := cutRangeOp(pattern)
if o != rangeOpNone {
i := getIndex(v)
if i == -1 {
// No match possible.
p = p.And(func(s IndexString) Match {
return BoolMatch(false)
})
continue
}
switch o {
// The greater values starts at the top with index 0.
case rangeOpGT:
p = p.And(func(s IndexString) Match {
return BoolMatch(s.Index < i)
})
case rangeOpGTE:
p = p.And(func(s IndexString) Match {
return BoolMatch(s.Index <= i)
})
case rangeOpLT:
p = p.And(func(s IndexString) Match {
return BoolMatch(s.Index > i)
})
case rangeOpLTE:
p = p.And(func(s IndexString) Match {
return BoolMatch(s.Index >= i)
})
case rangeOpEQ:
p = p.And(func(s IndexString) Match {
return BoolMatch(s.Index == i)
})
case rangeOpNE:
p = p.And(func(s IndexString) Match {
return BoolMatch(s.Index != i)
})
}
} else {
g, err := getGlob(pattern)
if err != nil {
return nil, err
}
p = p.Or(func(s IndexString) Match {
return BoolMatch(g.Match(s.String))
})
}
}
}
return p.BoolFunc(), nil
}
type IndexString struct {
Index int
String string
}
type IndexMatcher interface {
IndexMatch(match P[IndexString]) (iter.Seq[int], error)
}
+101
View File
@@ -146,6 +146,107 @@ func TestNewStringPredicateFromGlobs(t *testing.T) {
c.Assert(m("anything"), qt.IsFalse)
}
func TestNewIndexStringPredicateFromGlobsAndRanges(t *testing.T) {
c := qt.New(t)
// Simulate versions: v4.0.0=0, v3.0.0=1, v2.0.0=2, v1.0.0=3
// Lower index = greater value.
versions := []string{"v4.0.0", "v3.0.0", "v2.0.0", "v1.0.0"}
getIndex := func(s string) int {
for i, v := range versions {
if v == s {
return i
}
}
return -1
}
getGlob := func(pattern string) (glob.Glob, error) {
return glob.Compile(pattern)
}
n := func(patterns ...string) predicate.P[predicate.IndexString] {
p, err := predicate.NewIndexStringPredicateFromGlobsAndRanges(patterns, getIndex, getGlob)
c.Assert(err, qt.IsNil)
return p
}
is := func(i int) predicate.IndexString {
return predicate.IndexString{Index: i, String: versions[i]}
}
// Test >= v2.0.0 (index 2): should match indices <= 2
m := n(">= v2.0.0")
c.Assert(m(is(0)), qt.IsTrue) // v4.0.0
c.Assert(m(is(1)), qt.IsTrue) // v3.0.0
c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
c.Assert(m(is(3)), qt.IsFalse) // v1.0.0
// Test > v2.0.0 (index 2): should match indices < 2
m = n("> v2.0.0")
c.Assert(m(is(0)), qt.IsTrue) // v4.0.0
c.Assert(m(is(1)), qt.IsTrue) // v3.0.0
c.Assert(m(is(2)), qt.IsFalse) // v2.0.0
c.Assert(m(is(3)), qt.IsFalse) // v1.0.0
// Test < v3.0.0 (index 1): should match indices > 1
m = n("< v3.0.0")
c.Assert(m(is(0)), qt.IsFalse) // v4.0.0
c.Assert(m(is(1)), qt.IsFalse) // v3.0.0
c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
c.Assert(m(is(3)), qt.IsTrue) // v1.0.0
// Test range: >= v2.0.0 AND <= v3.0.0
m = n(">= v2.0.0", "<= v3.0.0")
c.Assert(m(is(0)), qt.IsFalse) // v4.0.0 - too high
c.Assert(m(is(1)), qt.IsTrue) // v3.0.0
c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
c.Assert(m(is(3)), qt.IsFalse) // v1.0.0 - too low
// Test glob pattern
m = n("v2.*.*")
c.Assert(m(is(0)), qt.IsFalse) // v4.0.0
c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
// Test glob with negation
m = n("v*.*.*", "! v3.*.*")
c.Assert(m(is(0)), qt.IsTrue) // v4.0.0
c.Assert(m(is(1)), qt.IsFalse) // v3.0.0 - negated
c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
// Test range with negation: >= v2.0.0 but not v3.0.0
m = n(">= v2.0.0", "! v3.0.0")
c.Assert(m(is(0)), qt.IsTrue) // v4.0.0
c.Assert(m(is(1)), qt.IsFalse) // v3.0.0 - negated
c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
c.Assert(m(is(3)), qt.IsFalse) // v1.0.0 - out of range
// Test unknown value in range returns no match
m = n(">= v99.0.0")
c.Assert(m(is(0)), qt.IsFalse)
c.Assert(m(is(3)), qt.IsFalse)
// Test == v2.0.0: should only match v2.0.0
m = n("== v2.0.0")
c.Assert(m(is(0)), qt.IsFalse) // v4.0.0
c.Assert(m(is(1)), qt.IsFalse) // v3.0.0
c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
c.Assert(m(is(3)), qt.IsFalse) // v1.0.0
// Test != v2.0.0: should match everything except v2.0.0
m = n("!= v2.0.0")
c.Assert(m(is(0)), qt.IsTrue) // v4.0.0
c.Assert(m(is(1)), qt.IsTrue) // v3.0.0
c.Assert(m(is(2)), qt.IsFalse) // v2.0.0
c.Assert(m(is(3)), qt.IsTrue) // v1.0.0
// Test != with range: >= v2.0.0 AND != v3.0.0
m = n(">= v2.0.0", "!= v3.0.0")
c.Assert(m(is(0)), qt.IsTrue) // v4.0.0
c.Assert(m(is(1)), qt.IsFalse) // v3.0.0 - excluded by !=
c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
c.Assert(m(is(3)), qt.IsFalse) // v1.0.0 - out of range
}
func BenchmarkPredicate(b *testing.B) {
b.Run("and or no match", func(b *testing.B) {
var p predicate.PR[int] = intP1
+52
View File
@@ -0,0 +1,52 @@
// Copyright 2026 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 predicate
import (
"testing"
qt "github.com/frankban/quicktest"
)
func TestCutRangeOp(t *testing.T) {
c := qt.New(t)
op, rest := cutRangeOp(">= value")
c.Assert(op, qt.Equals, rangeOpGTE)
c.Assert(rest, qt.Equals, "value")
op, rest = cutRangeOp("<= value")
c.Assert(op, qt.Equals, rangeOpLTE)
c.Assert(rest, qt.Equals, "value")
op, rest = cutRangeOp("> value")
c.Assert(op, qt.Equals, rangeOpGT)
c.Assert(rest, qt.Equals, "value")
op, rest = cutRangeOp("< value")
c.Assert(op, qt.Equals, rangeOpLT)
c.Assert(rest, qt.Equals, "value")
op, rest = cutRangeOp("== value")
c.Assert(op, qt.Equals, rangeOpEQ)
c.Assert(rest, qt.Equals, "value")
op, rest = cutRangeOp("!= value")
c.Assert(op, qt.Equals, rangeOpNE)
c.Assert(rest, qt.Equals, "value")
op, rest = cutRangeOp("value")
c.Assert(op, qt.Equals, rangeOpNone)
c.Assert(rest, qt.Equals, "value")
}
+16 -9
View File
@@ -37,6 +37,9 @@ type Version struct {
// HugoVersionSuffix is the suffix used in the Hugo version string.
// It will be blank for release versions.
Suffix string
// Set when parsed from a string,
source string
}
var (
@@ -44,11 +47,8 @@ var (
_ compare.Comparer = (*VersionString)(nil)
)
// IsAlphaBetaOrRC returns whether this version is an alpha, beta, or release candidate.
func (v Version) IsAlphaBetaOrRC() bool {
s := strings.ToLower(v.Suffix)
// e.g. "alpha.1", "beta.2", "rc.3"
return strings.Contains(s, "alpha.") || strings.Contains(s, "beta.") || strings.Contains(s, "rc.")
func (v Version) IsZero() bool {
return v.Major == 0 && v.Minor == 0 && v.PatchLevel == 0 && v.Suffix == ""
}
func (v Version) String() string {
@@ -109,6 +109,7 @@ func ParseVersion(s string) (Version, error) {
vv.Suffix = suffix
}
vv.Major, vv.Minor, vv.PatchLevel = parseVersion(s)
vv.source = s
return vv, nil
}
@@ -176,6 +177,10 @@ func CompareVersions(v1 Version, v2 any) int {
case int64:
c = compareFloatWithVersion(float64(d), v1)
case Version:
if v1.IsZero() && d.IsZero() {
// Fall back to source comparison.
return strings.Compare(v1.source, d.source)
}
if d.Major == v1.Major && d.Minor == v1.Minor && d.PatchLevel == v1.PatchLevel {
return strings.Compare(v1.Suffix, d.Suffix)
}
@@ -200,11 +205,13 @@ func CompareVersions(v1 Version, v2 any) int {
return -1
}
v, err := ParseVersion(s)
if err != nil {
return -1
v2v, _ := ParseVersion(s)
if v1.IsZero() && v2v.IsZero() {
// Fall back to source comparison.
return strings.Compare(v1.source, v2v.source)
}
return v1.Compare(v)
return v1.Compare(v2v)
}
+14
View File
@@ -50,6 +50,11 @@ func TestHugoVersion(t *testing.T) {
func TestCompareVersions(t *testing.T) {
c := qt.New(t)
parseIgnoreErr := func(s string) Version {
v, _ := ParseVersion(s)
return v
}
c.Assert(CompareVersions(MustParseVersion("0.20.0"), 0.20), qt.Equals, 0)
c.Assert(CompareVersions(MustParseVersion("0.20.0"), float32(0.20)), qt.Equals, 0)
c.Assert(CompareVersions(MustParseVersion("0.20.0"), float64(0.20)), qt.Equals, 0)
@@ -69,6 +74,15 @@ func TestCompareVersions(t *testing.T) {
c.Assert(CompareVersions(MustParseVersion("0.22.0-DEV"), "0.22"), qt.Equals, 1)
c.Assert(CompareVersions(MustParseVersion("0.22.1-DEV"), "0.22"), qt.Equals, -1)
c.Assert(CompareVersions(MustParseVersion("0.22.1-DEV"), "0.22.1-DEV"), qt.Equals, 0)
c.Assert(CompareVersions(parseIgnoreErr("foobar"), "v1.0.0"), qt.Equals, 1)
c.Assert(CompareVersions(parseIgnoreErr("v1.0.0"), "foobar"), qt.Equals, -1)
c.Assert(CompareVersions(parseIgnoreErr("foobar"), "foobar"), qt.Equals, 0)
c.Assert(CompareVersions(parseIgnoreErr("foobar"), parseIgnoreErr("foobar")), qt.Equals, 0)
c.Assert(CompareVersions(parseIgnoreErr("a"), "b"), qt.Equals, -1)
c.Assert(CompareVersions(parseIgnoreErr("a"), parseIgnoreErr("b")), qt.Equals, -1)
c.Assert(CompareVersions(parseIgnoreErr("b"), "a"), qt.Equals, 1)
c.Assert(CompareVersions(parseIgnoreErr("b"), parseIgnoreErr("a")), qt.Equals, 1)
}
func TestParseHugoVersion(t *testing.T) {
+25 -17
View File
@@ -29,10 +29,10 @@ import (
"github.com/gohugoio/hugo/cache/filecache"
"github.com/gohugoio/hugo/cache/httpcache"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstrings"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/urls"
"github.com/gohugoio/hugo/config"
@@ -212,7 +212,7 @@ type Config struct {
// User provided parameters.
// <docsmeta>{"refs": ["config:languages:params"] }</docsmeta>
Params maps.Params `mapstructure:"-"`
Params hmaps.Params `mapstructure:"-"`
// UglyURLs configuration. Either a boolean or a sections map.
UglyURLs any `mapstructure:"-"`
@@ -607,12 +607,13 @@ type RootConfig struct {
DefaultOutputFormat string
// Disable generation of redirect to the default language when DefaultContentLanguageInSubdir is enabled.
// Note that this currently is an alias for DisableDefaultDimensionRedirect introduced in v0.154.4.
// Note that this currently is an alias for DisableDefaultSiteRedirect introduced in v0.154.5.
// It's not obvious how a more fine grained setup would work.
DisableDefaultLanguageRedirect bool
// Disable generation of redirect to the default dimension when DefaultContentRoleInSubdir or DefaultContentVersionInSubdir or DefaultContentLanguageInSubdir is enabled.
DisableDefaultDimensionRedirect bool
// Disable generation of redirects to the default site when DefaultContentRoleInSubdir or DefaultContentVersionInSubdir or DefaultContentLanguageInSubdir is enabled.
// The default site is the site is the combination of defaultContentLanguage, defaultContentVersion and defaultContentRole.
DisableDefaultSiteRedirect bool
// Disable creation of alias redirect pages.
DisableAliases bool
@@ -821,6 +822,7 @@ type Configs struct {
Modules modules.Modules
ModulesClient *modules.Client
FileCaches filecache.Caches
// All below is set in Init.
Languages langs.Languages
@@ -851,7 +853,7 @@ func (c *Configs) IsZero() bool {
return c == nil || len(c.Languages) == 0
}
func (c *Configs) Init(logger loggers.Logger) error {
func (c *Configs) Init(sourceFs afero.Fs, logger loggers.Logger) error {
var languages langs.Languages
for _, f := range c.Base.Languages.Config.Sorted {
@@ -1022,7 +1024,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
if lang == "" {
lang = "en"
}
res.Cfg.Set("languages", maps.Params{lang: maps.Params{}})
res.Cfg.Set("languages", hmaps.Params{lang: hmaps.Params{}})
}
bcfg := res.BaseConfig
cfg := res.Cfg
@@ -1049,11 +1051,11 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
var differentRootKeys []string
switch x := v.(type) {
case nil:
case maps.Params:
case hmaps.Params:
_, found := x["params"]
if !found {
x["params"] = maps.Params{
maps.MergeStrategyKey: maps.ParamsMergeStrategyDeep,
x["params"] = hmaps.Params{
hmaps.MergeStrategyKey: hmaps.ParamsMergeStrategyDeep,
}
}
for kk, vv := range x {
@@ -1065,16 +1067,16 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
isMultihost = true
}
if p, ok := vv.(maps.Params); ok {
if p, ok := vv.(hmaps.Params); ok {
// With the introduction of YAML anchor and alias support, language config entries
// may be contain shared references.
// This also break potential cycles.
vv = maps.CloneParamsDeep(p)
vv = hmaps.CloneParamsDeep(p)
}
if kk == "cascade" {
// If not set, add the current language to make sure it does not get applied to other languages.
page.AddLangToCascadeTargetMap(k, vv.(maps.Params))
page.AddLangToCascadeTargetMap(k, vv.(hmaps.Params))
// Always clone cascade config to get the sites matrix right.
differentRootKeys = append(differentRootKeys, kk)
}
@@ -1085,14 +1087,14 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
// This overrides a root key and potentially needs a merge.
if !reflect.DeepEqual(rootv, vv) {
switch vvv := vv.(type) {
case maps.Params:
case hmaps.Params:
differentRootKeys = append(differentRootKeys, kk)
// Use the language value as base.
// Note that this is already cloned above.
mergedConfigEntry := vvv
// Merge in the root value.
maps.MergeParams(mergedConfigEntry, rootv.(maps.Params))
hmaps.MergeParams(mergedConfigEntry, rootv.(hmaps.Params))
mergedConfig.Set(kk, mergedConfigEntry)
default:
@@ -1102,7 +1104,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
}
} else {
switch vv.(type) {
case maps.Params:
case hmaps.Params:
differentRootKeys = append(differentRootKeys, kk)
default:
// Apply new values to the root.
@@ -1138,7 +1140,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
}
langConfigMap[k] = clone
case maps.ParamsMergeStrategy:
case hmaps.ParamsMergeStrategy:
default:
panic(fmt.Sprintf("unknown type in languages config: %T", v))
@@ -1153,10 +1155,16 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
l.CommonDirs.CacheDir = bcfg.CacheDir
}
caches, err := filecache.NewCaches(all.Caches, fs)
if err != nil {
return nil, fmt.Errorf("failed to create file caches from configuration: %w", err)
}
cm := &Configs{
Base: all,
LanguageConfigMap: langConfigMap,
LoadingInfo: res,
FileCaches: caches,
IsMultihost: isMultihost,
}
+13 -13
View File
@@ -22,9 +22,9 @@ import (
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/cache/httpcache"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstrings"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/privacy"
@@ -196,7 +196,7 @@ var allDecoderSetups = map[string]decodeWeight{
key: "outputs",
decode: func(d decodeWeight, p decodeConfig) error {
defaults := createDefaultOutputFormats(p.c.OutputFormats.Config)
m := maps.CleanConfigStringMap(p.p.GetStringMap("outputs"))
m := hmaps.CleanConfigStringMap(p.p.GetStringMap("outputs"))
p.c.Outputs = make(map[string][]string)
for k, v := range m {
s := types.ToStringSlicePreserveString(v)
@@ -225,13 +225,13 @@ var allDecoderSetups = map[string]decodeWeight{
"languages": {
key: "languages",
decode: func(d decodeWeight, p decodeConfig) error {
m := maps.CleanConfigStringMap(p.p.GetStringMap(d.key))
m := hmaps.CleanConfigStringMap(p.p.GetStringMap(d.key))
if len(m) == 1 {
// In v0.112.4 we moved this to the language config, but it's very commmon for mono language sites to have this at the top level.
var first maps.Params
var first hmaps.Params
var ok bool
for _, v := range m {
first, ok = v.(maps.Params)
first, ok = v.(hmaps.Params)
if ok {
break
}
@@ -266,7 +266,7 @@ var allDecoderSetups = map[string]decodeWeight{
key: "versions",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
m := maps.CleanConfigStringMap(p.p.GetStringMap(d.key))
m := hmaps.CleanConfigStringMap(p.p.GetStringMap(d.key))
p.c.Versions, err = versions.DecodeConfig(p.c.RootConfig.DefaultContentVersion, m)
return err
},
@@ -278,7 +278,7 @@ var allDecoderSetups = map[string]decodeWeight{
err error
defaultContentRole string
)
m := maps.CleanConfigStringMap(p.p.GetStringMap(d.key))
m := hmaps.CleanConfigStringMap(p.p.GetStringMap(d.key))
p.c.Roles, defaultContentRole, err = roles.DecodeConfig(p.c.RootConfig.DefaultContentRole, m)
p.c.RootConfig.DefaultContentRole = defaultContentRole
@@ -289,7 +289,7 @@ var allDecoderSetups = map[string]decodeWeight{
"params": {
key: "params",
decode: func(d decodeWeight, p decodeConfig) error {
p.c.Params = maps.CleanConfigStringMap(p.p.GetStringMap("params"))
p.c.Params = hmaps.CleanConfigStringMap(p.p.GetStringMap("params"))
if p.c.Params == nil {
p.c.Params = make(map[string]any)
}
@@ -335,7 +335,7 @@ var allDecoderSetups = map[string]decodeWeight{
key: "taxonomies",
decode: func(d decodeWeight, p decodeConfig) error {
if p.p.IsSet(d.key) {
p.c.Taxonomies = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
p.c.Taxonomies = hmaps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
}
return nil
},
@@ -447,7 +447,7 @@ var allDecoderSetups = map[string]decodeWeight{
"author": {
key: "author",
decode: func(d decodeWeight, p decodeConfig) error {
p.c.Author = maps.CleanConfigStringMap(p.p.GetStringMap(d.key))
p.c.Author = hmaps.CleanConfigStringMap(p.p.GetStringMap(d.key))
return nil
},
internalOrDeprecated: true,
@@ -455,7 +455,7 @@ var allDecoderSetups = map[string]decodeWeight{
"social": {
key: "social",
decode: func(d decodeWeight, p decodeConfig) error {
p.c.Social = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
p.c.Social = hmaps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
return nil
},
internalOrDeprecated: true,
@@ -469,8 +469,8 @@ var allDecoderSetups = map[string]decodeWeight{
p.c.UglyURLs = vv
case string:
p.c.UglyURLs = vv == "true"
case maps.Params:
p.c.UglyURLs = cast.ToStringMapBool(maps.CleanConfigStringMap(vv))
case hmaps.Params:
p.c.UglyURLs = cast.ToStringMapBool(hmaps.CleanConfigStringMap(vv))
default:
p.c.UglyURLs = cast.ToStringMapBool(v)
}
+6 -3
View File
@@ -25,9 +25,8 @@ import (
)
type ConfigLanguage struct {
config *Config
baseConfig config.BaseConfig
config *Config
baseConfig config.BaseConfig
m *Configs
language *langs.Language
languageIndex int
@@ -123,6 +122,10 @@ func (c ConfigLanguage) BaseConfig() config.BaseConfig {
return c.baseConfig
}
func (c ConfigLanguage) FileCaches() any {
return c.m.FileCaches
}
func (c ConfigLanguage) Dirs() config.CommonDirs {
return c.config.CommonDirs
}
+6 -6
View File
@@ -14,7 +14,7 @@
package allconfig
import (
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/docshelper"
)
@@ -27,12 +27,12 @@ func init() {
if v.internalOrDeprecated {
continue
}
cfg.Set(configRoot, make(maps.Params))
cfg.Set(configRoot, make(hmaps.Params))
}
lang := maps.Params{
"en": maps.Params{
"menus": maps.Params{},
"params": maps.Params{},
lang := hmaps.Params{
"en": hmaps.Params{
"menus": hmaps.Params{},
"params": hmaps.Params{},
},
}
cfg.Set("languages", lang)
+7 -6
View File
@@ -25,9 +25,9 @@ import (
"github.com/gobwas/glob"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/hexec"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
@@ -95,7 +95,7 @@ func LoadConfig(d ConfigSourceDescriptor) (configs *Configs, err error) {
configs.Modules = moduleConfig.AllModules
configs.ModulesClient = modulesClient
if err := configs.Init(d.Logger); err != nil {
if err := configs.Init(d.Fs, d.Logger); err != nil {
return nil, fmt.Errorf("failed to init config: %w", err)
}
@@ -166,7 +166,7 @@ func (l configLoader) applyConfigAliases() error {
}
func (l configLoader) applyDefaultConfig() error {
defaultSettings := maps.Params{
defaultSettings := hmaps.Params{
// These dirs are used early/before we build the config struct.
"themesDir": "themes",
"configDir": "config",
@@ -186,7 +186,7 @@ func (l configLoader) normalizeCfg(cfg config.Provider) error {
} else if b, ok := cfg.Get("minify").(bool); ok {
hugo.Deprecate("site config minify", "Use minify.minifyOutput instead.", "v0.150.0")
if b {
cfg.Set("minify", maps.Params{"minifyOutput": true})
cfg.Set("minify", hmaps.Params{"minifyOutput": true})
}
}
@@ -240,7 +240,7 @@ func (l configLoader) applyOsEnvOverrides(environ []string) error {
}
for _, env := range hugoEnv {
existing, nestedKey, owner, err := maps.GetNestedParamFn(env.Key, delim, l.cfg.Get)
existing, nestedKey, owner, err := hmaps.GetNestedParamFn(env.Key, delim, l.cfg.Get)
if err != nil {
return err
}
@@ -477,6 +477,7 @@ func (l *configLoader) loadModules(configs *Configs, ignoreModuleDoesNotExist bo
PublishDir: publishDir,
Environment: l.Environment,
CacheDir: conf.Caches.CacheDirModules(),
ModuleQueriesCache: configs.FileCaches.ModuleQueriesCache(),
ModuleConfig: conf.Module,
IgnoreVendor: ignoreVendor,
IgnoreModuleDoesNotExist: ignoreModuleDoesNotExist,
@@ -547,7 +548,7 @@ func (l configLoader) loadConfig(configName string) (string, error) {
}
func (l configLoader) deleteMergeStrategies() (err error) {
l.cfg.WalkParams(func(params ...maps.KeyParams) bool {
l.cfg.WalkParams(func(params ...hmaps.KeyParams) bool {
params[len(params)-1].Params.DeleteMergeStrategy()
return false
})
+1 -1
View File
@@ -437,7 +437,7 @@ func (c *CacheBuster) CompileConfig(logger loggers.Logger) error {
return nil
}
return func(ss string) bool {
match = targetRe.MatchString(ss)
match := targetRe.MatchString(ss)
matchString := "no match"
if match {
matchString = "match!"
+3 -3
View File
@@ -20,11 +20,11 @@ import (
"strings"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/parser"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/spf13/afero"
)
@@ -222,11 +222,11 @@ func LoadConfigFromDir(sourceFs afero.Fs, configDir, environment string) (Provid
return cfg, dirnames, nil
}
var keyAliases maps.KeyRenamer
var keyAliases hmaps.KeyRenamer
func init() {
var err error
keyAliases, err = maps.NewKeyRenamer(
keyAliases, err = hmaps.NewKeyRenamer(
// Before 0.53 we used singular for "menu".
"{menu,languages/*/menu}", "menus",
)
+5 -4
View File
@@ -16,7 +16,7 @@ package config
import (
"time"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/common/urls"
@@ -41,6 +41,7 @@ type AllProvider interface {
Dirs() CommonDirs
Quiet() bool
DirsBase() CommonDirs
FileCaches() any
ContentTypes() ContentTypesProvider
GetConfigSection(string) any
GetConfig() any
@@ -96,7 +97,7 @@ type Provider interface {
GetString(key string) string
GetInt(key string) int
GetBool(key string) bool
GetParams(key string) maps.Params
GetParams(key string) hmaps.Params
GetStringMap(key string) map[string]any
GetStringMapString(key string) map[string]string
GetStringSlice(key string) []string
@@ -104,9 +105,9 @@ type Provider interface {
Set(key string, value any)
Keys() []string
Merge(key string, value any)
SetDefaults(params maps.Params)
SetDefaults(params hmaps.Params)
SetDefaultMergeStrategy()
WalkParams(walkFn func(params ...maps.KeyParams) bool)
WalkParams(walkFn func(params ...hmaps.KeyParams) bool)
IsSet(key string) bool
}
+49 -50
View File
@@ -20,21 +20,20 @@ import (
"strings"
"sync"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/spf13/cast"
"github.com/gohugoio/hugo/common/maps"
)
// New creates a Provider backed by an empty maps.Params.
// New creates a Provider backed by an empty hmaps.Params.
func New() Provider {
return &defaultConfigProvider{
root: make(maps.Params),
root: make(hmaps.Params),
}
}
// NewFrom creates a Provider backed by params.
func NewFrom(params maps.Params) Provider {
maps.PrepareParams(params)
func NewFrom(params hmaps.Params) Provider {
hmaps.PrepareParams(params)
return &defaultConfigProvider{
root: params,
}
@@ -44,7 +43,7 @@ func NewFrom(params maps.Params) Provider {
// All methods are thread safe.
type defaultConfigProvider struct {
mu sync.RWMutex
root maps.Params
root hmaps.Params
keyCache sync.Map
}
@@ -90,22 +89,22 @@ func (c *defaultConfigProvider) GetString(k string) string {
return cast.ToString(v)
}
func (c *defaultConfigProvider) GetParams(k string) maps.Params {
func (c *defaultConfigProvider) GetParams(k string) hmaps.Params {
v := c.Get(k)
if v == nil {
return nil
}
return v.(maps.Params)
return v.(hmaps.Params)
}
func (c *defaultConfigProvider) GetStringMap(k string) map[string]any {
v := c.Get(k)
return maps.ToStringMap(v)
return hmaps.ToStringMap(v)
}
func (c *defaultConfigProvider) GetStringMapString(k string) map[string]string {
v := c.Get(k)
return maps.ToStringMapString(v)
return hmaps.ToStringMapString(v)
}
func (c *defaultConfigProvider) GetStringSlice(k string) []string {
@@ -120,9 +119,9 @@ func (c *defaultConfigProvider) Set(k string, v any) {
k = strings.ToLower(k)
if k == "" {
if p, err := maps.ToParamsAndPrepare(v); err == nil {
if p, err := hmaps.ToParamsAndPrepare(v); err == nil {
// Set the values directly in root.
maps.SetParams(c.root, p)
hmaps.SetParams(c.root, p)
} else {
c.root[k] = v
}
@@ -132,7 +131,7 @@ func (c *defaultConfigProvider) Set(k string, v any) {
switch vv := v.(type) {
case map[string]any, map[any]any, map[string]string:
p := maps.MustToParamsAndPrepare(vv)
p := hmaps.MustToParamsAndPrepare(vv)
v = p
}
@@ -142,9 +141,9 @@ func (c *defaultConfigProvider) Set(k string, v any) {
}
if existing, found := m[key]; found {
if p1, ok := existing.(maps.Params); ok {
if p2, ok := v.(maps.Params); ok {
maps.SetParams(p1, p2)
if p1, ok := existing.(hmaps.Params); ok {
if p2, ok := v.(hmaps.Params); ok {
hmaps.SetParams(p1, p2)
return
}
}
@@ -154,8 +153,8 @@ func (c *defaultConfigProvider) Set(k string, v any) {
}
// SetDefaults will set values from params if not already set.
func (c *defaultConfigProvider) SetDefaults(params maps.Params) {
maps.PrepareParams(params)
func (c *defaultConfigProvider) SetDefaults(params hmaps.Params) {
hmaps.PrepareParams(params)
for k, v := range params {
if _, found := c.root[k]; !found {
c.root[k] = v
@@ -170,28 +169,28 @@ func (c *defaultConfigProvider) Merge(k string, v any) {
if k == "" {
rs, f := c.root.GetMergeStrategy()
if f && rs == maps.ParamsMergeStrategyNone {
if f && rs == hmaps.ParamsMergeStrategyNone {
// The user has set a "no merge" strategy on this,
// nothing more to do.
return
}
if p, err := maps.ToParamsAndPrepare(v); err == nil {
if p, err := hmaps.ToParamsAndPrepare(v); err == nil {
// As there may be keys in p not in root, we need to handle
// those as a special case.
var keysToDelete []string
for kk, vv := range p {
if pp, ok := vv.(maps.Params); ok {
if pp, ok := vv.(hmaps.Params); ok {
if pppi, ok := c.root[kk]; ok {
ppp := pppi.(maps.Params)
maps.MergeParamsWithStrategy("", ppp, pp)
ppp := pppi.(hmaps.Params)
hmaps.MergeParamsWithStrategy("", ppp, pp)
} else {
// We need to use the default merge strategy for
// this key.
np := make(maps.Params)
strategy := c.determineMergeStrategy(maps.KeyParams{Key: "", Params: c.root}, maps.KeyParams{Key: kk, Params: np})
np := make(hmaps.Params)
strategy := c.determineMergeStrategy(hmaps.KeyParams{Key: "", Params: c.root}, hmaps.KeyParams{Key: kk, Params: np})
np.SetMergeStrategy(strategy)
maps.MergeParamsWithStrategy("", np, pp)
hmaps.MergeParamsWithStrategy("", np, pp)
c.root[kk] = np
if np.IsZero() {
// Just keep it until merge is done.
@@ -201,7 +200,7 @@ func (c *defaultConfigProvider) Merge(k string, v any) {
}
}
// Merge the rest.
maps.MergeParams(c.root, p)
hmaps.MergeParams(c.root, p)
for _, k := range keysToDelete {
delete(c.root, k)
}
@@ -214,7 +213,7 @@ func (c *defaultConfigProvider) Merge(k string, v any) {
switch vv := v.(type) {
case map[string]any, map[any]any, map[string]string:
p := maps.MustToParamsAndPrepare(vv)
p := hmaps.MustToParamsAndPrepare(vv)
v = p
}
@@ -224,9 +223,9 @@ func (c *defaultConfigProvider) Merge(k string, v any) {
}
if existing, found := m[key]; found {
if p1, ok := existing.(maps.Params); ok {
if p2, ok := v.(maps.Params); ok {
maps.MergeParamsWithStrategy("", p1, p2)
if p1, ok := existing.(hmaps.Params); ok {
if p2, ok := v.(hmaps.Params); ok {
hmaps.MergeParamsWithStrategy("", p1, p2)
}
}
} else {
@@ -245,10 +244,10 @@ func (c *defaultConfigProvider) Keys() []string {
return keys
}
func (c *defaultConfigProvider) WalkParams(walkFn func(params ...maps.KeyParams) bool) {
func (c *defaultConfigProvider) WalkParams(walkFn func(params ...hmaps.KeyParams) bool) {
maxDepth := 1000
var walk func(depth int, params ...maps.KeyParams)
walk = func(depth int, params ...maps.KeyParams) {
var walk func(depth int, params ...hmaps.KeyParams)
walk = func(depth int, params ...hmaps.KeyParams) {
if depth > maxDepth {
panic(errors.New("max depth exceeded"))
}
@@ -258,24 +257,24 @@ func (c *defaultConfigProvider) WalkParams(walkFn func(params ...maps.KeyParams)
p1 := params[len(params)-1]
i := len(params)
for k, v := range p1.Params {
if p2, ok := v.(maps.Params); ok {
paramsplus1 := make([]maps.KeyParams, i+1)
if p2, ok := v.(hmaps.Params); ok {
paramsplus1 := make([]hmaps.KeyParams, i+1)
copy(paramsplus1, params)
paramsplus1[i] = maps.KeyParams{Key: k, Params: p2}
paramsplus1[i] = hmaps.KeyParams{Key: k, Params: p2}
walk(depth+1, paramsplus1...)
}
}
}
walk(0, maps.KeyParams{Key: "", Params: c.root})
walk(0, hmaps.KeyParams{Key: "", Params: c.root})
}
func (c *defaultConfigProvider) determineMergeStrategy(params ...maps.KeyParams) maps.ParamsMergeStrategy {
func (c *defaultConfigProvider) determineMergeStrategy(params ...hmaps.KeyParams) hmaps.ParamsMergeStrategy {
if len(params) == 0 {
return maps.ParamsMergeStrategyNone
return hmaps.ParamsMergeStrategyNone
}
var (
strategy maps.ParamsMergeStrategy
strategy hmaps.ParamsMergeStrategy
prevIsRoot bool
curr = params[len(params)-1]
)
@@ -299,10 +298,10 @@ func (c *defaultConfigProvider) determineMergeStrategy(params ...maps.KeyParams)
// Don't set a merge strategy on the root unless set by user.
// This will be handled as a special case.
case "params":
strategy = maps.ParamsMergeStrategyDeep
strategy = hmaps.ParamsMergeStrategyDeep
case "outputformats", "mediatypes":
if prevIsRoot {
strategy = maps.ParamsMergeStrategyShallow
strategy = hmaps.ParamsMergeStrategyShallow
}
case "menus":
isMenuKey := prevIsRoot
@@ -314,11 +313,11 @@ func (c *defaultConfigProvider) determineMergeStrategy(params ...maps.KeyParams)
}
}
if isMenuKey {
strategy = maps.ParamsMergeStrategyShallow
strategy = hmaps.ParamsMergeStrategyShallow
}
default:
if strategy == "" {
strategy = maps.ParamsMergeStrategyNone
strategy = hmaps.ParamsMergeStrategyNone
}
}
@@ -326,7 +325,7 @@ func (c *defaultConfigProvider) determineMergeStrategy(params ...maps.KeyParams)
}
func (c *defaultConfigProvider) SetDefaultMergeStrategy() {
c.WalkParams(func(params ...maps.KeyParams) bool {
c.WalkParams(func(params ...hmaps.KeyParams) bool {
if len(params) == 0 {
return false
}
@@ -344,7 +343,7 @@ func (c *defaultConfigProvider) SetDefaultMergeStrategy() {
})
}
func (c *defaultConfigProvider) getNestedKeyAndMap(key string, create bool) (string, maps.Params) {
func (c *defaultConfigProvider) getNestedKeyAndMap(key string, create bool) (string, hmaps.Params) {
var parts []string
v, ok := c.keyCache.Load(key)
if ok {
@@ -358,14 +357,14 @@ func (c *defaultConfigProvider) getNestedKeyAndMap(key string, create bool) (str
next, found := current[parts[i]]
if !found {
if create {
next = make(maps.Params)
next = make(hmaps.Params)
current[parts[i]] = next
} else {
return "", nil
}
}
var ok bool
current, ok = next.(maps.Params)
current, ok = next.(hmaps.Params)
if !ok {
// E.g. a string, not a map that we can store values in.
return "", nil
+31 -32
View File
@@ -22,10 +22,9 @@ import (
"strings"
"testing"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/para"
"github.com/gohugoio/hugo/common/maps"
qt "github.com/frankban/quicktest"
)
@@ -48,7 +47,7 @@ func TestDefaultConfigProvider(t *testing.T) {
c.Assert(cfg.Get(k), qt.Equals, v)
c.Assert(cfg.GetInt(k), qt.Equals, v)
c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
"foo": 42,
})
})
@@ -60,7 +59,7 @@ func TestDefaultConfigProvider(t *testing.T) {
"bar": "baz",
})
c.Assert(cfg.Get("foo"), qt.DeepEquals, maps.Params{
c.Assert(cfg.Get("foo"), qt.DeepEquals, hmaps.Params{
"bar": "baz",
})
@@ -76,14 +75,14 @@ func TestDefaultConfigProvider(t *testing.T) {
})
cfg.Set("a.c", "cv")
c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
c.Assert(cfg.Get("a"), qt.DeepEquals, hmaps.Params{
"b": "bv",
"c": "cv",
})
c.Assert(cfg.Get("a.c"), qt.Equals, "cv")
cfg.Set("b.a", "av")
c.Assert(cfg.Get("b"), qt.DeepEquals, maps.Params{
c.Assert(cfg.Get("b"), qt.DeepEquals, hmaps.Params{
"a": "av",
})
@@ -91,7 +90,7 @@ func TestDefaultConfigProvider(t *testing.T) {
"b": "bv",
})
c.Assert(cfg.Get("b"), qt.DeepEquals, maps.Params{
c.Assert(cfg.Get("b"), qt.DeepEquals, hmaps.Params{
"a": "av",
"b": "bv",
})
@@ -105,7 +104,7 @@ func TestDefaultConfigProvider(t *testing.T) {
"b": "bv2",
})
c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
"a": "av2",
"b": "bv2",
})
@@ -118,7 +117,7 @@ func TestDefaultConfigProvider(t *testing.T) {
"b": "bv2",
})
c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
"a": "av",
"b": "bv2",
})
@@ -137,7 +136,7 @@ func TestDefaultConfigProvider(t *testing.T) {
},
})
c.Assert(cfg.Get("foo"), qt.DeepEquals, maps.Params{
c.Assert(cfg.Get("foo"), qt.DeepEquals, hmaps.Params{
"a": "av",
"b": "bv2",
})
@@ -155,7 +154,7 @@ func TestDefaultConfigProvider(t *testing.T) {
"c": "cv2",
})
c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
c.Assert(cfg.Get("a"), qt.DeepEquals, hmaps.Params{
"b": "bv",
"c": "cv2",
})
@@ -169,7 +168,7 @@ func TestDefaultConfigProvider(t *testing.T) {
"b": "bv2",
})
c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
"a": "av",
})
})
@@ -192,11 +191,11 @@ func TestDefaultConfigProvider(t *testing.T) {
"e": "ev2",
})
c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
c.Assert(cfg.Get("a"), qt.DeepEquals, hmaps.Params{
"e": "ev2",
"_merge": maps.ParamsMergeStrategyShallow,
"_merge": hmaps.ParamsMergeStrategyShallow,
"b": "bv",
"c": maps.Params{
"c": hmaps.Params{
"b": "bv",
},
})
@@ -221,15 +220,15 @@ func TestDefaultConfigProvider(t *testing.T) {
"b": left,
})
cfg.Merge("", maps.Params{
"b": maps.Params{
cfg.Merge("", hmaps.Params{
"b": hmaps.Params{
"c": "cv2",
"d": "dv2",
},
})
c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
"b": maps.Params{
c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
"b": hmaps.Params{
"c": "cv1",
"d": "dv2",
},
@@ -267,8 +266,8 @@ func TestDefaultConfigProvider(t *testing.T) {
cfg.Merge("a", right)
c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
"a": maps.Params{
c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
"a": hmaps.Params{
"b": "bv1",
"c": "cv2",
},
@@ -292,7 +291,7 @@ func TestDefaultConfigProvider(t *testing.T) {
},
})
c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
"b": "bv",
})
})
@@ -341,7 +340,7 @@ func TestDefaultConfigProvider(t *testing.T) {
return err
}
m := maps.Params{
m := hmaps.Params{
"new": 42,
}
@@ -371,8 +370,8 @@ func TestDefaultConfigProvider(t *testing.T) {
cfg := New()
k := "foo"
cfg.Set(k, maps.Params{k: true})
c.Assert(cfg.GetParams(k), qt.DeepEquals, maps.Params{
cfg.Set(k, hmaps.Params{k: true})
c.Assert(cfg.GetParams(k), qt.DeepEquals, hmaps.Params{
k: true,
})
@@ -384,8 +383,8 @@ func TestDefaultConfigProvider(t *testing.T) {
k := "foo"
k2 := "bar"
cfg.Set(k, maps.Params{k: struct{}{}})
cfg.Set(k2, maps.Params{k2: struct{}{}})
cfg.Set(k, hmaps.Params{k: struct{}{}})
cfg.Set(k2, hmaps.Params{k2: struct{}{}})
c.Assert(len(cfg.Keys()), qt.Equals, 2)
@@ -401,11 +400,11 @@ func TestDefaultConfigProvider(t *testing.T) {
c.Run("WalkParams", func(c *qt.C) {
cfg := New()
cfg.Set("x", maps.Params{})
cfg.Set("y", maps.Params{})
cfg.Set("x", hmaps.Params{})
cfg.Set("y", hmaps.Params{})
var got []string
cfg.WalkParams(func(params ...maps.KeyParams) bool {
cfg.WalkParams(func(params ...hmaps.KeyParams) bool {
got = append(got, params[len(params)-1].Key)
return false
})
@@ -417,7 +416,7 @@ func TestDefaultConfigProvider(t *testing.T) {
c.Assert(got, qt.DeepEquals, want)
cfg = New()
cfg.WalkParams(func(params ...maps.KeyParams) bool {
cfg.WalkParams(func(params ...hmaps.KeyParams) bool {
return true
})
@@ -429,7 +428,7 @@ func TestDefaultConfigProvider(t *testing.T) {
c.Run("SetDefaults", func(c *qt.C) {
cfg := New()
cfg.SetDefaults(maps.Params{
cfg.SetDefaults(hmaps.Params{
"foo": "bar",
"bar": "baz",
})
+2 -2
View File
@@ -18,7 +18,7 @@ import (
"testing"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/mitchellh/mapstructure"
)
@@ -32,7 +32,7 @@ func TestNamespace(t *testing.T) {
map[string]any{"foo": "bar"},
func(v any) (*tstNsExt, any, error) {
t := &tstNsExt{}
m, err := maps.ToStringMapE(v)
m, err := hmaps.ToStringMapE(v)
if err != nil {
return nil, nil, err
}
+2 -2
View File
@@ -17,7 +17,7 @@ package testconfig
import (
_ "unsafe"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/allconfig"
"github.com/gohugoio/hugo/deps"
@@ -84,7 +84,7 @@ func GetTestConfigSectionFromStruct(section string, v any) config.AllProvider {
if err != nil {
panic(err)
}
p := maps.Params{
p := hmaps.Params{
section: config.FromTOMLConfigString(string(data)).Get(""),
}
cfg := config.NewFrom(p)
+11 -11
View File
@@ -28,8 +28,8 @@ import (
"github.com/gohugoio/hugo/cache/dynacache"
"github.com/gohugoio/hugo/cache/filecache"
"github.com/gohugoio/hugo/common/hexec"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/allconfig"
@@ -173,8 +173,8 @@ func (d *Deps) Init() error {
d.BuildState.DeferredExecutionsGroupedByRenderingContext = make(map[tpl.RenderingContext]*DeferredExecutions)
}
d.BuildState.DeferredExecutions = &DeferredExecutions{
Executions: maps.NewCache[string, *tpl.DeferredExecution](),
FilenamesWithPostPrefix: maps.NewCache[string, bool](),
Executions: hmaps.NewCache[string, *tpl.DeferredExecution](),
FilenamesWithPostPrefix: hmaps.NewCache[string, bool](),
}
}
@@ -260,10 +260,10 @@ func (d *Deps) Init() error {
common = d.ResourceSpec.SpecCommon
}
fileCaches, err := filecache.NewCaches(d.PathSpec)
if err != nil {
return fmt.Errorf("failed to create file caches from configuration: %w", err)
}
d.Cfg.BaseConfig()
fileCaches := d.Cfg.FileCaches().(filecache.Caches)
fileCaches.SetResourceFs(d.BaseFs.ResourcesCache)
resourceSpec, err := resources.NewSpec(d.PathSpec, common, d.WasmDispatchers, fileCaches, d.MemCache, d.BuildState, d.Log, d, d.ExecHelper, d.BuildClosers, d.BuildState)
if err != nil {
@@ -464,10 +464,10 @@ type Counters struct {
type DeferredExecutions struct {
// A set of filenames in /public that
// contains a post-processing prefix.
FilenamesWithPostPrefix *maps.Cache[string, bool]
FilenamesWithPostPrefix *hmaps.Cache[string, bool]
// Maps a placeholder to a deferred execution.
Executions *maps.Cache[string, *tpl.DeferredExecution]
Executions *hmaps.Cache[string, *tpl.DeferredExecution]
}
var _ identity.SignalRebuilder = (*BuildState)(nil)
@@ -480,8 +480,8 @@ func (b *BuildState) StartStageRender(stage tpl.RenderingContext) {
func (b *BuildState) StopStageRender(stage tpl.RenderingContext) {
b.DeferredExecutionsGroupedByRenderingContext[stage] = b.DeferredExecutions
b.DeferredExecutions = &DeferredExecutions{
Executions: maps.NewCache[string, *tpl.DeferredExecution](),
FilenamesWithPostPrefix: maps.NewCache[string, bool](),
Executions: hmaps.NewCache[string, *tpl.DeferredExecution](),
FilenamesWithPostPrefix: hmaps.NewCache[string, bool](),
}
}
+813 -567
View File
File diff suppressed because it is too large Load Diff
+19 -19
View File
@@ -3,9 +3,9 @@ module github.com/gohugoio/hugo
require (
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69
github.com/JohannesKaufmann/html-to-markdown/v2 v2.5.0
github.com/alecthomas/chroma/v2 v2.21.1
github.com/aws/aws-sdk-go-v2 v1.41.0
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.58.3
github.com/alecthomas/chroma/v2 v2.23.1
github.com/aws/aws-sdk-go-v2 v1.41.1
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.59.0
github.com/bep/clocks v0.5.0
github.com/bep/debounce v1.2.0
github.com/bep/gitmap v1.9.0
@@ -14,8 +14,8 @@ require (
github.com/bep/golibsass v1.2.0
github.com/bep/goportabletext v0.1.0
github.com/bep/helpers v0.6.0
github.com/bep/imagemeta v0.12.1
github.com/bep/lazycache v0.8.0
github.com/bep/imagemeta v0.14.0
github.com/bep/lazycache v0.8.1
github.com/bep/logg v0.4.0
github.com/bep/mclib v1.20400.20402
github.com/bep/overlayfs v0.10.0
@@ -25,7 +25,6 @@ require (
github.com/bits-and-blooms/bitset v1.24.4
github.com/cespare/xxhash/v2 v2.3.0
github.com/clbanning/mxj/v2 v2.7.0
github.com/disintegration/gift v1.2.1
github.com/dustin/go-humanize v1.0.1
github.com/evanw/esbuild v0.27.2
github.com/fatih/color v1.18.0
@@ -36,6 +35,7 @@ require (
github.com/gobuffalo/flect v1.0.3
github.com/gobwas/glob v0.2.3
github.com/goccy/go-yaml v1.19.2
github.com/gohugoio/gift v0.2.0
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20251018145728-cfcc22d823c6
github.com/gohugoio/go-radix v1.2.0
github.com/gohugoio/hashstructure v0.6.0
@@ -57,7 +57,7 @@ require (
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c
github.com/muesli/smartcrop v0.3.0
github.com/niklasfasching/go-org v1.9.1
github.com/olekukonko/tablewriter v1.1.2
github.com/olekukonko/tablewriter v1.1.3
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
github.com/pelletier/go-toml/v2 v2.2.4
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
@@ -74,12 +74,12 @@ require (
github.com/yuin/goldmark-emoji v1.0.6
go.uber.org/automaxprocs v1.5.3
gocloud.dev v0.44.0
golang.org/x/image v0.34.0
golang.org/x/mod v0.31.0
golang.org/x/net v0.48.0
golang.org/x/image v0.35.0
golang.org/x/mod v0.32.0
golang.org/x/net v0.49.0
golang.org/x/sync v0.19.0
golang.org/x/text v0.32.0
golang.org/x/tools v0.40.0
golang.org/x/text v0.33.0
golang.org/x/tools v0.41.0
google.golang.org/api v0.255.0
rsc.io/qr v0.2.0
)
@@ -109,8 +109,8 @@ require (
github.com/aws/aws-sdk-go-v2/credentials v1.18.21 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 // indirect
@@ -123,7 +123,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/sts v1.39.1 // indirect
github.com/aws/smithy-go v1.24.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/clipperhouse/displaywidth v0.6.0 // indirect
github.com/clipperhouse/displaywidth v0.6.2 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
@@ -151,7 +151,7 @@ require (
github.com/kr/text v0.2.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
@@ -159,7 +159,7 @@ require (
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
github.com/olekukonko/errors v1.1.0 // indirect
github.com/olekukonko/ll v0.1.3 // indirect
github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
@@ -177,9 +177,9 @@ require (
go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/crypto v0.47.0 // indirect
golang.org/x/oauth2 v0.32.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 // indirect
+38 -40
View File
@@ -98,12 +98,12 @@ github.com/JohannesKaufmann/html-to-markdown/v2 v2.5.0 h1:mklaPbT4f/EiDr1Q+zPrEt
github.com/JohannesKaufmann/html-to-markdown/v2 v2.5.0/go.mod h1:D56Cl9r8M5i3UwAchE+LlLc5hPN3kJtdZNVJn06lSHU=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.21.1 h1:FaSDrp6N+3pphkNKU6HPCiYLgm8dbe5UXIXcoBhZSWA=
github.com/alecthomas/chroma/v2 v2.21.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=
github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4=
github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU=
github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 h1:DHctwEM8P8iTXFxC/QK0MRjwEpWQeM9yzidCRjldUz0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3/go.mod h1:xdCzcZEtnSTKVDOmUZs4l/j3pSV6rpo1WXl5ugNsL8Y=
github.com/aws/aws-sdk-go-v2/config v1.31.17 h1:QFl8lL6RgakNK86vusim14P2k8BFSxjvUkcWLDjgz9Y=
@@ -114,16 +114,16 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 h1:T1brd5dR3/fzNFAQch/iBK
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13/go.mod h1:Peg/GBAQ6JDt+RoBf4meB1wylmAipb7Kg2ZFakZTlwk=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.3 h1:4GNV1lhyELGjMz5ILMRxDvxvOaeo3Ux9Z69S1EgVMMQ=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.3/go.mod h1:br7KA6edAAqDGUYJ+zVVPAyMrPhnN+zdt17yTUT6FPw=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13 h1:eg/WYAa12vqTphzIdWMzqYRVKKnCboVPRlvaybNCqPA=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13/go.mod h1:/FDdxWhz1486obGrKKC1HONd7krpk38LBt+dutLcN9k=
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.58.3 h1:/nyo0QD97D5VQQL/UE+rKGNKz+BesiqJgjdmp0qtTOQ=
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.58.3/go.mod h1:Jp0zmzn87l3dKarpDT/qbHNyISst5OnmzMACKuiyMvY=
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.59.0 h1:evSZnlPGyDgStAmjLK9LcSoLvEk3oSUyJz4KIFfzJEs=
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.59.0/go.mod h1:9Hd/cqshF4zl13KGLkWtRfITbvKR6m6FZHwhL2BYDSY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 h1:x2Ibm/Af8Fi+BH+Hsn9TXGdT+hKbDd5XOTZxTMxDk7o=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3/go.mod h1:IW1jwyrQgMdhisceG8fQLmQIydcT/jWY21rFhzgaKwo=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4 h1:NvMjwvv8hpGUILarKw7Z4Q0w1H9anXKsesMxtw++MA4=
@@ -160,10 +160,10 @@ github.com/bep/goportabletext v0.1.0 h1:8dqym2So1cEqVZiBa4ZnMM1R9l/DnC1h4ONg4J5k
github.com/bep/goportabletext v0.1.0/go.mod h1:6lzSTsSue75bbcyvVc0zqd1CdApuT+xkZQ6Re5DzZFg=
github.com/bep/helpers v0.6.0 h1:qtqMCK8XPFNM9hp5Ztu9piPjxNNkk8PIyUVjg6v8Bsw=
github.com/bep/helpers v0.6.0/go.mod h1:IOZlgx5PM/R/2wgyCatfsgg5qQ6rNZJNDpWGXqDR044=
github.com/bep/imagemeta v0.12.1 h1:43sIg/XJhXLVOo6troJFj9dyUr1jH+VN2UjO4/l26cQ=
github.com/bep/imagemeta v0.12.1/go.mod h1:23AF6O+4fUi9avjiydpKLStUNtJr5hJB4rarG18JpN8=
github.com/bep/lazycache v0.8.0 h1:lE5frnRjxaOFbkPZ1YL6nijzOPPz6zeXasJq8WpG4L8=
github.com/bep/lazycache v0.8.0/go.mod h1:BQ5WZepss7Ko91CGdWz8GQZi/fFnCcyWupv8gyTeKwk=
github.com/bep/imagemeta v0.14.0 h1:xmeB/XPmhrXJmSxTiE7KT4C56xfcSrcaGjVsNe+t6Ro=
github.com/bep/imagemeta v0.14.0/go.mod h1:3psQjuZwn53rPCa86ai0p4KKnO+QArpuWLRdi5/30q8=
github.com/bep/lazycache v0.8.1 h1:ko6ASLjkPxyV5DMWoNNZ8B2M0weyjqXX8IZkjBoBtvg=
github.com/bep/lazycache v0.8.1/go.mod h1:pbEiFsZoq7cLXvrTll0AHOPEurB1aGGxx4jKjOtlx9w=
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=
@@ -189,8 +189,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/clipperhouse/displaywidth v0.6.0 h1:k32vueaksef9WIKCNcoqRNyKbyvkvkysNYnAWz2fN4s=
github.com/clipperhouse/displaywidth v0.6.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
github.com/clipperhouse/displaywidth v0.6.2 h1:ZDpTkFfpHOKte4RG5O/BOyf3ysnvFswpyYrV7z2uAKo=
github.com/clipperhouse/displaywidth v0.6.2/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
@@ -209,8 +209,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
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.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
@@ -270,6 +268,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/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/gohugoio/gift v0.2.0 h1:vA31pP0rTVmBxBrhpY3WEt+4zM4g+1sDqYeemwsYeqc=
github.com/gohugoio/gift v0.2.0/go.mod h1:1Mrm5CjF33KpD749Dwj+UAjWZ3LC6cBXGuTMa5XwoP4=
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20251018145728-cfcc22d823c6 h1:pxlAea9eRwuAnt/zKbGqlFO2ZszpIe24YpOVLf+N+4I=
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20251018145728-cfcc22d823c6/go.mod h1:m5hu1im5Qc7LDycVLvee6MPobJiRLBYHklypFJR0/aE=
github.com/gohugoio/go-radix v1.2.0 h1:D5GTk8jIoeXirBSc2P4E4NdHKDrenk9k9N0ctU5Yrhg=
@@ -412,9 +412,8 @@ github.com/makeworld-the-better-one/dither/v2 v2.4.0 h1:Az/dYXiTcwcRSe59Hzw4RI1r
github.com/makeworld-the-better-one/dither/v2 v2.4.0/go.mod h1:VBtN8DXO7SNtyGmLiGA7IsFeKrBkQPze1/iAeM95arc=
github.com/marekm4/color-extractor v1.2.1 h1:3Zb2tQsn6bITZ8MBVhc33Qn1k5/SEuZ18mrXGUqIwn0=
github.com/marekm4/color-extractor v1.2.1/go.mod h1:90VjmiHI6M8ez9eYUaXLdcKnS+BAOp7w+NpwBdkJmpA=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
@@ -441,10 +440,10 @@ github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
github.com/olekukonko/ll v0.1.3 h1:sV2jrhQGq5B3W0nENUISCR6azIPf7UBUpVq0x/y70Fg=
github.com/olekukonko/ll v0.1.3/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew=
github.com/olekukonko/tablewriter v1.1.2 h1:L2kI1Y5tZBct/O/TyZK1zIE9GlBj/TVs+AY5tZDCDSc=
github.com/olekukonko/tablewriter v1.1.2/go.mod h1:z7SYPugVqGVavWoA2sGsFIoOVNmEHxUAAMrhXONtfkg=
github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0 h1:jrYnow5+hy3WRDCBypUFvVKNSPPCdqgSXIE9eJDD8LM=
github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew=
github.com/olekukonko/tablewriter v1.1.3 h1:VSHhghXxrP0JHl+0NnKid7WoEmd9/urKRJLysb70nnA=
github.com/olekukonko/tablewriter v1.1.3/go.mod h1:9VU0knjhmMkXjnMKrZ3+L2JhhtsQ/L38BbL3CRNE8tM=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
@@ -570,8 +569,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
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.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
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=
@@ -584,8 +583,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8=
golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU=
golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I=
golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -608,8 +607,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
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=
@@ -643,8 +642,8 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
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.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
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=
@@ -707,11 +706,10 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -720,8 +718,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -775,8 +773,8 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
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.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
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=
+8
View File
@@ -105,6 +105,14 @@ func DiffStrings(s1, s2 string) []string {
return DiffStringSlices(strings.Fields(s1), strings.Fields(s2))
}
// SkipSlowTestUnlessCI skips the test unless we're running in a CI server.
// Note that you can set CI_LOCAL=1 to run slow tests locally in a CI-like setup.
func SkipSlowTestUnlessCI(t testing.TB) {
if !IsCI() {
t.Skip("skipping slow test in CI")
}
}
// IsCI reports whether we're running in a CI server.
func IsCI() bool {
return (os.Getenv("CI") != "" || os.Getenv("CI_LOCAL") != "") && os.Getenv("CIRCLE_BRANCH") == ""
+4
View File
@@ -133,6 +133,10 @@ func (f *componentFsDir) ReadDirWithContext(ctx context.Context, count int) ([]i
bi, bj := fimim.PathInfo.Base(), fimjm.PathInfo.Base()
if bi == bj {
// Check mount weight first for same module (earlier mounts should win).
if fimim.ModuleOrdinal == fimjm.ModuleOrdinal && fimim.Weight != fimjm.Weight {
return fimim.Weight > fimjm.Weight
}
matrixi, matrixj := fimim.SitesMatrix, fimjm.SitesMatrix
l1, l2 := matrixi.LenVectors(), matrixj.LenVectors()
if l1 != l2 {
+4 -4
View File
@@ -22,7 +22,7 @@ import (
"github.com/gobwas/glob"
"github.com/gobwas/glob/syntax"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/identity"
)
@@ -32,9 +32,9 @@ var (
isWindows = runtime.GOOS == "windows"
defaultGlobCache = &pathGlobCache{
isWindows: isWindows,
cache: maps.NewCache[string, globErr](),
cache: hmaps.NewCache[string, globErr](),
}
dotGlobCache = maps.NewCache[string, globErr]()
dotGlobCache = hmaps.NewCache[string, globErr]()
)
type globErr struct {
@@ -47,7 +47,7 @@ type pathGlobCache struct {
isWindows bool
// Cache
cache *maps.Cache[string, globErr]
cache *hmaps.Cache[string, globErr]
}
// GetGlobDot returns a glob.Glob that matches the given pattern, using '.' as the path separator.
+2 -2
View File
@@ -21,7 +21,7 @@ import (
iofs "io/fs"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/hugofs/hglob"
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
@@ -556,5 +556,5 @@ func TestRootMappingFileFilter(t *testing.T) {
var testDims = sitesmatrix.NewTestingDimensions([]string{"en", "no"}, []string{"v1", "v2", "v3"}, []string{"admin", "editor", "viewer", "guest"})
func sitesMatrixForLangs(langs ...int) *sitesmatrix.IntSets {
return sitesmatrix.NewIntSetsBuilder(testDims).WithSets(maps.NewOrderedIntSet(langs...), nil, nil).Build()
return sitesmatrix.NewIntSetsBuilder(testDims).WithSets(hmaps.NewOrderedIntSet(langs...), nil, nil).Build()
}
+492
View File
@@ -191,3 +191,495 @@ aliases:
b.AssertPublishDir("n/index.html", "yes/index.html", "no/index.html", "yes/index.html")
}
// Issue 14381
func TestIssue14381(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = 'https://example.org'
disableKinds = ['home', 'rss', 'sitemap', 'taxonomy', 'term']
[outputFormats.print]
isHTML = IS_HTML
permalinkable = true
mediaType = 'text/html'
path = 'print'
[outputs]
page = ['html', 'print']
section = ['html', 'print']
-- content/foo/s1/_index.md --
---
title: s1
aliases: [s1-alias]
---
-- content/foo/s1/p1.md --
---
title: p1
aliases: [p1-alias]
---
-- content/foo/s2/_index.md --
---
title: s2
aliases: [/s2-alias]
---
-- content/foo/s2/p2.md --
---
title: p2
aliases: [/p2-alias]
---
-- layouts/all.html --
{{ .Title }}
`
// ------------------------------------------------------------------------
// Test 1: Create aliases for the html output format only
// ------------------------------------------------------------------------
// public/
// ├── foo/
// │ ├── s1/
// │ │ ├── p1/
// │ │ │ └── index.html
// │ │ ├── p1-alias/
// │ │ │ └── index.html
// │ │ └── index.html
// │ ├── s1-alias/
// │ │ └── index.html
// │ ├── s2/
// │ │ ├── p2/
// │ │ │ └── index.html
// │ │ └── index.html
// │ └── index.html
// ├── p2-alias/
// │ └── index.html
// ├── print/
// │ └── foo/
// │ ├── s1/
// │ │ ├── p1/
// │ │ │ └── index.html
// │ │ └── index.html
// │ ├── s2/
// │ │ ├── p2/
// │ │ │ └── index.html
// │ │ └── index.html
// │ └── index.html
// └── s2-alias/
// └── index.html
f := strings.ReplaceAll(files, "IS_HTML", "false")
b := Test(t, f)
// output format: html
b.AssertFileContent("public/foo/s1-alias/index.html",
`<title>https://example.org/foo/s1/</title>`,
`<link rel="canonical" href="https://example.org/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/foo/s1/">`,
)
b.AssertFileContent("public/foo/s1/p1-alias/index.html",
`<title>https://example.org/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://example.org/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/foo/s1/p1/">`,
)
b.AssertFileContent("public/s2-alias/index.html",
`<title>https://example.org/foo/s2/</title>`,
`<link rel="canonical" href="https://example.org/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/foo/s2/">`,
)
b.AssertFileContent("public/p2-alias/index.html",
`<title>https://example.org/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://example.org/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/foo/s2/p2/">`,
)
// output format: print
b.AssertFileExists("public/print/foo/s1-alias/index.html", false)
b.AssertFileExists("public/print/foo/s1/p1-alias/index.html", false)
b.AssertFileExists("public/print/s2-alias/index.html", false)
b.AssertFileExists("public/print/p2-alias/index.html", false)
// ------------------------------------------------------------------------
// Test 2: Create aliases for the html and print output formats
// ------------------------------------------------------------------------
// public/
// ├── foo/
// │ ├── s1/
// │ │ ├── p1/
// │ │ │ └── index.html
// │ │ ├── p1-alias/
// │ │ │ └── index.html
// │ │ └── index.html
// │ ├── s1-alias/
// │ │ └── index.html
// │ ├── s2/
// │ │ ├── p2/
// │ │ │ └── index.html
// │ │ └── index.html
// │ └── index.html
// ├── p2-alias/
// │ └── index.html
// ├── print/
// │ ├── foo/
// │ │ ├── s1/
// │ │ │ ├── p1/
// │ │ │ │ └── index.html
// │ │ │ ├── p1-alias/
// │ │ │ │ └── index.html
// │ │ │ └── index.html
// │ │ ├── s1-alias/
// │ │ │ └── index.html
// │ │ ├── s2/
// │ │ │ ├── p2/
// │ │ │ │ └── index.html
// │ │ │ └── index.html
// │ │ └── index.html
// │ ├── p2-alias/
// │ │ └── index.html
// │ └── s2-alias/
// │ └── index.html
// └── s2-alias/
// └── index.html
f = strings.ReplaceAll(files, "IS_HTML", "true")
b = Test(t, f)
// output format: html
b.AssertFileContent("public/foo/s1-alias/index.html",
`<title>https://example.org/foo/s1/</title>`,
`<link rel="canonical" href="https://example.org/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/foo/s1/">`,
)
b.AssertFileContent("public/foo/s1/p1-alias/index.html",
`<title>https://example.org/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://example.org/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/foo/s1/p1/">`,
)
b.AssertFileContent("public/s2-alias/index.html",
`<title>https://example.org/foo/s2/</title>`,
`<link rel="canonical" href="https://example.org/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/foo/s2/">`,
)
b.AssertFileContent("public/p2-alias/index.html",
`<title>https://example.org/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://example.org/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/foo/s2/p2/">`,
)
// output format: print
b.AssertFileContent("public/print/foo/s1-alias/index.html",
`<title>https://example.org/print/foo/s1/</title>`,
`<link rel="canonical" href="https://example.org/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/print/foo/s1/">`,
)
b.AssertFileContent("public/print/foo/s1/p1-alias/index.html",
`<title>https://example.org/print/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://example.org/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/print/foo/s1/p1/">`,
)
b.AssertFileContent("public/print/s2-alias/index.html",
`<title>https://example.org/print/foo/s2/</title>`,
`<link rel="canonical" href="https://example.org/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/print/foo/s2/">`,
)
b.AssertFileContent("public/print/p2-alias/index.html",
`<title>https://example.org/print/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://example.org/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/print/foo/s2/p2/">`,
)
}
// Issue 14388
func TestIssue14388(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = 'https://example.org'
disableKinds = ['home', 'rss', 'sitemap', 'taxonomy', 'term']
defaultContentLanguage = 'en'
defaultContentLanguageInSubdir = true
defaultContentRoleInSubdir = true
defaultContentVersionInSubdir = true
[languages.en]
weight = 1
# baseURL = 'https://en.example.org/'
[languages.de]
weight = 2
# baseURL = 'https://de.example.org/'
[outputFormats.print]
isHTML = true
mediaType = 'text/html'
path = 'print'
permalinkable = true
[outputs]
page = ['html', 'print']
section = ['html', 'print']
-- content/foo/s1/_index.de.md --
---
title: s1 de
aliases: [s1-alias]
---
-- content/foo/s1/_index.en.md --
---
title: s1 en
aliases: [s1-alias]
---
-- content/foo/s1/p1.de.md --
---
title: p1 de
aliases: [p1-alias]
---
-- content/foo/s1/p1.en.md --
---
title: p1 en
aliases: [p1-alias]
---
-- content/foo/s2/_index.de.md --
---
title: s2 de
aliases: [/s2-alias]
---
-- content/foo/s2/_index.en.md --
---
title: s2 en
aliases: [/s2-alias]
---
-- content/foo/s2/p2.de.md --
---
title: p2 de
aliases: [/p2-alias]
---
-- content/foo/s2/p2.en.md --
---
title: p2 en
aliases: [/p2-alias]
---
-- layouts/all.html --
{{ .Title }}
`
// ------------------------------------------------------------------------
// Test 1: Multilingual single-host
// ------------------------------------------------------------------------
b := Test(t, files)
// language: de, output format: html
b.AssertFileContent("public/guest/v1.0.0/de/foo/s1-alias/index.html",
`<title>https://example.org/guest/v1.0.0/de/foo/s1/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/de/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/de/foo/s1/">`,
)
b.AssertFileContent("public/guest/v1.0.0/de/foo/s1/p1-alias/index.html",
`<title>https://example.org/guest/v1.0.0/de/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/de/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/de/foo/s1/p1/">`,
)
b.AssertFileContent("public/guest/v1.0.0/de/s2-alias/index.html",
`<title>https://example.org/guest/v1.0.0/de/foo/s2/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/de/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/de/foo/s2/">`,
)
b.AssertFileContent("public/guest/v1.0.0/de/p2-alias/index.html",
`<title>https://example.org/guest/v1.0.0/de/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/de/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/de/foo/s2/p2/">`,
)
// language: de, output format: print
b.AssertFileContent("public/guest/v1.0.0/de/print/foo/s1-alias/index.html",
`<title>https://example.org/guest/v1.0.0/de/print/foo/s1/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/de/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/de/print/foo/s1/">`,
)
b.AssertFileContent("public/guest/v1.0.0/de/print/foo/s1/p1-alias/index.html",
`<title>https://example.org/guest/v1.0.0/de/print/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/de/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/de/print/foo/s1/p1/">`,
)
b.AssertFileContent("public/guest/v1.0.0/de/print/s2-alias/index.html",
`<title>https://example.org/guest/v1.0.0/de/print/foo/s2/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/de/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/de/print/foo/s2/">`,
)
b.AssertFileContent("public/guest/v1.0.0/de/print/p2-alias/index.html",
`<title>https://example.org/guest/v1.0.0/de/print/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/de/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/de/print/foo/s2/p2/">`,
)
// language: en, output format: html
b.AssertFileContent("public/guest/v1.0.0/en/foo/s1-alias/index.html",
`<title>https://example.org/guest/v1.0.0/en/foo/s1/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/en/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/en/foo/s1/">`,
)
b.AssertFileContent("public/guest/v1.0.0/en/foo/s1/p1-alias/index.html",
`<title>https://example.org/guest/v1.0.0/en/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/en/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/en/foo/s1/p1/">`,
)
b.AssertFileContent("public/guest/v1.0.0/en/s2-alias/index.html",
`<title>https://example.org/guest/v1.0.0/en/foo/s2/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/en/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/en/foo/s2/">`,
)
b.AssertFileContent("public/guest/v1.0.0/en/p2-alias/index.html",
`<title>https://example.org/guest/v1.0.0/en/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/en/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/en/foo/s2/p2/">`,
)
// language: en, output format: print
b.AssertFileContent("public/guest/v1.0.0/en/print/foo/s1-alias/index.html",
`<title>https://example.org/guest/v1.0.0/en/print/foo/s1/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/en/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/en/print/foo/s1/">`,
)
b.AssertFileContent("public/guest/v1.0.0/en/print/foo/s1/p1-alias/index.html",
`<title>https://example.org/guest/v1.0.0/en/print/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/en/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/en/print/foo/s1/p1/">`,
)
b.AssertFileContent("public/guest/v1.0.0/en/print/s2-alias/index.html",
`<title>https://example.org/guest/v1.0.0/en/print/foo/s2/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/en/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/en/print/foo/s2/">`,
)
b.AssertFileContent("public/guest/v1.0.0/en/print/p2-alias/index.html",
`<title>https://example.org/guest/v1.0.0/en/print/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/en/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/en/print/foo/s2/p2/">`,
)
// ------------------------------------------------------------------------
// Test 2: Multilingual multihost
// ------------------------------------------------------------------------
files = strings.ReplaceAll(files, "# baseURL", "baseURL")
b = Test(t, files)
// language: de, output format: html
b.AssertFileContent("public/de/guest/v1.0.0/foo/s1-alias/index.html",
`<title>https://de.example.org/guest/v1.0.0/foo/s1/</title>`,
`<link rel="canonical" href="https://de.example.org/guest/v1.0.0/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://de.example.org/guest/v1.0.0/foo/s1/">`,
)
b.AssertFileContent("public/de/guest/v1.0.0/foo/s1/p1-alias/index.html",
`<title>https://de.example.org/guest/v1.0.0/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://de.example.org/guest/v1.0.0/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://de.example.org/guest/v1.0.0/foo/s1/p1/">`,
)
b.AssertFileContent("public/de/guest/v1.0.0/s2-alias/index.html",
`<title>https://de.example.org/guest/v1.0.0/foo/s2/</title>`,
`<link rel="canonical" href="https://de.example.org/guest/v1.0.0/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://de.example.org/guest/v1.0.0/foo/s2/">`,
)
b.AssertFileContent("public/de/guest/v1.0.0/p2-alias/index.html",
`<title>https://de.example.org/guest/v1.0.0/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://de.example.org/guest/v1.0.0/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://de.example.org/guest/v1.0.0/foo/s2/p2/">`,
)
// language: de, output format: print
b.AssertFileContent("public/de/guest/v1.0.0/print/foo/s1-alias/index.html",
`<title>https://de.example.org/guest/v1.0.0/print/foo/s1/</title>`,
`<link rel="canonical" href="https://de.example.org/guest/v1.0.0/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://de.example.org/guest/v1.0.0/print/foo/s1/">`,
)
b.AssertFileContent("public/de/guest/v1.0.0/print/foo/s1/p1-alias/index.html",
`<title>https://de.example.org/guest/v1.0.0/print/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://de.example.org/guest/v1.0.0/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://de.example.org/guest/v1.0.0/print/foo/s1/p1/">`,
)
b.AssertFileContent("public/de/guest/v1.0.0/print/s2-alias/index.html",
`<title>https://de.example.org/guest/v1.0.0/print/foo/s2/</title>`,
`<link rel="canonical" href="https://de.example.org/guest/v1.0.0/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://de.example.org/guest/v1.0.0/print/foo/s2/">`,
)
b.AssertFileContent("public/de/guest/v1.0.0/print/p2-alias/index.html",
`<title>https://de.example.org/guest/v1.0.0/print/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://de.example.org/guest/v1.0.0/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://de.example.org/guest/v1.0.0/print/foo/s2/p2/">`,
)
// language: en, output format: html
b.AssertFileContent("public/en/guest/v1.0.0/foo/s1-alias/index.html",
`<title>https://en.example.org/guest/v1.0.0/foo/s1/</title>`,
`<link rel="canonical" href="https://en.example.org/guest/v1.0.0/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://en.example.org/guest/v1.0.0/foo/s1/">`,
)
b.AssertFileContent("public/en/guest/v1.0.0/foo/s1/p1-alias/index.html",
`<title>https://en.example.org/guest/v1.0.0/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://en.example.org/guest/v1.0.0/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://en.example.org/guest/v1.0.0/foo/s1/p1/">`,
)
b.AssertFileContent("public/en/guest/v1.0.0/s2-alias/index.html",
`<title>https://en.example.org/guest/v1.0.0/foo/s2/</title>`,
`<link rel="canonical" href="https://en.example.org/guest/v1.0.0/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://en.example.org/guest/v1.0.0/foo/s2/">`,
)
b.AssertFileContent("public/en/guest/v1.0.0/p2-alias/index.html",
`<title>https://en.example.org/guest/v1.0.0/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://en.example.org/guest/v1.0.0/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://en.example.org/guest/v1.0.0/foo/s2/p2/">`,
)
// language: en, output format: print
b.AssertFileContent("public/en/guest/v1.0.0/print/foo/s1-alias/index.html",
`<title>https://en.example.org/guest/v1.0.0/print/foo/s1/</title>`,
`<link rel="canonical" href="https://en.example.org/guest/v1.0.0/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://en.example.org/guest/v1.0.0/print/foo/s1/">`,
)
b.AssertFileContent("public/en/guest/v1.0.0/print/foo/s1/p1-alias/index.html",
`<title>https://en.example.org/guest/v1.0.0/print/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://en.example.org/guest/v1.0.0/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://en.example.org/guest/v1.0.0/print/foo/s1/p1/">`,
)
b.AssertFileContent("public/en/guest/v1.0.0/print/s2-alias/index.html",
`<title>https://en.example.org/guest/v1.0.0/print/foo/s2/</title>`,
`<link rel="canonical" href="https://en.example.org/guest/v1.0.0/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://en.example.org/guest/v1.0.0/print/foo/s2/">`,
)
b.AssertFileContent("public/en/guest/v1.0.0/print/p2-alias/index.html",
`<title>https://en.example.org/guest/v1.0.0/print/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://en.example.org/guest/v1.0.0/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://en.example.org/guest/v1.0.0/print/foo/s2/p2/">`,
)
}
// Issue #14402.
func TestComprehensiveAliasesRedirectsFile(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableAliases = true
defaultContentLanguageInSubdir = true
defaultContentRoleInSubdir = true
defaultContentVersionInSubdir = true
baseURL = "https://example.org/"
-- content/foo/p1.md --
---
aliases: ["/foo/p2/", "../p3/"]
---
-- content/foo/p2.md --
-- content/p3.md --
-- layouts/home.html --
Home.
{{ range $p := site.RegularPages }}{{ range .Aliases }}{{ . | printf "%-35s" }}=>{{ $p.RelPermalink -}}|{{ end -}}{{ end }}
`
b := Test(t, files)
b.AssertFileContent("public/guest/v1.0.0/en/index.html",
"/guest/v1.0.0/en/foo/p2 =>/guest/v1.0.0/en/foo/p1/|",
"/guest/v1.0.0/en/p3 =>/guest/v1.0.0/en/foo/p1/|",
)
}
+63
View File
@@ -416,3 +416,66 @@ title: p1 (en)
b.AssertFileContent("public/en/s1/p1/index.html", "|color: red (en)|size: medium|") // fails: file contains "|color: red (en)|size: |"
b.AssertFileContent("public/de/s1/p1/index.html", "|color: red (de)|size: medium|")
}
// Issue 14409
func TestCascadeDraftTrue14409(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['rss','taxonomy','term']
defaultContentLanguage = 'en'
defaultContentLanguageInSubdir = true
[languages.en]
weight = 1
[languages.de]
weight = 2
[[cascade]]
draft = true
[cascade.target.sites.matrix]
languages = ['en']
-- content/_index.en.md --
---
title: home en
draft: false
---
-- content/_index.de.md --
---
title: home de
draft: false
---
-- content/s1/_index.en.md --
---
title: s1 en
draft: false
---
-- content/s1/_index.de.md --
---
title: s1 de
draft: false
---
-- content/s1/p1.en.md --
---
title: p1 en
---
-- content/s1/p1.de.md --
---
title: p1 de
---
-- layouts/all.html --
{{ .Title }}
`
b := Test(t, files)
b.AssertFileExists("public/de/index.html", true)
b.AssertFileExists("public/en/index.html", true)
b.AssertFileExists("public/de/s1/index.html", true)
b.AssertFileExists("public/en/s1/index.html", true)
b.AssertFileExists("public/de/s1/p1/index.html", true)
b.AssertFileExists("public/en/s1/p1/index.html", false)
}
+14 -12
View File
@@ -20,11 +20,12 @@ import (
"testing"
"github.com/bep/logg"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/allconfig"
"github.com/gohugoio/hugo/htesting"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/maps"
"github.com/spf13/afero"
)
@@ -53,8 +54,8 @@ title = "English Comments Title"
enSite := b.H.Sites[0]
b.Assert(enSite.Title(), qt.Equals, "English Title")
b.Assert(enSite.Home().Title(), qt.Equals, "English Title")
b.Assert(enSite.Params(), qt.DeepEquals, maps.Params{
"comments": maps.Params{
b.Assert(enSite.Params(), qt.DeepEquals, hmaps.Params{
"comments": hmaps.Params{
"color": "blue",
"title": "English Comments Title",
},
@@ -424,13 +425,13 @@ name = "menu-theme"
got := b.H.Configs.Base
b.Assert(got.Params, qt.DeepEquals, maps.Params{
"b": maps.Params{
b.Assert(got.Params, qt.DeepEquals, hmaps.Params{
"b": hmaps.Params{
"b1": "b1 main",
"c": maps.Params{
"c": hmaps.Params{
"bc1": "bc1 main",
"bc2": "bc2 theme",
"d": maps.Params{"bcd1": string("bcd1 theme")},
"d": hmaps.Params{"bcd1": string("bcd1 theme")},
},
"b2": "b2 theme",
},
@@ -447,11 +448,11 @@ name = "menu-theme"
got := b.H.Configs.Base.Params
// Shallow merge, only add new keys to params.
b.Assert(got, qt.DeepEquals, maps.Params{
b.Assert(got, qt.DeepEquals, hmaps.Params{
"p1": "p1 main",
"b": maps.Params{
"b": hmaps.Params{
"b1": "b1 main",
"c": maps.Params{
"c": hmaps.Params{
"bc1": "bc1 main",
},
},
@@ -468,7 +469,7 @@ name = "menu-theme"
got := b.H.Configs.Base.Params
b.Assert(got, qt.DeepEquals, maps.Params{
b.Assert(got, qt.DeepEquals, hmaps.Params{
"p1": "p1 theme",
})
})
@@ -534,7 +535,7 @@ t3: {{ .Site.Params.t3 }}
got := b.H.Configs.Base.Params
b.Assert(got, qt.DeepEquals, maps.Params{
b.Assert(got, qt.DeepEquals, hmaps.Params{
"m1": "mv1",
"m2": "mv2",
"t1": "tv1",
@@ -711,6 +712,7 @@ themeconfigdirparam: {{ site.Params.themeconfigdirparam }}
// Issue #11089
func TestHugoConfigSliceOverrides(t *testing.T) {
htesting.SkipSlowTestUnlessCI(t)
t.Parallel()
filesTemplate := `
+5 -6
View File
@@ -26,6 +26,7 @@ import (
"github.com/bep/logg"
"github.com/gohugoio/go-radix"
"github.com/gohugoio/hugo/cache/dynacache"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/predicate"
@@ -39,8 +40,6 @@ import (
"github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/resources/kinds"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/resource"
@@ -100,7 +99,7 @@ type pageMap struct {
cacheContentPlain *dynacache.Partition[string, *resources.StaleValue[contentPlainPlainWords]]
contentTableOfContents *dynacache.Partition[string, *resources.StaleValue[contentTableOfContents]]
contentDataFileSeenItems *maps.Cache[string, map[uint64]bool]
contentDataFileSeenItems *hmaps.Cache[string, map[uint64]bool]
cfg contentMapConfig
}
@@ -710,7 +709,7 @@ func newPageMap(s *Site, mcache *dynacache.Cache, pageTrees *pageTrees) *pageMap
cacheContentPlain: dynacache.GetOrCreatePartition[string, *resources.StaleValue[contentPlainPlainWords]](mcache, fmt.Sprintf("/cont/pla/%s", languageVersionRole), dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
contentTableOfContents: dynacache.GetOrCreatePartition[string, *resources.StaleValue[contentTableOfContents]](mcache, fmt.Sprintf("/cont/toc/%s", languageVersionRole), dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
contentDataFileSeenItems: maps.NewCache[string, map[uint64]bool](),
contentDataFileSeenItems: hmaps.NewCache[string, map[uint64]bool](),
cfg: contentMapConfig{
lang: s.Lang(),
@@ -756,13 +755,13 @@ func newPageMap(s *Site, mcache *dynacache.Cache, pageTrees *pageTrees) *pageMap
func newContentTreeTreverseIndex(init func(get func(key any) (contentNode, bool), set func(key any, val contentNode))) *contentTreeReverseIndex {
return &contentTreeReverseIndex{
initFn: init,
mm: maps.NewCache[any, contentNode](),
mm: hmaps.NewCache[any, contentNode](),
}
}
type contentTreeReverseIndex struct {
initFn func(get func(key any) (contentNode, bool), set func(key any, val contentNode))
mm *maps.Cache[any, contentNode]
mm *hmaps.Cache[any, contentNode]
}
func (c *contentTreeReverseIndex) Reset() {
+12 -16
View File
@@ -22,7 +22,7 @@ import (
"time"
"github.com/gohugoio/go-radix"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/hugofs/files"
@@ -54,9 +54,9 @@ type allPagesAssembler struct {
rwRoot *doctree.NodeShiftTreeWalker[contentNode] // walks resources.
// Walking state.
seenTerms *maps.Map[term, sitesmatrix.Vectors]
droppedPages *maps.Map[*Site, []string] // e.g. drafts, expired, future.
seenRootSections *maps.Map[string, bool]
seenTerms *hmaps.Map[term, sitesmatrix.Vectors]
droppedPages *hmaps.Map[*Site, []string] // e.g. drafts, expired, future.
seenRootSections *hmaps.Map[string, bool]
seenHome bool // set before we fan out to multiple goroutines.
}
@@ -75,7 +75,7 @@ func newAllPagesAssembler(
pw := rw.Extend()
pw.Tree = m.treePages
seenRootSections := maps.NewMap[string, bool]()
seenRootSections := hmaps.NewMap[string, bool]()
seenRootSections.Set("", true) // home.
return &allPagesAssembler{
@@ -83,10 +83,10 @@ func newAllPagesAssembler(
h: h,
m: m,
assembleChanges: assembleChanges,
seenTerms: maps.NewMap[term, sitesmatrix.Vectors](),
droppedPages: maps.NewMap[*Site, []string](),
seenTerms: hmaps.NewMap[term, sitesmatrix.Vectors](),
droppedPages: hmaps.NewMap[*Site, []string](),
seenRootSections: seenRootSections,
assembleSectionsInParallel: true,
assembleSectionsInParallel: !h.isRebuild(), // On partial rebuilds, there's potential data races with parallel section assembly.
pwRoot: pw,
rwRoot: rw,
}
@@ -273,27 +273,23 @@ func (a *allPagesAssembler) doCreatePages(prefix string, depth int) error {
return false
}
var drop bool
if !site.shouldBuild(p) {
(&p.m.pageConfig.Build).Disable()
switch p.Kind() {
case kinds.KindHome, kinds.KindSection, kinds.KindTaxonomy:
// We need to keep these for the structure, but disable
// them so they don't get listed/rendered.
(&p.m.pageConfig.Build).Disable()
// We need to keep these for the structure.
default:
// Skip this page.
// Drop this page.
a.droppedPages.WithWriteLock(
func(m map[*Site][]string) error {
m[site] = append(m[site], s)
return nil
},
)
drop = true
}
}
if !drop && n == nil {
if n == nil {
if n2 == nil {
// Avoid creating a map for one node.
n2 = p
@@ -40,6 +40,18 @@ func (s *contentNodeShifter) Delete(n contentNode, vec sitesmatrix.Vector) (cont
}
resource.MarkStale(v)
return v, true, true
case contentNodes:
var deleted contentNodes
for i, nn := range v {
if vv, ok, _ := s.Delete(nn, vec); ok {
deleted = append(deleted, vv)
v = append(v[:i], v[i+1:]...)
}
}
if len(deleted) == 0 {
return nil, false, false
}
return deleted, true, len(v) == 0
default:
v = v.(contentNodeSingle) // Ensure single node.
resource.MarkStale(v)
+1 -1
View File
@@ -190,7 +190,7 @@ DATA
t.Run("Build config, no render link", func(t *testing.T) {
files := filesForDisabledKind("")
b := Test(t, files)
b.AssertFileExists("public/sect/no-render/index.html", false)
b.AssertFileExists("public/sect/no-render-link/index.html", false)
b.AssertFileContent("public/link-alias/index.html", "refresh")
})
+3 -3
View File
@@ -21,7 +21,7 @@ import (
radix "github.com/gohugoio/go-radix"
"github.com/gohugoio/hugo/common/collections"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
)
@@ -70,7 +70,7 @@ func (ctx *WalkContext[T]) Data() *SimpleThreadSafeTree[any] {
func (ctx *WalkContext[T]) initDataRaw() {
ctx.dataRawInit.Do(func() {
ctx.dataRaw = maps.NewCache[sitesmatrix.Vector, *SimpleThreadSafeTree[any]]()
ctx.dataRaw = hmaps.NewCache[sitesmatrix.Vector, *SimpleThreadSafeTree[any]]()
})
}
@@ -215,7 +215,7 @@ type WalkContext[T any] struct {
data *SimpleThreadSafeTree[any]
dataInit sync.Once
dataRaw *maps.Cache[sitesmatrix.Vector, *SimpleThreadSafeTree[any]]
dataRaw *hmaps.Cache[sitesmatrix.Vector, *SimpleThreadSafeTree[any]]
dataRawInit sync.Once
eventHandlersMu sync.Mutex
+28
View File
@@ -160,3 +160,31 @@ title: p10
variant = `{{ template "_internal/pagination.html" (dict "page" . "format" "terse") }}`
test(variant, expectedOutputTerseFormat)
}
func TestOpengraphPartialIssue14433(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "https://example.org"
-- content/_index.md --
---
title: Home
---
-- layouts/home.html --
<!DOCTYPE html>
<html lang="en">
<head>
{{- partial "opengraph.html" . }}
</head>
<body>
</body>
</html>
`
b := Test(t, files)
b.AssertFileContent("public/index.html",
`<meta property="og:title" content="Home">`,
`! false`,
)
}
+4 -4
View File
@@ -38,10 +38,10 @@ import (
"github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hsync"
"github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/para"
"github.com/gohugoio/hugo/common/terminal"
"github.com/gohugoio/hugo/common/types"
@@ -95,11 +95,11 @@ type HugoSites struct {
// Now the different page dimensions (e.g. language) are built-in to the page trees above.
// But we sill need to support the overridden translationKey, but that should
// be relatively rare and low volume.
translationKeyPages *maps.SliceCache[page.Page]
translationKeyPages *hmaps.SliceCache[page.Page]
pageTrees *pageTrees
previousPageTreesWalkContext *doctree.WalkContext[contentNode] // Set for rebuilds only.
previousSeenTerms *maps.Map[term, sitesmatrix.Vectors] // Set for rebuilds only.
previousPageTreesWalkContext *doctree.WalkContext[contentNode] // Set for rebuilds only.
previousSeenTerms *hmaps.Map[term, sitesmatrix.Vectors] // Set for rebuilds only.
printUnusedTemplatesInit sync.Once
printPathWarningsInit sync.Once
+3 -1
View File
@@ -1034,7 +1034,9 @@ func (h *HugoSites) processPartialFileEvents(ctx context.Context, l logg.LevelLo
changes = append(changes, identity.GenghisKhan)
}
if strings.Contains(base, "shortcodes") {
changes = append(changes, hglob.NewGlobIdentity(fmt.Sprintf("shortcodes/%s*", pathInfo.BaseNameNoIdentifier())))
// Add both the shortcode file itself (for template refresh) and a glob for dependent content
changes = append(changes, pathInfo)
changes = append(changes, hglob.NewGlobIdentity(fmt.Sprintf("/_shortcodes/%s*", pathInfo.BaseNameNoIdentifier())))
} else {
changes = append(changes, pathInfo)
}
+2 -2
View File
@@ -562,7 +562,7 @@ func TestSiteBuildTimeout(t *testing.T) {
var filesBuilder strings.Builder
filesBuilder.WriteString(`
-- hugo.toml --
timeout = 5
timeout = '100ms'
-- layouts/single.html --
{{ .WordCount }}
-- layouts/_shortcodes/c.html --
@@ -571,7 +571,7 @@ timeout = 5
{{ end }}
`)
for i := 1; i < 100; i++ {
for i := range 20 {
filesBuilder.WriteString(fmt.Sprintf(`
-- content/page%d.md --
---
+3 -3
View File
@@ -26,8 +26,8 @@ import (
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/hexec"
"github.com/gohugoio/hugo/common/himage"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/allconfig"
@@ -858,12 +858,12 @@ func (s *IntegrationTestBuilder) initBuilder() error {
}
if s.Cfg.Running {
flags.Set("internal", maps.Params{
flags.Set("internal", hmaps.Params{
"running": s.Cfg.Running,
"watch": s.Cfg.Running,
})
} else if s.Cfg.Watching {
flags.Set("internal", maps.Params{
flags.Set("internal", hmaps.Params{
"watch": s.Cfg.Watching,
})
}
+3 -3
View File
@@ -29,9 +29,9 @@ import (
"github.com/bep/logg"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/predicate"
"github.com/gohugoio/hugo/common/types/hstring"
"github.com/gohugoio/hugo/helpers"
@@ -114,7 +114,7 @@ func (m *pageMeta) newCachedContent(s *Site) (*cachedContent, error) {
StaleInfo: m,
pi: m.pi,
enableEmoji: s.conf.EnableEmoji,
scopes: maps.NewCache[string, *cachedContentScope](),
scopes: hmaps.NewCache[string, *cachedContentScope](),
}
var hasName predicate.P[string] = m.pi.shortcodeParseInfo.hasName
c.hasShortcode.Store(&hasName)
@@ -136,7 +136,7 @@ type cachedContent struct {
// Whether the content has a given shortcode name.
hasShortcode atomic.Pointer[predicate.P[string]]
scopes *maps.Cache[string, *cachedContentScope]
scopes *hmaps.Cache[string, *cachedContentScope]
}
func (c *cachedContent) getOrCreateScope(scope string, pco *pageContentOutput) *cachedContentScope {
+12 -17
View File
@@ -15,13 +15,12 @@ package hugolib
import (
"fmt"
"maps"
"path/filepath"
"regexp"
"strings"
"time"
xmaps "maps"
"github.com/bep/logg"
"github.com/gobuffalo/flect"
"github.com/gohugoio/hugo/hugofs"
@@ -36,10 +35,10 @@ import (
"github.com/gohugoio/hugo/common/hashing"
"github.com/gohugoio/hugo/common/hiter"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
@@ -373,7 +372,7 @@ func (s *Site) newPageMetaFromPageMetasource(ms *pageMetaSource) (*pageMeta, err
m := &pageMeta{
pageMetaSource: ms,
pageConfig: &pagemeta.PageConfigLate{
Params: make(maps.Params),
Params: make(hmaps.Params),
},
}
return m, nil
@@ -385,10 +384,6 @@ func (m *pageMeta) prepareRebuild() {
m.pageConfig.Dates = m.datesOriginal
}
func (m *pageMeta) Aliases() []string {
return m.pageConfig.Aliases
}
func (m *pageMeta) BundleType() string {
switch m.pathInfo.Type() {
case paths.TypeLeaf:
@@ -470,7 +465,7 @@ func (m *pageMeta) IsPage() bool {
return m.Kind() == kinds.KindPage
}
func (m *pageMeta) Params() maps.Params {
func (m *pageMeta) Params() hmaps.Params {
return m.pageConfig.Params
}
@@ -524,17 +519,17 @@ func (m *pageMeta) Weight() int {
func (ps *pageState) setMetaPost(cascades *page.PageMatcherParamsConfigs) error {
if ps.m.pageConfigSource.Frontmatter != nil {
if ps.m.pageConfigSource.IsFromContentAdapter {
ps.m.pageConfig.ContentAdapterData = xmaps.Clone(ps.m.pageConfigSource.Frontmatter)
ps.m.pageConfig.ContentAdapterData = maps.Clone(ps.m.pageConfigSource.Frontmatter)
} else {
ps.m.pageConfig.Params = xmaps.Clone(ps.m.pageConfigSource.Frontmatter)
ps.m.pageConfig.Params = maps.Clone(ps.m.pageConfigSource.Frontmatter)
}
}
if ps.m.pageConfig.Params == nil {
ps.m.pageConfig.Params = make(maps.Params)
ps.m.pageConfig.Params = make(hmaps.Params)
}
if ps.m.pageConfigSource.IsFromContentAdapter && ps.m.pageConfig.ContentAdapterData == nil {
ps.m.pageConfig.ContentAdapterData = make(maps.Params)
ps.m.pageConfig.ContentAdapterData = make(hmaps.Params)
}
// Cascade defined on itself has higher priority than inherited ones.
@@ -660,7 +655,7 @@ params:
loki := strings.ToLower(k)
if loki == "params" {
vv, err := maps.ToStringMapE(v)
vv, err := hmaps.ToStringMapE(v)
if err != nil {
return err
}
@@ -753,7 +748,7 @@ params:
}
pcfg.Params[loki] = pcfg.Aliases
case "sitemap":
pcfg.Sitemap, err = config.DecodeSitemap(ps.s.conf.Sitemap, maps.ToStringMap(v))
pcfg.Sitemap, err = config.DecodeSitemap(ps.s.conf.Sitemap, hmaps.ToStringMap(v))
if err != nil {
return fmt.Errorf("failed to decode sitemap config in front matter: %s", err)
}
@@ -771,7 +766,7 @@ params:
switch vv := v.(type) {
case []map[any]any:
for _, vvv := range vv {
resources = append(resources, maps.ToStringMap(vvv))
resources = append(resources, hmaps.ToStringMap(vvv))
}
case []map[string]any:
resources = append(resources, vv...)
@@ -779,7 +774,7 @@ params:
for _, vvv := range vv {
switch vvvv := vvv.(type) {
case map[any]any:
resources = append(resources, maps.ToStringMap(vvvv))
resources = append(resources, hmaps.ToStringMap(vvvv))
case map[string]any:
resources = append(resources, vvvv)
}
+36
View File
@@ -15,6 +15,8 @@ package hugolib
import (
"fmt"
"path"
"strings"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/output"
@@ -113,6 +115,40 @@ type pageOutput struct {
renderOnce bool // To make sure we at least try to render it once.
}
func (po *pageOutput) Aliases() []string {
conf := po.p.s.conf
p := po.p
f := po.f
// This is relatively cheap to create, and the common case is to call this once.
// So avoid caching this value.
aliases := make([]string, len(po.p.m.pageConfig.Aliases))
for i, a := range po.p.m.pageConfig.Aliases {
isRelative := !strings.HasPrefix(a, "/")
var baseDir string
if isRelative {
// Form the baseDir by taking the resource's base target
// and moving up one level to the parent.
parentContext := path.Join(p.targetPaths().SubResourceBaseTarget, "..")
baseDir = parentContext
} else {
// Form the baseDir by prepending the content dimension
// prefixes with the Output Format path.
baseDir = path.Join("/", p.targetPathDescriptor.PrefixFilePath, f.Path)
}
a = path.Join(baseDir, a)
if conf.C.IsUglyURLSection(p.Section()) && !strings.HasSuffix(a, ".html") {
a += ".html"
}
aliases[i] = a
}
return aliases
}
func (po *pageOutput) incrRenderState() {
po.renderState++
po.renderOnce = true
+3 -3
View File
@@ -22,7 +22,7 @@ import (
"sync"
"sync/atomic"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/text"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/tpl/tplimpl"
@@ -67,7 +67,7 @@ func newPageContentOutput(po *pageOutput) (*pageContentOutput, error) {
cp := &pageContentOutput{
po: po,
renderHooks: &renderHooks{},
otherOutputs: maps.NewCache[uint64, *pageContentOutput](),
otherOutputs: hmaps.NewCache[uint64, *pageContentOutput](),
}
return cp, nil
}
@@ -83,7 +83,7 @@ type pageContentOutput struct {
// Other pages involved in rendering of this page,
// typically included with .RenderShortcodes.
otherOutputs *maps.Cache[uint64, *pageContentOutput]
otherOutputs *hmaps.Cache[uint64, *pageContentOutput]
contentRenderedVersion uint32 // Incremented on reset.
contentRendered atomic.Bool // Set on content render.
+14 -1
View File
@@ -571,6 +571,7 @@ date: 2012-01-12
func TestPageSummary(t *testing.T) {
t.Parallel()
htesting.SkipSlowTestUnlessCI(t)
assertFunc := func(t *testing.T, ext string, pages page.Pages) {
p := pages[0]
checkPageTitle(t, p, "SimpleWithoutSummaryDelimiter")
@@ -586,6 +587,7 @@ func TestPageSummary(t *testing.T) {
}
func TestPageWithDelimiter(t *testing.T) {
htesting.SkipSlowTestUnlessCI(t)
t.Parallel()
assertFunc := func(t *testing.T, ext string, pages page.Pages) {
p := pages[0]
@@ -600,6 +602,7 @@ func TestPageWithDelimiter(t *testing.T) {
func TestPageWithSummaryParameter(t *testing.T) {
t.Parallel()
htesting.SkipSlowTestUnlessCI(t)
assertFunc := func(t *testing.T, ext string, pages page.Pages) {
p := pages[0]
checkPageTitle(t, p, "SimpleWithSummaryParameter")
@@ -617,6 +620,9 @@ func TestPageWithSummaryParameter(t *testing.T) {
// Issue #3854
// Also see https://github.com/gohugoio/hugo/issues/3977
func TestPageWithDateFields(t *testing.T) {
t.Parallel()
htesting.SkipSlowTestUnlessCI(t)
c := qt.New(t)
pageWithDate := `---
title: P%d
@@ -633,7 +639,6 @@ Simple Page With Some Date`
return fmt.Sprintf(pageWithDate, weight, weight, field)
}
t.Parallel()
assertFunc := func(t *testing.T, ext string, pages page.Pages) {
c.Assert(len(pages) > 0, qt.Equals, true)
for _, p := range pages {
@@ -698,6 +703,7 @@ baseURL = "http://example.com/"
}
func TestPageWithMoreTag(t *testing.T) {
htesting.SkipSlowTestUnlessCI(t)
t.Parallel()
assertFunc := func(t *testing.T, ext string, pages page.Pages) {
p := pages[0]
@@ -797,6 +803,7 @@ Content: {{ .Content }}|
// #2973
func TestSummaryWithHTMLTagsOnNextLine(t *testing.T) {
htesting.SkipSlowTestUnlessCI(t)
assertFunc := func(t *testing.T, ext string, pages page.Pages) {
c := qt.New(t)
p := pages[0]
@@ -875,6 +882,7 @@ home = ["HTML", "JSON"]
// Issue 8919
func TestContentProviderWithCustomOutputFormat(t *testing.T) {
t.Parallel()
htesting.SkipSlowTestUnlessCI(t)
files := `
-- hugo.toml --
@@ -1150,6 +1158,7 @@ date = ["` + dateHandler + `", "date"]
func TestWordCountWithAllCJKRunesWithoutHasCJKLanguage(t *testing.T) {
t.Parallel()
htesting.SkipSlowTestUnlessCI(t)
assertFunc := func(t *testing.T, ext string, pages page.Pages) {
p := pages[0]
if p.WordCount(context.Background()) != 8 {
@@ -1162,6 +1171,7 @@ func TestWordCountWithAllCJKRunesWithoutHasCJKLanguage(t *testing.T) {
func TestWordCountWithAllCJKRunesHasCJKLanguage(t *testing.T) {
t.Parallel()
htesting.SkipSlowTestUnlessCI(t)
settings := map[string]any{"hasCJKLanguage": true}
assertFunc := func(t *testing.T, ext string, pages page.Pages) {
@@ -1175,6 +1185,7 @@ func TestWordCountWithAllCJKRunesHasCJKLanguage(t *testing.T) {
func TestWordCountWithMainEnglishWithCJKRunes(t *testing.T) {
t.Parallel()
htesting.SkipSlowTestUnlessCI(t)
settings := map[string]any{"hasCJKLanguage": true}
assertFunc := func(t *testing.T, ext string, pages page.Pages) {
@@ -1189,6 +1200,7 @@ func TestWordCountWithMainEnglishWithCJKRunes(t *testing.T) {
func TestWordCountWithIsCJKLanguageFalse(t *testing.T) {
t.Parallel()
htesting.SkipSlowTestUnlessCI(t)
settings := map[string]any{
"hasCJKLanguage": true,
}
@@ -1205,6 +1217,7 @@ func TestWordCountWithIsCJKLanguageFalse(t *testing.T) {
func TestWordCount(t *testing.T) {
t.Parallel()
htesting.SkipSlowTestUnlessCI(t)
assertFunc := func(t *testing.T, ext string, pages page.Pages) {
p := pages[0]
if p.WordCount(context.Background()) != 483 {
+8 -8
View File
@@ -20,8 +20,8 @@ import (
"path/filepath"
"github.com/gohugoio/hugo/common/hashing"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstore"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
@@ -65,7 +65,7 @@ type pagesFromDataTemplateContext struct {
}
func (p *pagesFromDataTemplateContext) toPathSitesMap(v any) (string, map[string]any, map[string]any, error) {
m, err := maps.ToStringMapE(v)
m, err := hmaps.ToStringMapE(v)
if err != nil {
return "", nil, nil, err
}
@@ -75,7 +75,7 @@ func (p *pagesFromDataTemplateContext) toPathSitesMap(v any) (string, map[string
return "", nil, nil, fmt.Errorf("invalid path %q", path)
}
sites := maps.ToStringMap(m["sites"])
sites := hmaps.ToStringMap(m["sites"])
return path, sites, m, nil
}
@@ -161,7 +161,7 @@ func NewPagesFromTemplate(opts PagesFromTemplateOptions) *PagesFromTemplate {
PagesFromTemplateOptions: opts,
PagesFromTemplateDeps: opts.DepsFromSite(opts.Site),
buildState: &BuildState{
sourceInfosCurrent: maps.NewCache[string, *sourceInfo](),
sourceInfosCurrent: hmaps.NewCache[string, *sourceInfo](),
},
store: hstore.NewScratch(),
}
@@ -231,8 +231,8 @@ type BuildState struct {
NumPagesAdded uint64
NumResourcesAdded uint64
sourceInfosCurrent *maps.Cache[string, *sourceInfo]
sourceInfosPrevious *maps.Cache[string, *sourceInfo]
sourceInfosCurrent *hmaps.Cache[string, *sourceInfo]
sourceInfosPrevious *hmaps.Cache[string, *sourceInfo]
}
func (b *BuildState) hash(v any) uint64 {
@@ -304,7 +304,7 @@ func (b *BuildState) resolveDeletedPaths() {
func (b *BuildState) PrepareNextBuild() {
b.sourceInfosPrevious = b.sourceInfosCurrent
b.sourceInfosCurrent = maps.NewCache[string, *sourceInfo]()
b.sourceInfosCurrent = hmaps.NewCache[string, *sourceInfo]()
b.StaleVersion = 0
b.DeletedPaths = nil
b.ChangedIdentities = nil
@@ -317,7 +317,7 @@ func (p PagesFromTemplate) CloneForSite(s page.Site) *PagesFromTemplate {
p.PagesFromTemplateOptions.Site = s
p.PagesFromTemplateDeps = p.PagesFromTemplateOptions.DepsFromSite(s)
p.buildState = &BuildState{
sourceInfosCurrent: maps.NewCache[string, *sourceInfo](),
sourceInfosCurrent: hmaps.NewCache[string, *sourceInfo](),
}
return &p
}
+36
View File
@@ -2093,3 +2093,39 @@ All.
b.EditFileReplaceAll("content/mybundle/resource.txt", "This is a resource file.", "This is an edited resource file.").Build()
b.AssertFileContent("public/mybundle/resource.txt", "This is an edited resource file.")
}
// Issue #14207
func TestRebuildAddShortcodeThenEditContentUsingIt(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "https://example.com"
disableLiveReload = true
-- layouts/single.html --
Single: {{ .Title }}|{{ .Content }}|
-- content/p1.md --
---
title: "P1"
---
Content before shortcode.
`
b := TestRunning(t, files)
b.AssertFileContent("public/p1/index.html", "Single: P1|", "Content before shortcode.")
// Add a new shortcode file
b.AddFiles(
"layouts/_shortcodes/year.html", `{{- now.Format "2006" -}}`,
).Build()
// Verify shortcode was registered
ts := b.H.Sites[0].Deps.TemplateStore
if ti := ts.LookupShortcodeByName("year"); ti == nil {
t.Fatal("shortcode 'year' not found in cache after adding")
}
// Now edit content to use the shortcode - this should find the shortcode
currentYear := time.Now().Format("2006")
b.EditFileReplaceAll("content/p1.md", "Content before shortcode.", "This is {{< year >}} wow.").Build()
b.AssertFileContent("public/p1/index.html", "Single: P1|", fmt.Sprintf("This is %s wow.", currentYear))
}
+3 -3
View File
@@ -102,14 +102,14 @@ func (r RolesInternal) ResolveIndex(name string) int {
return i
}
}
panic(fmt.Sprintf("no role found for name %q", name))
return -1
}
// IndexMatch returns an iterator for the roles that match the filter.
func (r RolesInternal) IndexMatch(match predicate.P[string]) (iter.Seq[int], error) {
func (r RolesInternal) IndexMatch(match predicate.P[predicate.IndexString]) (iter.Seq[int], error) {
return func(yield func(i int) bool) {
for i, role := range r.Sorted {
if match(role.Name) {
if match(predicate.IndexString{Index: i, String: role.Name}) {
if !yield(i) {
return
}
+3 -3
View File
@@ -18,9 +18,9 @@ import (
"strings"
"github.com/gobwas/glob"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/predicate"
"github.com/gohugoio/hugo/config"
hglob "github.com/gohugoio/hugo/hugofs/hglob"
@@ -276,14 +276,14 @@ func getGlob(s string) (glob.Glob, error) {
func DecodeSegments(in map[string]any, segmentsToRender []string, logger loggers.Logger) (*config.ConfigNamespace[map[string]SegmentConfig, *Segments], error) {
buildConfig := func(in any) (*Segments, any, error) {
m, err := maps.ToStringMapE(in)
m, err := hmaps.ToStringMapE(in)
if err != nil {
return nil, nil, err
}
if m == nil {
m = map[string]any{}
}
m = maps.CleanConfigStringMap(m)
m = hmaps.CleanConfigStringMap(m)
var segmentCfg map[string]SegmentConfig
if err := mapstructure.WeakDecode(m, &segmentCfg); err != nil {
+24 -26
View File
@@ -18,6 +18,7 @@ import (
"errors"
"fmt"
"io"
"maps"
"mime"
"net/url"
"os"
@@ -32,13 +33,13 @@ import (
"github.com/bep/logg"
"github.com/gohugoio/go-radix"
"github.com/gohugoio/hugo/cache/dynacache"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstore"
"github.com/gohugoio/hugo/common/hsync"
"github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/para"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
@@ -55,8 +56,6 @@ import (
"github.com/gohugoio/hugo/modules"
"github.com/gohugoio/hugo/resources"
xmaps "maps"
"github.com/gohugoio/hugo/tpl/tplimpl"
"github.com/gohugoio/hugo/tpl/tplimplinit"
@@ -138,7 +137,8 @@ func (s *Site) resolveDimensionNames() types.Strings3 {
}
type siteLanguageVersionRole struct {
siteVector sitesmatrix.Vector
siteVector sitesmatrix.Vector
isDefaultLanguage bool
roleInternal roles.RoleInternal
role roles.Role
@@ -186,6 +186,10 @@ func (s siteLanguageVersionRole) Version() versions.Version {
return s.version
}
func (s siteLanguageVersionRole) isDefault() bool {
return s.isDefaultLanguage && s.roleInternal.Default && s.versionInternal.Default
}
func (s *Site) Debug() {
fmt.Println("Debugging site", s.Lang(), "=>")
// fmt.Println(s.pageMap.testDump())
@@ -197,6 +201,15 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
rolesSorted := cfg.Configs.Base.Roles.Config.Sorted
versionsSorted := cfg.Configs.Base.Versions.Config.Sorted
var (
poolSizeKatex = 2
poolSizeWebP = 1
)
if n := config.GetNumWorkerMultiplier(); n > 1 {
poolSizeKatex = min(n, 8)
poolSizeWebP = max(2, n/2)
}
var logger loggers.Logger
if cfg.TestLogger != nil {
logger = cfg.TestLogger
@@ -257,15 +270,14 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
warpc.Options{
CompilationCacheDir: compilationCacheDir,
// Katex is relatively slow.
PoolSize: 8,
PoolSize: poolSizeKatex,
Infof: logger.InfoCommand("katex").Logf,
Warnf: logger.WarnCommand("katex").Logf,
},
// WebP options.
warpc.Options{
CompilationCacheDir: compilationCacheDir,
PoolSize: 1,
PoolSize: poolSizeWebP,
Memory: 384, // 384 MiB (4096 MiB Max)
Infof: logger.InfoCommand("webp").Logf,
Warnf: logger.WarnCommand("webp").Logf,
@@ -341,7 +353,8 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
frontmatterHandler: frontmatterHandler,
store: hstore.NewScratch(),
siteLanguageVersionRole: &siteLanguageVersionRole{
siteVector: sitesmatrix.Vector{i, 0, 0},
isDefaultLanguage: language.IsDefault(),
siteVector: sitesmatrix.Vector{i, 0, 0},
},
}
@@ -472,7 +485,7 @@ func newHugoSites(
dynacache.OptionsPartition{Weight: 10, ClearWhen: dynacache.ClearOnRebuild},
),
cacheContentSource: dynacache.GetOrCreatePartition[uint64, *resources.StaleValue[[]byte]](d.MemCache, "/cont/src", dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
translationKeyPages: maps.NewSliceCache[page.Page](),
translationKeyPages: hmaps.NewSliceCache[page.Page](),
currentSite: first[0],
skipRebuildForFilenames: make(map[string]bool),
init: &hugoSitesInit{},
@@ -663,7 +676,7 @@ func (s *Site) Lastmod() time.Time {
}
// Returns the Params configured for this site.
func (s *Site) Params() maps.Params {
func (s *Site) Params() hmaps.Params {
return s.conf.Params
}
@@ -1134,7 +1147,7 @@ func (w *WhatChanged) Changes() []identity.Identity {
if w == nil || w.ids == nil {
return nil
}
return slices.Collect(xmaps.Keys(w.ids))
return slices.Collect(maps.Keys(w.ids))
}
func (w *WhatChanged) Drain() []identity.Identity {
@@ -1747,21 +1760,6 @@ func (s *Site) render(ctx *siteRenderContext) (err error) {
return err
}
if ctx.outIdx == 0 && s.h.buildCounter.Load() == 0 {
// Note that even if disableAliases is set, the aliases themselves are
// preserved on page. The motivation with this is to be able to generate
// 301 redirects in a .htaccess file and similar using a custom output format.
if !s.conf.DisableAliases {
// Aliases must be rendered before pages.
// Some sites, Hugo docs included, have faulty alias definitions that point
// to itself or another real page. These will be overwritten in the next
// step.
if err = s.renderAliases(); err != nil {
return
}
}
}
if err = s.renderPages(ctx); err != nil {
return
}
+46 -78
View File
@@ -154,6 +154,20 @@ func pageRenderer(
}
}
if !s.conf.DisableAliases && s.h.buildCounter.Load() == 0 {
of := p.outputFormat()
if of.IsHTML && of.Permalinkable {
// Render any aliases for this page.
if err := s.renderAliasesForPage(p); err != nil {
if sendErr(err) {
continue
} else {
return
}
}
}
}
if !p.render {
// Nothing more to do for this page.
continue
@@ -203,8 +217,8 @@ func pageRenderer(
}
}
if p.IsHome() && p.outputFormat().IsHTML && s.siteVector.IsFirst() {
if err = s.renderDefaultDimensionRedirect(p); err != nil {
if p.IsHome() && p.outputFormat().IsHTML && s.isDefault() {
if err = s.renderDefaultSiteRedirect(p); err != nil {
if sendErr(err) {
continue
} else {
@@ -300,86 +314,30 @@ func (s *Site) renderPaginator(p *pageState, templ *tplimpl.TemplInfo) error {
return nil
}
// renderAliases renders shell pages that simply have a redirect in the header.
func (s *Site) renderAliases() error {
w := &doctree.NodeShiftTreeWalker[contentNode]{
Tree: s.pageMap.treePages,
Handle: func(key string, n contentNode) (radix.WalkFlag, error) {
p := n.(*pageState)
// We cannot alias a page that's not rendered.
if p.m.noLink() || p.skipRender() {
return radix.WalkContinue, nil
}
if len(p.Aliases()) == 0 {
return radix.WalkContinue, nil
}
pathSeen := make(map[string]bool)
for _, of := range p.OutputFormats() {
if !of.Format.IsHTML {
continue
}
f := of.Format
if pathSeen[f.Path] {
continue
}
pathSeen[f.Path] = true
plink := of.Permalink()
for _, a := range p.Aliases() {
isRelative := !strings.HasPrefix(a, "/")
if isRelative {
// Make alias relative, where "." will be on the
// same directory level as the current page.
basePath := path.Join(p.targetPaths().SubResourceBaseLink, "..")
a = path.Join(basePath, a)
} else {
// Make sure AMP and similar doesn't clash with regular aliases.
a = path.Join(f.Path, a)
}
if s.conf.C.IsUglyURLSection(p.Section()) && !strings.HasSuffix(a, ".html") {
a += ".html"
}
lang := p.Language().Lang
if s.h.Configs.IsMultihost && !strings.HasPrefix(a, "/"+lang) {
// These need to be in its language root.
a = path.Join(lang, a)
}
err := s.writeDestAlias(a, plink, f, p)
if err != nil {
return radix.WalkStop, err
}
}
}
return radix.WalkContinue, nil
},
func (s *Site) renderAliasesForPage(p *pageState) error {
po := p.pageOutput
f := po.f
plink := p.Permalink()
for _, a := range p.Aliases() {
err := s.writeDestAlias(a, plink, f, p)
if err != nil {
return err
}
}
return w.Walk(context.TODO())
return nil
}
// renderDefaultDimensionRedirect creates a redirect to the main dimension's home,
// renderDefaultSiteRedirect creates a redirect to the default site's home,
// depending on if it lives in sub folder (e.g. /en) or not.
func (s *Site) renderDefaultDimensionRedirect(home *pageState) error {
if s.conf.DisableDefaultLanguageRedirect || s.conf.DisableDefaultDimensionRedirect {
// The default site is the site is the combination of defaultContentLanguage,
// defaultContentVersion and defaultContentRole.
func (s *Site) renderDefaultSiteRedirect(home *pageState) error {
if s.conf.DisableDefaultLanguageRedirect || s.conf.DisableDefaultSiteRedirect {
return nil
}
shouldAdd := s.conf.DefaultContentLanguageInSubdir && !s.Conf.IsMultihost()
shouldAdd = shouldAdd || s.conf.DefaultContentVersionInSubdir || s.conf.DefaultContentRoleInSubdir
if !shouldAdd {
return nil
}
addRedirectInRoot := s.conf.DefaultContentLanguageInSubdir && !s.Conf.IsMultihost()
addRedirectInRoot = addRedirectInRoot || s.conf.DefaultContentVersionInSubdir || s.conf.DefaultContentRoleInSubdir
homeLink := home.pageOutput.targetPaths().Link // This doesn't have any baseURL paths in it.
@@ -388,10 +346,20 @@ func (s *Site) renderDefaultDimensionRedirect(home *pageState) error {
var ps []string
if of.Path != "" {
// For OutputFormats with a path, creating more than one alias will easily create path clashes without much value.
ps = []string{paths.AddLeadingAndTrailingSlash(of.Path)}
if addRedirectInRoot {
// For OutputFormats with a path, creating more than one alias will easily create path clashes without much value.
ps = []string{paths.AddLeadingAndTrailingSlash(of.Path)}
}
} else {
ps = []string{"/"}
if addRedirectInRoot {
ps = append(ps, "/")
}
if s.Conf.IsMultilingual() && !s.conf.DefaultContentLanguageInSubdir && !s.Conf.IsMultihost() {
// Create redirect from e.g. /en => /
ps = append(ps, homeLink+s.Lang()+"/")
}
// /guest/v1.0.0/en/
// /,/guest/,/guest/v1.0.0 = /guest/v1.0.0/en/
// /guest/v1.0.0/
@@ -1281,8 +1281,23 @@ defaultContentLanguageInSubDir = true
defaultContentVersionInSubDir = true
defaultContentRoleInSubDir = true
disableDefaultLanguageRedirect = false
disableDefaultDimensionRedirect = false
disableDefaultSiteRedirect = false
defaultContentLanguage = "en"
defaultContentVersion = "v1.0.0"
defaultContentRole = "guest"
[languages]
[languages.en]
weight = 2
[languages.nn]
weight = 1
[versions]
[versions."v1.0.0"]
weight = 2
[versions."v2.0.0"]
weight = 1
[roles]
[roles.guest]
[roles.member]
[outputs]
home = [ 'rss', 'amp', 'html']
-- layouts/home.html --
@@ -1334,7 +1349,7 @@ func TestSitesMatrixRedirectsDisableDefaultLanguageRedirect(t *testing.T) {
}
runTest(strings.ReplaceAll(files, "disableDefaultLanguageRedirect = false", "disableDefaultLanguageRedirect = true"))
runTest(strings.ReplaceAll(files, "disableDefaultDimensionRedirect = false", "disableDefaultDimensionRedirect = true"))
runTest(strings.ReplaceAll(files, "disableDefaultSiteRedirect = false", "disableDefaultSiteRedirect = true"))
}
func TestSitesMatrixRedirectsDefaultContentVersionInBase(t *testing.T) {
@@ -1361,7 +1376,7 @@ defaultContentLanguageInSubDir = true
defaultContentVersionInSubDir = true
defaultContentRoleInSubDir = true
disableDefaultLanguageRedirect = false
disableDefaultDimensionRedirect = false
disableDefaultSiteRedirect = false
defaultContentLanguage = "en"
[languages]
[languages.en]
@@ -1384,3 +1399,241 @@ Home.
b.AssertFileContent("public/en/guest/v1.0.0/index.html", "Home.")
b.AssertFileContent("public/en/guest/v1.0.0/amp/index.html", "Home.")
}
func TestSitesMatrixRedirectsDefaultContentLanguageInBase(t *testing.T) {
t.Parallel()
files := `
-- hugo.yml --
baseURL: https://example.org
disableKinds: [taxonomy]
defaultContentVersionInSubDir: true
defaultContentRoleInSubDir: true
defaultContentLanguage: en
languages:
en:
languageName: English
bn:
languageName: Bengali
fr:
languageName: French
-- layouts/all.html --
All.
`
b := hugolib.Test(t, files)
b.AssertFileContent("public/guest/v1.0.0/en/index.html", "url=https://example.org/guest/v1.0.0/\"", `lang="en"`)
files = strings.ReplaceAll(files, "defaultContentVersionInSubDir: true", "defaultContentVersionInSubDir: false")
b = hugolib.Test(t, files)
b.AssertFileContent("public/guest/en/index.html", "url=https://example.org/guest/\"")
}
func TestSitesMatrixRangeMatchers(t *testing.T) {
t.Parallel()
files := `TestSitesMatrixRangeMatchers
-- hugo.toml --
baseURL = "https://example.org/"
defaultContentVersion = "v4.0.0"
defaultContentVersionInSubDir = true
disableKinds = ["taxonomy", "term", "rss", "sitemap"]
[languages]
[languages.en]
weight = 1
[versions]
[versions."v1.0.0"]
[versions."v2.0.0"]
[versions."v2.1.0"]
[versions."v3.0.0"]
[versions."v4.0.0"]
-- content/_index.md --
---
title: "Home"
sites:
matrix:
versions: ["**"]
---
-- content/p1.md --
---
title: "P1 from v2.0.0"
sites:
matrix:
versions: [">= v2.0.0"]
---
Content for v2.0.0 and later.
-- content/p2.md --
---
title: "P2 between v2 and v3"
sites:
matrix:
versions: [">= v2.0.0", "<= v3.0.0"]
---
Content between v2.0.0 and v3.0.0 (inclusive).
-- content/p3.md --
---
title: "P3 before v3"
sites:
matrix:
versions: ["< v3.0.0"]
---
Content before v3.0.0.
-- content/p4.md --
---
title: "P4 after v2"
sites:
matrix:
versions: ["> v2.0.0"]
---
Content after v2.0.0 (exclusive).
-- content/p5.md --
---
title: "P5 range with negation"
sites:
matrix:
versions: [">= v2.0.0", "! v2.1.0", "<= v4.0.0"]
---
Content from v2.0.0 to v4.0.0 but not v2.1.0.
-- layouts/all.html --
Title: {{ .Title }}|
`
b := hugolib.Test(t, files)
// P1: >= v2.0.0 should be in v2.0.0, v2.1.0, v3.0.0, v4.0.0
b.AssertFileContent("public/v2.0.0/p1/index.html", "Title: P1 from v2.0.0|")
b.AssertFileContent("public/v2.1.0/p1/index.html", "Title: P1 from v2.0.0|")
b.AssertFileContent("public/v3.0.0/p1/index.html", "Title: P1 from v2.0.0|")
b.AssertFileContent("public/v4.0.0/p1/index.html", "Title: P1 from v2.0.0|")
b.AssertFileExists("public/v1.0.0/p1/index.html", false)
// P2: >= v2.0.0 AND <= v3.0.0 should be in v2.0.0, v2.1.0, v3.0.0
b.AssertFileContent("public/v2.0.0/p2/index.html", "Title: P2 between v2 and v3|")
b.AssertFileContent("public/v2.1.0/p2/index.html", "Title: P2 between v2 and v3|")
b.AssertFileContent("public/v3.0.0/p2/index.html", "Title: P2 between v2 and v3|")
b.AssertFileExists("public/v1.0.0/p2/index.html", false)
b.AssertFileExists("public/v4.0.0/p2/index.html", false)
// P3: < v3.0.0 should be in v1.0.0, v2.0.0, v2.1.0
b.AssertFileContent("public/v1.0.0/p3/index.html", "Title: P3 before v3|")
b.AssertFileContent("public/v2.0.0/p3/index.html", "Title: P3 before v3|")
b.AssertFileContent("public/v2.1.0/p3/index.html", "Title: P3 before v3|")
b.AssertFileExists("public/v3.0.0/p3/index.html", false)
b.AssertFileExists("public/v4.0.0/p3/index.html", false)
// P4: > v2.0.0 should be in v2.1.0, v3.0.0, v4.0.0
b.AssertFileContent("public/v2.1.0/p4/index.html", "Title: P4 after v2|")
b.AssertFileContent("public/v3.0.0/p4/index.html", "Title: P4 after v2|")
b.AssertFileContent("public/v4.0.0/p4/index.html", "Title: P4 after v2|")
b.AssertFileExists("public/v1.0.0/p4/index.html", false)
b.AssertFileExists("public/v2.0.0/p4/index.html", false)
// P5: >= v2.0.0 AND ! v2.1.0 AND <= v4.0.0 should be in v2.0.0, v3.0.0, v4.0.0
b.AssertFileContent("public/v2.0.0/p5/index.html", "Title: P5 range with negation|")
b.AssertFileContent("public/v3.0.0/p5/index.html", "Title: P5 range with negation|")
b.AssertFileContent("public/v4.0.0/p5/index.html", "Title: P5 range with negation|")
b.AssertFileExists("public/v1.0.0/p5/index.html", false)
b.AssertFileExists("public/v2.1.0/p5/index.html", false) // Excluded by negation
}
func TestSitesMatrixFileMountOrder(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "https://example.org/"
defaultContentVersion = "v1.0.0"
defaultContentVersionInSubDir = true
disableKinds = ["taxonomy", "term", "rss", "sitemap"]
[versions]
[versions."v1.0.0"]
[versions."v2.0.0"]
[[module.mounts]]
source = 'content/v1'
target = 'content'
[module.mounts.sites.matrix]
versions = ["**"]
[[module.mounts]]
source = 'content/v21'
target = 'content'
[module.mounts.sites.matrix]
versions = ["v2.0.0"]
[[module.mounts]]
source = 'content/v22'
target = 'content'
[module.mounts.sites.matrix]
versions = ["v2.0.0"]
-- content/v1/p1.md --
---
title: "P1 v1"
---
-- content/v21/p1.md --
---
title: "P1 v21"
---
-- content/v22/p1.md --
---
title: "P1 v22"
---
-- layouts/all.html --
Title: {{ .Title }}|
`
b := hugolib.Test(t, files)
b.AssertFileContent("public/v1.0.0/p1/index.html", "Title: P1 v1|")
b.AssertFileContent("public/v2.0.0/p1/index.html", "Title: P1 v1|")
}
func TestSitesMatrixFileMountThemeIssue14414(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ["taxonomy", "term", "rss", "sitemap"]
defaultContentVersion = "v1"
defaultContentVersionInSubDir = true
[versions]
[versions."v1"]
[versions."v2"]
[module]
[[module.mounts]]
source = 'content'
target = 'content'
[module.mounts.sites.matrix]
versions = '> v2.0.0'
[[module.imports]]
ignoreConfig = true
path = 'mytheme'
[[module.imports.mounts]]
source = 'content'
target = 'content'
[module.imports.mounts.sites.matrix]
versions = '<= v2.0.0'
-- themes/mytheme/content/p1.md --
---
title: "P1 from theme"
---
-- content/p1.md --
---
title: "P1 from site"
---
-- layouts/all.html --
Title: {{ .Title }}|Version: {{ .Site.Version.Name }}|
`
b := hugolib.Test(t, files)
b.AssertFileContent("public/v1/p1/index.html", "Title: P1 from theme|Version: v1|")
}
+39 -42
View File
@@ -17,14 +17,14 @@ import (
"cmp"
"fmt"
"iter"
xmaps "maps"
"maps"
"slices"
"sort"
"sync"
"github.com/gohugoio/hashstructure"
"github.com/gohugoio/hugo/common/hashing"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/predicate"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/hugofs/hglob"
@@ -250,7 +250,7 @@ func (s *vectorStoreMap) HasRole(i int) bool {
func (s *vectorStoreMap) clone() *vectorStoreMap {
c := *s
c.h = &hashOnce{}
c.sets = xmaps.Clone(s.sets)
c.sets = maps.Clone(s.sets)
return &c
}
@@ -277,7 +277,7 @@ type ConfiguredDimensions struct {
ConfiguredRoles ConfiguredDimension
CommonSitesMatrix CommonSitestMatrix
singleVectorStoreCache *maps.Cache[Vector, *IntSets]
singleVectorStoreCache *hmaps.Cache[Vector, *IntSets]
}
func (c *ConfiguredDimensions) IsSingleVector() bool {
@@ -295,7 +295,7 @@ func (c *ConfiguredDimensions) GetOrCreateSingleVectorStore(vec Vector) *IntSets
}
func (c *ConfiguredDimensions) Init() error {
c.singleVectorStoreCache = maps.NewCache[Vector, *IntSets]()
c.singleVectorStoreCache = hmaps.NewCache[Vector, *IntSets]()
b := NewIntSetsBuilder(c).WithDefaultsIfNotSet().Build()
defaultVec := b.VectorSample()
c.singleVectorStoreCache.Set(defaultVec, b)
@@ -339,9 +339,9 @@ func (c *ConfiguredDimensions) ResolveVector(names types.Strings3) Vector {
// IntSets holds the ordered sets of integers for the dimensions,
// which is used for fast membership testing of files, resources and pages.
type IntSets struct {
languages *maps.OrderedIntSet `mapstructure:"-" json:"-"`
versions *maps.OrderedIntSet `mapstructure:"-" json:"-"`
roles *maps.OrderedIntSet `mapstructure:"-" json:"-"`
languages *hmaps.OrderedIntSet `mapstructure:"-" json:"-"`
versions *hmaps.OrderedIntSet `mapstructure:"-" json:"-"`
roles *hmaps.OrderedIntSet `mapstructure:"-" json:"-"`
h *hashOnce
}
@@ -592,7 +592,7 @@ func (s IntSets) shallowClone() *IntSets {
// WithLanguageIndices replaces the current language set with a single language index.
func (s *IntSets) WithLanguageIndices(i int) VectorStore {
c := s.shallowClone()
c.languages = maps.NewOrderedIntSet(i)
c.languages = hmaps.NewOrderedIntSet(i)
return c.init()
}
@@ -615,22 +615,22 @@ func (s *IntSets) MustHash() uint64 {
// setDefaultsIfNotSet applies default values to the IntSets if they are not already set.
func (s *IntSets) setDefaultsIfNotSet(cfg *ConfiguredDimensions) {
if s.languages == nil {
s.languages = maps.NewOrderedIntSet()
s.languages = hmaps.NewOrderedIntSet()
s.languages.Set(cfg.ConfiguredLanguages.IndexDefault())
}
if s.versions == nil {
s.versions = maps.NewOrderedIntSet()
s.versions = hmaps.NewOrderedIntSet()
s.versions.Set(cfg.ConfiguredVersions.IndexDefault())
}
if s.roles == nil {
s.roles = maps.NewOrderedIntSet()
s.roles = hmaps.NewOrderedIntSet()
s.roles.Set(cfg.ConfiguredRoles.IndexDefault())
}
}
func (s *IntSets) setDefaultsAndAllLAnguagesIfNotSet(cfg *ConfiguredDimensions) {
if s.languages == nil {
s.languages = maps.NewOrderedIntSet()
s.languages = hmaps.NewOrderedIntSet()
for i := range cfg.ConfiguredLanguages.ForEachIndex() {
s.languages.Set(i)
}
@@ -640,19 +640,19 @@ func (s *IntSets) setDefaultsAndAllLAnguagesIfNotSet(cfg *ConfiguredDimensions)
func (s *IntSets) setAllIfNotSet(cfg *ConfiguredDimensions) {
if s.languages == nil {
s.languages = maps.NewOrderedIntSet()
s.languages = hmaps.NewOrderedIntSet()
for i := range cfg.ConfiguredLanguages.ForEachIndex() {
s.languages.Set(i)
}
}
if s.versions == nil {
s.versions = maps.NewOrderedIntSet()
s.versions = hmaps.NewOrderedIntSet()
for i := range cfg.ConfiguredVersions.ForEachIndex() {
s.versions.Set(i)
}
}
if s.roles == nil {
s.roles = maps.NewOrderedIntSet()
s.roles = hmaps.NewOrderedIntSet()
for i := range cfg.ConfiguredRoles.ForEachIndex() {
s.roles.Set(i)
}
@@ -696,20 +696,20 @@ func (s *IntSets) setDimensionsFromOtherIfNotSet(other VectorIterator) {
func (s *IntSets) setValuesInNilSets(vec Vector, setLang, setVer, setRole bool) {
if setLang {
if s.languages == nil {
s.languages = maps.NewOrderedIntSet()
s.languages = hmaps.NewOrderedIntSet()
}
s.languages.Set(vec.Language())
}
if setVer {
if s.versions == nil {
s.versions = maps.NewOrderedIntSet()
s.versions = hmaps.NewOrderedIntSet()
}
s.versions.Set(vec.Version())
}
if setRole {
if s.roles == nil {
s.roles = maps.NewOrderedIntSet()
s.roles = hmaps.NewOrderedIntSet()
}
s.roles.Set(vec.Role())
}
@@ -737,12 +737,12 @@ func (b *IntSetsBuilder) Build() *IntSets {
}
func (b *IntSetsBuilder) WithConfig(cfg IntSetsConfig) *IntSetsBuilder {
applyFilter := func(what string, values []string, matcher ConfiguredDimension) (*maps.OrderedIntSet, error) {
var result *maps.OrderedIntSet
applyFilter := func(what string, values []string, matcher ConfiguredDimension) (*hmaps.OrderedIntSet, error) {
var result *hmaps.OrderedIntSet
if len(values) == 0 {
if cfg.ApplyDefaults > 0 {
result = maps.NewOrderedIntSet()
result = hmaps.NewOrderedIntSet()
}
switch cfg.ApplyDefaults {
case IntSetsConfigApplyDefaultsIfNotSet:
@@ -760,22 +760,19 @@ func (b *IntSetsBuilder) WithConfig(cfg IntSetsConfig) *IntSetsBuilder {
return result, nil
}
// Dot separated globs.
filter, err := predicate.NewStringPredicateFromGlobs(values, hglob.GetGlobDot)
filter, err := predicate.NewIndexStringPredicateFromGlobsAndRanges(values, matcher.ResolveIndex, hglob.GetGlobDot)
if err != nil {
return nil, fmt.Errorf("failed to create filter for %s: %w", what, err)
}
for _, pattern := range values {
iter, err := matcher.IndexMatch(filter)
if err != nil {
return nil, fmt.Errorf("failed to match %s %q: %w", what, pattern, err)
}
for i := range iter {
if result == nil {
result = maps.NewOrderedIntSet()
}
result.Set(i)
iter, err := matcher.IndexMatch(filter)
if err != nil {
return nil, fmt.Errorf("failed to match %s %q: %w", what, values, err)
}
for i := range iter {
if result == nil {
result = hmaps.NewOrderedIntSet()
}
result.Set(i)
}
return result, nil
@@ -807,7 +804,7 @@ func (s *IntSetsBuilder) WithLanguageIndices(idxs ...int) *IntSetsBuilder {
return s
}
if s.s.languages == nil {
s.s.languages = maps.NewOrderedIntSet()
s.s.languages = hmaps.NewOrderedIntSet()
}
for _, i := range idxs {
s.s.languages.Set(i)
@@ -835,7 +832,7 @@ func (s *IntSetsBuilder) WithDimensionsFromOtherIfNotSet(other VectorIterator) *
return s
}
func (b *IntSetsBuilder) WithSets(languages, versions, roles *maps.OrderedIntSet) *IntSetsBuilder {
func (b *IntSetsBuilder) WithSets(languages, versions, roles *hmaps.OrderedIntSet) *IntSetsBuilder {
b.s.languages = languages
b.s.versions = versions
b.s.roles = roles
@@ -867,17 +864,17 @@ func (s *Sites) Equal(other Sites) bool {
return s.Matrix.Equal(other.Matrix) && s.Complements.Equal(other.Complements)
}
func (s *Sites) SetFromParamsIfNotSet(params maps.Params) {
func (s *Sites) SetFromParamsIfNotSet(params hmaps.Params) {
const (
matrixKey = "matrix"
complementsKey = "complements"
)
if m, ok := params[matrixKey]; ok {
s.Matrix.SetFromParamsIfNotSet(m.(maps.Params))
s.Matrix.SetFromParamsIfNotSet(m.(hmaps.Params))
}
if f, ok := params[complementsKey]; ok {
s.Complements.SetFromParamsIfNotSet(f.(maps.Params))
s.Complements.SetFromParamsIfNotSet(f.(hmaps.Params))
}
}
@@ -899,7 +896,7 @@ func (d StringSlices) Equal(other StringSlices) bool {
slices.Equal(d.Roles, other.Roles)
}
func (d *StringSlices) SetFromParamsIfNotSet(params maps.Params) {
func (d *StringSlices) SetFromParamsIfNotSet(params hmaps.Params) {
const (
languagesKey = "languages"
versionsKey = "versions"
@@ -968,10 +965,10 @@ func (m *testDimension) ForEachIndex() iter.Seq[int] {
}
}
func (m *testDimension) IndexMatch(match predicate.P[string]) (iter.Seq[int], error) {
func (m *testDimension) IndexMatch(match predicate.P[predicate.IndexString]) (iter.Seq[int], error) {
return func(yield func(i int) bool) {
for i, n := range m.names {
if match(n) {
if match(predicate.IndexString{Index: i, String: n}) {
if !yield(i) {
return
}
+67 -67
View File
@@ -18,7 +18,7 @@ import (
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/hashing"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
)
@@ -31,27 +31,27 @@ func TestIntSets(t *testing.T) {
testDims := newTestDims()
sets := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(1, 2),
maps.NewOrderedIntSet(1, 2, 3),
maps.NewOrderedIntSet(1, 2, 3),
hmaps.NewOrderedIntSet(1, 2),
hmaps.NewOrderedIntSet(1, 2, 3),
hmaps.NewOrderedIntSet(1, 2, 3),
).Build()
sets2 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(1, 2),
maps.NewOrderedIntSet(1, 2, 3),
maps.NewOrderedIntSet(1, 2, 4),
hmaps.NewOrderedIntSet(1, 2),
hmaps.NewOrderedIntSet(1, 2, 3),
hmaps.NewOrderedIntSet(1, 2, 4),
).Build()
sets3 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(2, 1),
maps.NewOrderedIntSet(1, 2, 3),
maps.NewOrderedIntSet(2, 1, 4),
hmaps.NewOrderedIntSet(2, 1),
hmaps.NewOrderedIntSet(1, 2, 3),
hmaps.NewOrderedIntSet(2, 1, 4),
).Build()
sets4 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(3, 4, 5),
maps.NewOrderedIntSet(3, 4, 5),
maps.NewOrderedIntSet(3, 4, 5),
hmaps.NewOrderedIntSet(3, 4, 5),
hmaps.NewOrderedIntSet(3, 4, 5),
hmaps.NewOrderedIntSet(3, 4, 5),
).Build()
c.Assert(hashing.HashStringHex(sets), qt.Equals, "790f6004619934ff")
@@ -68,24 +68,24 @@ func TestIntSets(t *testing.T) {
c.Assert(sets.EqualsVector(sets), qt.Equals, true)
c.Assert(sets.EqualsVector(
sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(1, 2),
maps.NewOrderedIntSet(1, 2, 3),
maps.NewOrderedIntSet(1, 2, 3),
hmaps.NewOrderedIntSet(1, 2),
hmaps.NewOrderedIntSet(1, 2, 3),
hmaps.NewOrderedIntSet(1, 2, 3),
).Build(),
), qt.Equals, true)
c.Assert(sets.EqualsVector(
sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(1, 2, 3),
maps.NewOrderedIntSet(1, 2, 3, 4),
maps.NewOrderedIntSet(1, 2, 3, 4),
hmaps.NewOrderedIntSet(1, 2, 3),
hmaps.NewOrderedIntSet(1, 2, 3, 4),
hmaps.NewOrderedIntSet(1, 2, 3, 4),
).Build(),
), qt.Equals, false)
c.Assert(sets.EqualsVector(
sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(1, 2),
maps.NewOrderedIntSet(1, 2, 3),
maps.NewOrderedIntSet(2, 3, 4),
hmaps.NewOrderedIntSet(1, 2),
hmaps.NewOrderedIntSet(1, 2, 3),
hmaps.NewOrderedIntSet(2, 3, 4),
).Build(),
), qt.Equals, false)
@@ -126,17 +126,17 @@ func TestIntSetsComplement(t *testing.T) {
testDims := newTestDims()
self := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(test.left.v0...),
maps.NewOrderedIntSet(test.left.v1...),
maps.NewOrderedIntSet(test.left.v2...),
hmaps.NewOrderedIntSet(test.left.v0...),
hmaps.NewOrderedIntSet(test.left.v1...),
hmaps.NewOrderedIntSet(test.left.v2...),
).Build()
var input []sitesmatrix.VectorProvider
for _, v := range test.input {
input = append(input, sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(v.v0...),
maps.NewOrderedIntSet(v.v1...),
maps.NewOrderedIntSet(v.v2...),
hmaps.NewOrderedIntSet(v.v0...),
hmaps.NewOrderedIntSet(v.v1...),
hmaps.NewOrderedIntSet(v.v2...),
).Build())
}
@@ -262,15 +262,15 @@ func TestIntSetsComplementOfComplement(t *testing.T) {
testDims := newTestDims()
sets1 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(1),
maps.NewOrderedIntSet(1),
maps.NewOrderedIntSet(1),
hmaps.NewOrderedIntSet(1),
hmaps.NewOrderedIntSet(1),
hmaps.NewOrderedIntSet(1),
).Build()
sets2 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(1, 2),
maps.NewOrderedIntSet(1),
maps.NewOrderedIntSet(1, 3),
hmaps.NewOrderedIntSet(1, 2),
hmaps.NewOrderedIntSet(1),
hmaps.NewOrderedIntSet(1, 3),
).Build()
c1 := sets2.Complement(sets1)
@@ -296,27 +296,27 @@ func BenchmarkIntSetsComplement(b *testing.B) {
testDims := newTestDims()
sets1 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(1, 2, 3),
maps.NewOrderedIntSet(1, 2, 3),
maps.NewOrderedIntSet(1, 2, 3),
hmaps.NewOrderedIntSet(1, 2, 3),
hmaps.NewOrderedIntSet(1, 2, 3),
hmaps.NewOrderedIntSet(1, 2, 3),
).Build()
sets1Copy := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(1, 2, 3),
maps.NewOrderedIntSet(1, 2, 3),
maps.NewOrderedIntSet(1, 2, 3),
hmaps.NewOrderedIntSet(1, 2, 3),
hmaps.NewOrderedIntSet(1, 2, 3),
hmaps.NewOrderedIntSet(1, 2, 3),
).Build()
sets2 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(1, 2, 3, 4),
maps.NewOrderedIntSet(1, 2, 3, 4),
maps.NewOrderedIntSet(1, 2, 3, 4, 6),
hmaps.NewOrderedIntSet(1, 2, 3, 4),
hmaps.NewOrderedIntSet(1, 2, 3, 4),
hmaps.NewOrderedIntSet(1, 2, 3, 4, 6),
).Build()
setsLanguage1 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(1),
maps.NewOrderedIntSet(1),
maps.NewOrderedIntSet(1),
hmaps.NewOrderedIntSet(1),
hmaps.NewOrderedIntSet(1),
hmaps.NewOrderedIntSet(1),
).Build()
b.ResetTimer()
@@ -361,21 +361,21 @@ func BenchmarkSets(b *testing.B) {
testDims := newTestDims()
sets1 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(1, 2),
maps.NewOrderedIntSet(1, 2, 3),
maps.NewOrderedIntSet(1, 2, 3),
hmaps.NewOrderedIntSet(1, 2),
hmaps.NewOrderedIntSet(1, 2, 3),
hmaps.NewOrderedIntSet(1, 2, 3),
).Build()
sets1Copy := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(1, 2),
maps.NewOrderedIntSet(1, 2, 3),
maps.NewOrderedIntSet(1, 2, 3),
hmaps.NewOrderedIntSet(1, 2),
hmaps.NewOrderedIntSet(1, 2, 3),
hmaps.NewOrderedIntSet(1, 2, 3),
).Build()
sets2 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(1, 2, 3),
maps.NewOrderedIntSet(1, 2, 3, 4),
maps.NewOrderedIntSet(1, 2, 3, 4),
hmaps.NewOrderedIntSet(1, 2, 3),
hmaps.NewOrderedIntSet(1, 2, 3, 4),
hmaps.NewOrderedIntSet(1, 2, 3, 4),
).Build()
v1 := sitesmatrix.Vector{1, 2, 3}
@@ -385,9 +385,9 @@ func BenchmarkSets(b *testing.B) {
b.Run("Build", func(b *testing.B) {
for b.Loop() {
_ = sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(1, 2),
maps.NewOrderedIntSet(1, 2, 3),
maps.NewOrderedIntSet(1, 2, 3),
hmaps.NewOrderedIntSet(1, 2),
hmaps.NewOrderedIntSet(1, 2, 3),
hmaps.NewOrderedIntSet(1, 2, 3),
).Build()
}
})
@@ -484,17 +484,17 @@ func TestVectorStoreMap(t *testing.T) {
c.Run("Complement", func(c *qt.C) {
v1 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(1, 3),
maps.NewOrderedIntSet(1),
maps.NewOrderedIntSet(1, 3),
hmaps.NewOrderedIntSet(1, 3),
hmaps.NewOrderedIntSet(1),
hmaps.NewOrderedIntSet(1, 3),
).Build()
m := v1.ToVectorStoreMap()
v2 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(1, 2),
maps.NewOrderedIntSet(1),
maps.NewOrderedIntSet(1, 3),
hmaps.NewOrderedIntSet(1, 2),
hmaps.NewOrderedIntSet(1),
hmaps.NewOrderedIntSet(1, 3),
).Build()
complement := m.Complement(v2)
@@ -509,9 +509,9 @@ func TestVectorStoreMap(t *testing.T) {
func BenchmarkHasAnyVectorSingle(b *testing.B) {
testDims := newTestDims()
set := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
maps.NewOrderedIntSet(0),
maps.NewOrderedIntSet(0),
maps.NewOrderedIntSet(0),
hmaps.NewOrderedIntSet(0),
hmaps.NewOrderedIntSet(0),
hmaps.NewOrderedIntSet(0),
).Build()
v := sitesmatrix.Vector{0, 0, 0}
+21 -6
View File
@@ -64,6 +64,10 @@ type VersionInternal struct {
// Name of the version.
// This is the key from the config.
Name string
// Parsed version of Name.
parsedVersion version.Version
// Whether this version is the default version.
// This will be by default rendered in the root.
// There can only be one default version.
@@ -104,14 +108,23 @@ func (r VersionsInternal) ResolveIndex(name string) int {
return i
}
}
panic(fmt.Sprintf("no version found for name %q", name))
// Compare against parsed versions as well.
v, err := version.ParseVersion(name)
if err == nil {
for i, version := range r.Sorted {
if version.parsedVersion.Compare(v) == 0 {
return i
}
}
}
return -1
}
// IndexMatch returns an iterator for the versions that match the filter.
func (r VersionsInternal) IndexMatch(match predicate.P[string]) (iter.Seq[int], error) {
func (r VersionsInternal) IndexMatch(match predicate.P[predicate.IndexString]) (iter.Seq[int], error) {
return func(yield func(i int) bool) {
for i, version := range r.Sorted {
if match(version.Name) {
if match(predicate.IndexString{Index: i, String: version.Name}) {
if !yield(i) {
return
}
@@ -161,15 +174,17 @@ func (r *VersionsInternal) init(defaultContentVersion string) error {
defaultSeen = true
}
r.Sorted = append(r.Sorted, VersionInternal{Name: k, Default: isDefault, VersionConfig: v})
// Ignore error, this may not be a semver version.
parsedVersion, _ := version.ParseVersion(k)
r.Sorted = append(r.Sorted, VersionInternal{Name: k, parsedVersion: parsedVersion, Default: isDefault, VersionConfig: v})
}
// Sort by weight if set, then semver descending.
sort.SliceStable(r.Sorted, func(i, j int) bool {
ri, rj := r.Sorted[i], r.Sorted[j]
if ri.Weight == rj.Weight {
v1, v2 := version.MustParseVersion(ri.Name), version.MustParseVersion(rj.Name)
return v1.Compare(v2) < 0
return ri.parsedVersion.Compare(rj.parsedVersion) < 0
}
if rj.Weight == 0 {
return true
+6 -2
View File
@@ -1,7 +1,11 @@
# Release env.
# These will be replaced by script before release.
HUGORELEASER_TAG=v0.154.3
HUGORELEASER_COMMITISH=b1c1bd019f4d65d4ebc979e2157279bb229b457f
HUGORELEASER_TAG=v0.155.1
HUGORELEASER_COMMITISH=f23576f2fb8d1b45d981a5e87e75b4cefa381592
+2 -2
View File
@@ -16,14 +16,14 @@ package js
import (
"context"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/resources/resource"
)
// BatcherClient is used to do JS batch operations.
type BatcherClient interface {
New(id string) (Batcher, error)
Store() *maps.Cache[string, Batcher]
Store() *hmaps.Cache[string, Batcher]
}
// BatchPackage holds a group of JavaScript resources.
+14 -14
View File
@@ -31,9 +31,9 @@ import (
"github.com/evanw/esbuild/pkg/api"
"github.com/gohugoio/hugo/cache/dynacache"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hsync"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/helpers"
@@ -74,8 +74,8 @@ func NewBatcherClient(deps *deps.Deps) (js.BatcherClient, error) {
d: deps,
buildClient: NewBuildClient(deps.BaseFs.Assets, deps.ResourceSpec),
createClient: create.New(deps.ResourceSpec),
batcherStore: maps.NewCache[string, js.Batcher](),
bundlesStore: maps.NewCache[string, js.BatchPackage](),
batcherStore: hmaps.NewCache[string, js.Batcher](),
bundlesStore: hmaps.NewCache[string, js.BatchPackage](),
}
deps.BuildEndListeners.Add(func(...any) bool {
@@ -196,8 +196,8 @@ type BatcherClient struct {
createClient *create.Client
buildClient *BuildClient
batcherStore *maps.Cache[string, js.Batcher]
bundlesStore *maps.Cache[string, js.BatchPackage]
batcherStore *hmaps.Cache[string, js.Batcher]
bundlesStore *hmaps.Cache[string, js.BatchPackage]
}
// New creates a new Batcher with the given ID.
@@ -279,7 +279,7 @@ func (c *BatcherClient) New(id string) (js.Batcher, error) {
return b, nil
}
func (c *BatcherClient) Store() *maps.Cache[string, js.Batcher] {
func (c *BatcherClient) Store() *hmaps.Cache[string, js.Batcher] {
return c.batcherStore
}
@@ -435,15 +435,15 @@ func (b *batcher) doBuild(ctx context.Context) (*Package, error) {
}
state := struct {
importResource *maps.Cache[string, resource.Resource]
resultResource *maps.Cache[string, resource.Resource]
importerImportContext *maps.Cache[string, importContext]
pathGroup *maps.Cache[string, string]
importResource *hmaps.Cache[string, resource.Resource]
resultResource *hmaps.Cache[string, resource.Resource]
importerImportContext *hmaps.Cache[string, importContext]
pathGroup *hmaps.Cache[string, string]
}{
importResource: maps.NewCache[string, resource.Resource](),
resultResource: maps.NewCache[string, resource.Resource](),
importerImportContext: maps.NewCache[string, importContext](),
pathGroup: maps.NewCache[string, string](),
importResource: hmaps.NewCache[string, resource.Resource](),
resultResource: hmaps.NewCache[string, resource.Resource](),
importerImportContext: hmaps.NewCache[string, importContext](),
pathGroup: hmaps.NewCache[string, string](),
}
multihostBasePaths := b.client.d.ResourceSpec.MultihostTargetBasePaths
@@ -24,6 +24,7 @@ import (
"github.com/bep/logg"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/htesting"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/internal/js/esbuild"
)
@@ -428,6 +429,7 @@ func TestBatchErrorScriptResourceInBundleSyntaxError(t *testing.T) {
}
func TestBatch(t *testing.T) {
htesting.SkipSlowTestUnlessCI(t)
files := `
-- hugo.toml --
disableKinds = ["taxonomy", "term"]
+2 -2
View File
@@ -19,8 +19,8 @@ import (
"path/filepath"
"strings"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/identity"
@@ -299,7 +299,7 @@ func (opts *Options) compile() (err error) {
var defines map[string]string
if opts.Defines != nil {
defines = maps.ToStringMapString(opts.Defines)
defines = hmaps.ToStringMapString(opts.Defines)
}
var drop api.Drop
+4 -4
View File
@@ -18,16 +18,16 @@ import (
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"github.com/evanw/esbuild/pkg/api"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/resources/resource"
"github.com/spf13/afero"
"slices"
)
const (
@@ -132,12 +132,12 @@ func ResolveResource(impPath string, resourceGetter resource.ResourceGetter) (r
}
func newFSResolver(fs afero.Fs) *fsResolver {
return &fsResolver{fs: fs, resolved: maps.NewCache[string, *hugofs.FileMeta]()}
return &fsResolver{fs: fs, resolved: hmaps.NewCache[string, *hugofs.FileMeta]()}
}
type fsResolver struct {
fs afero.Fs
resolved *maps.Cache[string, *hugofs.FileMeta]
resolved *hmaps.Cache[string, *hugofs.FileMeta]
}
func (r *fsResolver) resolveComponent(impPath string) *hugofs.FileMeta {
+27 -2
View File
@@ -68,6 +68,7 @@ typedef struct
int loopCount;
int frameCount;
int *frameDurations;
bool hasAlpha;
} InputParams;
@@ -79,6 +80,7 @@ typedef struct
int preset; // preset to use; resolved from hint.
bool useSharpYuv; // use sharp YUV for better quality.
int method; // quality/speed trade-off (0=fast, 6=slower-better). Default is 2.
} InputOptions;
@@ -316,6 +318,7 @@ static uint8_t initEncoderConfig(WebPConfig *config, InputOptions opts)
}
config->use_sharp_yuv = opts.useSharpYuv ? 1 : 0;
config->method = opts.method;
return 1;
}
@@ -410,6 +413,11 @@ InputMessage parse_input_message(const char *line)
msg.data.options.hint[sizeof(msg.data.options.hint) - 1] = '\0';
}
msg.data.options.useSharpYuv = json_object_get_number(options_object, "useSharpYuv") != 0;
msg.data.options.method = (int)json_object_get_number(options_object, "method");
if (msg.data.options.method < 0 || msg.data.options.method > 6)
{
msg.data.options.method = 4; // default
}
if (msg.data.options.quality < 0 || msg.data.options.quality > 100)
{
msg.data.options.quality = 75; // default
@@ -493,6 +501,7 @@ void write_output_message(const OutputMessage *msg)
json_object_set_number(params_object, "width", msg->data.params.width);
json_object_set_number(params_object, "height", msg->data.params.height);
json_object_set_number(params_object, "stride", msg->data.params.stride);
json_object_set_boolean(params_object, "hasAlpha", msg->data.params.hasAlpha);
if (msg->data.params.frameDurations != NULL)
{
JSON_Value *durations_value = json_value_init_array();
@@ -611,6 +620,7 @@ void handle_commands(FILE *stream)
output.data.params.stride = anim_info.canvas_width * 4;
output.data.params.frameCount = anim_info.frame_count;
output.data.params.loopCount = anim_info.loop_count;
output.data.params.hasAlpha = true; // Animated WebP always decoded as RGBA
int *frameDurations = malloc(sizeof(int) * anim_info.frame_count);
if (frameDurations == NULL)
@@ -680,7 +690,22 @@ void handle_commands(FILE *stream)
}
else
{
uint8_t *output_buffer = WebPDecodeRGBA(blob_data, (size_t)blob_size, &output.data.params.width, &output.data.params.height);
uint8_t *output_buffer;
int bytesPerPixel;
output.data.params.hasAlpha = config.input.has_alpha;
if (config.input.has_alpha)
{
output_buffer = WebPDecodeRGBA(blob_data, (size_t)blob_size, &output.data.params.width, &output.data.params.height);
bytesPerPixel = 4;
}
else
{
output_buffer = WebPDecodeRGB(blob_data, (size_t)blob_size, &output.data.params.width, &output.data.params.height);
bytesPerPixel = 3;
}
if (output_buffer == NULL)
{
strncpy(output.header.err, "Failed to decode WebP", sizeof(output.header.err) - 1);
@@ -688,7 +713,7 @@ void handle_commands(FILE *stream)
goto cleanup;
}
output.data.params.stride = output.data.params.width * 4;
output.data.params.stride = output.data.params.width * bytesPerPixel;
write_output_message(&output);
+8 -4
View File
@@ -30,9 +30,9 @@ import (
"github.com/bep/textandbinarywriter"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstrings"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/common/maps"
"golang.org/x/sync/errgroup"
"github.com/tetratelabs/wazero"
@@ -253,7 +253,7 @@ func (d *dispatcher[Q, R]) newCall(q Message[Q]) (*call[Q, R], error) {
if err := q.init(); err != nil {
return nil, err
}
responseKinds := maps.NewMap[string, bool]()
responseKinds := hmaps.NewMap[string, bool]()
for _, rk := range q.Header.ResponseKinds {
responseKinds.Set(rk, true)
}
@@ -424,7 +424,7 @@ func (d *dispatcher[Q, R]) pendingCall(id uint32) *call[Q, R] {
type call[Q, R any] struct {
request Message[Q]
response Message[R]
responseKinds *maps.Map[string, bool]
responseKinds *hmaps.Map[string, bool]
err error
donec chan *call[Q, R]
}
@@ -530,7 +530,11 @@ func Start[Q, R any](opts Options) (Dispatcher[Q, R], error) {
opts.PoolSize = 1
}
return newDispatcher[Q, R](opts)
d, err := newDispatcher[Q, R](opts)
if err != nil {
return nil, err
}
return d, nil
}
type dispatcherPool[Q, R any] struct {
Binary file not shown.
+33 -7
View File
@@ -34,9 +34,10 @@ var (
)
type CommonImageProcessingParams struct {
Width int `json:"width,omitempty"`
Height int `json:"height,omitempty"`
Stride int `json:"stride,omitempty"`
Width int `json:"width,omitempty"`
Height int `json:"height,omitempty"`
Stride int `json:"stride,omitempty"`
HasAlpha bool `json:"hasAlpha,omitempty"`
// For animated images.
FrameDurations []int `json:"frameDurations,omitempty"`
@@ -132,7 +133,7 @@ func (d *WebpCodec) Decode(r io.Reader) (image.Image, error) {
}
if len(out.Data.Params.FrameDurations) > 0 {
// Animated WebP.
// Animated WebP (always RGBA).
img := &WEBP{
frameDurations: out.Data.Params.FrameDurations,
loopCount: out.Data.Params.LoopCount,
@@ -153,9 +154,35 @@ func (d *WebpCodec) Decode(r io.Reader) (image.Image, error) {
return img, nil
}
var pix []byte
var nrgbaStride int
if out.Data.Params.HasAlpha {
// RGBA data (4 bytes per pixel).
pix = destination.Bytes()
nrgbaStride = stride
} else {
// RGB data (3 bytes per pixel) - convert to NRGBA.
rgbData := destination.Bytes()
nrgbaStride = w * 4
pix = make([]byte, nrgbaStride*h)
for y := 0; y < h; y++ {
srcIdx := y * stride
dstIdx := y * nrgbaStride
for x := 0; x < w; x++ {
pix[dstIdx+0] = rgbData[srcIdx+0] // R
pix[dstIdx+1] = rgbData[srcIdx+1] // G
pix[dstIdx+2] = rgbData[srcIdx+2] // B
pix[dstIdx+3] = 0xFF // A (fully opaque)
srcIdx += 3
dstIdx += 4
}
}
}
img := &image.NRGBA{
Pix: destination.Bytes(),
Stride: stride,
Pix: pix,
Stride: nrgbaStride,
Rect: image.Rect(0, 0, w, h),
}
@@ -294,7 +321,6 @@ func (d *WebpCodec) Encode(w io.Writer, img image.Image, opts map[string]any) er
}
opts = maps.Clone(opts)
opts["useSharpYuv"] = true // Use sharp (and slow) RGB->YUV conversion.
message := Message[WebpInput]{
Header: Header{
+53
View File
@@ -120,3 +120,56 @@ sourcefilename: ../../resources/testdata/giphy.gif
b.ImageHelper("public/anim_hu_edc2f24aaad2cee6.webp").AssertFormat("webp").AssertIsAnimated(true).AssertLoopCount(0).AssertFrameDurations(animFrameDurations)
b.ImageHelper("public/anim_hu_58eb49733894e7ce.gif").AssertFormat("gif").AssertIsAnimated(true).AssertLoopCount(0).AssertFrameDurations(animFrameDurations)
}
func BenchmarkWebp(b *testing.B) {
files := `
-- content/p1/sunrise.webp --
sourcefilename: ../../resources/testdata/sunrise.webp
-- content/p1/index.md --
-- content/p2/sunrise.webp --
sourcefilename: ../../resources/testdata/sunrise.webp
-- content/p2/index.md --
-- content/p3/sunrise.webp --
sourcefilename: ../../resources/testdata/sunrise.webp
-- content/p3/index.md --
-- content/p4/sunrise.webp --
sourcefilename: ../../resources/testdata/sunrise.webp
-- content/p4/index.md --
-- content/p5/sunrise.webp --
sourcefilename: ../../resources/testdata/sunrise.webp
-- content/p5/index.md --
-- content/p6/sunrise.webp --
sourcefilename: ../../resources/testdata/sunrise.webp
-- content/p6/index.md --
-- content/p7/sunrise.webp --
sourcefilename: ../../resources/testdata/sunrise.webp
-- content/p7/index.md --
-- content/p8/sunrise.webp --
sourcefilename: ../../resources/testdata/sunrise.webp
-- content/p8/index.md --
-- content/p9/sunrise.webp --
sourcefilename: ../../resources/testdata/sunrise.webp
-- content/p9/index.md --
-- content/p10/sunrise.webp --
sourcefilename: ../../resources/testdata/sunrise.webp
-- content/p10/index.md --
-- layouts/home.html --
Home.
-- layouts/page.html --
{{ $image := .Resources.Get "sunrise.webp" }}
{{ $resized := $image.Fit "400x300 webp" }}
Resized RelPermalink: {{ $resized.RelPermalink }}|
`
cfg := hugolib.IntegrationTestConfig{
T: b,
TxtarString: files,
}
for b.Loop() {
b.StopTimer()
builder := hugolib.NewIntegrationTestBuilder(cfg).Init()
b.StartTimer()
builder.Build()
}
}
+3 -3
View File
@@ -89,7 +89,7 @@ func (ls LanguagesInternal) ResolveIndex(name string) int {
return i
}
}
panic(fmt.Sprintf("no language found for name %q", name))
return -1
}
func (ls LanguagesInternal) Len() int {
@@ -97,10 +97,10 @@ func (ls LanguagesInternal) Len() int {
}
// IndexMatch returns an iterator for the roles that match the filter.
func (ls LanguagesInternal) IndexMatch(match predicate.P[string]) (iter.Seq[int], error) {
func (ls LanguagesInternal) IndexMatch(match predicate.P[predicate.IndexString]) (iter.Seq[int], error) {
return func(yield func(i int) bool) {
for i, l := range ls.Sorted {
if match(l.Name) {
if match(predicate.IndexString{Index: i, String: l.Name}) {
if !yield(i) {
return
}
+4 -4
View File
@@ -22,8 +22,8 @@ import (
"golang.org/x/text/collate"
"golang.org/x/text/language"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
"github.com/gohugoio/locales"
translators "github.com/gohugoio/localescompressed"
@@ -54,7 +54,7 @@ type Language struct {
isDefault bool
// This is just an alias of Site.Params.
params maps.Params
params hmaps.Params
}
// Name is an alias for Lang.
@@ -114,7 +114,7 @@ var DeprecationFunc = func(item, alternative string, err bool) {}
// Params returns the language params.
// Note that this is the same as the Site.Params, but we keep it here for legacy reasons.
// Deprecated: Use the site.Params instead.
func (l *Language) Params() maps.Params {
func (l *Language) Params() hmaps.Params {
// TODO(bep) Remove this for now as it created a little too much noise. Need to think about this.
// See https://github.com/gohugoio/hugo/issues/11025
// DeprecationFunc(".Language.Params", paramsDeprecationWarning, false)
@@ -167,7 +167,7 @@ func (l Languages) AsIndexSet() map[string]int {
// Internal access to unexported Language fields.
// This construct is to prevent them from leaking to the templates.
func SetParams(l *Language, params maps.Params) {
func SetParams(l *Language, params hmaps.Params) {
l.params = params
}

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