Compare commits

...

145 Commits

Author SHA1 Message Date
hugoreleaser 8a858213b7 releaser: Bump versions for release of 0.155.3
[ci skip]
2026-02-08 16:40:42 +00:00
Bjørn Erik Pedersen 6ac7d081d1 hugolib: Don't render default site redirect for non-primary isHTML output formats
When a custom output format has isHTML=true but no Path, renderDefaultSiteRedirect
would derive intermediate redirect paths from homeLink (e.g. /en/foo.html),
creating a spurious redirect at /en/ that overwrites the actual HTML content.

Only render the default site redirect for the canonical "html" format
or for formats with their own Path prefix (e.g. AMP).

Fixes #14482

Co-Authored-By: Joe Mooring <joe.mooring@veriphor.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 17:35:32 +01:00
Bjørn Erik Pedersen 24eb84f868 server: Fix stuck server global error logging
Fixes #14469
2026-02-06 15:06:19 +01:00
dependabot[bot] 95a3678245 build(deps): bump github.com/evanw/esbuild from 0.27.2 to 0.27.3
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.27.2 to 0.27.3.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.2...v0.27.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-06 10:50:39 +01:00
Bjørn Erik Pedersen 9045797d5f server: Fix panic when the server browser error handler tried to use a config in a state of flux
I tried and failed to create a test for this, but I have tested this manually.,

Fixes #14470
2026-02-05 20:11:48 +01:00
hugoreleaser 73641aeca1 releaser: Prepare repository for 0.156.0-DEV
[ci skip]
2026-02-02 10:27:50 +00:00
hugoreleaser d8c0dfccf7 releaser: Bump versions for release of 0.155.2
[ci skip]
2026-02-02 10:04:51 +00:00
Bjørn Erik Pedersen 6bd2bde9d3 resources/image: Add some image decode/encode debug logging
Fixes #14337
Closes #14460
2026-02-01 22:46:01 +01:00
Bjørn Erik Pedersen 0f1c7d1200 Fix template change detection for multi-version sites
When templates were edited in a multi-version/role setup, only pages
from the current site slice (h.Sites) were checked for dependencies,
not pages from all sites. Fix by changing withPage to iterate over
all sites using allSites.

