Compare commits

...

658 Commits

Author SHA1 Message Date
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
dependabot[bot] 364d237b51 build(deps): bump golang.org/x/image from 0.33.0 to 0.34.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.33.0 to 0.34.0.
- [Commits](https://github.com/golang/image/compare/v0.33.0...v0.34.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-10 12:12:56 +01:00
dependabot[bot] 079f3eb8d1 build(deps): bump golang.org/x/tools from 0.39.0 to 0.40.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.39.0 to 0.40.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.39.0...v0.40.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-09 10:33:42 +01:00
dependabot[bot] 4e6f2b6791 build(deps): bump github.com/tdewolff/minify/v2 from 2.24.7 to 2.24.8
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.24.7 to 2.24.8.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.24.7...v2.24.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-09 10:32:38 +01:00
dependabot[bot] dff5e168a9 build(deps): bump gocloud.dev from 0.43.0 to 0.44.0
Bumps [gocloud.dev](https://github.com/google/go-cloud) from 0.43.0 to 0.44.0.
- [Release notes](https://github.com/google/go-cloud/releases)
- [Commits](https://github.com/google/go-cloud/compare/v0.43.0...v0.44.0)

---
updated-dependencies:
- dependency-name: gocloud.dev
  dependency-version: 0.44.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-05 18:03:07 +01:00
dependabot[bot] c82a035bf1 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.1 to 1.58.2.
- [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.1...service/s3/v1.58.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-05 15:58:01 +01:00
dependabot[bot] 86b01eda10 build(deps): bump github.com/evanw/esbuild from 0.27.0 to 0.27.1
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.27.0 to 0.27.1.
- [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.0...v0.27.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-05 15:57:45 +01:00
Bjørn Erik Pedersen e2e64aeec5 Fix server rebuilds on editing content with Chinese terms
Fixes #14240
2025-12-05 15:53:48 +01:00
dependabot[bot] 9e7182e9e6 build(deps): bump github.com/aws/aws-sdk-go-v2 from 1.40.0 to 1.40.1
Bumps [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) from 1.40.0 to 1.40.1.
- [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.0...v1.40.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-04 09:59:53 +01:00
dependabot[bot] cf3ad62c3b build(deps): bump github.com/spf13/cobra from 1.9.1 to 1.10.2
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.9.1 to 1.10.2.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.9.1...v1.10.2)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-version: 1.10.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-04 09:59:04 +01:00
Bjørn Erik Pedersen c1cc65fcb9 release: Skip pushing stable and docs update for pre-releases 2025-12-03 11:51:37 +01:00
Bjørn Erik Pedersen 663075920a release: Support alpha, beta, and RC releases 2025-12-02 18:44:42 +01:00
Joe Mooring 596517a259 snap: Address snapcraft deprecations 2025-12-01 23:44:24 -08:00
dependabot[bot] bf42138661 build(deps): bump github.com/goccy/go-yaml from 1.18.0 to 1.19.0
Bumps [github.com/goccy/go-yaml](https://github.com/goccy/go-yaml) from 1.18.0 to 1.19.0.
- [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.18.0...v1.19.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 12:05:55 +01:00
dependabot[bot] 57fecb6b71 build(deps): bump github.com/olekukonko/tablewriter from 1.1.1 to 1.1.2
Bumps [github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter) from 1.1.1 to 1.1.2.
- [Commits](https://github.com/olekukonko/tablewriter/compare/v1.1.1...v1.1.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 12:05:40 +01:00
dependabot[bot] 6d0ab6a34e build(deps): bump github.com/JohannesKaufmann/html-to-markdown/v2
Bumps [github.com/JohannesKaufmann/html-to-markdown/v2](https://github.com/JohannesKaufmann/html-to-markdown) from 2.4.0 to 2.5.0.
- [Release notes](https://github.com/JohannesKaufmann/html-to-markdown/releases)
- [Commits](https://github.com/JohannesKaufmann/html-to-markdown/compare/v2.4.0...v2.5.0)

---
updated-dependencies:
- dependency-name: github.com/JohannesKaufmann/html-to-markdown/v2
  dependency-version: 2.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 12:04:55 +01:00
Bjørn Erik Pedersen 6be463b586 langs/i18n: Prefer languageCode when picking translation file
Fixes #14204
Closes #14217

Co-authored-by: Patrice Chalin <pchalin@gmail.com>
2025-11-28 21:24:28 +01:00
Bjørn Erik Pedersen 9e24b5645d Add entitlements for WebAssembly for macOS Tahoe
Fixes #14220
2025-11-27 20:43:03 +01:00
Bjørn Erik Pedersen 45b67f6c18 testscripts: Move layouts file to new structure 2025-11-27 20:41:56 +01:00
Joe Mooring 5e649eb4d0 resources: Skip integration test if Dart Sass is not installed 2025-11-27 19:32:23 +01:00
dependabot[bot] 0de8f8607b 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.57.0 to 1.58.1.
- [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.57.0...service/s3/v1.58.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-27 13:22:29 +01:00
Bjørn Erik Pedersen 3073fd5ccf testing: Replace legacy config.toml with hugo.toml in most tests 2025-11-27 12:08:08 +01:00
Bjørn Erik Pedersen b9b304a126 testing: Port integration tests to new templates structure
As introduced in  v0.146.0. Keep some legecy test to preserve backwards compatibility.
2025-11-27 12:00:15 +01:00
Bjørn Erik Pedersen 8be30ef2cb github: Correct dependabot => dependabot[bot] 2025-11-27 09:48:54 +01:00
Joe Mooring 565a10cbf0 config/privacy: Change GoogleAnalytics.RespectDoNotTrack default to true
Closes #13307
2025-11-27 09:46:53 +01:00
Bjørn Erik Pedersen 438f113f83 Add signed and notarized MacOS pkg builds
Closes #14135
2025-11-26 16:48:51 +01:00
Bjørn Erik Pedersen dab54058ea Upgrade to Go 1.25.4 2025-11-26 16:38:47 +01:00
Joe Mooring 1a1b062a01 tpl/urls: Add PathEscape and PathUnescape functions
Closes #14209
2025-11-25 23:53:04 +01:00
Bjørn Erik Pedersen 7a43b928a6 Fix slow server startup of very big content trees
As in content trees with 10 thousand of directories and more.

A benchmark with the bottle neck code in `helpers.ExtractAndGroupRootPaths`:

```
                            │ cmp20251125.bench │ fix-extractandgrouproot-14211.bench │
                            │      sec/op       │    sec/op     vs base               │
ExtractAndGroupRootPaths-10     1282818.8µ ± 8%   493.8µ ± 38%  -99.96% (p=0.002 n=6)

                            │ cmp20251125.bench │ fix-extractandgrouproot-14211.bench │
                            │       B/op        │     B/op      vs base               │
ExtractAndGroupRootPaths-10       3343.8Ki ± 0%   146.3Ki ± 0%  -95.63% (p=0.002 n=6)

                            │ cmp20251125.bench │ fix-extractandgrouproot-14211.bench │
                            │     allocs/op     │  allocs/op    vs base               │
ExtractAndGroupRootPaths-10        20.043k ± 0%    2.979k ± 0%  -85.14% (p=0.002 n=6)
```

For test project that started this (a 60k directory conent tree), the server startup with no rendering, wen from 1.5 minutes to less than 4 seconds:

```
hugop server --renderSegments none                        main ✚ ✖ ✱ ◼
Watching for changes in /Users/bep/dev/sites/hugotestsites/60k/content/{section0,section1,section10,section100,section101,section102,section103,section104,section105,section106,...}
Watching for changes in /Users/bep/dev/sites/hugotestsites/60k/layouts/_default
Watching for config changes in /Users/bep/dev/sites/hugotestsites/60k/config.toml
Start building sites …
hugo v0.153.0-DEV-7e27c303904ed8b221d6a5a4fc9a764bb7b2935b darwin/arm64 BuildDate=2025-11-25T15:02:58Z

                  │ EN
──────────────────┼────
 Pages            │  0
 Paginator pages  │  0
 Non-page files   │  0
 Static files     │  0
 Processed images │  0
 Aliases          │  0
 Cleaned          │  0

Built in 3884 ms
Environment: "development"
Serving pages from disk
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at //localhost:1313/ (bind address 127.0.0.1)
Press Ctrl+C to stop
```

Note that the output may be a little different and a little more verbose than before., but the information is correct and this implementation is significantly faster and simpler.

Fixes #14211
2025-11-25 22:55:25 +01:00
Bjørn Erik Pedersen 555dfa207a Speedup and simplify page assembly for deeper content trees
This commit moves to a forked version go-radix (fork source has not had any updates in 3 years.), whith 2 notable changes:

* It's generic (using Go generics) and thus removes a lot of type conversions/assertions.
* It allows nodes to be replaced during walk, which allows to partition the tree for parallel processing without worrying about locking.

For this repo, this means:

* The assembly step now processes nested sections in parallel, which gives a speedup for deep content trees with a slight allocation penalty (see benchmarks below).
* Nodes that needs to be reinserted are inserted directly.
* Also, there are some drive-by fixes of some allocation issues, e.g. avoid wrapping mutexes in returned anonomous functions, a common source of hidden allocations.

```
                                                                                   │ master.bench │           perf-p3.bench            │
                                                                                   │    sec/op    │   sec/op     vs base               │
AssembleDeepSiteWithManySections/depth=1/sectionsPerLevel=1/pagesPerSection=50-10     6.958m ± 3%   7.015m ± 3%        ~ (p=0.589 n=6)
AssembleDeepSiteWithManySections/depth=1/sectionsPerLevel=6/pagesPerSection=100-10    14.25m ± 1%   14.56m ± 8%        ~ (p=0.394 n=6)
AssembleDeepSiteWithManySections/depth=1/sectionsPerLevel=6/pagesPerSection=500-10    48.07m ± 3%   49.23m ± 3%        ~ (p=0.394 n=6)
AssembleDeepSiteWithManySections/depth=2/sectionsPerLevel=6/pagesPerSection=100-10    66.66m ± 4%   66.47m ± 6%        ~ (p=0.485 n=6)
AssembleDeepSiteWithManySections/depth=4/sectionsPerLevel=2/pagesPerSection=100-10    59.57m ± 4%   50.73m ± 5%  -14.85% (p=0.002 n=6)
geomean                                                                               28.54m        27.92m        -2.18%

                                                                                   │ master.bench │           perf-p3.bench            │
                                                                                   │     B/op     │     B/op      vs base              │
AssembleDeepSiteWithManySections/depth=1/sectionsPerLevel=1/pagesPerSection=50-10    4.513Mi ± 0%   4.527Mi ± 0%  +0.33% (p=0.002 n=6)
AssembleDeepSiteWithManySections/depth=1/sectionsPerLevel=6/pagesPerSection=100-10   15.35Mi ± 0%   15.49Mi ± 0%  +0.94% (p=0.002 n=6)
AssembleDeepSiteWithManySections/depth=1/sectionsPerLevel=6/pagesPerSection=500-10   62.50Mi ± 0%   63.19Mi ± 0%  +1.10% (p=0.002 n=6)
AssembleDeepSiteWithManySections/depth=2/sectionsPerLevel=6/pagesPerSection=100-10   86.78Mi ± 0%   87.73Mi ± 0%  +1.09% (p=0.002 n=6)
AssembleDeepSiteWithManySections/depth=4/sectionsPerLevel=2/pagesPerSection=100-10   62.96Mi ± 0%   63.66Mi ± 0%  +1.12% (p=0.002 n=6)
geomean                                                                              29.84Mi        30.11Mi       +0.92%

                                                                                   │ master.bench │           perf-p3.bench           │
                                                                                   │  allocs/op   │  allocs/op   vs base              │
AssembleDeepSiteWithManySections/depth=1/sectionsPerLevel=1/pagesPerSection=50-10     60.44k ± 0%   60.97k ± 0%  +0.87% (p=0.002 n=6)
AssembleDeepSiteWithManySections/depth=1/sectionsPerLevel=6/pagesPerSection=100-10    205.8k ± 0%   211.4k ± 0%  +2.70% (p=0.002 n=6)
AssembleDeepSiteWithManySections/depth=1/sectionsPerLevel=6/pagesPerSection=500-10    831.1k ± 0%   858.3k ± 0%  +3.27% (p=0.002 n=6)
AssembleDeepSiteWithManySections/depth=2/sectionsPerLevel=6/pagesPerSection=100-10    1.157M ± 0%   1.197M ± 0%  +3.41% (p=0.002 n=6)
AssembleDeepSiteWithManySections/depth=4/sectionsPerLevel=2/pagesPerSection=100-10    839.9k ± 0%   867.8k ± 0%  +3.31% (p=0.002 n=6)
geomean                                                                               398.5k        409.3k       +2.71%
```
2025-11-25 11:37:05 +01:00
Joe Mooring 3d21b0687b markup/asciidocext: Improve Asciidoctor integration
Fixes an issue where improper attribute derivation from the page's
relative permalink caused failures with `outdir`, `imagesoutdir`, and
`imagesdir` when `markup.asciidocext.workingFolderCurrent` is enabled.
The updated logic now correctly handles:

- Multi-byte characters
- Multilingual multi-host sites
- Site builds from a subdirectory
- Pages using ugly URLs

Supports diagram caching as implemented in v3.1.0 of the asciidoctor-diagram
extension:

- Enables caching by default
- Sets default cache location to the compiled value of caches.misc.dir

Reduces duration of integration tests by:

- Generating GoAT diagrams instead of Ditaa diagrams
- Taking advantage of asciidoctor-diagram caching

Closes #9202
Closes #10183
Closes #10473
Closes #14160
2025-11-24 23:03:53 +01:00
Bjørn Erik Pedersen 34b0c15a54 gemini: Remove styleguide.md (for now) 2025-11-24 21:00:24 +01:00
Bjørn Erik Pedersen 9dc38e917c github: Reenable Gemini, but no auto code review 2025-11-24 20:37:19 +01:00
Bjørn Erik Pedersen b592d34efe github: Partition tests by their root 2025-11-24 19:42:28 +01:00
Bjørn Erik Pedersen 18a2afa519 github: Make the clean commands work 2025-11-24 19:04:33 +01:00
Bjørn Erik Pedersen 0b0c827d5b github: Also test the root package (left out in previous commit) 2025-11-24 18:38:53 +01:00
Bjørn Erik Pedersen 30dc75e620 github: More disk space saving optimizations 2025-11-24 18:38:21 +01:00
Joe Mooring 6bc8e6f491 github: Add PR Template 2025-11-21 20:22:50 +01:00
Bjørn Erik Pedersen cda4d75187 Adjust benchmark 2025-11-21 16:19:24 +01:00
Bjørn Erik Pedersen 84950ed2b2 tpl/openapi: Add support for OpenAPI external file references
Fixes #8067
2025-11-21 16:03:05 +01:00
dependabot[bot] 2b337cd212 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.56.3 to 1.57.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/ecs/v1.56.3...service/s3/v1.57.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-21 11:49:04 +01:00
dependabot[bot] cc61d00df9 build(deps): bump golang.org/x/image from 0.32.0 to 0.33.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.32.0 to 0.33.0.
- [Commits](https://github.com/golang/image/compare/v0.32.0...v0.33.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-21 11:48:42 +01:00
dependabot[bot] 8ef2c61985 build(deps): bump golang.org/x/crypto from 0.43.0 to 0.45.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.43.0 to 0.45.0.
- [Commits](https://github.com/golang/crypto/compare/v0.43.0...v0.45.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.45.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-21 11:47:10 +01:00
Bjørn Erik Pedersen a8ed613742 github: Skip dependabot for AI Watchdog workflow 2025-11-21 11:46:26 +01:00
dependabot[bot] b91688034f build(deps): bump github.com/tdewolff/minify/v2 from 2.24.5 to 2.24.7
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.24.5 to 2.24.7.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.24.5...v2.24.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-21 11:27:13 +01:00
Bjørn Erik Pedersen b971b7f911 github: Remove the 386 test step in GitHub test workflow
Fixes #14201
2025-11-21 11:00:50 +01:00
Bjørn Erik Pedersen 24210dcad9 github: Remove test binaries after CI test runs 2025-11-21 10:59:03 +01:00
Bjørn Erik Pedersen b037b9300c github: Fix "no space left on device" issue in CI 2025-11-20 14:26:52 +01:00
dependabot[bot] 268046732e 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.56.0 to 1.56.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.56.0...service/ecs/v1.56.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-20 10:39:15 +01:00
dependabot[bot] 8ce80f755d build(deps): bump golang.org/x/tools from 0.38.0 to 0.39.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.38.0 to 0.39.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.38.0...v0.39.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-20 10:38:53 +01:00
dependabot[bot] 4baa95ba65 build(deps): bump github.com/tetratelabs/wazero from 1.10.0 to 1.10.1
Bumps [github.com/tetratelabs/wazero](https://github.com/tetratelabs/wazero) from 1.10.0 to 1.10.1.
- [Release notes](https://github.com/tetratelabs/wazero/releases)
- [Commits](https://github.com/tetratelabs/wazero/compare/v1.10.0...v1.10.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-20 10:36:59 +01:00
Bjørn Erik Pedersen 0f94274a28 github: Adjust watchdog run logic (now with correct spelling) 2025-11-19 16:01:38 +01:00
Bjørn Erik Pedersen 5f9c3a3e4c github: Adjust watchdog run logic (again) 2025-11-19 15:59:33 +01:00
Bjørn Erik Pedersen 51d77a6ca8 github: Adjust watchdog run logic 2025-11-19 15:49:52 +01:00
Bjørn Erik Pedersen d25b619ce2 github: Only run AI Watchdog when the PR is ready for review 2025-11-19 15:32:28 +01:00
dependabot[bot] b81861fd21 build(deps): bump github.com/bits-and-blooms/bitset
Bumps [github.com/bits-and-blooms/bitset](https://github.com/bits-and-blooms/bitset) from 1.22.0 to 1.24.4.
- [Release notes](https://github.com/bits-and-blooms/bitset/releases)
- [Commits](https://github.com/bits-and-blooms/bitset/compare/v1.22.0...v1.24.4)

---
updated-dependencies:
- dependency-name: github.com/bits-and-blooms/bitset
  dependency-version: 1.24.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-19 13:55:59 +01:00
dependabot[bot] 15cce89e0e build(deps): bump github.com/olekukonko/tablewriter from 1.1.0 to 1.1.1
Bumps [github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter) from 1.1.0 to 1.1.1.
- [Commits](https://github.com/olekukonko/tablewriter/compare/v1.1.0...v1.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-19 13:28:46 +01:00
dependabot[bot] 187e683bb2 build(deps): bump golang.org/x/text from 0.30.0 to 0.31.0
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.30.0 to 0.31.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.30.0...v0.31.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-19 13:28:05 +01:00
dependabot[bot] 432078a6af build(deps): bump github.com/evanw/esbuild from 0.25.12 to 0.27.0
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.25.12 to 0.27.0.
- [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.25.12...v0.27.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-19 13:24:04 +01:00
Bjørn Erik Pedersen 4a76d8c611 Update aiwatchdog.yml 2025-11-19 12:37:14 +01:00
Bjørn Erik Pedersen d837eff08f Update aiwatchdog.yml 2025-11-19 12:29:22 +01:00
Bjørn Erik Pedersen 323e306bd7 Update aiwatchdog.yml 2025-11-19 12:27:47 +01:00
Bjørn Erik Pedersen 2047638a2c github: Add label to AI suspects and do not fail when confident
The latter is mostly to get the  dependabot PRs through. We may add a filter to that later.
2025-11-19 12:13:11 +01:00
Bjørn Erik Pedersen 38efb702aa github: Adjust AI Watchdog workflow to make it run PRs from forks 2025-11-18 20:31:04 +01:00
Bjørn Erik Pedersen bdf8b1a915 github: Adjust workflow permissions 2025-11-18 19:37:32 +01:00
Bjørn Erik Pedersen f33c1a3fdb performance: Misc allocation improvements
```
                               │ stash.bench  │      perf-allloc20251118.bench      │
                               │    sec/op    │    sec/op     vs base               │
CreateShortcodePlaceholders-10   54.16n ± ∞ ¹   39.06n ± ∞ ¹  -27.87% (p=0.029 n=4)
¹ need >= 6 samples for confidence interval at level 0.95

                               │ stash.bench │     perf-allloc20251118.bench     │
                               │    B/op     │    B/op      vs base              │
CreateShortcodePlaceholders-10   52.00 ± ∞ ¹   48.00 ± ∞ ¹  -7.69% (p=0.029 n=4)
¹ need >= 6 samples for confidence interval at level 0.95

                               │ stash.bench │     perf-allloc20251118.bench      │
                               │  allocs/op  │  allocs/op   vs base               │
CreateShortcodePlaceholders-10   2.000 ± ∞ ¹   1.000 ± ∞ ¹  -50.00% (p=0.029 n=4)
¹ need >= 6 samples for confidence interval at level 0.95
````
2025-11-18 19:17:15 +01:00
Bjørn Erik Pedersen fee095768c hugolib/doctree: Simplify lock setup in SimpleThreadSafeTree to reduce read allocation
```
                                      │    sec/op    │   sec/op     vs base               │
SimpleThreadSafeTree/Get-10             45.80n ± 11%   27.71n ± 7%  -39.51% (p=0.002 n=6)
SimpleThreadSafeTree/Insert-10          40.96n ±  1%   40.36n ± 0%   -1.47% (p=0.002 n=6)
SimpleThreadSafeTree/LongestPrefix-10   47.12n ±  0%   32.60n ± 1%  -30.81% (p=0.002 n=6)
geomean                                 44.54n         33.16n       -25.57%

                                      │ cmp152.bench │       perf-allloc20251118.bench        │
                                      │     B/op     │    B/op     vs base                    │
SimpleThreadSafeTree/Get-10               16.00 ± 0%    0.00 ± 0%  -100.00% (p=0.002 n=6)
SimpleThreadSafeTree/Insert-10            8.000 ± 0%   8.000 ± 0%         ~ (p=1.000 n=6) ¹
SimpleThreadSafeTree/LongestPrefix-10     16.00 ± 0%    0.00 ± 0%  -100.00% (p=0.002 n=6)
````
2025-11-18 19:17:15 +01:00
Bjørn Erik Pedersen 56d7925a97 github: Add ai-watchdog workflow and update other workflows' versions
See #14147
2025-11-18 19:15:14 +01:00
dependabot[bot] 38608c7c14 build(deps): bump google.golang.org/api from 0.251.0 to 0.255.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.251.0 to 0.255.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.251.0...v0.255.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-version: 0.255.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-18 12:14:13 +01:00
dependabot[bot] d75f0dc6a5 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.54.4 to 1.56.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.54.4...service/s3/v1.56.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-18 12:14:02 +01:00
dependabot[bot] 579362efd4 build(deps): bump github.com/tetratelabs/wazero from 1.9.0 to 1.10.0
Bumps [github.com/tetratelabs/wazero](https://github.com/tetratelabs/wazero) from 1.9.0 to 1.10.0.
- [Release notes](https://github.com/tetratelabs/wazero/releases)
- [Commits](https://github.com/tetratelabs/wazero/compare/v1.9.0...v1.10.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-18 12:12:04 +01:00
Joe Mooring c6b6910a79 tpl/collections: Improve collections.D
Changes:

- If n > hi, return the full, sorted range [0, hi) of size hi.
- Throw errors when seed, n, or hi are < 0
- Improve error messages

Closes #14143
2025-11-18 11:42:33 +01:00
Bjørn Erik Pedersen ca4025405c Optimize memory allocations for sites matrix vector stores
By

* Caching common sites matrix setups (e.g. the single site in single site setups).
* Adding a fast path to IntSets.HasAnyVector for the common case of single vector input.

```
AssembleDeepSiteWithManySections/depth=3/sectionsPerLevel=2/pagesPerSection=100-10   31.62m ± 46%   30.68m ± 42%  ~ (p=0.310 n=6)

                                                                                   │ master.bench │          perfcommon.bench          │
                                                                                   │     B/op     │     B/op      vs base              │
AssembleDeepSiteWithManySections/depth=3/sectionsPerLevel=2/pagesPerSection=100-10   31.98Mi ± 0%   31.24Mi ± 0%  -2.30% (p=0.002 n=6)

                                                                                   │ master.bench │         perfcommon.bench          │
                                                                                   │  allocs/op   │  allocs/op   vs base              │
AssembleDeepSiteWithManySections/depth=3/sectionsPerLevel=2/pagesPerSection=100-10    460.9k ± 0%   419.9k ± 0%  -8.90% (p=0.002 n=6)
````
2025-11-17 16:01:37 +01:00
Bjørn Erik Pedersen 44b5f13f86 gemini: Disable auto PR codereviews for now
I'm assuming it's still possible to trigger commands like `/gemini review` manually.
2025-11-17 14:50:41 +01:00
Jordan Elver 5af311284a docs: Fix link to CGO wiki page
The Go wiki is no longer hosted on GitHub, and has moved to go.dev. See https://github.com/golang/go/wiki/
2025-11-17 14:14:47 +01:00
Joe Mooring 9d80c3978f github: Update asciidoctor-diagrams extension and add GoAT 2025-11-16 12:36:05 -08:00
Bjørn Erik Pedersen 62c47407b2 Fix grammatical error in styleguide.md 2025-11-16 20:25:33 +01:00
Bjørn Erik Pedersen 2c6574ebdb Update styleguide.md 2025-11-16 20:05:32 +01:00
Bjørn Erik Pedersen 60c4245e67 Add gemini setup files 2025-11-16 19:30:46 +01:00
Ahamed_Meyan 5e6b269c41 static: Preserve .gitignore and .gitattributes in --cleanDestinationDir
Fixes #14097
2025-11-16 11:10:20 +01:00
Bjørn Erik Pedersen 94a6233aab hugolib: Fix recently introduced data race
This introduces a new thread safe maps.Map type to avoid abusing the exsting maps.Cache for regular map uses.

Fixes #14140
2025-11-15 21:17:42 +01:00
Bjørn Erik Pedersen 26f31ff6ce hugolib: Improve performance of content trees with many sections
Hugo's build process is roughly divided into three steps:

1. Process content (walk file system and insert source nodes into content tree)
2. Assemble content (assemble pages and resources according to sites matrix)
3. Render content

In #13679 we consolidated the page creation logic into one place (the assemble step). This made it much simpler to reason about, but it lost us some performance esp. in big content trees.

This commit re-introduces parallelization in the first step in the assemble step by handling each top level section in its own goroutine. This gives significant performance improvements for content trees with many sections.

Compared to master:

```
AssembleDeepSiteWithManySections/depth=1/sectionsPerLevel=6/pagesPerSection=100-10    19.26m ± ∞ ¹   14.54m ± ∞ ¹  -24.52% (p=0.029 n=4)
AssembleDeepSiteWithManySections/depth=2/sectionsPerLevel=2/pagesPerSection=100-10    19.74m ± ∞ ¹   16.45m ± ∞ ¹  -16.71% (p=0.029 n=4)
AssembleDeepSiteWithManySections/depth=2/sectionsPerLevel=6/pagesPerSection=100-10   106.18m ± ∞ ¹   71.23m ± ∞ ¹  -32.91% (p=0.029 n=4)
AssembleDeepSiteWithManySections/depth=3/sectionsPerLevel=2/pagesPerSection=100-10    38.85m ± ∞ ¹   30.47m ± ∞ ¹  -21.59% (p=0.029 n=4)
```
2025-11-14 11:24:31 +01:00
Joe Mooring bca171b691 markup/asciidocext: Support boolean document attributes
Closes #14138
2025-11-13 19:04:21 +01:00
Joe Mooring 9289aa43aa github: Allow AsciiDoc content in tests to render Ditaa diagrams
In 2c80dee358 we enabled rendering of
PlantUML diagrams within integration tests. This commit removes PlantUML
support from the tests in favor of rendering Ditaa diagrams, which take
less time to generate.
2025-11-12 22:48:43 +01:00
Bjørn Erik Pedersen 606415ec18 Add a site assembly benchmark test for a deeper site structure with more sections and pages 2025-11-12 18:26:52 +01:00
Bjørn Erik Pedersen dc2f6ae27a sitesmatrix: Clary default dimension values
And make the default content version `v.1.0.0`. It has not much practical difference, but this encourages semver usage.
2025-11-12 13:00:55 +01:00
Bjørn Erik Pedersen a0944ac7a4 Run go mod tidy to clean up go.mod and go.sum 2025-11-12 12:58:30 +01:00
Bjørn Erik Pedersen 22d0c17e82 docshelper: Fix some YAML serialization issues with sites matrix configuration
Fixes #14132
2025-11-11 13:49:31 +01:00
David Karlsson 25c7c18f9f resources/page: Fix slugorcontentbasename for section pages
The slugorcontentbasename permalink token was creating an extra
subdirectory for section pages (_index.md files). This was because
pageToPermalinkContentBaseName did not have the same special handling
for _index.md files as pageToPermalinkFilename.

This commit adds the special handling to return an empty string for
section pages, ensuring backward compatibility with slugorfilename.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

Fixes #14104
2025-11-10 17:03:26 +01:00
dependabot[bot] 0e8f88fa72 build(deps): bump github.com/evanw/esbuild from 0.25.11 to 0.25.12
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.25.11 to 0.25.12.
- [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.25.11...v0.25.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-10 12:36:36 +01:00
Bjørn Erik Pedersen a2469d504e testing: Rewrite all the old style integration tests to txtar style tests
And remove some not worth keeping (or too much work to convert).
2025-11-10 12:00:58 +01:00
Alexandru Grigore 0efcb24c37 commands: newDocsHelper encode integers as ints in generated YAML
Previously the JSON -> map roundtrip produced float64 values which caused the YAML encoder to emit integers like 404.0 instead of 404. Use the YAML encoder option yaml.AutoInt() so numbers that are integer-valued are encoded as integers.

This change affects the
ewDocsHelper command (commands/gen.go) where the docs data is written to docs/data/docs.yaml. After this change generated YAML will contain integer values (e.g. status: 404) instead of floats (e.g. status: 404.0).

Fixes #14122
2025-11-09 23:01:16 +01:00
Joe Mooring e1236e3d0d config: Add struct tags to deprecated and unused fields
This will remove these fields from the output of newDocsHelper.
2025-11-09 12:59:46 +01:00
Bjørn Erik Pedersen e24b604c30 hugolib: Delete some old integration tests
We have had several had several integration test setups over the years. What we have now, backed by txtar files, has been working very well for a long time.

The tests deleted here may lose us some coverage, but they are painful to maintain and it's unlikely that we will port them to the new setup.
2025-11-07 19:44:58 +01:00
Bjørn Erik Pedersen 4c7a78f5ca testing: Revise usage of b.N and b.Loop() in benchmarks 2025-11-07 13:33:39 +01:00
Bjørn Erik Pedersen 91eac9e573 all: Fix some benchmarks broken by modernize
It's not possible to use `b.Loop()` when `b.N` is used to prepare test data.

See 264022a75a
See #14107
2025-11-06 10:43:12 +01:00
Bjørn Erik Pedersen 04650ce778 all: Run modernize -fix ./...
Closes #14107
2025-11-05 21:05:42 +01:00
Bjørn Erik Pedersen 264022a75a Add roles and versions as new dimensions (in addition to language)
See the main issue #13776 for details.

Fixes #519
Fixes #13680
Fixes #13663
Fixes #13776
Fixes #13855
Fixes #13648
Fixes #13996
Fixes #14001
Fixes #14031
Fixes #13818
Fixes #13196
2025-11-05 20:39:25 +01:00
Bjørn Erik Pedersen ff0f67ea7f Update CONTRIBUTING.md 2025-11-04 10:57:26 +01:00
Joe Mooring 2c80dee358 github: Allow AsciiDoc content in tests to render diagrams 2025-10-31 12:47:40 -07:00
Bjørn Erik Pedersen e9bda21ce9 hreflect: Cache reflect method lookups used in collections.Where and others
We already cached the method index on the struct, but caching the resolved `reflect.Method` itself saves us from having to do another lookup on each call, which is escpeciall import in the hot path used by collections.Where and otherrs:

```bash
                                        │ master.bench │    fix-reflectmethodcache.bench     │
                                        │    sec/op    │    sec/op     vs base               │
WhereSliceOfStructPointersWithMethod-10   592.2µ ± ∞ ¹   390.1µ ± ∞ ¹  -34.14% (p=0.029 n=4)
¹ need >= 6 samples for confidence interval at level 0.95

                                        │  master.bench  │     fix-reflectmethodcache.bench     │
                                        │      B/op      │     B/op       vs base               │
WhereSliceOfStructPointersWithMethod-10   205.14Ki ± ∞ ¹   64.52Ki ± ∞ ¹  -68.55% (p=0.029 n=4)
¹ need >= 6 samples for confidence interval at level 0.95

                                        │ master.bench │    fix-reflectmethodcache.bench     │
                                        │  allocs/op   │  allocs/op    vs base               │
WhereSliceOfStructPointersWithMethod-10   9.003k ± ∞ ¹   4.503k ± ∞ ¹  -49.98% (p=0.029 n=4)
````
2025-10-27 15:45:50 +01:00
Bjørn Erik Pedersen 3893e70510 all: Simplify the reflect usage
* Use helper funcs in hreflect package when possible.
* Use hreflect.ConvertIfPossible to handle conversions when possible.
* Move scratch.go from common/maps to common/hstore to clear cyclic import in the next step.
* Move Indirect to hreflect and reimplementing it and adusting the behavior to preserve struct pointers.
* Adjust evaluateSubElem used by where and others making the struct pointer method case slightly faster.
2025-10-26 17:36:58 +01:00
hugoreleaser b95f7d2552 releaser: Prepare repository for 0.153.0-DEV
[ci skip]
2025-10-24 15:44:59 +00:00
hugoreleaser 6abdacad3f releaser: Bump versions for release of 0.152.2
[ci skip]
2025-10-24 15:31:49 +00:00
Joe Mooring 1c8c21e45e deps: Update github.com/tdewolff/minify v2.24.4 => v2.24.5
Closes #14086
2025-10-24 17:28:42 +02:00
Bjørn Erik Pedersen 809ebe01fa hugofs: Make node_modules a "special case" mount
For this and similar mounts in a theme:

```toml
[[module.mounts]]
source = 'node_modules/bootstrap'
target = 'assets/vendor/bootstrap'
```

We first check the theme itself, then the project root.

For backwards compatibility, we also make any `../../node_modules/...` `source` paths into `node_modules/...` paths when defined in themes/modules.

Fixes #14089
2025-10-24 17:12:46 +02:00
Jordan Elver 08a0679a89 github: Fix typo in stale PR message 2025-10-24 14:30:48 +02:00
hugoreleaser 524b98665b releaser: Prepare repository for 0.153.0-DEV
[ci skip]
2025-10-22 19:24:59 +00:00
hugoreleaser 5869cbddd8 releaser: Bump versions for release of 0.152.1
[ci skip]
2025-10-22 19:10:44 +00:00
Bjørn Erik Pedersen e08278d165 Expand the numeric conversions to template funcs/methods
So the example mentioned in #14079 also works for TOML and JSON front matter.

See #14079
2025-10-22 21:04:39 +02:00
Bjørn Erik Pedersen df4f80d54f Fix where with uint64
Fixes #14081
2025-10-22 21:04:39 +02:00
Bjørn Erik Pedersen d4c78885ae Fix it so YAML integer types can be used where Go int types are expected
E.g. in date.AddDate.

In Hugo v0.152.0 we moved to a new YAML library (github.com/goccy/go-yaml) which produces uint64 for unsigned integers.

This unfortunately breaks common constructs like:

  .Date.AddDate 0 0 7

when .Date is a time.Time and the integers are unmarshaled from YAML front matter.

This commit adds code to handle conversion from uint64 (and other int types) to the required int types where possible.

Fixes #14079
2025-10-22 21:04:39 +02:00
Bjørn Erik Pedersen 29e2c2fa92 tpl/compare: Fix compare/sort of uint64s
`uint64` is used by github.com/goccy/go-yaml  for unsigned integers, which is the reason why this long-living bug has surfaced now.

Fixes #14078
2025-10-22 13:40:03 +02:00
Bjørn Erik Pedersen 0579afc3c5 Fix "assignment to entry in nil map" on empty YAML config files
Fixes #14074
2025-10-22 13:40:03 +02:00
hugoreleaser 0ccbc63ab8 releaser: Prepare repository for 0.153.0-DEV
[ci skip]
2025-10-21 16:43:50 +00:00
hugoreleaser 06bc2c16c0 releaser: Bump versions for release of 0.152.0
[ci skip]
2025-10-21 16:29:49 +00:00
Bjørn Erik Pedersen d51adca26a Merge branch 'release-0.151.2' 2025-10-21 16:08:23 +02:00
Bjørn Erik Pedersen a1307700dd config: Clone language map entries before modifying them
Now, with YAML anchor and alias support, these can point to shared data,
which must not be modified in place.

Fixes #14072
2025-10-21 13:34:15 +02:00
Bjørn Erik Pedersen 9425b939c6 Skip flaky test for now
See #14072
2025-10-21 10:32:52 +02:00
Bjørn Erik Pedersen bd50c9c7e7 Misc YAML adjustments
Closes #14067
2025-10-20 16:05:05 +02:00
Bjørn Erik Pedersen a8e0ca9254 hugofs: Make sure that non-project module mounts are local paths
Fixes #14069
2025-10-19 20:27:10 +02:00
Joe Mooring 559a029de7 langs/i18n: Improve reserved key error message
Closes #14061
2025-10-19 13:47:04 +02:00
Bjørn Erik Pedersen 184b10ed45 deps: Upgrade github.com/gohugoio/go-i18n/v2
Gets the fork line with https://github.com/nicksnyder/go-i18n/commit/28dedbee2baafdef1b89b46fc5b729d09232e245 + patch.
2025-10-18 18:21:14 +02:00
Bjørn Erik Pedersen 5bad0d523e langs: Add test case using a "reserved" i18n code
See #14061
2025-10-18 15:28:33 +02:00
Bjørn Erik Pedersen a3d9548469 Replace to gopkg.in/yaml with github.com/goccy/go-yaml (note)
This commit also adds validation to prevent the "Billion Laughs" attack (see https://github.com/goccy/go-yaml/issues/461). The limit of non-scalar aliases to the same node is set to 10,000. See benchmarks below.

```                                                            │        sec/op         │
UnmarshalBillionLaughs/Billion_Laughs_no_validation-10                 125.2µ ± ∞ ¹
UnmarshalBillionLaughs/Billion_Laughs_with_validation-10               655.8µ ± ∞ ¹
UnmarshalBillionLaughs/YAML_Front_Matter_no_validation-10              9.223µ ± ∞ ¹
UnmarshalBillionLaughs/YAML_Front_Matter_with_validation-10            9.443µ ± ∞ ¹
geomean                                                                51.71µ
¹ need >= 6 samples for confidence interval at level 0.95

                                                            │ fix-goyaml-8822.bench │
                                                            │         B/op          │
UnmarshalBillionLaughs/Billion_Laughs_no_validation-10                177.0Ki ± ∞ ¹
UnmarshalBillionLaughs/Billion_Laughs_with_validation-10              177.0Ki ± ∞ ¹
UnmarshalBillionLaughs/YAML_Front_Matter_no_validation-10             11.67Ki ± ∞ ¹
UnmarshalBillionLaughs/YAML_Front_Matter_with_validation-10           11.67Ki ± ∞ ¹
geomean                                                               45.45Ki
¹ need >= 6 samples for confidence interval at level 0.95

                                                            │ fix-goyaml-8822.bench │
                                                            │       allocs/op       │
UnmarshalBillionLaughs/Billion_Laughs_no_validation-10                 3.302k ± ∞ ¹
UnmarshalBillionLaughs/Billion_Laughs_with_validation-10               3.305k ± ∞ ¹
UnmarshalBillionLaughs/YAML_Front_Matter_no_validation-10               253.0 ± ∞ ¹
UnmarshalBillionLaughs/YAML_Front_Matter_with_validation-10             253.0 ± ∞ ¹
````

Fixes #8822
Fixes #13043
Fixes #14053
Fixes ##8427
2025-10-18 13:52:22 +02:00
dependabot[bot] 9e344bbe49 build(deps): bump github.com/tdewolff/minify/v2 from 2.24.3 to 2.24.4
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.24.3 to 2.24.4.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.24.3...v2.24.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-17 18:44:56 +02:00
hugoreleaser 1ec0e2ebec releaser: Prepare repository for 0.152.0-DEV
[ci skip]
2025-10-16 17:06:05 +00:00
hugoreleaser a3574f6f70 releaser: Bump versions for release of 0.151.2
[ci skip]
2025-10-16 16:52:34 +00:00
Bjørn Erik Pedersen 989454a52d parser/pageparser: Add a testcase for nested shortcodes of the same name
See #14054
2025-10-16 18:50:02 +02:00
Bjørn Erik Pedersen 1e91e46520 parser/pageparser: Fix shortcode nesting regression
This reverts commit a133393eda.

While that commit fixed a bug, it introduced a worse one.

Closes #14054
2025-10-16 18:50:02 +02:00
Bjørn Erik Pedersen 8a57d0f15f testscripts: Add and improve commands tests for static mounts 2025-10-16 12:39:11 +02:00
hugoreleaser b76c50ac18 releaser: Prepare repository for 0.152.0-DEV
[ci skip]
2025-10-15 15:06:42 +00:00
hugoreleaser 1cdd17882c releaser: Bump versions for release of 0.151.1
[ci skip]
2025-10-15 14:51:34 +00:00
Bjørn Erik Pedersen e2fb0b0e80 Upgrade Go to 1.25.3
See https://go.dev/doc/devel/release#go1.25.3
2025-10-15 13:00:33 +02:00
Kazuo Oishi 88aea56683 tpl: Fix strings/truncate CJK handling
Fix truncate position for mix of CJK and non-CJK text.

Fixes #14039
2025-10-15 13:00:10 +02:00
dependabot[bot] f4c11571bb build(deps): bump github.com/gohugoio/hashstructure from 0.5.0 to 0.6.0
Bumps [github.com/gohugoio/hashstructure](https://github.com/gohugoio/hashstructure) from 0.5.0 to 0.6.0.
- [Release notes](https://github.com/gohugoio/hashstructure/releases)
- [Commits](https://github.com/gohugoio/hashstructure/compare/v0.5.0...v0.6.0)

---
updated-dependencies:
- dependency-name: github.com/gohugoio/hashstructure
  dependency-version: 0.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-15 11:00:35 +02:00
dependabot[bot] 54075acc29 build(deps): bump golang.org/x/image from 0.30.0 to 0.32.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.30.0 to 0.32.0.
- [Commits](https://github.com/golang/image/compare/v0.30.0...v0.32.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-15 10:59:28 +02:00
dependabot[bot] 8b52303e31 build(deps): bump github.com/evanw/esbuild from 0.25.10 to 0.25.11
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.25.10 to 0.25.11.
- [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.25.10...v0.25.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-15 10:57:33 +02:00
dependabot[bot] 3d45d30a41 build(deps): bump golang.org/x/tools from 0.37.0 to 0.38.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.37.0 to 0.38.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.37.0...v0.38.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-14 22:10:08 +02:00
dependabot[bot] 095157cd65 build(deps): bump golang.org/x/mod from 0.28.0 to 0.29.0
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.28.0 to 0.29.0.
- [Commits](https://github.com/golang/mod/compare/v0.28.0...v0.29.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-14 16:34:43 +02:00
Sardorbek Imomaliev 29cf87444f create/skeletons: Wrap section and home lists with section tags
Avoiding issues with tag leakage when using .Summary
See issue #14044 for details.
2025-10-14 11:54:45 +02:00
Joe Mooring 1b4dd436dd markup/goldmark: Align blockquote default output with Goldmark
Closes #14046
2025-10-14 09:38:29 +02:00
Bjørn Erik Pedersen 4414ef73f3 parser/pageparser: Store shortcode names as unique.Handle[string] to save memory allocations
```
                        │ stash.bench  │        perf-pagelexer.bench        │
                        │    sec/op    │    sec/op     vs base              │
ShortcodeLexer-10         68.87µ ± ∞ ¹   65.91µ ± ∞ ¹       ~ (p=0.200 n=4)
Parse-10                  7.421µ ± ∞ ¹   7.030µ ± ∞ ¹  -5.28% (p=0.029 n=4)
HasShortcode/Match-10     70.42n ± ∞ ¹   77.37n ± ∞ ¹  +9.86% (p=0.029 n=4)
HasShortcode/NoMatch-10   14.32n ± ∞ ¹   14.38n ± ∞ ¹       ~ (p=0.086 n=4)
geomean                   847.2n         847.3n        +0.01%
¹ need >= 6 samples for confidence interval at level 0.95

                        │  stash.bench  │         perf-pagelexer.bench          │
                        │     B/op      │     B/op       vs base                │
ShortcodeLexer-10         181.3Ki ± ∞ ¹   177.4Ki ± ∞ ¹  -2.19% (p=0.029 n=4)
Parse-10                  24.56Ki ± ∞ ¹   24.19Ki ± ∞ ¹  -1.53% (p=0.029 n=4)
HasShortcode/Match-10       0.000 ± ∞ ¹     0.000 ± ∞ ¹       ~ (p=1.000 n=4) ²
HasShortcode/NoMatch-10     0.000 ± ∞ ¹     0.000 ± ∞ ¹       ~ (p=1.000 n=4) ²
geomean                               ³                  -0.93%               ³
¹ need >= 6 samples for confidence interval at level 0.95
² all samples are equal
³ summaries must be >0 to compute geomean

                        │ stash.bench  │         perf-pagelexer.bench         │
                        │  allocs/op   │  allocs/op   vs base                 │
ShortcodeLexer-10         1004.0 ± ∞ ¹   921.0 ± ∞ ¹   -8.27% (p=0.029 n=4)
Parse-10                   34.00 ± ∞ ¹   14.00 ± ∞ ¹  -58.82% (p=0.029 n=4)
HasShortcode/Match-10      0.000 ± ∞ ¹   0.000 ± ∞ ¹        ~ (p=1.000 n=4) ²
HasShortcode/NoMatch-10    0.000 ± ∞ ¹   0.000 ± ∞ ¹        ~ (p=1.000 n=4) ²
```
2025-10-05 19:06:46 +02:00
Bjørn Erik Pedersen a133393eda parser/pagerparser: Fix closing shortcode error handling when repeated 2025-10-05 19:06:46 +02:00
Bjørn Erik Pedersen 9197debbfe testscripts: Make test assertion less specific 2025-10-05 18:08:52 +02:00
hugoreleaser c29897fac0 releaser: Prepare repository for 0.152.0-DEV
[ci skip]
2025-10-02 13:45:36 +00:00
hugoreleaser c70ab27ceb releaser: Bump versions for release of 0.151.0
[ci skip]
2025-10-02 13:30:36 +00:00
Bjørn Erik Pedersen 510d98b778 Adjust the terminal progress reporter a little
Mostly to reduce the amount of OSC 9;4 sequences written to stdout.
2025-10-02 14:57:42 +02:00
Andrii Chubatiuk 7fd6762c16 transform/livereloadinject: Skip livereload.js injection if no tags found (note)
This change is mainly motivated to support sites built by HTML fragments with e.g. a JS framework.

Now we don't inject the script if we don't find any of `doctype` (the only one required by the HTML 5 spec), `html` or `head`.

Co-authored-by: bep <bjorn.erik.pedersen@gmail.com>
2025-10-02 13:39:35 +02:00
dependabot[bot] 584f052f39 build(deps): bump google.golang.org/api from 0.248.0 to 0.251.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.248.0 to 0.251.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.248.0...v0.251.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-version: 0.251.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-01 23:05:48 +02:00
dependabot[bot] b76d71761e 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.53.0 to 1.54.4.
- [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.53.0...service/s3/v1.54.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-01 23:05:36 +02:00
Bjørn Erik Pedersen c5dca3bdee Add transform.HTMLToMarkdown
Fixes #13946
2025-10-01 19:17:26 +02:00
Bjørn Erik Pedersen ec463c0977 Report OSC 9;4 progress when building
As supported by the Ghostty terminal and others.
2025-10-01 14:27:23 +02:00
dependabot[bot] 4d2743e4c8 build(deps): bump github.com/aws/aws-sdk-go-v2 from 1.38.1 to 1.39.2
Bumps [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) from 1.38.1 to 1.39.2.
- [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.38.1...v1.39.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-30 21:13:50 +02:00
dependabot[bot] c20f70d9a3 build(deps): bump golang.org/x/tools from 0.36.0 to 0.37.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.36.0 to 0.37.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.36.0...v0.37.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-30 21:11:37 +02:00
dependabot[bot] 1b556216a8 build(deps): bump github.com/spf13/afero from 1.14.0 to 1.15.0
Bumps [github.com/spf13/afero](https://github.com/spf13/afero) from 1.14.0 to 1.15.0.
- [Release notes](https://github.com/spf13/afero/releases)
- [Commits](https://github.com/spf13/afero/compare/v1.14.0...v1.15.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/afero
  dependency-version: 1.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-30 21:11:14 +02:00
dependabot[bot] 106c8e6de6 build(deps): bump github.com/tdewolff/minify/v2 from 2.24.2 to 2.24.3
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.24.2 to 2.24.3.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.24.2...v2.24.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-30 21:10:54 +02:00
Bjørn Erik Pedersen 03b33ecb5e Fix file caching for 404 responses in resources.GetRemote
Fixes #14019
2025-09-30 20:43:05 +02:00
dependabot[bot] 9928122eb8 build(deps): bump github.com/evanw/esbuild from 0.25.9 to 0.25.10
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.25.9 to 0.25.10.
- [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.25.9...v0.25.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-30 17:43:39 +02:00
Dr. Tobias Quathamer 105d3bc32f tpl: Workaround s390x precision of Atan and Tan
On s390x, math.Atan(1) and math.Tan(1) give values that slightly differ
from other architectures, causing TestTemplateFuncsExamples to fail.

The golang math package states in the overview:
"This package does not guarantee bit-identical results across architectures."
2025-09-29 17:09:29 +02:00
Bjørn Erik Pedersen 3e46ba5ce2 cache/httpcache: Add respectCacheControlNoStoreInResponse and respectCacheControlNoStoreInRequest options
This cache is used by `resources.GetRemote`.

Default values are:

* respectCacheControlNoStoreInResponse: false
* respectCacheControlNoStoreInRequest: true

This is a slightly breaking change, but the current behaviour is confusing, as:

* Many servers set the `no-store` header without much consideration, see https://developer.chrome.com/docs/web-platform/bfcache-ccns for more context
* We almost always want to cache the `resources.GetRemote` to disk.

Fixes #13990
2025-09-29 17:06:50 +02:00
Bjørn Erik Pedersen 4d1303512b common/hreflect: Speed up IsTrutfulValue
By caching the calculation of whether a type implements `IsZero`:

```
IsTruthFulVAlue-10    467.95n ± ∞ ¹   79.13n ± ∞ ¹  -83.09% (p=0.029 n=4)
```
2025-09-29 17:06:50 +02:00
dependabot[bot] 9943c1bef0 build(deps): bump golang.org/x/text from 0.28.0 to 0.29.0
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.28.0 to 0.29.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.28.0...v0.29.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-29 15:09:06 +02:00
dependabot[bot] 766757310a build(deps): bump github.com/olekukonko/tablewriter from 1.0.9 to 1.1.0
Bumps [github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter) from 1.0.9 to 1.1.0.
- [Commits](https://github.com/olekukonko/tablewriter/compare/v1.0.9...v1.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-29 15:08:34 +02:00
dependabot[bot] d71c07cf3c build(deps): bump github.com/spf13/cast from 1.9.2 to 1.10.0
Bumps [github.com/spf13/cast](https://github.com/spf13/cast) from 1.9.2 to 1.10.0.
- [Release notes](https://github.com/spf13/cast/releases)
- [Commits](https://github.com/spf13/cast/compare/v1.9.2...v1.10.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/cast
  dependency-version: 1.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-29 15:07:36 +02:00
Joe Mooring b462980ac2 markup/goldmark: Enhance footnote extension with backlinkHTML option
This commit introduces a new option, backlinkHTML, to the Goldmark
footnote extension. This allows users to the set custom text for
footnote backlink anchors.

Closes #11434
2025-09-29 12:21:17 +02:00
Joe Mooring 47678d8cbd markup/goldmark: Enhance footnote extension with auto-prefixing option
This commit introduces a new option, enableAutoIDPrefix, to the Goldmark
footnote extension. When enabled, it prepends 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.
This change also refactors the extension's configuration from a boolean
to a struct.

Closes #8045
2025-09-27 18:16:42 +02:00
hugoreleaser 18b9b6488b releaser: Prepare repository for 0.151.0-DEV
[ci skip]
2025-09-25 10:45:05 +00:00
hugoreleaser ce44a8e835 releaser: Bump versions for release of 0.150.1
[ci skip]
2025-09-25 10:26:04 +00:00
Joe Mooring 64f40731f1 hugolib: Change duplicate content path warning to an info log
Improves #13993
2025-09-20 20:13:08 +02:00
Joe Mooring 1140314be1 hugolib: Restore integration test
Closes #13991
2025-09-18 18:02:13 +02:00
Joe Mooring 404fd9e512 commands: Map --minify CLI flag to the correct configuration key
Closes #13988
2025-09-18 16:50:47 +02:00
Max Kapur b1b0cdee3a snap: Add desktop plug 2025-09-18 06:22:39 -07:00
Max Kapur 3eea082903 test(deps): Update setup-ruby action to v1.257.0 2025-09-17 09:30:21 -07:00
hugoreleaser 0f18cbe990 releaser: Prepare repository for 0.151.0-DEV
[ci skip]
2025-09-08 13:16:24 +00:00
hugoreleaser 3f5473b7d4 releaser: Bump versions for release of 0.150.0
[ci skip]
2025-09-08 13:01:12 +00:00
dependabot[bot] d1f6a1dc59 build(deps): bump golang.org/x/mod from 0.27.0 to 0.28.0
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.27.0 to 0.28.0.
- [Commits](https://github.com/golang/mod/compare/v0.27.0...v0.28.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 11:23:39 +02:00
Bjørn Erik Pedersen 747cf4ad65 modules: Add support for direct version module imports in hugo.toml
Fixes #13964
2025-09-07 22:47:25 +02:00
Joe Mooring d8774d7fc3 resources/page: Fix truncated summary logic
Fixes #13967
Fixes #13968
2025-09-07 11:07:41 +02:00
Joe Mooring 3b8947d821 config/security: Add PROGRAMDATA to the osenv allowlist 2025-09-06 18:11:38 +02:00
hugoreleaser 321a66ef19 releaser: Prepare repository for 0.150.0-DEV
[ci skip]
2025-09-04 14:18:57 +00:00
hugoreleaser 57a784e027 releaser: Bump versions for release of 0.149.1
[ci skip]
2025-09-04 14:04:46 +00:00
Lukáš Zapletal 25c0f2408a Remove noindex meta tag from alias.html
Google does not behave as expected when an alias is used. It does not index the client-side alias page, but on top of that it also does not index the original page.

Google does not require noindex to be present and since alias pages do not have any content, it makes little sense to provide it. Therefore, this patch removes the line completely.

https://developers.google.com/search/docs/crawling-indexing/301-redirects
2025-09-04 15:58:23 +02:00
Bjørn Erik Pedersen 4f2d2b2cc4 Fix nilpointer on ToC heading
Fixes #11843

Co-authored-by: Joe Mooring <joe.mooring@veriphor.com>
2025-09-04 14:01:51 +02:00
Bjørn Erik Pedersen b8eb45c9df tpl/collections: Require collections.D args to be ints
Fixes #13952

Co-authored-by: Joe Mooring <joe.mooring@veriphor.com>
2025-09-04 14:01:51 +02:00
Bjørn Erik Pedersen 1d90afff1b Upgrade to Go 1.25.1
Fixes #13960
2025-09-04 11:11:29 +02:00
PikachuTW e751afa9bd Fix config env handling for some slice options
Fixes #13950
2025-09-04 10:49:14 +02:00
Joe Mooring a09b8a60eb minifiers: Update deprecation handling
1. With minify.tdewolff.css and minify.tdewolff.svg, check
   for "decimals" instead of "decimal"

2. Add deprecation messages for:

  - minify.tdewolff.css.decimals
  - minify.tdewolff.svg.decimals
  - minify.tdewolff.html.keepconditionalcomments
  - config root minify (bool)
  - config root minifyoutput (bool)

3. Deprecate minify.tdewolff.css.keepcss2 in favor
   of minify.tdewolff.css.version

4. Refactor minify configuration tests

Closes #11893
Closes #13947
Closes #13948
2025-09-03 10:56:32 +02:00
Bjørn Erik Pedersen 0071b47b8b Update README.md 2025-09-01 17:11:08 +02:00
hugoreleaser 70d62993ee releaser: Prepare repository for 0.150.0-DEV
[ci skip]
2025-08-27 15:51:44 +00:00
hugoreleaser 66240338f1 releaser: Bump versions for release of 0.149.0
[ci skip]
2025-08-27 15:37:16 +00:00
Bjørn Erik Pedersen 84b5123912 tpl/collections: Add an integration test for collections.D
Also
* improve the handling if invalid input
* add a sanity check for number of elements asked for in D (1000000).
2025-08-27 17:25:05 +02:00
Joe Mooring 2912415955 misc: Update Go version to 1.24.0 in README
Update Go version requirements for building Hugo.
2025-08-27 16:46:38 +02:00
Justus Perlwitz bb4e66cd7c create: Fix new content command with future dates
Fixes #12599
2025-08-27 09:33:17 +02:00
dependabot[bot] 45ec2f88bb build(deps): bump github.com/getkin/kin-openapi from 0.132.0 to 0.133.0
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.132.0 to 0.133.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.132.0...v0.133.0)

---
updated-dependencies:
- dependency-name: github.com/getkin/kin-openapi
  dependency-version: 0.133.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-27 09:31:02 +02:00
Bjørn Erik Pedersen 1ba80874e4 tpl/collections: Add collections.D using Vitter's Method D for sequential random sampling 2025-08-26 20:37:08 +02:00
dependabot[bot] 84dd495f2b build(deps): bump google.golang.org/api from 0.247.0 to 0.248.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.247.0 to 0.248.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.247.0...v0.248.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-version: 0.248.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-26 09:54:26 +02:00
dependabot[bot] 327bbc613f build(deps): bump github.com/evanw/esbuild from 0.25.6 to 0.25.9
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.25.6 to 0.25.9.
- [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.25.6...v0.25.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-26 09:54:07 +02:00
dependabot[bot] 2447138f1d build(deps): bump gocloud.dev from 0.40.0 to 0.43.0
Bumps [gocloud.dev](https://github.com/google/go-cloud) from 0.40.0 to 0.43.0.
- [Release notes](https://github.com/google/go-cloud/releases)
- [Commits](https://github.com/google/go-cloud/compare/v0.40.0...v0.43.0)

---
updated-dependencies:
- dependency-name: gocloud.dev
  dependency-version: 0.43.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-25 20:47:05 +02:00
Bjørn Erik Pedersen 61ec7a20a5 commands: Deprecate --omitEmpty on chromastyles command
It's no longer needed -- empty classes are now always omitted.

See  See https://github.com/alecthomas/chroma/commit/5b2a4c5a26c503c79bc86ba3c4ae5b330028bd3d
2025-08-25 18:24:07 +02:00
Bjørn Erik Pedersen c289fcaaaa commands: Add --omitClassComments to the chromastyles command 2025-08-25 18:24:07 +02:00
Bjørn Erik Pedersen cfc38ecfed deps: Upgrade github.com/alecthomas/chroma/v2 v2.19.0 => v2.20.0
CLoses #13917
2025-08-25 18:24:07 +02:00
dependabot[bot] 22e579e050 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.44.10 to 1.53.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/macie2/v1.44.10...service/s3/v1.53.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-25 15:32:19 +02:00
dependabot[bot] b886615d1b build(deps): bump github.com/tdewolff/minify/v2 from 2.23.11 to 2.24.0
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.23.11 to 2.24.0.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.23.11...v2.24.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-25 15:29:50 +02:00
minxinyi ecdef2be70 all: Use slices.Equal 2025-08-24 20:35:18 +02:00
Bjørn Erik Pedersen ff3ae62933 Merge commit 'bfa74537929f409fca841540b971125b7678963a' 2025-08-23 12:39:28 +02:00
Bjørn Erik Pedersen bfa7453792 Squashed 'docs/' changes from 60dd993a6..71f739460
71f739460 content: Reformat procedures to use description lists instead of H3 elements
420a78802 content: Remove 21YunBox hosting guide
abeea342f content: Add hosting guide for Vercel
3aaca6c0a content: Add hosting guide for Render
d77b06aa6 content: Fix formatting
82885415f content: Change build.sh file perms in Cloudflare hosting docs
0569c68ca content: Fix typo
8f14be5da content: Remove duplicate list items
e76f17815 misc: Fix typo in configuration for the Markdown linter
32e450b7c misc: Fix typo in configuration for the Markdown linter
81bd0056a mics: Update configuration for the Markdown linter
480ae9014 content: Improve file names in the PageInner description
b17999021 content: Adjust Markdown to pass linter tests
1d9d56897 misc: Implement Markdown linter
91fc34c58 content: Miscellaneous edits
b7c60ae0b content: Add admonition for outdated learning resources
51b6df588 misc: Activate stale action
84a897f5f misc: Implement stale action
167ea9713 content: Update GitHub Pages workflow example
3672d9116 misc: Use actions/checkout@v5 in workflows
2f2b4c968 misc: Update spellcheck configs
55a8ff7ae content: Fix typo
36e3d7b7b Adjust sponsors
51014e6b9 content: Fix link
37cfc093f content: Add Cloudflare Worker hosting instructions
09862ffad content: Update hosting considerations related to GitInfo
477f3e7c0 misc: Update cspell config
7a7bb4874 content: Miscellaneous edits
48486d5ac content: Remove Cloudflare Pages documentation
382f8052d content: Remove KeyCDN documentation
8ecbc985c content: Update hosting workflow files
57fd362bf content: Clarify applicability of the slug front matter field
0dcfff9c7 content: Update directory-structure.md
58e3609a0 content: Fix typo
22fb43695 content: Fix search/replace errors
fba8342a8 content: Fix missing link refs
97e035006 Update netlify.toml
6ab5ae6df content: Improve GitLab Pages compression performance
172bfbc4b content: Differentiate content view templates from other templates
978770fc8 content: Clarify handling of goldmark extensions conflict
cb74aa4c7 content: Improve Codeberg Pages hosting instructions
794477792 content: Add custom domain instructions to Codeberg Pages setup
235fac27d content: Improve description of code block render hook options map
35a507d31 content: Improve WP2Hugo description
8191f4dcf content: Improve WP2Hugo description
25cca30fd content: Fix type
e955652ae content: Improve base template description
922aa91bd content: Improve description of partial lookup logic
072536ef6 content: Update template type documentation
5bf153531 Update netlify.toml
cdd20a433 content: Indicate language sort order for relevant page methods
fc9a868c9 content: Add Sitepins to list of commercial CMS platforms
b6077449c content: Update version references
b71388cac content: Fix typo
c27dfdef3 content: Move link reference
d008ea6ac content: Fix typo
040f14a3c content: Fix typo
1ac119d26 content: Fix typo
a5a4fa907 content: More updates for v0.148.0
b7247a8ea content: Remove command chaining from GitHub Actions workflow
6ecb96bcd content: Updates for v0.148.0
aea9e4c29 netlify: Update Hugo version
4e71546d1 Add Ancestors (plural) method to GitInfo, rename Ancestor field to Parent
68620a6c2 source: Expose Ancestor in GitInfo

git-subtree-dir: docs
git-subtree-split: 71f739460f
2025-08-23 12:36:13 +02:00
Dustin Fischer 12ace3ad5c resources/page: Add :sectionslug and :sectionslugs permalink tokens
Add slugified section permalink tokens with fallback behavior and slice syntax support.

Fixes #13788
2025-08-23 12:35:21 +02:00
Bjørn Erik Pedersen c14fdddada Upgrade to Go 1.25
Fixes #13924
Fixes #13931
2025-08-22 18:17:10 +02:00
Bjørn Erik Pedersen 186934feb4 common/hcontext: Replace with external package 2025-08-22 09:13:46 +02:00
Bjørn Erik Pedersen 13b43e6117 Fix server rebuild when adding a new leaf bundle with resources in one go
E.g. `cp -r`.

Note that this was not an issue if you first created the bundle, waited, and then created the resource(s).

Fixes #13925
2025-08-21 21:52:31 +02:00
cui bff4dddb12 resources/page: Use reflect.TypeFor 2025-08-20 13:20:14 +02:00
dependabot[bot] 885cd299b7 build(deps): bump github.com/tdewolff/minify/v2 from 2.23.8 to 2.23.11
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.23.8 to 2.23.11.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.23.8...v2.23.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-20 11:42:54 +02:00
Bjørn Erik Pedersen 80e973ea5c Remove test with deprecated path usage
This now creates a warning and flaky CI tests.
2025-08-20 11:36:10 +02:00
dependabot[bot] debf3c559a build(deps): bump github.com/olekukonko/tablewriter from 1.0.8 to 1.0.9
Bumps [github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter) from 1.0.8 to 1.0.9.
- [Commits](https://github.com/olekukonko/tablewriter/compare/v1.0.8...v1.0.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-20 11:29:31 +02:00
Bjørn Erik Pedersen 01b0eda96c Update README.md 2025-08-16 10:51:26 +02:00
Bjørn Erik Pedersen 87e100e61f Fix rebuild when deleting a content adapter file 2025-08-15 17:32:42 +02:00
dependabot[bot] 1649f3126f build(deps): bump google.golang.org/api from 0.237.0 to 0.247.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.237.0 to 0.247.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.237.0...v0.247.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-version: 0.247.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-15 15:26:53 +02:00
dependabot[bot] ccd6a4b71e build(deps): bump github.com/aws/aws-sdk-go-v2 from 1.36.4 to 1.38.0
Bumps [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) from 1.36.4 to 1.38.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.36.4...v1.38.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-15 14:51:29 +02:00
dependabot[bot] 6dc1a1752b build(deps): bump golang.org/x/tools from 0.35.0 to 0.36.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.35.0 to 0.36.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.35.0...v0.36.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-15 14:02:06 +02:00
dependabot[bot] 806d4848c3 build(deps): bump github.com/bep/simplecobra from 0.6.0 to 0.6.1
Bumps [github.com/bep/simplecobra](https://github.com/bep/simplecobra) from 0.6.0 to 0.6.1.
- [Release notes](https://github.com/bep/simplecobra/releases)
- [Commits](https://github.com/bep/simplecobra/compare/v0.6.0...v0.6.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-15 14:01:33 +02:00
Nigel van Keulen 348aae91e8 tpl/strings: Remove unnecessary error check 2025-08-15 13:43:16 +02:00
Joe Mooring 61482cfab6 markup/goldmark: Apply Hugo Goldmark Extras when rendering TOC
Closes #12605
2025-08-14 22:39:14 +02:00
dependabot[bot] 04ee1b9784 build(deps): bump golang.org/x/mod from 0.25.0 to 0.27.0
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.25.0 to 0.27.0.
- [Commits](https://github.com/golang/mod/compare/v0.25.0...v0.27.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-13 11:29:34 +02:00
dependabot[bot] 7a86fe9905 build(deps): bump github.com/yuin/goldmark from 1.7.12 to 1.7.13
Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.7.12 to 1.7.13.
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.7.12...v1.7.13)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-13 11:28:53 +02:00
Joe Mooring 5fdcc09062 markup/goldmark: Sanitize TOC heading titles
Fixes #13401
2025-08-13 11:23:30 +02:00
dependabot[bot] f5245a7d5f build(deps): bump golang.org/x/image from 0.28.0 to 0.30.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.28.0 to 0.30.0.
- [Commits](https://github.com/golang/image/compare/v0.28.0...v0.30.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 14:46:29 +02:00
Joe Mooring 5029676aca deps: Upgrade github.com/niklasfasching/go-org v1.8.0 => v1.9.1
Fixes #13846
2025-08-11 10:54:00 +02:00
n1xx1 2216028620 Add a key to the partialCached deadlock prevention
Fixes #13889
2025-08-07 11:17:14 +02:00
n1xx1 ecc3dd1f53 transform: Add support for "format" option in transform.Unmarshal
Fixes #13887
2025-08-05 22:19:51 +02:00
Bjørn Erik Pedersen de4a7f1e04 Skip flakey test on CI 2025-08-05 19:45:46 +02:00
hugoreleaser 3aa22b0942 releaser: Prepare repository for 0.149.0-DEV
[ci skip]
2025-07-27 12:59:26 +00:00
hugoreleaser 40c3d8233d releaser: Bump versions for release of 0.148.2
[ci skip]
2025-07-27 12:43:24 +00:00
Bjørn Erik Pedersen 7ff5ec734c tpl: Add test for recent template selection regression
Closes #13868

Co-authored-by: Joe Mooring <joe.mooring@veriphor.com>
2025-07-27 14:39:39 +02:00
Bjørn Erik Pedersen 3937ab24d0 Revert "hugolib: Honor implicit "page" type during template selection"
This reverts commit cfc8d315b4.

See #13868
2025-07-27 14:39:39 +02:00
Bjørn Erik Pedersen 9c57af1351 Fix regression with hyphenated codeblock templates, e.g. render-codeblock-go-html-template.html
Fixes #13864

Co-authored-by: Joe Mooring <joe.mooring@veriphor.com>
2025-07-27 14:39:39 +02:00
Bjørn Erik Pedersen d240a705d6 commands: Avoid full browser refresh on simple CSS changes 2025-07-27 14:39:26 +02:00
hugoreleaser bbcc2a7973 releaser: Prepare repository for 0.149.0-DEV
[ci skip]
2025-07-11 13:13:44 +00:00
hugoreleaser 98ba786f2f releaser: Bump versions for release of 0.148.1
[ci skip]
2025-07-11 12:56:21 +00:00
Bjørn Erik Pedersen 6f42cfbc9b Fix assignment to entry in nil map
Fixes #13853
2025-07-11 14:21:53 +02:00
Bjørn Erik Pedersen a84beee429 deps: Downgrade github.com/niklasfasching/go-org v1.9.0 => v1.8.0
See #13846
2025-07-11 11:10:48 +02:00
hugoreleaser 65893efd8d releaser: Prepare repository for 0.149.0-DEV
[ci skip]
2025-07-08 13:51:56 +00:00
hugoreleaser c0d9bebacc releaser: Bump versions for release of 0.148.0
[ci skip]
2025-07-08 13:34:49 +00:00
Bjørn Erik Pedersen 3e2f1cdfdb Add Ancestors (plural) method to GitInfo, rename Ancestor field to Parent
Fixes #13839
2025-07-08 15:08:09 +02:00
dependabot[bot] 0a5b870281 build(deps): bump github.com/evanw/esbuild from 0.25.5 to 0.25.6
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.25.5 to 0.25.6.
- [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.25.5...v0.25.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-08 12:01:51 +02:00
Bjørn Erik Pedersen bba6996e15 Allow creating home pages from content adapters
* Allow "" (empty string) or "/" to represent the home page path.
* Be a little more lenient about path validation.
2025-07-07 18:41:45 +02:00
dependabot[bot] 94e2c276a8 build(deps): bump github.com/olekukonko/tablewriter from 1.0.7 to 1.0.8
Bumps [github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter) from 1.0.7 to 1.0.8.
- [Commits](https://github.com/olekukonko/tablewriter/compare/v1.0.7...v1.0.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-07 14:59:20 +02:00
Bjørn Erik Pedersen 90d397b142 Remove the internal GitInfo type and make Page.GitInf() return a pointer
See #5693
2025-07-07 14:49:27 +02:00
Jens Broekens 61e6c730dd source: Expose Ancestor in GitInfo
Also updates docs and bumps bep/gitmap to v1.7.0

Closes #5693

Co-authored-by: bep <bjorn.erik.pedersen@gmail.com>
2025-07-07 14:49:27 +02:00
Bjørn Erik Pedersen e4f6b9eef8 Merge commit 'bb147f91ee9078e6a55e8c32ab4b2e5dbc5cee45' 2025-07-07 13:17:48 +02:00
Bjørn Erik Pedersen bb147f91ee Squashed 'docs/' changes from b654fcba0..60dd993a6
60dd993a6 content: Update GitHub Pages workflow
6247e3590 content: Fix typo
a22255b0b content: Update version references
21afec198 content: Update version reference
6226532c8 content: Update version reference
80d26621a content: Update version reference
cda7135b5 content: Fix typo
70c509e8a content: Fix typo
136b20310 content: Change render hook example
038246c2f content: Fix accidental removal
441375fa4 netlify: Hugo 0.147.9
4a620931c content: Note that config/env/cascade.xxx is not supported
6d3a7587f config: Ignore front matter param override warnings
623b48624 content: Set searchable param to false for content/en/documentation.md
d1b2aad69 content: Fix Codeberg deployment article
5e3d849b8 search: Use front matter parameter to control search indexing
b4f90e39d content: Note that PATH segments must exclude project directory
a81406e26 content: Create aliases for deleted template pages
7c3adc9ad content: Change order of template types
e093eb036 content: Remove admonition from template types page
0015e7a9b content: Update to align with v0.146.0 template system (phase 1)
cf986240d content: Update templates.Defer note
ab51fdcc7 content: Use consistent templates.Defer examples
b4f8e492f content: Fix formatting
62aa94add content: Fix typo
83747f36b content: Update FAQs
fc644d894 Update netlify.toml
4151f3a5b content: Fix typo
9452bca92 content: Update description ToMath strict option
982b863c3 content: Add instructions about deploying to root of user's Codeberg Pages
fc7cd2878 theme: Generate QR code once per page
784891b87 content: Update links to the CommonMark specification
741113fb5 content: Fix incorrect link to Shortcodes tutorial
c4962f1ac content: Update transform.ToMath
ad7b87223 Update netlify.toml
2d64e57ae theme: Remove bullet from new-in badge
55a832f8e Update ToMath.md
e1e624f49 content: Document transform.ToMath strict mode
2df6218f8 netlify: Hugo 0.147.6
0259b88f1 Revert "theme: Display date on news items from content adapter"
43b35e56a theme: Adjust admonition dark mode colors
2f15e9c34 theme: Add link to Mastodon
d99e979d4 theme: Display date on news items from content adapter
6c93dac97 content: Clarify the AddResource content value
49006468e content: Describe Codeberg's Forgejo Actions
be80c3b82 content: Update css.TailwindCSS opts
b3ba4329c theme: Update css.TailwindCSS opts and remove tailwind.config.js
902e360b0 content: Update stylesheet link for katex.js
8b9e1ba3e content: Update stylesheet link for katex.js
7dae3b0e8 content: Update TailwindCSS instructions
cb093d210 Update netlify.toml
577d6fbb1 content: Remove GitCMS for now
8352af6cd Updates for v0.147.4
2240f07a5 content: Fix typo
677b3fed3 Correct openapi3.Unmarshal example
580477aa5 content: Fix typo
0dc43b481 content: Add GitCMS to commercial front ends list
ac4179f2b content: Fix relref shortcode examples
a699d267b Fix typos
7e28c4380 Make sponsor logo responsive
8e05f0425 Update MaxInt64.md
f715265d3 content: Document math.MaxInt64
0d57ff7d4 Update netlify.toml
d210e3476 theme: Adjust sponsor
e73444b43 content: Clarify page title values
fd4c292b8 content: Fix links to Pandoc
dd074dc67 content: Bump version in GitHub Pages example
fe5537bb2 content: Describe the pandoc format
10834529e theme: Remove duplicate meta element
838706d6e theme: More changes related to new template system
b0b6b1e69 Update new-templatesystem-overview.md
e719d2360 Update new-templatesystem-overview.md
ec2bbd395 Update new-templatesystem-overview.md
53319a681 Add a one pager about the new template system
e17416c05 Fix the spelling issue on the Host on Render description
e8e1c82aa content: Document range-over-int introduced in v0.123.0
bc478f98f content: Fix editing errors
606547b48 Update netlify.toml
e224f3f82 content: Fix formatting
24e4acc41 content: Fix typo
1e773f34b content: Fix typo
5ef94a725 content: Document alternative to shuffle for large collections
7fe42d76c content: Fix sample code formatting
5d601448f content: Update mathematical markup guidance
63b4fe2c0 content: Update version references
722da91f1 content: Fix GO_VERSION in Netlify hosting example
8cc589c3a theme: Add current Hugo version to the header
37aa24ac5 netlify: Hugo 0.147.1
a80b25e24 content: Remove dated new-in
89dcf0f92 content: Fix path formatting
1a5191d78 content: Update transformation examples
3327065ab Update Text.md
557c10dd2 content: Miscellaneous updates for v0.147.0
0dae1a185 netlify: Hugo 0.147.0
9b44821c3 Merge branch 'tempv0.147.0'
f1481e8c3 content: Update GitLab Pages workflow example
2e61a0360 content: Update content format descriptions
fb42d7c2a images: Add option for vertical alignment to images.Text

git-subtree-dir: docs
git-subtree-split: 60dd993a65
2025-07-07 13:16:31 +02:00
Ruslan Semagin 266d46dccc config: Increase test coverage 2025-07-07 11:27:34 +02:00
dependabot[bot] e77b2ad8fd build(deps): bump github.com/niklasfasching/go-org from 1.8.0 to 1.9.0
Bumps [github.com/niklasfasching/go-org](https://github.com/niklasfasching/go-org) from 1.8.0 to 1.9.0.
- [Commits](https://github.com/niklasfasching/go-org/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: github.com/niklasfasching/go-org
  dependency-version: 1.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-07 11:23:08 +02:00
dependabot[bot] 9487acf6ab build(deps): bump github.com/alecthomas/chroma/v2 from 2.18.0 to 2.19.0
Bumps [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) from 2.18.0 to 2.19.0.
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.18.0...v2.19.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-07 11:22:29 +02:00
Joe Mooring 84b31721bf markup/goldmark: Change link and image render hook enablement to enums
Closes #13535
2025-07-07 11:19:34 +02:00
Bjørn Erik Pedersen b8ba33ca95 Fix some uglyURLs issues for home, section and taxonomy kind (note)
Fixes #4428
Fixes #7497

Co-authored-by: Joe Mooring <joe.mooring@veriphor.com>
2025-07-05 18:11:28 +02:00
Bjørn Erik Pedersen f967212b72 Fix branch paths when OutputFormat.Path is configured (note)
Fixes #13829

Co-authored-by: Joe Mooring <joe.mooring@veriphor.com>
2025-07-05 18:11:28 +02:00
Joe Mooring 1b4c423667 resources/page: Allow full datetime prefix in filenames
Allow YYYY-MM-DD-HH-MM-SS prefix when extracting date from file names.
This is in addition to the YYYY-MM-DD prefix that is already supported.

Closes #13830
2025-07-05 11:26:35 +02:00
dependabot[bot] 1e9a0b93e3 build(deps): bump golang.org/x/tools from 0.32.0 to 0.34.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.32.0 to 0.34.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.32.0...v0.34.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-03 10:46:37 +02:00
Joe Mooring cfc8d315b4 hugolib: Honor implicit "page" type during template selection
Closes #13826
2025-07-02 13:43:40 +02:00
David Jones dd6e2c8724 deploy: walkLocal worker pool for performance 2025-06-29 16:41:56 +02:00
hugoreleaser 762417617c releaser: Prepare repository for 0.148.0-DEV
[ci skip]
2025-06-23 08:38:21 +00:00
hugoreleaser 29bdbde19c releaser: Bump versions for release of 0.147.9
[ci skip]
2025-06-23 08:22:20 +00:00
Bjørn Erik Pedersen 6a4a3ab8f8 Remove WARN with false negatives
Fixes #13806
2025-06-22 16:55:43 +02:00
Bjørn Erik Pedersen 36f6f987a9 resources/page: Make sure a map is always initialized
Fixes #13810
2025-06-21 14:38:06 +02:00
Joe Mooring 18a9ca7d7a tpl/tplimpl: Copy embedded HTML table render hook to each output format
Closes #13351
2025-06-21 14:37:47 +02:00
Joe Mooring b6c8dfa9dc tpl/tplimpl: Change resources.GetRemote errors to suppressible warnings
Closes #13803
2025-06-17 07:35:14 -07:00
dependabot[bot] 621ea42f3c build(deps): bump google.golang.org/api from 0.221.0 to 0.237.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.221.0 to 0.237.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.221.0...v0.237.0)

---
updated-dependencies:
- dependency-name: google.golang.org/api
  dependency-version: 0.237.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-13 13:53:25 +02:00
Bjørn Erik Pedersen 4ef5720141 hugolib: Remove test for deprecated future 2025-06-13 12:17:06 +02:00
dependabot[bot] 34e83789f7 build(deps): bump github.com/aws/aws-sdk-go-v2 from 1.36.1 to 1.36.4
Bumps [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) from 1.36.1 to 1.36.4.
- [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.36.1...v1.36.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-13 11:25:11 +02:00
dependabot[bot] 4d3ebe4d21 build(deps): bump golang.org/x/image from 0.27.0 to 0.28.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.27.0 to 0.28.0.
- [Commits](https://github.com/golang/image/compare/v0.27.0...v0.28.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-12 14:17:16 +02:00
Joe Mooring b5c0383bda deps: Upgrade github.com/spf13/cast v1.8.0 => v1.9.2 2025-06-12 10:07:09 +02:00
Joe Mooring 4217fee4b0 common/terminal: Enable color output on windows
Closes #8209
2025-06-09 11:44:13 +02:00
hugoreleaser fad57964aa releaser: Prepare repository for 0.148.0-DEV
[ci skip]
2025-06-07 13:13:56 +00:00
hugoreleaser 10da2bd765 releaser: Bump versions for release of 0.147.8
[ci skip]
2025-06-07 12:59:52 +00:00
Joe Mooring 01241d5dc9 hugolib: Emit ignorable warning when home page is a leaf bundle
Closes #13538
2025-06-07 13:02:28 +02:00
Bjørn Erik Pedersen 8e61f1fe12 dockerfile: Update Alpine
Closes #13783
2025-06-06 08:16:30 +02:00
Bjørn Erik Pedersen f37412a575 dockerfile: Update Go version
FIxes #13780
2025-06-04 16:52:17 +02:00
dependabot[bot] 21a4a9acd7 build(deps): bump github.com/evanw/esbuild from 0.25.3 to 0.25.5
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.25.3 to 0.25.5.
- [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.25.3...v0.25.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-03 11:27:57 +02:00
dependabot[bot] 7a4a4790e5 build(deps): bump github.com/niklasfasching/go-org from 1.7.0 to 1.8.0
Bumps [github.com/niklasfasching/go-org](https://github.com/niklasfasching/go-org) from 1.7.0 to 1.8.0.
- [Commits](https://github.com/niklasfasching/go-org/compare/v1.7.0...v1.8.0)

---
updated-dependencies:
- dependency-name: github.com/niklasfasching/go-org
  dependency-version: 1.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-03 11:26:51 +02:00
dependabot[bot] 54065b7ef8 build(deps): bump golang.org/x/net from 0.39.0 to 0.40.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.39.0 to 0.40.0.
- [Commits](https://github.com/golang/net/compare/v0.39.0...v0.40.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-03 09:01:37 +02:00
dependabot[bot] e333836f49 build(deps): bump github.com/yuin/goldmark from 1.7.11 to 1.7.12
Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.7.11 to 1.7.12.
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.7.11...v1.7.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-02 18:18:44 +02:00
dependabot[bot] cc7bfeea32 build(deps): bump github.com/tdewolff/minify/v2 from 2.23.5 to 2.23.8
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.23.5 to 2.23.8.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.23.5...v2.23.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-02 18:18:24 +02:00
Bjørn Erik Pedersen 32eb1a8ad4 all: Replace _build with build in tests
_build is deprecated and now shows up as warning.
2025-06-02 16:35:37 +02:00
hugoreleaser 32af02cd3e releaser: Prepare repository for 0.148.0-DEV
[ci skip]
2025-05-31 12:55:24 +00:00
hugoreleaser 189453612e releaser: Bump versions for release of 0.147.7
[ci skip]
2025-05-31 12:41:12 +00:00
Bjørn Erik Pedersen 5273a884d4 Fix language handling in shortcode templates
Fixes #13767
2025-05-31 13:57:00 +02:00
Bjørn Erik Pedersen 6334948515 Handle KaTeX warnings (#13760)
Co-authored-by: Joe Mooring <joe.mooring@veriphor.com>

Fixes #13735
2025-05-30 20:57:54 +02:00
dependabot[bot] 75259636c8 build(deps): bump golang.org/x/image from 0.26.0 to 0.27.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.26.0 to 0.27.0.
- [Commits](https://github.com/golang/image/compare/v0.26.0...v0.27.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-30 15:13:21 +02:00
dependabot[bot] 0df9f3510f build(deps): bump golang.org/x/text from 0.24.0 to 0.25.0
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.24.0 to 0.25.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.24.0...v0.25.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-30 12:50:09 +02:00
dependabot[bot] 302e6a726b build(deps): bump github.com/spf13/cast from 1.7.1 to 1.8.0
Bumps [github.com/spf13/cast](https://github.com/spf13/cast) from 1.7.1 to 1.8.0.
- [Release notes](https://github.com/spf13/cast/releases)
- [Commits](https://github.com/spf13/cast/compare/v1.7.1...v1.8.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/cast
  dependency-version: 1.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-30 08:34:09 +02:00
dependabot[bot] 202fe0d45c build(deps): bump github.com/alecthomas/chroma/v2 from 2.17.2 to 2.18.0
Bumps [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) from 2.17.2 to 2.18.0.
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.17.2...v2.18.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-29 22:12:58 +02:00
Joe Mooring 843ffeb48d resources/page: Respect disablePathToLower for permalink tokens
Fixes #13755
2025-05-29 22:10:03 +02:00
Ruslan Semagin bff5d19121 common/collections: Increase test coverage 2025-05-29 09:17:13 +02:00
Ruslan Semagin da370d30de parser/pageparser: Add coverage for all IsX methods of Item
Added tests for all boolean methods on Item, increasing overall code coverage.
2025-05-28 21:53:17 +02:00
Bjørn Erik Pedersen 6bd328c584 resources: Remove unused interface 2025-05-28 19:32:28 +02:00
Bjørn Erik Pedersen 766a2e7868 Make sure that unreferenced but changed bundle resources gets republished
Fixes #13748
2025-05-28 19:32:28 +02:00
Bjørn Erik Pedersen 13e1617557 deps: Upgrade github.com/olekukonko/tablewriter v0.0.5 => v1.0.7 2025-05-28 18:07:14 +02:00
hugoreleaser 463e440c7a releaser: Prepare repository for 0.148.0-DEV
[ci skip]
2025-05-27 11:31:53 +00:00
hugoreleaser 0a5fd8ebb8 releaser: Bump versions for release of 0.147.6
[ci skip]
2025-05-27 11:17:16 +00:00
Andreas Deininger e57dcd3795 Improve warning message on superfluous prefix when using function 'partials.Include' 2025-05-27 12:16:26 +02:00
Bjørn Erik Pedersen eaf5ace30d Fix recent regression with cascading of params to content adapters
Fixes #13743
2025-05-26 21:26:19 +02:00
Bjørn Erik Pedersen 9ad26b69ad Fix it so e.g. de in layouts/_shortcodes/de.html is not interpreted as a language code
Fixes #13740
2025-05-26 20:26:56 +02:00
Bjørn Erik Pedersen f47193669d commands: Make sure the browser gets refreshed on changes when --disableFastRender is set
Fixes #13727
2025-05-23 19:46:16 +02:00
Joe Mooring 013c8cfb25 tpl/transform: Expose the KaTeX strict option
Closes #13729
2025-05-23 19:21:38 +02:00
hugoreleaser e25db38467 releaser: Prepare repository for 0.148.0-DEV
[ci skip]
2025-05-22 11:51:47 +00:00
hugoreleaser 7766fc6241 releaser: Bump versions for release of 0.147.5
[ci skip]
2025-05-22 11:37:19 +00:00
Bjørn Erik Pedersen 4a48facef4 Merge branch 'release-0.147.4' 2025-05-22 13:19:34 +02:00
Bjørn Erik Pedersen 0c7b1a3f26 Fix live reload when editing inline partials
Fixes #13723
2025-05-22 13:15:41 +02:00
dependabot[bot] 970b887ba1 build(deps): bump github.com/tdewolff/minify/v2 from 2.20.37 to 2.23.5
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.20.37 to 2.23.5.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.20.37...v2.23.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-20 14:49:06 +02:00
hugoreleaser b9b95e5aec releaser: Prepare repository for 0.148.0-DEV
[ci skip]
2025-05-20 10:55:43 +00:00
hugoreleaser 84c8426f32 releaser: Bump versions for release of 0.147.4
[ci skip]
2025-05-20 10:41:19 +00:00
Bjørn Erik Pedersen a03a245f0c Fix it so css.TailwindCSS inlineImports options isn't always enabled
To avoid breaking existing setup and to make a better default option, the option is now `disableInlineImports` (default false).
Fixes #13719
2025-05-19 19:36:48 +02:00
Bjørn Erik Pedersen 5a81a3a4cf tpl: Add a test case
I'm not able to reproduce this, but leaving it in.

Closes #13699
2025-05-18 12:48:24 +02:00
Bjørn Erik Pedersen 61317821e4 tpl: Narrow down the usage of plain text shortcodes when rendering HTML
After this commit, if you want to resolve `layouts/_shortcodes/myshortcode.txt` when rendering HTML content, you need to use the `{{%` shortcode delimiter:

```
{{% myshortcode %}}
```

This should be what people would do anyway, but we have also as part of this improved the error message to inform about what needs to be done.

Note that this is not relevant for partials.

Fixes #13698
2025-05-18 12:48:24 +02:00
Bjørn Erik Pedersen 6142bc701c tpl: Fix theme overrides when theme has old layout setup (e.g. _default)
Fixes #13715
2025-05-18 12:48:24 +02:00
hugoreleaser e6574cf7a7 releaser: Prepare repository for 0.148.0-DEV
[ci skip]
2025-05-12 12:40:04 +00:00
hugoreleaser 05417512bd releaser: Bump versions for release of 0.147.3
[ci skip]
2025-05-12 12:25:03 +00:00
Joe Mooring 81426998b8 tpl/tplimpl: Change calls to simple versions of embedded shortcodes
Closes #13700
2025-05-12 14:19:11 +02:00
Bjørn Erik Pedersen 6def5a1ba9 Update README.md 2025-05-12 12:10:27 +02:00
Bjørn Erik Pedersen bc98e7a80d config: Fix env override of slices
Fixes #13707
2025-05-11 19:24:24 +02:00
Bjørn Erik Pedersen c745a3e108 Fix/implement cascade for content adapters
Fixes #13692
2025-05-10 15:12:24 +02:00
Joe Mooring 9d1d8c8899 commands: Fix description of new theme commands
Fixes #13701
2025-05-09 20:03:21 +02:00
Joe Mooring 84d7a108e8 tpl/tplimpl: Fix vimeo shortcode test to accommodate API changes
Fixes #13687
2025-05-09 10:02:00 -07:00
Joe Mooring 325a0dba63 tpl/math: Add MaxInt64 function
Closes #13693
2025-05-07 19:40:29 +02:00
hugoreleaser d70f828e2b releaser: Prepare repository for 0.148.0-DEV
[ci skip]
2025-05-06 11:33:36 +00:00
hugoreleaser c7feb15d10 releaser: Bump versions for release of 0.147.2
[ci skip]
2025-05-06 11:18:55 +00:00
Bjørn Erik Pedersen 363ab48a24 Fix handling of "outputs" from content adapter pages
Fixes #13689
2025-05-06 11:40:46 +02:00
Bjørn Erik Pedersen 80f0595311 tpl: Fix case issue in templates.Exists
Fixes #13684
2025-05-05 11:23:52 +02:00
Bjørn Erik Pedersen b39b249623 config: Add some more merge tests
See #13681
2025-05-02 11:15:38 +02:00
hugoreleaser d799c045fd releaser: Prepare repository for 0.148.0-DEV
[ci skip]
2025-05-01 14:04:58 +00:00
hugoreleaser 95666fc5a4 releaser: Bump versions for release of 0.147.1
[ci skip]
2025-05-01 13:50:04 +00:00
dependabot[bot] 620fc87b56 build(deps): bump github.com/alecthomas/chroma/v2 from 2.17.0 to 2.17.2
Bumps [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) from 2.17.0 to 2.17.2.
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.17.0...v2.17.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-01 15:44:26 +02:00
dependabot[bot] 8b2124e7c3 build(deps): bump github.com/getkin/kin-openapi from 0.131.0 to 0.132.0
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.131.0 to 0.132.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.131.0...v0.132.0)

---
updated-dependencies:
- dependency-name: github.com/getkin/kin-openapi
  dependency-version: 0.132.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-01 13:19:22 +02:00
Bjørn Erik Pedersen 5fec7829b1 tpl: Add some more test cases
See #13672
See #13668
2025-05-01 12:55:11 +02:00
dependabot[bot] 927d1ec6c1 build(deps): bump github.com/yuin/goldmark from 1.7.10 to 1.7.11
Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.7.10 to 1.7.11.
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.7.10...v1.7.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-30 22:09:10 +02:00
Bjørn Erik Pedersen be93d5218b tpl: Fix overlapping layout sections
Fixes #13672
2025-04-30 21:04:03 +02:00
Bjørn Erik Pedersen a1cb15e1cf Fix it so the owning taxonomy gets rerendered in server when new tags are added
Updates #13648
2025-04-28 21:42:16 +02:00
Joe Mooring 673a4d00eb commands/server: Display correct multihost language in console
Fixes #12564
2025-04-28 20:00:15 +02:00
Joe Mooring 31db7edf6d hugolib: Use new build key in content placeholder
Fixes #13655
2025-04-27 13:18:43 +02:00
hugoreleaser 5857b60cbc releaser: Prepare repository for 0.148.0-DEV
[ci skip]
2025-04-25 15:41:01 +00:00
hugoreleaser 7d0039b86d releaser: Bump versions for release of 0.147.0
[ci skip]
2025-04-25 15:26:28 +00:00
Bjørn Erik Pedersen 07983e04e2 tpl: Fix it so we always prefer internal codeblock rendering over render-codeblock-foo.html and similar
Fixes #13651
2025-04-25 10:51:33 +02:00
Joe Mooring 5c491409d3 tpl/tplimpl: Fix allowFullScreen option in Vimeo and YouTube shortcodes
Closes #13650
2025-04-24 14:14:46 -07:00
Joe Mooring 75b219db89 create/skeletons: Adjust template names in theme skeleton 2025-04-24 19:02:11 +02:00
Bjørn Erik Pedersen ad4f63c92f tpl: Remove some unreached code branches 2025-04-24 14:27:59 +02:00
Bjørn Erik Pedersen 53202314ab images: Add some test cases for aligny on images.Text
See #13414
2025-04-24 14:09:13 +02:00
Pranshu Gaba 2fce0bac03 images: Add option for vertical alignment to images.Text
Add option ``aligny`` to specify the vertical alignment of the text
with respect to the ``y`` offset from the top of the image. Possible
values of ``aligny`` are ``top`` (default), ``center``, and ``bottom``.

The height of the block of text is measured from the top of the first
line to the baseline of the last line.

- ``top``: (Current behaviour) The top of the first line of the block of
  text is at an offset of ``y`` from the top of the image.

- ``center``: The vertical center of the block of text is at an offset of
  ``y`` from the top of the image.

- ``bottom``: The baseline of the last line of the text is at an offset
  of ``y`` from the top of the image.

Resolves #13414
2025-04-24 14:09:13 +02:00
Bjørn Erik Pedersen 179aea11ac config: Fix _merge issue when key doesn't exist on the left side
Fixes #13643
Fixes #13646
2025-04-24 13:56:27 +02:00
Bjørn Erik Pedersen b3d87dd0fd Squashed 'docs/' changes from dc7a9ae12..b654fcba0
b654fcba0 content: Fix links
d44357418 content: Update GitLab Pages workflow example
33968c7e2 content: Update Netlify configuration file
a6d0c8c50 content: Update GitHub Pages workflow example
d1aabfa36 content: Fix broken link
7b50139a6 content: Miscellaneous edits
a30e2c189 Menus: add forgotten link target
5c2aa88b4 content: Updates for v0.146.7
114413c18 netlify: Hugo 0.146.7
67e9261b1 netlify: Hugo 0.146.6
efa040229 content: Update templates/embedded.md
b8f888c76 theme: Rename internal templates from partials/ to _partials/
727178cbb content: Fix broken anchor links
0f12708f1 Fix typo
380b1c163 Update Current.md
8b500f3e5 netlify: Hugo 0.146.5
e3d6b6fad netlify: Hugo 0.146.4
ac1b92713 content: Fix text formatting in templates/partial.md
719329530 content: Clarify usage of template function
a95eca524 theme: Misc adjustments for the themes site
8e6c26067 Add package.hugo.json
9691007fb netlify: Hugo 0.146.3
ec08acc59 netlify: Hugo 0.146.2
8f320a0b6 netlify: Hugo 0.146.1
d5e6cb618 content: Remove expired new-in badges
b5779d7fc content: Update templates.Current function
5df1229d5 theme: Move templates to new structure
a7a6a614d theme: Remove accidentally added template
195b368e8 content: Miscellaneous updates related to v0.146.0
0a906ad49 netlify: Hugo 0.146.0

git-subtree-dir: docs
git-subtree-split: b654fcba0d
2025-04-24 10:23:16 +02:00
Bjørn Erik Pedersen 61a286595e Merge commit 'b3d87dd0fd746f07f9afa6e6a2969aea41da6a38' 2025-04-24 10:23:16 +02:00
Christian Oliff 6a0e04241a all: Fix typos 2025-04-24 10:19:17 +02:00
dependabot[bot] 1bd7ac7ed9 build(deps): bump github.com/evanw/esbuild from 0.25.2 to 0.25.3
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.25.2 to 0.25.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.25.2...v0.25.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-24 10:15:48 +02:00
dependabot[bot] 41cb880f9a build(deps): bump github.com/alecthomas/chroma/v2 from 2.16.0 to 2.17.0
Bumps [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) from 2.16.0 to 2.17.0.
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.16.0...v2.17.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-24 10:11:50 +02:00
hugoreleaser df44ee1353 releaser: Prepare repository for 0.147.0-DEV
[ci skip]
2025-04-22 17:42:19 +00:00
hugoreleaser 1ad3d39dc4 releaser: Bump versions for release of 0.146.7
[ci skip]
2025-04-22 17:26:42 +00:00
Bjørn Erik Pedersen 496730840e Revert the breaking change from 0.146.0 with dots in content filenames
Closes #13632
2025-04-22 19:23:19 +02:00
Bjørn Erik Pedersen 6d69dc88a4 tpl: Fix indeterminate template lookup with templates with and without lang
Close #13636
2025-04-22 17:29:39 +02:00
Joe Mooring db72a1f075 parser/metadecoders: Add CSV targetType (map or slice) option to transform.Unmarshal
Closes #8859
2025-04-21 19:33:20 +02:00
dependabot[bot] ad787614e8 build(deps): bump github.com/yuin/goldmark-emoji from 1.0.5 to 1.0.6
Bumps [github.com/yuin/goldmark-emoji](https://github.com/yuin/goldmark-emoji) from 1.0.5 to 1.0.6.
- [Release notes](https://github.com/yuin/goldmark-emoji/releases)
- [Commits](https://github.com/yuin/goldmark-emoji/compare/v1.0.5...v1.0.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-21 15:18:02 +02:00
dependabot[bot] 9c65b9e88d build(deps): bump github.com/bep/imagemeta from 0.11.0 to 0.12.0
Bumps [github.com/bep/imagemeta](https://github.com/bep/imagemeta) from 0.11.0 to 0.12.0.
- [Release notes](https://github.com/bep/imagemeta/releases)
- [Commits](https://github.com/bep/imagemeta/compare/v0.11.0...v0.12.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-21 15:17:49 +02:00
Bjørn Erik Pedersen 4eb0e4286a tpl/collections: Fix where ... not in with empty slice
Fixes #13621
2025-04-21 15:17:20 +02:00
Bjørn Erik Pedersen 5e62cc6fce tpl: Fix layout fall back logic when layout is set in front matter but not found
Fixes #13630
2025-04-21 15:17:20 +02:00
Bjørn Erik Pedersen 1408c156d8 tpl: Detect and fail on infinite template recursion
Fixes #13627
2025-04-21 15:17:20 +02:00
dependabot[bot] be3b147860 build(deps): bump github.com/yuin/goldmark from 1.7.9 to 1.7.10
Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.7.9 to 1.7.10.
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.7.9...v1.7.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-21 11:56:44 +02:00
hugoreleaser 99fff2997d releaser: Prepare repository for 0.147.0-DEV
[ci skip]
2025-04-20 11:13:56 +00:00
hugoreleaser 1e0b058efe releaser: Bump versions for release of 0.146.6
[ci skip]
2025-04-20 10:58:40 +00:00
Bjørn Erik Pedersen 088cd2f996 tpl: Fix when layout specified in front matter and no match is found
Fixes #13628
2025-04-20 12:55:18 +02:00
broughtupsy a88b488181 Update watchtestscripts.sh 2025-04-16 08:28:19 +02:00
hugoreleaser d5a8c330cb releaser: Prepare repository for 0.147.0-DEV
[ci skip]
2025-04-15 18:09:42 +00:00
hugoreleaser 61328976e1 releaser: Bump versions for release of 0.146.5
[ci skip]
2025-04-15 17:54:38 +00:00
dependabot[bot] 64cf008880 build(deps): bump github.com/yuin/goldmark from 1.7.8 to 1.7.9
Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.7.8 to 1.7.9.
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.7.8...v1.7.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-15 14:33:56 +02:00
Bjørn Erik Pedersen d61b9fc605 tpl: Fix language handling in partials
We now use the same code path for all templates re this.

Fixes #13612
2025-04-15 11:25:54 +02:00
hugoreleaser 01667275d4 releaser: Prepare repository for 0.147.0-DEV
[ci skip]
2025-04-14 13:25:05 +00:00
hugoreleaser 985af1c097 releaser: Bump versions for release of 0.146.4
[ci skip]
2025-04-14 13:10:30 +00:00
Bjørn Erik Pedersen 65c94c7b23 tpl: Fix issue with partials without suffix
Fixes #13601
2025-04-14 14:38:22 +02:00
Bjørn Erik Pedersen e8e8ce10d2 tpl: Avoid panic on nonsensical return construct
Fixes #13600
2025-04-14 14:38:22 +02:00
Bjørn Erik Pedersen cf9e6904cc tpl: Fix the case for a shortcode in a nested folder only
Fixes #13605
2025-04-14 14:38:22 +02:00
Bjørn Erik Pedersen 8a2830f2dc tpl: Add proper file context to template parse errors
Fixes #13604
2025-04-14 14:38:22 +02:00
Bjørn Erik Pedersen 1e0287f472 tpl: Make {{ template "partials/foo.html" . }} work in older setups
Fixes #13599
2025-04-14 14:38:22 +02:00
hugoreleaser 915ba3f7f0 releaser: Prepare repository for 0.147.0-DEV
[ci skip]
2025-04-12 17:36:11 +00:00
hugoreleaser 05ef8b713a releaser: Bump versions for release of 0.146.3
[ci skip]
2025-04-12 17:21:50 +00:00
Bjørn Erik Pedersen 30b9c19c76 tpl: Make any layout set in front matter higher priority
Fixes #13541
2025-04-12 18:12:06 +02:00
Bjørn Erik Pedersen c8710625b7 tpl: Fix it so embedded render-codeblock-goat is used even if custom render-codeblock exists
Fixes #13595
2025-04-12 13:03:45 +02:00
hugoreleaser 53221f88ca releaser: Prepare repository for 0.147.0-DEV
[ci skip]
2025-04-11 17:39:58 +00:00
hugoreleaser ff3ab192c2 releaser: Bump versions for release of 0.146.2
[ci skip]
2025-04-11 17:25:13 +00:00
Bjørn Erik Pedersen d1c394442b tpl: Fix codeblock hook resolve issue
Fixes #13593
2025-04-11 18:59:42 +02:00
Bjørn Erik Pedersen 1074e01152 tpl: Fix legacy section mappings
Fixes #13584
2025-04-11 18:23:51 +02:00
Bjørn Erik Pedersen c19f1f2363 tpl: Resolve layouts/all.html for all html output formats
Fixes #13587
2025-04-11 18:23:51 +02:00
Bjørn Erik Pedersen 9221cbca49 tpl: Fix some baseof lookup issues
We were mistakingly using the templates (e.g. list.html) descriptor to resolve the base template and not the page,
which worked fine in most cases, but not all.

Fixes #13583
2025-04-11 18:23:51 +02:00
hugoreleaser e3e3f9ae17 releaser: Prepare repository for 0.147.0-DEV
[ci skip]
2025-04-10 16:59:48 +00:00
hugoreleaser acc4fe24be releaser: Bump versions for release of 0.146.1
[ci skip]
2025-04-10 16:44:46 +00:00
Bjørn Erik Pedersen 3b9f2a7ded tpl: Skip dot and temp files inside /layouts
Fixes #13579
2025-04-10 18:41:01 +02:00
hugoreleaser 648204b3f1 releaser: Prepare repository for 0.147.0-DEV
[ci skip]
2025-04-10 15:13:26 +00:00
hugoreleaser 5d1b9d3985 releaser: Bump versions for release of 0.146.0
[ci skip]
2025-04-10 14:57:56 +00:00
Bjørn Erik Pedersen 383dd82f95 tpl: Warn and skip non-hook templates inside /layouts/_markup
Fixes #13577
2025-04-10 16:54:05 +02:00
Bjørn Erik Pedersen 653f1c1d46 Merge commit '5be51ac3db225d5df501ed1fa1499c41d97dbf65' 2025-04-10 13:04:51 +02:00
Bjørn Erik Pedersen 5be51ac3db Squashed 'docs/' changes from d1a251933..dc7a9ae12
dc7a9ae12 content: Update JS options
07d3d8803 npm: Use tilde ranges for versions
e06362a13 Minor grammar fixes
c42db0838 content: Algolia DocSearch clarification
d67412b82 deps: Upgrade to TailwindCSS 4.1.0
da1fb12d3 theme: Update Lato font path for images.Text example
140fa3bb9 Update introduction.md
7b1fcca27 content: Fix links to embedded template source
908a55532 theme: Improve dark mode syntax highlighting background
d830e5962 Update XxHash.md
807be5dbf Update Defer.md
4b56693f6 content: Update GitHub Pages sample workflow
57b766fba Update TailwindCSS.md
64982ab6a Update TailwindCSS.md
1b6e879c6 Update TailwindCSS.md
e67cbcdd2 content: Add caching to the GitHub Pages workflow example
b7ca3b07c Update index.md
14e1a3977 Update XxHash.md
980f344ae Update XxHash.md
6b815f03e theme: Remove aria-label attribute from links
1c00bbc45 theme: Update npm dependencies
533149bf9 theme: Include section pages in related content
2b6bda6bd content: Fix typos
92ce95cbb content: Miscellaneous edits
f12936681 theme: Add a simple LRU cache for search
339ca3388 Fix the previous broken commit
8537e59e0 Make it into a non-link
e13f17d29 Add Algolia logo/link to the search listing dialog
d3e09e886 content: Fix typo
7217f64b6 content: Miscellaneous edits
51aa1ae73 content: Improve some examples
8ebaa53f9 theme: Adjust shortcodes
898870438 theme: Hide anchor until mouse-over
c933ea237 content: More front matter cleanup
f26ca047a content: Miscellaneous edits
e272b2039 theme: Fix inline partial refs
c540e6d29 content: Replace note shortcode calls with blockquote alerts
aef899bc5 theme: Add title and QR code when printing
bd46ef626 theme: Implement blockquote render hook
ddefbefaa content: Improve contributing page
f41d28ee1 content: Adjust usage of whitespace removal with action delimiters
03315336d theme: Use full title in related content sidebar
4f4076364 misc: Document the front matter fields used on this site
2f78d7632 misc: Fix gitignore file
7de6dbab3 content: Fix front matter for several function pages
5d3542ea6 config: Disable tags taxonomy
68bc28d67 content: Add linkTitle to shortcode pages
5f32c92ed theme: Restore deduplication logic for related items
a943a4bb8 theme: Implement related content tooling
fd628be6e content: Replace calls to the code shortcode with fenced code blocks
b23c9a583 content: Fix position of new-in badges
ec056f251 content: Fix typo
245351c84 theme: Adjust spacing in highlighting theme examples
2fcd21ee5 content: Remove "related" array from function and method pages
71d8426ee theme: Create code block render hook
4cdde6649 theme: Adjust number of news items to display
34ab45261 content: Miscellaneous edits
b6cae5cbc content: Consolidate configuration documentation
727ef6f66 theme: Fix overflow issue for wide tables
c4f759e01 Add es2024
93cce62c8 Update support list of more recent targets with js.Build / esbuild
974d0655f Update hosting-on-codeberg.md
b3f0ed9ce content: Add hosting instructions for Codeberg Pages
8217c0900 content: Correct the SourceHut repository URL
a8cf3d28f content: Add hosting instructions for SourceHut Pages
8c059cbe1 theme: Use content adapter for news section
03938c600 Remove some old new-in
5c50a75e8 content: Fix typo
5cf89f2f6 theme: Re-enable banner gtag outbound link tracking
3c555d5f8 One more copy button
94bce999a Add some copy buttons
9e1cc0c2b Update PortableText.md
dd26ac49f Document transform.PortableText
5f632ab32 netlify: Hugo 0.145.0
59e057bb4 Update index.md
d07e07d6c Remove some unused home page front matter
d482657b7 Add footnote about alias to the build front matter key
f0629b77c content: Fix typo
d91c4cccf theme: Fix news items URLs
ca931cd1f theme: Restore RSS feed for news section
e6b870bc9 theme: Adjust copy-to-clipboard button
071851431 Update netlify.toml
e68431034 theme: Format layouts
aa3cd839a theme: Format assets
22ad3eee3 theme: Add some more space on the right for copy buttons
f4a19083a netlify: Hugo 0.144.1
974cb8795 theme: Remove Internet Explorer configs (#2929)
4a23a1f41 content: Fix typos
d49f15d03 theme: Get the scrollspy back working
720c7ff67 config: Evict getresource cache hourly
1f62ca97e theme: Hide the Turbo progress bar
f6449ace3 Move the dark class up to the html element
074cd1a07 content: Miscellaneous edits
e098a7716 content: Miscellaneous edits
8e1e104aa content: Updates for v0.144.0
18e1aa916 theme: Add anchor links via JS
ac3b5505c Close new-in
bfa4db6b8 netlify: Hugo 0.144.0
fd6e7feee Regen CLI docs
da9d1218a Regen docs helper
5de494ded Merge branch 'tempv0.144.0'
f683e6469 theme: Minor improvements to base template
ef8bf89d8 resources/page: Revise the new contentbasename permalinks tokens
901adb07b resources/page: Add :contentbasename and :contentbasenameorslug permalink tokens
0fca8ef25 all: Change shortcode usage and design to prevent invalid HTML
c41d76613 Update RegularPagesRecursive.md
e93574748 content: Correct return type for strings.Split
3d504abba Revert "content: Add ids to the Netlify steps"
c08c8e15e content: Add ids to the Netlify steps
5a3b470a2 theme: "move" the id from content to article
b878613aa theme: Add page kind as a CSS class to body
269657e8f content: Fix formatting error
664f6c92f config: Change image cache location
fa6b719b1 theme: Reduce ToC to level 2-4
bc16341ca theme: Render mathematical markup with transform.ToMath
14bf9dc70 theme: Add aria-label attribute to search fields
ed42af5b3 theme: Adjust search field and search activation
f042e4970 dev: Add prettier-plugin-void-html
f6ec83533 content: Fix inline shortcode example
0a74210e2 theme: Remove readfile shortcode
334ca06ac theme: Fix some dark mode accessibility as reported by Axe core
02626ff92 theme: aria-lbabel => aria-label
227b76ab7 Add Prettier and config
43ab22428 content: Bump minimum required Go version to 1.23.0
03e54683f modules: Add GOAUTH to module config
9f06a3b9c js/esbuild: Add drop option
ddcd99369 Merge commit 'a024bc7d76fcc5e49e8210f9b0896db9ef21861a'
733731253 helpers: Add Chroma styles to docs.yaml

git-subtree-dir: docs
git-subtree-split: dc7a9ae127
2025-04-10 13:02:49 +02:00
Bjørn Erik Pedersen 208a0de6c3 tpl: Add a partial lookup cache
````
                 │ stash.bench  │          perf-v146.bench           │
                 │    sec/op    │   sec/op     vs base               │
LookupPartial-10   248.00n ± 0%   14.75n ± 2%  -94.05% (p=0.002 n=6)

                 │ stash.bench │          perf-v146.bench          │
                 │    B/op     │   B/op     vs base                │
LookupPartial-10    48.00 ± 0%   0.00 ± 0%  -100.00% (p=0.002 n=6)

                 │ stash.bench │          perf-v146.bench           │
                 │  allocs/op  │ allocs/op   vs base                │
LookupPartial-10    3.000 ± 0%   0.000 ± 0%  -100.00% (p=0.002 n=6)
```

THe speedup above assumes reuse of the same partials over and over again, which I think is not uncommon.

This commits also adds some more lookup benchmarks. The current output of these on my MacBook looks decent:

```
BenchmarkLookupPagesLayout/Single_root-10                3031562               395.5 ns/op             0 B/op          0 allocs/op
BenchmarkLookupPagesLayout/Single_sub_folder-10          2515915               480.9 ns/op             0 B/op          0 allocs/op
BenchmarkLookupPartial-10                               84808112                14.13 ns/op            0 B/op          0 allocs/op
BenchmarkLookupShortcode/toplevelpage-10                 8111779               148.2 ns/op             0 B/op          0 allocs/op
BenchmarkLookupShortcode/nestedpage-10                   8088183               148.6 ns/op             0 B/op          0 allocs/op
```

Note that in the above the partial lookups are cahced, the others not (they are harder to cache because of the page path).

Closes #13571
2025-04-10 11:07:19 +02:00
dependabot[bot] 18d2d2f985 build(deps): bump github.com/fsnotify/fsnotify from 1.8.0 to 1.9.0
Bumps [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/fsnotify/fsnotify/releases)
- [Changelog](https://github.com/fsnotify/fsnotify/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fsnotify/fsnotify/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: github.com/fsnotify/fsnotify
  dependency-version: 1.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-10 10:51:46 +02:00
dependabot[bot] cd3e3499bd build(deps): bump golang.org/x/tools from 0.31.0 to 0.32.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.31.0 to 0.32.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.31.0...v0.32.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-10 10:51:20 +02:00
dependabot[bot] 8cd799aac9 build(deps): bump github.com/evanw/esbuild from 0.25.1 to 0.25.2
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.25.1 to 0.25.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.25.1...v0.25.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-10 10:51:07 +02:00
Bjørn Erik Pedersen d4c6dd16b1 tpl: Add templates.Current
This commit also

* Unexport all internal state in TemplateInfo.
* Make the dispatcher keys used for passing context.Context into uint8 from string to save memory allocations.

Co-authored-by: Joe Mooring <joe@mooring.com>

Updates #13571
2025-04-09 20:15:40 +02:00
dependabot[bot] af0602c343 build(deps): bump golang.org/x/image from 0.25.0 to 0.26.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.25.0 to 0.26.0.
- [Commits](https://github.com/golang/image/compare/v0.25.0...v0.26.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-09 13:07:03 +02:00
dependabot[bot] 650fe6cd01 build(deps): bump github.com/pelletier/go-toml/v2 from 2.2.3 to 2.2.4
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.2.3 to 2.2.4.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml)
- [Commits](https://github.com/pelletier/go-toml/compare/v2.2.3...v2.2.4)

---
updated-dependencies:
- dependency-name: github.com/pelletier/go-toml/v2
  dependency-version: 2.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-09 13:06:38 +02:00
dependabot[bot] d63491beb2 build(deps): bump github.com/alecthomas/chroma/v2 from 2.15.0 to 2.16.0
Bumps [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) from 2.15.0 to 2.16.0.
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.15.0...v2.16.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-09 13:05:13 +02:00
Joe Mooring 24ac6a9de9 commands/new: Improve theme creation
- Update the skeleton structure to match the new template system.
- Add a --format flag to the `hugo new theme` command to control the
  format of the site configuration and default archetype files.
- Remove theme.toml. This file's presence can be confusing for new
  users, and the README in the themes repository already has an example.
- Remove the LICENSE and README files from the skeleton. These files
  are not needed for a theme to work, and they can be added later by
  the user if desired.

Closes #13489
Closes #13544
2025-04-08 16:35:46 +02:00
dependabot[bot] e6e18e9122 build(deps): bump golang.org/x/net from 0.37.0 to 0.39.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.37.0 to 0.39.0.
- [Commits](https://github.com/golang/net/compare/v0.37.0...v0.39.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 12:14:30 +02:00
dependabot[bot] 43af3bc07d build(deps): bump github.com/bep/imagemeta from 0.10.0 to 0.11.0
Bumps [github.com/bep/imagemeta](https://github.com/bep/imagemeta) from 0.10.0 to 0.11.0.
- [Release notes](https://github.com/bep/imagemeta/releases)
- [Commits](https://github.com/bep/imagemeta/compare/v0.10.0...v0.11.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 12:02:20 +02:00
Joe Mooring 10b55439a8 deps: Upgrade github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.3.0 => v0.3.1 2025-04-07 21:33:06 +02:00
Joe Mooring 1e00842489 tpl/tplimpl: Update embedded pagination template 2025-04-07 21:29:53 +02:00
Bjørn Erik Pedersen 83cfdd78ca Reimplement and simplify Hugo's template system
See #13541 for details.

Fixes #13545
Fixes #13515
Closes #7964
Closes #13365
Closes #12988
Closes #4891
2025-04-06 20:59:41 +02:00
Bjørn Erik Pedersen 812ea0b325 config: Use the non-global logger for deprecations when possible
To prevent warnings leaking into parallel tests.
2025-04-03 13:43:54 +02:00
Joe Mooring 07cbe5701e tpl/time: Add time.In function
Closes #13548
2025-04-03 09:45:51 +02:00
Joe Mooring c15ebce2fd resources: Add option to silence dependency deprecation warnings
Closes #13530
2025-03-29 19:04:53 +01:00
Joe Mooring 6f14dbe24c tpl/tplimpl: Fix full screen option in vimeo and youtube shortcodes
Closes #13531

Co-authored-by: Stefan Ritter <60473875+gideonstar-git@users.noreply.github.com>
2025-03-27 12:32:00 +01:00
Bjørn Erik Pedersen 8d2379bcb3 common/hreflect: Replace the map/RWMutex method cache with sync.Map
It's much faster when running in parallel:

```
GetMethodByName-10        125.1n ± 6%   181.7n ± 7%  +45.30% (p=0.002 n=6)
GetMethodByNamePara-10   770.10n ± 1%   24.77n ± 9%  -96.78% (p=0.002 n=6)
```
2025-03-26 14:24:51 +01:00
dependabot[bot] 26d986fc0d build(deps): bump golang.org/x/tools from 0.30.0 to 0.31.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.30.0 to 0.31.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.30.0...v0.31.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-25 12:02:15 +01:00
dependabot[bot] ebc0693659 build(deps): bump github.com/getkin/kin-openapi from 0.129.0 to 0.131.0
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.129.0 to 0.131.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.129.0...v0.131.0)

---
updated-dependencies:
- dependency-name: github.com/getkin/kin-openapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-25 12:02:03 +01:00
dependabot[bot] 7ca6bb06b6 build(deps): bump github.com/spf13/afero from 1.11.0 to 1.14.0
Bumps [github.com/spf13/afero](https://github.com/spf13/afero) from 1.11.0 to 1.14.0.
- [Release notes](https://github.com/spf13/afero/releases)
- [Commits](https://github.com/spf13/afero/compare/v1.11.0...v1.14.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-25 12:00:07 +01:00
dependabot[bot] bddd2f9001 build(deps): bump github.com/bep/imagemeta from 0.9.0 to 0.10.0
Bumps [github.com/bep/imagemeta](https://github.com/bep/imagemeta) from 0.9.0 to 0.10.0.
- [Release notes](https://github.com/bep/imagemeta/releases)
- [Commits](https://github.com/bep/imagemeta/compare/v0.9.0...v0.10.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 10:45:08 +01:00
dependabot[bot] 1c691358f7 build(deps): bump github.com/golang-jwt/jwt/v5 from 5.2.1 to 5.2.2
Bumps [github.com/golang-jwt/jwt/v5](https://github.com/golang-jwt/jwt) from 5.2.1 to 5.2.2.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v5.2.1...v5.2.2)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 10:44:37 +01:00
dependabot[bot] 17db4edb02 build(deps): bump github.com/evanw/esbuild from 0.24.2 to 0.25.1
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.24.2 to 0.25.1.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2024.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.24.2...v0.25.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 10:44:16 +01:00
dependabot[bot] 3968f9158e build(deps): bump github.com/bep/godartsass/v2 from 2.4.0 to 2.4.1
Bumps [github.com/bep/godartsass/v2](https://github.com/bep/godartsass) from 2.4.0 to 2.4.1.
- [Release notes](https://github.com/bep/godartsass/releases)
- [Commits](https://github.com/bep/godartsass/compare/v2.4.0...v2.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 10:43:58 +01:00
dependabot[bot] e9bd2373a0 build(deps): bump golang.org/x/net from 0.35.0 to 0.37.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.35.0 to 0.37.0.
- [Commits](https://github.com/golang/net/compare/v0.35.0...v0.37.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-23 21:01:52 +01:00
Joe Mooring a6bd67793b common/hexec: Remove github.com/cli/safeexec
We began using the safeexec package in v0.79.1 to address
https://github.com/gohugoio/hugo/security/advisories/GHSA-8j34-9876-pvfq.

The vulnerability was addressed by the Go team in 1.19, so the safeexec
package is no longer needed.

Closes #13516
2025-03-23 13:55:42 +01:00
Ville Vesilehto f34cdc382a parser/metadecoder: Improve errors for non-map XML root values
Previously, the XML decoder would panic when encountering a root element with
a non-map value due to an unsafe type assertion.

The fix adds proper type checking before the map conversion and provides
clear error messages to help users identify and fix invalid XML structures.

Example error for invalid XML like:
<root>just text</root>

Will now return:
"XML root element 'root' must be a map/object, got string"
2025-03-22 18:48:23 +01:00
Bjørn Erik Pedersen 52561d561a identity: Use clear to clear the finder seen map
Faster:

```
Finder/Find_one-10    172.8n ± 26%   129.3n ± 2%  -25.18% (p=0.002 n=6)
Finder/Find_none-10   174.0n ±  1%   130.6n ± 0%  -24.94% (p=0.002 n=6)
```
2025-03-15 19:05:45 +01:00
dependabot[bot] f4f21f5ea3 build(deps): bump github.com/bep/godartsass/v2 from 2.3.2 to 2.4.0
Bumps [github.com/bep/godartsass/v2](https://github.com/bep/godartsass) from 2.3.2 to 2.4.0.
- [Release notes](https://github.com/bep/godartsass/releases)
- [Commits](https://github.com/bep/godartsass/compare/v2.3.2...v2.4.0)

---
updated-dependencies:
- dependency-name: github.com/bep/godartsass/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-15 18:37:33 +01:00
Joe Mooring a98ec3bd01 commands/gen: Set url in command pages to /docs/reference/commands/ 2025-03-14 21:11:46 +01:00
khayyam d28c84a871 cache: Apply httpcache defaults for polling config
Previously, compiling the config with partial or missing poll configs
would introduce a panic. This ensures that the default poll configs
are applied in such scenarios to ensure config is valid.

Fixes #13471
2025-03-14 14:37:26 +01:00
dependabot[bot] 61c39ae63b build(deps): bump golang.org/x/image from 0.24.0 to 0.25.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.24.0 to 0.25.0.
- [Commits](https://github.com/golang/image/compare/v0.24.0...v0.25.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-13 16:17:49 +01:00
Bjørn Erik Pedersen 2037137fbf deps: Upgrade golang.org/x/mod v0.23.0 => v0.24.0
Closes #13484
2025-03-12 16:24:48 +01:00
dependabot[bot] d78d4cf161 build(deps): bump github.com/bep/overlayfs from 0.9.2 to 0.10.0
Bumps [github.com/bep/overlayfs](https://github.com/bep/overlayfs) from 0.9.2 to 0.10.0.
- [Release notes](https://github.com/bep/overlayfs/releases)
- [Commits](https://github.com/bep/overlayfs/compare/v0.9.2...v0.10.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-11 21:03:49 +01:00
dependabot[bot] f9aae1581b build(deps): bump github.com/bep/lazycache from 0.7.0 to 0.8.0
Bumps [github.com/bep/lazycache](https://github.com/bep/lazycache) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/bep/lazycache/releases)
- [Commits](https://github.com/bep/lazycache/compare/v0.7.0...v0.8.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-11 21:03:27 +01:00
Joe Mooring b9add1c702 tpl/tplimpl: Add loading attribute to Vimeo shortcode
Closes #13445
2025-03-11 21:02:12 +01:00
dependabot[bot] b0686712ba build(deps): bump github.com/bep/imagemeta from 0.8.4 to 0.9.0
Bumps [github.com/bep/imagemeta](https://github.com/bep/imagemeta) from 0.8.4 to 0.9.0.
- [Release notes](https://github.com/bep/imagemeta/releases)
- [Commits](https://github.com/bep/imagemeta/compare/v0.8.4...v0.9.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-11 16:51:32 +01:00
dependabot[bot] 93d9c0533a build(deps): bump github.com/bep/simplecobra from 0.5.0 to 0.6.0
Bumps [github.com/bep/simplecobra](https://github.com/bep/simplecobra) from 0.5.0 to 0.6.0.
- [Release notes](https://github.com/bep/simplecobra/releases)
- [Commits](https://github.com/bep/simplecobra/compare/v0.5.0...v0.6.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-11 16:51:19 +01:00
Joe Mooring 93df17661f commands: Add --omitEmpty flag to gen chromastyles
Closes #13475
2025-03-11 16:50:59 +01:00
Will Faught eebea9ec41 tpl: Add trailing newline to robots.txt template 2025-03-05 15:02:50 +01:00
Bjørn Erik Pedersen 3f2e1c08e3 commands: Skip flaky test on Windows
Closes #13465
2025-03-03 12:50:33 +01:00
Hannes Braun 3a11d22da3 resources/image: Mark loong64 as FMA-using architecture 2025-02-26 21:17:06 +01:00
hugoreleaser cb4a097190 releaser: Prepare repository for 0.146.0-DEV
[ci skip]
2025-02-26 15:58:38 +00:00
hugoreleaser 666444f0a5 releaser: Bump versions for release of 0.145.0
[ci skip]
2025-02-26 15:41:25 +00:00
margau e5eecbd9bc github: Build docker image with both extended and withdeploy tags 2025-02-26 16:33:22 +01:00
coliff 4094a1e12c all: Typo fixes 2025-02-26 15:10:28 +01:00
Bjørn Erik Pedersen 6927e6f048 deps: Upgrade github.com/rogpeppe/go-internal v1.13.1 => v1.14.1
Closes #13449
2025-02-26 14:43:03 +01:00
Bjørn Erik Pedersen c498d0fe1e Use the page path and not the backing filename as the last resort in the default sort
This should:

1. Fix some (rare) tiebreaker issues when sorting pages from multiple content adapters.
2. Improve the sorting for pages without a backing file.
2025-02-26 13:06:26 +01:00
Bjørn Erik Pedersen 521911a576 all: Run modernize -fix ./... 2025-02-26 11:44:25 +01:00
dependabot[bot] b7ae24b9c2 build(deps): bump github.com/yuin/goldmark-emoji from 1.0.4 to 1.0.5
Bumps [github.com/yuin/goldmark-emoji](https://github.com/yuin/goldmark-emoji) from 1.0.4 to 1.0.5.
- [Release notes](https://github.com/yuin/goldmark-emoji/releases)
- [Commits](https://github.com/yuin/goldmark-emoji/compare/v1.0.4...v1.0.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-26 10:56:23 +01:00
Christian Oliff 2f4e666d7e tpl: HTTPS the instagram Shortcode JS
This JavaScript will always load over HTTPS so it's best to specify that. Protocol-less loaded resources are an anti-pattern (REF: https://www.paulirish.com/2010/the-protocol-relative-url/)
2025-02-26 09:27:31 +01:00
dependabot[bot] 1a4851f138 build(deps): bump github.com/google/go-cmp from 0.6.0 to 0.7.0
Bumps [github.com/google/go-cmp](https://github.com/google/go-cmp) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/google/go-cmp/releases)
- [Commits](https://github.com/google/go-cmp/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: github.com/google/go-cmp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-25 19:41:18 +01:00
dependabot[bot] d1f23309bf build(deps): bump github.com/tetratelabs/wazero from 1.8.2 to 1.9.0
Bumps [github.com/tetratelabs/wazero](https://github.com/tetratelabs/wazero) from 1.8.2 to 1.9.0.
- [Release notes](https://github.com/tetratelabs/wazero/releases)
- [Commits](https://github.com/tetratelabs/wazero/compare/v1.8.2...v1.9.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-25 19:40:01 +01:00
Bjørn Erik Pedersen 04f21b4d80 Add transform.PortableText
Using this in a content adapter could look like this:

```handlebars
{{ $projectID := "<myproject>" }}
{{ $useCached := true }}
{{ $api := "api" }}
{{ if $useCached }}
  {{/* See https://www.sanity.io/docs/api-cdn */}}
  {{ $api = "apicdn" }}
{{ end }}
{{ $url := printf "https://%s.%s.sanity.io/v2021-06-07/data/query/production"  $projectID $api }}

{{/* prettier-ignore-start */ -}}
{{ $q :=  `*[_type == 'post']{
  title, publishedAt, summary, slug, body[]{
    ...,
    _type == "image" => {
      ...,
      asset->{
        _id,
        path,
        url,
        altText,
        title,
        description,
        metadata {
          dimensions {
            aspectRatio,
            width,
            height
          }
        }
      }
    }
  },
  }`
}}
{{/* prettier-ignore-end */ -}}
{{ $body := dict "query" $q | jsonify }}
{{ $opts := dict "method" "post" "body" $body }}
{{ $t := debug.Timer "sanity.get" }}
{{ $r := resources.GetRemote $url $opts }}
{{ $t.Stop }}
{{ $m := $r | transform.Unmarshal }}
{{ $result := $m.result }}
{{ $t := debug.Timer "sanity.parse" }}
{{ range $result }}
  {{ if not .slug }}
    {{ continue }}
  {{ end }}
  {{ $markdown := transform.PortableText .body }}
  {{ $t.Stop }}
  {{ $content := dict
    "mediaType" "text/markdown"
    "value" $markdown
  }}
  {{ $params := dict
    "portabletext" (.body | jsonify (dict "indent" " "))
  }}
  {{ $page := dict
    "content" $content
    "kind" "page"
    "path" .slug.current
    "title" .title
    "date" (.publishedAt | time )
    "summary" .summary
    "params" $params
  }}
  {{ $.AddPage $page }}
{{ end }}
```
2025-02-25 19:35:47 +01:00
Martey Dodoo ab9e545760 readme: Fix relative links in Editions section
Make relative gohugo.io links used in Editions section of README
absolute so that they resolve successfully.
2025-02-25 18:26:05 +01:00
Bjørn Erik Pedersen 381c0da85d Fix some related content issues with content adapters
Fixes #13443
2025-02-25 14:14:56 +01:00
Bjørn Erik Pedersen 227e429267 Fix potential nilpointer in httpcache config 2025-02-24 18:42:50 +01:00
Joe Mooring d0ce942190 hugolib: Deprecate _build front matter key in favor of build
We effectively aliased "_build" to "build" in v0.123.0, and
removed "_build" from the documentation at the same time.
2025-02-24 10:13:53 +01:00
Joe Mooring 4fd7f3233f snap: Update Node.js to 22.x 2025-02-22 07:49:02 -08:00
Bjørn Erik Pedersen c3d435acfa Fix --printPathWarnings when site calls templates.Defer
This issue was introduced recently in eb7a5aabaa.

Fixes #13420
2025-02-19 12:59:40 +01:00
hugoreleaser 669216e204 releaser: Prepare repository for 0.145.0-DEV
[ci skip]
2025-02-18 12:29:51 +00:00
hugoreleaser a79d63a446 releaser: Bump versions for release of 0.144.1
[ci skip]
2025-02-18 12:14:07 +00:00
Bjørn Erik Pedersen 494e88abf6 markup/goldmark: Fix panic on empty Markdown header
Fixes #13416
2025-02-18 11:12:39 +01:00
hugoreleaser f1e799c2e1 releaser: Prepare repository for 0.145.0-DEV
[ci skip]
2025-02-17 16:36:56 +00:00
hugoreleaser b289b17c43 releaser: Bump versions for release of 0.144.0
[ci skip]
2025-02-17 16:22:31 +00:00
Bjørn Erik Pedersen 85c1727748 markup/goldmark: Fix panic on stray attribute nodes 2025-02-17 17:19:53 +01:00
Bjørn Erik Pedersen 641403f7de Fix Position for passthrough hooks
Fixes #13406
2025-02-17 12:23:49 +01:00
Bjørn Erik Pedersen 24cc25552f Fix auto generated header ids so they don't contain e.g. hyperlink destinations (note)
This makes the header ids match the newly added dt ids.

Also make sure newlines are preserved in hooks' `.PlainText`.

Fixes #13405
Fixes #13410
2025-02-17 12:23:49 +01:00
dependabot[bot] a2ca95629a build(deps): bump github.com/spf13/cobra from 1.8.1 to 1.9.1
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.8.1 to 1.9.1.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.8.1...v1.9.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 12:08:14 +01:00
dependabot[bot] 367fe526e2 build(deps): bump github.com/sanity-io/litter from 1.5.7 to 1.5.8
Bumps [github.com/sanity-io/litter](https://github.com/sanity-io/litter) from 1.5.7 to 1.5.8.
- [Changelog](https://github.com/sanity-io/litter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sanity-io/litter/compare/v1.5.7...v1.5.8)

---
updated-dependencies:
- dependency-name: github.com/sanity-io/litter
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 12:06:55 +01:00
Joe Mooring 5de3913ed4 config/allconfig: Deprecate :filename and :slugorfilename tokens 2025-02-17 12:06:32 +01:00
Bjørn Erik Pedersen 7b7a0f3624 resources/page: Revise the new contentbasename permalinks tokens
* Make it work for all pages, including those created from content adapters and not backed by a file.
* Allow the `slug` to win, so the new tokens are:

`:contentbasename`:

1. ContentBaseName

`:slugorcontentbasename`:

1. Slug
2. ContentBaseName

Note that a page will always have a `ContentBaseName`, so no need to fall back to e.g. the title.

Closes #11722
2025-02-17 09:41:49 +01:00
Henrique Dias cb7a4339b7 resources/page: Add :contentbasename and :contentbasenameorslug permalink tokens
See #11722
2025-02-17 09:41:49 +01:00
Bjørn Erik Pedersen 157d3703c3 Add autoID for definition terms
Fixes #13403
See #11566

Co-authored-by: Joe Mooring <joe@mooring.com>
2025-02-16 20:55:27 +01:00
dependabot[bot] 9c2f8ec61b build(deps): bump github.com/sanity-io/litter from 1.5.5 to 1.5.7
Bumps [github.com/sanity-io/litter](https://github.com/sanity-io/litter) from 1.5.5 to 1.5.7.
- [Changelog](https://github.com/sanity-io/litter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sanity-io/litter/compare/v1.5.5...v1.5.7)

---
updated-dependencies:
- dependency-name: github.com/sanity-io/litter
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-14 14:25:11 +01:00
dependabot[bot] bd77944ba2 build(deps): bump github.com/gohugoio/hugo-goldmark-extensions/extras
Bumps [github.com/gohugoio/hugo-goldmark-extensions/extras](https://github.com/gohugoio/hugo-goldmark-extensions) from 0.2.0 to 0.3.0.
- [Release notes](https://github.com/gohugoio/hugo-goldmark-extensions/releases)
- [Commits](https://github.com/gohugoio/hugo-goldmark-extensions/compare/extras/v0.2.0...extras/v0.3.0)

---
updated-dependencies:
- dependency-name: github.com/gohugoio/hugo-goldmark-extensions/extras
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-14 14:24:04 +01:00
Peter Weston c1f4228d61 config: Fix server.redirects.fromRe being ignored unless server.redirects.from is also set 2025-02-14 10:21:19 +01:00
Joe Mooring 503dcf6252 internal/warpc: Enable KaTeX mhchem extension 2025-02-14 10:04:15 +01:00
Joe Mooring 5dd0ba00f7 Update README.md
- Include section about editions
- Bump minimum required Go version for building from source
- Include instructions for building the extended/deploy edition
- Update dependency list
2025-02-13 17:17:59 +01:00
dependabot[bot] 2e835d0841 build(deps): bump golang.org/x/tools from 0.29.0 to 0.30.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.29.0 to 0.30.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.29.0...v0.30.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-13 15:01:51 +01:00
Bjørn Erik Pedersen 2c77719cd6 modules: Add GOAUTH to module config
Closes #13385
2025-02-13 15:01:28 +01:00
dependabot[bot] d89b9d891c 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.44.9 to 1.44.10.
- [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/macie2/v1.44.9...service/s3control/v1.44.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-13 13:48:54 +01:00
dependabot[bot] 8721a35202 build(deps): bump google.golang.org/api from 0.206.0 to 0.221.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.206.0 to 0.221.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.206.0...v0.221.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-13 13:47:55 +01:00
Bjørn Erik Pedersen fd38aee694 Support menus as maps in content adapters
Fixes #13384
2025-02-13 13:29:24 +01:00
Bjørn Erik Pedersen d25f7ec172 js/esbuild: Add drop option
Fixes #13362
2025-02-13 13:26:46 +01:00
Bjørn Erik Pedersen a024bc7d76 Squashed 'docs/' changes from 73a01565c..d1a251933
d1a251933 theme: Optimize for printing
d906488d5 theme: Add border to highlighted code blocks within list items
9630c38cb theme: Display short command on commands list
7f81dd997 content: Regen commands markup
a84008e99 content: Cleanup primary list page
0874b03e9 content: Publish QR code examples to the images/qr directory
ef1ce3498 Restore static/img
489a68458 content: Restore KeyCDN screen captures
7d6e6184b theme: Move search button in navbar to the right on smaller screens
b03b96082 theme: Misc style adjustments
ecad97821 theme: Misc fixes
cf5e07b0f content: Restore example data and images
c7265041f content: Reformat css.TailwindCSS setup steps
3226e668f theme: Make the search button more compact on mobile
1f6614ee8 content: Update css.TailwindCSS example
49a2e7d71 theme: Improve search output
7e6b81ffa misc: Create issue template
826740223 theme: Misc search improvements
75f32b2f6 theme:  Remove the search alert
fd3de4ac2 config: Enable link render hook warnings
769e387cd Adjust logic to mark current section for Algolia
ba73ce646 Adjust markup to make it easier to match in Algolia
5accec5f1 Misc adjustments
eb5842566 Some content adjustments
bdf97b7b4 Add new theme
b19d68ee5 Remove old theme
a04e96e55 Rewrite the css.TailwindCSS page
1c46f1864 Update deprecation notes
aedcb444c Fix typo
c3290b876 Changes related to release of v0.144.0
a0012fcce netlify: Hugo 0.143.1
9e4d73e87 Include sections in quick-reference page collection
4591058f5 Miscellaneous edits
612b8528f glossary: More edits
1f0c54e60 Fit typo
6f14084c1 Move glossary to quick reference section
2d94905be Improve branch bundle admonition
2ea56bef3 Fix typos found with codespell
71c5fe951 Close shortcode
e95d06592 Document the responseHeaders option for resources.GetRemote
da7c12aaa Deprecate gist shortcode
c2d3e2c25 netlify: Bump to 0.143.0
367d3a7ab Improve new-in shortcode
1a7413a16 Clean up shortcode documentation
d847892aa Improve glossary
6a7fd42ff Update pages describing Store methods and functions
fa7643d1b Revert "Improve link render hook performance"
fe4c86ec4 Reformat MathJax example
cea44922a Update MathJax example to use safe mode
0b6d0292c Update KaTeX example to use latest version
fd4508645 Improve link render hook performance
23aeb5bd0 Update purgecss example
f7ef83e56 Refer each Hugo Pipe page to the corresponding function
81b91ef4e Remove related pages from pages for deprecated functions and methods
53c7627d5 Remove body from deprecated functions and methods
d9cf034c8 Consolidate css.ToCSS information
b7ed108f9 Improve js.Build example and presentation of options
86a4a5088 Improve babel example and description of options
d1874c5f5 Consistently use "edition" when referring to standard, extended, etc.
92f03a350 Revise description of privacy settings
152a92d80 Update Last.md
82a2365bd Add print-only QR code example
d22cadc25 Update mathematics.md
b8160af03 Delete page self-reference in page/type.md
ce24fe4e0 Update param.md
51cb92180 Improve description of output format template selection
78bcf358e Update Tailwind CSS npm package version to 4.0
2ca9da4af Include winget command to uninstall

git-subtree-dir: docs
git-subtree-split: d1a2519330
2025-02-13 10:40:34 +01:00
Bjørn Erik Pedersen 304a7e5e74 Merge commit 'a024bc7d76fcc5e49e8210f9b0896db9ef21861a' 2025-02-13 10:40:34 +01:00
dependabot[bot] c054e18827 build(deps): bump github.com/bep/imagemeta from 0.8.3 to 0.8.4
Bumps [github.com/bep/imagemeta](https://github.com/bep/imagemeta) from 0.8.3 to 0.8.4.
- [Release notes](https://github.com/bep/imagemeta/releases)
- [Commits](https://github.com/bep/imagemeta/compare/v0.8.3...v0.8.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-13 09:02:59 +01:00
Joe Mooring 34dcac53bf config: Remove unused code 2025-02-13 08:40:49 +01:00
Joe Mooring ee438606dd commands: Use punctuation consistently in short description 2025-02-13 08:40:26 +01:00
Bjørn Erik Pedersen fd8b0fbf8a Upgrade to Go 1.24
Fixes #13381
2025-02-12 22:01:25 +01:00
Bjørn Erik Pedersen 9b5f786df8 Deprecate kind, lang, and path from front matter
These were added to the page meta object when we implemented "pages from data", but were not meant to be used in front matter.

That is not supported, so we might as well add validation.

Fixes #12484
2025-02-10 21:22:43 +01:00
Bjørn Erik Pedersen e6feb9e0be commands: Move the CHMOD event filter up
To prevent ghost rebuilds (from VSCode and possibly others).

Fixes #13373
2025-02-10 17:02:06 +01:00
dependabot[bot] 5e4ffa0e89 build(deps): bump golang.org/x/image from 0.22.0 to 0.24.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.22.0 to 0.24.0.
- [Commits](https://github.com/golang/image/compare/v0.22.0...v0.24.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-10 12:46:06 +01:00
dependabot[bot] 0cdcc2b584 build(deps): bump golang.org/x/mod from 0.22.0 to 0.23.0
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.22.0 to 0.23.0.
- [Commits](https://github.com/golang/mod/compare/v0.22.0...v0.23.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-10 11:43:49 +01:00
dependabot[bot] a591c4406a build(deps): bump github.com/gohugoio/hashstructure from 0.3.0 to 0.5.0
Bumps [github.com/gohugoio/hashstructure](https://github.com/gohugoio/hashstructure) from 0.3.0 to 0.5.0.
- [Release notes](https://github.com/gohugoio/hashstructure/releases)
- [Commits](https://github.com/gohugoio/hashstructure/compare/v0.3.0...v0.5.0)

---
updated-dependencies:
- dependency-name: github.com/gohugoio/hashstructure
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-10 11:43:11 +01:00
dependabot[bot] 2d86a0512c build(deps): bump github.com/bep/simplecobra from 0.4.0 to 0.5.0
Bumps [github.com/bep/simplecobra](https://github.com/bep/simplecobra) from 0.4.0 to 0.5.0.
- [Release notes](https://github.com/bep/simplecobra/releases)
- [Commits](https://github.com/bep/simplecobra/compare/v0.4.0...v0.5.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-09 23:31:45 +01:00
Dominik Süß f02da7fbce parser: Handle org-mode filetags as slice
This adds support for filetags by slicing them according to [the org mode tag
specification](https://orgmode.org/guide/Tags.html).

Can be used to create taxonomies based on org-mode tags
2025-02-09 22:40:28 +01:00
dependabot[bot] 22ee0918f3 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.41.0 to 1.44.9.
- [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.41.0...service/macie2/v1.44.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-08 15:46:45 +01:00
dependabot[bot] b50ab04031 build(deps): bump github.com/aws/aws-sdk-go-v2 from 1.32.4 to 1.36.1
Bumps [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) from 1.32.4 to 1.36.1.
- [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.32.4...v1.36.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-08 10:33:03 +01:00
dependabot[bot] b772f0e3d2 build(deps): bump github.com/getkin/kin-openapi from 0.123.0 to 0.129.0
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.123.0 to 0.129.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.123.0...v0.129.0)

---
updated-dependencies:
- dependency-name: github.com/getkin/kin-openapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-08 10:32:42 +01:00
dependabot[bot] d58c0198d3 build(deps): bump github.com/spf13/pflag from 1.0.5 to 1.0.6
Bumps [github.com/spf13/pflag](https://github.com/spf13/pflag) from 1.0.5 to 1.0.6.
- [Release notes](https://github.com/spf13/pflag/releases)
- [Commits](https://github.com/spf13/pflag/compare/v1.0.5...v1.0.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-08 10:31:42 +01:00
Bjørn Erik Pedersen c2fb221209 Add ContentTypes to config
This is an empty struct for now, but we will most likely expand on that.

```
[contentTypes]
  [contentTypes.'text/markdown']
```

The above means that only Markdown will be considered a content type. E.g. HTML will be treated as plain text.

Fixes #12274
2025-02-08 10:31:15 +01:00
Oleksandr Redko 4245a4514d all: Remove deprecated build tags 2025-02-08 10:29:42 +01:00
Joe Mooring f0c1852978 helpers: Add Chroma styles to docs.yaml
Closes #13360
2025-02-08 00:30:05 +01:00
Joe Mooring a352e69b02 commands: Validate style argument passed to gen chromastyles
Closes #13357
2025-02-07 22:44:22 +01:00
Bjørn Erik Pedersen 1dd27be81a Update CONTRIBUTING.md 2025-02-07 22:25:19 +01:00
Bjørn Erik Pedersen eb7a5aabaa Move "print unused templates" after renderDeferred
Fixes #13355
2025-02-07 18:04:12 +01:00
Bjørn Erik Pedersen 029d1e0ced Add some more server options/improvements
New options:

* `FromHeaders`: Server header matching for redirects
* `FromRe`: Regexp with group support, i.e. it replaces $1, $2 in To with the group matches.

Note that if both `From` and `FromRe` is set, both must match.

Also

* Allow redirects to non HTML URLs as long as the Sec-Fetch-Mode is set to navigate on the request.
* Detect and stop redirect loops.

This was all done while testing out InertiaJS with Hugo. So, after this commit, this setup will support the main parts of the protocol that Inertia uses:

```toml
[server]
    [[server.headers]]
        for = '/**/inertia.json'
        [server.headers.values]
            Content-Type = 'text/html'
            X-Inertia    = 'true'
            Vary         = 'Accept'

    [[server.redirects]]
        force       = true
        from        = '/**/'
        fromRe      = "^/(.*)/$"
        fromHeaders = { "X-Inertia" = "true" }
        status      = 301
        to          = '/$1/inertia.json'
```

Unfortunately, a provider like Netlify does not support redirects matching by request headers. It should be possible with some edge function, but then again, I'm not sure that InertiaJS is a very good fit with the common Hugo use cases.

But this commit should be generally useful.
2025-02-05 18:14:15 +01:00
Bjørn Erik Pedersen e865d59844 Fix shortcode name in error message on self-closing shortcodes with no .Inner
Fixes #13344
2025-02-05 16:35:02 +01:00
hugoreleaser 377287a614 releaser: Prepare repository for 0.144.0-DEV
[ci skip]
2025-02-04 09:14:03 +00:00
hugoreleaser 0270364a34 releaser: Bump versions for release of 0.143.1
[ci skip]
2025-02-04 08:57:38 +00:00
Bjørn Erik Pedersen 760c13a7ac Fix RSS with baseURL with sub dir when render hooks is enabled
Fixes #13332
2025-02-03 19:00:12 +01:00
Oleksandr Redko 3bd73d262d mage: Simplify magefile 2025-02-03 18:34:16 +01:00
Joe Mooring 7104de83ce common/hugo: Adjust deprecation timing and message
Closes #13333
2025-02-03 18:32:30 +01:00
Bjørn Erik Pedersen 835579b338 Re-introduce the LRU-evicted identities in change set calculation
This is a follow up to db28695ff5 -- that commit dropped the cache items evicted in the LRU process. This was done as performance optimization for large Hugo sites.

That made much sense, but now there's a slight chance that we miss out on a change when rebuilding.

This commit fixes this by applying the same logic to the evicted items as if they were still in the cache. This should preserve the performance gains in db28695ff5 and close the hole for the possible false negatives.
2025-02-02 15:55:11 +01:00
hugoreleaser 05e067ced8 releaser: Prepare repository for 0.144.0-DEV
[ci skip]
2025-02-01 16:09:38 +00:00
hugoreleaser ee48d9692a releaser: Bump versions for release of 0.143.0
[ci skip]
2025-02-01 15:56:04 +00:00
Bjørn Erik Pedersen db28695ff5 Fix some server/watch rebuild issues
Two issues:

1. Fixe potential edit-loop in server/watch mode (see below)
2. Drain the cache eviction stack before we start calculating the change set. This should allow more fine grained rebuilds for bigger sites and/or in low memory situations.

The fix in 6c68142cc1 wasn't really fixing the complete problem.

In Hugo we have some steps that takes more time than others, one example being CSS building with TailwindCSS.

The symptom here is that sometimes when you:

1. Edit content or templates that does not trigger a CSS rebuild => Snappy rebuild.
2. Edit stylesheet or add a CSS class to template that triggers a CSS rebuild => relatively slow rebuild (expected)
3. Then back to content editing or template edits that should not trigger a CSS rebuild => relatively slow rebuild (not expected)

This commit fixes this by pulling the dynacache GC step up and merge it with the cache buster step.

Fixes #13316
2025-02-01 16:29:14 +01:00
Bjørn Erik Pedersen 778f0d9002 Don't re-render aliases on server rebuilds
This can lead to stale aliases when rebuilding, but that's a trade-off we need to take for snappier rebuilds on bigger sites.

Note that it should be possible to detect alias changes, but I'm not sure it's worth it.
2025-02-01 15:23:36 +01:00
Alex Shpak 13b208e2f7 tpl/tplimpl: Remove leading whitespaces produced by Youtube shortcode 2025-01-31 09:29:10 +01:00
Bjørn Erik Pedersen 329b2342f0 Fix "concurrent map iteration and map write" in pages from data
Fixes #13254
2025-01-30 19:57:09 +01:00
Joe Mooring 33b46d8a41 resources: Remove debug statement
Closes #13320
2025-01-30 11:32:57 +01:00
Bjørn Erik Pedersen 6c68142cc1 Fix TailwindCSS related server rebuild issue
Fixes #13316
2025-01-29 18:46:12 +01:00
2457 changed files with 71377 additions and 59020 deletions
+2 -2
View File
@@ -4,7 +4,7 @@ parameters:
defaults: &defaults
resource_class: large
docker:
- image: bepsays/ci-hugoreleaser:1.22301.20401
- image: bepsays/ci-hugoreleaser:1.22500.20400
environment: &buildenv
GOMODCACHE: /root/project/gomodcache
version: 2
@@ -58,7 +58,7 @@ jobs:
environment:
<<: [*buildenv]
docker:
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22301.20401
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22500.20400
steps:
- *restore-cache
- &attach-workspace
+13
View File
@@ -0,0 +1,13 @@
have_fun: false
memory_config:
disabled: false
code_review:
disable: false
comment_severity_threshold: HIGH
max_review_comments: -1
pull_request_opened:
help: true
summary: false
code_review: false
include_drafts: false
ignore_patterns: []
+10
View File
@@ -0,0 +1,10 @@
🛑 Before submitting this PR
All PRs must be linked to an Issue in this repository, and that Issue must be approved by a project maintainer:
- For a bug, approval means that the "needs triage" label is removed.
- For a proposal, approval means that the "proposal" label is replaced by an "enhancement" label.
PRs without a linked, approved Issue will be closed immediately.
Please review the contribution guide (CONTRIBUTING.md) to understand commit message guidelines and AI assistance disclosures.
+22
View File
@@ -0,0 +1,22 @@
name: AI Detection
on:
# Use pull_request_target instead of pull_request (we need the secrets.OPENAI_API_KEY to be available for forks)
pull_request_target:
types: [opened, ready_for_review]
permissions:
contents: read
pull-requests: write
jobs:
detect-ai:
runs-on: ubuntu-latest
if: ${{ !github.event.pull_request.draft }}
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- name: AI Watchdog
uses: bep/ai-watchdog@995475dd81767f8035dc2c943e9230918d06caca # temporary fork (probably)
with:
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
pr-label: AI Suspect
fail-when-confident: false
# CSV list.
pr-user-logins-skip: dependabot[bot],bep,jmooring
+6 -5
View File
@@ -16,20 +16,20 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- name: Docker meta
id: meta
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0
with:
images: ${{ env.REGISTRY_IMAGE }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Login to GHCR
# Login is only needed when the image is pushed
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -37,7 +37,7 @@ jobs:
- name: Build and push
id: build
uses: docker/build-push-action@16ebe778df0e7752d2cfcbd924afdbbd89c1a755 # v6.6.1
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
context: .
provenance: mode=max
@@ -46,3 +46,4 @@ jobs:
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: HUGO_BUILD_TAGS=extended,withdeploy
+2 -2
View File
@@ -24,7 +24,7 @@ jobs:
This pull request has been automatically locked since there
has not been any recent activity after it was closed.
Please open a new issue for related bugs.
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
with:
operations-per-run: 999
days-before-issue-stale: 365
@@ -43,7 +43,7 @@ jobs:
stale-pr-message: This PR has been automatically marked as stale because it has not had
recent activity. The resources of the Hugo team are limited, and so we are asking for your help.
Please check https://github.com/gohugoio/hugo/blob/master/CONTRIBUTING.md#code-contribution and verify that this code contribution fits with the description. If yes, tell is in a comment.
Please check https://github.com/gohugoio/hugo/blob/master/CONTRIBUTING.md#code-contribution and verify that this code contribution fits with the description. If yes, tell us in a comment.
This PR will automatically be closed in the near future if no further activity occurs. Thank you for all your contributions.
stale-issue-label: 'Stale'
+19 -17
View File
@@ -16,7 +16,7 @@ jobs:
test:
strategy:
matrix:
go-version: [1.22.x, 1.23.x]
go-version: [1.24.x, 1.25.x]
os: [ubuntu-latest, windows-latest] # macos disabled for now because of disk space issues.
runs-on: ${{ matrix.os }}
steps:
@@ -24,9 +24,7 @@ jobs:
name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: false
tool-cache: true
android: true
dotnet: true
haskell: true
@@ -34,9 +32,9 @@ jobs:
docker-images: true
swap-storage: true
- name: Checkout code
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- name: Install Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version: ${{ matrix.go-version }}
check-latest: true
@@ -45,18 +43,21 @@ jobs:
**/go.sum
**/go.mod
- name: Install Ruby
uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # v1.190.0
uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0
with:
ruby-version: "2.7"
bundler-cache: true #
ruby-version: "3.4.5"
- name: Install Ruby gems
run: |
gem install asciidoctor -v "2.0.26"
gem install asciidoctor-diagram -v "3.1.0"
- name: Install GoAT
run: go install github.com/blampe/goat/cmd/goat@177de93b192b8ffae608e5d9ec421cc99bf68402
- name: Install Python
uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: "3.x"
- name: Install Mage
run: go install github.com/magefile/mage@v1.15.0
- name: Install asciidoctor
uses: reitzig/actions-asciidoctor@c642db5eedd1d729bb8c92034770d0b2f769eda6 # v2.0.2
- name: Install docutils
run: |
pip install docutils
@@ -105,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: |
@@ -117,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
+15 -3
View File
@@ -1,4 +1,4 @@
>**Note:** We would apprecitate if you hold on with any big refactorings (like renaming deprecated Go packages), mainly because of potential for extra merge work for future coming in in the near future.
>**Note:** We would appreciate if you hold on with any big refactoring (like renaming deprecated Go packages), mainly because of potential for extra merge work for future coming in in the near future.
# Contributing to Hugo
@@ -19,6 +19,7 @@ The Hugo community and maintainers are [very active](https://github.com/gohugoio
* [Reporting Issues](#reporting-issues)
* [Submitting Patches](#submitting-patches)
* [Code Contribution Guidelines](#code-contribution-guidelines)
* [AI Assistance Notice](#ai-assistance-notice)
* [Git Commit Message Guidelines](#git-commit-message-guidelines)
* [Fetching the Sources From GitHub](#fetching-the-sources-from-github)
* [Building Hugo with Your Changes](#building-hugo-with-your-changes)
@@ -54,7 +55,7 @@ If it is of some complexity, the contributor is expected to maintain and support
Any non-trivial code change needs to update an open [issue](https://github.com/gohugoio/hugo/issues). A non-trivial code change without an issue reference with one of the labels `bug` or `enhancement` will not be merged.
Note that we do not accept new features that require [CGO](https://github.com/golang/go/wiki/cgo).
Note that we do not accept new features that require [CGO](https://go.dev/wiki/cgo).
We have one exception to this rule which is LibSASS.
**Bug fixes are, of course, always welcome.**
@@ -76,9 +77,19 @@ 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
If a substantial part of your contribution is autogenerated with AI, **this must be disclosed in the pull request**, along with the extent to which AI assistance was used.
An example disclosure:
> This PR was written primarily by Claude Code.
When using AI assistance, we expect contributors to understand the code that is produced and be able to answer critical questions about it.
### Git Commit Message Guidelines
This [blog article](https://cbea.ms/git-commit/) is a good resource for learning how to write good commit messages,
@@ -93,6 +104,7 @@ Most title/subjects should have a lower-cased prefix with a colon and one whites
* If this commit touches many packages without a common functional topic, prefix with `all:` (e.g. `all: Reformat Go code`)
* If this is a documentation update, prefix with `docs:`.
* If nothing of the above applies, just leave the prefix out.
* Note that the above excludes nouns seen in other repositories, e.g. "chore:".
Also, if your commit references one or more GitHub issues, always end your commit message body with *See #1234* or *Fixes #1234*.
Replace *1234* with the GitHub issue ID. The last example will close the issue when the commit is merged into *master*.
+4 -4
View File
@@ -2,8 +2,8 @@
# Twitter: https://twitter.com/gohugoio
# Website: https://gohugo.io/
ARG GO_VERSION="1.23.2"
ARG ALPINE_VERSION="3.20"
ARG GO_VERSION="1.25"
ARG ALPINE_VERSION="3.22"
ARG DART_SASS_VERSION="1.79.3"
FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.5.0 AS xx
@@ -19,7 +19,7 @@ RUN apk add clang lld
COPY --from=xx / /
ARG TARGETPLATFORM
RUN xx-apk add musl-dev gcc g++
RUN xx-apk add musl-dev gcc g++
# Optionally set HUGO_BUILD_TAGS to "none" or "withdeploy" when building like so:
# docker build --build-arg HUGO_BUILD_TAGS=withdeploy .
@@ -72,7 +72,7 @@ RUN mkdir -p /var/hugo/bin /cache && \
adduser -Sg hugo -u 1000 -h /var/hugo hugo && \
chown -R hugo: /var/hugo /cache && \
# For the Hugo's Git integration to work.
runuser -u hugo -- git config --global --add safe.directory /project && \
runuser -u hugo -- git config --global --add safe.directory /project && \
# See https://github.com/gohugoio/hugo/issues/9810
runuser -u hugo -- git config --global core.quotepath false
+105 -66
View File
@@ -34,6 +34,7 @@ A fast and flexible static site generator built with love by [bep], [spf13], and
[![Go Report Card](https://goreportcard.com/badge/github.com/gohugoio/hugo)](https://goreportcard.com/report/github.com/gohugoio/hugo)
[Website] | [Installation] | [Documentation] | [Support] | [Contributing] | <a rel="me" href="https://fosstodon.org/@gohugoio">Mastodon</a>
## Overview
Hugo is a [static site generator] written in [Go], optimized for speed and designed for flexibility. With its advanced templating system and fast asset pipelines, Hugo renders a complete site in seconds, often less.
@@ -51,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
@@ -64,11 +65,24 @@ 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/gohugoioTheme/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/gohugoioTheme/master/assets/images/sponsors/goland.svg" width="200" alt="The complete IDE crafted for professional Go developers."></a>
<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>
</p>
## Editions
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
:--|:-:|:-:
[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
[transpile sass to css]: https://gohugo.io/functions/css/sass/
[details]: https://gohugo.io/hosting-and-deployment/hugo-deploy/
## Installation
Install Hugo from a [prebuilt binary], package manager, or package repository. Please see the installation instructions for your operating system:
@@ -80,15 +94,11 @@ Install Hugo from a [prebuilt binary], package manager, or package repository. P
## Build from source
Hugo is available in two editions: standard and extended. With the extended edition you can:
- Encode to the WebP format when processing images. You can decode WebP images with either edition.
- Transpile Sass to CSS using the embedded LibSass transpiler. The extended edition is not required to use the Dart Sass transpiler.
Prerequisites to build Hugo from source:
- Standard edition: Go 1.20 or later
- Extended edition: Go 1.20 or later, and GCC
- Standard edition: Go 1.24.0 or later
- Extended edition: Go 1.24.0 or later, and GCC
- Extended/deploy edition: Go 1.24.0 or later, and GCC
Build the standard edition:
@@ -102,6 +112,12 @@ Build the extended edition:
CGO_ENABLED=1 go install -tags extended github.com/gohugoio/hugo@latest
```
Build the extended/deploy edition:
```text
CGO_ENABLED=1 go install -tags extended,withdeploy github.com/gohugoio/hugo@latest
```
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=gohugoio/hugo&type=Timeline)](https://star-history.com/#gohugoio/hugo&Timeline)
@@ -140,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.
@@ -149,50 +175,58 @@ 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/alecthomas/chroma/v2="v2.14.0"
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.3"
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.4.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/cli/safeexec="v1.0.1"
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.0"
github.com/evanw/esbuild="v0.24.0"
github.com/dlclark/regexp2="v1.11.5"
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.123.0"
github.com/ghodss/yaml="v1.0.0"
github.com/go-openapi/jsonpointer="v0.20.2"
github.com/go-openapi/swag="v0.22.8"
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/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.1.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/google/go-cmp="v0.6.0"
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"
github.com/invopop/yaml="v0.2.0"
github.com/jdkato/prose="v1.2.1"
github.com/josharian/intern="v1.0.0"
github.com/kr/pretty="v0.3.1"
@@ -203,46 +237,51 @@ 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/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/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/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/dart-sass/compiler="1.81.0"
github.com/sass/dart-sass/implementation="1.81.0"
github.com/sass/dart-sass/protocol="3.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.5"
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.31.0"
golang.org/x/exp="v0.0.0-20221031165847-c99f073a8326"
golang.org/x/image="v0.22.0"
golang.org/x/mod="v0.22.0"
golang.org/x/net="v0.33.0"
golang.org/x/sync="v0.10.0"
golang.org/x/sys="v0.28.0"
golang.org/x/text="v0.21.0"
golang.org/x/tools="v0.28.0"
google.golang.org/protobuf="v1.35.2"
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"
rsc.io/qr="v0.2.0"
software.sslmate.com/src/go-pkcs12="v0.2.0"
+14 -8
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
@@ -176,11 +176,12 @@ func (c *Cache) ClearMatching(predicatePartition func(k string, p PartitionManag
}
// ClearOnRebuild prepares the cache for a new rebuild taking the given changeset into account.
func (c *Cache) ClearOnRebuild(changeset ...identity.Identity) {
// predicate is optional and will clear any entry for which it returns true.
func (c *Cache) ClearOnRebuild(predicate func(k, v any) bool, changeset ...identity.Identity) {
g := rungroup.Run[PartitionManager](context.Background(), rungroup.Config[PartitionManager]{
NumWorkers: len(c.partitions),
Handle: func(ctx context.Context, partition PartitionManager) error {
partition.clearOnRebuild(changeset...)
partition.clearOnRebuild(predicate, changeset...)
return nil
},
})
@@ -339,7 +340,7 @@ func GetOrCreatePartition[K comparable, V any](c *Cache, name string, opts Optio
panic("invalid Weight, must be between 1 and 100")
}
if partitionNameRe.FindString(name) != name {
if !partitionNameRe.MatchString(name) {
panic(fmt.Sprintf("invalid partition name %q", name))
}
@@ -479,7 +480,12 @@ func (p *Partition[K, V]) clearMatching(predicate func(k, v any) bool) {
})
}
func (p *Partition[K, V]) clearOnRebuild(changeset ...identity.Identity) {
func (p *Partition[K, V]) clearOnRebuild(predicate func(k, v any) bool, changeset ...identity.Identity) {
if predicate == nil {
predicate = func(k, v any) bool {
return false
}
}
opts := p.getOptions()
if opts.ClearWhen == ClearNever {
return
@@ -525,7 +531,7 @@ func (p *Partition[K, V]) clearOnRebuild(changeset ...identity.Identity) {
// Second pass needs to be done in a separate loop to catch any
// elements marked as stale in the other partitions.
p.c.DeleteFunc(func(key K, v V) bool {
if shouldDelete(key, v) {
if predicate(key, v) || shouldDelete(key, v) {
p.trace.Log(
logg.StringFunc(
func() string {
@@ -601,7 +607,7 @@ type PartitionManager interface {
adjustMaxSize(addend int) int
getMaxSize() int
getOptions() OptionsPartition
clearOnRebuild(changeset ...identity.Identity)
clearOnRebuild(predicate func(k, v any) bool, changeset ...identity.Identity)
clearMatching(predicate func(k, v any) bool)
clearStale()
}
+6 -6
View File
@@ -147,13 +147,13 @@ func TestClear(t *testing.T) {
c.Assert(cache.Keys(predicateAll), qt.HasLen, 4)
cache.ClearOnRebuild()
cache.ClearOnRebuild(nil)
// Stale items are always cleared.
c.Assert(cache.Keys(predicateAll), qt.HasLen, 2)
cache = newTestCache(t)
cache.ClearOnRebuild(identity.StringIdentity("changed"))
cache.ClearOnRebuild(nil, identity.StringIdentity("changed"))
c.Assert(cache.Keys(nil), qt.HasLen, 1)
@@ -191,16 +191,16 @@ func TestPanicInCreate(t *testing.T) {
return err
}
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
for i := range 3 {
for range 3 {
c.Assert(willPanic(i), qt.PanicMatches, fmt.Sprintf("panic-%d", i))
c.Assert(willErr(i), qt.ErrorMatches, fmt.Sprintf("error-%d", i))
}
}
// Test the same keys again without the panic.
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
for i := range 3 {
for range 3 {
v, err := p1.GetOrCreate(fmt.Sprintf("panic-%d", i), func(key string) (testItem, error) {
return testItem{
name: key,
+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
+49 -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,27 +41,39 @@ 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
// CacheDirModules returns the compiled path to the modules cache.
// For internal use.
func (c Configs) CacheDirModules() string {
return c[CacheKeyModules].DirCompiled
}
// CacheDirMisc returns the compiled path to the misc cache.
// For internal use.
func (c Configs) CacheDirMisc() string {
return c[CacheKeyMisc].DirCompiled
}
var defaultCacheConfigs = Configs{
CacheKeyModules: {
MaxAge: -1,
Dir: ":cacheDir/modules",
},
CacheKeyModuleQueries: {
MaxAge: 24 * time.Hour,
Dir: ":cacheDir/modules",
},
CacheKeyGetJSON: defaultCacheConfig,
CacheKeyGetCSV: defaultCacheConfig,
CacheKeyImages: {
@@ -100,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]
@@ -120,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]
@@ -147,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`)
}
+1 -1
View File
@@ -67,7 +67,7 @@ title: "Home"
---
-- assets/a/pixel.png --
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
-- layouts/index.html --
-- layouts/home.html --
{{ warnf "HOME!" }}
{{ $img := resources.GetMatch "**.png" }}
{{ $img = $img.Resize "3x3" }}
+10 -6
View File
@@ -55,11 +55,14 @@ 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 := 0; i < 10; i++ {
for i := range 10 {
id := fmt.Sprintf("i%d", i)
cache.GetOrCreateBytes(id, func() ([]byte, error) {
return []byte("abc"), nil
@@ -74,7 +77,7 @@ dir = ":resourceDir/_gen"
c.Assert(err, qt.IsNil)
c.Assert(count, qt.Equals, 5, msg)
for i := 0; i < 10; i++ {
for i := range 10 {
id := fmt.Sprintf("i%d", i)
v := cache.GetString(id)
if i < 5 {
@@ -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) {
@@ -97,7 +101,7 @@ dir = ":resourceDir/_gen"
c.Assert(count, qt.Equals, 4)
// Now only the i5 should be left.
for i := 0; i < 10; i++ {
for i := range 10 {
id := fmt.Sprintf("i%d", i)
v := cache.GetString(id)
if i != 5 {
+14 -8
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))
@@ -105,7 +109,7 @@ dir = ":cacheDir/c"
}
for _, ca := range []*filecache.Cache{caches.ImageCache(), caches.AssetsCache(), caches.GetJSONCache(), caches.GetCSVCache()} {
for i := 0; i < 2; i++ {
for range 2 {
info, r, err := ca.GetOrCreate("a", rf("abc"))
c.Assert(err, qt.IsNil)
c.Assert(r, 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"
@@ -193,11 +199,11 @@ dir = "/cache/c"
var wg sync.WaitGroup
for i := 0; i < 50; i++ {
for i := range 50 {
wg.Add(1)
go func(i int) {
defer wg.Done()
for j := 0; j < 20; j++ {
for range 20 {
ca := caches.Get(cacheName)
c.Assert(ca, qt.Not(qt.IsNil))
filename, data := filenameData(i)
@@ -270,7 +276,7 @@ func newPathsSpec(t *testing.T, fs afero.Fs, configStr string) *helpers.PathSpec
cfg, err := config.FromConfigString(configStr, "toml")
c.Assert(err, qt.IsNil)
acfg := testconfig.GetTestConfig(fs, cfg)
p, err := helpers.NewPathSpec(hugofs.NewFrom(fs, acfg.BaseConfig()), acfg, nil)
p, err := helpers.NewPathSpec(hugofs.NewFrom(fs, acfg.BaseConfig()), acfg, nil, nil)
c.Assert(err, qt.IsNil)
return p
}
+45 -12
View File
@@ -25,6 +25,8 @@ import (
// DefaultConfig holds the default configuration for the HTTP cache.
var DefaultConfig = Config{
RespectCacheControlNoStoreInRequest: true,
RespectCacheControlNoStoreInResponse: false,
Cache: Cache{
For: GlobMatcher{
Excludes: []string{"**"},
@@ -42,7 +44,13 @@ var DefaultConfig = Config{
// Config holds the configuration for the HTTP cache.
type Config struct {
// Configures the HTTP cache behaviour (RFC 9111).
// When enabled and there's a Cache-Control: no-store directive in the request, response will never be stored in disk cache.
RespectCacheControlNoStoreInRequest bool
// When enabled and there's a Cache-Control: no-store directive in the response, response will never be stored in disk cache.
RespectCacheControlNoStoreInResponse bool
// Enables HTTP cache behavior (RFC 9111) for these resources.
// When this is not enabled for a resource, Hugo will go straight to the file cache.
Cache Cache
@@ -52,12 +60,14 @@ type Config struct {
}
type Cache struct {
// Enable HTTP cache behaviour (RFC 9111) for these rsources.
// Enable HTTP cache behavior (RFC 9111) for these resources.
For GlobMatcher
}
func (c *Config) Compile() (ConfigCompiled, error) {
var cc ConfigCompiled
cc := ConfigCompiled{
Base: *c,
}
p, err := c.Cache.For.CompilePredicate()
if err != nil {
@@ -122,7 +132,12 @@ type GlobMatcher struct {
Includes []string
}
func (gm GlobMatcher) IsZero() bool {
return len(gm.Includes) == 0 && len(gm.Excludes) == 0
}
type ConfigCompiled struct {
Base Config
For predicate.P[string]
PollConfigs []PollConfigCompiled
}
@@ -155,16 +170,19 @@ func (p PollConfigCompiled) IsZero() bool {
}
func (gm *GlobMatcher) CompilePredicate() (func(string) bool, error) {
var p predicate.P[string]
if gm.IsZero() {
panic("no includes or excludes")
}
var b predicate.PR[string]
for _, include := range gm.Includes {
g, err := glob.Compile(include, '/')
if err != nil {
return nil, err
}
fn := func(s string) bool {
return g.Match(s)
fn := func(s string) predicate.Match {
return predicate.BoolMatch(g.Match(s))
}
p = p.Or(fn)
b = b.Or(fn)
}
for _, exclude := range gm.Excludes {
@@ -172,16 +190,16 @@ func (gm *GlobMatcher) CompilePredicate() (func(string) bool, error) {
if err != nil {
return nil, err
}
fn := func(s string) bool {
return !g.Match(s)
fn := func(s string) predicate.Match {
return predicate.BoolMatch(!g.Match(s))
}
p = p.And(fn)
b = b.And(fn)
}
return p, nil
return b.BoolFunc(), nil
}
func DecodeConfig(bcfg config.BaseConfig, m map[string]any) (Config, error) {
func DecodeConfig(_ config.BaseConfig, m map[string]any) (Config, error) {
if len(m) == 0 {
return DefaultConfig, nil
}
@@ -203,5 +221,20 @@ func DecodeConfig(bcfg config.BaseConfig, m map[string]any) (Config, error) {
return c, err
}
if c.Cache.For.IsZero() {
c.Cache.For = DefaultConfig.Cache.For
}
for pci := range c.Polls {
if c.Polls[pci].For.IsZero() {
c.Polls[pci].For = DefaultConfig.Cache.For
c.Polls[pci].Disable = true
}
}
if len(c.Polls) == 0 {
c.Polls = DefaultConfig.Polls
}
return c, nil
}
+31
View File
@@ -22,6 +22,8 @@ import (
)
func TestConfigCustom(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
[httpcache]
@@ -51,6 +53,8 @@ includes = ["**gohugo.io**"]
}
func TestConfigDefault(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
`
@@ -62,3 +66,30 @@ func TestConfigDefault(t *testing.T) {
b.Assert(compiled.For("https://gohugo.io/foo.jpg"), qt.IsFalse)
b.Assert(compiled.PollConfigFor("https://gohugo.io/foo.jpg").Config.Disable, qt.IsTrue)
}
func TestConfigPollsOnly(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
[httpcache]
[[httpcache.polls]]
low = "5s"
high = "32s"
[httpcache.polls.for]
includes = ["**gohugo.io**"]
`
b := hugolib.Test(t, files)
compiled := b.H.Configs.Base.C.HTTPCache
b.Assert(compiled.For("https://gohugo.io/posts.json"), qt.IsFalse)
b.Assert(compiled.For("https://gohugo.io/foo.jpg"), qt.IsFalse)
pc := compiled.PollConfigFor("https://gohugo.io/foo.jpg")
b.Assert(pc.Config.Low, qt.Equals, 5*time.Second)
b.Assert(pc.Config.High, qt.Equals, 32*time.Second)
b.Assert(compiled.PollConfigFor("https://example.com/foo.jpg").IsZero(), qt.IsTrue)
}
+31
View File
@@ -17,6 +17,7 @@ import (
"testing"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
)
func TestGlobMatcher(t *testing.T) {
@@ -40,3 +41,33 @@ func TestGlobMatcher(t *testing.T) {
c.Assert(p("foo/bar/foo.css"), qt.IsFalse)
c.Assert(p("foo/bar/foo.xml"), qt.IsTrue)
}
func TestDefaultConfig(t *testing.T) {
c := qt.New(t)
_, err := DefaultConfig.Compile()
c.Assert(err, qt.IsNil)
}
func TestDecodeConfigInjectsDefaultAndCompiles(t *testing.T) {
c := qt.New(t)
cfg, err := DecodeConfig(config.BaseConfig{}, map[string]any{})
c.Assert(err, qt.IsNil)
c.Assert(cfg, qt.DeepEquals, DefaultConfig)
_, err = cfg.Compile()
c.Assert(err, qt.IsNil)
cfg, err = DecodeConfig(config.BaseConfig{}, map[string]any{
"cache": map[string]any{
"polls": []map[string]any{
{"disable": true},
},
},
})
c.Assert(err, qt.IsNil)
_, err = cfg.Compile()
c.Assert(err, qt.IsNil)
}
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"
+8 -7
View File
@@ -26,6 +26,7 @@ import (
"path/filepath"
"reflect"
"regexp"
"slices"
"sort"
"strings"
"sync"
@@ -102,7 +103,7 @@ func (c *Inspector) MethodsFromTypes(include []reflect.Type, exclude []reflect.T
}
for _, t := range include {
for i := 0; i < t.NumMethod(); i++ {
for i := range t.NumMethod() {
m := t.Method(i)
if excludes[m.Name] || seen[m.Name] {
@@ -122,7 +123,7 @@ func (c *Inspector) MethodsFromTypes(include []reflect.Type, exclude []reflect.T
method := Method{Owner: t, OwnerName: ownerName, Name: m.Name}
for i := 0; i < numIn; i++ {
for i := range numIn {
in := m.Type.In(i)
name, pkg := nameAndPackage(in)
@@ -137,7 +138,7 @@ func (c *Inspector) MethodsFromTypes(include []reflect.Type, exclude []reflect.T
numOut := m.Type.NumOut()
if numOut > 0 {
for i := 0; i < numOut; i++ {
for i := range numOut {
out := m.Type.Out(i)
name, pkg := nameAndPackage(out)
@@ -304,7 +305,7 @@ func (m Method) inOutStr() string {
}
args := make([]string, len(m.In))
for i := 0; i < len(args); i++ {
for i := range args {
args[i] = fmt.Sprintf("arg%d", i)
}
return "(" + strings.Join(args, ", ") + ")"
@@ -316,7 +317,7 @@ func (m Method) inStr() string {
}
args := make([]string, len(m.In))
for i := 0; i < len(args); i++ {
for i := range args {
args[i] = fmt.Sprintf("arg%d %s", i, m.In[i])
}
return "(" + strings.Join(args, ", ") + ")"
@@ -339,7 +340,7 @@ func (m Method) outStrNamed() string {
}
outs := make([]string, len(m.Out))
for i := 0; i < len(outs); i++ {
for i := range outs {
outs[i] = fmt.Sprintf("o%d %s", i, m.Out[i])
}
@@ -435,7 +436,7 @@ func (m Methods) ToMarshalJSON(receiver, pkgPath string, excludes ...string) (st
// Exclude self
for i, pkgImp := range pkgImports {
if pkgImp == pkgPath {
pkgImports = append(pkgImports[:i], pkgImports[i+1:]...)
pkgImports = slices.Delete(pkgImports, i, i+1)
}
}
}
+6 -8
View File
@@ -101,8 +101,8 @@ type configKey struct {
// This is the root command.
type rootCommand struct {
Printf func(format string, v ...interface{})
Println func(a ...interface{})
Printf func(format string, v ...any)
Println func(a ...any)
StdOut io.Writer
StdErr io.Writer
@@ -401,9 +401,7 @@ func (r *rootCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args
watchGroups := helpers.ExtractAndGroupRootPaths(watchDirs)
for _, group := range watchGroups {
r.Printf("Watching for changes in %s\n", group)
}
r.Printf("Watching for changes in %s\n", strings.Join(watchGroups, ", "))
watcher, err := b.newWatcher(r.poll, watchDirs...)
if err != nil {
return err
@@ -431,12 +429,12 @@ func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
// Used by mkcert (server).
log.SetOutput(r.StdOut)
r.Printf = func(format string, v ...interface{}) {
r.Printf = func(format string, v ...any) {
if !r.quiet {
fmt.Fprintf(r.StdOut, format, v...)
}
}
r.Println = func(a ...interface{}) {
r.Println = func(a ...any) {
if !r.quiet {
fmt.Fprintln(r.StdOut, a...)
}
@@ -448,7 +446,7 @@ func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
return err
}
// Set up the global logger early to allow info deprecations during config load.
loggers.InitGlobalLogger(r.logger.Level(), false)
loggers.SetGlobalLogger(r.logger)
r.changesFromBuild = make(chan []identity.Identity, 10)
+9 -8
View File
@@ -23,8 +23,9 @@ 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"
"github.com/gohugoio/hugo/parser"
"github.com/gohugoio/hugo/parser/metadecoders"
@@ -71,7 +72,7 @@ func (c *configCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, arg
return fmt.Errorf("language %q not found", c.lang)
}
} else {
config = conf.configs.LanguageConfigSlice[0]
config = conf.configs.LanguageConfigMap[conf.configs.Base.DefaultContentLanguage]
}
var buf bytes.Buffer
@@ -90,11 +91,11 @@ func (c *configCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, arg
os.Stdout.Write(buf.Bytes())
default:
// Decode the JSON to a map[string]interface{} and then unmarshal it again to the correct format.
var m map[string]interface{}
var m map[string]any
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)
@@ -128,9 +129,9 @@ func (c *configCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
}
type configModMount struct {
Source string `json:"source"`
Target string `json:"target"`
Lang string `json:"lang,omitempty"`
Source string `json:"source"`
Target string `json:"target"`
Sites sitesmatrix.Sites `json:"sites,omitzero"`
}
type configModMounts struct {
@@ -146,7 +147,7 @@ func (m *configModMounts) MarshalJSON() ([]byte, error) {
mounts = append(mounts, configModMount{
Source: mount.Source,
Target: mount.Target,
Lang: mount.Lang,
Sites: mount.Sites,
})
}
-1
View File
@@ -12,7 +12,6 @@
// limitations under the License.
//go:build withdeploy
// +build withdeploy
package commands
-1
View File
@@ -12,7 +12,6 @@
// limitations under the License.
//go:build !withdeploy
// +build !withdeploy
// Copyright 2024 The Hugo Authors. All rights reserved.
//
+28 -8
View File
@@ -21,12 +21,14 @@ import (
"os"
"path"
"path/filepath"
"slices"
"strings"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/formatters/html"
"github.com/alecthomas/chroma/v2/styles"
"github.com/bep/simplecobra"
"github.com/goccy/go-yaml"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/docshelper"
"github.com/gohugoio/hugo/helpers"
@@ -35,7 +37,6 @@ import (
"github.com/gohugoio/hugo/parser"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
"gopkg.in/yaml.v2"
)
func newGenCommand() *genCommand {
@@ -49,6 +50,8 @@ func newGenCommand() *genCommand {
highlightStyle string
lineNumbersInlineStyle string
lineNumbersTableStyle string
omitEmpty bool
omitClassComments bool
)
newChromaStyles := func() simplecobra.Commander {
@@ -60,6 +63,10 @@ func newGenCommand() *genCommand {
See https://xyproto.github.io/splash/docs/all.html for a preview of the available styles`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
style = strings.ToLower(style)
if !slices.Contains(styles.Names(), style) {
return fmt.Errorf("invalid style: %s", style)
}
builder := styles.Get(style).Builder()
if highlightStyle != "" {
builder.Add(chroma.LineHighlight, highlightStyle)
@@ -74,7 +81,16 @@ See https://xyproto.github.io/splash/docs/all.html for a preview of the availabl
if err != nil {
return err
}
formatter := html.New(html.WithAllClasses(true))
if omitEmpty {
// See https://github.com/alecthomas/chroma/commit/5b2a4c5a26c503c79bc86ba3c4ae5b330028bd3d
hugo.Deprecate("--omitEmpty", "Flag is no longer needed, empty classes are now always omitted.", "v0.149.0")
}
options := []html.Option{
html.WithCSSComments(!omitClassComments),
}
formatter := html.New(options...)
w := os.Stdout
fmt.Fprintf(w, "/* Generated using: hugo %s */\n\n", strings.Join(os.Args[1:], " "))
formatter.WriteCSS(w, style)
@@ -90,6 +106,10 @@ See https://xyproto.github.io/splash/docs/all.html for a preview of the availabl
_ = cmd.RegisterFlagCompletionFunc("lineNumbersInlineStyle", cobra.NoFileCompletions)
cmd.PersistentFlags().StringVar(&lineNumbersTableStyle, "lineNumbersTableStyle", "", `foreground and background colors for table line numbers, e.g. --lineNumbersTableStyle "#fff000 bg:#000fff"`)
_ = cmd.RegisterFlagCompletionFunc("lineNumbersTableStyle", cobra.NoFileCompletions)
cmd.PersistentFlags().BoolVar(&omitEmpty, "omitEmpty", false, `omit empty CSS rules (deprecated, no longer needed)`)
_ = cmd.RegisterFlagCompletionFunc("omitEmpty", cobra.NoFileCompletions)
cmd.PersistentFlags().BoolVar(&omitClassComments, "omitClassComments", false, `omit CSS class comment prefixes in the generated CSS`)
_ = cmd.RegisterFlagCompletionFunc("omitClassComments", cobra.NoFileCompletions)
},
}
}
@@ -144,7 +164,7 @@ url: %s
return &simpleCommand{
name: "doc",
short: "Generate Markdown documentation for the Hugo CLI.",
short: "Generate Markdown documentation for the Hugo CLI",
long: `Generate Markdown documentation for the Hugo CLI.
This command is, mostly, used to create up-to-date documentation
of Hugo's command-line interface for https://gohugo.io/.
@@ -169,13 +189,13 @@ url: %s
prepender := func(filename string) string {
name := filepath.Base(filename)
base := strings.TrimSuffix(name, path.Ext(name))
url := "/commands/" + strings.ToLower(base) + "/"
url := "/docs/reference/commands/" + strings.ToLower(base) + "/"
return fmt.Sprintf(gendocFrontmatterTemplate, strings.Replace(base, "_", " ", -1), base, url)
}
linkHandler := func(name string) string {
base := strings.TrimSuffix(name, path.Ext(name))
return "/commands/" + strings.ToLower(base) + "/"
return "/docs/reference/commands/" + strings.ToLower(base) + "/"
}
r.Println("Generating Hugo command-line documentation in", gendocdir, "...")
doc.GenMarkdownTreeCustom(cd.CobraCommand.Root(), gendocdir, prepender, linkHandler)
@@ -196,7 +216,7 @@ url: %s
newDocsHelper := func() simplecobra.Commander {
return &simpleCommand{
name: "docshelper",
short: "Generate some data files for the Hugo docs.",
short: "Generate some data files for the Hugo docs",
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
r.Println("Generate docs data to", docsHelperTarget)
@@ -217,7 +237,7 @@ url: %s
}
// Decode the JSON to a map[string]interface{} and then unmarshal it again to the correct format.
var m map[string]interface{}
var m map[string]any
if err := json.Unmarshal(buf.Bytes(), &m); err != nil {
return err
}
@@ -229,7 +249,7 @@ url: %s
return err
}
defer f.Close()
yamlEnc := yaml.NewEncoder(f)
yamlEnc := yaml.NewEncoder(f, yaml.UseSingleQuote(true), yaml.AutoInt())
if err := yamlEnc.Encode(m); err != nil {
return err
}
+1 -1
View File
@@ -76,7 +76,7 @@ func flagsToCfgWithAdditionalConfigBase(cd *simplecobra.Commandeer, cfg config.P
// Flags with a different name in the config.
keyMap := map[string]string{
"minify": "minifyOutput",
"minify": "minify.minifyOutput",
"destination": "publishDir",
"editor": "newContentEditor",
}
+52 -22
View File
@@ -27,13 +27,15 @@ import (
"sync/atomic"
"time"
"github.com/bep/debounce"
"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"
@@ -142,7 +144,7 @@ func (c *hugoBuilder) getDirList() ([]string, error) {
return nil, err
}
return helpers.UniqueStringsSorted(h.PathSpec.BaseFs.WatchFilenames()), nil
return hstrings.UniqueStringsSorted(h.PathSpec.BaseFs.WatchFilenames()), nil
}
func (c *hugoBuilder) initCPUProfile() (func(), error) {
@@ -462,7 +464,15 @@ func (c *hugoBuilder) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint
infol.Logf("removing all files from destination that don't exist in static dirs")
syncer.DeleteFilter = func(f fsync.FileInfo) bool {
return f.IsDir() && strings.HasPrefix(f.Name(), ".")
name := f.Name()
// Keep .gitignore and .gitattributes anywhere
if name == ".gitignore" || name == ".gitattributes" {
return true
}
// Keep Hugo's original dot-directory behavior
return f.IsDir() && strings.HasPrefix(name, ".")
}
}
start := time.Now()
@@ -515,6 +525,14 @@ func (c *hugoBuilder) doWithPublishDirs(f func(sourceFs *filesystems.SourceFiles
return langCount, nil
}
func (c *hugoBuilder) progressIntermediate() {
terminal.ReportProgress(c.r.StdOut, terminal.ProgressIntermediate, 0)
}
func (c *hugoBuilder) progressHidden() {
terminal.ReportProgress(c.r.StdOut, terminal.ProgressHidden, 0)
}
func (c *hugoBuilder) fullBuild(noBuildLock bool) error {
var (
g errgroup.Group
@@ -663,7 +681,20 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
var n int
for _, ev := range evs {
keep := true
if ev.Has(fsnotify.Create) || ev.Has(fsnotify.Write) {
// Write and rename operations are often followed by CHMOD.
// There may be valid use cases for rebuilding the site on CHMOD,
// but that will require more complex logic than this simple conditional.
// On OS X this seems to be related to Spotlight, see:
// https://github.com/go-fsnotify/fsnotify/issues/15
// A workaround is to put your site(s) on the Spotlight exception list,
// but that may be a little mysterious for most end users.
// So, for now, we skip reload on CHMOD.
// We do have to check for WRITE though. On slower laptops a Chmod
// could be aggregated with other important events, and we still want
// to rebuild on those
if ev.Op == fsnotify.Chmod {
keep = false
} else if ev.Has(fsnotify.Create) || ev.Has(fsnotify.Write) {
if _, err := os.Stat(ev.Name); err != nil {
keep = false
}
@@ -805,22 +836,7 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
continue
}
// Write and rename operations are often followed by CHMOD.
// There may be valid use cases for rebuilding the site on CHMOD,
// but that will require more complex logic than this simple conditional.
// On OS X this seems to be related to Spotlight, see:
// https://github.com/go-fsnotify/fsnotify/issues/15
// A workaround is to put your site(s) on the Spotlight exception list,
// but that may be a little mysterious for most end users.
// So, for now, we skip reload on CHMOD.
// We do have to check for WRITE though. On slower laptops a Chmod
// could be aggregated with other important events, and we still want
// to rebuild on those
if ev.Op&(fsnotify.Chmod|fsnotify.Write|fsnotify.Create) == fsnotify.Chmod {
continue
}
walkAdder := func(path string, f hugofs.FileMetaInfo) error {
walkAdder := func(ctx context.Context, path string, f hugofs.FileMetaInfo) error {
if f.IsDir() {
c.r.logger.Println("adding created directory to watchlist", path)
if err := watcher.Add(path); err != nil {
@@ -964,7 +980,7 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
lrl.Logf("no page to navigate to, force refresh")
livereload.ForceRefresh()
}
} else if len(otherChanges) > 0 {
} else if len(otherChanges) > 0 || len(cssChanges) > 0 {
if len(otherChanges) == 1 {
// Allow single changes to be refreshed without a full page reload.
pathToRefresh := h.PathSpec.RelURL(paths.ToSlashTrimLeading(otherChanges[0]), false)
@@ -974,6 +990,9 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
lrl.Logf("force refresh")
livereload.ForceRefresh()
}
} else {
lrl.Logf("force refresh")
livereload.ForceRefresh()
}
if len(cssChanges) > 0 {
@@ -1026,6 +1045,17 @@ func (c *hugoBuilder) hugoTry() *hugolib.HugoSites {
}
func (c *hugoBuilder) loadConfig(cd *simplecobra.Commandeer, running bool) error {
if terminal.PrintANSIColors(os.Stdout) {
defer c.progressHidden()
// If the configuration takes a while to load, we want to show some progress.
// This is typically loading of external modules.
d := debounce.New(500 * time.Millisecond)
d(func() {
c.progressIntermediate()
})
defer d(func() {})
}
cfg := config.New()
cfg.Set("renderToMemory", c.r.renderToMemory)
watch := c.r.buildWatch || (c.s != nil && c.s.serverWatch)
@@ -1047,7 +1077,7 @@ func (c *hugoBuilder) loadConfig(cd *simplecobra.Commandeer, running bool) error
}
cfg.Set("environment", c.r.environment)
cfg.Set("internal", maps.Params{
cfg.Set("internal", hmaps.Params{
"running": running,
"watch": watch,
"verbose": c.r.isVerbose(),
+4 -4
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
}
@@ -427,7 +427,7 @@ func (c *importCommand) importFromJekyll(args []string) error {
c.r.Println("Importing...")
fileCount := 0
callback := func(path string, fi hugofs.FileMetaInfo) error {
callback := func(ctx context.Context, path string, fi hugofs.FileMetaInfo) error {
if fi.IsDir() {
return nil
}
+14 -14
View File
@@ -44,16 +44,16 @@ func newModCommands() *modCommands {
npmCommand := &simpleCommand{
name: "npm",
short: "Various npm helpers.",
short: "Various npm helpers",
long: `Various npm (Node package manager) helpers.`,
commands: []simplecobra.Commander{
&simpleCommand{
name: "pack",
short: "Experimental: Prepares and writes a composite package.json file for your project.",
short: "Experimental: Prepares and writes a composite package.json file for your project",
long: `Prepares and writes a composite package.json file for your project.
On first run it creates a "package.hugo.json" in the project root if not already there. This file will be used as a template file
with the base dependency set.
with the base dependency set.
This set will be merged with all "package.hugo.json" files found in the dependency tree, picking the version closest to the project.
@@ -80,12 +80,12 @@ so this may/will change in future versions of Hugo.
commands: []simplecobra.Commander{
&simpleCommand{
name: "init",
short: "Initialize this project as a Hugo Module.",
short: "Initialize this project as a Hugo Module",
long: `Initialize this project as a Hugo Module.
It will try to guess the module path, but you may help by passing it as an argument, e.g:
hugo mod init github.com/gohugoio/testshortcodes
Note that Hugo Modules supports multi-module projects, so you can initialize a Hugo Module
inside a subfolder on GitHub, as one example.
`,
@@ -111,7 +111,7 @@ so this may/will change in future versions of Hugo.
},
&simpleCommand{
name: "verify",
short: "Verify dependencies.",
short: "Verify dependencies",
long: `Verify checks that the dependencies of the current module, which are stored in a local downloaded source cache, have not been modified since being downloaded.`,
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
@@ -129,7 +129,7 @@ so this may/will change in future versions of Hugo.
},
&simpleCommand{
name: "graph",
short: "Print a module dependency graph.",
short: "Print a module dependency graph",
long: `Print a module dependency graph with information about module status (disabled, vendored).
Note that for vendored modules, that is the version listed and not the one from go.mod.
`,
@@ -149,7 +149,7 @@ Note that for vendored modules, that is the version listed and not the one from
},
&simpleCommand{
name: "clean",
short: "Delete the Hugo Module cache for the current project.",
short: "Delete the Hugo Module cache for the current project",
long: `Delete the Hugo Module cache for the current project.`,
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
@@ -175,7 +175,7 @@ Note that for vendored modules, that is the version listed and not the one from
},
&simpleCommand{
name: "tidy",
short: "Remove unused entries in go.mod and go.sum.",
short: "Remove unused entries in go.mod and go.sum",
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
applyLocalFlagsBuildConfig(cmd, r)
@@ -190,7 +190,7 @@ Note that for vendored modules, that is the version listed and not the one from
},
&simpleCommand{
name: "vendor",
short: "Vendor all module dependencies into the _vendor directory.",
short: "Vendor all module dependencies into the _vendor directory",
long: `Vendor all module dependencies into the _vendor directory.
If a module is vendored, that is where Hugo will look for it's dependencies.
`,
@@ -209,16 +209,16 @@ Note that for vendored modules, that is the version listed and not the one from
&simpleCommand{
name: "get",
short: "Resolves dependencies in your current Hugo Project.",
short: "Resolves dependencies in your current Hugo project",
long: `
Resolves dependencies in your current Hugo Project.
Resolves dependencies in your current Hugo project.
Some examples:
Install the latest version possible for a given module:
hugo mod get github.com/gohugoio/testshortcodes
Install a specific version:
hugo mod get github.com/gohugoio/testshortcodes@v0.3.0
+17 -12
View File
@@ -53,7 +53,9 @@ Ensure you run this within the root directory of your site.`,
if len(args) < 1 {
return newUserError("path needs to be provided")
}
h, err := r.Hugo(flagsToCfg(cd, nil))
cfg := flagsToCfg(cd, nil)
cfg.Set("BuildFuture", true)
h, err := r.Hugo(cfg)
if err != nil {
return err
}
@@ -76,10 +78,8 @@ Ensure you run this within the root directory of your site.`,
&simpleCommand{
name: "site",
use: "site [path]",
short: "Create a new site (skeleton)",
long: `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.`,
short: "Create a new site",
long: `Create a new site at the specified path.`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
if len(args) < 1 {
return newUserError("path needs to be provided")
@@ -124,11 +124,9 @@ Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
&simpleCommand{
name: "theme",
use: "theme [name]",
short: "Create a new theme (skeleton)",
long: `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.`,
short: "Create a new theme",
long: `Create a new theme with the specified name in the ./themes directory.
This generates a functional theme including template examples and sample content.`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
if len(args) < 1 {
return newUserError("theme name needs to be provided")
@@ -144,7 +142,7 @@ according to your needs.`,
createpath := paths.AbsPathify(conf.configs.Base.WorkingDir, filepath.Join(conf.configs.Base.ThemesDir, args[0]))
r.Println("Creating new theme in", createpath)
err = skeletons.CreateTheme(createpath, sourceFs)
err = skeletons.CreateTheme(createpath, sourceFs, format)
if err != nil {
return err
}
@@ -152,7 +150,14 @@ according to your needs.`,
return nil
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return []string{}, cobra.ShellCompDirectiveNoFileComp
}
return []string{}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveFilterDirs
}
cmd.Flags().StringVar(&format, "format", "toml", "preferred file format (toml, yaml or json)")
_ = cmd.RegisterFlagCompletionFunc("format", cobra.FixedCompletions([]string{"toml", "yaml", "json"}, cobra.ShellCompDirectiveNoFileComp))
},
},
},
+1 -1
View File
@@ -32,7 +32,7 @@ func newReleaseCommand() simplecobra.Commander {
return &simpleCommand{
name: "release",
short: "Release a new version of Hugo.",
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)
if err != nil {
+95 -69
View File
@@ -23,6 +23,7 @@ import (
"errors"
"fmt"
"io"
"maps"
"net"
"net/http"
_ "net/http/pprof"
@@ -48,6 +49,9 @@ import (
"github.com/fsnotify/fsnotify"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/tpl/tplimpl"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/common/urls"
@@ -57,7 +61,6 @@ import (
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/hugolib/filesystems"
"github.com/gohugoio/hugo/livereload"
"github.com/gohugoio/hugo/tpl"
"github.com/gohugoio/hugo/transform"
"github.com/gohugoio/hugo/transform/livereloadinject"
"github.com/spf13/afero"
@@ -84,6 +87,10 @@ const (
configChangeGoWork = "go work file"
)
const (
hugoHeaderRedirect = "X-Hugo-Redirect"
)
func newHugoBuilder(r *rootCommand, s *serverCommand, onConfigLoaded ...func(reloaded bool) error) *hugoBuilder {
var visitedURLs *types.EvictingQueue[string]
if s != nil && !s.disableFastRender {
@@ -115,7 +122,7 @@ func newServerCommand() *serverCommand {
commands: []simplecobra.Commander{
&simpleCommand{
name: "trust",
short: "Install the local CA in the system trust store.",
short: "Install the local CA in the system trust store",
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
action := "-install"
if uninstall {
@@ -191,9 +198,7 @@ func (f *fileChangeDetector) PrepareNew() {
}
f.prev = make(map[string]uint64)
for k, v := range f.current {
f.prev[k] = v
}
maps.Copy(f.prev, f.current)
f.current = make(map[string]uint64)
}
@@ -307,67 +312,71 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
w.Header().Set(header.Key, header.Value)
}
if redirect := serverConfig.MatchRedirect(requestURI); !redirect.IsZero() {
// fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))
doRedirect := true
// This matches Netlify's behavior and is needed for SPA behavior.
// See https://docs.netlify.com/routing/redirects/rewrites-proxies/
if !redirect.Force {
path := filepath.Clean(strings.TrimPrefix(requestURI, baseURL.Path()))
if root != "" {
path = filepath.Join(root, path)
}
var fs afero.Fs
f.c.withConf(func(conf *commonConfig) {
fs = conf.fs.PublishDirServer
})
fi, err := fs.Stat(path)
if err == nil {
if fi.IsDir() {
// There will be overlapping directories, so we
// need to check for a file.
_, err = fs.Stat(filepath.Join(path, "index.html"))
doRedirect = err != nil
} else {
doRedirect = false
if canRedirect(requestURI, r) {
if redirect := serverConfig.MatchRedirect(requestURI, r.Header); !redirect.IsZero() {
doRedirect := true
// This matches Netlify's behavior and is needed for SPA behavior.
// See https://docs.netlify.com/routing/redirects/rewrites-proxies/
if !redirect.Force {
path := filepath.Clean(strings.TrimPrefix(requestURI, baseURL.Path()))
if root != "" {
path = filepath.Join(root, path)
}
}
}
var fs afero.Fs
f.c.withConf(func(conf *commonConfig) {
fs = conf.fs.PublishDirServer
})
fi, err := fs.Stat(path)
if doRedirect {
switch redirect.Status {
case 404:
w.WriteHeader(404)
file, err := fs.Open(strings.TrimPrefix(redirect.To, baseURL.Path()))
if err == nil {
defer file.Close()
io.Copy(w, file)
} else {
fmt.Fprintln(w, "<h1>Page Not Found</h1>")
if fi.IsDir() {
// There will be overlapping directories, so we
// need to check for a file.
_, err = fs.Stat(filepath.Join(path, "index.html"))
doRedirect = err != nil
} else {
doRedirect = false
}
}
return
case 200:
if r2 := f.rewriteRequest(r, strings.TrimPrefix(redirect.To, baseURL.Path())); r2 != nil {
requestURI = redirect.To
r = r2
}
default:
w.Header().Set("Content-Type", "")
http.Redirect(w, r, redirect.To, redirect.Status)
return
}
if doRedirect {
w.Header().Set(hugoHeaderRedirect, "true")
switch redirect.Status {
case 404:
w.WriteHeader(404)
file, err := fs.Open(strings.TrimPrefix(redirect.To, baseURL.Path()))
if err == nil {
defer file.Close()
io.Copy(w, file)
} else {
fmt.Fprintln(w, "<h1>Page Not Found</h1>")
}
return
case 200:
if r2 := f.rewriteRequest(r, strings.TrimPrefix(redirect.To, baseURL.Path())); r2 != nil {
requestURI = redirect.To
r = r2
}
default:
w.Header().Set("Content-Type", "")
http.Redirect(w, r, redirect.To, redirect.Status)
return
}
}
}
}
if f.c.fastRenderMode && f.c.errState.buildErr() == nil {
// Sec-Fetch-Mode should be sent by all recent browser versions, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Mode#navigate
// Fall back to the file extension if not set.
// The main take here is that we don't want to have CSS/JS files etc. partake in this logic.
if r.Header.Get("Sec-Fetch-Mode") == "navigate" || strings.HasSuffix(requestURI, "/") || strings.HasSuffix(requestURI, "html") || strings.HasSuffix(requestURI, "htm") {
if isNavigation(requestURI, r) {
// See issue 14240.
// Hugo escapes the URL paths when generating them,
// that may not be the case when we receive it back from the browser.
// PathEscape will escape if it is not already escaped.
requestURI = paths.PathEscape(requestURI)
if !f.c.visitedURLs.Contains(requestURI) {
// If not already on stack, re-render that single page.
if err := f.c.partialReRender(requestURI); err != nil {
@@ -490,9 +499,7 @@ func (c *serverCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, arg
watchGroups := helpers.ExtractAndGroupRootPaths(watchDirs)
for _, group := range watchGroups {
c.r.Printf("Watching for changes in %s\n", group)
}
c.r.Printf("Watching for changes in %s\n", strings.Join(watchGroups, ", "))
watcher, err := c.newWatcher(c.r.poll, watchDirs...)
if err != nil {
return err
@@ -757,7 +764,7 @@ func (c *serverCommand) createServerPorts(cd *simplecobra.Commandeer) error {
c.serverPorts = make([]serverPortListener, len(conf.configs.Languages))
}
currentServerPort := c.serverPort
for i := 0; i < len(c.serverPorts); i++ {
for i := range c.serverPorts {
l, err := net.Listen("tcp", net.JoinHostPort(c.serverInterface, strconv.Itoa(currentServerPort)))
if err == nil {
c.serverPorts[i] = serverPortListener{ln: l, p: currentServerPort}
@@ -878,7 +885,7 @@ func (c *serverCommand) serve() error {
if isMultihost {
for _, l := range conf.configs.ConfigLangs() {
baseURLs = append(baseURLs, l.BaseURL())
roots = append(roots, l.Language().Lang)
roots = append(roots, l.Language().(*langs.Language).Lang)
}
} else {
l := conf.configs.GetFirstLanguageConfig()
@@ -896,16 +903,16 @@ func (c *serverCommand) serve() error {
// To allow the en user to change the error template while the server is running, we use
// the freshest template we can provide.
var (
errTempl tpl.Template
templHandler tpl.TemplateHandler
errTempl *tplimpl.TemplInfo
templHandler *tplimpl.TemplateStore
)
getErrorTemplateAndHandler := func(h *hugolib.HugoSites) (tpl.Template, tpl.TemplateHandler) {
getErrorTemplateAndHandler := func(h *hugolib.HugoSites) (*tplimpl.TemplInfo, *tplimpl.TemplateStore) {
if h == nil {
return errTempl, templHandler
}
templHandler := h.Tmpl()
errTempl, found := templHandler.Lookup("_server/error.html")
if !found {
templHandler := h.GetTemplateStore()
errTempl := templHandler.LookupByPath("/_server/error.html")
if errTempl == nil {
panic("template server/error.html not found")
}
return errTempl, templHandler
@@ -1033,7 +1040,6 @@ func (c *serverCommand) serve() error {
defer cancel()
wg2, ctx := errgroup.WithContext(ctx)
for _, srv := range servers {
srv := srv
wg2.Go(func() error {
return srv.Shutdown(ctx)
})
@@ -1165,7 +1171,6 @@ func chmodFilter(dst, src os.FileInfo) bool {
}
func cleanErrorLog(content string) string {
content = strings.ReplaceAll(content, "\n", " ")
content = logReplacer.Replace(content)
content = logDuplicateTemplateExecuteRe.ReplaceAllString(content, "")
content = logDuplicateTemplateParseRe.ReplaceAllString(content, "")
@@ -1233,3 +1238,24 @@ func formatByteCount(b uint64) string {
return fmt.Sprintf("%.1f %cB",
float64(b)/float64(div), "kMGTPE"[exp])
}
func canRedirect(requestURIWithoutQuery string, r *http.Request) bool {
if r.Header.Get(hugoHeaderRedirect) != "" {
return false
}
return isNavigation(requestURIWithoutQuery, r)
}
// Sec-Fetch-Mode should be sent by all recent browser versions, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Mode#navigate
// Fall back to the file extension if not set.
// The main take here is that we don't want to have CSS/JS files etc. partake in this logic.
func isNavigation(requestURIWithoutQuery string, r *http.Request) bool {
return r.Header.Get("Sec-Fetch-Mode") == "navigate" || isPropablyHTMLRequest(requestURIWithoutQuery)
}
func isPropablyHTMLRequest(requestURIWithoutQuery string) bool {
if strings.HasSuffix(requestURIWithoutQuery, "/") || strings.HasSuffix(requestURIWithoutQuery, "html") || strings.HasSuffix(requestURIWithoutQuery, "htm") {
return true
}
return !strings.Contains(requestURIWithoutQuery, ".")
}
+6 -18
View File
@@ -16,6 +16,8 @@ package collections
import (
"fmt"
"reflect"
"github.com/gohugoio/hugo/common/hreflect"
)
// Append appends from to a slice to and returns the resulting slice.
@@ -25,7 +27,7 @@ func Append(to any, from ...any) (any, error) {
if len(from) == 0 {
return to, nil
}
tov, toIsNil := indirect(reflect.ValueOf(to))
tov, toIsNil := hreflect.Indirect(reflect.ValueOf(to))
toIsNil = toIsNil || to == nil
var tot reflect.Type
@@ -100,7 +102,7 @@ func Append(to any, from ...any) (any, error) {
fv := reflect.ValueOf(f)
if !fv.IsValid() || !fv.Type().AssignableTo(tot) {
// Fall back to a []interface{} slice.
tov, _ := indirect(reflect.ValueOf(to))
tov, _ := hreflect.Indirect(reflect.ValueOf(to))
return appendToInterfaceSlice(tov, from...)
}
tov = reflect.Append(tov, fv)
@@ -117,7 +119,7 @@ func appendToInterfaceSliceFromValues(slice1, slice2 reflect.Value) ([]any, erro
tos = append(tos, nil)
continue
}
for i := 0; i < slice.Len(); i++ {
for i := range slice.Len() {
tos = append(tos, slice.Index(i).Interface())
}
}
@@ -128,7 +130,7 @@ func appendToInterfaceSliceFromValues(slice1, slice2 reflect.Value) ([]any, erro
func appendToInterfaceSlice(tov reflect.Value, from ...any) ([]any, error) {
var tos []any
for i := 0; i < tov.Len(); i++ {
for i := range tov.Len() {
tos = append(tos, tov.Index(i).Interface())
}
@@ -136,17 +138,3 @@ func appendToInterfaceSlice(tov reflect.Value, from ...any) ([]any, error) {
return tos, nil
}
// indirect is borrowed from the Go stdlib: 'text/template/exec.go'
// TODO(bep) consolidate
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
if v.IsNil() {
return v, true
}
if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
break
}
}
return v, false
}
+1
View File
@@ -77,6 +77,7 @@ func TestAppend(t *testing.T) {
{[]string{"a", "b"}, []any{nil}, []any{"a", "b", nil}},
{[]string{"a", "b"}, []any{nil, "d", nil}, []any{"a", "b", nil, "d", nil}},
{[]any{"a", nil, "c"}, []any{"d", nil, "f"}, []any{"a", nil, "c", "d", nil, "f"}},
{[]string{"a", "b"}, []any{}, []string{"a", "b"}},
} {
result, err := Append(test.start, test.addend...)
+34
View File
@@ -136,3 +136,37 @@ func TestSortedStringSlice(t *testing.T) {
c.Assert(s.Count("z"), qt.Equals, 0)
c.Assert(s.Count("a"), qt.Equals, 1)
}
func TestStringSliceToInterfaceSlice(t *testing.T) {
t.Parallel()
c := qt.New(t)
tests := []struct {
name string
in []string
want []any
}{
{
name: "empty slice",
in: []string{},
want: []any{},
},
{
name: "single element",
in: []string{"hello"},
want: []any{"hello"},
},
{
name: "multiple elements",
in: []string{"a", "b", "c"},
want: []any{"a", "b", "c"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := StringSliceToInterfaceSlice(tt.in)
c.Assert(got, qt.DeepEquals, tt.want)
})
}
}
+67 -12
View File
@@ -13,26 +13,32 @@
package collections
import "sync"
import (
"iter"
"slices"
"sync"
// Stack is a simple LIFO stack that is safe for concurrent use.
type Stack[T any] struct {
"github.com/gohugoio/hugo/common/hiter"
)
// 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 {
@@ -43,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 {
@@ -52,13 +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)
}
func (s *Stack[T]) Drain() []T {
// All returns all items in the stack, from bottom to top.
func (s *StackThreadSafe[T]) All() iter.Seq2[int, T] {
return hiter.Lock2(slices.All(s.items), s.mu.RLock, s.mu.RUnlock)
}
func (s *StackThreadSafe[T]) Drain() []T {
s.mu.Lock()
defer s.mu.Unlock()
items := s.items
@@ -66,15 +77,59 @@ 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
for i := len(s.items) - 1; i >= 0; i-- {
if predicate(s.items[i]) {
items = append(items, s.items[i])
s.items = append(s.items[:i], s.items[i+1:]...)
s.items = slices.Delete(s.items, i, i+1)
}
}
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
}
+77
View File
@@ -0,0 +1,77 @@
package collections
import (
"testing"
qt "github.com/frankban/quicktest"
)
func TestNewStack(t *testing.T) {
t.Parallel()
c := qt.New(t)
s := NewStackThreadSafe[int]()
c.Assert(s, qt.IsNotNil)
}
func TestStackBasic(t *testing.T) {
t.Parallel()
c := qt.New(t)
s := NewStackThreadSafe[int]()
c.Assert(s.Len(), qt.Equals, 0)
s.Push(1)
s.Push(2)
s.Push(3)
c.Assert(s.Len(), qt.Equals, 3)
top, ok := s.Peek()
c.Assert(ok, qt.Equals, true)
c.Assert(top, qt.Equals, 3)
popped, ok := s.Pop()
c.Assert(ok, qt.Equals, true)
c.Assert(popped, qt.Equals, 3)
c.Assert(s.Len(), qt.Equals, 2)
_, _ = s.Pop()
_, _ = s.Pop()
_, ok = s.Pop()
c.Assert(ok, qt.Equals, false)
}
func TestStackDrain(t *testing.T) {
t.Parallel()
c := qt.New(t)
s := NewStackThreadSafe[string]()
s.Push("a")
s.Push("b")
got := s.Drain()
c.Assert(got, qt.DeepEquals, []string{"a", "b"})
c.Assert(s.Len(), qt.Equals, 0)
}
func TestStackDrainMatching(t *testing.T) {
t.Parallel()
c := qt.New(t)
s := NewStackThreadSafe[int]()
s.Push(1)
s.Push(2)
s.Push(3)
s.Push(4)
got := s.DrainMatching(func(v int) bool { return v%2 == 0 })
c.Assert(got, qt.DeepEquals, []int{4, 2})
c.Assert(s.Drain(), qt.DeepEquals, []int{1, 3})
}
+2
View File
@@ -23,6 +23,8 @@ const (
WarnFrontMatterParamsOverrides = "warning-frontmatter-params-overrides"
WarnRenderShortcodesInHTML = "warning-rendershortcodes-in-html"
WarnGoldmarkRawHTML = "warning-goldmark-raw-html"
WarnPartialSuperfluousPrefix = "warning-partial-superfluous-prefix"
WarnHomePageIsLeafBundle = "warning-home-page-is-leaf-bundle"
)
// Field/method names with special meaning.
+34 -3
View File
@@ -23,6 +23,7 @@ import (
"github.com/cespare/xxhash/v2"
"github.com/gohugoio/hashstructure"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/identity"
)
@@ -38,6 +39,34 @@ func XXHashFromReader(r io.Reader) (uint64, int64, error) {
return h.Sum64(), size, nil
}
type Hasher interface {
io.StringWriter
io.Writer
io.ReaderFrom
Sum64() uint64
}
type HashCloser interface {
Hasher
io.Closer
}
// XxHasher returns a Hasher that uses xxHash.
// Remember to call Close when done.
func XxHasher() HashCloser {
h := getXxHashReadFrom()
return struct {
Hasher
io.Closer
}{
Hasher: h,
Closer: hugio.CloserFunc(func() error {
putXxHashReadFrom(h)
return nil
}),
}
}
// XxHashFromReaderHexEncoded calculates the xxHash for the given reader
// and returns the hash as a hex encoded string.
func XxHashFromReaderHexEncoded(r io.Reader) (string, error) {
@@ -58,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)
}
+12 -15
View File
@@ -37,12 +37,11 @@ func TestXxHashFromReaderPara(t *testing.T) {
c := qt.New(t)
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
i := i
for i := range 10 {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 100; j++ {
for j := range 100 {
s := strings.Repeat("Hello ", i+j+1*42)
r := strings.NewReader(s)
got, size, err := XXHashFromReader(r)
@@ -75,8 +74,8 @@ func TestXxHashFromStringHexEncoded(t *testing.T) {
func BenchmarkXXHashFromReader(b *testing.B) {
r := strings.NewReader("Hello World")
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
XXHashFromReader(r)
r.Seek(0, 0)
}
@@ -84,16 +83,16 @@ func BenchmarkXXHashFromReader(b *testing.B) {
func BenchmarkXXHashFromString(b *testing.B) {
s := "Hello World"
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
XXHashFromString(s)
}
}
func BenchmarkXXHashFromStringHexEncoded(b *testing.B) {
s := "The quick brown fox jumps over the lazy dog"
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
XxHashFromStringHexEncoded(s)
}
}
@@ -136,7 +135,7 @@ func BenchmarkHashString(b *testing.B) {
for _, test := range tests {
b.Run(fmt.Sprintf("n%d", len(test)), func(b *testing.B) {
for i := 0; i < b.N; i++ {
for b.Loop() {
HashString(test)
}
})
@@ -144,14 +143,12 @@ func BenchmarkHashString(b *testing.B) {
}
func BenchmarkHashMap(b *testing.B) {
m := map[string]interface{}{}
for i := 0; i < 1000; i++ {
m := map[string]any{}
for i := range 1000 {
m[fmt.Sprintf("key%d", i)] = i
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
HashString(m)
}
}
-46
View File
@@ -1,46 +0,0 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hcontext
import "context"
// ContextDispatcher is a generic interface for setting and getting values from a context.
type ContextDispatcher[T any] interface {
Set(ctx context.Context, value T) context.Context
Get(ctx context.Context) T
}
// NewContextDispatcher creates a new ContextDispatcher with the given key.
func NewContextDispatcher[T any, R comparable](key R) ContextDispatcher[T] {
return keyInContext[T, R]{
id: key,
}
}
type keyInContext[T any, R comparable] struct {
zero T
id R
}
func (f keyInContext[T, R]) Get(ctx context.Context) T {
v := ctx.Value(f.id)
if v == nil {
return f.zero
}
return v.(T)
}
func (f keyInContext[T, R]) Set(ctx context.Context, value T) context.Context {
return context.WithValue(ctx, f.id, value)
}
+61
View File
@@ -0,0 +1,61 @@
// 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 hdebug
import (
"fmt"
"strings"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/htesting"
)
// Printf is a debug print function that should be removed before committing code to the repository.
func Printf(format string, args ...any) {
panicIfRealCI()
if len(args) == 1 && !strings.Contains(format, "%") {
format = format + ": %v"
}
if !strings.HasSuffix(format, "\n") {
format = format + "\n"
}
fmt.Printf(format, args...)
}
func AssertNotNil(a ...any) {
panicIfRealCI()
for _, v := range a {
if types.IsNil(v) {
panic("hdebug.AssertNotNil: value is nil")
}
}
}
func Panicf(format string, args ...any) {
panicIfRealCI()
// fmt.Println(stack())
if len(args) == 1 && !strings.Contains(format, "%") {
format = format + ": %v"
}
if !strings.HasSuffix(format, "\n") {
format = format + "\n"
}
panic(fmt.Sprintf(format, args...))
}
func panicIfRealCI() {
if htesting.IsRealCI() {
panic("This debug statement should be removed before committing code!")
}
}
+2 -8
View File
@@ -152,10 +152,7 @@ func locateError(r io.Reader, le FileError, matches LineMatcherFn) *ErrorContext
}
if ectx.Position.LineNumber > 0 {
low := ectx.Position.LineNumber - 3
if low < 0 {
low = 0
}
low := max(ectx.Position.LineNumber-3, 0)
if ectx.Position.LineNumber > 2 {
ectx.LinesPos = 2
@@ -163,10 +160,7 @@ func locateError(r io.Reader, le FileError, matches LineMatcherFn) *ErrorContext
ectx.LinesPos = ectx.Position.LineNumber - 1
}
high := ectx.Position.LineNumber + 2
if high > len(lines) {
high = len(lines)
}
high := min(ectx.Position.LineNumber+2, len(lines))
ectx.Lines = lines[low:high]
+3 -10
View File
@@ -17,22 +17,13 @@ package herrors
import (
"errors"
"fmt"
"io"
"os"
"regexp"
"runtime"
"runtime/debug"
"strings"
"time"
)
// PrintStackTrace prints the current stacktrace to w.
func PrintStackTrace(w io.Writer) {
buf := make([]byte, 1<<16)
runtime.Stack(buf, true)
fmt.Fprintf(w, "%s", buf)
}
// ErrorSender is a, typically, non-blocking error handler.
type ErrorSender interface {
SendError(err error)
@@ -45,7 +36,9 @@ type ErrorSender interface {
func Recover(args ...any) {
if r := recover(); r != nil {
fmt.Println("ERR:", r)
args = append(args, "stacktrace from panic: \n"+string(debug.Stack()), "\n")
buf := make([]byte, 64<<10)
buf = buf[:runtime.Stack(buf, false)]
args = append(args, "stacktrace from panic: \n"+string(buf), "\n")
fmt.Println(args...)
}
}
+53 -34
View File
@@ -110,11 +110,11 @@ func (fe *fileError) UpdateContent(r io.Reader, linematcher LineMatcherFn) FileE
fe.errorContext = ectx
if ectx.Position.LineNumber > 0 {
if ectx.Position.LineNumber > 0 && ectx.Position.LineNumber > fe.position.LineNumber {
fe.position.LineNumber = ectx.Position.LineNumber
}
if ectx.Position.ColumnNumber > 0 {
if ectx.Position.ColumnNumber > 0 && ectx.Position.ColumnNumber > fe.position.ColumnNumber {
fe.position.ColumnNumber = ectx.Position.ColumnNumber
}
@@ -177,6 +177,7 @@ func NewFileErrorFromName(err error, name string) FileError {
// Filetype is used to determine the Chroma lexer to use.
fileType, pos := extractFileTypePos(err)
pos.Filename = name
if fileType == "" {
_, fileType = paths.FileAndExtNoDelimiter(filepath.Clean(name))
}
@@ -234,7 +235,9 @@ func NewFileErrorFromFile(err error, filename string, fs afero.Fs, linematcher L
return NewFileErrorFromName(err, realFilename)
}
defer f.Close()
return NewFileErrorFromName(err, realFilename).UpdateContent(f, linematcher)
fe := NewFileErrorFromName(err, realFilename)
fe = fe.UpdateContent(f, linematcher)
return fe
}
func openFile(filename string, fs afero.Fs) (afero.File, string, error) {
@@ -285,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)
@@ -321,13 +368,9 @@ func extractFileTypePos(err error) (string, text.Position) {
}
// Look in the error message for the line number.
for _, handle := range lineNumberExtractors {
lno, col := handle(err)
if lno > 0 {
pos.ColumnNumber = col
pos.LineNumber = lno
break
}
if lno, col := commonLineNumberExtractor(err); lno > 0 {
pos.ColumnNumber = col
pos.LineNumber = lno
}
if fileType == "" && pos.Filename != "" {
@@ -351,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:
+13 -3
View File
@@ -19,17 +19,27 @@ import (
)
var lineNumberExtractors = []lineNumberExtractor{
// YAML parse errors.
newLineNumberErrHandlerFromRegexp(`\[(\d+):(\d+)\]`),
// Template/shortcode parse errors
newLineNumberErrHandlerFromRegexp(`:(\d+):(\d*):`),
newLineNumberErrHandlerFromRegexp(`:(\d+):`),
// YAML parse errors
newLineNumberErrHandlerFromRegexp(`line (\d+):`),
// i18n bundle errors
newLineNumberErrHandlerFromRegexp(`\((\d+),\s(\d*)`),
}
func commonLineNumberExtractor(e error) (int, int) {
for _, handler := range lineNumberExtractors {
lno, col := handler(e)
if lno > 0 {
return lno, col
}
}
return 0, 0
}
type lineNumberExtractor func(e error) (int, int)
func newLineNumberErrHandlerFromRegexp(expression string) lineNumberExtractor {
@@ -1,4 +1,4 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
// 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.
@@ -11,27 +11,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package doctree
package herrors
import (
"errors"
"testing"
qt "github.com/frankban/quicktest"
)
func TestDimensionFlag(t *testing.T) {
func TestCommonLineNumberExtractor(t *testing.T) {
t.Parallel()
c := qt.New(t)
var zero DimensionFlag
var d DimensionFlag
var o DimensionFlag = 1
var p DimensionFlag = 12
c.Assert(d.Has(o), qt.Equals, false)
d = d.Set(o)
c.Assert(d.Has(o), qt.Equals, true)
c.Assert(d.Has(d), qt.Equals, true)
c.Assert(func() { zero.Index() }, qt.PanicMatches, "dimension flag not set")
c.Assert(DimensionLanguage.Index(), qt.Equals, 0)
c.Assert(p.Index(), qt.Equals, 11)
lno, col := commonLineNumberExtractor(errors.New("[4:9] value is not allowed in this context"))
c.Assert(lno, qt.Equals, 4)
c.Assert(col, qt.Equals, 9)
}
+8 -21
View File
@@ -27,9 +27,8 @@ import (
"sync"
"github.com/bep/logg"
"github.com/cli/safeexec"
"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"
)
@@ -103,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)](),
}
}
@@ -113,18 +112,6 @@ func IsNotFound(err error) bool {
return errors.As(err, &notFoundErr)
}
// SafeCommand is a wrapper around os/exec Command which uses a LookPath
// implementation that does not search in current directory before looking in PATH.
// See https://github.com/cli/safeexec and the linked issues.
func SafeCommand(name string, arg ...string) (*exec.Cmd, error) {
bin, err := safeexec.LookPath(name)
if err != nil {
return nil, err
}
return exec.Command(bin, arg...), nil
}
// Exec enforces a security policy for commands run via os/exec.
type Exec struct {
sc security.Config
@@ -134,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
}
@@ -197,7 +184,7 @@ func (e *Exec) Npx(name string, arg ...any) (Runner, error) {
tryFuncs := map[binaryLocation]tryFunc{
binaryLocationNodeModules: func() func(...any) (Runner, error) {
nodeBinFilename := filepath.Join(e.workingDir, nodeModulesBinPath, name)
_, err := safeexec.LookPath(nodeBinFilename)
_, err := exec.LookPath(nodeBinFilename)
if err != nil {
return nil
}
@@ -215,7 +202,7 @@ func (e *Exec) Npx(name string, arg ...any) (Runner, error) {
}
},
binaryLocationPath: func() func(...any) (Runner, error) {
if _, err := safeexec.LookPath(name); err != nil {
if _, err := exec.LookPath(name); err != nil {
return nil
}
return func(arg2 ...any) (Runner, error) {
@@ -346,7 +333,7 @@ func (c *commandeer) command(arg ...any) (*cmdWrapper, error) {
bin = c.fullyQualifiedName
} else {
var err error
bin, err = safeexec.LookPath(c.name)
bin, err = exec.LookPath(c.name)
if err != nil {
return nil, &NotFoundError{
name: c.name,
@@ -384,7 +371,7 @@ func InPath(binaryName string) bool {
if strings.Contains(binaryName, "/") {
panic("binary name should not contain any slash")
}
_, err := safeexec.LookPath(binaryName)
_, err := exec.LookPath(binaryName)
return err == nil
}
@@ -394,7 +381,7 @@ func LookPath(binaryName string) string {
if strings.Contains(binaryName, "/") {
panic("binary name should not contain any slash")
}
s, err := safeexec.LookPath(binaryName)
s, err := exec.LookPath(binaryName)
if err != nil {
return ""
}
+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
}
+66
View File
@@ -0,0 +1,66 @@
package hiter
// Common iterator functions.
// Some of these are are based on this discsussion: https://github.com/golang/go/issues/61898
import "iter"
// Concat returns an iterator over the concatenation of the sequences.
// Any nil sequences are ignored.
func Concat[V any](seqs ...iter.Seq[V]) iter.Seq[V] {
return func(yield func(V) bool) {
for _, seq := range seqs {
if seq == nil {
continue
}
for e := range seq {
if !yield(e) {
return
}
}
}
}
}
// Concat2 returns an iterator over the concatenation of the sequences.
// Any nil sequences are ignored.
func Concat2[K, V any](seqs ...iter.Seq2[K, V]) iter.Seq2[K, V] {
return func(yield func(K, V) bool) {
for _, seq := range seqs {
if seq == nil {
continue
}
for k, v := range seq {
if !yield(k, v) {
return
}
}
}
}
}
// Lock returns an iterator that locks before iterating and unlocks after.
func Lock[V any](seq iter.Seq[V], lock, unlock func()) iter.Seq[V] {
return func(yield func(V) bool) {
lock()
defer unlock()
for e := range seq {
if !yield(e) {
return
}
}
}
}
// Lock2 returns an iterator that locks before iterating and unlocks after.
func Lock2[K, V any](seq iter.Seq2[K, V], lock, unlock func()) iter.Seq2[K, V] {
return func(yield func(K, V) bool) {
lock()
defer unlock()
for k, v := range seq {
if !yield(k, v) {
return
}
}
}
}
+47 -5
View File
@@ -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"
@@ -20,13 +20,27 @@ import (
// Cache is a simple thread safe cache backed by a map.
type Cache[K comparable, T any] struct {
m map[K]T
opts CacheOptions
hasBeenInitialized bool
sync.RWMutex
}
// NewCache creates a new Cache.
// CacheOptions are the options for the Cache.
type CacheOptions struct {
// If set, the cache will not grow beyond this size.
Size uint64
}
var defaultCacheOptions = CacheOptions{}
// NewCache creates a new Cache with default options.
func NewCache[K comparable, T any]() *Cache[K, T] {
return &Cache[K, T]{m: make(map[K]T)}
return &Cache[K, T]{m: make(map[K]T), opts: defaultCacheOptions}
}
// NewCacheWithOptions creates a new Cache with the given options.
func NewCacheWithOptions[K comparable, T any](opts CacheOptions) *Cache[K, T] {
return &Cache[K, T]{m: make(map[K]T), opts: opts}
}
// Delete deletes the given key from the cache.
@@ -65,10 +79,19 @@ func (c *Cache[K, T]) GetOrCreate(key K, create func() (T, error)) (T, error) {
if err != nil {
return v, err
}
c.clearIfNeeded()
c.m[key] = v
return v, nil
}
// Contains returns whether the given key exists in the cache.
func (c *Cache[K, T]) Contains(key K) bool {
c.RLock()
_, found := c.m[key]
c.RUnlock()
return found
}
// InitAndGet initializes the cache if not already done and returns the value for the given key.
// The init state will be reset on Reset or Drain.
func (c *Cache[K, T]) InitAndGet(key K, init func(get func(key K) (T, bool), set func(key K, value T)) error) (T, error) {
@@ -108,7 +131,26 @@ func (c *Cache[K, T]) Set(key K, value T) {
c.Unlock()
}
// SetIfAbsent sets the given key to the given value if the key does not already exist in the cache.
func (c *Cache[K, T]) SetIfAbsent(key K, value T) {
c.RLock()
if _, found := c.get(key); !found {
c.RUnlock()
c.Set(key, value)
} else {
c.RUnlock()
}
}
func (c *Cache[K, T]) clearIfNeeded() {
if c.opts.Size > 0 && uint64(len(c.m)) >= c.opts.Size {
// clear the map
clear(c.m)
}
}
func (c *Cache[K, T]) set(key K, value T) {
c.clearIfNeeded()
c.m[key] = value
}
@@ -141,7 +183,7 @@ func (c *Cache[K, T]) Len() int {
func (c *Cache[K, T]) Reset() {
c.Lock()
c.m = make(map[K]T)
clear(c.m)
c.hasBeenInitialized = false
c.Unlock()
}
+57
View File
@@ -0,0 +1,57 @@
// 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 hmaps
import (
"testing"
qt "github.com/frankban/quicktest"
)
func TestCacheSize(t *testing.T) {
c := qt.New(t)
cache := NewCacheWithOptions[string, string](CacheOptions{Size: 10})
for i := range 30 {
cache.Set(string(rune('a'+i)), "value")
}
c.Assert(len(cache.m), qt.Equals, 10)
for i := 20; i < 50; i++ {
cache.GetOrCreate(string(rune('a'+i)), func() (string, error) {
return "value", nil
})
}
c.Assert(len(cache.m), qt.Equals, 10)
for i := 100; i < 200; i++ {
cache.SetIfAbsent(string(rune('a'+i)), "value")
}
c.Assert(len(cache.m), qt.Equals, 10)
cache.InitAndGet("foo", func(
get func(key string) (string, bool), set func(key string, value string),
) error {
for i := 50; i < 100; i++ {
set(string(rune('a'+i)), "value")
}
return nil
})
c.Assert(len(cache.m), qt.Equals, 10)
}
+129
View File
@@ -0,0 +1,129 @@
// 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 hmaps
import (
"iter"
"sync"
)
func NewMap[K comparable, T any]() *Map[K, T] {
return &Map[K, T]{
m: make(map[K]T),
}
}
// Map is a thread safe map backed by a Go map.
type Map[K comparable, T any] struct {
m map[K]T
mu sync.RWMutex
}
// Get gets the value for the given key.
// It returns the zero value of T if the key is not found.
func (m *Map[K, T]) Get(key K) T {
v, _ := m.Lookup(key)
return v
}
// Lookup looks up the given key in the map.
// It returns the value and a boolean indicating whether the key was found.
func (m *Map[K, T]) Lookup(key K) (T, bool) {
m.mu.RLock()
v, found := m.m[key]
m.mu.RUnlock()
return v, found
}
// GetOrCreate gets the value for the given key if it exists, or creates it if not.
func (m *Map[K, T]) GetOrCreate(key K, create func() (T, error)) (T, error) {
v, found := m.Lookup(key)
if found {
return v, nil
}
m.mu.Lock()
defer m.mu.Unlock()
v, found = m.m[key]
if found {
return v, nil
}
v, err := create()
if err != nil {
return v, err
}
m.m[key] = v
return v, nil
}
// Set sets the given key to the given value.
func (m *Map[K, T]) Set(key K, value T) {
m.mu.Lock()
m.m[key] = value
m.mu.Unlock()
}
// 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()
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.
// It returns true if the value was set, false otherwise.
func (m *Map[K, T]) SetIfAbsent(key K, value T) bool {
m.mu.RLock()
if _, found := m.m[key]; !found {
m.mu.RUnlock()
return m.doSetIfAbsent(key, value)
}
m.mu.RUnlock()
return false
}
func (m *Map[K, T]) doSetIfAbsent(key K, value T) bool {
m.mu.Lock()
defer m.mu.Unlock()
if _, found := m.m[key]; !found {
m.m[key] = value
return true
}
return false
}
// All returns an iterator over all key/value pairs in the map.
// A read lock is held during the iteration.
func (m *Map[K, T]) All() iter.Seq2[K, T] {
return func(yield func(K, T) bool) {
m.mu.RLock()
defer m.mu.RUnlock()
for k, v := range m.m {
if !yield(k, v) {
return
}
}
}
}
+72
View File
@@ -0,0 +1,72 @@
// 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 hmaps
import (
"testing"
qt "github.com/frankban/quicktest"
)
func TestMap(t *testing.T) {
c := qt.New(t)
m := NewMap[string, int]()
m.Set("b", 42)
v, found := m.Lookup("b")
c.Assert(found, qt.Equals, true)
c.Assert(v, qt.Equals, 42)
v = m.Get("b")
c.Assert(v, qt.Equals, 42)
v, found = m.Lookup("c")
c.Assert(found, qt.Equals, false)
c.Assert(v, qt.Equals, 0)
v = m.Get("c")
c.Assert(v, qt.Equals, 0)
v, err := m.GetOrCreate("d", func() (int, error) {
return 100, nil
})
c.Assert(err, qt.IsNil)
c.Assert(v, qt.Equals, 100)
v, found = m.Lookup("d")
c.Assert(found, qt.Equals, true)
c.Assert(v, qt.Equals, 100)
v, err = m.GetOrCreate("d", func() (int, error) {
return 200, nil
})
c.Assert(err, qt.IsNil)
c.Assert(v, qt.Equals, 100)
wasSet := m.SetIfAbsent("e", 300)
c.Assert(wasSet, qt.Equals, true)
v, found = m.Lookup("e")
c.Assert(found, qt.Equals, true)
c.Assert(v, qt.Equals, 300)
wasSet = m.SetIfAbsent("e", 400)
c.Assert(wasSet, qt.Equals, false)
v, found = m.Lookup("e")
c.Assert(found, qt.Equals, true)
c.Assert(v, qt.Equals, 300)
m.WithWriteLock(func(m map[string]int) error {
m["f"] = 500
return nil
})
v, found = m.Lookup("f")
c.Assert(found, qt.Equals, true)
c.Assert(v, qt.Equals, 500)
}
+4 -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"
@@ -98,6 +98,8 @@ func ToSliceStringMap(in any) ([]map[string]any, error) {
return v, nil
case Params:
return []map[string]any{v}, nil
case map[string]any:
return []map[string]any{v}, nil
case []any:
var s []map[string]any
for _, entry := range v {
@@ -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"
@@ -73,10 +73,14 @@ func TestPrepareParams(t *testing.T) {
for i, test := range tests {
t.Run(fmt.Sprint(i), func(t *testing.T) {
// PrepareParams modifies input.
prepareClone := PrepareParamsClone(test.input)
PrepareParams(test.input)
if !reflect.DeepEqual(test.expected, test.input) {
t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, test.input)
}
if !reflect.DeepEqual(test.expected, prepareClone) {
t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, prepareClone)
}
})
}
}
@@ -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,9 +11,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package maps
package hmaps
import (
"slices"
"github.com/gohugoio/hugo/common/hashing"
)
@@ -56,6 +58,15 @@ func (m *Ordered[K, T]) Get(key K) (T, bool) {
return value, found
}
// Has returns whether the given key exists in the map.
func (m *Ordered[K, T]) Has(key K) bool {
if m == nil {
return false
}
_, found := m.values[key]
return found
}
// Delete deletes the value for the given key.
func (m *Ordered[K, T]) Delete(key K) {
if m == nil {
@@ -64,7 +75,7 @@ func (m *Ordered[K, T]) Delete(key K) {
delete(m.values, key)
for i, k := range m.keys {
if k == key {
m.keys = append(m.keys[:i], m.keys[i+1:]...)
m.keys = slices.Delete(m.keys, i, i+1)
break
}
}
@@ -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"
+154
View File
@@ -0,0 +1,154 @@
// 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 hmaps
import (
"fmt"
"slices"
"github.com/bits-and-blooms/bitset"
)
type OrderedIntSet struct {
keys []int
values *bitset.BitSet
}
// NewOrderedIntSet creates a new OrderedIntSet.
// Note that this is backed by https://github.com/bits-and-blooms/bitset
func NewOrderedIntSet(vals ...int) *OrderedIntSet {
m := &OrderedIntSet{
keys: make([]int, 0, len(vals)),
values: bitset.New(uint(len(vals))),
}
for _, v := range vals {
m.Set(v)
}
return m
}
// Set sets the value for the given key.
// Note that insertion order is not affected if a key is re-inserted into the set.
func (m *OrderedIntSet) Set(key int) {
if m == nil {
panic("nil OrderedIntSet")
}
keyu := uint(key)
if m.values.Test(keyu) {
return
}
m.values.Set(keyu)
m.keys = append(m.keys, key)
}
// SetFrom sets the values from another OrderedIntSet.
func (m *OrderedIntSet) SetFrom(other *OrderedIntSet) {
if m == nil || other == nil {
return
}
for _, key := range other.keys {
m.Set(key)
}
}
func (m *OrderedIntSet) Clone() *OrderedIntSet {
if m == nil {
return nil
}
newSet := &OrderedIntSet{
keys: slices.Clone(m.keys),
values: m.values.Clone(),
}
return newSet
}
// Next returns the next key in the set possibly including the given key.
// It returns -1 if the key is not found or if there are no keys greater than the given key.
func (m *OrderedIntSet) Next(i int) int {
n, ok := m.values.NextSet(uint(i))
if !ok {
return -1
}
return int(n)
}
// The reason we don't use iter.Seq is https://github.com/golang/go/issues/69015
// This is 70% faster than using iter.Seq2[int, int] for the keys.
// It returns false if the iteration was stopped early.
func (m *OrderedIntSet) ForEachKey(yield func(int) bool) bool {
if m == nil {
return true
}
for _, key := range m.keys {
if !yield(key) {
return false
}
}
return true
}
func (m *OrderedIntSet) Has(key int) bool {
if m == nil {
return false
}
return m.values.Test(uint(key))
}
func (m *OrderedIntSet) Len() int {
if m == nil {
return 0
}
return len(m.keys)
}
// KeysSorted returns the keys in sorted order.
func (m *OrderedIntSet) KeysSorted() []int {
if m == nil {
return nil
}
keys := slices.Clone(m.keys)
slices.Sort(keys)
return m.keys
}
func (m *OrderedIntSet) String() string {
if m == nil {
return "[]"
}
return fmt.Sprintf("%v", m.keys)
}
func (m *OrderedIntSet) Values() *bitset.BitSet {
if m == nil {
return nil
}
return m.values
}
func (m *OrderedIntSet) IsSuperSet(other *OrderedIntSet) bool {
if m == nil || other == nil {
return false
}
return m.values.IsSuperSet(other.values)
}
// Words returns the bitset as array of 64-bit words, giving direct access to the internal representation.
// It is not a copy, so changes to the returned slice will affect the bitset.
// It is meant for advanced users.
func (m *OrderedIntSet) Words() []uint64 {
if m == nil {
return nil
}
return m.values.Words()
}
+117
View File
@@ -0,0 +1,117 @@
// 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 hmaps
import (
"testing"
qt "github.com/frankban/quicktest"
)
func TestOrderedIntSet(t *testing.T) {
c := qt.New(t)
m := NewOrderedIntSet(2, 1, 3, 7)
c.Assert(m.Len(), qt.Equals, 4)
c.Assert(m.Has(1), qt.Equals, true)
c.Assert(m.Has(4), qt.Equals, false)
c.Assert(m.String(), qt.Equals, "[2 1 3 7]")
m.Set(4)
c.Assert(m.Len(), qt.Equals, 5)
c.Assert(m.Has(4), qt.Equals, true)
c.Assert(m.Next(0), qt.Equals, 1)
c.Assert(m.Next(1), qt.Equals, 1)
c.Assert(m.Next(2), qt.Equals, 2)
c.Assert(m.Next(3), qt.Equals, 3)
c.Assert(m.Next(4), qt.Equals, 4)
c.Assert(m.Next(7), qt.Equals, 7)
c.Assert(m.Next(8), qt.Equals, -1)
c.Assert(m.String(), qt.Equals, "[2 1 3 7 4]")
var nilset *OrderedIntSet
c.Assert(nilset.Len(), qt.Equals, 0)
c.Assert(nilset.Has(1), qt.Equals, false)
c.Assert(nilset.String(), qt.Equals, "[]")
var collected []int
m.ForEachKey(func(key int) bool {
collected = append(collected, key)
return true
})
c.Assert(collected, qt.DeepEquals, []int{2, 1, 3, 7, 4})
}
func BenchmarkOrderedIntSet(b *testing.B) {
smallSet := NewOrderedIntSet()
for i := range 8 {
smallSet.Set(i)
}
mediumSet := NewOrderedIntSet()
for i := range 64 {
mediumSet.Set(i)
}
largeSet := NewOrderedIntSet()
for i := range 1024 {
largeSet.Set(i)
}
b.Run("New", func(b *testing.B) {
for b.Loop() {
NewOrderedIntSet(1, 2, 3, 4, 5, 6, 7, 8)
}
})
b.Run("Has small", func(b *testing.B) {
for i := 0; b.Loop(); i++ {
smallSet.Has(i % 32)
}
})
b.Run("Has medium", func(b *testing.B) {
for i := 0; b.Loop(); i++ {
mediumSet.Has(i % 32)
}
})
b.Run("Next", func(b *testing.B) {
for i := 0; b.Loop(); i++ {
mediumSet.Next(i % 32)
}
})
b.Run("ForEachKey small", func(b *testing.B) {
for b.Loop() {
smallSet.ForEachKey(func(key int) bool {
return true
})
}
})
b.Run("ForEachKey medium", func(b *testing.B) {
for b.Loop() {
mediumSet.ForEachKey(func(key int) bool {
return true
})
}
})
b.Run("ForEachKey large", func(b *testing.B) {
for b.Loop() {
largeSet.ForEachKey(func(key int) bool {
return true
})
}
})
}
@@ -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,9 +11,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package maps
package hmaps
import (
"errors"
"fmt"
"strings"
@@ -40,6 +41,14 @@ func (p Params) GetNested(indices ...string) any {
// SetParams overwrites values in dst with values in src for common or new keys.
// This is done recursively.
func SetParams(dst, src Params) {
setParams(dst, src, 0)
}
func setParams(dst, src Params, depth int) {
const maxDepth = 1000
if depth > maxDepth {
panic(errors.New("max depth exceeded"))
}
for k, v := range src {
vv, found := dst[k]
if !found {
@@ -48,7 +57,7 @@ func SetParams(dst, src Params) {
switch vvv := vv.(type) {
case Params:
if pv, ok := v.(Params); ok {
SetParams(vvv, pv)
setParams(vvv, pv, depth+1)
} else {
dst[k] = v
}
@@ -101,7 +110,6 @@ func (p Params) merge(ps ParamsMergeStrategy, pp Params) {
noUpdate = noUpdate || (ps != "" && ps == ParamsMergeStrategyShallow)
for k, v := range pp {
if k == MergeStrategyKey {
continue
}
@@ -179,6 +187,56 @@ func getNested(m map[string]any, indices []string) (any, string, map[string]any)
}
}
// CreateNestedParamsFromSegements creates empty nested maps for the given keySegments in the target map.
func CreateNestedParamsFromSegements(target Params, keySegments ...string) Params {
if len(keySegments) == 0 {
return target
}
m := target
for i, key := range keySegments {
v, found := m[key]
if !found {
nm := Params{}
m[key] = nm
m = nm
if i == len(keySegments)-1 {
return nm
}
continue
}
m = v.(Params)
}
return m
}
// CreateNestedParamsSepString creates empty nested maps for the given keyStr in the target map
// It returns the last map created.
func CreateNestedParamsSepString(keyStr, separator string, target Params) Params {
keySegments := strings.Split(keyStr, separator)
return CreateNestedParamsFromSegements(target, keySegments...)
}
// SetNestedParamIfNotSet sets the value for the given keyStr in the target map if it does not exist.
// It assumes that all but the last key in keyStr is a Params map or should be one.
func SetNestedParamIfNotSet(keyStr, separator string, value any, target Params) Params {
keySegments := strings.Split(keyStr, separator)
if len(keySegments) == 0 {
return target
}
base := keySegments[:len(keySegments)-1]
last := keySegments[len(keySegments)-1]
m := CreateNestedParamsFromSegements(target, base...)
if _, ok := m[last]; !ok {
m[last] = value
}
return target
}
// GetNestedParam gets the first match of the keyStr in the candidates given.
// It will first try the exact match and then try to find it as a nested map value,
// using the given separator, e.g. "mymap.name".
@@ -266,9 +324,17 @@ func CleanConfigStringMapString(m map[string]string) map[string]string {
// CleanConfigStringMap is the same as CleanConfigStringMapString but for
// map[string]any.
func CleanConfigStringMap(m map[string]any) map[string]any {
return doCleanConfigStringMap(m, 0)
}
func doCleanConfigStringMap(m map[string]any, depth int) map[string]any {
if len(m) == 0 {
return m
}
const maxDepth = 1000
if depth > maxDepth {
panic(errors.New("max depth exceeded"))
}
if _, found := m[MergeStrategyKey]; !found {
return m
}
@@ -280,9 +346,9 @@ func CleanConfigStringMap(m map[string]any) map[string]any {
}
switch v2 := v.(type) {
case map[string]any:
m2[k] = CleanConfigStringMap(v2)
m2[k] = doCleanConfigStringMap(v2, depth+1)
case Params:
var p Params = CleanConfigStringMap(v2)
var p Params = doCleanConfigStringMap(v2, depth+1)
m2[k] = p
case map[string]string:
m2[k] = CleanConfigStringMapString(v2)
@@ -303,7 +369,7 @@ func toMergeStrategy(v any) ParamsMergeStrategy {
}
// PrepareParams
// * makes all the keys in the given map lower cased and will do so
// * makes all the keys in the given map lower cased and will do so recursively.
// * This will modify the map given.
// * Any nested map[interface{}]interface{}, map[string]interface{},map[string]string will be converted to Params.
// * Any _merge value will be converted to proper type and value.
@@ -343,3 +409,65 @@ func PrepareParams(m Params) {
}
}
}
// CloneParamsDeep does a deep clone of the given Params,
// meaning that any nested Params will be cloned as well.
func CloneParamsDeep(m Params) Params {
return cloneParamsDeep(m, 0)
}
func cloneParamsDeep(m Params, depth int) Params {
const maxDepth = 1000
if depth > maxDepth {
panic(errors.New("max depth exceeded"))
}
m2 := make(Params)
for k, v := range m {
switch vv := v.(type) {
case Params:
m2[k] = cloneParamsDeep(vv, depth+1)
default:
m2[k] = v
}
}
return m2
}
// PrepareParamsClone is like PrepareParams, but it does not modify the input.
func PrepareParamsClone(m Params) Params {
m2 := make(Params)
for k, v := range m {
var retyped bool
lKey := strings.ToLower(k)
if lKey == MergeStrategyKey {
v = toMergeStrategy(v)
retyped = true
} else {
switch vv := v.(type) {
case map[any]any:
var p Params = cast.ToStringMap(v)
v = PrepareParamsClone(p)
retyped = true
case map[string]any:
var p Params = v.(map[string]any)
v = PrepareParamsClone(p)
retyped = true
case map[string]string:
p := make(Params)
for k, v := range vv {
p[k] = v
}
v = p
PrepareParams(p)
retyped = true
}
}
if retyped || k != lKey {
m2[lKey] = v
} else {
m2[k] = v
}
}
return m2
}
@@ -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"
@@ -167,3 +167,35 @@ func TestParamsIsZero(t *testing.T) {
c.Assert(Params{"_merge": "foo", "foo": "bar"}.IsZero(), qt.IsFalse)
c.Assert(Params{"_merge": "foo"}.IsZero(), qt.IsTrue)
}
func TestSetNestedParamIfNotSet(t *testing.T) {
c := qt.New(t)
m := Params{}
SetNestedParamIfNotSet("a.b.c", ".", "value", m)
c.Assert(m, qt.DeepEquals, Params{
"a": Params{
"b": Params{
"c": "value",
},
},
})
m = Params{
"a": Params{
"b": Params{
"c": "existingValue",
},
},
}
SetNestedParamIfNotSet("a.b.c", ".", "value", m)
c.Assert(m, qt.DeepEquals, Params{
"a": Params{
"b": Params{
"c": "existingValue",
},
},
})
}
+222
View File
@@ -0,0 +1,222 @@
// 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 hreflect
import (
"fmt"
"math"
"reflect"
)
var (
typeInt64 = reflect.TypeFor[int64]()
typeFloat64 = reflect.TypeFor[float64]()
typeString = reflect.TypeFor[string]()
)
// ToInt64 converts v to int64 if possible, returning an error if not.
func ToInt64E(v reflect.Value) (int64, error) {
if v, ok := ConvertIfPossible(v, typeInt64); ok {
return v.Int(), nil
}
return 0, errConvert(v, "int64")
}
// ToInt64 converts v to int64 if possible. It panics if the conversion is not possible.
func ToInt64(v reflect.Value) int64 {
vv, err := ToInt64E(v)
if err != nil {
panic(err)
}
return vv
}
// ToFloat64E converts v to float64 if possible, returning an error if not.
func ToFloat64E(v reflect.Value) (float64, error) {
if v, ok := ConvertIfPossible(v, typeFloat64); ok {
return v.Float(), nil
}
return 0, errConvert(v, "float64")
}
// ToFloat64 converts v to float64 if possible, panicking if not.
func ToFloat64(v reflect.Value) float64 {
vv, err := ToFloat64E(v)
if err != nil {
panic(err)
}
return vv
}
// ToStringE converts v to string if possible, returning an error if not.
func ToStringE(v reflect.Value) (string, error) {
vv, err := ToStringValueE(v)
if err != nil {
return "", err
}
return vv.String(), nil
}
func ToStringValueE(v reflect.Value) (reflect.Value, error) {
if v, ok := ConvertIfPossible(v, typeString); ok {
return v, nil
}
return reflect.Value{}, errConvert(v, "string")
}
// ToString converts v to string if possible, panicking if not.
func ToString(v reflect.Value) string {
vv, err := ToStringE(v)
if err != nil {
panic(err)
}
return vv
}
func errConvert(v reflect.Value, s string) error {
return fmt.Errorf("unable to convert value of type %q to %q", v.Type().String(), s)
}
// ConvertIfPossible tries to convert val to typ if possible.
// This is currently only implemented for int kinds,
// added to handle the move to a new YAML library which produces uint64 for unsigned integers.
// We can expand on this later if needed.
// This conversion is lossless.
// See Issue 14079.
func ConvertIfPossible(val reflect.Value, typ reflect.Type) (reflect.Value, bool) {
switch val.Kind() {
case reflect.Ptr, reflect.Interface:
if val.IsNil() {
// Return typ's zero value.
return reflect.Zero(typ), true
}
val = val.Elem()
}
if val.Type().AssignableTo(typ) {
// No conversion needed.
return val, true
}
if IsInt(typ.Kind()) {
return convertToIntIfPossible(val, typ)
}
if IsFloat(typ.Kind()) {
return convertToFloatIfPossible(val, typ)
}
if IsUint(typ.Kind()) {
return convertToUintIfPossible(val, typ)
}
if IsString(typ.Kind()) && IsString(val.Kind()) {
return val.Convert(typ), true
}
return reflect.Value{}, false
}
func convertToUintIfPossible(val reflect.Value, typ reflect.Type) (reflect.Value, bool) {
if IsInt(val.Kind()) {
i := val.Int()
if i < 0 {
return reflect.Value{}, false
}
u := uint64(i)
if typ.OverflowUint(u) {
return reflect.Value{}, false
}
return reflect.ValueOf(u).Convert(typ), true
}
if IsUint(val.Kind()) {
if typ.OverflowUint(val.Uint()) {
return reflect.Value{}, false
}
return val.Convert(typ), true
}
if IsFloat(val.Kind()) {
f := val.Float()
if f < 0 || f > float64(math.MaxUint64) {
return reflect.Value{}, false
}
if f != math.Trunc(f) {
return reflect.Value{}, false
}
u := uint64(f)
if typ.OverflowUint(u) {
return reflect.Value{}, false
}
return reflect.ValueOf(u).Convert(typ), true
}
return reflect.Value{}, false
}
func convertToFloatIfPossible(val reflect.Value, typ reflect.Type) (reflect.Value, bool) {
if IsInt(val.Kind()) {
i := val.Int()
f := float64(i)
if typ.OverflowFloat(f) {
return reflect.Value{}, false
}
return reflect.ValueOf(f).Convert(typ), true
}
if IsUint(val.Kind()) {
u := val.Uint()
f := float64(u)
if typ.OverflowFloat(f) {
return reflect.Value{}, false
}
return reflect.ValueOf(f).Convert(typ), true
}
if IsFloat(val.Kind()) {
if typ.OverflowFloat(val.Float()) {
return reflect.Value{}, false
}
return val.Convert(typ), true
}
return reflect.Value{}, false
}
func convertToIntIfPossible(val reflect.Value, typ reflect.Type) (reflect.Value, bool) {
if IsInt(val.Kind()) {
if typ.OverflowInt(val.Int()) {
return reflect.Value{}, false
}
return val.Convert(typ), true
}
if IsUint(val.Kind()) {
if val.Uint() > uint64(math.MaxInt64) {
return reflect.Value{}, false
}
if typ.OverflowInt(int64(val.Uint())) {
return reflect.Value{}, false
}
return val.Convert(typ), true
}
if IsFloat(val.Kind()) {
f := val.Float()
if f < float64(math.MinInt64) || f > float64(math.MaxInt64) {
return reflect.Value{}, false
}
if f != math.Trunc(f) {
return reflect.Value{}, false
}
if typ.OverflowInt(int64(f)) {
return reflect.Value{}, false
}
return reflect.ValueOf(int64(f)).Convert(typ), true
}
return reflect.Value{}, false
}
+284
View File
@@ -0,0 +1,284 @@
// 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 hreflect
import (
"math"
"reflect"
"testing"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/htesting/hqt"
)
func TestToFuncs(t *testing.T) {
c := qt.New(t)
c.Assert(ToInt64(reflect.ValueOf(int(42))), qt.Equals, int64(42))
c.Assert(ToFloat64(reflect.ValueOf(float32(3.14))), hqt.IsSameFloat64, float64(3.14))
c.Assert(ToString(reflect.ValueOf("hello")), qt.Equals, "hello")
}
func TestConvertIfPossible(t *testing.T) {
c := qt.New(t)
for _, test := range []struct {
name string
value any
typ any
expected any
ok bool
}{
// From uint to int.
{
name: "uint64(math.MaxUint64) to int16",
value: uint64(math.MaxUint64),
typ: int16(0),
ok: false, // overflow
},
{
name: "uint64(math.MaxUint64) to int64",
value: uint64(math.MaxUint64),
typ: int64(0),
ok: false, // overflow
},
{
name: "uint64(math.MaxInt16) to int16",
value: uint64(math.MaxInt16),
typ: int64(0),
ok: true,
expected: int64(math.MaxInt16),
},
// From int to int.
{
name: "int64(math.MaxInt64) to int16",
value: int64(math.MaxInt64),
typ: int16(0),
ok: false, // overflow
},
{
name: "int64(math.MaxInt16) to int",
value: int64(math.MaxInt16),
typ: int(0),
ok: true,
expected: int(math.MaxInt16),
},
{
name: "int64(math.MaxInt16) to int",
value: int64(math.MaxInt16),
typ: int(0),
ok: true,
expected: int(math.MaxInt16),
},
// From float64 to int.
{
name: "float64(1.5) to int",
value: float64(1.5),
typ: int(0),
ok: false, // loss of precision
},
{
name: "float64(1.0) to int",
value: float64(1.0),
typ: int(0),
ok: true,
expected: int(1),
},
{
name: "float64(math.MaxInt16+1) to int16",
value: float64(math.MaxInt16 + 1),
typ: int16(0),
ok: false, // overflow
},
{
name: "float64(math.MaxFloat64) to int64",
value: float64(math.MaxFloat64),
typ: int64(0),
ok: false, // overflow
},
{
name: "float64(32767) to int16",
value: float64(32767),
typ: int16(0),
ok: true,
expected: int16(32767),
},
// From float32 to int.
{
name: "float32(1.5) to int",
value: float32(1.5),
typ: int(0),
ok: false, // loss of precision
},
{
name: "float32(1.0) to int",
value: float32(1.0),
typ: int(0),
ok: true,
expected: int(1),
},
{
name: "float32(math.MaxFloat32) to int16",
value: float32(math.MaxFloat32),
typ: int16(0),
ok: false, // overflow
},
{
name: "float32(math.MaxFloat32) to int64",
value: float32(math.MaxFloat32),
typ: int64(0),
ok: false, // overflow
},
{
name: "float32(math.MaxInt16) to int16",
value: float32(math.MaxInt16),
typ: int16(0),
ok: true,
expected: int16(32767),
},
{
name: "float32(math.MaxInt16+1) to int16",
value: float32(math.MaxInt16 + 1),
typ: int16(0),
ok: false, // overflow
},
// Int to float.
{
name: "int16(32767) to float32",
value: int16(32767),
typ: float32(0),
ok: true,
expected: float32(32767),
},
{
name: "int64(32767) to float32",
value: int64(32767),
typ: float32(0),
ok: true,
expected: float32(32767),
},
{
name: "int64(math.MaxInt64) to float32",
value: int64(math.MaxInt64),
typ: float32(0),
ok: true,
expected: float32(math.MaxInt64),
},
{
name: "int64(math.MaxInt64) to float64",
value: int64(math.MaxInt64),
typ: float64(0),
ok: true,
expected: float64(math.MaxInt64),
},
// Int to uint.
{
name: "int16(32767) to uint16",
value: int16(32767),
typ: uint16(0),
ok: true,
expected: uint16(32767),
},
{
name: "int16(32767) to uint8",
value: int16(32767),
typ: uint8(0),
ok: false,
},
{
name: "float64(3.14) to uint64",
value: float64(3.14),
typ: uint64(0),
ok: false,
},
{
name: "float64(3.0) to uint64",
value: float64(3.0),
typ: uint64(0),
ok: true,
expected: uint64(3),
},
// From uint to float.
{
name: "uint64(math.MaxInt16) to float64",
value: uint64(math.MaxInt16),
typ: float64(0),
ok: true,
expected: float64(math.MaxInt16),
},
// Float to float.
{
name: "float64(3.14) to float32",
value: float64(3.14),
typ: float32(0),
ok: true,
expected: float32(3.14),
},
{
name: "float32(3.14) to float64",
value: float32(3.14),
typ: float64(0),
ok: true,
expected: float64(3.14),
},
{
name: "float64(3.14) to float64",
value: float64(3.14),
typ: float64(0),
ok: true,
expected: float64(3.14),
},
} {
v, ok := ConvertIfPossible(reflect.ValueOf(test.value), reflect.TypeOf(test.typ))
c.Assert(ok, qt.Equals, test.ok, qt.Commentf("test case: %s", test.name))
if test.ok {
c.Assert(v.Interface(), hqt.IsSameNumber, test.expected, qt.Commentf("test case: %s", test.name))
}
}
}
func TestConvertIfPossibleMisc(t *testing.T) {
c := qt.New(t)
type s string
var (
i = int32(42)
i64 = int64(i)
iv any = i
ip = &i
inil any = (*int32)(nil)
shello = s("hello")
)
convertOK := func(v any, typ any) any {
rv, ok := ConvertIfPossible(reflect.ValueOf(v), reflect.TypeOf(typ))
c.Assert(ok, qt.IsTrue)
return rv.Interface()
}
c.Assert(convertOK(shello, ""), qt.Equals, "hello")
c.Assert(convertOK(ip, int64(0)), qt.Equals, i64)
c.Assert(convertOK(iv, int64(0)), qt.Equals, i64)
c.Assert(convertOK(inil, int64(0)), qt.Equals, int64(0))
}
func BenchmarkToInt64(b *testing.B) {
v := reflect.ValueOf(int(42))
for b.Loop() {
ToInt64(v)
}
}
+114 -33
View File
@@ -1,6 +1,4 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
// Some functions in this file (see comments) is based on the Go source code,
// copyright The Go Authors and governed by a BSD-style license.
// 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.
@@ -22,11 +20,16 @@ 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"
)
// IsInterfaceOrPointer returns whether the given kind is an interface or a pointer.
func IsInterfaceOrPointer(kind reflect.Kind) bool {
return kind == reflect.Interface || kind == reflect.Ptr
}
// TODO(bep) replace the private versions in /tpl with these.
// IsNumber returns whether the given kind is a number.
func IsNumber(kind reflect.Kind) bool {
@@ -63,6 +66,11 @@ func IsFloat(kind reflect.Kind) bool {
}
}
// IsString returns whether the given kind is a string.
func IsString(kind reflect.Kind) bool {
return kind == reflect.String
}
// IsTruthful returns whether in represents a truthful value.
// See IsTruthfulValue
func IsTruthful(in any) bool {
@@ -74,8 +82,30 @@ func IsTruthful(in any) bool {
}
}
// IsMap reports whether v is a map.
func IsMap(v any) bool {
return reflect.ValueOf(v).Kind() == reflect.Map
}
// IsSlice reports whether v is a slice.
func IsSlice(v any) bool {
return reflect.ValueOf(v).Kind() == reflect.Slice
}
var zeroType = reflect.TypeOf((*types.Zeroer)(nil)).Elem()
var isZeroCache sync.Map
func implementsIsZero(tp reflect.Type) bool {
v, ok := isZeroCache.Load(tp)
if ok {
return v.(bool)
}
implements := tp.Implements(zeroType)
isZeroCache.Store(tp, implements)
return implements
}
// IsTruthfulValue returns whether the given value has a meaningful truth value.
// This is based on template.IsTrue in Go's stdlib, but also considers
// IsZero and any interface value will be unwrapped before it's considered
@@ -84,14 +114,18 @@ var zeroType = reflect.TypeOf((*types.Zeroer)(nil)).Elem()
// Based on:
// https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L306
func IsTruthfulValue(val reflect.Value) (truth bool) {
val = indirectInterface(val)
val, isNil := Indirect(val)
if !val.IsValid() {
// Something like var x interface{}, never set. It's a form of nil.
// Something like: var x any, never set. It's a form of nil.
return
}
if val.Type().Implements(zeroType) {
if val.Kind() == reflect.Pointer && isNil {
return
}
if implementsIsZero(val.Type()) {
return !val.Interface().(types.Zeroer).IsZero()
}
@@ -124,15 +158,31 @@ type methodKey struct {
name string
}
type methods struct {
sync.RWMutex
cache map[methodKey]int
var (
methodIndexCache sync.Map
methodCache sync.Map
)
// GetMethodByNameForType returns the method with the given name for the given type,
// or a zero Method if no such method exists.
// It panics if tp is an interface type.
// It caches the lookup.
func GetMethodByNameForType(tp reflect.Type, name string) reflect.Method {
if tp.Kind() == reflect.Interface {
// Func field is nil for interface types.
panic("not supported for interface types")
}
k := methodKey{tp, name}
v, found := methodCache.Load(k)
if found {
return v.(reflect.Method)
}
m, _ := tp.MethodByName(name)
methodCache.Store(k, m)
return m
}
var methodCache = &methods{cache: make(map[methodKey]int)}
// GetMethodByName is the same as reflect.Value.MethodByName, but it caches the
// type lookup.
// GetMethodByName is the same as reflect.Value.MethodByName, but it caches the lookup.
func GetMethodByName(v reflect.Value, name string) reflect.Value {
index := GetMethodIndexByName(v.Type(), name)
@@ -147,22 +197,16 @@ func GetMethodByName(v reflect.Value, name string) reflect.Value {
// -1 if no such method exists.
func GetMethodIndexByName(tp reflect.Type, name string) int {
k := methodKey{tp, name}
methodCache.RLock()
index, found := methodCache.cache[k]
methodCache.RUnlock()
v, found := methodIndexCache.Load(k)
if found {
return index
return v.(int)
}
methodCache.Lock()
defer methodCache.Unlock()
m, ok := tp.MethodByName(name)
index = m.Index
index := m.Index
if !ok {
index = -1
}
methodCache.cache[k] = index
methodIndexCache.Store(k, index)
if !ok {
return -1
@@ -235,7 +279,7 @@ func ToSliceAny(v any) ([]any, bool) {
vvv := reflect.ValueOf(v)
if vvv.Kind() == reflect.Slice {
out := make([]any, vvv.Len())
for i := 0; i < vvv.Len(); i++ {
for i := range vvv.Len() {
out[i] = vvv.Index(i).Interface()
}
return out, true
@@ -244,6 +288,7 @@ func ToSliceAny(v any) ([]any, bool) {
return nil, false
}
// CallMethodByName calls the method with the given name on v.
func CallMethodByName(cxt context.Context, name string, v reflect.Value) []reflect.Value {
fn := v.MethodByName(name)
var args []reflect.Value
@@ -261,20 +306,56 @@ func CallMethodByName(cxt context.Context, name string, v reflect.Value) []refle
return fn.Call(args)
}
// Based on: https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L931
func indirectInterface(v reflect.Value) reflect.Value {
if v.Kind() != reflect.Interface {
return v
// Indirect unwraps interfaces and pointers until it finds a non-interface/pointer value.
// If a nil is encountered, the second return value is true.
// If a pointer to a struct is encountered, it is not unwrapped.
func Indirect(v reflect.Value) (vv reflect.Value, isNil bool) {
for ; IsInterfaceOrPointer(v.Kind()); v = v.Elem() {
if IsNil(v) {
return v, true
}
if v.Kind() != reflect.Interface {
// A pointer.
if v.NumMethod() > 0 {
break
}
if v.Elem().Kind() == reflect.Struct {
// Avoid unwrapping pointers to structs.
break
}
}
}
if v.IsNil() {
return reflect.Value{}
return v, false
}
// IndirectElem is like Indirect, but if the final value is a pointer, it unwraps it.
func IndirectElem(v reflect.Value) (vv reflect.Value, isNil bool) {
vv, isNil = Indirect(v)
if isNil {
return vv, isNil
}
return v.Elem()
if vv.Kind() == reflect.Pointer {
vv = vv.Elem()
}
return vv, isNil
}
// IsNil reports whether v is nil.
// Based on reflect.Value.IsNil, but also considers invalid values as nil.
func IsNil(v reflect.Value) bool {
if !v.IsValid() {
return true
}
switch v.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
return v.IsNil()
}
return false
}
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
+140 -16
View File
@@ -1,4 +1,4 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
// 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.
@@ -20,15 +20,32 @@ import (
"time"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/htesting/hqt"
)
type zeroStruct struct {
zero bool
}
func (z zeroStruct) IsZero() bool {
return z.zero
}
func TestIsTruthful(t *testing.T) {
c := qt.New(t)
var nilpointerZero *zeroStruct
c.Assert(IsTruthful(true), qt.Equals, true)
c.Assert(IsTruthful(false), qt.Equals, false)
c.Assert(IsTruthful(time.Now()), qt.Equals, true)
c.Assert(IsTruthful(time.Time{}), qt.Equals, false)
c.Assert(IsTruthful(&zeroStruct{zero: false}), qt.Equals, true)
c.Assert(IsTruthful(&zeroStruct{zero: true}), qt.Equals, false)
c.Assert(IsTruthful(zeroStruct{zero: false}), qt.Equals, true)
c.Assert(IsTruthful(zeroStruct{zero: true}), qt.Equals, false)
c.Assert(IsTruthful(nil), qt.Equals, false)
c.Assert(IsTruthful(nilpointerZero), qt.Equals, false)
}
func TestGetMethodByName(t *testing.T) {
@@ -63,18 +80,90 @@ func TestToSliceAny(t *testing.T) {
checkOK([]int{1, 2, 3}, []any{1, 2, 3})
}
type testIndirectStruct struct {
S string
}
func (t *testIndirectStruct) GetS() string {
return t.S
}
func (t testIndirectStruct) Foo() string {
return "bar"
}
type testIndirectStructNoMethods struct {
S string
}
func TestIsNil(t *testing.T) {
c := qt.New(t)
var (
nilPtr *testIndirectStruct
nilIface any = nil
nonNilIface any = &testIndirectStruct{S: "hello"}
)
c.Assert(IsNil(reflect.ValueOf(nilPtr)), qt.Equals, true)
c.Assert(IsNil(reflect.ValueOf(nilIface)), qt.Equals, true)
c.Assert(IsNil(reflect.ValueOf(nonNilIface)), qt.Equals, false)
}
func TestIndirectInterface(t *testing.T) {
c := qt.New(t)
var (
structWithMethods = testIndirectStruct{S: "hello"}
structWithMethodsPointer = &testIndirectStruct{S: "hello"}
structWithMethodsPointerAny any = structWithMethodsPointer
structPointerToPointer = &structWithMethodsPointer
structNoMethodsPtr = &testIndirectStructNoMethods{S: "no methods"}
structNoMethods = testIndirectStructNoMethods{S: "no methods"}
intValue = 32
intPtr = &intValue
nilPtr *testIndirectStruct
nilIface any = nil
)
ind := func(v any) any {
c.Helper()
vv, isNil := Indirect(reflect.ValueOf(v))
c.Assert(isNil, qt.IsFalse)
return vv.Interface()
}
c.Assert(ind(intValue), hqt.IsSameType, 32)
c.Assert(ind(intPtr), hqt.IsSameType, 32)
c.Assert(ind(structNoMethodsPtr), hqt.IsSameType, structNoMethodsPtr)
c.Assert(ind(structWithMethods), hqt.IsSameType, structWithMethods)
c.Assert(ind(structNoMethods), hqt.IsSameType, structNoMethods)
c.Assert(ind(structPointerToPointer), hqt.IsSameType, &testIndirectStruct{})
c.Assert(ind(structWithMethodsPointer), hqt.IsSameType, &testIndirectStruct{})
c.Assert(ind(structWithMethodsPointerAny), hqt.IsSameType, structWithMethodsPointer)
vv, isNil := Indirect(reflect.ValueOf(nilPtr))
c.Assert(isNil, qt.IsTrue)
c.Assert(vv, qt.Equals, reflect.ValueOf(nilPtr))
vv, isNil = Indirect(reflect.ValueOf(nilIface))
c.Assert(isNil, qt.IsFalse)
c.Assert(vv, qt.Equals, reflect.ValueOf(nilIface))
}
func BenchmarkIsContextType(b *testing.B) {
const size = 1000
type k string
b.Run("value", func(b *testing.B) {
ctx := context.Background()
ctxs := make([]reflect.Type, b.N)
for i := 0; i < b.N; i++ {
ctxs := make([]reflect.Type, size)
for i := range size {
ctxs[i] = reflect.TypeOf(context.WithValue(ctx, k("key"), i))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if !IsContextType(ctxs[i]) {
for i := 0; b.Loop(); i++ {
idx := i % size
if !IsContextType(ctxs[idx]) {
b.Fatal("not context")
}
}
@@ -82,7 +171,7 @@ func BenchmarkIsContextType(b *testing.B) {
b.Run("background", func(b *testing.B) {
var ctxt reflect.Type = reflect.TypeOf(context.Background())
for i := 0; i < b.N; i++ {
for b.Loop() {
if !IsContextType(ctxt) {
b.Fatal("not context")
}
@@ -90,14 +179,25 @@ func BenchmarkIsContextType(b *testing.B) {
})
}
func BenchmarkIsTruthFul(b *testing.B) {
v := reflect.ValueOf("Hugo")
func BenchmarkIsTruthFulValue(b *testing.B) {
var (
stringHugo = reflect.ValueOf("Hugo")
stringEmpty = reflect.ValueOf("")
zero = reflect.ValueOf(time.Time{})
timeNow = reflect.ValueOf(time.Now())
boolTrue = reflect.ValueOf(true)
boolFalse = reflect.ValueOf(false)
nilPointer = reflect.ValueOf((*zeroStruct)(nil))
)
b.ResetTimer()
for i := 0; i < b.N; i++ {
if !IsTruthfulValue(v) {
b.Fatal("not truthful")
}
for b.Loop() {
IsTruthfulValue(stringHugo)
IsTruthfulValue(stringEmpty)
IsTruthfulValue(zero)
IsTruthfulValue(timeNow)
IsTruthfulValue(boolTrue)
IsTruthfulValue(boolFalse)
IsTruthfulValue(nilPointer)
}
}
@@ -123,14 +223,38 @@ func (t *testStruct) Method5() string {
return "Hugo"
}
func BenchmarkGetMethodByNameForType(b *testing.B) {
tp := reflect.TypeFor[*testStruct]()
methods := []string{"Method1", "Method2", "Method3", "Method4", "Method5"}
for b.Loop() {
for _, method := range methods {
_ = GetMethodByNameForType(tp, method)
}
}
}
func BenchmarkGetMethodByName(b *testing.B) {
v := reflect.ValueOf(&testStruct{})
methods := []string{"Method1", "Method2", "Method3", "Method4", "Method5"}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
for _, method := range methods {
_ = GetMethodByName(v, method)
}
}
}
func BenchmarkGetMethodByNamePara(b *testing.B) {
v := reflect.ValueOf(&testStruct{})
methods := []string{"Method1", "Method2", "Method3", "Method4", "Method5"}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for _, method := range methods {
_ = GetMethodByName(v, method)
}
}
})
}
@@ -1,4 +1,4 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
// 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.
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package maps
package hstore
import (
"reflect"
@@ -1,4 +1,4 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
// 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.
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package maps
package hstore
import (
"reflect"
@@ -140,7 +140,7 @@ func TestScratchInParallel(t *testing.T) {
for i := 1; i <= 10; i++ {
wg.Add(1)
go func(j int) {
for k := 0; k < 10; k++ {
for k := range 10 {
newVal := int64(k + j)
_, err := scratch.Add(key, newVal)
@@ -214,8 +214,8 @@ func TestScratchGetSortedMapValues(t *testing.T) {
func BenchmarkScratchGet(b *testing.B) {
scratch := NewScratch()
scratch.Add("A", 1)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
scratch.Get("A")
}
}
+70 -14
View File
@@ -16,6 +16,8 @@ package hstrings
import (
"fmt"
"regexp"
"slices"
"sort"
"strings"
"sync"
@@ -50,12 +52,7 @@ func (s StringEqualFold) Eq(s2 any) bool {
// EqualAny returns whether a string is equal to any of the given strings.
func EqualAny(a string, b ...string) bool {
for _, s := range b {
if a == s {
return true
}
}
return false
return slices.Contains(b, a)
}
// regexpCache represents a cache of regexp objects protected by a mutex.
@@ -100,17 +97,22 @@ func GetOrCompileRegexp(pattern string) (re *regexp.Regexp, err error) {
return reCache.getOrCompileRegexp(pattern)
}
// InSlice checks if a string is an element of a slice of strings
// and returns a boolean value.
func InSlice(arr []string, el string) bool {
for _, v := range arr {
if v == el {
// HasAnyPrefix checks if the string s has any of the prefixes given.
func HasAnyPrefix(s string, prefixes ...string) bool {
for _, p := range prefixes {
if strings.HasPrefix(s, p) {
return true
}
}
return false
}
// InSlice checks if a string is an element of a slice of strings
// and returns a boolean value.
func InSlice(arr []string, el string) bool {
return slices.Contains(arr, el)
}
// InSlicEqualFold checks if a string is an element of a slice of strings
// and returns a boolean value.
// It uses strings.EqualFold to compare.
@@ -137,7 +139,61 @@ func ToString(v any) (string, bool) {
return "", false
}
type Tuple struct {
First string
Second string
// UniqueStrings returns a new slice with any duplicates removed.
func UniqueStrings(s []string) []string {
unique := make([]string, 0, len(s))
for i, val := range s {
var seen bool
for j := range i {
if s[j] == val {
seen = true
break
}
}
if !seen {
unique = append(unique, val)
}
}
return unique
}
// UniqueStringsReuse returns a slice with any duplicates removed.
// It will modify the input slice.
func UniqueStringsReuse(s []string) []string {
result := s[:0]
for i, val := range s {
var seen bool
for j := range i {
if s[j] == val {
seen = true
break
}
}
if !seen {
result = append(result, val)
}
}
return result
}
// UniqueStringsSorted returns a sorted slice with any duplicates removed.
// It will modify the input slice.
func UniqueStringsSorted(s []string) []string {
if len(s) == 0 {
return nil
}
ss := sort.StringSlice(s)
ss.Sort()
i := 0
for j := 1; j < len(s); j++ {
if !ss.Less(i, j) {
continue
}
i++
s[i] = s[j]
}
return s[:i+1]
}
+81 -3
View File
@@ -1,4 +1,4 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
// 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.
@@ -14,6 +14,7 @@
package hstrings
import (
"reflect"
"regexp"
"testing"
@@ -43,14 +44,91 @@ func TestGetOrCompileRegexp(t *testing.T) {
c.Assert(re.MatchString("123"), qt.Equals, true)
}
func TestUniqueStrings(t *testing.T) {
in := []string{"a", "b", "a", "b", "c", "", "a", "", "d"}
output := UniqueStrings(in)
expected := []string{"a", "b", "c", "", "d"}
if !reflect.DeepEqual(output, expected) {
t.Errorf("Expected %#v, got %#v\n", expected, output)
}
}
func TestUniqueStringsReuse(t *testing.T) {
in := []string{"a", "b", "a", "b", "c", "", "a", "", "d"}
output := UniqueStringsReuse(in)
expected := []string{"a", "b", "c", "", "d"}
if !reflect.DeepEqual(output, expected) {
t.Errorf("Expected %#v, got %#v\n", expected, output)
}
}
func TestUniqueStringsSorted(t *testing.T) {
c := qt.New(t)
in := []string{"a", "a", "b", "c", "b", "", "a", "", "d"}
output := UniqueStringsSorted(in)
expected := []string{"", "a", "b", "c", "d"}
c.Assert(output, qt.DeepEquals, expected)
c.Assert(UniqueStringsSorted(nil), qt.IsNil)
}
// Note that these cannot use b.Loop() because of golang/go#27217.
func BenchmarkUniqueStrings(b *testing.B) {
input := []string{"a", "b", "d", "e", "d", "h", "a", "i"}
b.Run("Safe", func(b *testing.B) {
for b.Loop() {
result := UniqueStrings(input)
if len(result) != 6 {
b.Fatalf("invalid count: %d", len(result))
}
}
})
b.Run("Reuse slice", func(b *testing.B) {
inputs := make([][]string, b.N)
for i := 0; i < b.N; i++ {
inputc := make([]string, len(input))
copy(inputc, input)
inputs[i] = inputc
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
inputc := inputs[i]
result := UniqueStringsReuse(inputc)
if len(result) != 6 {
b.Fatalf("invalid count: %d", len(result))
}
}
})
b.Run("Reuse slice sorted", func(b *testing.B) {
inputs := make([][]string, b.N)
for i := 0; i < b.N; i++ {
inputc := make([]string, len(input))
copy(inputc, input)
inputs[i] = inputc
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
inputc := inputs[i]
result := UniqueStringsSorted(inputc)
if len(result) != 6 {
b.Fatalf("invalid count: %d", len(result))
}
}
})
}
func BenchmarkGetOrCompileRegexp(b *testing.B) {
for i := 0; i < b.N; i++ {
for b.Loop() {
GetOrCompileRegexp(`\d+`)
}
}
func BenchmarkCompileRegexp(b *testing.B) {
for i := 0; i < b.N; i++ {
for b.Loop() {
regexp.MustCompile(`\d+`)
}
}
+162
View File
@@ -0,0 +1,162 @@
// 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 hsync
import (
"context"
"sync"
"sync/atomic"
)
// OnceMore is similar to sync.Once.
//
// Additional features are:
// * it can be reset, so the action can be repeated if needed
// * it has methods to check if it's done or in progress
type OnceMore struct {
_ doNotCopy
done atomic.Bool
mu sync.Mutex
}
func (t *OnceMore) Do(f func()) {
if t.Done() {
return
}
t.mu.Lock()
defer t.mu.Unlock()
// Double check
if t.Done() {
return
}
defer t.done.Store(true)
f()
}
func (t *OnceMore) Done() bool {
return t.done.Load()
}
func (t *OnceMore) Reset() {
t.mu.Lock()
t.done.Store(false)
t.mu.Unlock()
}
type ValueResetter[T any] struct {
reset func()
f func(context.Context) T
}
func (v *ValueResetter[T]) Value(ctx context.Context) T {
return v.f(ctx)
}
func (v *ValueResetter[T]) Reset() {
v.reset()
}
// OnceMoreValue returns a function that invokes f only once and returns the value
// returned by f. The returned function may be called concurrently.
//
// If f panics, the returned function will panic with the same value on every call.
func OnceMoreValue[T any](f func(context.Context) T) ValueResetter[T] {
v := struct {
f func(context.Context) T
once OnceMore
ok bool
p any
result T
}{
f: f,
}
ff := func(ctx context.Context) T {
v.once.Do(func() {
v.ok = false
defer func() {
v.p = recover()
if !v.ok {
panic(v.p)
}
}()
v.result = v.f(ctx)
v.ok = true
})
if !v.ok {
panic(v.p)
}
return v.result
}
return ValueResetter[T]{
reset: v.once.Reset,
f: ff,
}
}
type FuncResetter struct {
f func(context.Context) error
reset func()
}
func (v *FuncResetter) Do(ctx context.Context) error {
return v.f(ctx)
}
func (v *FuncResetter) Reset() {
v.reset()
}
func OnceMoreFunc(f func(context.Context) error) FuncResetter {
v := struct {
f func(context.Context) error
once OnceMore
ok bool
err error
p any
}{
f: f,
}
ff := func(ctx context.Context) error {
v.once.Do(func() {
v.ok = false
defer func() {
v.p = recover()
if !v.ok {
panic(v.p)
}
}()
v.err = v.f(ctx)
v.ok = true
})
if !v.ok {
panic(v.p)
}
return v.err
}
return FuncResetter{
f: ff,
reset: v.once.Reset,
}
}
type doNotCopy struct{}
// Lock is a no-op used by -copylocks checker from `go vet`.
func (*doNotCopy) Lock() {}
func (*doNotCopy) Unlock() {}
+80
View File
@@ -0,0 +1,80 @@
// 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 hsync
import (
"context"
"testing"
qt "github.com/frankban/quicktest"
)
func TestOnceMoreValue(t *testing.T) {
c := qt.New(t)
var counter int
f := func(context.Context) int {
counter++
return counter
}
omf := OnceMoreValue(f)
for range 10 {
c.Assert(omf.Value(context.Background()), qt.Equals, 1)
}
omf.Reset()
for range 10 {
c.Assert(omf.Value(context.Background()), qt.Equals, 2)
}
}
func TestOnceMoreFunc(t *testing.T) {
c := qt.New(t)
var counter int
f := func(context.Context) error {
counter++
return nil
}
omf := OnceMoreFunc(f)
for range 10 {
c.Assert(omf.Do(context.Background()), qt.IsNil)
c.Assert(counter, qt.Equals, 1)
}
omf.Reset()
for range 10 {
c.Assert(omf.Do(context.Background()), qt.IsNil)
c.Assert(counter, qt.Equals, 2)
}
}
func BenchmarkOnceMoreValue(b *testing.B) {
var counter int
f := func(context.Context) int {
counter++
return counter
}
for b.Loop() {
omf := OnceMoreValue(f)
for range 10 {
omf.Value(context.Background())
}
omf.Reset()
for range 10 {
omf.Value(context.Background())
}
}
}
+2 -2
View File
@@ -24,9 +24,9 @@ func TestApplyWithContext(t *testing.T) {
t.Parallel()
files := `
-- config.toml --
-- hugo.toml --
defaultContentLanguage = 'it'
-- layouts/index.html --
-- layouts/home.html --
{{ $dates := slice
"2022-01-03"
"2022-02-01"
+3 -3
View File
@@ -112,7 +112,7 @@ func BenchmarkTimeFormatter(b *testing.B) {
june06, _ := time.Parse("2006-Jan-02", "2018-Jun-06")
b.Run("Native", func(b *testing.B) {
for i := 0; i < b.N; i++ {
for b.Loop() {
got := june06.Format("Monday Jan 2 2006")
if got != "Wednesday Jun 6 2018" {
b.Fatalf("invalid format, got %q", got)
@@ -123,7 +123,7 @@ func BenchmarkTimeFormatter(b *testing.B) {
b.Run("Localized", func(b *testing.B) {
f := NewTimeFormatter(translators.GetTranslator("nn"))
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
got := f.Format(june06, "Monday Jan 2 2006")
if got != "onsdag juni 6 2018" {
b.Fatalf("invalid format, got %q", got)
@@ -134,7 +134,7 @@ func BenchmarkTimeFormatter(b *testing.B) {
b.Run("Localized Custom", func(b *testing.B) {
f := NewTimeFormatter(translators.GetTranslator("nn"))
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
got := f.Format(june06, ":date_medium")
if got != "6. juni 2018" {
b.Fatalf("invalid format, got %q", got)
+1 -1
View File
@@ -46,7 +46,7 @@ func TestHasBytesWriter(t *testing.T) {
return strings.Repeat("ab cfo", r.Intn(33))
}
for i := 0; i < 22; i++ {
for range 22 {
h, w := neww()
fmt.Fprint(w, rndStr()+"abc __foobar"+rndStr())
c.Assert(h.Patterns[0].Match, qt.Equals, true)
+31
View File
@@ -32,6 +32,37 @@ 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
func (f CloserFunc) Close() error {
return f()
}
// ReadSeekCloserProvider provides a ReadSeekCloser.
type ReadSeekCloserProvider interface {
ReadSeekCloser() (ReadSeekCloser, error)
+85 -20
View File
@@ -28,12 +28,14 @@ import (
"github.com/bep/logg"
"github.com/bep/godartsass/v2"
"github.com/gohugoio/hugo/common/hcontext"
"github.com/gohugoio/hugo/common/hexec"
"github.com/gohugoio/hugo/common/hstore"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/version"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/bep/helpers/contexthelpers"
"github.com/spf13/afero"
iofs "io/fs"
@@ -54,7 +56,7 @@ var (
vendorInfo string
)
var _ maps.StoreProvider = (*HugoInfo)(nil)
var _ hstore.StoreProvider = (*HugoInfo)(nil)
// HugoInfo contains information about the current Hugo environment
type HugoInfo struct {
@@ -73,14 +75,14 @@ type HugoInfo struct {
conf ConfigProvider
deps []*Dependency
store *maps.Scratch
store *hstore.Scratch
// Context gives access to some of the context scoped variables.
Context Context
}
// Version returns the current version as a comparable version string.
func (i HugoInfo) Version() VersionString {
func (i HugoInfo) Version() version.VersionString {
return CurrentVersion.Version()
}
@@ -119,7 +121,7 @@ func (i HugoInfo) Deps() []*Dependency {
return i.deps
}
func (i HugoInfo) Store() *maps.Scratch {
func (i HugoInfo) Store() *hstore.Scratch {
return i.store
}
@@ -139,9 +141,13 @@ func (i HugoInfo) IsMultilingual() bool {
return i.conf.IsMultilingual()
}
type contextKey string
type contextKey uint8
var markupScope = hcontext.NewContextDispatcher[string](contextKey("markupScope"))
const (
contextKeyMarkupScope contextKey = iota
)
var markupScope = contexthelpers.NewContextDispatcher[string](contextKeyMarkupScope)
type Context struct{}
@@ -192,7 +198,7 @@ func NewInfo(conf ConfigProvider, deps []*Dependency) HugoInfo {
Environment: conf.Environment(),
conf: conf,
deps: deps,
store: maps.NewScratch(),
store: hstore.NewScratch(),
GoVersion: goVersion,
}
}
@@ -304,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"),
)
}
@@ -411,36 +416,96 @@ func IsDartSassGeV2() bool {
// 2. Their theme to work for at least the last few Hugo versions.
func Deprecate(item, alternative string, version string) {
level := deprecationLogLevelFromVersion(version)
DeprecateLevel(item, alternative, version, level)
deprecateLevel(item, alternative, version, level)
}
// See Deprecate for details.
func DeprecateWithLogger(item, alternative string, version string, log logg.Logger) {
level := deprecationLogLevelFromVersion(version)
deprecateLevelWithLogger(item, alternative, version, level, log)
}
// DeprecateLevelMin informs about a deprecation starting at the given version, but with a minimum log level.
func DeprecateLevelMin(item, alternative string, version string, minLevel logg.Level) {
level := max(deprecationLogLevelFromVersion(version), minLevel)
deprecateLevel(item, alternative, version, level)
}
// deprecateLevel informs about a deprecation logging at the given level.
func deprecateLevel(item, alternative, version string, level logg.Level) {
deprecateLevelWithLogger(item, alternative, version, level, loggers.Log().Logger())
}
// DeprecateLevel informs about a deprecation logging at the given level.
func DeprecateLevel(item, alternative, version string, level logg.Level) {
func deprecateLevelWithLogger(item, alternative, version string, level logg.Level, log logg.Logger) {
var msg string
if level == logg.LevelError {
msg = fmt.Sprintf("%s was deprecated in Hugo %s and will be removed in Hugo %s. %s", item, version, CurrentVersion.Next().ReleaseVersion(), alternative)
msg = fmt.Sprintf("%s was deprecated in Hugo %s and subsequently removed. %s", item, version, alternative)
} else {
msg = fmt.Sprintf("%s was deprecated in Hugo %s and will be removed in a future release. %s", item, version, alternative)
}
loggers.Log().Logger().WithLevel(level).WithField(loggers.FieldNameCmd, "deprecated").Logf("%s", msg)
log.WithLevel(level).WithField(loggers.FieldNameCmd, "deprecated").Logf("%s", msg)
}
// We usually do about one minor version a month.
// We want people to run at least the current and previous version without any warnings.
// We want people who don't update Hugo that often to see the warnings and errors before we remove the feature.
func deprecationLogLevelFromVersion(ver string) logg.Level {
from := MustParseVersion(ver)
from := version.MustParseVersion(ver)
to := CurrentVersion
minorDiff := to.Minor - from.Minor
switch {
case minorDiff >= 12:
// Start failing the build after about a year.
case minorDiff >= 15:
// Start failing the build after about 15 months.
return logg.LevelError
case minorDiff >= 6:
// Start printing warnings after about six months.
case minorDiff >= 3:
// Start printing warnings after about 3 months.
return logg.LevelWarn
default:
return logg.LevelInfo
}
}
// BuildVersionString creates a version string. This is what you see when
// running "hugo version".
func BuildVersionString() string {
// program := "Hugo Static Site Generator"
program := "hugo"
version := "v" + CurrentVersion.String()
bi := getBuildInfo()
if bi == nil {
return version
}
if bi.Revision != "" {
version += "-" + bi.Revision
}
if IsExtended {
version += "+extended"
}
if IsWithdeploy {
version += "+withdeploy"
}
osArch := bi.GoOS + "/" + bi.GoArch
date := bi.RevisionTime
if date == "" {
// Accept vendor-specified build date if .git/ is unavailable.
date = buildDate
}
if date == "" {
date = "unknown"
}
versionString := fmt.Sprintf("%s %s %s BuildDate=%s",
program, version, osArch, date)
if vendorInfo != "" {
versionString += " VendorInfo=" + vendorInfo
}
return versionString
}
+1 -1
View File
@@ -35,7 +35,7 @@ baseURL = 'https://en.example.org/'
---
title: home
---
-- layouts/index.html --
-- layouts/home.html --
multilingual={{ hugo.IsMultilingual }}
multihost={{ hugo.IsMultihost }}
`
+6 -5
View File
@@ -20,6 +20,7 @@ import (
"github.com/bep/logg"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/version"
)
func TestHugoInfo(t *testing.T) {
@@ -29,7 +30,7 @@ func TestHugoInfo(t *testing.T) {
hugoInfo := NewInfo(conf, nil)
c.Assert(hugoInfo.Version(), qt.Equals, CurrentVersion.Version())
c.Assert(fmt.Sprintf("%T", VersionString("")), qt.Equals, fmt.Sprintf("%T", hugoInfo.Version()))
c.Assert(fmt.Sprintf("%T", version.VersionString("")), qt.Equals, fmt.Sprintf("%T", hugoInfo.Version()))
c.Assert(hugoInfo.WorkingDir(), qt.Equals, "/mywork")
bi := getBuildInfo()
@@ -57,11 +58,11 @@ func TestDeprecationLogLevelFromVersion(t *testing.T) {
c.Assert(deprecationLogLevelFromVersion("0.55.0"), qt.Equals, logg.LevelError)
ver := CurrentVersion
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelInfo)
ver.Minor -= 1
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelInfo)
ver.Minor -= 6
ver.Minor -= 3
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelWarn)
ver.Minor -= 6
ver.Minor -= 4
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelWarn)
ver.Minor -= 13
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelError)
// Added just to find the threshold for where we can remove deprecated items.
-1
View File
@@ -12,7 +12,6 @@
// limitations under the License.
//go:build extended
// +build extended
package hugo
-1
View File
@@ -12,7 +12,6 @@
// limitations under the License.
//go:build !extended
// +build !extended
package hugo
-1
View File
@@ -12,7 +12,6 @@
// limitations under the License.
//go:build withdeploy
// +build withdeploy
package hugo
-1
View File
@@ -12,7 +12,6 @@
// limitations under the License.
//go:build !withdeploy
// +build !withdeploy
package hugo
+7 -5
View File
@@ -1,4 +1,4 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
// 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.
@@ -13,11 +13,13 @@
package hugo
import "github.com/gohugoio/hugo/common/version"
// CurrentVersion represents the current build version.
// This should be the only one.
var CurrentVersion = Version{
var CurrentVersion = version.Version{
Major: 0,
Minor: 143,
PatchLevel: 0,
Suffix: "-DEV",
Minor: 155,
PatchLevel: 1,
Suffix: "",
}
+2 -2
View File
@@ -37,7 +37,7 @@ func TestLogDistinct(t *testing.T) {
l := loggers.New(opts)
for i := 0; i < 10; i++ {
for range 10 {
l.Errorln("error 1")
l.Errorln("error 2")
l.Warnln("warn 1")
@@ -137,7 +137,7 @@ func TestReset(t *testing.T) {
l := loggers.New(opts)
for i := 0; i < 3; i++ {
for range 3 {
l.Errorln("error 1")
l.Errorln("error 2")
l.Errorln("error 1")
+10 -2
View File
@@ -21,7 +21,15 @@ import (
"github.com/bep/logg"
)
func InitGlobalLogger(level logg.Level, panicOnWarnings bool) {
// SetGlobalLogger sets the global logger.
// This is used in a few places in Hugo, e.g. deprecated functions.
func SetGlobalLogger(logger Logger) {
logMu.Lock()
defer logMu.Unlock()
log = logger
}
func initGlobalLogger(level logg.Level, panicOnWarnings bool) {
logMu.Lock()
defer logMu.Unlock()
var logHookLast func(e *logg.Entry) error
@@ -50,5 +58,5 @@ func Log() Logger {
var log Logger
func init() {
InitGlobalLogger(logg.LevelWarn, false)
initGlobalLogger(logg.LevelWarn, false)
}
+3 -4
View File
@@ -42,7 +42,7 @@ func TestPara(t *testing.T) {
c.Run("Order", func(c *qt.C) {
n := 500
ints := make([]int, n)
for i := 0; i < n; i++ {
for i := range n {
ints[i] = i
}
@@ -51,8 +51,7 @@ func TestPara(t *testing.T) {
var result []int
var mu sync.Mutex
for i := 0; i < n; i++ {
i := i
for i := range n {
r.Run(func() error {
mu.Lock()
defer mu.Unlock()
@@ -78,7 +77,7 @@ func TestPara(t *testing.T) {
var counter int64
for i := 0; i < n; i++ {
for range n {
r.Run(func() error {
atomic.AddInt64(&counter, 1)
time.Sleep(1 * time.Millisecond)
+2 -2
View File
@@ -115,8 +115,8 @@ func ReplaceExtension(path string, newExt string) string {
func makePathRelative(inPath string, possibleDirectories ...string) (string, error) {
for _, currentPath := range possibleDirectories {
if strings.HasPrefix(inPath, currentPath) {
return strings.TrimPrefix(inPath, currentPath), nil
if after, ok := strings.CutPrefix(inPath, currentPath); ok {
return after, nil
}
}
return inPath, errors.New("can't extract relative path, unknown prefix")
+60 -2
View File
@@ -225,7 +225,7 @@ func BenchmarkSanitize(b *testing.B) {
// This should not allocate any memory.
b.Run("All allowed", func(b *testing.B) {
for i := 0; i < b.N; i++ {
for b.Loop() {
got := Sanitize(allAlowedPath)
if got != allAlowedPath {
b.Fatal(got)
@@ -235,7 +235,7 @@ func BenchmarkSanitize(b *testing.B) {
// This will allocate some memory.
b.Run("Spaces", func(b *testing.B) {
for i := 0; i < b.N; i++ {
for b.Loop() {
got := Sanitize(spacePath)
if got != "foo-bar" {
b.Fatal(got)
@@ -311,3 +311,61 @@ func TestIsSameFilePath(t *testing.T) {
c.Assert(IsSameFilePath(filepath.FromSlash(this.a), filepath.FromSlash(this.b)), qt.Equals, this.expected, qt.Commentf("a: %s b: %s", this.a, this.b))
}
}
func BenchmarkAddLeadingSlash(b *testing.B) {
const (
noLeadingSlash = "a/b/c"
withLeadingSlash = "/a/b/c"
)
// This should not allocate any memory.
b.Run("With leading slash", func(b *testing.B) {
for b.Loop() {
got := AddLeadingSlash(withLeadingSlash)
if got != withLeadingSlash {
b.Fatal(got)
}
}
})
// This will allocate some memory.
b.Run("Without leading slash", func(b *testing.B) {
for b.Loop() {
got := AddLeadingSlash(noLeadingSlash)
if got != "/a/b/c" {
b.Fatal(got)
}
}
})
b.Run("Blank string", func(b *testing.B) {
for b.Loop() {
got := AddLeadingSlash("")
if got != "/" {
b.Fatal(got)
}
}
})
}
func TestPathEscape(t *testing.T) {
c := qt.New(t)
for _, this := range []struct {
input string
expected string
}{
{"/tags/欢迎", "/tags/%E6%AC%A2%E8%BF%8E"},
{"/path with spaces", "/path%20with%20spaces"},
{"/simple-path", "/simple-path"},
{"/path/with/slash", "/path/with/slash"},
{"/path/with special&chars", "/path/with%20special&chars"},
} {
in := this.input
for range 2 {
result := PathEscape(in)
c.Assert(result, qt.Equals, this.expected, qt.Commentf("input: %q", this.input))
in = result // test idempotency
}
}
}
+390 -101
View File
@@ -14,18 +14,27 @@
package paths
import (
"fmt"
"path"
"path/filepath"
"runtime"
"strings"
"sync"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/resources/kinds"
)
// PathParser parses a path into a Path.
const (
identifierBaseof = "baseof"
identifierCurstomWrapper = "_"
)
// PathParser parses and manages paths.
type PathParser struct {
// Maps the language code to its index in the languages/sites slice.
LanguageIndex map[string]int
@@ -33,8 +42,25 @@ type PathParser struct {
// Reports whether the given language is disabled.
IsLangDisabled func(string) bool
// IsOutputFormat reports whether the given name is a valid output format.
// The second argument is optional.
IsOutputFormat func(name, ext string) bool
// Reports whether the given ext is a content file.
IsContentExt func(string) bool
// The configured sites matrix.
ConfiguredDimensions *sitesmatrix.ConfiguredDimensions
// Below gets created on demand.
initOnce sync.Once
sitesMatrixCache *hmaps.Cache[string, sitesmatrix.VectorStore] // Maps language index to sites matrix vector store.
}
func (pp *PathParser) init() {
pp.initOnce.Do(func() {
pp.sitesMatrixCache = hmaps.NewCache[string, sitesmatrix.VectorStore]()
})
}
// NormalizePathString returns a normalized path string using the very basic Hugo rules.
@@ -48,6 +74,32 @@ func NormalizePathStringBasic(s string) string {
return s
}
func (pp *PathParser) SitesMatrixFromPath(p *Path) sitesmatrix.VectorStore {
pp.init()
lang := p.Lang()
v, _ := pp.sitesMatrixCache.GetOrCreate(lang, func() (sitesmatrix.VectorStore, error) {
builder := sitesmatrix.NewIntSetsBuilder(pp.ConfiguredDimensions)
if lang != "" {
if idx, ok := pp.LanguageIndex[lang]; ok {
builder.WithLanguageIndices(idx)
}
}
switch p.Component() {
case files.ComponentFolderContent:
builder.WithDefaultsIfNotSet()
case files.ComponentFolderLayouts:
builder.WithAllIfNotSet()
case files.ComponentFolderStatic:
builder.WithDefaultsAndAllLanguagesIfNotSet()
}
return builder.Build(), nil
})
return v
}
// ParseIdentity parses component c with path s into a StringIdentity.
func (pp *PathParser) ParseIdentity(c, s string) identity.StringIdentity {
p := pp.parsePooled(c, s)
@@ -83,13 +135,10 @@ func (pp *PathParser) Parse(c, s string) *Path {
}
func (pp *PathParser) newPath(component string) *Path {
return &Path{
component: component,
posContainerLow: -1,
posContainerHigh: -1,
posSectionHigh: -1,
posIdentifierLanguage: -1,
}
p := &Path{}
p.reset()
p.component = component
return p
}
func (pp *PathParser) parse(component, s string) (*Path, error) {
@@ -114,10 +163,114 @@ func (pp *PathParser) parse(component, s string) (*Path, error) {
return p, nil
}
func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
hasLang := pp.LanguageIndex != nil
hasLang = hasLang && (component == files.ComponentFolderContent || component == files.ComponentFolderLayouts)
func (pp *PathParser) parseIdentifier(component, s string, p *Path, i, lastDot, numDots int, isLast bool) {
if p.posContainerHigh != -1 {
return
}
mayHaveLang := numDots > 1 && p.posIdentifierLanguage == -1 && pp.LanguageIndex != nil
mayHaveLang = mayHaveLang && (component == files.ComponentFolderContent || component == files.ComponentFolderLayouts)
mayHaveOutputFormat := component == files.ComponentFolderLayouts
mayHaveKind := p.posIdentifierKind == -1 && mayHaveOutputFormat
var mayHaveLayout bool
if p.pathType == TypeShortcode {
mayHaveLayout = !isLast && component == files.ComponentFolderLayouts
} else {
mayHaveLayout = component == files.ComponentFolderLayouts
}
var found bool
var high int
if len(p.identifiersKnown) > 0 {
high = lastDot
} else {
high = len(p.s)
}
id := types.LowHigh[string]{Low: i + 1, High: high}
sid := p.s[id.Low:id.High]
if strings.HasPrefix(sid, identifierCurstomWrapper) && strings.HasSuffix(sid, identifierCurstomWrapper) {
p.identifiersKnown = append(p.identifiersKnown, id)
p.posIdentifierCustom = len(p.identifiersKnown) - 1
found = true
}
if len(p.identifiersKnown) == 0 {
// The first is always the extension.
p.identifiersKnown = append(p.identifiersKnown, id)
found = true
// May also be the output format.
if mayHaveOutputFormat && pp.IsOutputFormat(sid, "") {
p.posIdentifierOutputFormat = 0
}
} else {
var langFound bool
if mayHaveLang {
var disabled bool
_, langFound = pp.LanguageIndex[sid]
if !langFound {
disabled = pp.IsLangDisabled != nil && pp.IsLangDisabled(sid)
if disabled {
p.disabled = true
langFound = true
}
}
found = langFound
if langFound {
p.identifiersKnown = append(p.identifiersKnown, id)
p.posIdentifierLanguage = len(p.identifiersKnown) - 1
}
}
if !found && mayHaveOutputFormat {
// At this point we may already have resolved an output format,
// but we need to keep looking for a more specific one, e.g. amp before html.
// Use both name and extension to prevent
// false positives on the form css.html.
if pp.IsOutputFormat(sid, p.Ext()) {
found = true
p.identifiersKnown = append(p.identifiersKnown, id)
p.posIdentifierOutputFormat = len(p.identifiersKnown) - 1
}
}
if !found && mayHaveKind {
if kinds.GetKindMain(sid) != "" {
found = true
p.identifiersKnown = append(p.identifiersKnown, id)
p.posIdentifierKind = len(p.identifiersKnown) - 1
}
}
if !found && sid == identifierBaseof {
found = true
p.identifiersKnown = append(p.identifiersKnown, id)
p.posIdentifierBaseof = len(p.identifiersKnown) - 1
}
if !found && mayHaveLayout {
if p.posIdentifierLayout != -1 {
// Move it to identifiersUnknown.
p.identifiersUnknown = append(p.identifiersUnknown, p.identifiersKnown[p.posIdentifierLayout])
p.identifiersKnown[p.posIdentifierLayout] = id
} else {
p.identifiersKnown = append(p.identifiersKnown, id)
p.posIdentifierLayout = len(p.identifiersKnown) - 1
}
found = true
}
if !found {
p.identifiersUnknown = append(p.identifiersUnknown, id)
}
}
}
func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
if runtime.GOOS == "windows" {
s = path.Clean(filepath.ToSlash(s))
if s == "." {
@@ -140,46 +293,26 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
p.s = s
slashCount := 0
lastDot := 0
lastSlashIdx := strings.LastIndex(s, "/")
numDots := strings.Count(s[lastSlashIdx+1:], ".")
if strings.Contains(s, "/_shortcodes/") {
p.pathType = TypeShortcode
}
for i := len(s) - 1; i >= 0; i-- {
c := s[i]
switch c {
case '.':
if p.posContainerHigh == -1 {
var high int
if len(p.identifiers) > 0 {
high = p.identifiers[len(p.identifiers)-1].Low - 1
} else {
high = len(p.s)
}
id := types.LowHigh[string]{Low: i + 1, High: high}
if len(p.identifiers) == 0 {
p.identifiers = append(p.identifiers, id)
} else if len(p.identifiers) == 1 {
// Check for a valid language.
s := p.s[id.Low:id.High]
if hasLang {
var disabled bool
_, langFound := pp.LanguageIndex[s]
if !langFound {
disabled = pp.IsLangDisabled != nil && pp.IsLangDisabled(s)
if disabled {
p.disabled = true
langFound = true
}
}
if langFound {
p.posIdentifierLanguage = 1
p.identifiers = append(p.identifiers, id)
}
}
}
}
pp.parseIdentifier(component, s, p, i, lastDot, numDots, false)
lastDot = i
case '/':
slashCount++
if p.posContainerHigh == -1 {
if lastDot > 0 {
pp.parseIdentifier(component, s, p, i, lastDot, numDots, true)
}
p.posContainerHigh = i + 1
} else if p.posContainerLow == -1 {
p.posContainerLow = i + 1
@@ -190,26 +323,52 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
}
}
if len(p.identifiers) > 0 {
if len(p.identifiersKnown) > 0 {
isContentComponent := p.component == files.ComponentFolderContent || p.component == files.ComponentFolderArchetypes
isContent := isContentComponent && pp.IsContentExt(p.Ext())
id := p.identifiers[len(p.identifiers)-1]
b := p.s[p.posContainerHigh : id.Low-1]
if isContent {
switch b {
case "index":
p.bundleType = PathTypeLeaf
case "_index":
p.bundleType = PathTypeBranch
default:
p.bundleType = PathTypeContentSingle
}
id := p.identifiersKnown[len(p.identifiersKnown)-1]
if slashCount == 2 && p.IsLeafBundle() {
p.posSectionHigh = 0
if id.Low > p.posContainerHigh {
b := p.s[p.posContainerHigh : id.Low-1]
if isContent {
switch b {
case "index":
p.pathType = TypeLeaf
case "_index":
p.pathType = TypeBranch
default:
p.pathType = TypeContentSingle
}
if slashCount == 2 && p.IsLeafBundle() {
p.posSectionHigh = 0
}
} else if b == files.NameContentData && files.IsContentDataExt(p.Ext()) {
p.pathType = TypeContentData
}
} else if b == files.NameContentData && files.IsContentDataExt(p.Ext()) {
p.bundleType = PathTypeContentData
}
}
if p.pathType < TypeMarkup && component == files.ComponentFolderLayouts {
if p.posIdentifierBaseof != -1 {
p.pathType = TypeBaseof
} else {
pth := p.Path()
if strings.Contains(pth, "/_shortcodes/") {
p.pathType = TypeShortcode
} else if strings.Contains(pth, "/_markup/") {
p.pathType = TypeMarkup
} else if strings.HasPrefix(pth, "/_partials/") {
p.pathType = TypePartial
}
}
}
if p.pathType == TypeShortcode && p.posIdentifierLayout != -1 {
id := p.identifiersKnown[p.posIdentifierLayout]
if id.Low == p.posContainerHigh {
// First identifier is shortcode name.
p.posIdentifierLayout = -1
}
}
@@ -218,35 +377,44 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
func ModifyPathBundleTypeResource(p *Path) {
if p.IsContent() {
p.bundleType = PathTypeContentResource
p.pathType = TypeContentResource
} else {
p.bundleType = PathTypeFile
p.pathType = TypeFile
}
}
type PathType int
//go:generate stringer -type Type
type Type int
const (
// A generic resource, e.g. a JSON file.
PathTypeFile PathType = iota
// A generic file, e.g. a JSON file.
TypeFile Type = iota
// All below are content files.
// A resource of a content type with front matter.
PathTypeContentResource
TypeContentResource
// E.g. /blog/my-post.md
PathTypeContentSingle
TypeContentSingle
// All below are bundled content files.
// Leaf bundles, e.g. /blog/my-post/index.md
PathTypeLeaf
TypeLeaf
// Branch bundles, e.g. /blog/_index.md
PathTypeBranch
TypeBranch
// Content data file, _content.gotmpl.
PathTypeContentData
TypeContentData
// Layout types.
TypeMarkup
TypeShortcode
TypePartial
TypeBaseof
)
type Path struct {
@@ -257,13 +425,19 @@ type Path struct {
posContainerHigh int
posSectionHigh int
component string
bundleType PathType
component string
pathType Type
identifiers []types.LowHigh[string]
identifiersKnown []types.LowHigh[string]
identifiersUnknown []types.LowHigh[string]
posIdentifierLanguage int
disabled bool
posIdentifierLanguage int
posIdentifierOutputFormat int
posIdentifierKind int
posIdentifierLayout int
posIdentifierBaseof int
posIdentifierCustom int
disabled bool
trimLeadingSlash bool
@@ -293,9 +467,14 @@ func (p *Path) reset() {
p.posContainerHigh = -1
p.posSectionHigh = -1
p.component = ""
p.bundleType = 0
p.identifiers = p.identifiers[:0]
p.pathType = 0
p.identifiersKnown = p.identifiersKnown[:0]
p.posIdentifierLanguage = -1
p.posIdentifierOutputFormat = -1
p.posIdentifierKind = -1
p.posIdentifierLayout = -1
p.posIdentifierBaseof = -1
p.posIdentifierCustom = -1
p.disabled = false
p.trimLeadingSlash = false
p.unnormalized = nil
@@ -316,6 +495,9 @@ func (p *Path) norm(s string) string {
// IdentifierBase satisfies identity.Identity.
func (p *Path) IdentifierBase() string {
if p.Component() == files.ComponentFolderLayouts {
return p.Path()
}
return p.Base()
}
@@ -332,6 +514,13 @@ func (p *Path) Container() string {
return p.norm(p.s[p.posContainerLow : p.posContainerHigh-1])
}
func (p *Path) String() string {
if p == nil {
return "<nil>"
}
return p.Path()
}
// ContainerDir returns the container directory for this path.
// For content bundles this will be the parent directory.
func (p *Path) ContainerDir() string {
@@ -352,13 +541,13 @@ func (p *Path) Section() string {
// IsContent returns true if the path is a content file (e.g. mypost.md).
// Note that this will also return true for content files in a bundle.
func (p *Path) IsContent() bool {
return p.BundleType() >= PathTypeContentResource
return p.Type() >= TypeContentResource && p.Type() <= TypeContentData
}
// isContentPage returns true if the path is a content file (e.g. mypost.md),
// but nof if inside a leaf bundle.
func (p *Path) isContentPage() bool {
return p.BundleType() >= PathTypeContentSingle
return p.Type() >= TypeContentSingle && p.Type() <= TypeContentData
}
// Name returns the last element of path.
@@ -372,7 +561,7 @@ func (p *Path) Name() string {
// Name returns the last element of path without any extension.
func (p *Path) NameNoExt() string {
if i := p.identifierIndex(0); i != -1 {
return p.s[p.posContainerHigh : p.identifiers[i].Low-1]
return p.s[p.posContainerHigh : p.identifiersKnown[i].Low-1]
}
return p.s[p.posContainerHigh:]
}
@@ -384,7 +573,7 @@ func (p *Path) NameNoLang() string {
return p.Name()
}
return p.s[p.posContainerHigh:p.identifiers[i].Low-1] + p.s[p.identifiers[i].High:]
return p.s[p.posContainerHigh:p.identifiersKnown[i].Low-1] + p.s[p.identifiersKnown[i].High:]
}
// BaseNameNoIdentifier returns the logical base name for a resource without any identifier (e.g. no extension).
@@ -398,10 +587,26 @@ func (p *Path) BaseNameNoIdentifier() string {
// NameNoIdentifier returns the last element of path without any identifier (e.g. no extension).
func (p *Path) NameNoIdentifier() string {
if len(p.identifiers) > 0 {
return p.s[p.posContainerHigh : p.identifiers[len(p.identifiers)-1].Low-1]
lowHigh := p.nameLowHigh()
return p.s[lowHigh.Low:lowHigh.High]
}
func (p *Path) nameLowHigh() types.LowHigh[string] {
if len(p.identifiersKnown) > 0 {
lastID := p.identifiersKnown[len(p.identifiersKnown)-1]
if p.posContainerHigh == lastID.Low {
// The last identifier is the name.
return lastID
}
return types.LowHigh[string]{
Low: p.posContainerHigh,
High: p.identifiersKnown[len(p.identifiersKnown)-1].Low - 1,
}
}
return types.LowHigh[string]{
Low: p.posContainerHigh,
High: len(p.s),
}
return p.s[p.posContainerHigh:]
}
// Dir returns all but the last element of path, typically the path's directory.
@@ -421,6 +626,11 @@ func (p *Path) Path() (d string) {
return p.norm(p.s)
}
// PathNoLeadingSlash returns the full path without the leading slash.
func (p *Path) PathNoLeadingSlash() string {
return p.Path()[1:]
}
// Unnormalized returns the Path with the original case preserved.
func (p *Path) Unnormalized() *Path {
return p.unnormalized
@@ -428,6 +638,9 @@ func (p *Path) Unnormalized() *Path {
// PathNoLang returns the Path but with any language identifier removed.
func (p *Path) PathNoLang() string {
if p.identifierIndex(p.posIdentifierLanguage) == -1 {
return p.Path()
}
return p.base(true, false)
}
@@ -436,6 +649,28 @@ func (p *Path) PathNoIdentifier() string {
return p.base(false, false)
}
// PathBeforeLangAndOutputFormatAndExt returns the path up to the first identifier that is not a language or output format.
func (p *Path) PathBeforeLangAndOutputFormatAndExt() string {
if len(p.identifiersKnown) == 0 {
return p.norm(p.s)
}
i := p.identifierIndex(0)
if j := p.posIdentifierOutputFormat; i == -1 || (j != -1 && j < i) {
i = j
}
if j := p.posIdentifierLanguage; i == -1 || (j != -1 && j < i) {
i = j
}
if i == -1 {
return p.norm(p.s)
}
id := p.identifiersKnown[i]
return p.norm(p.s[:id.Low-1])
}
// PathRel returns the path relative to the given owner.
func (p *Path) PathRel(owner *Path) string {
ob := owner.Base()
@@ -459,7 +694,27 @@ func (p *Path) BaseRel(owner *Path) string {
//
// For other files (Resources), any extension is kept.
func (p *Path) Base() string {
return p.base(!p.isContentPage(), p.IsBundle())
s := p.base(!p.isContentPage(), p.IsBundle())
if s == "/" && p.isContentPage() {
// The content home page is represented as "".
s = ""
}
return s
}
// Used in template lookups.
// For pages with Type set, we treat that as the section.
func (p *Path) BaseReTyped(typ string) (d string) {
base := p.Base()
if typ == "" || p.Section() == typ {
return base
}
d = "/" + typ
if p.posSectionHigh != -1 {
d += base[p.posSectionHigh:]
}
d = p.norm(d)
return
}
// BaseNoLeadingSlash returns the base path without the leading slash.
@@ -468,20 +723,21 @@ func (p *Path) BaseNoLeadingSlash() string {
}
func (p *Path) base(preserveExt, isBundle bool) string {
if len(p.identifiers) == 0 {
if len(p.identifiersKnown) == 0 {
return p.norm(p.s)
}
if preserveExt && len(p.identifiers) == 1 {
if preserveExt && len(p.identifiersKnown) == 1 {
// Preserve extension.
return p.norm(p.s)
}
id := p.identifiers[len(p.identifiers)-1]
high := id.Low - 1
var high int
if isBundle {
high = p.posContainerHigh - 1
} else {
high = p.nameLowHigh().High
}
if high == 0 {
@@ -493,7 +749,7 @@ func (p *Path) base(preserveExt, isBundle bool) string {
}
// For txt files etc. we want to preserve the extension.
id = p.identifiers[0]
id := p.identifiersKnown[0]
return p.norm(p.s[:high] + p.s[id.Low-1:id.High])
}
@@ -502,8 +758,24 @@ func (p *Path) Ext() string {
return p.identifierAsString(0)
}
func (p *Path) OutputFormat() string {
return p.identifierAsString(p.posIdentifierOutputFormat)
}
func (p *Path) Kind() string {
return p.identifierAsString(p.posIdentifierKind)
}
func (p *Path) Layout() string {
return p.identifierAsString(p.posIdentifierLayout)
}
func (p *Path) Lang() string {
return p.identifierAsString(1)
return p.identifierAsString(p.posIdentifierLanguage)
}
func (p *Path) Custom() string {
return strings.TrimSuffix(strings.TrimPrefix(p.identifierAsString(p.posIdentifierCustom), identifierCurstomWrapper), identifierCurstomWrapper)
}
func (p *Path) Identifier(i int) string {
@@ -515,35 +787,43 @@ func (p *Path) Disabled() bool {
}
func (p *Path) Identifiers() []string {
ids := make([]string, len(p.identifiers))
for i, id := range p.identifiers {
ids := make([]string, len(p.identifiersKnown))
for i, id := range p.identifiersKnown {
ids[i] = p.s[id.Low:id.High]
}
return ids
}
func (p *Path) BundleType() PathType {
return p.bundleType
func (p *Path) IdentifiersUnknown() []string {
ids := make([]string, len(p.identifiersUnknown))
for i, id := range p.identifiersUnknown {
ids[i] = p.s[id.Low:id.High]
}
return ids
}
func (p *Path) Type() Type {
return p.pathType
}
func (p *Path) IsBundle() bool {
return p.bundleType >= PathTypeLeaf
return p.pathType >= TypeLeaf && p.pathType <= TypeContentData
}
func (p *Path) IsBranchBundle() bool {
return p.bundleType == PathTypeBranch
return p.pathType == TypeBranch
}
func (p *Path) IsLeafBundle() bool {
return p.bundleType == PathTypeLeaf
return p.pathType == TypeLeaf
}
func (p *Path) IsContentData() bool {
return p.bundleType == PathTypeContentData
return p.pathType == TypeContentData
}
func (p Path) ForBundleType(t PathType) *Path {
p.bundleType = t
func (p Path) ForType(t Type) *Path {
p.pathType = t
return &p
}
@@ -553,12 +833,12 @@ func (p *Path) identifierAsString(i int) string {
return ""
}
id := p.identifiers[i]
id := p.identifiersKnown[i]
return p.s[id.Low:id.High]
}
func (p *Path) identifierIndex(i int) int {
if i < 0 || i >= len(p.identifiers) {
if i < 0 || i >= len(p.identifiersKnown) {
return -1
}
return i
@@ -576,3 +856,12 @@ func HasExt(p string) bool {
}
return false
}
// ValidateIdentifier returns true if the given string is a valid identifier according
// to Hugo's basic path normalization rules.
func ValidateIdentifier(s string) error {
if s == NormalizePathStringBasic(s) {
return nil
}
return fmt.Errorf("must be all lower case and no spaces")
}
+302 -16
View File
@@ -18,18 +18,33 @@ import (
"testing"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
"github.com/gohugoio/hugo/resources/kinds"
qt "github.com/frankban/quicktest"
)
var testParser = &PathParser{
LanguageIndex: map[string]int{
"no": 0,
"en": 1,
},
IsContentExt: func(ext string) bool {
return ext == "md"
},
func newTestParser() *PathParser {
dims := sitesmatrix.NewTestingDimensions([]string{"en", "no", "fr"}, []string{"v1", "v2", "v3"}, []string{"admin", "editor", "viewer", "guest"})
return &PathParser{
LanguageIndex: map[string]int{
"no": 0,
"en": 1,
"fr": 2,
},
IsContentExt: func(ext string) bool {
return ext == "md"
},
IsOutputFormat: func(name, ext string) bool {
switch name {
case "html", "amp", "csv", "rss":
return true
}
return false
},
ConfiguredDimensions: dims,
}
}
func TestParse(t *testing.T) {
@@ -105,17 +120,19 @@ func TestParse(t *testing.T) {
"Basic Markdown file",
"/a/b/c.md",
func(c *qt.C, p *Path) {
c.Assert(p.Ext(), qt.Equals, "md")
c.Assert(p.Type(), qt.Equals, TypeContentSingle)
c.Assert(p.IsContent(), qt.IsTrue)
c.Assert(p.IsLeafBundle(), qt.IsFalse)
c.Assert(p.Name(), qt.Equals, "c.md")
c.Assert(p.Base(), qt.Equals, "/a/b/c")
c.Assert(p.BaseReTyped("foo"), qt.Equals, "/foo/b/c")
c.Assert(p.Section(), qt.Equals, "a")
c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "c")
c.Assert(p.Path(), qt.Equals, "/a/b/c.md")
c.Assert(p.Dir(), qt.Equals, "/a/b")
c.Assert(p.Container(), qt.Equals, "b")
c.Assert(p.ContainerDir(), qt.Equals, "/a/b")
c.Assert(p.Ext(), qt.Equals, "md")
},
},
{
@@ -130,7 +147,7 @@ func TestParse(t *testing.T) {
// Reclassify it as a content resource.
ModifyPathBundleTypeResource(p)
c.Assert(p.BundleType(), qt.Equals, PathTypeContentResource)
c.Assert(p.Type(), qt.Equals, TypeContentResource)
c.Assert(p.IsContent(), qt.IsTrue)
c.Assert(p.Name(), qt.Equals, "b.md")
c.Assert(p.Base(), qt.Equals, "/a/b.md")
@@ -163,8 +180,10 @@ func TestParse(t *testing.T) {
c.Assert(p.NameNoIdentifier(), qt.Equals, "b.a.b")
c.Assert(p.NameNoLang(), qt.Equals, "b.a.b.txt")
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no"})
c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{"b", "a", "b"})
c.Assert(p.Base(), qt.Equals, "/a/b.a.b.txt")
c.Assert(p.BaseNoLeadingSlash(), qt.Equals, "a/b.a.b.txt")
c.Assert(p.Path(), qt.Equals, "/a/b.a.b.no.txt")
c.Assert(p.PathNoLang(), qt.Equals, "/a/b.a.b.txt")
c.Assert(p.Ext(), qt.Equals, "txt")
c.Assert(p.PathNoIdentifier(), qt.Equals, "/a/b.a.b")
@@ -174,7 +193,11 @@ func TestParse(t *testing.T) {
"Home branch cundle",
"/_index.md",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/")
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md"})
c.Assert(p.IsBranchBundle(), qt.IsTrue)
c.Assert(p.IsBundle(), qt.IsTrue)
c.Assert(p.Base(), qt.Equals, "")
c.Assert(p.BaseReTyped("foo"), qt.Equals, "/foo")
c.Assert(p.Path(), qt.Equals, "/_index.md")
c.Assert(p.Container(), qt.Equals, "")
c.Assert(p.ContainerDir(), qt.Equals, "/")
@@ -185,12 +208,14 @@ func TestParse(t *testing.T) {
"/a/index.md",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a")
c.Assert(p.BaseReTyped("foo"), qt.Equals, "/foo/a")
c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "a")
c.Assert(p.Container(), qt.Equals, "a")
c.Assert(p.Container(), qt.Equals, "a")
c.Assert(p.ContainerDir(), qt.Equals, "")
c.Assert(p.Dir(), qt.Equals, "/a")
c.Assert(p.Ext(), qt.Equals, "md")
c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{"index"})
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md"})
c.Assert(p.IsBranchBundle(), qt.IsFalse)
c.Assert(p.IsBundle(), qt.IsTrue)
@@ -208,6 +233,7 @@ func TestParse(t *testing.T) {
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a/b")
c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "b")
c.Assert(p.BaseReTyped("foo"), qt.Equals, "/foo/b")
c.Assert(p.Container(), qt.Equals, "b")
c.Assert(p.ContainerDir(), qt.Equals, "/a")
c.Assert(p.Dir(), qt.Equals, "/a/b")
@@ -220,6 +246,7 @@ func TestParse(t *testing.T) {
c.Assert(p.NameNoExt(), qt.Equals, "index.no")
c.Assert(p.NameNoIdentifier(), qt.Equals, "index")
c.Assert(p.NameNoLang(), qt.Equals, "index.md")
c.Assert(p.Path(), qt.Equals, "/a/b/index.no.md")
c.Assert(p.PathNoLang(), qt.Equals, "/a/b/index.md")
c.Assert(p.Section(), qt.Equals, "a")
},
@@ -245,7 +272,7 @@ func TestParse(t *testing.T) {
"Index root no slash",
"_index.md",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/")
c.Assert(p.Base(), qt.Equals, "")
c.Assert(p.Ext(), qt.Equals, "md")
c.Assert(p.Name(), qt.Equals, "_index.md")
},
@@ -254,7 +281,7 @@ func TestParse(t *testing.T) {
"Index root",
"/_index.md",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/")
c.Assert(p.Base(), qt.Equals, "")
c.Assert(p.Ext(), qt.Equals, "md")
c.Assert(p.Name(), qt.Equals, "_index.md")
},
@@ -352,10 +379,249 @@ func TestParse(t *testing.T) {
c.Assert(p.IsContentData(), qt.IsFalse)
},
},
{
"Custom identifier",
"/a/b/p1._myid_.no.md",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a/b/p1")
c.Assert(p.Lang(), qt.Equals, "no")
c.Assert(p.Ext(), qt.Equals, "md")
c.Assert(p.Custom(), qt.Equals, "myid")
},
},
}
parser := newTestParser()
for _, test := range tests {
c.Run(test.name, func(c *qt.C) {
test.assert(c, testParser.Parse(files.ComponentFolderContent, test.path))
if test.name != "Caret up identifier" {
// return
}
test.assert(c, parser.Parse(files.ComponentFolderContent, test.path))
})
}
}
func TestParseLayouts(t *testing.T) {
c := qt.New(t)
tests := []struct {
name string
path string
assert func(c *qt.C, p *Path)
}{
{
"Basic",
"/list.html",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/list.html")
c.Assert(p.OutputFormat(), qt.Equals, "html")
},
},
{
"Lang",
"/list.no.html",
func(c *qt.C, p *Path) {
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "no", "list"})
c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
c.Assert(p.Base(), qt.Equals, "/list.html")
c.Assert(p.Lang(), qt.Equals, "no")
},
},
{
"Kind",
"/section.no.html",
func(c *qt.C, p *Path) {
c.Assert(p.Kind(), qt.Equals, kinds.KindSection)
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "no", "section"})
c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
c.Assert(p.Base(), qt.Equals, "/section.html")
c.Assert(p.Lang(), qt.Equals, "no")
},
},
{
"Layout",
"/list.section.no.html",
func(c *qt.C, p *Path) {
c.Assert(p.Layout(), qt.Equals, "list")
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "no", "section", "list"})
c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
c.Assert(p.Base(), qt.Equals, "/list.html")
c.Assert(p.Lang(), qt.Equals, "no")
},
},
{
"Layout multiple",
"/mylayout.list.section.no.html",
func(c *qt.C, p *Path) {
c.Assert(p.Layout(), qt.Equals, "mylayout")
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "no", "section", "mylayout"})
c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{"list"})
c.Assert(p.Base(), qt.Equals, "/mylayout.html")
c.Assert(p.Lang(), qt.Equals, "no")
},
},
{
"Layout shortcode",
"/_shortcodes/myshort.list.no.html",
func(c *qt.C, p *Path) {
c.Assert(p.Layout(), qt.Equals, "list")
},
},
{
"Layout baseof",
"/baseof.list.no.html",
func(c *qt.C, p *Path) {
c.Assert(p.Layout(), qt.Equals, "list")
},
},
{
"Lang and output format",
"/list.no.amp.not.html",
func(c *qt.C, p *Path) {
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "list", "amp", "no"})
c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{"not"})
c.Assert(p.OutputFormat(), qt.Equals, "amp")
c.Assert(p.Ext(), qt.Equals, "html")
c.Assert(p.Lang(), qt.Equals, "no")
c.Assert(p.Base(), qt.Equals, "/list.html")
},
},
{
"Term",
"/term.html",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/term.html")
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "term"})
c.Assert(p.PathNoIdentifier(), qt.Equals, "/term")
c.Assert(p.PathBeforeLangAndOutputFormatAndExt(), qt.Equals, "/term")
c.Assert(p.Lang(), qt.Equals, "")
c.Assert(p.Kind(), qt.Equals, "term")
c.Assert(p.OutputFormat(), qt.Equals, "html")
},
},
{
"Shortcode with layout",
"/_shortcodes/myshortcode.list.html",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/_shortcodes/myshortcode.html")
c.Assert(p.Type(), qt.Equals, TypeShortcode)
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "list"})
c.Assert(p.Layout(), qt.Equals, "list")
c.Assert(p.PathNoIdentifier(), qt.Equals, "/_shortcodes/myshortcode")
c.Assert(p.PathBeforeLangAndOutputFormatAndExt(), qt.Equals, "/_shortcodes/myshortcode.list")
c.Assert(p.Lang(), qt.Equals, "")
c.Assert(p.Kind(), qt.Equals, "")
c.Assert(p.OutputFormat(), qt.Equals, "html")
},
},
{
"Sub dir",
"/pages/home.html",
func(c *qt.C, p *Path) {
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "home"})
c.Assert(p.Lang(), qt.Equals, "")
c.Assert(p.Kind(), qt.Equals, "home")
c.Assert(p.OutputFormat(), qt.Equals, "html")
c.Assert(p.Dir(), qt.Equals, "/pages")
},
},
{
"Baseof",
"/pages/baseof.list.section.fr.amp.html",
func(c *qt.C, p *Path) {
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "amp", "fr", "section", "list", "baseof"})
c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
c.Assert(p.Kind(), qt.Equals, kinds.KindSection)
c.Assert(p.Lang(), qt.Equals, "fr")
c.Assert(p.OutputFormat(), qt.Equals, "amp")
c.Assert(p.Dir(), qt.Equals, "/pages")
c.Assert(p.NameNoIdentifier(), qt.Equals, "baseof")
c.Assert(p.Type(), qt.Equals, TypeBaseof)
c.Assert(p.IdentifierBase(), qt.Equals, "/pages/baseof.list.section.fr.amp.html")
},
},
{
"Markup",
"/_markup/render-link.html",
func(c *qt.C, p *Path) {
c.Assert(p.Type(), qt.Equals, TypeMarkup)
},
},
{
"Markup nested",
"/foo/_markup/render-link.html",
func(c *qt.C, p *Path) {
c.Assert(p.Type(), qt.Equals, TypeMarkup)
},
},
{
"Shortcode",
"/_shortcodes/myshortcode.html",
func(c *qt.C, p *Path) {
c.Assert(p.Type(), qt.Equals, TypeShortcode)
},
},
{
"Shortcode nested",
"/foo/_shortcodes/myshortcode.html",
func(c *qt.C, p *Path) {
c.Assert(p.Type(), qt.Equals, TypeShortcode)
},
},
{
"Shortcode nested sub",
"/foo/_shortcodes/foo/myshortcode.html",
func(c *qt.C, p *Path) {
c.Assert(p.Type(), qt.Equals, TypeShortcode)
},
},
{
"Partials",
"/_partials/foo.bar",
func(c *qt.C, p *Path) {
c.Assert(p.Type(), qt.Equals, TypePartial)
},
},
{
"Shortcode lang in root",
"/_shortcodes/no.html",
func(c *qt.C, p *Path) {
c.Assert(p.Type(), qt.Equals, TypeShortcode)
c.Assert(p.Lang(), qt.Equals, "")
c.Assert(p.NameNoIdentifier(), qt.Equals, "no")
},
},
{
"Shortcode lang layout",
"/_shortcodes/myshortcode.no.html",
func(c *qt.C, p *Path) {
c.Assert(p.Type(), qt.Equals, TypeShortcode)
c.Assert(p.Lang(), qt.Equals, "no")
c.Assert(p.Layout(), qt.Equals, "")
c.Assert(p.NameNoIdentifier(), qt.Equals, "myshortcode")
},
},
{
"Not lang",
"/foo/index.xy.html",
func(c *qt.C, p *Path) {
c.Assert(p.Lang(), qt.Equals, "")
c.Assert(p.Layout(), qt.Equals, "index")
c.Assert(p.NameNoLang(), qt.Equals, "index.xy.html")
c.Assert(p.PathNoLang(), qt.Equals, "/foo/index.xy.html")
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "index"})
c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{"xy"})
},
},
}
parser := newTestParser()
for _, test := range tests {
c.Run(test.name, func(c *qt.C) {
if test.name != "Not lang" {
return
}
test.assert(c, parser.Parse(files.ComponentFolderLayouts, test.path))
})
}
}
@@ -370,7 +636,27 @@ func TestHasExt(t *testing.T) {
}
func BenchmarkParseIdentity(b *testing.B) {
for i := 0; i < b.N; i++ {
testParser.ParseIdentity(files.ComponentFolderAssets, "/a/b.css")
parser := newTestParser()
for b.Loop() {
parser.ParseIdentity(files.ComponentFolderAssets, "/a/b.css")
}
}
func TestSitesMatrixFromPath(t *testing.T) {
c := qt.New(t)
parser := newTestParser()
p := parser.Parse(files.ComponentFolderContent, "/a/b/c.fr.md")
v := parser.SitesMatrixFromPath(p)
c.Assert(v.HasLanguage(2), qt.IsTrue)
c.Assert(v.LenVectors(), qt.Equals, 1)
c.Assert(v.VectorSample(), qt.Equals, sitesmatrix.Vector{2, 0, 0})
}
func BenchmarkSitesMatrixFromPath(b *testing.B) {
parser := newTestParser()
p := parser.Parse(files.ComponentFolderContent, "/a/b/c.fr.md")
for b.Loop() {
parser.SitesMatrixFromPath(p)
}
}
+27 -4
View File
@@ -35,9 +35,9 @@ weight = 2
removePathAccents = true
-- content/διακριτικός.md --
-- content/διακριτικός.fr.md --
-- layouts/_default/single.html --
-- layouts/single.html --
{{ .Language.Lang }}|Single.
-- layouts/_default/list.html --
-- layouts/list.html --
List
`
b := hugolib.Test(t, files)
@@ -64,9 +64,9 @@ disablePathToLower = true
-- content/MySection/MyPage.fr.md --
-- content/MySection/MyBundle/index.md --
-- content/MySection/MyBundle/index.fr.md --
-- layouts/_default/single.html --
-- layouts/single.html --
{{ .Language.Lang }}|Single.
-- layouts/_default/list.html --
-- layouts/list.html --
{{ .Language.Lang }}|List.
`
b := hugolib.Test(t, files)
@@ -78,3 +78,26 @@ disablePathToLower = true
b.AssertFileContent("public/en/mysection/mybundle/index.html", "en|Single")
b.AssertFileContent("public/fr/MySection/MyBundle/index.html", "fr|Single")
}
func TestIssue13596(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
-- content/p1/index.md --
---
title: p1
---
-- content/p1/a.1.txt --
-- content/p1/a.2.txt --
-- layouts/all.html --
{{ range .Resources.Match "*" }}{{ .Name }}|{{ end }}
`
b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html", "a.1.txt|a.2.txt|")
b.AssertFileExists("public/p1/a.1.txt", true)
b.AssertFileExists("public/p1/a.2.txt", true) // fails
}
-27
View File
@@ -1,27 +0,0 @@
// Code generated by "stringer -type=PathType"; DO NOT EDIT.
package paths
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[PathTypeFile-0]
_ = x[PathTypeContentResource-1]
_ = x[PathTypeContentSingle-2]
_ = x[PathTypeLeaf-3]
_ = x[PathTypeBranch-4]
}
const _PathType_name = "PathTypeFilePathTypeContentResourcePathTypeContentSinglePathTypeLeafPathTypeBranch"
var _PathType_index = [...]uint8{0, 12, 35, 56, 68, 82}
func (i PathType) String() string {
if i < 0 || i >= PathType(len(_PathType_index)-1) {
return "PathType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _PathType_name[_PathType_index[i]:_PathType_index[i+1]]
}

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