Fixes #14461

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 22:24:52 +01:00
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
hugoreleaser fbcf0d52a1 releaser: Bump versions for release of 0.154.4
[ci skip]
2026-01-10 15:24:22 +00:00
dependabot[bot] a2b2a5a978 build(deps): bump github.com/goccy/go-yaml from 1.19.1 to 1.19.2
Bumps [github.com/goccy/go-yaml](https://github.com/goccy/go-yaml) from 1.19.1 to 1.19.2.
- [Release notes](https://github.com/goccy/go-yaml/releases)
- [Changelog](https://github.com/goccy/go-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/goccy/go-yaml/compare/v1.19.1...v1.19.2)

---
updated-dependencies:
- dependency-name: github.com/goccy/go-yaml
  dependency-version: 1.19.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-10 15:45:39 +01:00
Barkın Balcı ec178ea463 tpl: Fix language resolution for markdown shortcodes
Fixes #14098
2026-01-10 15:45:03 +01:00
Bjørn Erik Pedersen 2d80b8a741 For multiple dimensions setups, fix alias handling and multihost publish path
* The alias handling was left as is when we added new dimensions in v0.152.0, which meant that if you had `defaultContentVersionInSubDir=true` set, the alias page in the root would not be correctly created.
* Also, if you had multihost setup with multiple dimensions, the publish would be incorrect: The language key was added last instead of first.
* The home page alias handling is reworked and made more robust, which also fixes some subtle issues:

* It now supports redircects for multiple HTML output formats for the home page.
* We skip creating aliases for disabled home pages.
* With potentially multiple output formats with one of them canonical, we added a new  `Canonical` method to the `Page.OutputFormats`, which allows you to do this in a template:

```handlebars
{{ with .OutputFormats.Canonical }}<link rel="{{ .Rel }}" href="{{ .Permalink }}">{{ end }}
```

Fixes #14354
Fixes #14356
2026-01-10 15:34:51 +01:00
hugoreleaser 5ea3e13db6 releaser: Prepare repository for 0.155.0-DEV
[ci skip]
2026-01-06 16:51:07 +00:00
hugoreleaser b1c1bd019f releaser: Bump versions for release of 0.154.3
[ci skip]
2026-01-06 16:30:17 +00:00
dependabot[bot] 866b8e5aa6 build(deps): bump github.com/yuin/goldmark from 1.7.13 to 1.7.16
Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.7.13 to 1.7.16.
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.7.13...v1.7.16)

---
updated-dependencies:
- dependency-name: github.com/yuin/goldmark
  dependency-version: 1.7.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-06 10:53:03 +01:00
hugoreleaser 409669fa48 releaser: Prepare repository for 0.155.0-DEV
[ci skip]
2026-01-02 16:30:54 +00:00
hugoreleaser f66d094446 releaser: Bump versions for release of 0.154.2
[ci skip]
2026-01-02 16:08:44 +00:00
Bjørn Erik Pedersen e9b9b36f46 Fix alpha/fuzzy border issue with new webp decoder for images with with transparent background
Fixes #14339
2026-01-02 17:06:10 +01:00
Joe Mooring b8a2c10d06 snap: Limit build platforms to amd64, arm64, ppc64el, and s390x
The Node 24 Snap has dropped support for armhf and riscv64.
2026-01-01 19:58:01 -08:00
Joe Mooring 601df38834 snap: Update to core 24 2026-01-01 15:51:32 -08:00
hugoreleaser 3bed212a2f releaser: Prepare repository for 0.155.0-DEV
[ci skip]
2026-01-01 17:52:38 +00:00
hugoreleaser e2fd6764be releaser: Bump versions for release of 0.154.1
[ci skip]
2026-01-01 17:32:20 +00:00
Bjørn Erik Pedersen 8f3527f61c Add WASM licensing information to README
Added a section on licensing for the Hugo project, detailing the licenses of bundled libraries.
2026-01-01 18:27:57 +01:00
Bjørn Erik Pedersen 09048aad76 Fix partial decorator detection in partial with blocks with outer range break or continue
E.g.:

```handlebars
{{- $items := slice "a" "b" "c" }}
  {{- range $items }}
    {{- with partial "b" . -}}
	 {{break}}
	{{- else }}
	  else: {{ . -}}
	{{- end }}
  {{- end }}
 ```

Fixes #14333
2026-01-01 18:05:10 +01:00
hugoreleaser f42c422a12 releaser: Prepare repository for 0.155.0-DEV
[ci skip]
2025-12-31 13:09:12 +00:00
hugoreleaser 0b71db299a releaser: Bump versions for release of 0.154.0
[ci skip]
2025-12-31 12:45:55 +00:00
Bjørn Erik Pedersen 5c7fad23a3 tpl/collections: Fix apply to work with built-in funcs like len
Fixes #13418
2025-12-31 13:09:04 +01:00
Bjørn Erik Pedersen 700bb78198 Remove Linode sponsor from README
Removed Linode sponsor link from the README.
2025-12-30 19:54:11 +01:00
majiayu000 d3b5d47a43 helpers: Limit verbose watch output for better readability
Limits the number of root groups shown in 'Watching for changes' output
to 10 maximum, with a summary message for remaining paths. This prevents
the message from becoming excessively long when watching sites with many
mount points.

Fixes #14277

Signed-off-by: majiayu000 <1835304752@qq.com>
2025-12-30 19:44:35 +01:00
Bjørn Erik Pedersen 86cd1838ff tpl/reflect: Make the IsImageResource implementation less technical
And closer to the documentation. The old one was correct, but a bit too tied to the internal implementation.
2025-12-30 19:43:55 +01:00
Joe Mooring 871da3370a internal/warpc: Increase WebP memory limit to 384 MiB
Closes #14309
2025-12-30 16:51:58 +01:00
dependabot[bot] 2637aa1540 build(deps): bump github.com/tetratelabs/wazero from 1.10.1 to 1.11.0
Bumps [github.com/tetratelabs/wazero](https://github.com/tetratelabs/wazero) from 1.10.1 to 1.11.0.
- [Release notes](https://github.com/tetratelabs/wazero/releases)
- [Commits](https://github.com/tetratelabs/wazero/compare/v1.10.1...v1.11.0)

---
updated-dependencies:
- dependency-name: github.com/tetratelabs/wazero
  dependency-version: 1.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-30 16:01:32 +01:00
Bjørn Erik Pedersen bf1d20d735 Revert "resources/page: Fix slugorcontentbasename for section pages"
This reverts commit 25c7c18f9f.

See #14104
See #14325
2025-12-30 16:00:37 +01:00
Bjørn Erik Pedersen 1deec99bba Update tpl/reflect/reflect.go
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-30 16:00:19 +01:00
Bjørn Erik Pedersen b7bb557c6a Add reflect.Is{Page,Site,Resource,ImageResource}
Fixes #14307
2025-12-30 16:00:19 +01:00
Bjørn Erik Pedersen 7c19c196c3 Allow partials to work as decorators
Fixes #13193
2025-12-30 15:58:55 +01:00
hugoreleaser 555443b64a releaser: Prepare repository for 0.154.0-DEV
[ci skip]
2025-12-30 10:48:50 +00:00
hugoreleaser 1f2de189ed releaser: Bump versions for release of 0.153.5
[ci skip]
2025-12-30 10:27:17 +00:00
Bjørn Erik Pedersen c6ae33c61d images: Add compression option to image config and clean up some of the options handling
The original idea of setting quality to 0 for lossless WebP was didn't allow a setting that made sense for both JPEG and WebP.

Note that the above didn't really work at all, so this should not break anything.
2025-12-29 21:38:19 +01:00
Bjørn Erik Pedersen edeebf0d33 config: Fix cascade per language in hugo.toml regression
An onfurtunate side effect of the fix in v0.153.4.

Fixes #14321
2025-12-29 20:48:12 +01:00
Simon Heimlicher ea9675f6e7 images: Fix WebP quality and hint parameters being ignored
Fixes #14316
2025-12-29 16:47:43 +01:00
hugoreleaser b1f7e35a98 releaser: Prepare repository for 0.154.0-DEV
[ci skip]
2025-12-28 21:55:32 +00:00
hugoreleaser a0d4e1fb26 releaser: Bump versions for release of 0.153.4
[ci skip]
2025-12-28 21:34:16 +00:00
Bjørn Erik Pedersen 96777d9b89 Set cascade target to the content matrix if not set in the cascade itself
Fixes #14310

Co-authored-by: Joe Mooring <joe.mooring@veriphor.com>
2025-12-28 22:30:55 +01:00
hugoreleaser 25126e5f87 releaser: Prepare repository for 0.154.0-DEV
[ci skip]
2025-12-26 16:33:20 +00:00
hugoreleaser 179034abbb releaser: Bump versions for release of 0.153.3
[ci skip]
2025-12-26 15:33:04 +00:00
dependabot[bot] 96ac146eef build(deps): bump github.com/bep/imagemeta from 0.12.0 to 0.12.1
Bumps [github.com/bep/imagemeta](https://github.com/bep/imagemeta) from 0.12.0 to 0.12.1.
- [Release notes](https://github.com/bep/imagemeta/releases)
- [Commits](https://github.com/bep/imagemeta/compare/v0.12.0...v0.12.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-26 11:36:41 +01:00
Bjørn Erik Pedersen d4c0e4452c Fix error with _content.gotmpl file with index.md siblings
Regression introduced in Hugo 0.153.0.

Note that this is a very uncommon setup, but it worked before.

Fixes #14299
2025-12-25 14:22:14 +01:00
hugoreleaser 418156ec3e releaser: Prepare repository for 0.154.0-DEV
[ci skip]
2025-12-22 17:15:37 +00:00
hugoreleaser 798533a201 releaser: Bump versions for release of 0.153.2
[ci skip]
2025-12-22 16:53:01 +00:00
Bjørn Erik Pedersen a8c5d0d00d Fix "image: unknown format" error
Fixes #14295
2025-12-22 17:49:44 +01:00
Bjørn Erik Pedersen a94a941fe3 modules: Remove extended edition check
With WebP support now in the standard Hugo binary, this warning has too many false positives.

Fixes #14284
2025-12-22 11:12:10 +01:00
Joe Mooring 385d1a1342 misc: Update edition comparison and guidance in README.md 2025-12-20 21:37:30 +01:00
hugoreleaser d30bd50467 releaser: Prepare repository for 0.154.0-DEV
[ci skip]
2025-12-20 15:38:51 +00:00
hugoreleaser 8e6cac8462 releaser: Bump versions for release of 0.153.1
[ci skip]
2025-12-20 15:15:53 +00:00
Bjørn Erik Pedersen 4085ee9340 Handle PNG named *.webp
Fixes #14288
2025-12-20 16:12:15 +01:00
Bjørn Erik Pedersen 168bf17e70 Revert deprecation logging for contentDir per language
It was accidentally introduced because we compile it to file mounts, and `lang` on file mounts is deprecated in favor of `sites.matrix`.

But contentDir per language is something we have had for a long time, and is to useful too to deprecate.

Fixes #14287
2025-12-20 14:59:46 +01:00
Bjørn Erik Pedersen 80ea90c880 images: Add some more PNG tests
To try to figure out #14288.
2025-12-20 13:42:21 +01:00
Bjørn Erik Pedersen f740d7cfe0 Fix panic when 404 is backed by a content file
Fixes #14283
2025-12-20 13:42:21 +01:00
Joe Mooring 5f46da6e2a internal/warpc: Increase WebP memory limit to 256 MiB
Closes #14282


---------

Co-authored-by: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
2025-12-20 12:16:36 +01:00
hugoreleaser fe64a68225 releaser: Prepare repository for 0.154.0-DEV
[ci skip]
2025-12-19 10:35:25 +00:00
hugoreleaser b4128babb8 releaser: Bump versions for release of 0.153.0
[ci skip]
2025-12-19 10:14:01 +00:00
Bjørn Erik Pedersen 790a5b7014 hugoreleaser: Updage macospkgremote to increase notarization timeout
Apple's servers seems to be busy today.
2025-12-19 11:11:24 +01:00
Bjørn Erik Pedersen 70ce2446b5 Merge commit '08e1ea5c709d3d49bdc3ce3c21e8fa05a33150d0' 2025-12-19 10:07:30 +01:00
Bjørn Erik Pedersen 08e1ea5c70 Squashed 'docs/' changes from 71f739460..1ad3c75ad
1ad3c75ad theme: Run ncu -u to update dependencies (#3311)
2942d3753 content: Update version refs
e298fdfaf content: Remove ref to old version
c1fc876ea content: Improve taxonomic weight example
d706a013d content: Document additional functions
5751c37cc content: Standardize string function signatures
d65e6fae7 misc: Add data/docs.yaml to ignorePaths in cspell config
ba1f98a32 content: Improve collections.First/Last string examples
d7b5a2efd content: Improve collections.First/Last examples
e94e49db2 content: Add Smart Hugo to editor plugins
af204597a content: Clarify config description
9d1cb33cd content: Fix typo
dded23962 content: Clarify note on rare use case for PageRef method
8af9e3be4 content: Update the Homebrew installation description
be56390b3 content: Clarify command description
fa228e9f0 content: Clarify AsciiDoc extensions setting description
3a23a4af2 docs: Add best practices for manual summary marker placement
050d7c75e misc: Fix cspell configuration
74642b18d content: Miscellaneous edits
75a2c8425 content: Add missing reference definitions
6303f27e1 content: Improve description of the SITE.Home method
4dfbd647f Revert the minimal analytics changes (#3265)
c369e534d theme: Move tracking to turbo:load
88b451022 theme: Move tracking to turbo:render
7040dc5ee Move to minimalanalytics for GA tracking
4e8a523fb content: Update KaTeX link attributes to v0.16.25 (phase 2)
21046b942 content: Update KaTeX link attributes to v0.16.25 (phase 1)
b8df9d941 theme: Use Path for the glossary link destination
acd6c2954 theme: Use page Path when generating Markdown links
543f78340 content: Fix formatting
aef89d192 content: Correct page collection quick-reference data
08645236a content: Use RSS output format in shortcode lookup example
58654c106 content: Fix archetype lookup order
9f79bacc4 content: Describe valid uses of the nil keyword
41f139d14 theme: Remove the root view transition
6ab407bb9 content: Improve links on lang.Translate page
91e724065 content: Add missing fragments to links in the module mounts description
2f6b4a58c content: Clarify ByCount sort order
8594590b2 Update netlify.toml
811574060 content: Update version references
a12bcf7dd content: Update version references
23ca199ed Update netlify.toml
5d3da2351 content: Update version references
1617ee75b misc: Update Netlify config to build with v0.152.0
6467bda93 content: Remove outdated new-in badges
4b3ef4bd5 content: Improve description of default mount behavior
5a861f500 content: Revise partials.IncludeCached description and signature
2ad9d705a content: Correct the partials.IncludeCached signature
9f611b879 Update netlify.toml
ca21f5fbd content: Add warning about automatic summaries
2065fadde content: Update commit message guidelines
a0784f261 content: Remove outdated new-in badges
ce5a249f2 content: Remove Codeberg Pages documentation
3424b95f2 content: Update CLI docs
27fd350a6 content: Change KaTeX version references to 0.16.23
ba8c5fd3b content: Change MathJax version reference from v3 to v4
c83465222 content: Fix broken link
c6cde1074 content: Update version references
467277939 content: Fix typo
e424b6b19 content: Document new features in v0.151.0
a46898f79 Update netlify.toml
f8407c3b5 content: Enhance content view template details
974175690 content: Optionally disable whitespace trimming in fenced code blocks
25bffc4c4 Update netlify.toml
98e9ef5f0 theme: Move initColorScheme() to head
a1566495b theme: Preload fonts (#3209)
c0271d266 content: Add missing word in deploy-with-rsync.md
47099c4fa content: Remove outdated new-in badges
439b5c18b content: Fix collections.D seed example
d2486ba67 content: Update description of IsNode method
4de8815e9 content: Update description of the cascade front matter field
0801e9b3f content: Fix broken link
9bbbfb59d content: Update Netlify hosting guide and version refs
4f444fb53 misc: Update docs.yaml
a7a9a7563 Update module.md
a7909fe37 Update netlify.toml
47a2c2038 content: Update collections.D page to remove casting note
ecca5ef48 content: Update summaryLength description with default value
310e11195 content: Change alias example
bd6b53b5d content: Update minify config documentation
21bb0bb66 Update netlify.toml
68912f1a3 content: Update version reference
28a47a50f content: Fix missing link to latest release under Prebuilt Binaries
8eac2a120 theme: Revise inline code span styling
2e71b6d0a Adjust sponsor
d93340a78 content: Update documentation guide
9f6213ebe content: Fix glob pattern-alternatives syntax in cascade.md
1f61fd6be content: Add seed examples to collections.D
65385a5d0 content: Fix Cloudflare deploy not_found_handling value
4f70df235 content: Fix typo
fc216f2d9 content: Fix broken link
e98b340f2 Update cascade.md
e316dbf93 content: Update version references
f8e863e97 misc: Remove textlint config file
3b9c3e2a1 content: Miscellaneous updates related to v0.149.0
3ccc439fc Update netlify.toml
07f5140e8 Merge branch 'tempv0.149.0'
17f8d33d8 content: Add format option to transform.Unmarshal
a6a971596 content: Document the collections.D function
66eee712b content: Update front matter configuration example
863945e13 content: Update fmt.Println example
1d9a92bc7 theme: Don't exclude current section from related content
1386d8ed2 Update introduction.md
0ac89b015 content: Replace inline URLs with autolinks
54fad1e01 content: Fix typo
e09f6b0c5 content: Wrap calls to eturl shortcode in angle brackets
23efd9766 Merge commit 'bfa74537929f409fca841540b971125b7678963a'
e98754e72 resources/page: Add :sectionslug and :sectionslugs permalink tokens

git-subtree-dir: docs
git-subtree-split: 1ad3c75ad0e997d66598c1453db9989fde7d9680
2025-12-19 10:04:19 +01:00
Bjørn Erik Pedersen 65d43e1d0d resources/images: Don't trust the file extension when decoding JPEG and PNG images
Which is it behaved before we added the extended WebP support.

Testing on some sites shows that it's not uncommon to store JPEGs with PNG extensions and vice versa.
This an error situation that needs to be reported, but let us push that to a future Hugo version to reduce the noise in this one.
2025-12-18 19:36:48 +01:00
Bjørn Erik Pedersen 65a76664e3 Add full filename to image processing error messages if possible
Fixes #14278
2025-12-18 17:46:26 +01:00
Bjørn Erik Pedersen da5b1fcf8f tailwindcss: Add referece to skipInlineImportsNotFound when import not found in assets
See #14273
2025-12-18 10:57:01 +01:00
dependabot[bot] 39649dce49 build(deps): bump github.com/goccy/go-yaml from 1.19.0 to 1.19.1
Bumps [github.com/goccy/go-yaml](https://github.com/goccy/go-yaml) from 1.19.0 to 1.19.1.
- [Release notes](https://github.com/goccy/go-yaml/releases)
- [Changelog](https://github.com/goccy/go-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/goccy/go-yaml/compare/v1.19.0...v1.19.1)

---
updated-dependencies:
- dependency-name: github.com/goccy/go-yaml
  dependency-version: 1.19.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-18 10:12:10 +01:00
dependabot[bot] 614fb16ec6 build(deps): bump github.com/alecthomas/chroma/v2 from 2.21.0 to 2.21.1
Bumps [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) from 2.21.0 to 2.21.1.
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.21.0...v2.21.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-18 10:11:52 +01:00
Bjørn Erik Pedersen 0637adb239 Improve error messages for template failures 2025-12-17 21:12:15 +01:00
Bjørn Erik Pedersen 0bf61353f6 Improve error handling/messages in Hugo Pipes
Fixes #14257
Closes #14270
2025-12-17 21:12:15 +01:00
Bjørn Erik Pedersen b82e496c13 Fix some outdated front matter
Fixes #14271
2025-12-17 18:51:02 +01:00
dependabot[bot] 33542d3966 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.2 to 1.58.3.
- [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.2...service/s3/v1.58.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-17 13:02:00 +01:00
Bjørn Erik Pedersen 588d20b315 deps: Upgrade github.com/alecthomas/chroma/v2 v2.20.0 => v2.21.0
Closes #14266
2025-12-17 11:18:56 +01:00
dependabot[bot] b33730293d build(deps): bump github.com/aws/aws-sdk-go-v2 from 1.40.1 to 1.41.0
Bumps [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) from 1.40.1 to 1.41.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/v1.40.1...v1.41.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-17 10:23:06 +01:00
dependabot[bot] e6a3f1d62b build(deps): bump github.com/evanw/esbuild from 0.27.1 to 0.27.2
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.27.1 to 0.27.2.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.1...v0.27.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-17 10:07:25 +01:00
Bjørn Erik Pedersen 573ecb530f images: Add a webp test with bg color 2025-12-17 09:41:25 +01:00
Bjørn Erik Pedersen 9937a5dcaa tpl/css: Deprecate libsass in favor of dartsass (note)
The upstream library is now EOL and the repo is archived. Also, it's time for us to move away from the extended CGO build and for Hugo users not already done that, move to https://gohugo.io/functions/css/sass/#dart-sass

https://github.com/sass/libsass

Closes #14261
2025-12-16 22:31:13 +01:00
Bjørn Erik Pedersen 1b4514e020 Encode and Decode using the libwebp library via WASM with animation support
Fixes #10030
Fixes #8500
Fixes #12843
Fixes #8879
Fixes #12842
2025-12-16 19:29:16 +01:00
Joe Mooring 429e57201d config/allconfig: Correct error message
Closes #14259
2025-12-15 18:09:57 +01:00
Joe Mooring fa7d37f0c8 tpl: Add missing functions to init files
Closes #14249
2025-12-13 18:32:26 +01:00
Bjørn Erik Pedersen fae49a22f1 github: Add some known humans to the AI whitelist 2025-12-13 18:29:28 +01:00
495 changed files with 12424 additions and 3195 deletions
+1 -1
View File
@@ -19,4 +19,4 @@ jobs:
pr-label: AI Suspect
fail-when-confident: false
# CSV list.
pr-user-logins-skip: dependabot[bot]
pr-user-logins-skip: dependabot[bot],bep,jmooring
+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
+78 -71
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
@@ -65,8 +65,6 @@ See the [features] section of the documentation for a comprehensive summary of H
<p>&nbsp;</p>
<p float="left">
<a href="https://www.linode.com/?utm_campaign=hugosponsor&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/hugoDocs/master/assets/images/sponsors/linode-logo_standard_light_medium.png" width="200" alt="Linode"></a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.jetbrains.com/go/?utm_source=OSS&utm_medium=referral&utm_campaign=hugo" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/hugoDocs/master/assets/images/sponsors/goland.svg" width="200" alt="The complete IDE crafted for professional Go developers."></a>
&nbsp;&nbsp;&nbsp;
<a href="https://cloudcannon.com/hugo-cms/?utm_campaign=HugoSponsorship&utm_source=sponsor&utm_content=gohugo" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/hugoDocs/master/assets/images/sponsors/cloudcannon-cms-logo.svg" width="200" alt="CloudCannon"></a>
@@ -74,21 +72,17 @@ See the [features] section of the documentation for a comprehensive summary of H
## Editions
Hugo is available in three editions: standard, extended, and extended/deploy. While the standard edition provides core functionality, the extended and extended/deploy editions offer advanced features.
Hugo offers a standard edition with core features, plus extended and extended/deploy editions with more. Use the standard edition unless you need the features below.
Feature|extended edition|extended/deploy edition
:--|:-:|:-:
Encode to the WebP format when [processing images]. You can decode WebP images with any edition.|:heavy_check_mark:|:heavy_check_mark:
[Transpile Sass to CSS] using the embedded LibSass transpiler. You can use the [Dart Sass] transpiler with any edition.|:heavy_check_mark:|:heavy_check_mark:
[Transpile Sass to CSS] via embedded LibSass. Note that embedded LibSass was deprecated in v0.153.0 and will be removed in a future release. Use the [Dart Sass] transpiler instead, which is compatible with any edition.|:heavy_check_mark:|:heavy_check_mark:
Deploy your site directly to a Google Cloud Storage bucket, an AWS S3 bucket, or an Azure Storage container. See&nbsp;[details].|:x:|:heavy_check_mark:
[dart sass]: https://gohugo.io/functions/css/sass/#dart-sass
[processing images]: https://gohugo.io/content-management/image-processing/
[transpile sass to css]: https://gohugo.io/functions/css/sass/
[details]: https://gohugo.io/hosting-and-deployment/hugo-deploy/
Unless your specific deployment needs require the extended/deploy edition, we recommend the extended edition.
## Installation
Install Hugo from a [prebuilt binary], package manager, or package repository. Please see the installation instructions for your operating system:
@@ -162,6 +156,16 @@ If there is sufficient interest, [create a proposal]. Do not submit a pull reque
For a complete guide to contributing to Hugo, see the [Contribution Guide](CONTRIBUTING.md).
## License
For the Hugo source code, see [LICENSE](/LICENSE).
We also bundle some libraries in binary/WASM form:
* [libwebp](https://github.com/webmproject/libwebp), [BSD-3-Clause license](https://github.com/webmproject/libwebp?tab=BSD-3-Clause-1-ov-file#readme)
* [Katex](https://github.com/KaTeX/KaTeX), [MIT license](https://github.com/KaTeX/KaTeX?tab=MIT-1-ov-file#readme)
* [QuickJS](https://github.com/bellard/quickjs?tab=License-1-ov-file#readme), [License](https://github.com/bellard/quickjs?tab=License-1-ov-file#readme)
## Dependencies
Hugo stands on the shoulders of great open source libraries. Run `hugo env --logLevel info` to display a list of dependencies.
@@ -171,51 +175,55 @@ Hugo stands on the shoulders of great open source libraries. Run `hugo env --log
```text
github.com/BurntSushi/locker="v0.0.0-20171006230638-a6e239ea1c69"
github.com/PuerkitoBio/goquery="v1.10.1"
github.com/alecthomas/chroma/v2="v2.15.0"
github.com/andybalholm/cascadia="v1.3.3"
github.com/armon/go-radix="v1.0.1-0.20221118154546-54df44f2176c"
github.com/JohannesKaufmann/dom="v0.2.0"
github.com/JohannesKaufmann/html-to-markdown/v2="v2.5.0"
github.com/alecthomas/chroma/v2="v2.21.1"
github.com/aymerick/douceur="v0.2.0"
github.com/bep/clocks="v0.5.0"
github.com/bep/debounce="v1.2.0"
github.com/bep/gitmap="v1.6.0"
github.com/bep/gitmap="v1.9.0"
github.com/bep/goat="v0.5.0"
github.com/bep/godartsass/v2="v2.3.2"
github.com/bep/godartsass/v2="v2.5.0"
github.com/bep/golibsass="v1.2.0"
github.com/bep/gowebp="v0.3.0"
github.com/bep/imagemeta="v0.8.4"
github.com/bep/lazycache="v0.7.0"
github.com/bep/goportabletext="v0.1.0"
github.com/bep/helpers="v0.6.0"
github.com/bep/imagemeta="v0.12.0"
github.com/bep/lazycache="v0.8.0"
github.com/bep/logg="v0.4.0"
github.com/bep/mclib="v1.20400.20402"
github.com/bep/overlayfs="v0.9.2"
github.com/bep/simplecobra="v0.5.0"
github.com/bep/overlayfs="v0.10.0"
github.com/bep/simplecobra="v0.6.1"
github.com/bep/textandbinarywriter="v0.0.0-20251212174530-cd9f0732f60f"
github.com/bep/tmc="v0.5.1"
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/cpuguy83/go-md2man/v2="v2.0.4"
github.com/clipperhouse/displaywidth="v0.6.0"
github.com/clipperhouse/stringish="v0.1.1"
github.com/clipperhouse/uax29/v2="v2.3.0"
github.com/cpuguy83/go-md2man/v2="v2.0.6"
github.com/disintegration/gift="v1.2.1"
github.com/dlclark/regexp2="v1.11.5"
github.com/dop251/goja="v0.0.0-20250125213203-5ef83b82af17"
github.com/evanw/esbuild="v0.24.2"
github.com/evanw/esbuild="v0.27.2"
github.com/fatih/color="v1.18.0"
github.com/frankban/quicktest="v1.14.6"
github.com/fsnotify/fsnotify="v1.8.0"
github.com/getkin/kin-openapi="v0.129.0"
github.com/ghodss/yaml="v1.0.0"
github.com/fsnotify/fsnotify="v1.9.0"
github.com/getkin/kin-openapi="v0.133.0"
github.com/go-openapi/jsonpointer="v0.21.0"
github.com/go-openapi/swag="v0.23.0"
github.com/go-sourcemap/sourcemap="v2.1.4+incompatible"
github.com/gobuffalo/flect="v1.0.3"
github.com/gobwas/glob="v0.2.3"
github.com/gohugoio/go-i18n/v2="v2.1.3-0.20230805085216-e63c13218d0e"
github.com/gohugoio/hashstructure="v0.5.0"
github.com/gohugoio/httpcache="v0.7.0"
github.com/gohugoio/hugo-goldmark-extensions/extras="v0.2.0"
github.com/gohugoio/hugo-goldmark-extensions/passthrough="v0.3.0"
github.com/goccy/go-yaml="v1.19.1"
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"
github.com/gohugoio/httpcache="v0.8.0"
github.com/gohugoio/hugo-goldmark-extensions/extras="v0.5.0"
github.com/gohugoio/hugo-goldmark-extensions/passthrough="v0.3.1"
github.com/gohugoio/locales="v0.14.0"
github.com/gohugoio/localescompressed="v1.0.1"
github.com/golang/freetype="v0.0.0-20170609003504-e2365dfdc4a0"
github.com/google/go-cmp="v0.6.0"
github.com/google/pprof="v0.0.0-20250208200701-d0013a598941"
github.com/google/go-cmp="v0.7.0"
github.com/gorilla/css="v1.0.1"
github.com/gorilla/websocket="v1.5.3"
github.com/hairyhenderson/go-codeowners="v0.7.0"
github.com/hashicorp/golang-lru/v2="v2.0.7"
@@ -224,58 +232,57 @@ github.com/josharian/intern="v1.0.0"
github.com/kr/pretty="v0.3.1"
github.com/kr/text="v0.2.0"
github.com/kyokomi/emoji/v2="v2.2.13"
github.com/lucasb-eyer/go-colorful="v1.2.0"
github.com/mailru/easyjson="v0.7.7"
github.com/makeworld-the-better-one/dither/v2="v2.4.0"
github.com/marekm4/color-extractor="v1.2.1"
github.com/mattn/go-colorable="v0.1.13"
github.com/mattn/go-isatty="v0.0.20"
github.com/mattn/go-runewidth="v0.0.9"
github.com/mazznoer/csscolorparser="v0.1.5"
github.com/mattn/go-runewidth="v0.0.19"
github.com/microcosm-cc/bluemonday="v1.0.27"
github.com/mitchellh/mapstructure="v1.5.1-0.20231216201459-8508981c8b6c"
github.com/mohae/deepcopy="v0.0.0-20170929034955-c48cc78d4826"
github.com/muesli/smartcrop="v0.3.0"
github.com/niklasfasching/go-org="v1.7.0"
github.com/oasdiff/yaml3="v0.0.0-20241210130736-a94c01f36349"
github.com/oasdiff/yaml="v0.0.0-20241210131133-6b86fb107d80"
github.com/olekukonko/tablewriter="v0.0.5"
github.com/niklasfasching/go-org="v1.9.1"
github.com/oasdiff/yaml3="v0.0.0-20250309153720-d2182401db90"
github.com/oasdiff/yaml="v0.0.0-20250309154309-f31be36b4037"
github.com/olekukonko/cat="v0.0.0-20250911104152-50322a0618f6"
github.com/olekukonko/errors="v1.1.0"
github.com/olekukonko/ll="v0.1.3"
github.com/olekukonko/tablewriter="v1.1.2"
github.com/pbnjay/memory="v0.0.0-20210728143218-7b4eea64cf58"
github.com/pelletier/go-toml/v2="v2.2.3"
github.com/pelletier/go-toml/v2="v2.2.4"
github.com/perimeterx/marshmallow="v1.1.5"
github.com/pkg/browser="v0.0.0-20240102092130-5ac0b6a4141c"
github.com/pkg/errors="v0.9.1"
github.com/rivo/uniseg="v0.4.7"
github.com/rogpeppe/go-internal="v1.13.1"
github.com/rogpeppe/go-internal="v1.14.1"
github.com/russross/blackfriday/v2="v2.1.0"
github.com/sass/libsass="3.6.6"
github.com/spf13/afero="v1.11.0"
github.com/spf13/cast="v1.7.1"
github.com/spf13/cobra="v1.8.1"
github.com/sass/dart-sass/compiler="1.97.1"
github.com/sass/dart-sass/implementation="1.97.1"
github.com/sass/dart-sass/protocol="3.2.0"
github.com/spf13/afero="v1.15.0"
github.com/spf13/cast="v1.10.0"
github.com/spf13/cobra="v1.10.2"
github.com/spf13/fsync="v0.10.1"
github.com/spf13/pflag="v1.0.6"
github.com/tdewolff/minify/v2="v2.20.37"
github.com/tdewolff/parse/v2="v2.7.15"
github.com/tetratelabs/wazero="v1.8.2"
github.com/webmproject/libwebp="v1.3.2"
github.com/yuin/goldmark-emoji="v1.0.4"
github.com/yuin/goldmark="v1.7.8"
github.com/spf13/pflag="v1.0.9"
github.com/tdewolff/minify/v2="v2.24.8"
github.com/tdewolff/parse/v2="v2.8.5"
github.com/tetratelabs/wazero="v1.10.1"
github.com/webmproject/libwebp="v1.6.0"
github.com/woodsbury/decimal128="v1.3.0"
github.com/yuin/goldmark-emoji="v1.0.6"
github.com/yuin/goldmark="v1.7.13"
go.uber.org/automaxprocs="v1.5.3"
golang.org/x/crypto="v0.33.0"
golang.org/x/exp="v0.0.0-20250210185358-939b2ce775ac"
golang.org/x/image="v0.24.0"
golang.org/x/mod="v0.23.0"
golang.org/x/net="v0.35.0"
golang.org/x/sync="v0.11.0"
golang.org/x/sys="v0.30.0"
golang.org/x/text="v0.22.0"
golang.org/x/tools="v0.30.0"
golang.org/x/xerrors="v0.0.0-20240903120638-7835f813f4da"
gonum.org/v1/plot="v0.15.0"
google.golang.org/protobuf="v1.36.5"
gopkg.in/yaml.v2="v2.4.0"
go.yaml.in/yaml/v3="v3.0.4"
golang.org/x/crypto="v0.46.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/sync="v0.19.0"
golang.org/x/sys="v0.39.0"
golang.org/x/text="v0.32.0"
golang.org/x/tools="v0.40.0"
google.golang.org/protobuf="v1.36.10"
gopkg.in/yaml.v3="v3.0.1"
oss.terrastruct.com/d2="v0.6.9"
oss.terrastruct.com/util-go="v0.0.0-20241005222610-44c011a04896"
rsc.io/qr="v0.2.0"
software.sslmate.com/src/go-pkcs12="v0.2.0"
```
+2 -2
View File
@@ -69,7 +69,7 @@ func New(opts Options) *Cache {
infol := opts.Log.InfoCommand("dynacache")
evictedIdentities := collections.NewStack[KeyIdentity]()
evictedIdentities := collections.NewStackThreadSafe[KeyIdentity]()
onEvict := func(k, v any) {
if !opts.Watching {
@@ -129,7 +129,7 @@ type Cache struct {
partitions map[string]PartitionManager
onEvict func(k, v any)
evictedIdentities *collections.Stack[KeyIdentity]
evictedIdentities *collections.StackThreadSafe[KeyIdentity]
opts Options
infol logg.LevelLogger
+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"
+3
View File
@@ -331,6 +331,9 @@ func (r *rootCommand) ConfigFromProvider(key configKey, cfg config.Provider) (*c
}
func (r *rootCommand) HugFromConfig(conf *commonConfig) (*hugolib.HugoSites, error) {
if conf == nil {
return nil, fmt.Errorf("conf must be set")
}
k := configKey{counter: r.configVersionID.Load()}
h, _, err := r.hugoSites.GetOrCreate(k, func(key configKey) (*hugolib.HugoSites, error) {
depsCfg := r.newDepsConfig(conf)
+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)
+29 -5
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"
@@ -56,8 +56,9 @@ import (
type hugoBuilder struct {
r *rootCommand
confmu sync.Mutex
conf *commonConfig
confmu sync.Mutex
confOld *commonConfig
conf *commonConfig
// May be nil.
s *serverCommand
@@ -94,6 +95,27 @@ func (c *hugoBuilder) withConf(fn func(conf *commonConfig)) {
fn(c.conf)
}
func (c *hugoBuilder) withConfOrOldConf(fn func(conf *commonConfig)) {
c.confmu.Lock()
defer c.confmu.Unlock()
if c.conf != nil {
fn(c.conf)
} else if c.confOld != nil {
fn(c.confOld)
}
}
func (c *hugoBuilder) withConfOrOldConfE(fn func(conf *commonConfig) error) error {
c.confmu.Lock()
defer c.confmu.Unlock()
if c.conf != nil {
return fn(c.conf)
} else if c.confOld != nil {
return fn(c.confOld)
}
return errConfigNotSet
}
type hugoBuilderErrState struct {
mu sync.Mutex
paused bool
@@ -1077,7 +1099,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(),
@@ -1095,6 +1117,7 @@ func (c *hugoBuilder) loadConfig(cd *simplecobra.Commandeer, running bool) error
}
c.conf = conf
c.confOld = conf
if c.onConfigLoaded != nil {
if err := c.onConfigLoaded(false); err != nil {
return err
@@ -1158,8 +1181,9 @@ func (c *hugoBuilder) reloadConfig() error {
c.r.resetLogs()
c.r.configVersionID.Add(1)
if err := c.withConfE(func(conf *commonConfig) error {
if err := c.withConfOrOldConfE(func(conf *commonConfig) error {
oldConf := conf
c.conf = nil
newConf, err := c.r.ConfigFromConfig(configKey{counter: c.r.configVersionID.Load()}, conf)
if err != nil {
return err
+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))
},
}
+1 -1
View File
@@ -282,7 +282,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
}
port = 1313
f.c.withConf(func(conf *commonConfig) {
f.c.withConfOrOldConf(func(conf *commonConfig) {
if lrport := conf.configs.GetFirstLanguageConfig().BaseURLLiveReload().Port(); lrport != 0 {
port = lrport
}
+55 -11
View File
@@ -21,24 +21,24 @@ import (
"github.com/gohugoio/hugo/common/hiter"
)
// Stack is a simple LIFO stack that is safe for concurrent use.
type Stack[T any] struct {
// StackThreadSafe is a simple LIFO stack that is safe for concurrent use.
type StackThreadSafe[T any] struct {
items []T
zero T
mu sync.RWMutex
}
func NewStack[T any]() *Stack[T] {
return &Stack[T]{}
func NewStackThreadSafe[T any]() *StackThreadSafe[T] {
return &StackThreadSafe[T]{}
}
func (s *Stack[T]) Push(item T) {
func (s *StackThreadSafe[T]) Push(item T) {
s.mu.Lock()
defer s.mu.Unlock()
s.items = append(s.items, item)
}
func (s *Stack[T]) Pop() (T, bool) {
func (s *StackThreadSafe[T]) Pop() (T, bool) {
s.mu.Lock()
defer s.mu.Unlock()
if len(s.items) == 0 {
@@ -49,7 +49,7 @@ func (s *Stack[T]) Pop() (T, bool) {
return item, true
}
func (s *Stack[T]) Peek() (T, bool) {
func (s *StackThreadSafe[T]) Peek() (T, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
if len(s.items) == 0 {
@@ -58,18 +58,18 @@ func (s *Stack[T]) Peek() (T, bool) {
return s.items[len(s.items)-1], true
}
func (s *Stack[T]) Len() int {
func (s *StackThreadSafe[T]) Len() int {
s.mu.RLock()
defer s.mu.RUnlock()
return len(s.items)
}
// All returns all items in the stack, from bottom to top.
func (s *Stack[T]) All() iter.Seq2[int, T] {
func (s *StackThreadSafe[T]) All() iter.Seq2[int, T] {
return hiter.Lock2(slices.All(s.items), s.mu.RLock, s.mu.RUnlock)
}
func (s *Stack[T]) Drain() []T {
func (s *StackThreadSafe[T]) Drain() []T {
s.mu.Lock()
defer s.mu.Unlock()
items := s.items
@@ -77,7 +77,7 @@ func (s *Stack[T]) Drain() []T {
return items
}
func (s *Stack[T]) DrainMatching(predicate func(T) bool) []T {
func (s *StackThreadSafe[T]) DrainMatching(predicate func(T) bool) []T {
s.mu.Lock()
defer s.mu.Unlock()
var items []T
@@ -89,3 +89,47 @@ func (s *Stack[T]) DrainMatching(predicate func(T) bool) []T {
}
return items
}
// Stack is a simple LIFO stack that is not safe for concurrent use.
type Stack[T any] struct {
items []T
zero T
}
func NewStack[T any]() *Stack[T] {
return &Stack[T]{}
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
return s.zero, false
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item, true
}
func (s *Stack[T]) Peek() (T, bool) {
if len(s.items) == 0 {
return s.zero, false
}
return s.items[len(s.items)-1], true
}
func (s *Stack[T]) Len() int {
return len(s.items)
}
func (s *Stack[T]) All() iter.Seq2[int, T] {
return slices.All(s.items)
}
func (s *Stack[T]) Drain() []T {
items := s.items
s.items = nil
return items
}
+4 -4
View File
@@ -10,7 +10,7 @@ func TestNewStack(t *testing.T) {
t.Parallel()
c := qt.New(t)
s := NewStack[int]()
s := NewStackThreadSafe[int]()
c.Assert(s, qt.IsNotNil)
}
@@ -19,7 +19,7 @@ func TestStackBasic(t *testing.T) {
t.Parallel()
c := qt.New(t)
s := NewStack[int]()
s := NewStackThreadSafe[int]()
c.Assert(s.Len(), qt.Equals, 0)
@@ -50,7 +50,7 @@ func TestStackDrain(t *testing.T) {
t.Parallel()
c := qt.New(t)
s := NewStack[string]()
s := NewStackThreadSafe[string]()
s.Push("a")
s.Push("b")
@@ -64,7 +64,7 @@ func TestStackDrainMatching(t *testing.T) {
t.Parallel()
c := qt.New(t)
s := NewStack[int]()
s := NewStackThreadSafe[int]()
s.Push(1)
s.Push(2)
s.Push(3)
+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)
}
+44 -24
View File
@@ -288,6 +288,50 @@ func Unwrap(err error) error {
return err
}
// UnwrapFileErrors returns all FileError contained in err.
func UnwrapFileErrors(err error) []FileError {
if err == nil {
return nil
}
errs := Errors(err)
var fileErrors []FileError
for _, e := range errs {
if v, ok := e.(FileError); ok {
fileErrors = append(fileErrors, v)
}
fileErrors = append(fileErrors, UnwrapFileErrors(errors.Unwrap(e))...)
}
return fileErrors
}
// UnwrapFileErrorsWithErrorContext tries to unwrap all FileError in err that has an ErrorContext.
func UnwrapFileErrorsWithErrorContext(err error) []FileError {
errs := UnwrapFileErrors(err)
var n int
for _, e := range errs {
if e.ErrorContext() != nil {
errs[n] = e
n++
}
}
return errs[:n]
}
// Errors returns the list of errors contained in err.
func Errors(err error) []error {
if err == nil {
return nil
}
type unwrapper interface {
Unwrap() []error
}
if u, ok := err.(unwrapper); ok {
return u.Unwrap()
}
return []error{err}
}
func extractFileTypePos(err error) (string, text.Position) {
err = Unwrap(err)
@@ -350,30 +394,6 @@ func UnwrapFileError(err error) FileError {
return nil
}
// UnwrapFileErrors tries to unwrap all FileError.
func UnwrapFileErrors(err error) []FileError {
var errs []FileError
for err != nil {
if v, ok := err.(FileError); ok {
errs = append(errs, v)
}
err = errors.Unwrap(err)
}
return errs
}
// UnwrapFileErrorsWithErrorContext tries to unwrap all FileError in err that has an ErrorContext.
func UnwrapFileErrorsWithErrorContext(err error) []FileError {
var errs []FileError
for err != nil {
if v, ok := err.(FileError); ok && v.ErrorContext() != nil {
errs = append(errs, v)
}
err = errors.Unwrap(err)
}
return errs
}
func extractOffsetAndType(e error) (int, string) {
switch v := e.(type) {
case *json.UnmarshalTypeError:
+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
}
+67
View File
@@ -0,0 +1,67 @@
// Copyright 2025 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 himage provides some high level image types and interfaces.
package himage
import "image"
// AnimatedImage represents an animated image.
// This is currently supported for GIF and WebP images.
type AnimatedImage interface {
image.Image // The first frame.
GetRaw() any // *gif.GIF or *WEBP.
GetLoopCount() int // Number of times to loop the animation. 0 means infinite.
ImageFrames
}
// ImageFrames provides access to the frames of an animated image.
type ImageFrames interface {
GetFrames() []image.Image
// Frame durations in milliseconds.
// Note that Gif frame durations are in 100ths of a second,
// so they need to be multiplied by 10 to get milliseconds and vice versa.
GetFrameDurations() []int
SetFrames(frames []image.Image)
SetWidthHeight(width, height int)
}
// ImageConfigProvider provides access to the image.Config of an image.
type ImageConfigProvider interface {
GetImageConfig() image.Config
}
// FrameDurationsToGifDelays converts frame durations in milliseconds to
// GIF delays in 100ths of a second.
func FrameDurationsToGifDelays(frameDurations []int) []int {
delays := make([]int, len(frameDurations))
for i, fd := range frameDurations {
delays[i] = fd / 10
if delays[i] == 0 && fd > 0 {
delays[i] = 1
}
}
return delays
}
// GifDelaysToFrameDurations converts GIF delays in 100ths of a second to
// frame durations in milliseconds.
func GifDelaysToFrameDurations(delays []int) []int {
frameDurations := make([]int, len(delays))
for i, d := range delays {
frameDurations[i] = d * 10
}
return frameDurations
}
@@ -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"
+17 -5
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"
@@ -73,11 +73,23 @@ func (m *Map[K, T]) Set(key K, value T) {
m.mu.Unlock()
}
// WithWriteLock executes the given function with a write lock on the map.
func (m *Map[K, T]) WithWriteLock(f func(m map[K]T)) {
// Delete deletes the given key from the map.
// It returns true if the key was found and deleted, false otherwise.
func (m *Map[K, T]) Delete(key K) bool {
m.mu.Lock()
defer m.mu.Unlock()
f(m.m)
if _, found := m.m[key]; found {
delete(m.m, key)
return true
}
return false
}
// WithWriteLock executes the given function with a write lock on the map.
func (m *Map[K, T]) WithWriteLock(f func(m map[K]T) error) error {
m.mu.Lock()
defer m.mu.Unlock()
return f(m.m)
}
// SetIfAbsent sets the given key to the given value if the key does not already exist in the map.
@@ -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"
@@ -62,8 +62,9 @@ func TestMap(t *testing.T) {
c.Assert(found, qt.Equals, true)
c.Assert(v, qt.Equals, 300)
m.WithWriteLock(func(m map[string]int) {
m.WithWriteLock(func(m map[string]int) error {
m["f"] = 500
return nil
})
v, found = m.Lookup("f")
c.Assert(found, qt.Equals, true)
+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
+24
View File
@@ -32,6 +32,30 @@ type ReadSeekCloser interface {
io.Closer
}
// Sizer provides the size of, typically, a io.Reader.
// As implemented by e.g. os.File and io.SectionReader.
type Sizer interface {
Size() int64
}
type SizeReader interface {
io.Reader
Sizer
}
// ToSizeReader converts the given io.Reader to a SizeReader.
// Note that if r is not a SizeReader, the entire content will be read into memory
func ToSizeReader(r io.Reader) (SizeReader, error) {
if sr, ok := r.(SizeReader); ok {
return sr, nil
}
b, err := io.ReadAll(r)
if err != nil {
return nil, err
}
return bytes.NewReader(b), nil
}
// CloserFunc is an adapter to allow the use of ordinary functions as io.Closers.
type CloserFunc func() error
+1 -2
View File
@@ -310,13 +310,12 @@ func GetDependencyList() []string {
// GetDependencyListNonGo returns a list of non-Go dependencies.
func GetDependencyListNonGo() []string {
var deps []string
deps := []string{formatDep("github.com/webmproject/libwebp", "v1.6.0")} // via WASM. TODO(bep) get versions from the plugin setup.
if IsExtended {
deps = append(
deps,
formatDep("github.com/sass/libsass", "3.6.6"),
formatDep("github.com/webmproject/libwebp", "v1.3.2"),
)
}
+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: 153,
PatchLevel: 0,
Suffix: "-DEV",
Minor: 155,
PatchLevel: 3,
Suffix: "",
}
+13
View File
@@ -20,6 +20,7 @@ import (
"io"
"os"
"strings"
"sync"
"time"
"github.com/bep/logg"
@@ -123,8 +124,11 @@ func New(opts Options) Logger {
)
l := logger.WithLevel(opts.Level)
logMu := &sync.Mutex{}
reset := func() {
logMu.Lock()
defer logMu.Unlock()
logCounters.mu.Lock()
defer logCounters.mu.Unlock()
logCounters.counters = make(map[logg.Level]int)
@@ -135,6 +139,7 @@ func New(opts Options) Logger {
}
return &logAdapter{
mu: logMu,
logCounters: logCounters,
errors: errorsw,
reset: reset,
@@ -173,6 +178,7 @@ func LevelLoggerToWriter(l logg.LevelLogger) io.Writer {
type Logger interface {
Debug() logg.LevelLogger
DebugCommand(command string) logg.LevelLogger
Debugf(format string, v ...any)
Debugln(v ...any)
Error() logg.LevelLogger
@@ -203,6 +209,7 @@ type Logger interface {
}
type logAdapter struct {
mu *sync.Mutex
logCounters *logLevelCounter
errors *strings.Builder
reset func()
@@ -221,6 +228,10 @@ func (l *logAdapter) Debug() logg.LevelLogger {
return l.debugl
}
func (l *logAdapter) DebugCommand(command string) logg.LevelLogger {
return l.debugl.WithField(FieldNameCmd, command)
}
func (l *logAdapter) Debugf(format string, v ...any) {
l.debugl.Logf(format, v...)
}
@@ -323,6 +334,8 @@ func (l *logAdapter) Errorln(v ...any) {
}
func (l *logAdapter) Errors() string {
l.mu.Lock()
defer l.mu.Unlock()
return l.errors.String()
}
+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) {
+30 -18
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:"-"`
@@ -471,10 +471,10 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
gc.RenderHookUseEmbeddedNever,
}
if !slices.Contains(renderHookUseEmbeddedModes, c.Markup.Goldmark.RenderHooks.Image.UseEmbedded) {
return fmt.Errorf("site config markup.goldmark.renderHooks.image must be one of %s", helpers.StringSliceToList(renderHookUseEmbeddedModes, "or"))
return fmt.Errorf("site config markup.goldmark.renderHooks.image.useEmbedded must be one of %s", helpers.StringSliceToList(renderHookUseEmbeddedModes, "or"))
}
if !slices.Contains(renderHookUseEmbeddedModes, c.Markup.Goldmark.RenderHooks.Link.UseEmbedded) {
return fmt.Errorf("site config markup.goldmark.renderHooks.link must be one of %s", helpers.StringSliceToList(renderHookUseEmbeddedModes, "or"))
return fmt.Errorf("site config markup.goldmark.renderHooks.link.useEmbedded must be one of %s", helpers.StringSliceToList(renderHookUseEmbeddedModes, "or"))
}
c.C = &ConfigCompiled{
@@ -607,8 +607,14 @@ 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 DisableDefaultSiteRedirect introduced in v0.154.5.
// It's not obvious how a more fine grained setup would work.
DisableDefaultLanguageRedirect 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
@@ -816,6 +822,7 @@ type Configs struct {
Modules modules.Modules
ModulesClient *modules.Client
FileCaches filecache.Caches
// All below is set in Init.
Languages langs.Languages
@@ -846,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 {
@@ -915,10 +922,9 @@ func (c *Configs) Init(logger loggers.Logger) error {
// avoid initializing the same config more than once.
for i, l := range c.Languages {
langConfig := c.LanguageConfigMap[l.Lang]
sitesMatrix := sitesmatrix.NewIntSetsBuilder(c.ConfiguredDimensions).WithLanguageIndices(i).WithAllIfNotSet().Build()
for _, s := range allDecoderSetups {
if getInitializer := s.getInitializer; getInitializer != nil {
if err := getInitializer(langConfig).InitConfig(logger, sitesMatrix, c.ConfiguredDimensions); err != nil {
if err := getInitializer(langConfig).InitConfig(logger, nil, c.ConfiguredDimensions); err != nil {
return err
}
}
@@ -1018,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
@@ -1045,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 {
@@ -1061,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)
}
@@ -1081,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:
@@ -1098,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.
@@ -1134,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))
@@ -1149,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
}
+11 -2
View File
@@ -17,11 +17,12 @@ 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"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/internal/warpc"
toml "github.com/pelletier/go-toml/v2"
"github.com/spf13/afero"
)
@@ -60,6 +61,14 @@ func GetTestDeps(fs afero.Fs, cfg config.Provider, beforeInit ...func(*deps.Deps
d := &deps.Deps{
Conf: conf,
Fs: hugofs.NewFrom(fs, conf.BaseConfig()),
WasmDispatchers: warpc.AllDispatchers(
warpc.Options{
PoolSize: 1,
},
warpc.Options{
PoolSize: 1,
},
),
}
for _, f := range beforeInit {
f(d)
@@ -75,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)
+25 -12
View File
@@ -1,3 +1,16 @@
// Copyright 2025 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 deps
import (
@@ -15,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"
@@ -160,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](),
}
}
@@ -247,12 +260,12 @@ 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()
resourceSpec, err := resources.NewSpec(d.PathSpec, common, fileCaches, d.MemCache, d.BuildState, d.Log, d, d.ExecHelper, d.BuildClosers, d.BuildState)
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 {
return fmt.Errorf("failed to create resource spec: %w", err)
}
@@ -451,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)
@@ -467,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](),
}
}
+6 -3
View File
@@ -23,7 +23,8 @@
"**/emojis.md",
"**/commands/*",
"**/showcase/*",
"**/tools/*"
"**/tools/*",
"data/docs.yaml"
],
"ignoreRegExpList": [
"# cspell: ignore fenced code blocks",
@@ -59,12 +60,14 @@
"marshal",
"marshaling",
"multihost",
"multiplatfom",
"multiplatform",
"performantly",
"preconfigured",
"prerendering",
"redirection",
"redirections",
"slugified",
"slugify",
"subexpression",
"suppressible",
"synchronisation",
@@ -113,7 +116,6 @@
"libros",
"mercredi",
"miesiąc",
"miesiąc",
"miesiąca",
"miesiące",
"miesięcy",
@@ -144,6 +146,7 @@
"Samsa",
"Stucki",
"Thénardier",
"Vitter",
"WASI",
"# ----------------------------------------------------------------------",
"# cspell: ignore operating systems and software packages",
+1 -1
View File
@@ -77,7 +77,7 @@ config:
full: true
inline: true
shortcut: true
url_inline: true
url_inline: false
MD055:
style: consistent
MD056: true
-3
View File
@@ -1,3 +0,0 @@
**/news/**
**/showcase/**
**/zh/**
+5
View File
@@ -129,3 +129,8 @@ body {
text-decoration: none;
padding-left: .0625em;
}
/* Code spans within paragraphs, tables cells, list items, etc. */
:not(pre) > code {
white-space: nowrap;
}
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

-6
View File
@@ -1,6 +0,0 @@
import { initColorScheme } from './alpinejs/stores/index';
(function () {
// This allows us to initialize the color scheme before AlpineJS etc. is loaded.
initColorScheme();
})();
+4
View File
@@ -1,6 +1,10 @@
import { scrollToActive } from 'js/helpers/index';
import { initColorScheme } from './alpinejs/stores/index';
(function () {
// This allows us to initialize the color scheme before AlpineJS etc. is loaded.
initColorScheme();
// Now we know that the browser has JS enabled.
document.documentElement.classList.remove('no-js');
-8
View File
@@ -8,14 +8,6 @@ import focus from '@alpinejs/focus';
var debug = 0 ? console.log.bind(console, '[index]') : function () {};
// Turbolinks init.
(function () {
document.addEventListener('turbo:render', function (e) {
// This is also called right after the body start. This is added to prevent flicker on navigation.
initColorScheme();
});
})();
// Set up and start Alpine.
(function () {
// Register AlpineJS plugins.
@@ -29,6 +29,7 @@ Please refer to the relevant documentation for installation instructions:
[cloudcannon]: https://cloudcannon.com/
[cloudflare pages]: https://pages.cloudflare.com/
[commit information]: /methods/page/GitInfo
[dart sass install]: /functions/css/sass/#dart-sass
[dart sass]: https://sass-lang.com/dart-sass
[git install]: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
@@ -37,4 +38,5 @@ Please refer to the relevant documentation for installation instructions:
[gitlab pages]: https://docs.gitlab.com/ee/user/project/pages/
[go install]: https://go.dev/doc/install
[go]: https://go.dev/
[hugo modules]: /hugo-modules/
[netlify]: https://www.netlify.com/
@@ -15,3 +15,5 @@ Prebuilt binaries are available for a variety of operating systems and architect
Please consult your operating system documentation if you need help setting file permissions or modifying your PATH environment variable.
If you do not see a prebuilt binary for the desired edition, operating system, and architecture, install Hugo using one of the methods described below.
[latest release]: https://github.com/gohugoio/hugo/releases/latest
@@ -7,7 +7,7 @@ _comment: Do not remove front matter.
To build the extended or extended/deploy edition from source you must:
1. Install [Git]
1. Install [Go] version 1.23.0 or later
1. Install [Go] version 1.24.0 or later
1. Install a C compiler, either [GCC] or [Clang]
1. Update your `PATH` environment variable as described in the [Go documentation]
@@ -4,7 +4,7 @@ _comment: Do not remove front matter.
### Homebrew
[Homebrew] is a free and open-source package manager for macOS and Linux. To install the extended edition of Hugo:
[Homebrew] is a free and open-source package manager for macOS and Linux. To install the extended/deploy edition of Hugo:
```sh
brew install hugo
@@ -27,12 +27,14 @@ _comment: Do not remove front matter.
: The content's section.
`:sectionslug`
: {{< new-in 0.149.0 />}}
: The content's section using slugified section name. The slugified section name is the `slug` as defined in front matter, else the `title` as defined in front matter, else the automatic title.
`:sections`
: The content's sections hierarchy. You can use a selection of the sections using _slice syntax_: `:sections[1:]` includes all but the first, `:sections[:last]` includes all but the last, `:sections[last]` includes only the last, `:sections[1:2]` includes section 2 and 3. Note that this slice access will not throw any out-of-bounds errors, so you don't have to be exact.
`:sectionslugs`
: {{< new-in 0.149.0 />}}
: The content's sections hierarchy using slugified section names. The slugified section name is the `slug` as defined in front matter, else the `title` as defined in front matter, else the automatic title. You can use a selection of the sections using _slice syntax_: `:sectionslugs[1:]` includes all but the first, `:sectionslugs[:last]` includes all but the last, `:sectionslugs[last]` includes only the last, `:sectionslugs[1:2]` includes section 2 and 3. Note that this slice access will not throw any out-of-bounds errors, so you don't have to be exact.
`:title`
@@ -43,5 +43,5 @@ As a practical example, Hugo's embedded link and image render hooks use the `Pag
[`RenderShortcodes`]: /methods/page/rendershortcodes/
[Markdown notation]: /content-management/shortcodes/#notation
[Embedded link render hook]: {{% eturl render-link %}}
[Embedded image render hook]: {{% eturl render-image %}}
[Embedded link render hook]: <{{% eturl render-link %}}>
[Embedded image render hook]: <{{% eturl render-image %}}>
+1 -1
View File
@@ -28,7 +28,7 @@ Learn more about Hugo's [features], [privacy protections], and [security model].
[Hugo Modules]: /hugo-modules/
[static site generator]: https://en.wikipedia.org/wiki/Static_site_generator
[features]: /about/features/
[security model]: about/security/
[security model]: /about/security/
[privacy protections]: /configuration/privacy
{{< youtube 0RKpf3rK57I >}}
@@ -24,6 +24,8 @@ hugo gen chromastyles [flags] [args]
--highlightStyle string foreground and background colors for highlighted lines, e.g. --highlightStyle "#fff000 bg:#000fff"
--lineNumbersInlineStyle string foreground and background colors for inline line numbers, e.g. --lineNumbersInlineStyle "#fff000 bg:#000fff"
--lineNumbersTableStyle string foreground and background colors for table line numbers, e.g. --lineNumbersTableStyle "#fff000 bg:#000fff"
--omitClassComments omit CSS class comment prefixes in the generated CSS
--omitEmpty omit empty CSS rules (deprecated, no longer needed)
--style string highlighter style (see https://xyproto.github.io/splash/docs/) (default "friendly")
```
+2 -2
View File
@@ -45,6 +45,6 @@ Ensure you run this within the root directory of your site.
* [hugo](/commands/hugo/) - Build your site
* [hugo new content](/commands/hugo_new_content/) - Create new content
* [hugo new site](/commands/hugo_new_site/) - Create a new site (skeleton)
* [hugo new theme](/commands/hugo_new_theme/) - Create a new theme (skeleton)
* [hugo new site](/commands/hugo_new_site/) - Create a new site
* [hugo new theme](/commands/hugo_new_theme/) - Create a new theme
+2 -4
View File
@@ -5,13 +5,11 @@ url: /commands/hugo_new_site/
---
## hugo new site
Create a new site (skeleton)
Create a new site
### Synopsis
Create a new site in the provided directory.
The new site will have the correct structure, but no content or theme yet.
Use `hugo new [contentPath]` to create new content.
Create a new site at the specified path.
```
hugo new site [path] [flags]
+5 -6
View File
@@ -5,14 +5,12 @@ url: /commands/hugo_new_theme/
---
## hugo new theme
Create a new theme (skeleton)
Create a new theme
### Synopsis
Create a new theme (skeleton) called [name] in ./themes.
New theme is a skeleton. Please add content to the touched files. Add your
name to the copyright line in the license and adjust the theme.toml file
according to your needs.
Create a new theme with the specified name in the ./themes directory.
This generates a functional theme including template examples and sample content.
```
hugo new theme [name] [flags]
@@ -21,7 +19,8 @@ hugo new theme [name] [flags]
### Options
```
-h, --help help for theme
--format string preferred file format (toml, yaml or json) (default "toml")
-h, --help help for theme
```
### Options inherited from parent commands
+5 -5
View File
@@ -47,7 +47,7 @@ cascade
: See [configure cascade](/configuration/cascade/).
cleanDestinationDir
: (`bool`) Whether to remove files from the site's destination directory that do not have corresponding files in the `static` directory during the build. Default is `false`.
: (`bool`) Whether to remove files from the [`publishDir`](#publishdir) that do not exist in the [`staticDir`](#staticdir) when building the site. This setting will not take effect if the `staticDir` does not exist. Note that `.gitignore` and `.gitattributes` files, along with directories named `.git`, are always preserved in the `publishDir`. Default is `false`.
contentDir
: (`string`) The designated directory for content files. Default is `content`. {{% module-mounts-note %}}
@@ -262,7 +262,7 @@ staticDir
: (`string`) The designated directory for static files. Default is `static`. {{% module-mounts-note %}}
summaryLength
: (`int`) Applicable to [automatic summaries], the minimum number of words returned by the [`Summary`] method on a `Page` object. The `Summary` method will return content truncated at the paragraph boundary closest to the specified `summaryLength`, but at least this minimum number of words.
: (`int`) Applicable to [automatic summaries], the minimum number of words returned by the [`Summary`] method on a `Page` object. The `Summary` method will return content truncated at the paragraph boundary closest to the specified `summaryLength`, but at least this minimum number of words. Default is `70`.
taxonomies
: See [configure taxonomies](/configuration/taxonomies/).
@@ -350,9 +350,9 @@ Some configuration settings, such as menus and custom parameters, can be defined
[Chicago Manual of Style]: https://www.chicagomanualofstyle.org/home.html
[default front matter configuration]: /configuration/front-matter/
[duration]: https://pkg.go.dev/time#Duration
[embedded alias template]: {{% eturl alias %}}
[embedded Open Graph template]: {{% eturl opengraph %}}
[embedded RSS template]: {{% eturl rss %}}
[embedded alias template]: <{{% eturl alias %}}>
[embedded Open Graph template]: <{{% eturl opengraph %}}>
[embedded RSS template]: <{{% eturl rss %}}>
[IANA Time Zone Database]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
[module mounts]: /configuration/module/#mounts
[os.UserCacheDir]: https://pkg.go.dev/os#UserCacheDir
+2 -3
View File
@@ -14,7 +14,6 @@ You can configure your site to cascade front matter values to the home page and
For example, to cascade a "color" parameter to the home page and all its descendants:
{{< code-toggle file=hugo >}}
title = 'Home'
[cascade.params]
color = 'red'
{{< /code-toggle >}}
@@ -61,14 +60,14 @@ Define an array of cascade parameters to apply different values to different tar
[cascade.params]
color = 'red'
[cascade.target]
path = '{/books/**}'
path = '/books/**'
kind = 'page'
lang = '{en,de}'
[[cascade]]
[cascade.params]
color = 'blue'
[cascade.target]
path = '{/films/**}'
path = '/films/**'
kind = 'page'
environment = 'production'
{{< /code-toggle >}}
@@ -90,10 +90,11 @@ Consider this site configuration:
{{< code-toggle file=hugo >}}
[frontmatter]
date = [':filename', ':default']
publishDate = [':filename', ':default']
lastmod = ['lastmod', ':fileModTime']
{{< /code-toggle >}}
To determine `date`, Hugo tries to extract the date from the file name, falling back to the default ordered sequence of date fields.
To determine `date` and `publishDate`, Hugo tries to extract the value from the file name, falling back to the default ordered sequence of date fields.
To determine `lastmod`, Hugo looks for a `lastmod` field in front matter, falling back to the file's last modification timestamp.
+34 -12
View File
@@ -44,20 +44,42 @@ The HTTP cache involves two key aspects: determining which content to cache (the
The HTTP cache behavior is defined for a configured set of resources. Stale resources will be refreshed from the file cache, even if their configured Time-To-Live (TTL) has not expired. If HTTP caching is disabled for a resource, Hugo will bypass the cache and access the file directly.
The default configuration disables everything:
This is the default configuration for HTTP caching:
{{< code-toggle file=hugo >}}
[HTTPCache.cache.for]
excludes = ['**']
includes = []
{{< /code-toggle >}}
{{< code-toggle config=HTTPCache />}}
respectCacheControlNoStoreInRequest
: {{< new-in 0.151.0 />}}
: (`bool`) Whether to respect the `no-store` directive in the server's `Cache-Control` request header when fetching remote resources via the [`resources.GetRemote`][] function. Default is `true`.
respectCacheControlNoStoreInResponse
: {{< new-in 0.151.0 />}}
: (`bool`) Whether to respect the `no-store` directive in the server's `Cache-Control` response header when fetching remote resources via the [`resources.GetRemote`][] function. Default is `false`.
cache.for.excludes
: (`string`) A list of [glob](g) patterns to exclude from caching.
: (`string`) A list of [glob](g) patterns to exclude from caching. In its default configuration HTTP caching excludes all files.
cache.for.includes
: (`string`) A list of [glob](g) patterns to cache.
polls
: A slice of polling configurations.
polls.disable
: (`bool`) Whether to disable polling for this configuration. Default is `true`.
polls.high
: (`string`) The maximum polling interval expressed as a [duration](g). This is used when the resource is considered stable. Default is `0s`.
polls.low
: (`string`) The minimum polling interval expressed as a [duration](g). This is used after a recent change and gradually increases towards `polls.high`. Default is `0s`.
polls.for.excludes
: (`string`) A list of [glob](g) patterns to exclude from polling for this configuration.
polls.for.includes
: (`string`) A list of [glob](g) patterns to include in polling for this configuration.
## HTTP polling
Polling is used in watch mode (e.g., `hugo server`) to detect changes in remote resources. Polling can be enabled even if HTTP caching is disabled. Detected changes trigger a rebuild of pages using the affected resource. Polling can be disabled for specific resources, typically those known to be static.
@@ -78,13 +100,13 @@ polls
: A slice of polling configurations.
polls.disable
: (`bool`) Whether to disable polling for this configuration.
polls.low
: (`string`) The minimum polling interval expressed as a [duration](g). This is used after a recent change and gradually increases towards `polls.high`.
: (`bool`) Whether to disable polling for this configuration. Default is `true`.
polls.high
: (`string`) The maximum polling interval expressed as a [duration](g). This is used when the resource is considered stable.
: (`string`) The maximum polling interval expressed as a [duration](g). This is used when the resource is considered stable. Default is `0s`.
polls.low
: (`string`) The minimum polling interval expressed as a [duration](g). This is used after a recent change and gradually increases towards `polls.high`. Default is `0s`.
polls.for.excludes
: (`string`) A list of [glob](g) patterns to exclude from polling for this configuration.
+3 -3
View File
@@ -185,9 +185,9 @@ public
[`Language.LanguageName`]: /methods/site/language/#languagename
[`Language.Weight`]: /methods/site/language/#weight
[`Title`]: /methods/site/title/
[embedded alias template]: {{% eturl alias %}}
[embedded OpenGraph template]: {{% eturl opengraph %}}
[embedded RSS template]: {{% eturl rss %}}
[embedded alias template]: <{{% eturl alias %}}>
[embedded OpenGraph template]: <{{% eturl opengraph %}}>
[embedded RSS template]: <{{% eturl rss %}}>
[RFC 5646]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.1
[RFC 5646 § 2.2.7]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.2.7
[translating by file name]: /content-management/multilingual/#translation-by-file-name
+17 -3
View File
@@ -94,9 +94,23 @@ enable = true
With this configuration, to format text as deleted, wrap it with double-tildes.
#### Passthrough
#### Footnote
{{< new-in 0.122.0 />}}
Enabled by default, the Footnote extension enables inclusion of footnotes in Markdown.
enable
: {{< new-in 0.151.0 />}}
: (`bool`) Whether to enable the Footnotes extension. Default is `true`.
backlinkHTML
: {{< new-in 0.151.0 />}}
: (`string`) The HTML to be displayed at the end of a footnote that links the user back to the corresponding reference in the main text. The default is &#x21a9;&#xfe0e; (a return arrow symbol).
enableAutoIDPrefix
: {{< new-in 0.151.0 />}}
: (`bool`) Whether to prepend a unique prefix to footnote IDs, preventing clashes when multiple documents are rendered together. This prefix is unique to each logical path, which means that the prefix is not unique across content dimensions such as language. Default is `false`.
#### Passthrough
Enable the Passthrough extension to include mathematical equations and expressions in Markdown using LaTeX markup. See [mathematics in Markdown] for details.
@@ -202,7 +216,7 @@ backend
: (`string`) The backend output file format. Default is `html5`.
extensions
: (`string array`) An array of enabled extensions, one or more of `asciidoctor-html5s`, `asciidoctor-bibtex`, `asciidoctor-diagram`, `asciidoctor-interdoc-reftext`, `asciidoctor-katex`, `asciidoctor-latex`, `asciidoctor-mathematical`, or `asciidoctor-question`.
: (`string array`) An array of enabled extensions, such as `asciidoctor-html5s`, `asciidoctor-bibtex`, or `asciidoctor-diagram`.
> [!note]
> To mitigate security risks, entries in the extension array may not contain forward slashes (`/`), backslashes (`\`), or periods. Due to this restriction, extensions must be in Ruby's `$LOAD_PATH`.
+6 -1
View File
@@ -10,6 +10,11 @@ This is the default configuration:
{{< code-toggle config=minify />}}
See the [tdewolff/minify] project page for details.
See the [tdewolff/minify] project page for details, but note the following:
- `css.inline` is for internal use. Changing this setting has no effect.
- `css.keepCSS2` has been deprecated. Use `css.version` instead.
- `html.keepConditionalComments` has been deprecated. Use `html.keepSpecialComments` instead.
- `svg.inline` is for internal use. Changing this setting has no effect.
[tdewolff/minify]: https://github.com/tdewolff/minify
+25 -21
View File
@@ -7,6 +7,8 @@ keywords: []
aliases: [/hugo-modules/configuration/]
---
{{% include "/_common/gomodules-info.md" %}}
## Top-level options
This is the default configuration:
@@ -40,7 +42,7 @@ proxy
: (`string`) The proxy server to use to download remote modules. Default is `direct`, which means `git clone` and similar.
replacements
: (`string`) Primarily useful for local module development, a comma-separated list of mappings from module paths to directories. Paths may be absolute or relative to the [`themesDir`].
: (`string`) Primarily useful for local module development, a comma-separated list of mappings from module paths to directories. Paths may be absolute or relative to the [`themesDir`][].
{{< code-toggle file=hugo >}}
[module]
@@ -61,8 +63,6 @@ export HUGO_MODULE_REPLACEMENTS="github.com/bep/my-theme -> ../.."
export HUGO_MODULE_WORKSPACE="/my/hugo.work"
```
{{% include "/_common/gomodules-info.md" %}}
## Hugo version
You can specify a required Hugo version for your module in the `module` section. Users will then receive a warning if their Hugo version is incompatible.
@@ -77,7 +77,7 @@ extended
: (`bool`) Whether the extended edition of Hugo is required, satisfied by installing either the extended or extended/deploy edition.
max
: (`string`) The maximum Hugo version supported, for example `0.148.0`.
: (`string`) The maximum Hugo version supported, for example `0.152.2`.
min
: (`string`) The minimum Hugo version supported, for example `0.102.0`.
@@ -110,32 +110,36 @@ noVendor
: (`bool`) Whether to disable vendoring for this import. This setting is restricted to the main project. Default is `false`.
path
: (`string`) The module path, either a valid Go module path (e.g., `github.com/gohugoio/myShortcodes`) or the directory name if stored in the [`themesDir`].
: (`string`) The module path, either a valid Go module path (e.g., `github.com/gohugoio/myShortcodes`) or the directory name if stored in the [`themesDir`][].
{{% include "/_common/gomodules-info.md" %}}
version
: {{< new-in 0.150.0 />}}
: If set to a [version query](https://go.dev/ref/mod#version-queries), this import becomes a direct dependency, in contrast to dependencies managed by Go Modules. See [this issue](https://github.com/gohugoio/hugo/pull/13966) for more information.
## Mounts
Before Hugo v0.56.0, custom component paths could only be configured by setting [`archetypeDir`], [`assetDir`], [`contentDir`], [`dataDir`], [`i18nDir`], [`layoutDi`], or [`staticDir`] in the site configuration. Module mounts offer greater flexibility than these legacy settings, but
you cannot use both.
{{% glossary-term mount %}}
[`archetypeDir`]: /configuration/all/
[`assetDir`]: /configuration/all/
[`contentDir`]: /configuration/all/
[`dataDir`]: /configuration/all/
[`i18nDir`]: /configuration/all/
[`layoutDi`]: /configuration/all/
[`staticDir`]: /configuration/all/
> [!important]
> If you define one or more mounts to map a file system path to a component path, do not use these legacy configuration settings: [`archetypeDir`][], [`assetDir`][], [`contentDir`][], [`dataDir`][], [`i18nDir`][], [`layoutDir`][], or [`staticDir`][].
> [!note]
> If you use module mounts do not use the legacy settings.
[`archetypeDir`]: /configuration/all/#archetypedir
[`assetDir`]: /configuration/all/#assetdir
[`contentDir`]: /configuration/all/#contentdir
[`dataDir`]: /configuration/all/#datadir
[`i18nDir`]: /configuration/all/#i18ndir
[`layoutDir`]: /configuration/all/#layoutdir
[`staticDir`]: /configuration/all/#staticdir
### Default mounts
> [!note]
> Adding a new mount to a target root will cause the existing default mount for that root to be ignored. If you still need the default mount, you must explicitly add it along with the new mount.
Within a project, if you define a mount to map a file system path to a component path, the corresponding default mount for that component will be removed. This action essentially overwrites the standard, automatic mapping for that specific component with your custom one.
The are the default mounts:
Within a module, if you define a mount to map a file system path to a component path, all of the default mounts will be removed. Defining a mount at the module level is a more sweeping change, causing all default mappings within that module to be discarded.
In either case, if you still need one of the default mounts, you must explicitly add it along with the new mount. Because custom mounts override defaults, any necessary default mappings must be re-added manually after you introduce your custom configuration.
These are the default mounts:
{{< code-toggle config=module.mounts />}}
@@ -143,7 +147,7 @@ source
: (`string`) The source directory of the mount. For the main project, this can be either project-relative or absolute. For other modules it must be project-relative.
target
: (`string`) Where the mount will reside within Hugo's virtual file system. It must begin with one of Hugo's component directories: `archetypes`, `assets`, `content`, `data`, `i18n`, `layouts`, or `static`. For example, `content/blog`.
: (`string`) Where the mount will reside within Hugo's [unified file system](g). It must begin with one of Hugo's [component](g) directories: archetypes, assets, content, data, i18n, layouts, or static. For example, content/blog.
disableWatch
: {{< new-in 0.128.0 />}}
@@ -44,7 +44,7 @@ archetypes/
Hugo looks for archetypes in the `archetypes` directory in the root of your project, falling back to the `archetypes` directory in themes or installed modules. An archetype for a specific content type takes precedence over the default archetype.
For example, with this command:
For example, if you have enabled a theme named `my-theme` and you run this command:
```sh
hugo new content posts/my-first-post.md
@@ -53,8 +53,8 @@ hugo new content posts/my-first-post.md
The archetype lookup order is:
1. `archetypes/posts.md`
1. `archetypes/default.md`
1. `themes/my-theme/archetypes/posts.md`
1. `archetypes/default.md`
1. `themes/my-theme/archetypes/default.md`
If none of these exists, Hugo uses a built-in default archetype.
@@ -256,5 +256,5 @@ Created from <https://arthursonzogni.com/Diagon/#Tree>
```
[code block render hook]: /render-hooks/code-blocks/
[embedded code block render hook]: {{% eturl render-codeblock-goat %}}
[embedded code block render hook]: <{{% eturl render-codeblock-goat %}}>
[GoAT]: https://github.com/bep/goat
@@ -47,7 +47,7 @@ build
: (`map`) A map of [build options].
cascade
: (`map`) A map of front matter keys whose values are passed down to the page's descendants unless overwritten by self or a closer ancestor's cascade. See the [cascade] section for details.
: (`map`) A map (or a slice of maps) of front matter keys whose values are passed down to the page's descendants unless overwritten by self or a closer ancestor's cascade. See the [cascade] section for details.
date
: (`string`) The date associated with the page, typically the creation date. Note that the TOML format also supports unquoted date/time values. See the [dates](#dates) section for examples. Access this value from a template using the [`Date`] method on a `Page` object.
@@ -322,18 +322,18 @@ To override the default time zone, set the [`timeZone`](/configuration/all/#time
[`lastmod`]: /methods/page/date/
[`layout`]: /methods/page/layout/
[`linktitle`]: /methods/page/linktitle/
[`opengraph.html`]: {{% eturl opengraph %}}
[`opengraph.html`]: <{{% eturl opengraph %}}>
[`Param`]: /methods/page/param/
[`Params`]: /methods/page/params/
[`publishdate`]: /methods/page/publishdate/
[`readingtime`]: /methods/page/readingtime/
[`schema.html`]: {{% eturl schema %}}
[`schema.html`]: <{{% eturl schema %}}>
[`sitemap`]: /methods/page/sitemap/
[`slug`]: /methods/page/slug/
[`Summary`]: /methods/page/summary/
[`title`]: /methods/page/title/
[`translationkey`]: /methods/page/translationkey/
[`twitter_cards.html`]: {{% eturl twitter_cards %}}
[`twitter_cards.html`]: <{{% eturl twitter_cards %}}>
[`type`]: /methods/page/type/
[`weight`]: /methods/page/weight/
[`wordcount`]: /methods/page/wordcount/
@@ -105,8 +105,6 @@ The `image` resource implements the [`Process`], [`Resize`], [`Fit`], [`Fill`]
### Process
{{< new-in 0.119.0 />}}
> [!note]
> The `Process` method is also available as a filter, which is more effective if you need to apply multiple filters to an image. See [Process filter](/functions/images/process).
@@ -6,13 +6,11 @@ categories: []
keywords: []
---
{{< new-in 0.122.0 />}}
## Overview
Mathematical equations and expressions written in [LaTeX] are common in academic and scientific publications. Your browser typically renders this mathematical markup using an open-source JavaScript display engine such as [MathJax] or [KaTeX].
Mathematical equations and expressions written in [LaTeX][] are common in academic and scientific publications. Your browser typically renders this mathematical markup using an open-source JavaScript display engine such as [MathJax][] or [KaTeX][].
For example, with this LaTeX markup:
For example, this LaTeX markup:
```text
\[
@@ -23,7 +21,7 @@ JS(\hat{y} || y) &= \frac{1}{2}(KL(y||\frac{y+\hat{y}}{2}) + KL(\hat{y}||\frac{y
\]
```
The MathJax display engine renders this:
Is rendered to:
\[
\begin{aligned}
@@ -37,7 +35,7 @@ Equations and expressions can be displayed inline with other text, or as standal
Whether an equation or expression appears inline, or as a block, depends on the delimiters that surround the mathematical markup. Delimiters are defined in pairs, where each pair consists of an opening and closing delimiter. The opening and closing delimiters may be the same, or different.
> [!note]
> You can configure Hugo to render mathematical markup on the client side using the MathJax or KaTeX display engine, or you can render the markup with the [`transform.ToMath`] function while building your site.
> You can configure Hugo to render mathematical markup on the client side using the MathJax or KaTeX display engine, or you can render the markup with the [`transform.ToMath`][] function while building your site.
>
> The first approach is described below.
@@ -46,7 +44,7 @@ Whether an equation or expression appears inline, or as a block, depends on the
Follow these instructions to include mathematical equations and expressions in your Markdown using LaTeX markup.
Step 1
: Enable and configure the Goldmark [passthrough extension] in your site configuration. The passthrough extension preserves raw Markdown within delimited snippets of text, including the delimiters themselves.
: Enable and configure the Goldmark [passthrough extension][] in your site configuration. The passthrough extension preserves raw Markdown within delimited snippets of text, including the delimiters themselves.
{{< code-toggle file=hugo copy=true >}}
[markup.goldmark.extensions.passthrough]
@@ -60,12 +58,12 @@ Step 1
math = true
{{< /code-toggle >}}
The configuration above enables mathematical rendering on every page unless you set the `math` parameter to `false` in front matter. To enable mathematical rendering as needed, set the `math` parameter to `false` in your site configuration, and set the `math` parameter to `true` in front matter. Use this parameter in your base template as shown in [Step 3](#step-3).
The configuration above enables mathematical rendering on every page unless you set the `math` parameter to `false` in front matter. To enable mathematical rendering as needed, set the `math` parameter to `false` in your site configuration, and set the `math` parameter to `true` in front matter. Use this parameter in your base template as shown in [Step 3][].
> [!note]
> The configuration above precludes the use of the `$...$` delimiter pair for inline equations. Although you can add this delimiter pair to the configuration and JavaScript, you must double-escape the `$` symbol when used outside of math contexts to avoid unintended formatting.
>
> See the [inline delimiters](#inline-delimiters) section for details.
> See the [inline delimiters][] section for details.
To disable passthrough of inline snippets, omit the `inline` key from the configuration:
@@ -74,7 +72,7 @@ Step 1
block = [['\[', '\]'], ['$$', '$$']]
{{< /code-toggle >}}
You can define your own opening and closing delimiters, provided they match the delimiters that you set in [Step 2].
You can define your own opening and closing delimiters, provided they match the delimiters that you set in [Step 2][].
{{< code-toggle file=hugo >}}
[markup.goldmark.extensions.passthrough.delimiters]
@@ -83,10 +81,11 @@ Step 1
{{< /code-toggle >}}
Step 2
: Create a _partial_ template to load MathJax or KaTeX. The example below loads MathJax, or you can use KaTeX as described in the [engines](#engines) section.
: Create a _partial_ template to load MathJax or KaTeX. The example below loads MathJax, or you can use KaTeX as described in the [engines][] section.
```go-html-template {file="layouts/_partials/math.html" copy=true}
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@4/tex-mml-chtml.js"></script>
<script>
MathJax = {
tex: {
@@ -165,35 +164,35 @@ I will give you \\$2 if you can solve $y = x^2$.
```
> [!note]
> If you use the `$...$` delimiter pair for inline equations, and occasionally use the&nbsp;`$`&nbsp;symbol outside of math contexts, you must use MathJax instead of KaTeX to avoid unintended formatting caused by [this KaTeX limitation](https://github.com/KaTeX/KaTeX/issues/437).
> If you use the `$...$` delimiter pair for inline equations, and occasionally use the&nbsp;`$`&nbsp;symbol outside of math contexts, you must use MathJax instead of KaTeX to avoid unintended formatting caused by [this KaTeX limitation][].
## Engines
MathJax and KaTeX are open-source JavaScript display engines. Both engines are fast, but at the time of this writing MathJax v3.2.2 is slightly faster than KaTeX v0.16.11.
MathJax and KaTeX are open-source JavaScript display engines.
> [!note]
> If you use the `$...$` delimiter pair for inline equations, and occasionally use the&nbsp;`$`&nbsp;symbol outside of math contexts, you must use MathJax instead of KaTeX to avoid unintended formatting caused by [this KaTeX limitation](https://github.com/KaTeX/KaTeX/issues/437).
> If you use the `$...$` delimiter pair for inline equations, and occasionally use the&nbsp;`$`&nbsp;symbol outside of math contexts, you must use MathJax instead of KaTeX to avoid unintended formatting caused by [this KaTeX limitation][].
>
>See the [inline delimiters](#inline-delimiters) section for details.
>See the [inline delimiters][] section for details.
To use KaTeX instead of MathJax, replace the _partial_ template from [Step 2] with this:
To use KaTeX instead of MathJax, replace the _partial_ template from [Step 2][] with this:
```go-html-template {file="layouts/_partials/math.html" copy=true}
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.css"
integrity="sha384-zh0CIslj+VczCZtlzBcjt5ppRcsAmDnRem7ESsYwWwg3m/OaJ2l4x7YBZl9Kxxib"
href="https://cdn.jsdelivr.net/npm/katex@0.16.25/dist/katex.min.css"
integrity="sha384-WcoG4HRXMzYzfCgiyfrySxx90XSl2rxY5mnVY5TwtWE6KLrArNKn0T/mOgNL0Mmi"
crossorigin="anonymous"
>
<script
defer
src="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.js"
integrity="sha384-Rma6DA2IPUwhNxmrB/7S3Tno0YY7sFu9WSYMCuulLhIqYSGZ2gKCJWIqhBWqMQfh"
src="https://cdn.jsdelivr.net/npm/katex@0.16.25/dist/katex.min.js"
integrity="sha384-J+9dG2KMoiR9hqcFao0IBLwxt6zpcyN68IgwzsCSkbreXUjmNVRhPFTssqdSGjwQ"
crossorigin="anonymous">
</script>
<script
defer
src="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/contrib/auto-render.min.js"
src="https://cdn.jsdelivr.net/npm/katex@0.16.25/dist/contrib/auto-render.min.js"
integrity="sha384-hCXGrW6PitJEwbkoStFjeJxv+fSOOQKOPbJxSfM6G5sWZjAyWhXiTIIAmQqnlLlh"
crossorigin="anonymous"
onload="renderMathInElement(document.body);">
@@ -224,10 +223,15 @@ $$C_p[\ce{H2O(l)}] = \pu{75.3 J // mol K}$$
$$C_p[\ce{H2O(l)}] = \pu{75.3 J // mol K}$$
As shown in [Step 2](#step-2) above, MathJax supports chemical equations without additional configuration. To add chemistry support to KaTeX, enable the mhchem extension as described in the KaTeX [documentation](https://katex.org/docs/libs).
As shown in [Step 2][] above, MathJax supports chemical equations without additional configuration. To add chemistry support to KaTeX, enable the mhchem extension as described in the KaTeX [documentation](https://katex.org/docs/libs).
[`transform.ToMath`]: /functions/transform/tomath/
[engines]: #engines
[inline delimiters]: #inline-delimiters
[KaTeX]: https://katex.org/
[LaTeX]: https://www.latex-project.org/
[MathJax]: https://www.mathjax.org/
[passthrough extension]: /configuration/markup/#passthrough
[Step 2]: #step-2
[Step 3]: #step-3
[this KaTeX limitation]: https://github.com/KaTeX/KaTeX/issues/437
@@ -9,7 +9,7 @@ aliases: [/content/sections/]
## Page bundles
Hugo `0.32` announced page-relative images and other resources packaged into `Page Bundles`.
Hugo supports page-relative images and other resources packaged into `Page Bundles`.
These terms are connected, and you also need to read about [Page Resources](/content-management/page-resources) and [Image Processing](/content-management/image-processing) to get the full picture.
@@ -33,6 +33,35 @@ This is the first paragraph.
This is the second paragraph.
```
> [!NOTE]
> Place the summary divider on its own line. Do not place it inline with other content.
Correct placement:
```text {file="content/example.md"}
---
title: 'Example'
---
This is an example of **strong text** in a sentence. This is another sentence.
<!--more-->
This is another paragraph.
```
Incorrect placement:
```text {file="content/example.md"}
---
title: 'Example'
---
This is an example of **strong text** <!--more--> in a sentence. This is another sentence.
This is another paragraph.
```
When using the Emacs Org Mode [content format], use a `# more` divider to indicate the end of the summary.
[content format]: /content-management/formats/
@@ -79,6 +108,9 @@ For example, with a `summaryLength` of 7, the automatic summary will be:
<p>This is the second paragraph.</p>
```
> [!warning]
> Automatic `.Summary` may cut block tags (e.g., `blockquote`) in the middle when `summaryLength` is reached, causing the browser to recover the end tag (the end tag will be inserted before the parent's end tag), resulting in unexpected rendering behavior. To avoid this, wrap `.Summary` in a `<div>`; alternatively, wrap it together with the heading tag using `<section>`. You can avoid this entirely by using a manual summary. See issue [#14044] for details.
## Comparison
Each summary type has different characteristics:
@@ -121,3 +153,5 @@ Instead of calling the `Summary` method on a `Page` object, use the [`strings.Tr
</div>
{{ end }}
```
[#14044]: https://github.com/gohugoio/hugo/issues/14044
@@ -90,23 +90,20 @@ tags = ['Tag A','Tag B']
categories = ['Category A','Category B']
{{< /code-toggle >}}
## Order taxonomies
## Taxonomic weight
A content file can assign weight for each of its associate taxonomies. Taxonomic weight can be used for sorting or ordering content in taxonomy templates and is declared in a content file's front matter. The convention for declaring taxonomic weight is `taxonomyname_weight`.
{{% glossary-term "taxonomic weight" %}}
The following show a piece of content that has a weight of 22, which can be used for ordering purposes when rendering the pages assigned to the "a", "b" and "c" values of the `tags` taxonomy. It has also been assigned the weight of 44 when rendering the "d" category page.
Assign a taxonomic weight using a front matter key named `[taxonomy_name]_weight`.
### Example: taxonomic `weight`
{{< code-toggle file=hugo >}}
title = "foo"
tags = [ "a", "b", "c" ]
tags_weight = 22
categories = ["d"]
categories_weight = 44
{{< code-toggle file="content/courses/organic-chemistry.md" fm=true >}}
title = 'Organic Chemistry'
weight = 10
tags_weight = 1000
tags = ['chemistry','science']
{{</ code-toggle >}}
By using taxonomic weight, the same piece of content can appear in different positions in different taxonomies.
With the front matter above, the "Organic Chemistry" page will float towards the top of the list on section and home pages, and it will sink towards the bottom of the list on the "chemistry" and "science" term pages.
## Metadata

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