Compare commits

..

582 commits

Author SHA1 Message Date
David Jones
dd6e2c8724
deploy: walkLocal worker pool for performance
Some checks are pending
Test / test (1.23.x, ubuntu-latest) (push) Waiting to run
Test / test (1.23.x, windows-latest) (push) Waiting to run
Test / test (1.24.x, ubuntu-latest) (push) Waiting to run
Test / test (1.24.x, windows-latest) (push) Waiting to run
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
61a286595e
Merge commit 'b3d87dd0fd' 2025-04-24 10:23:16 +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: b654fcba0d4385da1e47179ef3763f277f044242
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 '5be51ac3db' 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 'a024bc7d76'
733731253 helpers: Add Chroma styles to docs.yaml

git-subtree-dir: docs
git-subtree-split: dc7a9ae127717cde17840496cb0287f481c5f0ee
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: d1a2519330f3647447053c89c49d6098af29519f
2025-02-13 10:40:34 +01:00
Bjørn Erik Pedersen
304a7e5e74
Merge commit 'a024bc7d76' 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
Joe Mooring
e08d9af21e markup/goldmark: Trim space from blockquote render hook text
Closes #13302
2025-01-24 19:58:45 +01:00
Bjørn Erik Pedersen
cd7dc7a372 Fix some server rebuild issues for non-HTML custom output formats
The failing test case here is

* A custom search output format defined on the home page, marked as `noAlternative` and not `permalinkable`
* In fast render mode, when making a change to a data source for that search output format, the JSON file was not refreshed.

There are variants of the above, but the gist of it is:

* The change set was correctly determined, but since the search JSON file was not in the recently visited browser stack, we skipped rendering it.

Running with `hugo server --disableFastRender` would be a workaround for the above.

This commit fixes this by:

* Adding a check for the HTTP request header `Sec-Fetch-Mode = navigation` to the condition for if we should track server request as a user navigation (and not e.g. a HTTP request for a linked CSS stylesheet).
* Making sure that we compare against the real relative URL for non-permalinkable output formats.

Fixes #13014
2025-01-24 17:27:02 +01:00
Guilherme Soares
c939c33fd3 parser/pageparser: Don't allow parameters after closing tag in shortcodes
Problem:
Previously, the following self-closing shortcode syntax was incorrectly allowed:
{{% sc / param %}}

Solution:
Only allow parameters before the self-closing tag
2025-01-24 15:31:44 +01:00
Bjørn Erik Pedersen
bb7b3d3cdb Fix cascade with overlapping sections
Fixes #12465
2025-01-24 15:25:54 +01:00
Joe Mooring
873a5cda1a tpl/tplimpl: Improve shortcode test coverage 2025-01-24 09:10:45 +01:00
Joe Mooring
f42a4b6af5 tpl/tplimpl: Deprecate gist shortcode
Closes #13211
2025-01-23 20:12:36 +01:00
Bjørn Erik Pedersen
a5637831cd
resources: Remove conditional used for debugging 2025-01-23 17:53:38 +01:00
Bjørn Erik Pedersen
68586c891c resources: Add responseHeaders option to resources.GetRemote
* These response headers will be included in `.Data.Headers` if found.
* The header name matching is case insensitive.
* `Data.Headers` is of type `map[string][]string`
* In most cases there will be only one value per header key, but e.g. `Set-Cookie` commonly has multiple values.

Fixes #12521
2025-01-23 15:20:03 +01:00
Bjørn Erik Pedersen
51bb2fedab
tpl/tplimpl: Skip TestTemplateFuncsExamples on s390x
Closes #13204
2025-01-23 10:18:43 +01:00
Bjørn Erik Pedersen
43307b07f8
Merge commit '346b60358d' 2025-01-23 09:47:46 +01:00
Bjørn Erik Pedersen
346b60358d Squashed 'docs/' changes from 4429eeeea..73a01565c
73a01565c Remove comment shortcode documentation
0ca7ccd30 Replace usage of comment shortcode with HTML comments
fe10d9899 Remove expired new-in labels
11e89dfcb [editorial] Link to proper render-hook page in relref.md
11a581c2f netlify: Hugo 0.142.0
1a4fcf7f7 Miscellaneous edits
2c7a3165f Markdown linting and cleanup
69d7a781b Replace links to glossary terms with custom render hook syntax
441752d2d Refactor glossary lookup portion of link render hook
80109a14f Fix glossary term linking for plural form
cd95f0f34 Update link render hook to support glossary links
53eadb430 Remove the glossary template
1d40a7f3b Improve transform.ToMath examples
586970df2 Misc edits
6a06a8de7 Add glossary link shortcode
4171c0eb7 Improve description of masking with non-transparent images
41c8feb64 Fix example of image.Mask filter
704a81656 Add alignx option to images.Text usage example
7c03eb0cc Clarify context in example of using the try statement
56d9c9b71 Refactor glossary of terms
042a6846b Add expiry dates to deprecated methods pages
365ab345f Remove services key from instagram shortcode page
b7fe31e07 Reorder options list for images.Text filter
8051ff818 Format directory names, file names, and file paths as code
d09a14623 Update version refs for Hugo and Dart Sass
3bb006974 Add NODE_VERSION to Netlify config examples
3a0f2531e Fix typo
7e3198eaf Fix typo
cf56452a3 Fix typo
a9f51d13e Fix typo
82bfdd8c3 Fix typo
a5c41a053 Fix typo
abcfed7a5 Fix typo
8c1debf3a Remove outdated new-in badges
809ddf9ef Update theme
63867d56f Use warnf instead of errorf in try-catch example
dee3e5f09 Update theme
9791f7057 Remove TODO from comment shortcode examples
a346ca1fd Elevate embedded shortcode documentation to its own section
8fa19b50f hugoreleaser.toml =>  hugoreleaser.yaml
575d60345 Update docs for v0.141.0
a0a442d62 netlify: Hugo 0.141.0
6667cbcd8 Merge commit '81a7b63900'
f36fc013e docs: Regen CLI docs
365a47ded tpl/images: Change signature of images.QR to images.QR TEXT OPTIONS
ae8f8af0a images.Text: Add "alignx" option for horizontal alignment
8f45ccca6 docs: Regen CLI docs
f0e6304f4 Merge commit 'e9fbadacc3'
cb9bec2b2 tpl/images: Add images.QR function

git-subtree-dir: docs
git-subtree-split: 73a01565c5ba0774d65aa6f2384c44804fefa37d
2025-01-23 09:46:02 +01:00
Bjørn Erik Pedersen
7f0f50b133 Make cascade front matter order deterministic
Fixes #12594
2025-01-23 09:45:47 +01:00
Joe Mooring
77a8e347bc tpl/tplimpl: Deprecate comment shortcode 2025-01-22 19:38:02 +01:00
Patrice Chalin
f704d75699
markup/goldmark: Fix typo in func comment 2025-01-22 14:57:24 +01:00
hugoreleaser
9d765e1b99 releaser: Prepare repository for 0.143.0-DEV
[ci skip]
2025-01-22 12:35:08 +00:00
hugoreleaser
1f746a8724 releaser: Bump versions for release of 0.142.0
[ci skip]
2025-01-22 12:20:52 +00:00
Bjørn Erik Pedersen
6aa72acaf9 Fix render hook's PlainText with typographer extension enabled
Fixes #13286
Fixes #13292
2025-01-22 12:49:25 +01:00
Bjørn Erik Pedersen
9885e7020d Improve assert 2025-01-22 09:44:26 +01:00
Bjørn Erik Pedersen
91101d24f2 Improve assertions 2025-01-22 09:44:26 +01:00
Bjørn Erik Pedersen
637995ba8f Also handle inline HTML comments 2025-01-22 09:44:26 +01:00
Bjørn Erik Pedersen
f1de5d2a04 Do not warn on potentially unsafe HTML comments when unsafe=false
We will still not render these comments, so from a safety perspective this is the same, but HTML comments are very common also inside Markdown and too useful to throw away.

Updates #13278
2025-01-22 09:44:26 +01:00
W. Michael Petullo
4b0c194fb3 Fix build with Go 1.24
Go 1.24 provides stricter checking that forbids passing a variable as
a format string to Printf-family functions with no other arguments. Remove
instances of this. See also:

https://tip.golang.org/doc/go1.24#vet

Signed-off-by: W. Michael Petullo <mike@flyn.org>
2025-01-21 11:10:03 +01:00
Joe Mooring
8de4ffb294 tpl/tplimpl: Fix context in shortcode error messages
Fixes #13279
2025-01-20 16:17:55 +01:00
Bjørn Erik Pedersen
1f5a15aba0 resources: Fix 2 image file cache key issues
* Always include the content hash in the cache key for unprocessed images.
* Always include the image config hash in the cache key.

This is also a major cleanup/simplification of the implementation in this area.

Note that this, unfortunately, forces new hashes/filenames for generated images.

Fixes #13273
Fixes #13272
2025-01-18 12:23:18 +01:00
Nicolas Friedli
8897113666
tpl: Add loading attribute to qr shortcode 2025-01-17 19:06:54 +01:00
hugoreleaser
e91d3cff98 releaser: Prepare repository for 0.142.0-DEV
[ci skip]
2025-01-16 13:26:52 +00:00
hugoreleaser
e7bd51698e releaser: Bump versions for release of 0.141.0
[ci skip]
2025-01-16 13:11:18 +00:00
Bjørn Erik Pedersen
1fad3832a9 tpl/tplimpl: Simplify some test assertions 2025-01-16 11:29:24 +01:00
Jack Baldry
8b52626915
common/paths: Fix docstring 2025-01-16 10:58:18 +01:00
Bjørn Erik Pedersen
81a7b63900 Squashed 'docs/' changes from f0f4bcb24..4429eeeea
4429eeeea Update image render hooks examples to conform with Commonmark
5391dddea Add class attribute to heading render hook examples

git-subtree-dir: docs
git-subtree-split: 4429eeeea84bfa4b9e636deaab5c9620ccf776a6
2025-01-16 10:35:07 +01:00
Bjørn Erik Pedersen
5c5508817f
Merge commit '81a7b63900' 2025-01-16 10:35:07 +01:00
Bjørn Erik Pedersen
88ecc3b753
docs: Regen CLI docs 2025-01-16 10:34:52 +01:00
Joe Mooring
1191467c05 tpl/tplimpl: Deprecate twitter shortcode in favor of x shortcode
Closes #13214
2025-01-16 10:33:34 +01:00
Bjørn Erik Pedersen
60c24fc5ee
commands: Fix spelling in comment 2025-01-15 22:31:52 +01:00
Bjørn Erik Pedersen
41137077bc
commands: Set up the glboal logger early
Fixes #13265
2025-01-15 22:31:08 +01:00
Bjørn Erik Pedersen
5bb1564575 commands: Add --printZero to the config command
Will include zero config values (e.g. "", 0, false) in the output and will be more verbose, but useful if you cant to discover all available config options.
2025-01-15 22:48:14 +02:00
Bjørn Erik Pedersen
76993369a0 For render hooks, only fallback to HTML (or the defaultOutputFormat) template
Closes #13242
2025-01-15 19:59:58 +02:00
Bjørn Erik Pedersen
de7137cc35 tpl/collections: Use MapRange/SetIterKey/SetIterValue for Where, Sort and Merge
Some relevant benchmarks:

Where with maps:

```
cpu: Apple M1 Pro
            │ master.bench │         fix-mapkeys.bench          │
            │    sec/op    │   sec/op     vs base               │
WhereMap-10    79.26µ ± 1%   26.58µ ± 1%  -66.46% (p=0.002 n=6)

            │ master.bench │         fix-mapkeys.bench         │
            │     B/op     │    B/op     vs base               │
WhereMap-10   56685.0 ± 0%   111.0 ± 1%  -99.80% (p=0.002 n=6)

            │ master.bench  │         fix-mapkeys.bench         │
            │   allocs/op   │ allocs/op   vs base               │
WhereMap-10   2003.000 ± 0%   4.000 ± 0%  -99.80% (p=0.002 n=6)
```

Merge:

```
         │ master.bench │         fix-mapkeys.bench          │
         │    sec/op    │   sec/op     vs base               │
Merge-10    3.285µ ± 0%   2.268µ ± 1%  -30.96% (p=0.002 n=6)

         │ master.bench │          fix-mapkeys.bench          │
         │     B/op     │     B/op      vs base               │
Merge-10   3.079Ki ± 0%   1.891Ki ± 0%  -38.58% (p=0.002 n=6)

         │ master.bench │         fix-mapkeys.bench         │
         │  allocs/op   │ allocs/op   vs base               │
Merge-10     64.00 ± 0%   26.00 ± 0%  -59.38% (p=0.002 n=6)
```
Sort:

```
cpu: Apple M1 Pro
           │ master.bench │         fix-mapkeys.bench         │
           │    sec/op    │   sec/op     vs base              │
SortMap-10   1008.0n ± 1%   915.5n ± 0%  -9.18% (p=0.002 n=6)

           │ master.bench │         fix-mapkeys.bench         │
           │     B/op     │    B/op     vs base               │
SortMap-10     640.0 ± 0%   512.0 ± 0%  -20.00% (p=0.002 n=6)

           │ master.bench │        fix-mapkeys.bench         │
           │  allocs/op   │ allocs/op   vs base              │
SortMap-10     16.00 ± 0%   15.00 ± 0%  -6.25% (p=0.002 n=6)
```
2025-01-13 16:24:48 +02:00
Bjørn Erik Pedersen
a2edf04c27 deps: Upgrade github.com/gohugoio/hashstructure from 0.1.0 to 0.3.0
Faster hashing of maps:

```
           │ stash.bench  │      deps-hashstructv030.bench      │
           │    sec/op    │    sec/op     vs base               │
HashMap-10   198.9µ ± ∞ ¹   142.6µ ± ∞ ¹  -28.31% (p=0.029 n=4)
¹ need >= 6 samples for confidence interval at level 0.95

           │  stash.bench  │      deps-hashstructv030.bench       │
           │     B/op      │     B/op       vs base               │
HashMap-10   92.38Ki ± ∞ ¹   37.14Ki ± ∞ ¹  -59.80% (p=0.029 n=4)
¹ need >= 6 samples for confidence interval at level 0.95

           │ stash.bench  │      deps-hashstructv030.bench      │
           │  allocs/op   │  allocs/op    vs base               │
HashMap-10   6.747k ± ∞ ¹   4.748k ± ∞ ¹  -29.63% (p=0.029 n=4)
```
2025-01-13 16:22:24 +02:00
Bjørn Erik Pedersen
956f915f77
tpl/collections: Add BenchmarkWhereMap 2025-01-13 12:57:48 +02:00
Bjørn Erik Pedersen
a2a4166f6d
tpl/collections: Add BenchmarkSortMap 2025-01-13 10:38:12 +02:00
Bjørn Erik Pedersen
39f582f9f4
tpl/collections: Add Merge benchmark 2025-01-13 10:32:41 +02:00
Bjørn Erik Pedersen
2501de7aab resources/images: Refactor golden image tests to locate them closer to the implementation 2025-01-11 18:27:53 +02:00
Bjørn Erik Pedersen
06cc8673fa resources/images: Add some mask tests
Closes #13244
2025-01-11 12:20:11 +02:00
Markus Kienast
71fae99189 resources/images: Add images.Mask
See #13244
2025-01-11 12:20:11 +02:00
Joe Mooring
8af04745fb tpl/tplimpl: Use plain text for image render hook alt attribute
Co-authored-by: Heracles <email@heracl.es>
2025-01-10 19:44:39 +02:00
Bjørn Erik Pedersen
dde9d9d544 Adjust error handling in ToMath vs try (note)
Closes #13239
2025-01-09 11:47:19 +01:00
Bjørn Erik Pedersen
892b49110e resources/images: Add some golden tests for images.QR 2025-01-09 08:02:43 +01:00
Bjørn Erik Pedersen
c5a63a3b4f Fix branch resource overlapping bundle path
Fixes #13228
2025-01-09 08:00:30 +01:00
Bjørn Erik Pedersen
61d3d20129 templates: Fix handling of multiple defers in the same template
Fixes #13236
2025-01-09 07:59:25 +01:00
Joe Mooring
b13d0a68e6 tpl/images: Change signature of images.QR to images.QR TEXT OPTIONS
Closes #13230
2025-01-08 19:59:39 +01:00
Bjørn Erik Pedersen
d9594a96fc resources/images: Add some golden tests for images.Text 2025-01-08 18:56:26 +01:00
Julius Künzel
4a5cc2dd26 images.Text: Add "alignx" option for horizontal alignment
Add an "alignx" option to the images.Text to control whether the value of the "x" option is the left border of the text (current behaviour), the center of each line or the right border.

Fixes #10849
2025-01-08 17:23:46 +01:00
Bjørn Erik Pedersen
9cad8d38ca images: Rework the golden tests 2025-01-08 17:11:27 +01:00
Bjørn Erik Pedersen
cfa0801815 Fix NPX issue with TailwindCSS v4
This allows the `tailwindcss` CLI binary to live in the `PATH` for NPM-less projects.

Fixes #13221
2025-01-07 13:12:08 +01:00
dependabot[bot]
f024a5050e build(deps): bump golang.org/x/tools from 0.28.0 to 0.29.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.28.0 to 0.29.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.28.0...v0.29.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-01-07 10:46:27 +01:00
dependabot[bot]
80704bc9a1 build(deps): bump golang.org/x/net from 0.33.0 to 0.34.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.33.0 to 0.34.0.
- [Commits](https://github.com/golang/net/compare/v0.33.0...v0.34.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-01-07 10:46:04 +01:00
Joe Mooring
3682027a51
snap: Always package latest stable version of Go 2025-01-06 12:50:40 -08:00
Bjørn Erik Pedersen
44628616af
docs: Regen CLI docs 2025-01-06 18:12:20 +01:00
Bjørn Erik Pedersen
0fc86783ee
Merge commit 'e9fbadacc3' 2025-01-06 18:11:07 +01:00
Bjørn Erik Pedersen
e9fbadacc3 Squashed 'docs/' changes from 8390a4a3a..f0f4bcb24
f0f4bcb24 Update Batch.md
15a8b1de6 Update Batch.md
eb841ce66 Update theme
751097f24 Use CI_PAGES_URL in GitLab Pages workflow
8584a0581 Fix typos
b030a7149 Update configuration.md
f7ec2ee9c Revert "Update shortcode.md"
f8188f1c8 Update shortcode.md
083fc39c6 Update shortcode.md
d4bdfb96b Update Filter.md
ef00649db Document wrapperClass highlighting option
e227dc422 Hugo 0.140.2
b95d28444 Fix description of the the hugo server command
41ca381b0 Fix documentation of Fragments
73a6cd966 Update theme
2573fd120 netlify: Hugo 0.140.1
90b19ec0d Merge branch 'tempv0.140.1'
ad2e9aeec Update gocloud and docs for S3-Compatible Endpoints
34b30bad2 Remove indentation from include shortcode examples
97b4ef544 Update related.md
7234e0189 Update related.md
32cc191f6 Clarify Name and Title menu entry methods
bce733c63 Update theme
2f1843d10 Use .Page.GetPage in examples of an include shortcode
9e1c85bbd Revert "Document PAGE.String"
bb30e7c08 Document PAGE.String
2d151d77f Fix link
fbe2d442f Miscellaneous updates for v0.140.0
4d3195223 Update JS docs vs Hugo v0.140
8c190aabc netlify: Hugo 0.140.0
826005ffe Merge branch 'tempv0.140.0'
107289a2c Update theme (again)
9884425f9 Update theme
ff0a358d3 Update Go tool version in hugo.work
c9b23cf6d tpl/tplimpl: Update details shortcode
f29f19aff tpl/tplimpl: Add details shortcode

git-subtree-dir: docs
git-subtree-split: f0f4bcb243776d0552396d12758bd38cda779b62
2025-01-06 18:09:20 +01:00
Bjørn Erik Pedersen
2a7bb1c7c9 create: Respect --noBuildLock in hugo new 2025-01-06 17:59:24 +01:00
dependabot[bot]
b7b49fb0f8 build(deps): bump github.com/evanw/esbuild from 0.24.0 to 0.24.2
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.24.0 to 0.24.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.24.0...v0.24.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-06 15:07:05 +01:00
Bjørn Erik Pedersen
5f2adad2c7 tpl/images: Format the QR hashes as hex
Slightly shorter, and it looks more like a ... hash.
2025-01-06 15:05:03 +01:00
dependabot[bot]
a837976731 build(deps): bump github.com/alecthomas/chroma/v2 from 2.14.0 to 2.15.0
Bumps [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) from 2.14.0 to 2.15.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.14.0...v2.15.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-06 13:38:55 +01:00
Bjørn Erik Pedersen
0918e087ec resources: Replace error handling in GetRemote with try (note)
Closes #13216
2025-01-06 12:22:17 +01:00
Joe Mooring
4ea94c451d tpl/images: Add images.QR function
Closes #13205
2025-01-06 12:05:59 +01:00
Bjørn Erik Pedersen
5d2cbee989 Add try
Updates #9737
2025-01-05 15:32:21 +01:00
Bjørn Erik Pedersen
723e3f4342 resources: Add FromOpts for more effective resource creation
E.g. when the targetPath already contains a hash or if the resource content is expensive to create.
2025-01-05 12:43:37 +01:00
Bjørn Erik Pedersen
d913f46a8b Fix server refresh on 404 template changes
Fixes #13209
2025-01-02 11:21:25 +01:00
Joe Mooring
2db43f841c markup/highlight: Remove noHl option
Closes #9885
2024-12-31 15:46:46 +01:00
hugoreleaser
46ce1f191b releaser: Prepare repository for 0.141.0-DEV
[ci skip]
2024-12-30 15:16:00 +00:00
hugoreleaser
aae02ca612 releaser: Bump versions for release of 0.140.2
[ci skip]
2024-12-30 15:01:53 +00:00
Diwas Rimal
83cec785cf Print cli usage of hugo gen chromastyles alongside css 2024-12-30 15:06:35 +01:00
dependabot[bot]
4e52be8b90 build(deps): bump golang.org/x/net from 0.32.0 to 0.33.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.32.0 to 0.33.0.
- [Commits](https://github.com/golang/net/compare/v0.32.0...v0.33.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>
2024-12-30 10:57:04 +01:00
Joe Mooring
7888ac585c config/allconfig: Fix slice of language configs
Fixes #13201
2024-12-30 10:54:56 +01:00
Joe Mooring
eb1dbe0709 config/allconfig: Throw error when output format is not defined
Fixes #13199
2024-12-29 21:38:46 +01:00
Bjørn Erik Pedersen
77824d704c Fix same resource file published more than once
This may still happen, though, in low memory situations or very big sites, but I'm not sure it's worth spending time on fixing that. Writing the same file more than once isn't harmful, the negative effect is the false path warning.
 We may find a way to detect that situation if this becomes a real problem.

Fixes #13164
2024-12-27 18:55:24 +01:00
Bjørn Erik Pedersen
ec0caaec7c markup/highlight: Add wrapperClass option
The need comes from Tailwind's typography plugin. There's currently no way to turn that off outside of the markup, see https://github.com/tailwindlabs/tailwindcss-typography/pull/363
2024-12-25 19:31:47 +01:00
Bjørn Erik Pedersen
845b8885de
Update README.md 2024-12-23 18:57:19 +01:00
hugoreleaser
43385d6aeb releaser: Prepare repository for 0.141.0-DEV
[ci skip]
2024-12-23 16:40:37 +00:00
hugoreleaser
a9b0b95ef4 releaser: Bump versions for release of 0.140.1
[ci skip]
2024-12-23 16:26:35 +00:00
Chris Moultrie
e229f4b387 Update gocloud and docs for S3-Compatible Endpoints 2024-12-22 22:58:36 +01:00
Bjørn Erik Pedersen
020253904f js/esbuild: Don't try to resolve packages in /assets marked as external
Fixes #13183
2024-12-22 21:29:18 +01:00
Bjørn Erik Pedersen
4a5e94087b Fix union, complement, symdiff, and intersect for transient resources
Fixes #13181
2024-12-22 19:25:03 +01:00
Bjørn Erik Pedersen
48a7aee961
release: Add withdeploy deb extended archives
Also refactor and get the config up to date for new version of Hugoreleaser.

Closes #13166
2024-12-22 16:55:52 +01:00
Joe Mooring
6c583e3227 common/loggers: Write PrintTimerIfDelayed output to stdErr
Closes #13171
2024-12-19 19:52:13 +01:00
dependabot[bot]
5d64b492f4 build(deps): bump github.com/spf13/cast from 1.7.0 to 1.7.1
Bumps [github.com/spf13/cast](https://github.com/spf13/cast) from 1.7.0 to 1.7.1.
- [Release notes](https://github.com/spf13/cast/releases)
- [Commits](https://github.com/spf13/cast/compare/v1.7.0...v1.7.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-19 12:08:10 +01:00
Joe Mooring
b3f32949cb hugolib: Fix fallbacks for menu entry Name and Title
Closes #13161
2024-12-19 08:47:53 +01:00
hugoreleaser
55ecd3a90e releaser: Prepare repository for 0.141.0-DEV
[ci skip]
2024-12-17 14:37:51 +00:00
hugoreleaser
3f35721fb2 releaser: Bump versions for release of 0.140.0
[ci skip]
2024-12-17 14:20:55 +00:00
Bjørn Erik Pedersen
5c80cb0d20
js/esbuild: Add missing es2024 target 2024-12-17 14:35:13 +01:00
Bjørn Erik Pedersen
a5e5be234c Fix panic on server rebuilds when using both base templates and template.Defer
Fixes #12963
2024-12-16 18:03:04 +01:00
Bjørn Erik Pedersen
565c30eac9 js: Fix js.Batch for multihost setups
Note that this is an unreleased feature.

Fixes #13151
2024-12-16 11:52:18 +01:00
Joe Mooring
48dd6a918a parser/pageparser: Fix Org Mode summary delimiter assignment
Closes #13152
2024-12-16 10:30:27 +01:00
Bjørn Erik Pedersen
744b8566ec Fix a rebuild on resource rename case 2024-12-16 08:33:10 +01:00
Bjørn Erik Pedersen
7de5317aef js/esbuild: Add runners after scripts 2024-12-16 08:33:10 +01:00
Bjørn Erik Pedersen
4cbd4ef991 js/esbuild: Batch: Avoid nil Instances slice
Ranging over a nil slice in Go works great, but is a hassle onced passed to JS.
2024-12-16 08:33:10 +01:00
Joe Mooring
a32c889a7b
tpl/tplimpl: Fix title attribute in details shortcode 2024-12-13 10:50:17 -08:00
Joe Mooring
852d868549 tpl/tplimpl: Update youtube shortcode
Pass a map instead of a slice to the collections.Querify function.
2024-12-13 15:16:55 +01:00
Joe Mooring
1e34e5b26d tpl/tplimpl: Update details shortcode
- Remove localization of default summary value
- Add title attribute
- Reformat to be consistent with other embedded templates
- Simplify and improve integration test
- Update documentation
2024-12-13 14:21:13 +01:00
Joe Mooring
641d2616c7 tpl/collections: Allow querify to accept a map argument
Closes #13131
2024-12-13 13:30:55 +01:00
Bjørn Erik Pedersen
a834bb9f7e js/esbuild: Build groups in order of their ID
We already do this for scripts e.g. inside a group.

This makes sure that group A's entry points gets added before B's, which can be an important property, see https://github.com/evanw/esbuild/issues/399#issuecomment-1458680887
2024-12-13 13:29:35 +01:00
racehd
4f130f6e4f tpl/tplimpl: Add details shortcode
- Add new shortcode to render details HTML element.
- Implement integration tests to check: default state, custom summary, open state,  attribute sanitization, allowed attributes, and localization of default summary text.
- Update docs to include details shortcode.

Closes # 13090
2024-12-13 09:24:20 +01:00
Bjørn Erik Pedersen
9dfa112617
Write all logging (INFO, WARN, ERROR) to stderr
The old setup tried to log >= warning to stderr, the rest to stdout.

However, that logic was flawed, so warnings ended up in stdout, which makes `hugo list all` etc. hard to reason about from scripts.

This commit fixes this by making all logging (info, warn, error) log to stderr and let stdout be reserved for program output.

Fixes #13074
2024-12-13 09:23:09 +01:00
Bjørn Erik Pedersen
ec1933f79d js/esbuild: Add platform option
Closes #13136
2024-12-12 22:53:15 +01:00
Bjørn Erik Pedersen
75ad9cdaab Add config option disableDefaultLanguageRedirect
Fixes #13133
2024-12-12 21:47:03 +01:00
Bjørn Erik Pedersen
e293e7ca6d Add js.Batch
Fixes #12626
Closes #7499
Closes #9978
Closes #12879
Closes #13113
Fixes #13116
2024-12-12 21:43:17 +01:00
dependabot[bot]
157d86414d build(deps): bump golang.org/x/crypto from 0.30.0 to 0.31.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.30.0 to 0.31.0.
- [Commits](https://github.com/golang/crypto/compare/v0.30.0...v0.31.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-12 12:19:49 +01:00
Bjørn Erik Pedersen
6be253000f
Upgrade to Go 1.23.4
Closes #13130
2024-12-12 10:39:59 +01:00
dependabot[bot]
947e4e66b5 build(deps): bump golang.org/x/tools from 0.27.0 to 0.28.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.27.0 to 0.28.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.27.0...v0.28.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-12 09:25:40 +01:00
Bjørn Erik Pedersen
989b299b55
Remove some old and unused shell scripts 2024-12-11 13:15:09 +01:00
dependabot[bot]
5f897868c0 build(deps): bump github.com/hairyhenderson/go-codeowners
Bumps [github.com/hairyhenderson/go-codeowners](https://github.com/hairyhenderson/go-codeowners) from 0.6.1 to 0.7.0.
- [Release notes](https://github.com/hairyhenderson/go-codeowners/releases)
- [Changelog](https://github.com/hairyhenderson/go-codeowners/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hairyhenderson/go-codeowners/compare/v0.6.1...v0.7.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-11 10:10:14 +01:00
Bjørn Erik Pedersen
e477373487 Squashed 'docs/' changes from 227aab619..8390a4a3a
8390a4a3a Add Void Linux installation instructions
d6099aae8 Update link to PostCSS plugins
25dad4693 netlify: Hugo 0.139.4
2b1fa118c Fix typo
3ef1eb505 Update hosting-on-aws-amplify
c0f6d35d6 Fix typo
aa54d4301 Correct directory name
98aa26bdb Improve instructions for hosting with AWS Amplify
a07638a80 Add new-in badges
6ad018055 netlify: Hugo 0.139.3
1050835d6 Update title of hugo.Store page
ebbd2e851 Clarify the shortcode Ordinal method
b7716ed95 Revise code block render hook for Mermaid diagrams
f1da9b6ea netlify: Hugo 0.139.2
d8ac9f428 Downgrade the Go toolchain in go.mod to a slightly older Go version
254b3c4f2 netlify: Hugo 0.139.1
03e666038 Add hugo.Store, site.Store and Shortcode.Store
157e8983b Update Anchorize.md
59fa9f214 Document the PageRef menu entry method
bda544cce docs(transform.Unmarshal): match lang attribute to title language in examples
1985886bd Adjust front matter of shared Markdown snippets
da5bd70d1 Fix typo
431b65d6b Update theme
b63ef69f5 Update style guidance
d50ed3422 Remove old new-in badges
12bfb9933 Update docs.yaml
0b936cacd netlify: Hugo 0.139.0
ab7668b4d dartsass: Add silenceDeprecations option
154df9bfc Merge commit '838bd312b1'
efa80477c docs: Regen CLI docs
ad99e4a7a docs: Regenerate CLI docs

git-subtree-dir: docs
git-subtree-split: 8390a4a3ac36094f4cf47170af23b636608c420e
2024-12-11 09:53:33 +01:00
Bjørn Erik Pedersen
b47376586a
Merge commit 'e477373487' 2024-12-11 09:53:33 +01:00
Bjørn Erik Pedersen
5ab38de363
Fix Sass imports on the form index.{scss,sass}
Fixes #13123
2024-12-10 13:33:27 +01:00
dependabot[bot]
7b69218489 build(deps): bump golang.org/x/net from 0.31.0 to 0.32.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.31.0 to 0.32.0.
- [Commits](https://github.com/golang/net/compare/v0.31.0...v0.32.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>
2024-12-10 09:49:56 +01:00
Joe Mooring
34373407b7 markup/goldmark: Fix blockquote render hook text parsing
Fixes #12913
Fixes #13119
2024-12-10 09:43:33 +01:00
hugoreleaser
3afe91d4b1 releaser: Bump versions for release of 0.139.4
[ci skip]
2024-12-09 17:45:23 +00:00
Joe Mooring
54398f8d57
tpl/tplimpl: Escape Markdown attributes in render hooks and shortcodes 2024-12-09 17:58:57 +01:00
Bjørn Erik Pedersen
b8c15f245b deps: Upgrade github.com/bep/godartsass/v2 v2.3.1 => v2.3.2
See https://github.com/bep/godartsass/issues/31 for some context.
2024-12-06 14:37:51 +01:00
Oleksandr Redko
d0dc518844 common/maps: Simplify TestScratchSetInMap/DeleteInMap 2024-12-05 12:40:59 +01:00
Joe Mooring
b529859008 markup/tableofcontents: Cast Fragments.ToHTML args to int
Closes #13107
2024-12-04 21:37:06 +01:00
hugoreleaser
487bb96474 releaser: Prepare repository for 0.140.0-DEV
[ci skip]
2024-11-29 15:51:07 +00:00
hugoreleaser
2f6864387c releaser: Bump versions for release of 0.139.3
[ci skip]
2024-11-29 15:36:56 +00:00
Bjørn Erik Pedersen
c1dc35dd71 Fix server edits of resources included in shortcode/hooks
Fixes #13093
2024-11-29 14:22:39 +01:00
huajin tong
fc3d1cbadb
Fix some typos 2024-11-28 11:20:02 +01:00
dependabot[bot]
7e130e34f2 build(deps): bump github.com/bep/godartsass/v2 from 2.3.0 to 2.3.1
Bumps [github.com/bep/godartsass/v2](https://github.com/bep/godartsass) from 2.3.0 to 2.3.1.
- [Release notes](https://github.com/bep/godartsass/releases)
- [Commits](https://github.com/bep/godartsass/compare/v2.3.0...v2.3.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>
2024-11-28 11:06:11 +01:00
dependabot[bot]
88b7868fbd build(deps): bump github.com/tetratelabs/wazero from 1.8.1 to 1.8.2
Bumps [github.com/tetratelabs/wazero](https://github.com/tetratelabs/wazero) from 1.8.1 to 1.8.2.
- [Release notes](https://github.com/tetratelabs/wazero/releases)
- [Commits](https://github.com/tetratelabs/wazero/compare/v1.8.1...v1.8.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-27 23:50:07 +01:00
Bjørn Erik Pedersen
dea158c885
commands: Fix flaw in the livereload logic
I guess most commonly an issue with TailwindCSS editing in templates:

* Build changes both CSS and index.html => reload OK.
* Build changes both CSS and index.html and some other files => only CSS reloaded.

The above would fix itself with one more edit, but that's annoying.
2024-11-25 10:39:17 +01:00
hugoreleaser
467444ef64 releaser: Prepare repository for 0.140.0-DEV
[ci skip]
2024-11-23 15:46:37 +00:00
hugoreleaser
770f548b47 releaser: Bump versions for release of 0.139.2
[ci skip]
2024-11-23 15:33:51 +00:00
Bjørn Erik Pedersen
0ab81896d9
modules: Skip empty lines in modules.txt
Closes #13084
2024-11-23 16:29:32 +01:00
hugoreleaser
e00fdae456 releaser: Prepare repository for 0.140.0-DEV
[ci skip]
2024-11-23 14:26:42 +00:00
hugoreleaser
21299a7a67 releaser: Bump versions for release of 0.139.1
[ci skip]
2024-11-23 14:13:46 +00:00
Bjørn Erik Pedersen
aa3dd197f7 Revert "build(deps): bump github.com/tdewolff/minify/v2 from 2.20.37 to 2.21.1"
This reverts commit 7a2f04ee8c.

Closes #13082
2024-11-23 15:11:09 +01:00
Bjørn Erik Pedersen
5a50eee9da minifiers: Add failing test for upstream bug
See #13082
2024-11-23 15:11:09 +01:00
Bjørn Erik Pedersen
8d017a60fb dartsass: Fix nilpointer on Close when Dart Sass isn't installed
Fixes #13076
2024-11-21 20:00:36 +01:00
wangjingcun
8fcd3c1487
common: Fix some GoDoc typos 2024-11-19 11:29:19 +01:00
Bjørn Erik Pedersen
2d4c0c6c8d
readme: Update dependency list 2024-11-19 10:05:42 +01:00
hugoreleaser
59a55a1f9e releaser: Prepare repository for 0.140.0-DEV
[ci skip]
2024-11-18 16:32:01 +00:00
hugoreleaser
dacef0df92 releaser: Bump versions for release of 0.139.0
[ci skip]
2024-11-18 16:17:45 +00:00
Bjørn Erik Pedersen
8d92042ab9
dartsass: Fix error message
Closes #13065
2024-11-18 17:14:56 +01:00
Bjørn Erik Pedersen
e917401c71 Make sure term is always set
Fixes #13063
2024-11-18 16:20:25 +01:00
dependabot[bot]
eb298144b6 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.38.4 to 1.41.0.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.38.4...service/s3/v1.41.0)

---
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>
2024-11-18 13:41:31 +01:00
Bjørn Erik Pedersen
3b6eaf9b1f dartsass: Add silenceDeprecations option
Fixes #13045
2024-11-18 13:41:12 +01:00
Bjørn Erik Pedersen
1fd845eee4 dartsass: Remove support for v1 of the protocol/binary (note)
People who stil use a very old binary named `dart-sass-embedded` need to upgrade.

See https://gohugo.io/functions/css/sass/#dart-sass
2024-11-18 12:32:39 +01:00
dependabot[bot]
61e2ce2a49 build(deps): bump google.golang.org/api from 0.191.0 to 0.206.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.191.0 to 0.206.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.191.0...v0.206.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>
2024-11-18 10:51:33 +01:00
dependabot[bot]
2d503f82ac build(deps): bump github.com/aws/aws-sdk-go-v2 from 1.30.3 to 1.32.4
Bumps [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) from 1.30.3 to 1.32.4.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/v1.30.3...v1.32.4)

---
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>
2024-11-18 10:51:04 +01:00
dependabot[bot]
3a6b2e6b14 build(deps): bump github.com/fsnotify/fsnotify from 1.7.0 to 1.8.0
Bumps [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify) from 1.7.0 to 1.8.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.7.0...v1.8.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 10:50:22 +01:00
Bjørn Erik Pedersen
bbbfeb340a
Merge commit '838bd312b1' 2024-11-18 10:11:18 +01:00
Bjørn Erik Pedersen
838bd312b1 Squashed 'docs/' changes from 159c843fd..227aab619
227aab619 Update configuration-markup.md
019ff776c Fix grammatical error
c889827bf Remove old new-in
39807c5bc Remove duplicate yaml key

git-subtree-dir: docs
git-subtree-split: 227aab61905c0f778d173ed9e38621df4d7c429a
2024-11-18 10:11:17 +01:00
Bjørn Erik Pedersen
6bde8abaad deps: Upgrade github.com/bep/imagemeta v0.8.1 => v0.8.3 2024-11-17 12:05:37 +01:00
Bjørn Erik Pedersen
66dd6ecab2
docs: Regen CLI docs 2024-11-16 20:34:00 +01:00
Bjørn Erik Pedersen
ad43d137d5 Remove deprecations <= v0.122.0 (note)
These have, once we release this, been logging ERROR for 6 minor versions.
2024-11-16 20:32:43 +01:00
Bjørn Erik Pedersen
f7fc6ccd59
release: Add missing withdeploy archive for arm64 Linux
Closes #13029
2024-11-16 18:29:13 +01:00
Bjørn Erik Pedersen
d4de780edc Fix extra newline/paragraphs issue with .RenderShortcodes
Fixes #13051
2024-11-16 17:51:11 +01:00
dependabot[bot]
2c54c32986 build(deps): bump golang.org/x/tools from 0.26.0 to 0.27.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.26.0 to 0.27.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.26.0...v0.27.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-15 11:46:07 +01:00
dependabot[bot]
7a2f04ee8c build(deps): bump github.com/tdewolff/minify/v2 from 2.20.37 to 2.21.1
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.20.37 to 2.21.1.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.20.37...v2.21.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-15 10:04:07 +01:00
dependabot[bot]
7921777da1 build(deps): bump github.com/fatih/color from 1.17.0 to 1.18.0
Bumps [github.com/fatih/color](https://github.com/fatih/color) from 1.17.0 to 1.18.0.
- [Release notes](https://github.com/fatih/color/releases)
- [Commits](https://github.com/fatih/color/compare/v1.17.0...v1.18.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-15 10:02:13 +01:00
dependabot[bot]
33e964d40b build(deps): bump golang.org/x/image from 0.21.0 to 0.22.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.21.0 to 0.22.0.
- [Commits](https://github.com/golang/image/compare/v0.21.0...v0.22.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>
2024-11-15 10:01:30 +01:00
Bjørn Erik Pedersen
5e3133a7d8
Run go mod tidy 2024-11-14 16:45:21 +01:00
Bjørn Erik Pedersen
58a3c91a7f
docs: Regenerate CLI docs 2024-11-14 16:10:38 +01:00
Bjørn Erik Pedersen
ac6962d284 commands: Add -O flag to server to open browser
Fixes #13040
2024-11-14 15:59:23 +01:00
KN4CK3R
23d21b0d16 Preserve input type. 2024-11-14 15:05:37 +01:00
Bjørn Erik Pedersen
588c9019cf deps: Upgrade github.com/yuin/goldmark v1.7.4 => v1.7.8
Closes #12958
2024-11-14 14:31:04 +01:00
Bjørn Erik Pedersen
ce9cf882a5 server: Strip ANSI escape codes from browser error log
Fixes #13037
2024-11-14 14:25:07 +01:00
dependabot[bot]
46e17053c8 build(deps): bump golang.org/x/sync from 0.8.0 to 0.9.0
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.8.0 to 0.9.0.
- [Commits](https://github.com/golang/sync/compare/v0.8.0...v0.9.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-14 14:02:35 +01:00
dependabot[bot]
cb6580d008 build(deps): bump github.com/hairyhenderson/go-codeowners
Bumps [github.com/hairyhenderson/go-codeowners](https://github.com/hairyhenderson/go-codeowners) from 0.6.0 to 0.6.1.
- [Release notes](https://github.com/hairyhenderson/go-codeowners/releases)
- [Changelog](https://github.com/hairyhenderson/go-codeowners/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hairyhenderson/go-codeowners/compare/v0.6.0...v0.6.1)

---
updated-dependencies:
- dependency-name: github.com/hairyhenderson/go-codeowners
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-14 14:01:41 +01:00
Bjørn Erik Pedersen
8aba6dc661
parser/metadecoders: Add benchmark 2024-11-13 14:57:18 +01:00
Bjørn Erik Pedersen
a7df536a52 Add site.Store and hugo.Store and Shortcode.Store
Closes #13021
2024-11-13 12:41:16 +01:00
Bjørn Erik Pedersen
de0df119b5 Squashed 'docs/' changes from ccb1b97cb..159c843fd
159c843fd Fix front matter menu entry example
c3a476a19 Document soft deprecation of PAGE.Scratch
cdead9785 netlify: Hugo 0.138.0
9169b4da4 Update version references
3bc6bf431 Update embedded.md
5c7743b2e Update creation instructions for the emoji quick reference
109efe3eb Document the comment shortcode
83d7d3005 Update theme
d3c205054 netlify: Hugo 0.137.1
545290351 Handle inline HTML content
0204be97d Update theme
18d09235e Remove JS and CSS that prevents FOUC with client side math rendering
63d9dd876 Update RenderShortcodes.md
064b95539 Update output-format-definition.md
3744f3be2 Describe and refer to the extended/deploy edition
3d3302308 netlify: Hugo 0.137.0
b53aedcea Update RenderShortcodes.md
b5f289165 Replace HTML comments in markdown with the new comment shortcode
c673880b6 Remove superfluous right bracket
f80b0c61e Update faq.md
2ede707eb Document installation on NixOS
09b114914 Update theme
76a9f90b2 Update passthrough.md
9f3355630 Update Scratch.md
bc080ecaa Update Store.md
1507ede32 Update Scratch.md
54a90f569 Update Store.md
7c9145c43 Fix broken link
dd15f183b Update ToMath.md
2b021c34b Move the [build] documentation to its own page
cbb6677ec Fix typo
ac0969063 Update ToMath.md
7fbdfd7c8 netlify: Hugo 0.136.5
17f54223c Update ToMath.md
4c9c3bb06 Update multilingual.md
1432da7bd Make site and page language methods linkable
fd5b746cb Update urls.md
a746f1b3a Update urls.md
abf8738e2 netlify: Hugo 0.136.4
bd8759996 Update TrimSpace.md
6103c1e84 Documents strings.TrimSpace
533dd3a7b netlify: Hugo 0.136.3
30f3f97cf Update quick-start.md
b0d7b41a0 Update configuration-markup.md
760e5e4f0 Update quick-start.md
17daeb866 Update quick-start.md
1e158e723 netlify: Hugo 0.136.2
d32530839 Update theme
edb9bee02 Update description of url front matter field
e1c576e18 netlify: Hugo 0.136.1
1ad28e1e0 Describe how to configure uglyURLs per section
cbbd4c4fe netlify: Hugo 0.136.0
bb7f35e99 Merge branch 'tempv0.136.0'
706110736 docs: Regen CLI docs
bf0c7821f Update urls.md
8c544e6c0 Update quick-start.md
8d02733d0 Update Paginator.md
a45327aac Update Paginate.md
1377ed4de Clarify date parsing
e19fb8043 Document front matter date field aliases
a39951847 Update Tailwind CSS installation instructions
3be164c35 Remove duplicate token
05fc815f7 commands: Add "hugo build" as an alias for "hugo"
cb3cb504c Update table render hook example
efbee0bff Clarify resources.GetRemote 404 handling
4312d49c9 Update compare.Conditional documentation
4a46d53b6 Update theme
93e542d4f netlify: Hugo 0.135.0
b4da1c104 Remvoe some old new-in shortcodes
4c316f051 Update TailwindCSS.md
c2fe91509 Update introduction.md
906b7c66b Update configuration.md
5ab6b3cdd Update documentation.md
26fb4bb4c Update documentation.md
e9e917f37 Update version refs
83ce07f24 netlify: Hugo 0.134.3
8cb32f802 Update front-matter.md
94d7f576a Update faq.md
fafc1d8d9 netlify: Hugo 0.134.2
bfe9cdc3d Update content-adapters.md
9e49ae3e1 Document ignoreLogs configuration setting
6b47a1d57 Update configuration.md
fd98a0372 Document CLI options that can be set in configuration
07c2400d8 Document alternative to Summary method
d053fa163 Update to reflect changes in v0.134.1
137dc3241 Update ContentWithoutSummary.md
e8f6427d9 netlify: Hugo 0.134.1

git-subtree-dir: docs
git-subtree-split: 159c843fd79e94a0f49bee74c272cd0cc4a848a2
2024-11-13 11:07:57 +01:00
Bjørn Erik Pedersen
3477d9fcec
Merge commit 'de0df119b5' 2024-11-13 11:07:57 +01:00
Andreas Deininger
e79ee0d516
markup/goldmark: Fix typo in error message 2024-11-13 10:12:26 +01:00
Joe Mooring
2b97a2a8bf markup/goldmark: Improve the raw HTML omitted warning
Closes #13023
2024-11-07 23:00:09 +02:00
hugoreleaser
35afe6fe2a releaser: Prepare repository for 0.139.0-DEV
[ci skip]
2024-11-06 11:36:12 +00:00
hugoreleaser
ad82998d54 releaser: Bump versions for release of 0.138.0
[ci skip]
2024-11-06 11:22:34 +00:00
Bjørn Erik Pedersen
95e2d5beb8 Fix concurrent map read and map write in short page lookups
Regression introduced in Hugo `v0.137.0`.

Fixes #13019
2024-11-06 12:17:30 +01:00
Bjørn Erik Pedersen
2c3efc8106 Alias Page.Scratch to Page.Store (note)
Fixes #13016
2024-11-06 09:49:23 +01:00
hugoreleaser
df8bd4af4f releaser: Prepare repository for 0.138.0-DEV
[ci skip]
2024-11-05 12:04:27 +00:00
hugoreleaser
17e15b2148 releaser: Bump versions for release of 0.137.1
[ci skip]
2024-11-05 11:49:09 +00:00
Bjørn Erik Pedersen
ca4fc587c3 common/hugo: Add withdeploy to the version string printed in hugo version 2024-11-05 11:40:42 +01:00
Bjørn Erik Pedersen
4faaaf9c2f markup: Goldmark log "Raw HTML omitted" warning also for inline HTML
Fixes #13013
2024-11-05 11:40:42 +01:00
Bjørn Erik Pedersen
94b46c36b4 build: Add missing withdeploy archive for Windows
Fixes #13009
2024-11-05 11:40:42 +01:00
Bjørn Erik Pedersen
2b0b3b8584 commands: Print the "deploy not available" error message even if flags provided
Fixes #13012
2024-11-05 11:40:42 +01:00
Joe Mooring
801035bb7a tpl/tplimpl: Create an embedded comment shortcode
Closes #13010
2024-11-04 21:05:07 +01:00
hugoreleaser
b7861e586e releaser: Prepare repository for 0.138.0-DEV
[ci skip]
2024-11-04 16:19:39 +00:00
hugoreleaser
59c1158135 releaser: Bump versions for release of 0.137.0
[ci skip]
2024-11-04 16:04:06 +00:00
Bjørn Erik Pedersen
fcdc454cc5 Do not watch directories with no mounted files in it
Fixes #12912
Fixes #13007
2024-11-04 12:44:07 +01:00
Bjørn Erik Pedersen
6cf23bf882 markup/goldmark: Only log Raw HTML omitted WARN on block entering
Updates #12997
2024-11-04 10:13:53 +01:00
Bjørn Erik Pedersen
62a96cef7f markup/goldmark: Add warning (using Warnidf) on Goldmark <!-- raw HTML omitted -->
Fixes #12997
2024-11-03 14:17:10 +01:00
Bjørn Erik Pedersen
30d9aea860 Fix stale pages on rebuilds in GetPage with short refs
Fixes #13004
2024-11-03 13:33:31 +01:00
Bjørn Erik Pedersen
1f23b4949c Fix some RenderShortcodes error cases
This issue fixes two cases where `{{__hugo_ctx` artifacts were left in the rendered output:

1. Inclusion when `.RenderShortcodes` is wrapped in HTML.
2. Inclusion of Markdown file without a trailing newline in some cases.

Closes #12854
Updates #12998
2024-11-03 13:33:31 +01:00
Joe Mooring
5fc1639035
Update README.md 2024-11-01 21:10:52 -07:00
Joe Mooring
889308dd85 resources: Address Dart Sass deprecation of global built-in functions
See https://github.com/sass/dart-sass/releases/tag/1.80.0

Fixes #12961
2024-11-01 14:05:27 +01:00
Joe Mooring
72352f205a github: Adjust test workflow to install Dart Sass v1.80.3 2024-11-01 13:37:45 +01:00
Bjørn Erik Pedersen
e3304db617 deps: Upgrade github.com/bep/godartsass/v2 v2.1.0 => v2.2.0 2024-11-01 12:37:55 +01:00
Bjørn Erik Pedersen
1ec6fa36c0
Update README.md 2024-11-01 10:16:05 +01:00
Bjørn Erik Pedersen
89bd025ebf Build without the deploy feature by default
Build tags setup changed to:

* !nodeploy => withdeploy
* nodeploy => !withdeploy

Also move the deploy feature out into its own release archives.

See #12994 for the primary motivation for this change. But this also greatly reduces the number of dependencies in Hugo when you don't need this feature and cuts the binary size greatly.

Fixes #12994
2024-10-30 11:46:40 +01:00
Bjørn Erik Pedersen
62567d3820 deps: Upgrade github.com/bep/lazycache v0.6.0 => v0.7.0 2024-10-27 12:43:36 +01:00
Bjørn Erik Pedersen
e10915f80a dynacache: Fix potential deadlocks on panics in GetOrCreate 2024-10-26 18:27:10 +02:00
hugoreleaser
ec3890affe releaser: Prepare repository for 0.137.0-DEV
[ci skip]
2024-10-24 12:41:02 +00:00
hugoreleaser
46cccb021b releaser: Bump versions for release of 0.136.5
[ci skip]
2024-10-24 12:26:27 +00:00
Bjørn Erik Pedersen
cb6e27b32a hugolib/commands: Fix stuck server error issues
Fixes #11378
2024-10-24 13:34:11 +02:00
Joe Mooring
5bbe95f9c5 tpl/transform: Revert unmarshal whitespace removal
Fixes #12977
2024-10-22 09:42:40 +02:00
hugoreleaser
31d19b505d releaser: Prepare repository for 0.137.0-DEV
[ci skip]
2024-10-21 13:46:41 +00:00
hugoreleaser
bfa2fd683e releaser: Bump versions for release of 0.136.3
[ci skip]
2024-10-21 13:31:54 +00:00
David Karlsson
88d598a049
docker: Fix permission issues in Dockerfile
Closes #12971
Closes #12970
2024-10-21 15:28:36 +02:00
Bjørn Erik Pedersen
352be5ba87 Make sure that HugoSites is always closed when done
Including all the integration tests.
2024-10-20 13:04:58 +02:00
Joe Mooring
d37606d2c2 tpl/strings: Add TrimSpace function
Closes #12962
2024-10-19 21:10:00 +02:00
Bjørn Erik Pedersen
f5e54d9c7d
common/herrors: Fix the deferred error message cleaner regexp
Make it less gready.
2024-10-19 10:00:01 +02:00
Bjørn Erik Pedersen
42f37b4e98
tpl/transform: Don't fail on "no data to transform"
Fixes #12964
2024-10-18 10:30:36 +02:00
hugoreleaser
e971b7d866 releaser: Prepare repository for 0.137.0-DEV
[ci skip]
2024-10-17 14:44:10 +00:00
hugoreleaser
ad985550a4 releaser: Bump versions for release of 0.136.2
[ci skip]
2024-10-17 14:30:05 +00:00
David Karlsson
b5852d0e68
docker: Fix Dart Sass ARM64 arch mismatch, /cache permissions
Also improve the final build step.

Closes #12956
Closes #12957
Closes #12960
2024-10-17 15:42:41 +02:00
hugoreleaser
b5801d8b6a releaser: Prepare repository for 0.137.0-DEV
[ci skip]
2024-10-16 16:18:44 +00:00
hugoreleaser
64d1865c1e releaser: Bump versions for release of 0.136.1
[ci skip]
2024-10-16 16:05:18 +00:00
Bjørn Erik Pedersen
e4ad0c5271 Never sanitize when url set in front matter
Fixes #12954
2024-10-16 18:01:11 +02:00
Bjørn Erik Pedersen
a2f666b586 Remove erroneously permalink validation
Fixes #12948
2024-10-16 10:14:48 +02:00
Joe Mooring
b1b3bbcdbd create/skeletons: Add delimiters to archetype front matter
Fixes #12945
2024-10-15 21:37:26 +02:00
hugoreleaser
1cfe9741b9 releaser: Prepare repository for 0.137.0-DEV
[ci skip]
2024-10-15 13:31:16 +00:00
hugoreleaser
2939270a3b releaser: Bump versions for release of 0.136.0
[ci skip]
2024-10-15 13:17:24 +00:00
Bjørn Erik Pedersen
5db27b19c1
circleci: Use default docker image 2024-10-15 15:15:12 +02:00
Bjørn Erik Pedersen
b7d62d76c5
docs: Regen CLI docs 2024-10-15 10:30:35 +02:00
Bjørn Erik Pedersen
6e1c5b61b3 resources/page: Adjust the permalinks colon implementation a little
Mostly to get back to an attribute regexp that's reasonably simle to read/understand.

Updates #12918
2024-10-15 10:28:00 +02:00
n1xx1
e7d0757f95 resources/page: Allow colons in permalinks to be escaped
Updates #12918
2024-10-15 10:28:00 +02:00
Rohan Hasabe
6b5e117a12 commands: Use consistent style when describing subcommands
Closes #12897

Change-Id: Ib27a4a7b540d45243e6252db769d4b9fb7447718
Signed-off-by: Rohan Hasabe <rohanhasabe8@gmail.com>
2024-10-15 08:51:53 +02:00
dependabot[bot]
4985be1a4a build(deps): bump github.com/tetratelabs/wazero from 1.8.0 to 1.8.1
Bumps [github.com/tetratelabs/wazero](https://github.com/tetratelabs/wazero) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/tetratelabs/wazero/releases)
- [Commits](https://github.com/tetratelabs/wazero/compare/v1.8.0...v1.8.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-14 15:54:41 +02:00
dependabot[bot]
db653a8485 build(deps): bump golang.org/x/image from 0.20.0 to 0.21.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.20.0 to 0.21.0.
- [Commits](https://github.com/golang/image/compare/v0.20.0...v0.21.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>
2024-10-14 14:53:06 +02:00
dependabot[bot]
05bbd2c515 build(deps): bump github.com/yuin/goldmark-emoji from 1.0.3 to 1.0.4
Bumps [github.com/yuin/goldmark-emoji](https://github.com/yuin/goldmark-emoji) from 1.0.3 to 1.0.4.
- [Release notes](https://github.com/yuin/goldmark-emoji/releases)
- [Commits](https://github.com/yuin/goldmark-emoji/compare/v1.0.3...v1.0.4)

---
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>
2024-10-14 14:52:12 +02:00
Bjørn Erik Pedersen
21366e0411 config: Imrove uglyurls section test
Updates #12926
2024-10-14 10:03:44 +02:00
Akkuman
ea633dd809 config: Fix uglyurls map parse
Fixes #12926
2024-10-14 10:03:44 +02:00
Joe Mooring
57151a5e91 create/skeletons: Honor --format flag when creating default archetype
Closes #12666
2024-10-14 09:54:29 +02:00
Julia Evans
5bb22b6b4e hugolib: Make .Site.Author deprecation warning clearer
Fixes #12269
2024-10-14 09:16:11 +02:00
Bjørn Erik Pedersen
41f69a7255 Upgrade to latest Go version + Some Docker image improvements (note)
* Rename /site to /project
* Add ldflags
* Add go and node to the default image
* Add Dart Sass to the default image
* Build the extended version by default
* Add "npm i" install support with custom entry script override
* Adjust cache logic to speed up CGO rebuilds

Closes #12920
See #12885
2024-10-13 20:01:37 +02:00
David Karlsson
4a79956276 ci: Build multi-platform image with cross-compilation
The previous build workflow used emulation to build the Docker image,
which results in a somewhat complicated push-by-digest and merge
workflow to create a multi-platform image.

This commit changes the Docker build to use cross-compilation instead,
resulting in a faster and more straightforward build.

Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
2024-10-13 20:01:37 +02:00
Patrice Chalin
039845804f
tpl/tplimpl: Trim descriptions rather than just chomp 2024-10-12 22:41:42 +02:00
dependabot[bot]
9b635522e2 build(deps): bump golang.org/x/tools from 0.23.0 to 0.26.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.23.0 to 0.26.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.23.0...v0.26.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-11 15:41:03 +02:00
dependabot[bot]
3223a65c9b build(deps): bump github.com/rogpeppe/go-internal from 1.12.0 to 1.13.1
Bumps [github.com/rogpeppe/go-internal](https://github.com/rogpeppe/go-internal) from 1.12.0 to 1.13.1.
- [Release notes](https://github.com/rogpeppe/go-internal/releases)
- [Commits](https://github.com/rogpeppe/go-internal/compare/v1.12.0...v1.13.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-11 15:39:22 +02:00
dependabot[bot]
bc6fc47727 build(deps): bump github.com/evanw/esbuild from 0.23.1 to 0.24.0
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.23.1 to 0.24.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.23.1...v0.24.0)

---
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>
2024-10-11 15:36:02 +02:00
dependabot[bot]
ffb41d1111 build(deps): bump github.com/hairyhenderson/go-codeowners
Bumps [github.com/hairyhenderson/go-codeowners](https://github.com/hairyhenderson/go-codeowners) from 0.5.0 to 0.6.0.
- [Release notes](https://github.com/hairyhenderson/go-codeowners/releases)
- [Changelog](https://github.com/hairyhenderson/go-codeowners/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hairyhenderson/go-codeowners/compare/v0.5.0...v0.6.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-09 10:34:59 +02:00
Joe Mooring
5b0b663ec3 tailwind: Pin Tailwind 4 test to alpha 26 or later 2024-10-06 22:15:17 +02:00
Joe Mooring
3f68309148 resources/page: Treat null dates as zero dates
Closes #12906
2024-10-06 20:35:25 +02:00
Joe Mooring
ab03588db9 resources/page: Improve front matter date validation
Improve the error message and treat empty strings as zero dates.

Closes #12898
2024-10-05 15:29:57 +02:00
Bjørn Erik Pedersen
0450d69fc6 commands: Add "hugo build" as an alias for "hugo"
Closes #11391
2024-09-30 10:36:29 +02:00
Joe Mooring
1158e63072 markup/goldmark: Change default cell alignment in table render hook
Change the default table cell alignment from "left" to an empty string.

Closes #12886
2024-09-30 08:44:14 +02:00
Joe Mooring
d1ba52f3c3 tests: Address deprecation warnings and errors 2024-09-29 10:49:26 +02:00
hugoreleaser
d0dca65625 releaser: Prepare repository for 0.136.0-DEV
[ci skip]
2024-09-27 13:32:22 +00:00
hugoreleaser
f30603c47f releaser: Bump versions for release of 0.135.0
[ci skip]
2024-09-27 13:17:08 +00:00
dependabot[bot]
12c9ce34bb build(deps): bump golang.org/x/mod from 0.19.0 to 0.21.0
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.19.0 to 0.21.0.
- [Commits](https://github.com/golang/mod/compare/v0.19.0...v0.21.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-27 14:03:14 +02:00
dependabot[bot]
47b055589c build(deps): bump github.com/bep/helpers from 0.4.0 to 0.5.0
Bumps [github.com/bep/helpers](https://github.com/bep/helpers) from 0.4.0 to 0.5.0.
- [Release notes](https://github.com/bep/helpers/releases)
- [Commits](https://github.com/bep/helpers/compare/v0.4.0...v0.5.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-27 12:24:02 +02:00
dependabot[bot]
e56ea40666 build(deps): bump golang.org/x/net from 0.28.0 to 0.29.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.28.0 to 0.29.0.
- [Commits](https://github.com/golang/net/compare/v0.28.0...v0.29.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>
2024-09-27 12:22:52 +02:00
Joe Mooring
4c02a52f7c resources/page: Validate predefined front matter dates
Closes #10717
2024-09-27 11:19:27 +02:00
dependabot[bot]
578442f892 build(deps): bump golang.org/x/image from 0.19.0 to 0.20.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.19.0 to 0.20.0.
- [Commits](https://github.com/golang/image/compare/v0.19.0...v0.20.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>
2024-09-27 11:07:23 +02:00
Bjørn Erik Pedersen
0d390d7eb8
github: Trigger image workflow on release.published
Updates #12753
2024-09-27 10:59:45 +02:00
Bjørn Erik Pedersen
da72ac2db9
tailwind: Pin Tailwind 4 test to alpha 24
See #12880
2024-09-27 10:36:25 +02:00
Joe Mooring
0ea796dad1 tpl/compare: Use any data type for compare.Conditional condition
Improves #5792
2024-09-22 16:53:11 +02:00
Khayyam Saleem
1e690c0f23 deps: Upgrade github.com/gobuffalo/flect v1.0.2 => v1.0.3
Closes #12827
2024-09-21 19:10:34 +02:00
hugoreleaser
a619deec18 releaser: Prepare repository for 0.135.0-DEV
[ci skip]
2024-09-19 14:42:46 +00:00
hugoreleaser
5fb333b9f5 releaser: Bump versions for release of 0.134.3
[ci skip]
2024-09-19 14:28:20 +00:00
Bjørn Erik Pedersen
e07028cb90 tpl: Remove RSS deprecation site.Author check
The check itself creates a warning which I guess was not intended.

We could possibly make that work, but it has been deprecated since Hugo 0.98, so just remove the usage.
2024-09-19 13:39:40 +02:00
Bjørn Erik Pedersen
22a9f3fc98 modules: Improve console output on hugo mod init
Replace some of the stderr output from the Go command to match the Hugo commands needed:

```
go: creating new go.mod: module github.com/bep/foo
hugo: to add module requirements and sums:
hugo mod tidy
```

See #11458
2024-09-19 12:26:04 +02:00
Bjørn Erik Pedersen
e363964f2f commands: Ignore "module does not exist" errors in hugo mod init
Closes #11458
2024-09-19 12:26:04 +02:00
David Else
c260cb28a9
Add exclusion for helix .bck files
Updates #12677
2024-09-18 10:53:18 +02:00
Bjørn Erik Pedersen
e079145373
hugolib: Move hugolib/site_new.go into hugolib/site.go 2024-09-17 10:15:14 +02:00
Joe Mooring
5b442b3cce libsass: Resolve directory paths to directory index files
Closes #12851
2024-09-16 09:34:14 +02:00
Joe Mooring
2bc27657d8 dartsass: Resolve directory paths to directory index files
Closes #12849
2024-09-15 20:32:11 +02:00
Bjørn Erik Pedersen
28f621d4a7 internal/warpc: Improve the JS plugin API
* Move the error handling into commons and make sure the error returned also returns message errors
* Make the protocol version an int so it can be more easily compared
2024-09-12 13:51:37 +02:00
hugoreleaser
fe7e137e28 releaser: Prepare repository for 0.135.0-DEV
[ci skip]
2024-09-10 11:00:15 +00:00
hugoreleaser
1c74abd260 releaser: Bump versions for release of 0.134.2
[ci skip]
2024-09-10 10:46:33 +00:00
Bjørn Erik Pedersen
3d6baedaec Don't count HTML markup in auto summaries
This commit also fixes a bug where a `</picture>` end tag was wrongly used to detect a end paragraph. This should be very rare, though.

Closes #12837
2024-09-10 11:03:47 +02:00
hugoreleaser
84ee00bbc2 releaser: Prepare repository for 0.135.0-DEV
[ci skip]
2024-09-05 10:32:40 +00:00
hugoreleaser
2f89169baa releaser: Bump versions for release of 0.134.1
[ci skip]
2024-09-05 10:17:50 +00:00
Bjørn Erik Pedersen
66a3a11001 Fix stray end p tag in Obsidian callout titles
Also remove test references to deprecated funcs now in ERROR.

Closes #12828
2024-09-05 12:12:24 +02:00
Bjørn Erik Pedersen
8f2eac0195 Make ContentWithoutSummary return Content when summary is fetched from front matter
Fixes #12822
2024-09-05 12:12:24 +02:00
Bjørn Erik Pedersen
b570a5f118
Merge commit '39fd3b5570' 2024-09-04 18:57:34 +02:00
Bjørn Erik Pedersen
39fd3b5570 Squashed 'docs/' changes from a49697e53..ccb1b97cb
ccb1b97cb Update blockquotes.md
a5f51431c Add new-in label
39dac5838 Document ContentWithoutSummary
de3c75694 Clarify blockquote render hook Markdown syntax
d32f7856d Document change to data type returned by render hook Text methods
83fe7ccc3 Document table render hooks
601234147 netlify: Hugo 0.134.0
a583640a0 Add support for Obsidian type blockquote alerts
3e0080861 Merge commit 'dec8cd4ada'
2dde06576 output: Fix docshelper template lookup order for AMP pages

git-subtree-dir: docs
git-subtree-split: ccb1b97cbb11e60aab0108b33a270cccdd2218f6
2024-09-04 18:52:05 +02:00
hugoreleaser
aacff7aff4 releaser: Prepare repository for 0.135.0-DEV
[ci skip]
2024-09-03 10:14:44 +00:00
hugoreleaser
77df7bbbff releaser: Bump versions for release of 0.134.0
[ci skip]
2024-09-03 09:54:22 +00:00
Aymen Rachdi
ae3815898f Update Dockerfile 2024-09-02 21:56:33 +02:00
Bjørn Erik Pedersen
504a23184f
markup/goldmark/blockquotes: Improve some tests 2024-09-01 18:25:10 +02:00
Bjørn Erik Pedersen
e651d29801 Add support for Obsidian type blockquote alerts
* Make the alert type parsing more flexible to support more types
* Add `AlertTitle` and `AlertSign` (for folding)

Note that GitHub will not render callouts with alert title/sign.

See https://help.obsidian.md/Editing+and+formatting/Callouts

Closes #12805
Closes #12801
2024-09-01 18:12:26 +02:00
Bjørn Erik Pedersen
dec8cd4ada Squashed 'docs/' changes from fcc3ed651..a49697e53
a49697e53 Add private use subtag requirements to multilingual page
a5c6bb4da Add INFINI Pizza search engine
45b732efa Fix template lookup order for AMP pages
30c672d0b netlify: Hugo 0.133.1
7c766e724 Update page resources documentation
ca802fbec Document how to enable AsciiDoc syntax highlighting
c3350f4cf Update definition of falsy to include zero time.Time values
b0e5ab051 Fir typo
60f6cb63b Update migrations.md
ec52c7ba1 Improve formatting of example code
e5681ad01 Improve formatting of example code
bdf3ffc73 Clarify the various next/prev methods
b5505d22a Clarify template lookup order for shortcodes
cf8dd7034 Improve embedded.md
e5dee2651 Update transform.ToMath
4d419a128 Update pagination configuration to use new struct
05d4fd597 Update PrevInSection.md
fd33370ed Add new-in 0.133.0
f9062042f Add the new page config section
205645e97 Remove out-dated  new-in
3ed3673f7 Fix typo
41df91659 Document the 'else with' construct introduced with Go 1.23
9c4697ab3 netlify: Hugo 0.133.0
62506b052 Merge branch 'temp133'
877e1bfcd Add config options page.nextPrevSortOrder/nextPrevInSectionSortOrder
eb159fe62 Update menu.md
efa7795a0 Update theme
dbe8911ad netlify: Hugo 0.132.2
2f793d328 Document passthrough render hooks
a7ce9a5e8 netlify: Hugo 0.132.1
2c137cb48 Update blockquotes.md
e0fa2f0d1 Add new-in badge to blockquote render hook page
bf42bbe6b Update references to render hooks
85a3d9958 Update theme
2dae72128 Document blockquote render hooks
8f5afb55d Update plainify return type
160f22d0e netlify: Hugo 0.132.0
82b5586fb Document transform.ToMath
1efcbcddb tpl/transform: Make Plainify and ToMath return template.HTML
31727be2e docs: Regen docshelper
88a421426 Merge commit 'a6e635ca7d'

git-subtree-dir: docs
git-subtree-split: a49697e536ee0d477ab4e552cfa8dc74debeff27
2024-09-01 14:51:15 +02:00
Bjørn Erik Pedersen
0c453420e6
Merge commit 'dec8cd4ada' 2024-09-01 14:51:15 +02:00
Bjørn Erik Pedersen
469124823c
Rename hstring.RenderedHTML => hstring.HTML
And add a comment about why it exists.
2024-09-01 10:04:20 +02:00
Bjørn Erik Pedersen
96afea4acc github: Try to fix "no space left on device" on MacOS 2024-08-31 21:05:58 +02:00
Bjørn Erik Pedersen
6d97ee711e Make all renderhook Text methods return template.HTML 2024-08-31 21:05:58 +02:00
Bjørn Erik Pedersen
f738669a4d Add Markdown render hooks for tables
Fixes #9316
Fixes #12811
2024-08-31 17:21:31 +02:00
Joe Mooring
b63f24adc7 create/skeletons: Clean up lang attribute in base template 2024-08-31 17:02:47 +02:00
Andreas Deininger
8fb933550f Fix deprecation warning for resources.ToCSS 2024-08-30 22:26:29 +02:00
Joe Mooring
a3684c8361 tpl/resources: Improve resources.Concat error message
Closes #7428
2024-08-30 08:39:29 +02:00
Patrice Chalin
53a8de21b8
tpl: Trim whitespace from google_analytics.html 2024-08-29 21:32:46 +02:00
Bjørn Erik Pedersen
37609262dc Add Page.Contents with scope support
Note that this also adds a new `.ContentWithoutSummary` method, and to do that we had to unify the different summary types:

Both `auto` and `manual` now returns HTML. Before this commit, `auto` would return plain text. This could be considered to be a slightly breaking change, but for the better: Now you can treat the `.Summary` the same without thinking about where it comes from, and if you want plain text, pipe it into `{{ .Summary | plainify }}`.

Fixes #8680
Fixes #12761
Fixes #12778
Fixes #716
2024-08-29 16:45:21 +02:00
Bjørn Erik Pedersen
2b5c335e93 deps: Upgrade github.com/bep/golibsass v1.1.1 => v1.2.0
Fixes #12649
2024-08-28 17:02:19 +02:00
dependabot[bot]
12a28ef773 build(deps): bump github.com/evanw/esbuild from 0.23.0 to 0.23.1
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.23.0 to 0.23.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.23.0...v0.23.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-28 13:05:57 +02:00
dependabot[bot]
4ded32d077 build(deps): bump github.com/pelletier/go-toml/v2 from 2.2.2 to 2.2.3
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.2.2 to 2.2.3.
- [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.2...v2.2.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-28 13:05:42 +02:00
Joe Mooring
1ecd0596a3 output: Fix docshelper template lookup order for AMP pages
Fixes #12797
2024-08-28 13:05:28 +02:00
Bjørn Erik Pedersen
371246de25
hugolib: Add a test for overriding _internal templates 2024-08-28 08:17:16 +02:00
hugoreleaser
aa0f66b290 releaser: Prepare repository for 0.134.0-DEV
[ci skip]
2024-08-26 14:13:52 +00:00
hugoreleaser
47d00202e7 releaser: Bump versions for release of 0.133.1
[ci skip]
2024-08-26 13:58:46 +00:00
Bjørn Erik Pedersen
261d7a03ae
Revert "Adjust Circleci workflow to make the Docker images build"
This reverts commit 445283a593.
2024-08-26 15:56:39 +02:00
Bjørn Erik Pedersen
445283a593 Adjust Circleci workflow to make the Docker images build
Closes #12753
2024-08-26 14:19:22 +02:00
Bjørn Erik Pedersen
5f667f8796 Fix missing method NameNormalized panic
Closes #12795
2024-08-25 23:17:26 +02:00
Bjørn Erik Pedersen
e1becf1dfe
deps: Upgraded github.com/bep/imagemeta v0.8.0 => v0.8.1
Closes #12793
2024-08-24 18:52:03 +02:00
Joe Mooring
7792392a6f config: Fix pagination deprecation messages 2024-08-21 22:01:59 +02:00
Bjørn Erik Pedersen
46484bff84
Revert "releaser: Rework the run conditions"
This reverts commit 702b1e8b81.
2024-08-18 19:12:03 +02:00
Bjørn Erik Pedersen
0e5a26057c
Revert "circleci: Upgrade to version 2.1 of the schema"
This reverts commit c8f850ae17.
2024-08-18 19:12:02 +02:00
Bjørn Erik Pedersen
c8f850ae17
circleci: Upgrade to version 2.1 of the schema
Updates #12753
2024-08-18 18:49:42 +02:00
Bjørn Erik Pedersen
702b1e8b81 releaser: Rework the run conditions
Now after actually reading the CircleCI docs...

Closes #12753
2024-08-18 18:46:56 +02:00
Bjørn Erik Pedersen
73d32e7737
Revert "releaser: Rework the run conditions"
This reverts commit 777534b2a4.

Circle ci throws validation errors:

```
Config does not conform to schema: {:workflows {:release {:jobs [{:prepare_release (not (map? nil)), :when {:and disallowed-key}} nil nil nil]}}}
```
2024-08-18 12:00:47 +02:00
Bjørn Erik Pedersen
777534b2a4
releaser: Rework the run conditions
Closes #12753
2024-08-18 11:47:51 +02:00
hugoreleaser
c054cba042 releaser: Prepare repository for 0.134.0-DEV
[ci skip]
2024-08-17 20:11:05 +00:00
hugoreleaser
c9777473d1 releaser: Bump versions for release of 0.133.0
[ci skip]
2024-08-17 19:57:41 +00:00
Bjørn Erik Pedersen
01008ba512 Add config options page.nextPrevSortOrder/nextPrevInSectionSortOrder
See #12776
2024-08-17 16:47:50 +02:00
dependabot[bot]
53c0ddfcb4 build(deps): bump gocloud.dev from 0.38.0 to 0.39.0
Bumps [gocloud.dev](https://github.com/google/go-cloud) from 0.38.0 to 0.39.0.
- [Release notes](https://github.com/google/go-cloud/releases)
- [Commits](https://github.com/google/go-cloud/compare/v0.38.0...v0.39.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-15 12:14:59 +02:00
Bjørn Erik Pedersen
133eeafeb4 deps: Upgrade github.com/tetratelabs/wazero v1.7.4-0.20240805170331-2b12e189eeec => v1.8.0 2024-08-15 12:14:49 +02:00
Bjørn Erik Pedersen
2168c5b125 Upgrade to Go 1.23
Fixes #12763
2024-08-15 10:18:19 +02:00
hugoreleaser
b3ad58fa04 releaser: Prepare repository for 0.133.0-DEV
[ci skip]
2024-08-14 16:31:14 +00:00
2244 changed files with 49449 additions and 43051 deletions

View file

@ -4,7 +4,7 @@ parameters:
defaults: &defaults
resource_class: large
docker:
- image: bepsays/ci-hugoreleaser:1.22200.20501
- image: bepsays/ci-hugoreleaser:1.22400.20000
environment: &buildenv
GOMODCACHE: /root/project/gomodcache
version: 2
@ -14,9 +14,7 @@ jobs:
environment: &buildenv
GOMODCACHE: /root/project/gomodcache
steps:
- &remote-docker
setup_remote_docker:
version: 20.10.14
- setup_remote_docker
- checkout:
path: hugo
- &git-config
@ -60,7 +58,7 @@ jobs:
environment:
<<: [*buildenv]
docker:
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22200.20501
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22400.20000
steps:
- *restore-cache
- &attach-workspace

View file

@ -1,10 +1,9 @@
name: Build Docker image
on:
push:
tags:
- "*"
workflow_dispatch:
release:
types: [published]
pull_request:
permissions:
packages: write
@ -14,18 +13,8 @@ env:
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
- linux/arm64
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
steps:
- name: Checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
@ -35,9 +24,6 @@ jobs:
with:
images: ${{ env.REGISTRY_IMAGE }}
- name: Set up QEMU
uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1
@ -49,69 +35,15 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push by digest
- name: Build and push
id: build
uses: docker/build-push-action@16ebe778df0e7752d2cfcbd924afdbbd89c1a755 # v6.6.1
with:
context: .
push: ${{ startsWith(github.ref, 'refs/tags') }}
platforms: ${{ matrix.platform }}
provenance: mode=max
sbom: true
push: ${{ github.event_name != 'pull_request' }}
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge:
runs-on: ubuntu-latest
needs:
- build
steps:
- name: Download digests
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1
- name: Docker meta
id: meta
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
with:
images: ${{ env.REGISTRY_IMAGE }}
flavor: |
latest=false
- name: Login to GHCR
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create manifest list and push
if: ${{ startsWith(github.ref, 'refs/tags') }}
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
- name: Inspect image
if: ${{ startsWith(github.ref, 'refs/tags') }}
run: |
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
build-args: HUGO_BUILD_TAGS=extended,withdeploy

View file

@ -6,18 +6,18 @@ name: Test
env:
GOPROXY: https://proxy.golang.org
GO111MODULE: on
SASS_VERSION: 1.63.2
DART_SASS_SHA_LINUX: 3ea33c95ad5c35fda6e9a0956199eef38a398f496cfb8750e02479d7d1dd42af
DART_SASS_SHA_MACOS: 11c70f259836b250b44a9cb57fed70e030f21f45069b467d371685855f1eb4f0
DART_SASS_SHA_WINDOWS: cd8cd36a619dd8e27f93d3186c52d70eb7d69472aa6c85f5094b29693e773f64
SASS_VERSION: 1.80.3
DART_SASS_SHA_LINUX: 7c933edbad0a7d389192c5b79393485c088bd2c4398e32f5754c32af006a9ffd
DART_SASS_SHA_MACOS: 79e060b0e131c3bb3c16926bafc371dc33feab122bfa8c01aa337a072097967b
DART_SASS_SHA_WINDOWS: 0bc4708b37cd1bac4740e83ac5e3176e66b774f77fd5dd364da5b5cfc9bfb469
permissions:
contents: read
jobs:
test:
strategy:
matrix:
go-version: [1.21.x, 1.22.x]
os: [ubuntu-latest, macos-latest, windows-latest]
go-version: [1.23.x, 1.24.x]
os: [ubuntu-latest, windows-latest] # macos disabled for now because of disk space issues.
runs-on: ${{ matrix.os }}
steps:
- if: matrix.os == 'ubuntu-latest'
@ -112,17 +112,17 @@ jobs:
sass --version;
mage -v check;
env:
HUGO_BUILD_TAGS: extended
HUGO_BUILD_TAGS: extended,withdeploy
- if: matrix.os == 'windows-latest'
# See issue #11052. We limit the build to regular test (no -race flag) on Windows for now.
name: Test
run: |
mage -v test;
env:
HUGO_BUILD_TAGS: extended
HUGO_BUILD_TAGS: extended,withdeploy
- name: Build tags
run: |
go install -tags extended,nodeploy
go install -tags extended
- if: matrix.os == 'ubuntu-latest'
name: Build for dragonfly
run: |

5
.gitignore vendored
View file

@ -1,3 +1,6 @@
*.test
imports.*
imports.*
dist/
public/
.DS_Store

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
@ -93,6 +93,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*.

View file

@ -2,44 +2,98 @@
# Twitter: https://twitter.com/gohugoio
# Website: https://gohugo.io/
FROM golang:1.21-alpine AS build
ARG GO_VERSION="1.24"
ARG ALPINE_VERSION="3.22"
ARG DART_SASS_VERSION="1.79.3"
# Optionally set HUGO_BUILD_TAGS to "extended" or "nodeploy" when building like so:
# docker build --build-arg HUGO_BUILD_TAGS=extended .
ARG HUGO_BUILD_TAGS
FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.5.0 AS xx
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS gobuild
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS gorun
ARG CGO=1
ENV CGO_ENABLED=${CGO}
ENV GOOS=linux
ENV GO111MODULE=on
FROM gobuild AS build
RUN apk add clang lld
# Set up cross-compilation helpers
COPY --from=xx / /
ARG TARGETPLATFORM
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 .
#
# We build the extended version by default.
ARG HUGO_BUILD_TAGS="extended"
ENV CGO_ENABLED=1
ENV GOPROXY=https://proxy.golang.org
ENV GOCACHE=/root/.cache/go-build
ENV GOMODCACHE=/go/pkg/mod
ARG TARGETPLATFORM
WORKDIR /go/src/github.com/gohugoio/hugo
COPY . /go/src/github.com/gohugoio/hugo/
# For --mount=type=cache the value of target is the default cache id, so
# for the go mod cache it would be good if we could share it with other Go images using the same setup,
# but the go build cache needs to be per platform.
# See this comment: https://github.com/moby/buildkit/issues/1706#issuecomment-702238282
RUN --mount=target=. \
--mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build,id=go-build-$TARGETPLATFORM <<EOT
set -ex
xx-go build -tags "$HUGO_BUILD_TAGS" -ldflags "-s -w -X github.com/gohugoio/hugo/common/hugo.vendorInfo=docker" -o /usr/bin/hugo
xx-verify /usr/bin/hugo
EOT
# gcc/g++ are required to build SASS libraries for extended version
RUN apk update && \
apk add --no-cache gcc g++ musl-dev git && \
go install github.com/magefile/mage
# dart-sass downloads the dart-sass runtime dependency
FROM alpine:${ALPINE_VERSION} AS dart-sass
ARG TARGETARCH
ARG DART_SASS_VERSION
ARG DART_ARCH=${TARGETARCH/amd64/x64}
WORKDIR /out
ADD https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-${DART_ARCH}.tar.gz .
RUN tar -xf dart-sass-${DART_SASS_VERSION}-linux-${DART_ARCH}.tar.gz
RUN mage hugo && mage install
FROM gorun AS final
# ---
COPY --from=build /usr/bin/hugo /usr/bin/hugo
FROM alpine:3.18
# libc6-compat are required for extended libraries (libsass, libwebp).
RUN apk add --no-cache \
libc6-compat \
git \
runuser \
nodejs \
npm
COPY --from=build /go/bin/hugo /usr/bin/hugo
RUN mkdir -p /var/hugo/bin /cache && \
addgroup -Sg 1000 hugo && \
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 && \
# See https://github.com/gohugoio/hugo/issues/9810
runuser -u hugo -- git config --global core.quotepath false
# libc6-compat & libstdc++ are required for extended SASS libraries
# ca-certificates are required to fetch outside resources (like Twitter oEmbeds)
RUN apk update && \
apk add --no-cache ca-certificates libc6-compat libstdc++ git
USER hugo:hugo
VOLUME /project
WORKDIR /project
ENV HUGO_CACHEDIR=/cache
ENV PATH="/var/hugo/bin:$PATH"
VOLUME /site
WORKDIR /site
COPY scripts/docker/entrypoint.sh /entrypoint.sh
COPY --from=dart-sass /out/dart-sass /var/hugo/bin/dart-sass
# Update PATH to reflect the new dependencies.
# For more complex setups, we should probably find a way to
# delegate this to the script itself, but this will have to do for now.
# Also, the dart-sass binary is a little special, other binaries can be put/linked
# directly in /var/hugo/bin.
ENV PATH="/var/hugo/bin/dart-sass:$PATH"
# Expose port for live server
EXPOSE 1313
ENTRYPOINT ["hugo"]
ENTRYPOINT ["/entrypoint.sh"]
CMD ["--help"]

202
README.md
View file

@ -33,7 +33,7 @@ A fast and flexible static site generator built with love by [bep], [spf13], and
[![Tests on Linux, MacOS and Windows](https://github.com/gohugoio/hugo/workflows/Test/badge.svg)](https://github.com/gohugoio/hugo/actions?query=workflow%3ATest)
[![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> | <a rel="me" href="https://x.com/gohugoiov2">X</a>
[Website] | [Installation] | [Documentation] | [Support] | [Contributing] | <a rel="me" href="https://fosstodon.org/@gohugoio">Mastodon</a>
## Overview
@ -65,11 +65,30 @@ 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>
<a href="https://www.linode.com/?utm_campaign=hugosponsor&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/hugoDocs/master/assets/images/sponsors/linode-logo_standard_light_medium.png" width="200" alt="Linode"></a>
&nbsp;&nbsp;&nbsp;
<a href="https://route4me.com/" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/Route4MeLogoBlueOnWhite.svg" width="200" alt="Route Planning & Route Optimization Software"></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://pinme.eth.limo/?s=hugo" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/hugoDocs/master/assets/images/sponsors/logo-pinme.svg" width="200" alt="PinMe."></a>
</p>
## Editions
Hugo is available in three editions: standard, extended, and extended/deploy. While the standard edition provides core functionality, the extended and extended/deploy editions offer advanced features.
Feature|extended edition|extended/deploy edition
:--|:-:|:-:
Encode to the WebP format when [processing images]. You can decode WebP images with any edition.|:heavy_check_mark:|:heavy_check_mark:
[Transpile Sass to CSS] using the embedded LibSass transpiler. You can use the [Dart Sass] transpiler with any edition.|:heavy_check_mark:|:heavy_check_mark:
Deploy your site directly to a Google Cloud Storage bucket, an AWS S3 bucket, or an Azure Storage container. See&nbsp;[details].|:x:|:heavy_check_mark:
[dart sass]: https://gohugo.io/functions/css/sass/#dart-sass
[processing images]: https://gohugo.io/content-management/image-processing/
[transpile sass to css]: https://gohugo.io/functions/css/sass/
[details]: https://gohugo.io/hosting-and-deployment/hugo-deploy/
Unless your specific deployment needs require the extended/deploy edition, we recommend the extended edition.
## Installation
Install Hugo from a [prebuilt binary], package manager, or package repository. Please see the installation instructions for your operating system:
@ -81,15 +100,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.23.0 or later
- Extended edition: Go 1.23.0 or later, and GCC
- Extended/deploy edition: Go 1.23.0 or later, and GCC
Build the standard edition:
@ -102,6 +117,13 @@ Build the extended edition:
```text
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)
@ -148,153 +170,113 @@ Hugo stands on the shoulders of great open source libraries. Run `hugo env --log
<summary>See current dependencies</summary>
```text
cloud.google.com/go/compute/metadata="v0.2.3"
cloud.google.com/go/iam="v1.1.5"
cloud.google.com/go/storage="v1.35.1"
cloud.google.com/go="v0.110.10"
github.com/Azure/azure-sdk-for-go/sdk/azcore="v1.9.0"
github.com/Azure/azure-sdk-for-go/sdk/azidentity="v1.4.0"
github.com/Azure/azure-sdk-for-go/sdk/internal="v1.5.0"
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob="v1.2.0"
github.com/Azure/go-autorest/autorest/to="v0.4.0"
github.com/AzureAD/microsoft-authentication-library-for-go="v1.2.0"
github.com/BurntSushi/locker="v0.0.0-20171006230638-a6e239ea1c69"
github.com/alecthomas/chroma/v2="v2.14.0"
github.com/PuerkitoBio/goquery="v1.10.1"
github.com/alecthomas/chroma/v2="v2.15.0"
github.com/andybalholm/cascadia="v1.3.3"
github.com/armon/go-radix="v1.0.1-0.20221118154546-54df44f2176c"
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream="v1.5.4"
github.com/aws/aws-sdk-go-v2/config="v1.26.1"
github.com/aws/aws-sdk-go-v2/credentials="v1.16.12"
github.com/aws/aws-sdk-go-v2/feature/ec2/imds="v1.14.10"
github.com/aws/aws-sdk-go-v2/feature/s3/manager="v1.15.7"
github.com/aws/aws-sdk-go-v2/internal/configsources="v1.3.5"
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2="v2.6.5"
github.com/aws/aws-sdk-go-v2/internal/ini="v1.7.2"
github.com/aws/aws-sdk-go-v2/internal/v4a="v1.2.9"
github.com/aws/aws-sdk-go-v2/service/cloudfront="v1.35.4"
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding="v1.10.4"
github.com/aws/aws-sdk-go-v2/service/internal/checksum="v1.2.9"
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url="v1.10.9"
github.com/aws/aws-sdk-go-v2/service/internal/s3shared="v1.16.9"
github.com/aws/aws-sdk-go-v2/service/s3="v1.47.5"
github.com/aws/aws-sdk-go-v2/service/sso="v1.18.5"
github.com/aws/aws-sdk-go-v2/service/ssooidc="v1.21.5"
github.com/aws/aws-sdk-go-v2/service/sts="v1.26.5"
github.com/aws/aws-sdk-go-v2="v1.26.1"
github.com/aws/aws-sdk-go="v1.50.7"
github.com/aws/smithy-go="v1.20.2"
github.com/bep/clocks="v0.5.0"
github.com/bep/debounce="v1.2.0"
github.com/bep/gitmap="v1.1.2"
github.com/bep/gitmap="v1.6.0"
github.com/bep/goat="v0.5.0"
github.com/bep/godartsass/v2="v2.0.0"
github.com/bep/godartsass="v1.2.0"
github.com/bep/golibsass="v1.1.1"
github.com/bep/godartsass/v2="v2.3.2"
github.com/bep/golibsass="v1.2.0"
github.com/bep/gowebp="v0.3.0"
github.com/bep/lazycache="v0.4.0"
github.com/bep/imagemeta="v0.8.4"
github.com/bep/lazycache="v0.7.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/simplecobra="v0.5.0"
github.com/bep/tmc="v0.5.1"
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.3"
github.com/cpuguy83/go-md2man/v2="v2.0.4"
github.com/disintegration/gift="v1.2.1"
github.com/dlclark/regexp2="v1.11.0"
github.com/dustin/go-humanize="v1.0.1"
github.com/evanw/esbuild="v0.21.4"
github.com/fatih/color="v1.16.0"
github.com/dlclark/regexp2="v1.11.5"
github.com/dop251/goja="v0.0.0-20250125213203-5ef83b82af17"
github.com/evanw/esbuild="v0.24.2"
github.com/fatih/color="v1.18.0"
github.com/frankban/quicktest="v1.14.6"
github.com/fsnotify/fsnotify="v1.7.0"
github.com/getkin/kin-openapi="v0.123.0"
github.com/fsnotify/fsnotify="v1.8.0"
github.com/getkin/kin-openapi="v0.129.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/gobuffalo/flect="v1.0.2"
github.com/go-openapi/jsonpointer="v0.21.0"
github.com/go-openapi/swag="v0.23.0"
github.com/go-sourcemap/sourcemap="v2.1.4+incompatible"
github.com/gobuffalo/flect="v1.0.3"
github.com/gobwas/glob="v0.2.3"
github.com/gohugoio/go-i18n/v2="v2.1.3-0.20230805085216-e63c13218d0e"
github.com/gohugoio/hashstructure="v0.5.0"
github.com/gohugoio/httpcache="v0.7.0"
github.com/gohugoio/hugo-goldmark-extensions/extras="v0.2.0"
github.com/gohugoio/hugo-goldmark-extensions/passthrough="v0.2.0"
github.com/gohugoio/hugo-goldmark-extensions/passthrough="v0.3.0"
github.com/gohugoio/locales="v0.14.0"
github.com/gohugoio/localescompressed="v1.0.1"
github.com/golang-jwt/jwt/v5="v5.1.0"
github.com/golang/groupcache="v0.0.0-20210331224755-41bb18bfe9da"
github.com/golang/protobuf="v1.5.3"
github.com/golang/freetype="v0.0.0-20170609003504-e2365dfdc4a0"
github.com/google/go-cmp="v0.6.0"
github.com/google/s2a-go="v0.1.7"
github.com/google/uuid="v1.4.0"
github.com/google/wire="v0.5.0"
github.com/googleapis/enterprise-certificate-proxy="v0.3.2"
github.com/googleapis/gax-go/v2="v2.12.0"
github.com/gorilla/websocket="v1.5.1"
github.com/hairyhenderson/go-codeowners="v0.4.0"
github.com/google/pprof="v0.0.0-20250208200701-d0013a598941"
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/jmespath/go-jmespath="v0.4.0"
github.com/josharian/intern="v1.0.0"
github.com/kr/pretty="v0.3.1"
github.com/kr/text="v0.2.0"
github.com/kylelemons/godebug="v1.1.0"
github.com/kyokomi/emoji/v2="v2.2.12"
github.com/kyokomi/emoji/v2="v2.2.13"
github.com/lucasb-eyer/go-colorful="v1.2.0"
github.com/mailru/easyjson="v0.7.7"
github.com/makeworld-the-better-one/dither/v2="v2.4.0"
github.com/marekm4/color-extractor="v1.2.1"
github.com/mattn/go-colorable="v0.1.13"
github.com/mattn/go-isatty="v0.0.20"
github.com/mattn/go-runewidth="v0.0.9"
github.com/mitchellh/hashstructure="v1.1.0"
github.com/mazznoer/csscolorparser="v0.1.5"
github.com/mitchellh/mapstructure="v1.5.1-0.20231216201459-8508981c8b6c"
github.com/mohae/deepcopy="v0.0.0-20170929034955-c48cc78d4826"
github.com/muesli/smartcrop="v0.3.0"
github.com/niklasfasching/go-org="v1.7.0"
github.com/oasdiff/yaml3="v0.0.0-20241210130736-a94c01f36349"
github.com/oasdiff/yaml="v0.0.0-20241210131133-6b86fb107d80"
github.com/olekukonko/tablewriter="v0.0.5"
github.com/pbnjay/memory="v0.0.0-20210728143218-7b4eea64cf58"
github.com/pelletier/go-toml/v2="v2.2.2"
github.com/pelletier/go-toml/v2="v2.2.3"
github.com/perimeterx/marshmallow="v1.1.5"
github.com/pkg/browser="v0.0.0-20210911075715-681adbf594b8"
github.com/pkg/browser="v0.0.0-20240102092130-5ac0b6a4141c"
github.com/pkg/errors="v0.9.1"
github.com/rogpeppe/go-internal="v1.12.0"
github.com/rivo/uniseg="v0.4.7"
github.com/rogpeppe/go-internal="v1.13.1"
github.com/russross/blackfriday/v2="v2.1.0"
github.com/rwcarlsen/goexif="v0.0.0-20190401172101-9e8deecbddbd"
github.com/sass/dart-sass/compiler="1.77.5"
github.com/sass/dart-sass/implementation="1.77.5"
github.com/sass/dart-sass/protocol="2.7.1"
github.com/sass/libsass="3.6.5"
github.com/sass/libsass="3.6.6"
github.com/spf13/afero="v1.11.0"
github.com/spf13/cast="v1.6.0"
github.com/spf13/cobra="v1.8.0"
github.com/spf13/cast="v1.7.1"
github.com/spf13/cobra="v1.8.1"
github.com/spf13/fsync="v0.10.1"
github.com/spf13/pflag="v1.0.5"
github.com/tdewolff/minify/v2="v2.20.20"
github.com/tdewolff/parse/v2="v2.7.13"
github.com/spf13/pflag="v1.0.6"
github.com/tdewolff/minify/v2="v2.20.37"
github.com/tdewolff/parse/v2="v2.7.15"
github.com/tetratelabs/wazero="v1.8.2"
github.com/webmproject/libwebp="v1.3.2"
github.com/yuin/goldmark-emoji="v1.0.3"
github.com/yuin/goldmark="v1.7.4"
go.opencensus.io="v0.24.0"
github.com/yuin/goldmark-emoji="v1.0.4"
github.com/yuin/goldmark="v1.7.8"
go.uber.org/automaxprocs="v1.5.3"
gocloud.dev="v0.36.0"
golang.org/x/crypto="v0.23.0"
golang.org/x/exp="v0.0.0-20221031165847-c99f073a8326"
golang.org/x/image="v0.16.0"
golang.org/x/mod="v0.17.0"
golang.org/x/net="v0.25.0"
golang.org/x/oauth2="v0.15.0"
golang.org/x/sync="v0.7.0"
golang.org/x/sys="v0.20.0"
golang.org/x/text="v0.15.0"
golang.org/x/time="v0.5.0"
golang.org/x/tools="v0.20.0"
golang.org/x/xerrors="v0.0.0-20231012003039-104605ab7028"
google.golang.org/api="v0.152.0"
google.golang.org/genproto/googleapis/api="v0.0.0-20231120223509-83a465c0220f"
google.golang.org/genproto/googleapis/rpc="v0.0.0-20231120223509-83a465c0220f"
google.golang.org/genproto="v0.0.0-20231120223509-83a465c0220f"
google.golang.org/grpc="v1.59.0"
google.golang.org/protobuf="v1.33.0"
golang.org/x/crypto="v0.33.0"
golang.org/x/exp="v0.0.0-20250210185358-939b2ce775ac"
golang.org/x/image="v0.24.0"
golang.org/x/mod="v0.23.0"
golang.org/x/net="v0.35.0"
golang.org/x/sync="v0.11.0"
golang.org/x/sys="v0.30.0"
golang.org/x/text="v0.22.0"
golang.org/x/tools="v0.30.0"
golang.org/x/xerrors="v0.0.0-20240903120638-7835f813f4da"
gonum.org/v1/plot="v0.15.0"
google.golang.org/protobuf="v1.36.5"
gopkg.in/yaml.v2="v2.4.0"
gopkg.in/yaml.v3="v3.0.1"
oss.terrastruct.com/d2="v0.6.9"
oss.terrastruct.com/util-go="v0.0.0-20241005222610-44c011a04896"
rsc.io/qr="v0.2.0"
software.sslmate.com/src/go-pkcs12="v0.2.0"
```
</details>

View file

@ -1,37 +0,0 @@
#!/usr/bin/env bash
# allow user to override go executable by running as GOEXE=xxx make ...
GOEXE="${GOEXE-go}"
# Convenience script to
# - For a given branch
# - Run benchmark tests for a given package
# - Do the same for master
# - then compare the two runs with benchcmp
benchFilter=".*"
if (( $# < 2 ));
then
echo "USAGE: ./bench.sh <git-branch> <package-to-bench> (and <benchmark filter> (regexp, optional))"
exit 1
fi
if [ $# -eq 3 ]; then
benchFilter=$3
fi
BRANCH=$1
PACKAGE=$2
git checkout $BRANCH
"${GOEXE}" test -test.run=NONE -bench="$benchFilter" -test.benchmem=true ./$PACKAGE > /tmp/bench-$PACKAGE-$BRANCH.txt
git checkout master
"${GOEXE}" test -test.run=NONE -bench="$benchFilter" -test.benchmem=true ./$PACKAGE > /tmp/bench-$PACKAGE-master.txt
benchcmp /tmp/bench-$PACKAGE-master.txt /tmp/bench-$PACKAGE-$BRANCH.txt

View file

@ -1,12 +0,0 @@
#!/bin/bash
# allow user to override go executable by running as GOEXE=xxx make ...
GOEXE="${GOEXE-go}"
# Send in a regexp matching the benchmarks you want to run, i.e. './benchSite.sh "YAML"'.
# Note the quotes, which will be needed for more complex expressions.
# The above will run all variations, but only for front matter YAML.
echo "Running with BenchmarkSiteBuilding/${1}"
"${GOEXE}" test -run="NONE" -bench="BenchmarkSiteBuilding/${1}" -test.benchmem=true ./hugolib -memprofile mem.prof -count 3 -cpuprofile cpu.prof

View file

@ -1 +0,0 @@
gobench -package=./hugolib -bench="BenchmarkSiteNew/Deep_content_tree"

View file

@ -1 +0,0 @@
docker run --rm --mount type=bind,source="$(pwd)",target=/hugo -w /hugo -i -t bepsays/ci-goreleaser:1.11-2 /bin/bash

View file

@ -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
},
})
@ -430,12 +431,25 @@ func (p *Partition[K, V]) doGetOrCreateWitTimeout(key K, duration time.Duration,
errch := make(chan error, 1)
go func() {
v, _, err := p.c.GetOrCreate(key, create)
if err != nil {
errch <- err
return
}
resultch <- v
var (
v V
err error
)
defer func() {
if r := recover(); r != nil {
if rerr, ok := r.(error); ok {
err = rerr
} else {
err = fmt.Errorf("panic: %v", r)
}
}
if err != nil {
errch <- err
} else {
resultch <- v
}
}()
v, _, err = p.c.GetOrCreate(key, create)
}()
select {
@ -466,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
@ -512,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 {
@ -588,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()
}

View file

@ -14,8 +14,11 @@
package dynacache
import (
"errors"
"fmt"
"path/filepath"
"testing"
"time"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/loggers"
@ -144,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)
@ -165,6 +168,58 @@ func TestClear(t *testing.T) {
cache.adjustCurrentMaxSize()
}
func TestPanicInCreate(t *testing.T) {
t.Parallel()
c := qt.New(t)
cache := newTestCache(t)
p1 := GetOrCreatePartition[string, testItem](cache, "/aaaa/bbbb", OptionsPartition{Weight: 30, ClearWhen: ClearOnRebuild})
willPanic := func(i int) func() {
return func() {
p1.GetOrCreate(fmt.Sprintf("panic-%d", i), func(key string) (testItem, error) {
panic(errors.New(key))
})
}
}
// GetOrCreateWitTimeout needs to recover from panics in the create func.
willErr := func(i int) error {
_, err := p1.GetOrCreateWitTimeout(fmt.Sprintf("error-%d", i), 10*time.Second, func(key string) (testItem, error) {
return testItem{}, errors.New(key)
})
return err
}
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 := range 3 {
for range 3 {
v, err := p1.GetOrCreate(fmt.Sprintf("panic-%d", i), func(key string) (testItem, error) {
return testItem{
name: key,
}, nil
})
c.Assert(err, qt.IsNil)
c.Assert(v.name, qt.Equals, fmt.Sprintf("panic-%d", i))
v, err = p1.GetOrCreateWitTimeout(fmt.Sprintf("error-%d", i), 10*time.Second, func(key string) (testItem, error) {
return testItem{
name: key,
}, nil
})
c.Assert(err, qt.IsNil)
c.Assert(v.name, qt.Equals, fmt.Sprintf("error-%d", i))
}
}
}
func TestAdjustCurrentMaxSize(t *testing.T) {
t.Parallel()
c := qt.New(t)

View file

@ -59,7 +59,7 @@ dir = ":resourceDir/_gen"
caches, err := filecache.NewCaches(p)
c.Assert(err, qt.IsNil)
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 +74,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 {
@ -97,7 +97,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 {

View file

@ -105,7 +105,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))
@ -193,11 +193,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)

View file

@ -42,7 +42,7 @@ var DefaultConfig = Config{
// Config holds the configuration for the HTTP cache.
type Config struct {
// Configures the HTTP cache behaviour (RFC 9111).
// Configures the HTTP cache behavior (RFC 9111).
// When this is not enabled for a resource, Hugo will go straight to the file cache.
Cache Cache
@ -52,7 +52,7 @@ 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
}
@ -122,6 +122,10 @@ type GlobMatcher struct {
Includes []string
}
func (gm GlobMatcher) IsZero() bool {
return len(gm.Includes) == 0 && len(gm.Excludes) == 0
}
type ConfigCompiled struct {
For predicate.P[string]
PollConfigs []PollConfigCompiled
@ -155,6 +159,9 @@ func (p PollConfigCompiled) IsZero() bool {
}
func (gm *GlobMatcher) CompilePredicate() (func(string) bool, error) {
if gm.IsZero() {
panic("no includes or excludes")
}
var p predicate.P[string]
for _, include := range gm.Includes {
g, err := glob.Compile(include, '/')
@ -181,7 +188,7 @@ func (gm *GlobMatcher) CompilePredicate() (func(string) bool, error) {
return p, 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 +210,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
}

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)
}

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]interface{}{})
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)
}

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)
}
}
}

View file

@ -39,9 +39,9 @@ import (
"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/paths"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/allconfig"
"github.com/gohugoio/hugo/deps"
@ -66,6 +66,12 @@ func Execute(args []string) error {
}
args = mapLegacyArgs(args)
cd, err := x.Execute(context.Background(), args)
if cd != nil {
if closer, ok := cd.Root.Command.(types.Closer); ok {
closer.Close()
}
}
if err != nil {
if err == errHelp {
cd.CobraCommand.Help()
@ -88,11 +94,17 @@ type commonConfig struct {
fs *hugofs.Fs
}
type configKey struct {
counter int32
ignoreModulesDoesNotExists bool
}
// This is the root command.
type rootCommand struct {
Printf func(format string, v ...interface{})
Println func(a ...interface{})
Out io.Writer
Printf func(format string, v ...any)
Println func(a ...any)
StdOut io.Writer
StdErr io.Writer
logger loggers.Logger
@ -101,8 +113,8 @@ type rootCommand struct {
// Some, but not all commands need access to these.
// Some needs more than one, so keep them in a small cache.
commonConfigs *lazycache.Cache[int32, *commonConfig]
hugoSites *lazycache.Cache[int32, *hugolib.HugoSites]
commonConfigs *lazycache.Cache[configKey, *commonConfig]
hugoSites *lazycache.Cache[configKey, *hugolib.HugoSites]
// changesFromBuild received from Hugo in watch mode.
changesFromBuild chan []identity.Identity
@ -129,8 +141,6 @@ type rootCommand struct {
logLevel string
verbose bool
debug bool
quiet bool
devMode bool // Hidden flag.
@ -144,6 +154,18 @@ func (r *rootCommand) isVerbose() bool {
return r.logger.Level() <= logg.LevelInfo
}
func (r *rootCommand) Close() error {
if r.hugoSites != nil {
r.hugoSites.DeleteFunc(func(key configKey, value *hugolib.HugoSites) bool {
if value != nil {
value.Close()
}
return false
})
}
return nil
}
func (r *rootCommand) Build(cd *simplecobra.Commandeer, bcfg hugolib.BuildCfg, cfg config.Provider) (*hugolib.HugoSites, error) {
h, err := r.Hugo(cfg)
if err != nil {
@ -160,17 +182,18 @@ func (r *rootCommand) Commands() []simplecobra.Commander {
return r.commands
}
func (r *rootCommand) ConfigFromConfig(key int32, oldConf *commonConfig) (*commonConfig, error) {
cc, _, err := r.commonConfigs.GetOrCreate(key, func(key int32) (*commonConfig, error) {
func (r *rootCommand) ConfigFromConfig(key configKey, oldConf *commonConfig) (*commonConfig, error) {
cc, _, err := r.commonConfigs.GetOrCreate(key, func(key configKey) (*commonConfig, error) {
fs := oldConf.fs
configs, err := allconfig.LoadConfig(
allconfig.ConfigSourceDescriptor{
Flags: oldConf.cfg,
Fs: fs.Source,
Filename: r.cfgFile,
ConfigDir: r.cfgDir,
Logger: r.logger,
Environment: r.environment,
Flags: oldConf.cfg,
Fs: fs.Source,
Filename: r.cfgFile,
ConfigDir: r.cfgDir,
Logger: r.logger,
Environment: r.environment,
IgnoreModuleDoesNotExist: key.ignoreModulesDoesNotExists,
},
)
if err != nil {
@ -193,11 +216,11 @@ func (r *rootCommand) ConfigFromConfig(key int32, oldConf *commonConfig) (*commo
return cc, err
}
func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commonConfig, error) {
func (r *rootCommand) ConfigFromProvider(key configKey, cfg config.Provider) (*commonConfig, error) {
if cfg == nil {
panic("cfg must be set")
}
cc, _, err := r.commonConfigs.GetOrCreate(key, func(key int32) (*commonConfig, error) {
cc, _, err := r.commonConfigs.GetOrCreate(key, func(key configKey) (*commonConfig, error) {
var dir string
if r.source != "" {
dir, _ = filepath.Abs(r.source)
@ -220,12 +243,13 @@ func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commo
// Load the config first to allow publishDir to be configured in config file.
configs, err := allconfig.LoadConfig(
allconfig.ConfigSourceDescriptor{
Flags: cfg,
Fs: hugofs.Os,
Filename: r.cfgFile,
ConfigDir: r.cfgDir,
Environment: r.environment,
Logger: r.logger,
Flags: cfg,
Fs: hugofs.Os,
Filename: r.cfgFile,
ConfigDir: r.cfgDir,
Environment: r.environment,
Logger: r.logger,
IgnoreModuleDoesNotExist: key.ignoreModulesDoesNotExists,
},
)
if err != nil {
@ -307,7 +331,8 @@ func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commo
}
func (r *rootCommand) HugFromConfig(conf *commonConfig) (*hugolib.HugoSites, error) {
h, _, err := r.hugoSites.GetOrCreate(r.configVersionID.Load(), func(key int32) (*hugolib.HugoSites, error) {
k := configKey{counter: r.configVersionID.Load()}
h, _, err := r.hugoSites.GetOrCreate(k, func(key configKey) (*hugolib.HugoSites, error) {
depsCfg := r.newDepsConfig(conf)
return hugolib.NewHugoSites(depsCfg)
})
@ -315,7 +340,12 @@ func (r *rootCommand) HugFromConfig(conf *commonConfig) (*hugolib.HugoSites, err
}
func (r *rootCommand) Hugo(cfg config.Provider) (*hugolib.HugoSites, error) {
h, _, err := r.hugoSites.GetOrCreate(r.configVersionID.Load(), func(key int32) (*hugolib.HugoSites, error) {
return r.getOrCreateHugo(cfg, false)
}
func (r *rootCommand) getOrCreateHugo(cfg config.Provider, ignoreModuleDoesNotExist bool) (*hugolib.HugoSites, error) {
k := configKey{counter: r.configVersionID.Load(), ignoreModulesDoesNotExists: ignoreModuleDoesNotExist}
h, _, err := r.hugoSites.GetOrCreate(k, func(key configKey) (*hugolib.HugoSites, error) {
conf, err := r.ConfigFromProvider(key, cfg)
if err != nil {
return nil, err
@ -327,7 +357,7 @@ func (r *rootCommand) Hugo(cfg config.Provider) (*hugolib.HugoSites, error) {
}
func (r *rootCommand) newDepsConfig(conf *commonConfig) deps.DepsCfg {
return deps.DepsCfg{Configs: conf.configs, Fs: conf.fs, LogOut: r.logger.Out(), LogLevel: r.logger.Level(), ChangesFromBuild: r.changesFromBuild}
return deps.DepsCfg{Configs: conf.configs, Fs: conf.fs, StdOut: r.logger.StdOut(), StdErr: r.logger.StdErr(), LogLevel: r.logger.Level(), ChangesFromBuild: r.changesFromBuild}
}
func (r *rootCommand) Name() string {
@ -392,21 +422,23 @@ func (r *rootCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args
}
func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
r.Out = os.Stdout
r.StdOut = os.Stdout
r.StdErr = os.Stderr
if r.quiet {
r.Out = io.Discard
r.StdOut = io.Discard
r.StdErr = io.Discard
}
// Used by mkcert (server).
log.SetOutput(r.Out)
log.SetOutput(r.StdOut)
r.Printf = func(format string, v ...interface{}) {
r.Printf = func(format string, v ...any) {
if !r.quiet {
fmt.Fprintf(r.Out, format, v...)
fmt.Fprintf(r.StdOut, format, v...)
}
}
r.Println = func(a ...interface{}) {
r.Println = func(a ...any) {
if !r.quiet {
fmt.Fprintln(r.Out, a...)
fmt.Fprintln(r.StdOut, a...)
}
}
_, running := runner.Command.(*serverCommand)
@ -415,14 +447,16 @@ func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
if err != nil {
return err
}
// Set up the global logger early to allow info deprecations during config load.
loggers.SetGlobalLogger(r.logger)
r.changesFromBuild = make(chan []identity.Identity, 10)
r.commonConfigs = lazycache.New(lazycache.Options[int32, *commonConfig]{MaxEntries: 5})
r.commonConfigs = lazycache.New(lazycache.Options[configKey, *commonConfig]{MaxEntries: 5})
// We don't want to keep stale HugoSites in memory longer than needed.
r.hugoSites = lazycache.New(lazycache.Options[int32, *hugolib.HugoSites]{
r.hugoSites = lazycache.New(lazycache.Options[configKey, *hugolib.HugoSites]{
MaxEntries: 1,
OnEvict: func(key int32, value *hugolib.HugoSites) {
OnEvict: func(key configKey, value *hugolib.HugoSites) {
value.Close()
runtime.GC()
},
@ -450,32 +484,21 @@ func (r *rootCommand) createLogger(running bool) (loggers.Logger, error) {
default:
return nil, fmt.Errorf("invalid log level: %q, must be one of debug, warn, info or error", r.logLevel)
}
} else {
if r.verbose {
hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0")
hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0")
level = logg.LevelInfo
}
if r.debug {
hugo.Deprecate("--debug", "use --logLevel debug", "v0.114.0")
level = logg.LevelDebug
}
}
}
optsLogger := loggers.Options{
DistinctLevel: logg.LevelWarn,
Level: level,
Stdout: r.Out,
Stderr: r.Out,
StdOut: r.StdOut,
StdErr: r.StdErr,
StoreErrors: running,
}
return loggers.New(optsLogger), nil
}
func (r *rootCommand) Reset() {
func (r *rootCommand) resetLogs() {
r.logger.Reset()
loggers.Log().Reset()
}
@ -486,16 +509,26 @@ func (r *rootCommand) IsTestRun() bool {
}
func (r *rootCommand) Init(cd *simplecobra.Commandeer) error {
return r.initRootCommand("", cd)
}
func (r *rootCommand) initRootCommand(subCommandName string, cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
cmd.Use = "hugo [flags]"
cmd.Short = "hugo builds your site"
cmd.Long = `hugo is the main command, used to build your Hugo site.
commandName := "hugo"
if subCommandName != "" {
commandName = subCommandName
}
cmd.Use = fmt.Sprintf("%s [flags]", commandName)
cmd.Short = "Build your site"
cmd.Long = `COMMAND_NAME is the main command, used to build your Hugo site.
Hugo is a Fast and Flexible Static Site Generator
built with love by spf13 and friends in Go.
Complete documentation is available at https://gohugo.io/.`
cmd.Long = strings.ReplaceAll(cmd.Long, "COMMAND_NAME", commandName)
// Configure persistent flags
cmd.PersistentFlags().StringVarP(&r.source, "source", "s", "", "filesystem path to read files relative from")
_ = cmd.MarkFlagDirname("source")
@ -507,6 +540,7 @@ Complete documentation is available at https://gohugo.io/.`
cmd.PersistentFlags().StringP("themesDir", "", "", "filesystem path to themes directory")
_ = cmd.MarkFlagDirname("themesDir")
cmd.PersistentFlags().StringP("ignoreVendorPaths", "", "", "ignores any _vendor for module paths matching the given Glob pattern")
cmd.PersistentFlags().BoolP("noBuildLock", "", false, "don't create .hugo_build.lock file")
_ = cmd.RegisterFlagCompletionFunc("ignoreVendorPaths", cobra.NoFileCompletions)
cmd.PersistentFlags().String("clock", "", "set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00")
_ = cmd.RegisterFlagCompletionFunc("clock", cobra.NoFileCompletions)
@ -518,8 +552,6 @@ Complete documentation is available at https://gohugo.io/.`
cmd.PersistentFlags().BoolVar(&r.quiet, "quiet", false, "build in quiet mode")
cmd.PersistentFlags().BoolVarP(&r.renderToMemory, "renderToMemory", "M", false, "render to memory (mostly useful when running the server)")
cmd.PersistentFlags().BoolVarP(&r.verbose, "verbose", "v", false, "verbose output")
cmd.PersistentFlags().BoolVarP(&r.debug, "debug", "", false, "debug output")
cmd.PersistentFlags().BoolVarP(&r.devMode, "devMode", "", false, "only used for internal testing, flag hidden.")
cmd.PersistentFlags().StringVar(&r.logLevel, "logLevel", "", "log level (debug|info|warn|error)")
_ = cmd.RegisterFlagCompletionFunc("logLevel", cobra.FixedCompletions([]string{"debug", "info", "warn", "error"}, cobra.ShellCompDirectiveNoFileComp))
@ -564,7 +596,6 @@ func applyLocalFlagsBuild(cmd *cobra.Command, r *rootCommand) {
cmd.Flags().BoolVar(&r.forceSyncStatic, "forceSyncStatic", false, "copy all files when static is changed.")
cmd.Flags().BoolP("noTimes", "", false, "don't sync modification time of files")
cmd.Flags().BoolP("noChmod", "", false, "don't sync permission mode of files")
cmd.Flags().BoolP("noBuildLock", "", false, "don't create .hugo_build.lock file")
cmd.Flags().BoolP("printI18nWarnings", "", false, "print missing translations")
cmd.Flags().BoolP("printPathWarnings", "", false, "print warnings on duplicate target paths etc.")
cmd.Flags().BoolP("printUnusedTemplates", "", false, "print warnings on unused templates.")

View file

@ -14,6 +14,8 @@
package commands
import (
"context"
"github.com/bep/simplecobra"
)
@ -21,6 +23,7 @@ import (
func newExec() (*simplecobra.Exec, error) {
rootCmd := &rootCommand{
commands: []simplecobra.Commander{
newHugoBuildCmd(),
newVersionCmd(),
newEnvCommand(),
newServerCommand(),
@ -38,3 +41,33 @@ func newExec() (*simplecobra.Exec, error) {
return simplecobra.New(rootCmd)
}
func newHugoBuildCmd() simplecobra.Commander {
return &hugoBuildCommand{}
}
// hugoBuildCommand just delegates to the rootCommand.
type hugoBuildCommand struct {
rootCmd *rootCommand
}
func (c *hugoBuildCommand) Commands() []simplecobra.Commander {
return nil
}
func (c *hugoBuildCommand) Name() string {
return "build"
}
func (c *hugoBuildCommand) Init(cd *simplecobra.Commandeer) error {
c.rootCmd = cd.Root.Command.(*rootCommand)
return c.rootCmd.initRootCommand("build", cd)
}
func (c *hugoBuildCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
return c.rootCmd.PreRun(cd, runner)
}
func (c *hugoBuildCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
return c.rootCmd.Run(ctx, cd, args)
}

View file

@ -43,8 +43,9 @@ func newConfigCommand() *configCommand {
type configCommand struct {
r *rootCommand
format string
lang string
format string
lang string
printZero bool
commands []simplecobra.Commander
}
@ -58,7 +59,7 @@ func (c *configCommand) Name() string {
}
func (c *configCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
conf, err := c.r.ConfigFromProvider(c.r.configVersionID.Load(), flagsToCfg(cd, nil))
conf, err := c.r.ConfigFromProvider(configKey{counter: c.r.configVersionID.Load()}, flagsToCfg(cd, nil))
if err != nil {
return err
}
@ -78,7 +79,7 @@ func (c *configCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, arg
dec.SetIndent("", " ")
dec.SetEscapeHTML(false)
if err := dec.Encode(parser.ReplacingJSONMarshaller{Value: config, KeysToLower: true, OmitEmpty: true}); err != nil {
if err := dec.Encode(parser.ReplacingJSONMarshaller{Value: config, KeysToLower: true, OmitEmpty: !c.printZero}); err != nil {
return err
}
@ -89,7 +90,7 @@ 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
}
@ -110,11 +111,12 @@ func (c *configCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, arg
func (c *configCommand) Init(cd *simplecobra.Commandeer) error {
c.r = cd.Root.Command.(*rootCommand)
cmd := cd.CobraCommand
cmd.Short = "Print the site configuration"
cmd.Long = `Print the site configuration, both default and custom settings.`
cmd.Short = "Display site configuration"
cmd.Long = `Display site configuration, both default and custom settings.`
cmd.Flags().StringVar(&c.format, "format", "toml", "preferred file format (toml, yaml or json)")
_ = cmd.RegisterFlagCompletionFunc("format", cobra.FixedCompletions([]string{"toml", "yaml", "json"}, cobra.ShellCompDirectiveNoFileComp))
cmd.Flags().StringVar(&c.lang, "lang", "", "the language to display config for. Defaults to the first language defined.")
cmd.Flags().BoolVar(&c.printZero, "printZero", false, `include config options with zero values (e.g. false, 0, "") in the output`)
_ = cmd.RegisterFlagCompletionFunc("lang", cobra.NoFileCompletions)
applyLocalFlagsBuildConfig(cmd, c.r)
@ -209,7 +211,7 @@ func (c *configMountsCommand) Name() string {
func (c *configMountsCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
r := c.configCmd.r
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, nil))
conf, err := r.ConfigFromProvider(configKey{counter: c.r.configVersionID.Load()}, flagsToCfg(cd, nil))
if err != nil {
return err
}

View file

@ -105,8 +105,8 @@ func (c *convertCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, ar
func (c *convertCommand) Init(cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
cmd.Short = "Convert your content to different formats"
cmd.Long = `Convert your content (e.g. front matter) to different formats.
cmd.Short = "Convert front matter to another format"
cmd.Long = `Convert front matter to another format.
See convert's subcommands toJSON, toTOML and toYAML for more information.`

View file

@ -11,21 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !nodeploy
// +build !nodeploy
// 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.
//go:build withdeploy
package commands
@ -33,7 +19,6 @@ import (
"context"
"github.com/gohugoio/hugo/deploy"
"github.com/gohugoio/hugo/deploy/deployconfig"
"github.com/bep/simplecobra"
"github.com/spf13/cobra"
@ -42,8 +27,8 @@ import (
func newDeployCommand() simplecobra.Commander {
return &simpleCommand{
name: "deploy",
short: "Deploy your site to a Cloud provider.",
long: `Deploy your site to a Cloud provider.
short: "Deploy your site to a cloud provider",
long: `Deploy your site to a cloud provider
See https://gohugo.io/hosting-and-deployment/hugo-deploy/ for detailed
documentation.
@ -60,17 +45,7 @@ documentation.
return deployer.Deploy(ctx)
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
cmd.Flags().String("target", "", "target deployment from deployments section in config file; defaults to the first one")
_ = cmd.RegisterFlagCompletionFunc("target", cobra.NoFileCompletions)
cmd.Flags().Bool("confirm", false, "ask for confirmation before making changes to the target")
cmd.Flags().Bool("dryRun", false, "dry run")
cmd.Flags().Bool("force", false, "force upload of all files")
cmd.Flags().Bool("invalidateCDN", deployconfig.DefaultConfig.InvalidateCDN, "invalidate the CDN cache listed in the deployment target")
cmd.Flags().Int("maxDeletes", deployconfig.DefaultConfig.MaxDeletes, "maximum # of files to delete, or -1 to disable")
_ = cmd.RegisterFlagCompletionFunc("maxDeletes", cobra.NoFileCompletions)
cmd.Flags().Int("workers", deployconfig.DefaultConfig.Workers, "number of workers to transfer files. defaults to 10")
_ = cmd.RegisterFlagCompletionFunc("workers", cobra.NoFileCompletions)
applyDeployFlags(cmd, r)
},
}
}

33
commands/deploy_flags.go Normal file
View file

@ -0,0 +1,33 @@
// 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 commands
import (
"github.com/gohugoio/hugo/deploy/deployconfig"
"github.com/spf13/cobra"
)
func applyDeployFlags(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
cmd.Flags().String("target", "", "target deployment from deployments section in config file; defaults to the first one")
_ = cmd.RegisterFlagCompletionFunc("target", cobra.NoFileCompletions)
cmd.Flags().Bool("confirm", false, "ask for confirmation before making changes to the target")
cmd.Flags().Bool("dryRun", false, "dry run")
cmd.Flags().Bool("force", false, "force upload of all files")
cmd.Flags().Bool("invalidateCDN", deployconfig.DefaultConfig.InvalidateCDN, "invalidate the CDN cache listed in the deployment target")
cmd.Flags().Int("maxDeletes", deployconfig.DefaultConfig.MaxDeletes, "maximum # of files to delete, or -1 to disable")
_ = cmd.RegisterFlagCompletionFunc("maxDeletes", cobra.NoFileCompletions)
cmd.Flags().Int("workers", deployconfig.DefaultConfig.Workers, "number of workers to transfer files. defaults to 10")
_ = cmd.RegisterFlagCompletionFunc("workers", cobra.NoFileCompletions)
}

View file

@ -11,8 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build nodeploy
// +build nodeploy
//go:build !withdeploy
// Copyright 2024 The Hugo Authors. All rights reserved.
//
@ -31,6 +30,7 @@ package commands
import (
"context"
"errors"
"github.com/bep/simplecobra"
"github.com/spf13/cobra"
@ -40,9 +40,10 @@ func newDeployCommand() simplecobra.Commander {
return &simpleCommand{
name: "deploy",
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
return nil
return errors.New("deploy not supported in this version of Hugo; install a release with 'withdeploy' in the archive filename or build yourself with the 'withdeploy' build tag. Also see https://github.com/gohugoio/hugo/pull/12995")
},
withc: func(cmd *cobra.Command, r *rootCommand) {
applyDeployFlags(cmd, r)
cmd.Hidden = true
},
}

View file

@ -25,8 +25,8 @@ import (
func newEnvCommand() simplecobra.Commander {
return &simpleCommand{
name: "env",
short: "Print Hugo version and environment info",
long: "Print Hugo version and environment info. This is useful in Hugo bug reports",
short: "Display version and environment info",
long: "Display version and environment info. This is useful in Hugo bug reports",
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
r.Printf("%s\n", hugo.BuildVersionString())
r.Printf("GOOS=%q\n", runtime.GOOS)
@ -61,8 +61,8 @@ func newVersionCmd() simplecobra.Commander {
r.Println(hugo.BuildVersionString())
return nil
},
short: "Print Hugo version and environment info",
long: "Print Hugo version and environment info. This is useful in Hugo bug reports.",
short: "Display version",
long: "Display version and environment info. This is useful in Hugo bug reports.",
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
},

View file

@ -21,6 +21,7 @@ import (
"os"
"path"
"path/filepath"
"slices"
"strings"
"github.com/alecthomas/chroma/v2"
@ -49,6 +50,7 @@ func newGenCommand() *genCommand {
highlightStyle string
lineNumbersInlineStyle string
lineNumbersTableStyle string
omitEmpty bool
)
newChromaStyles := func() simplecobra.Commander {
@ -60,6 +62,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,8 +80,17 @@ 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))
formatter.WriteCSS(os.Stdout, style)
var formatter *html.Formatter
if omitEmpty {
formatter = html.New(html.WithClasses(true))
} else {
formatter = html.New(html.WithAllClasses(true))
}
w := os.Stdout
fmt.Fprintf(w, "/* Generated using: hugo %s */\n\n", strings.Join(os.Args[1:], " "))
formatter.WriteCSS(w, style)
return nil
},
withc: func(cmd *cobra.Command, r *rootCommand) {
@ -88,6 +103,8 @@ 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`)
_ = cmd.RegisterFlagCompletionFunc("omitEmpty", cobra.NoFileCompletions)
},
}
}
@ -142,7 +159,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/.
@ -167,13 +184,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)
@ -194,7 +211,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)
@ -215,7 +232,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
}
@ -273,7 +290,8 @@ func (c *genCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args [
func (c *genCommand) Init(cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
cmd.Short = "A collection of several useful generators."
cmd.Short = "Generate documentation and syntax highlighting styles"
cmd.Long = "Generate documentation for your project using Hugo's documentation engine, including syntax highlighting for various programming languages."
cmd.RunE = nil
return nil

View file

@ -27,7 +27,6 @@ import (
"sync/atomic"
"time"
"github.com/bep/logg"
"github.com/bep/simplecobra"
"github.com/fsnotify/fsnotify"
"github.com/gohugoio/hugo/common/herrors"
@ -63,7 +62,7 @@ type hugoBuilder struct {
// Currently only set when in "fast render mode".
changeDetector *fileChangeDetector
visitedURLs *types.EvictingStringQueue
visitedURLs *types.EvictingQueue[string]
fullRebuildSem *semaphore.Weighted
debounce func(f func())
@ -136,10 +135,6 @@ func (e *hugoBuilderErrState) wasErr() bool {
return e.waserr
}
func (c *hugoBuilder) errCount() int {
return c.r.logger.LoggCount(logg.LevelError) + loggers.Log().LoggCount(logg.LevelError)
}
// getDirList provides NewWatcher() with a list of directories to watch for changes.
func (c *hugoBuilder) getDirList() ([]string, error) {
h, err := c.hugo()
@ -345,7 +340,6 @@ func (c *hugoBuilder) newWatcher(pollIntervalStr string, dirList ...string) (*wa
for {
select {
case changes := <-c.r.changesFromBuild:
c.errState.setBuildErr(nil)
unlock, err := h.LockBuild()
if err != nil {
c.r.logger.Errorln("Failed to acquire a build lock: %s", err)
@ -358,7 +352,7 @@ func (c *hugoBuilder) newWatcher(pollIntervalStr string, dirList ...string) (*wa
}
if c.s != nil && c.s.doLiveReload {
doReload := c.changeDetector == nil || len(c.changeDetector.changed()) > 0
doReload = doReload || c.showErrorInBrowser && c.errCount() > 0
doReload = doReload || c.showErrorInBrowser && c.errState.buildErr() != nil
if doReload {
livereload.ForceRefresh()
}
@ -372,7 +366,7 @@ func (c *hugoBuilder) newWatcher(pollIntervalStr string, dirList ...string) (*wa
return
}
c.handleEvents(watcher, staticSyncer, evs, configSet)
if c.showErrorInBrowser && c.errCount() > 0 {
if c.showErrorInBrowser && c.errState.buildErr() != nil {
// Need to reload browser to show the error
livereload.ForceRefresh()
}
@ -419,11 +413,17 @@ func (c *hugoBuilder) build() error {
}
func (c *hugoBuilder) buildSites(noBuildLock bool) (err error) {
h, err := c.hugo()
defer func() {
c.errState.setBuildErr(err)
}()
var h *hugolib.HugoSites
h, err = c.hugo()
if err != nil {
return err
return
}
return h.Build(hugolib.BuildCfg{NoBuildLock: noBuildLock})
err = h.Build(hugolib.BuildCfg{NoBuildLock: noBuildLock})
return
}
func (c *hugoBuilder) copyStatic() (map[string]uint64, error) {
@ -619,6 +619,9 @@ func (c *hugoBuilder) fullRebuild(changeType string) {
// Set the processing on pause until the state is recovered.
c.errState.setPaused(true)
c.handleBuildErr(err, "Failed to reload config")
if c.s.doLiveReload {
livereload.ForceRefresh()
}
} else {
c.errState.setPaused(false)
}
@ -660,7 +663,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
}
@ -779,6 +795,7 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
istemp := strings.HasSuffix(ext, "~") ||
(ext == ".swp") || // vim
(ext == ".swx") || // vim
(ext == ".bck") || // helix
(ext == ".tmp") || // generic temp file
(ext == ".DS_Store") || // OSX Thumbnail
baseName == "4913" || // vim
@ -801,21 +818,6 @@ 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 {
if f.IsDir() {
c.r.logger.Println("adding created directory to watchlist", path)
@ -916,7 +918,11 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
changed := c.changeDetector.changed()
if c.changeDetector != nil {
lrl.Logf("build changed %d files", len(changed))
if len(changed) >= 10 {
lrl.Logf("build changed %d files", len(changed))
} else {
lrl.Logf("build changed %d files: %q", len(changed), changed)
}
if len(changed) == 0 {
// Nothing has changed.
return
@ -962,10 +968,13 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
pathToRefresh := h.PathSpec.RelURL(paths.ToSlashTrimLeading(otherChanges[0]), false)
lrl.Logf("refreshing %q", pathToRefresh)
livereload.RefreshPath(pathToRefresh)
} else if len(cssChanges) == 0 {
} else if len(cssChanges) == 0 || len(otherChanges) > 1 {
lrl.Logf("force refresh")
livereload.ForceRefresh()
}
} else {
lrl.Logf("force refresh")
livereload.ForceRefresh()
}
if len(cssChanges) > 0 {
@ -1046,7 +1055,7 @@ func (c *hugoBuilder) loadConfig(cd *simplecobra.Commandeer, running bool) error
"fastRenderMode": c.fastRenderMode,
})
conf, err := c.r.ConfigFromProvider(c.r.configVersionID.Load(), flagsToCfg(cd, cfg))
conf, err := c.r.ConfigFromProvider(configKey{counter: c.r.configVersionID.Load()}, flagsToCfg(cd, cfg))
if err != nil {
return err
}
@ -1080,42 +1089,49 @@ func (c *hugoBuilder) printChangeDetected(typ string) {
c.r.logger.Println(htime.Now().Format(layout))
}
func (c *hugoBuilder) rebuildSites(events []fsnotify.Event) error {
func (c *hugoBuilder) rebuildSites(events []fsnotify.Event) (err error) {
defer func() {
c.errState.setBuildErr(err)
}()
if err := c.errState.buildErr(); err != nil {
ferrs := herrors.UnwrapFileErrorsWithErrorContext(err)
for _, err := range ferrs {
events = append(events, fsnotify.Event{Name: err.Position().Filename, Op: fsnotify.Write})
}
}
c.errState.setBuildErr(nil)
h, err := c.hugo()
var h *hugolib.HugoSites
h, err = c.hugo()
if err != nil {
return err
return
}
return h.Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyVisited: c.visitedURLs, ErrRecovery: c.errState.wasErr()}, events...)
err = h.Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyTouched: c.visitedURLs, ErrRecovery: c.errState.wasErr()}, events...)
return
}
func (c *hugoBuilder) rebuildSitesForChanges(ids []identity.Identity) error {
c.errState.setBuildErr(nil)
h, err := c.hugo()
func (c *hugoBuilder) rebuildSitesForChanges(ids []identity.Identity) (err error) {
defer func() {
c.errState.setBuildErr(err)
}()
var h *hugolib.HugoSites
h, err = c.hugo()
if err != nil {
return err
return
}
whatChanged := &hugolib.WhatChanged{}
whatChanged.Add(ids...)
err = h.Build(hugolib.BuildCfg{NoBuildLock: true, WhatChanged: whatChanged, RecentlyVisited: c.visitedURLs, ErrRecovery: c.errState.wasErr()})
c.errState.setBuildErr(err)
return err
err = h.Build(hugolib.BuildCfg{NoBuildLock: true, WhatChanged: whatChanged, RecentlyTouched: c.visitedURLs, ErrRecovery: c.errState.wasErr()})
return
}
func (c *hugoBuilder) reloadConfig() error {
c.r.Reset()
c.r.resetLogs()
c.r.configVersionID.Add(1)
if err := c.withConfE(func(conf *commonConfig) error {
oldConf := conf
newConf, err := c.r.ConfigFromConfig(c.r.configVersionID.Load(), conf)
newConf, err := c.r.ConfigFromConfig(configKey{counter: c.r.configVersionID.Load()}, conf)
if err != nil {
return err
}

View file

@ -90,8 +90,8 @@ func (c *importCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, arg
func (c *importCommand) Init(cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
cmd.Short = "Import your site from others."
cmd.Long = `Import your site from other web site generators like Jekyll.
cmd.Short = "Import a site from another system"
cmd.Long = `Import a site from another system.
Import requires a subcommand, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`."

View file

@ -57,7 +57,7 @@ func newListCommand() *listCommand {
return err
}
writer := csv.NewWriter(r.Out)
writer := csv.NewWriter(r.StdOut)
defer writer.Flush()
writer.Write([]string{
@ -199,8 +199,8 @@ func (c *listCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args
func (c *listCommand) Init(cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
cmd.Short = "Listing out various types of content"
cmd.Long = `Listing out various types of content.
cmd.Short = "List content"
cmd.Long = `List content.
List requires a subcommand, e.g. hugo list drafts`

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.
`,
@ -94,7 +94,7 @@ so this may/will change in future versions of Hugo.
applyLocalFlagsBuildConfig(cmd, r)
},
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
h, err := r.Hugo(flagsToCfg(cd, nil))
h, err := r.getOrCreateHugo(flagsToCfg(cd, nil), true)
if err != nil {
return err
}
@ -102,12 +102,16 @@ so this may/will change in future versions of Hugo.
if len(args) >= 1 {
initPath = args[0]
}
return h.Configs.ModulesClient.Init(initPath)
c := h.Configs.ModulesClient
if err := c.Init(initPath); err != nil {
return err
}
return nil
},
},
&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
@ -115,7 +119,7 @@ so this may/will change in future versions of Hugo.
cmd.Flags().BoolVarP(&clean, "clean", "", false, "delete module cache for dependencies that fail verification")
},
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, nil))
conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, nil))
if err != nil {
return err
}
@ -125,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.
`,
@ -135,7 +139,7 @@ Note that for vendored modules, that is the version listed and not the one from
cmd.Flags().BoolVarP(&clean, "clean", "", false, "delete module cache for dependencies that fail verification")
},
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, nil))
conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, nil))
if err != nil {
return err
}
@ -145,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
@ -171,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)
@ -186,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.
`,
@ -205,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
@ -271,7 +275,7 @@ Run "go help get" for more information. All flags available for "go get" is also
cfg := config.New()
cfg.Set("workingDir", dir)
conf, err := r.ConfigFromProvider(r.configVersionID.Add(1), flagsToCfg(cd, cfg))
conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Add(1)}, flagsToCfg(cd, cfg))
if err != nil {
return err
}
@ -284,7 +288,7 @@ Run "go help get" for more information. All flags available for "go get" is also
})
return nil
} else {
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, nil))
conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, nil))
if err != nil {
return err
}
@ -313,7 +317,7 @@ func (c *modCommands) Name() string {
}
func (c *modCommands) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
_, err := c.r.ConfigFromProvider(c.r.configVersionID.Load(), nil)
_, err := c.r.ConfigFromProvider(configKey{counter: c.r.configVersionID.Load()}, nil)
if err != nil {
return err
}
@ -324,7 +328,7 @@ func (c *modCommands) Run(ctx context.Context, cd *simplecobra.Commandeer, args
func (c *modCommands) Init(cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
cmd.Short = "Various Hugo Modules helpers."
cmd.Short = "Manage modules"
cmd.Long = `Various helpers to help manage the modules in your project's dependency graph.
Most operations here requires a Go version installed on your system (>= Go 1.12) and the relevant VCS client (typically Git).
This is not needed if you only operate on modules inside /themes or if you have vendored them via "hugo mod vendor".

View file

@ -40,7 +40,7 @@ func newNewCommand() *newCommand {
&simpleCommand{
name: "content",
use: "content [path]",
short: "Create new content for your site",
short: "Create new content",
long: `Create a new content file and automatically set the date and title.
It will guess which kind of file to create based on the path provided.
@ -76,10 +76,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")
@ -93,7 +91,7 @@ Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
cfg.Set("workingDir", createpath)
cfg.Set("publishDir", "public")
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, cfg))
conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, cfg))
if err != nil {
return err
}
@ -124,11 +122,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")
@ -136,7 +132,7 @@ according to your needs.`,
cfg := config.New()
cfg.Set("publishDir", "public")
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, cfg))
conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, cfg))
if err != nil {
return err
}
@ -144,7 +140,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 +148,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))
},
},
},
@ -181,7 +184,7 @@ func (c *newCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args [
func (c *newCommand) Init(cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
cmd.Short = "Create new content for your site"
cmd.Short = "Create new content"
cmd.Long = `Create a new content file and automatically set the date and title.
It will guess which kind of file to create based on the path provided.

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 {

View file

@ -23,6 +23,7 @@ import (
"errors"
"fmt"
"io"
"maps"
"net"
"net/http"
_ "net/http/pprof"
@ -32,6 +33,7 @@ import (
"path"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"sync"
@ -40,12 +42,14 @@ import (
"time"
"github.com/bep/mclib"
"github.com/pkg/browser"
"github.com/bep/debounce"
"github.com/bep/simplecobra"
"github.com/fsnotify/fsnotify"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/tpl/tplimpl"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/common/urls"
@ -55,7 +59,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"
@ -82,10 +85,14 @@ 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.EvictingStringQueue
var visitedURLs *types.EvictingQueue[string]
if s != nil && !s.disableFastRender {
visitedURLs = types.NewEvictingStringQueue(20)
visitedURLs = types.NewEvictingQueue[string](20)
}
return &hugoBuilder{
r: r,
@ -113,7 +120,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 {
@ -189,9 +196,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)
}
@ -209,16 +214,17 @@ func (f *fileChangeDetector) changed() []string {
}
}
return f.filterIrrelevant(c)
return f.filterIrrelevantAndSort(c)
}
func (f *fileChangeDetector) filterIrrelevant(in []string) []string {
func (f *fileChangeDetector) filterIrrelevantAndSort(in []string) []string {
var filtered []string
for _, v := range in {
if !f.irrelevantRe.MatchString(v) {
filtered = append(filtered, v)
}
}
sort.Strings(filtered)
return filtered
}
@ -304,64 +310,65 @@ 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 {
if strings.HasSuffix(requestURI, "/") || strings.HasSuffix(requestURI, "html") || strings.HasSuffix(requestURI, "htm") {
if isNavigation(requestURI, r) {
if !f.c.visitedURLs.Contains(requestURI) {
// If not already on stack, re-render that single page.
if err := f.c.partialReRender(requestURI); err != nil {
@ -448,6 +455,7 @@ type serverCommand struct {
// Flags.
renderStaticToDisk bool
navigateToChanged bool
openBrowser bool
serverAppend bool
serverInterface string
tlsCertFile string
@ -508,7 +516,7 @@ func (c *serverCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, arg
func (c *serverCommand) Init(cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
cmd.Short = "A high performance webserver"
cmd.Short = "Start the embedded web server"
cmd.Long = `Hugo provides its own webserver which builds and serves the site.
While hugo server is high performance, it is a webserver with limited options.
@ -539,6 +547,7 @@ of a second, you will be able to save and see your changes nearly instantly.`
cmd.Flags().BoolVarP(&c.serverAppend, "appendPort", "", true, "append port to baseURL")
cmd.Flags().BoolVar(&c.disableLiveReload, "disableLiveReload", false, "watch without enabling live browser reload on rebuild")
cmd.Flags().BoolVarP(&c.navigateToChanged, "navigateToChanged", "N", false, "navigate to changed content file on live browser reload")
cmd.Flags().BoolVarP(&c.openBrowser, "openBrowser", "O", false, "open the site in a browser after server startup")
cmd.Flags().BoolVar(&c.renderStaticToDisk, "renderStaticToDisk", false, "serve static files from disk and dynamic files from memory")
cmd.Flags().BoolVar(&c.disableFastRender, "disableFastRender", false, "enables full re-renders on changes")
cmd.Flags().BoolVar(&c.disableBrowserError, "disableBrowserError", false, "do not show build errors in the browser")
@ -618,7 +627,7 @@ func (c *serverCommand) setServerInfoInConfig() error {
panic("no server ports set")
}
return c.withConfE(func(conf *commonConfig) error {
for i, language := range conf.configs.Languages {
for i, language := range conf.configs.LanguagesDefaultFirst {
isMultihost := conf.configs.IsMultihost
var serverPort int
if isMultihost {
@ -648,9 +657,8 @@ func (c *serverCommand) setServerInfoInConfig() error {
}
func (c *serverCommand) getErrorWithContext() any {
errCount := c.errCount()
if errCount == 0 {
buildErr := c.errState.buildErr()
if buildErr == nil {
return nil
}
@ -659,7 +667,7 @@ func (c *serverCommand) getErrorWithContext() any {
m["Error"] = cleanErrorLog(c.r.logger.Errors())
m["Version"] = hugo.BuildVersionString()
ferrors := herrors.UnwrapFileErrorsWithErrorContext(c.errState.buildErr())
ferrors := herrors.UnwrapFileErrorsWithErrorContext(buildErr)
m["Files"] = ferrors
return m
@ -750,7 +758,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}
@ -830,22 +838,25 @@ func (c *serverCommand) fixURL(baseURLFromConfig, baseURLFromFlag string, port i
return u.String(), nil
}
func (c *serverCommand) partialReRender(urls ...string) error {
func (c *serverCommand) partialReRender(urls ...string) (err error) {
defer func() {
c.errState.setWasErr(false)
}()
c.errState.setBuildErr(nil)
visited := types.NewEvictingStringQueue(len(urls))
visited := types.NewEvictingQueue[string](len(urls))
for _, url := range urls {
visited.Add(url)
}
h, err := c.hugo()
var h *hugolib.HugoSites
h, err = c.hugo()
if err != nil {
return err
return
}
// Note: We do not set NoBuildLock as the file lock is not acquired at this stage.
return h.Build(hugolib.BuildCfg{NoBuildLock: false, RecentlyVisited: visited, PartialReRender: true, ErrRecovery: c.errState.wasErr()})
err = h.Build(hugolib.BuildCfg{NoBuildLock: false, RecentlyTouched: visited, PartialReRender: true, ErrRecovery: c.errState.wasErr()})
return
}
func (c *serverCommand) serve() error {
@ -886,16 +897,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
@ -996,6 +1007,13 @@ func (c *serverCommand) serve() error {
c.r.Println("Press Ctrl+C to stop")
if c.openBrowser {
// There may be more than one baseURL in multihost mode, open the first.
if err := browser.OpenURL(baseURLs[0].String()); err != nil {
c.r.logger.Warnf("Failed to open browser: %s", err)
}
}
err = func() error {
for {
select {
@ -1012,10 +1030,6 @@ func (c *serverCommand) serve() error {
c.r.Println("Error:", err)
}
if h := c.hugoTry(); h != nil {
h.Close()
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
wg2, ctx := errgroup.WithContext(ctx)
@ -1220,3 +1234,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, ".")
}

View file

@ -117,7 +117,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 +128,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())
}

View file

@ -15,6 +15,7 @@ package collections
import (
"html/template"
"reflect"
"testing"
qt "github.com/frankban/quicktest"
@ -77,6 +78,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...)
@ -146,3 +148,66 @@ func TestAppendShouldMakeACopyOfTheInputSlice(t *testing.T) {
c.Assert(result, qt.DeepEquals, []string{"a", "b", "c"})
c.Assert(slice, qt.DeepEquals, []string{"d", "b"})
}
func TestIndirect(t *testing.T) {
t.Parallel()
c := qt.New(t)
type testStruct struct {
Field string
}
var (
nilPtr *testStruct
nilIface interface{} = nil
nonNilIface interface{} = &testStruct{Field: "hello"}
)
tests := []struct {
name string
input any
wantKind reflect.Kind
wantNil bool
}{
{
name: "nil pointer",
input: nilPtr,
wantKind: reflect.Ptr,
wantNil: true,
},
{
name: "nil interface",
input: nilIface,
wantKind: reflect.Invalid,
wantNil: false,
},
{
name: "non-nil pointer to struct",
input: &testStruct{Field: "abc"},
wantKind: reflect.Struct,
wantNil: false,
},
{
name: "non-nil interface holding pointer",
input: nonNilIface,
wantKind: reflect.Struct,
wantNil: false,
},
{
name: "plain value",
input: testStruct{Field: "xyz"},
wantKind: reflect.Struct,
wantNil: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
v := reflect.ValueOf(tt.input)
got, isNil := indirect(v)
c.Assert(got.Kind(), qt.Equals, tt.wantKind)
c.Assert(isNil, qt.Equals, tt.wantNil)
})
}
}

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)
})
}
}

View file

@ -13,6 +13,8 @@
package collections
import "slices"
import "sync"
// Stack is a simple LIFO stack that is safe for concurrent use.
@ -73,7 +75,7 @@ func (s *Stack[T]) DrainMatching(predicate func(T) bool) []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

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 := NewStack[int]()
c.Assert(s, qt.IsNotNil)
}
func TestStackBasic(t *testing.T) {
t.Parallel()
c := qt.New(t)
s := NewStack[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 := NewStack[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 := NewStack[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})
}

View file

@ -21,6 +21,10 @@ const (
ErrRemoteGetCSV = "error-remote-getcsv"
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.
@ -39,7 +43,7 @@ const (
ResourceTransformationFingerprint = "fingerprint"
)
// IsResourceTransformationLinkChange returns whether the given name is a resource transformation that changes the permalink based on the content.
// IsResourceTransformationPermalinkHash returns whether the given name is a resource transformation that changes the permalink based on the content.
func IsResourceTransformationPermalinkHash(name string) bool {
return name == ResourceTransformationFingerprint
}

View file

@ -38,6 +38,19 @@ func XXHashFromReader(r io.Reader) (uint64, int64, error) {
return h.Sum64(), size, 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) {
h := getXxHashReadFrom()
defer putXxHashReadFrom(h)
_, err := io.Copy(h, r)
if err != nil {
return "", err
}
hash := h.Sum(nil)
return hex.EncodeToString(hash), nil
}
// XXHashFromString calculates the xxHash for the given string.
func XXHashFromString(s string) (uint64, error) {
h := xxhash.New()
@ -70,6 +83,13 @@ func HashString(vs ...any) string {
return strconv.FormatUint(hash, 10)
}
// HashStringHex returns a hash from the given elements as a hex encoded string.
// See HashString for more information.
func HashStringHex(vs ...any) string {
hash := HashUint64(vs...)
return strconv.FormatUint(hash, 16)
}
var hashOptsPool = sync.Pool{
New: func() any {
return &hashstructure.HashOptions{
@ -103,16 +123,24 @@ func HashUint64(vs ...any) uint64 {
o = elements
}
hashOpts := getHashOpts()
defer putHashOpts(hashOpts)
hash, err := hashstructure.Hash(o, hashOpts)
hash, err := Hash(o)
if err != nil {
panic(err)
}
return hash
}
// Hash returns a hash from vs.
func Hash(vs ...any) (uint64, error) {
hashOpts := getHashOpts()
defer putHashOpts(hashOpts)
var v any = vs
if len(vs) == 1 {
v = vs[0]
}
return hashstructure.Hash(v, hashOpts)
}
type keyer interface {
Key() string
}

View file

@ -37,12 +37,12 @@ func TestXxHashFromReaderPara(t *testing.T) {
c := qt.New(t)
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
for i := range 10 {
i := i
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)
@ -142,3 +142,16 @@ func BenchmarkHashString(b *testing.B) {
})
}
}
func BenchmarkHashMap(b *testing.B) {
m := map[string]any{}
for i := range 1000 {
m[fmt.Sprintf("key%d", i)] = i
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
HashString(m)
}
}

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]

View file

@ -133,11 +133,26 @@ func IsNotExist(err error) bool {
return false
}
// IsExist returns true if the error is a file exists error.
// Unlike os.IsExist, this also considers wrapped errors.
func IsExist(err error) bool {
if os.IsExist(err) {
return true
}
// os.IsExist does not consider wrapped errors.
if os.IsExist(errors.Unwrap(err)) {
return true
}
return false
}
var nilPointerErrRe = regexp.MustCompile(`at <(.*)>: error calling (.*?): runtime error: invalid memory address or nil pointer dereference`)
const deferredPrefix = "__hdeferred/"
var deferredStringToRemove = regexp.MustCompile(`executing "__hdeferred/.*" `)
var deferredStringToRemove = regexp.MustCompile(`executing "__hdeferred/.*?" `)
// ImproveRenderErr improves the error message for rendering errors.
func ImproveRenderErr(inErr error) (outErr error) {

View file

@ -20,8 +20,6 @@ import (
"io"
"path/filepath"
godartsassv1 "github.com/bep/godartsass"
"github.com/bep/godartsass/v2"
"github.com/bep/golibsass/libsass/libsasserrors"
"github.com/gohugoio/hugo/common/paths"
@ -153,8 +151,6 @@ func (e *fileError) causeString() string {
// Avoid repeating the file info in the error message.
case godartsass.SassError:
return v.Message
case godartsassv1.SassError:
return v.Message
case libsasserrors.Error:
return v.Message
default:
@ -262,8 +258,27 @@ func openFile(filename string, fs afero.Fs) (afero.File, string, error) {
return f, realFilename, nil
}
// Cause returns the underlying error or itself if it does not implement Unwrap.
// Cause returns the underlying error, that is,
// it unwraps errors until it finds one that does not implement
// the Unwrap method.
// For a shallow variant, see Unwrap.
func Cause(err error) error {
type unwrapper interface {
Unwrap() error
}
for err != nil {
cause, ok := err.(unwrapper)
if !ok {
break
}
err = cause.Unwrap()
}
return err
}
// Unwrap returns the underlying error or itself if it does not implement Unwrap.
func Unwrap(err error) error {
if u := errors.Unwrap(err); u != nil {
return u
}
@ -271,7 +286,7 @@ func Cause(err error) error {
}
func extractFileTypePos(err error) (string, text.Position) {
err = Cause(err)
err = Unwrap(err)
var fileType string
@ -388,14 +403,7 @@ func extractPosition(e error) (pos text.Position) {
case godartsass.SassError:
span := v.Span
start := span.Start
filename, _ := paths.UrlToFilename(span.Url)
pos.Filename = filename
pos.Offset = start.Offset
pos.ColumnNumber = start.Column
case godartsassv1.SassError:
span := v.Span
start := span.Start
filename, _ := paths.UrlToFilename(span.Url)
filename, _ := paths.UrlStringToFilename(span.Url)
pos.Filename = filename
pos.Offset = start.Offset
pos.ColumnNumber = start.Column

View file

@ -26,7 +26,9 @@ import (
"strings"
"sync"
"github.com/cli/safeexec"
"github.com/bep/logg"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/security"
)
@ -86,7 +88,7 @@ var WithEnviron = func(env []string) func(c *commandeer) {
}
// New creates a new Exec using the provided security config.
func New(cfg security.Config, workingDir string) *Exec {
func New(cfg security.Config, workingDir string, log loggers.Logger) *Exec {
var baseEnviron []string
for _, v := range os.Environ() {
k, _ := config.SplitEnvVar(v)
@ -96,9 +98,11 @@ func New(cfg security.Config, workingDir string) *Exec {
}
return &Exec{
sc: cfg,
workingDir: workingDir,
baseEnviron: baseEnviron,
sc: cfg,
workingDir: workingDir,
infol: log.InfoCommand("exec"),
baseEnviron: baseEnviron,
newNPXRunnerCache: maps.NewCache[string, func(arg ...any) (Runner, error)](),
}
}
@ -108,28 +112,18 @@ 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
workingDir string
infol logg.LevelLogger
// os.Environ filtered by the Exec.OsEnviron whitelist filter.
baseEnviron []string
npxInit sync.Once
npxAvailable bool
newNPXRunnerCache *maps.Cache[string, func(arg ...any) (Runner, error)]
npxInit sync.Once
npxAvailable bool
}
func (e *Exec) New(name string, arg ...any) (Runner, error) {
@ -155,25 +149,86 @@ func (e *Exec) new(name string, fullyQualifiedName string, arg ...any) (Runner,
return cm.command(arg...)
}
type binaryLocation int
func (b binaryLocation) String() string {
switch b {
case binaryLocationNodeModules:
return "node_modules/.bin"
case binaryLocationNpx:
return "npx"
case binaryLocationPath:
return "PATH"
}
return "unknown"
}
const (
binaryLocationNodeModules binaryLocation = iota + 1
binaryLocationNpx
binaryLocationPath
)
// Npx will in order:
// 1. Try fo find the binary in the WORKINGDIR/node_modules/.bin directory.
// 2. If not found, and npx is available, run npx --no-install <name> <args>.
// 3. Fall back to the PATH.
// If name is "tailwindcss", we will try the PATH as the second option.
func (e *Exec) Npx(name string, arg ...any) (Runner, error) {
// npx is slow, so first try the common case.
nodeBinFilename := filepath.Join(e.workingDir, nodeModulesBinPath, name)
_, err := safeexec.LookPath(nodeBinFilename)
if err == nil {
return e.new(name, nodeBinFilename, arg...)
if err := e.sc.CheckAllowedExec(name); err != nil {
return nil, err
}
e.checkNpx()
if e.npxAvailable {
r, err := e.npx(name, arg...)
if err == nil {
return r, nil
newRunner, err := e.newNPXRunnerCache.GetOrCreate(name, func() (func(...any) (Runner, error), error) {
type tryFunc func() func(...any) (Runner, error)
tryFuncs := map[binaryLocation]tryFunc{
binaryLocationNodeModules: func() func(...any) (Runner, error) {
nodeBinFilename := filepath.Join(e.workingDir, nodeModulesBinPath, name)
_, err := exec.LookPath(nodeBinFilename)
if err != nil {
return nil
}
return func(arg2 ...any) (Runner, error) {
return e.new(name, nodeBinFilename, arg2...)
}
},
binaryLocationNpx: func() func(...any) (Runner, error) {
e.checkNpx()
if !e.npxAvailable {
return nil
}
return func(arg2 ...any) (Runner, error) {
return e.npx(name, arg2...)
}
},
binaryLocationPath: func() func(...any) (Runner, error) {
if _, err := exec.LookPath(name); err != nil {
return nil
}
return func(arg2 ...any) (Runner, error) {
return e.New(name, arg2...)
}
},
}
locations := []binaryLocation{binaryLocationNodeModules, binaryLocationNpx, binaryLocationPath}
if name == "tailwindcss" {
// See https://github.com/gohugoio/hugo/issues/13221#issuecomment-2574801253
locations = []binaryLocation{binaryLocationNodeModules, binaryLocationPath, binaryLocationNpx}
}
for _, loc := range locations {
if f := tryFuncs[loc](); f != nil {
e.infol.Logf("resolve %q using %s", name, loc)
return f, nil
}
}
return nil, &NotFoundError{name: name, method: fmt.Sprintf("in %s", locations[len(locations)-1])}
})
if err != nil {
return nil, err
}
return e.New(name, arg...)
return newRunner(arg...)
}
const (
@ -278,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,
@ -316,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
}
@ -326,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 ""
}

View file

@ -74,6 +74,16 @@ 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()
// IsTruthfulValue returns whether the given value has a meaningful truth value.
@ -124,12 +134,7 @@ type methodKey struct {
name string
}
type methods struct {
sync.RWMutex
cache map[methodKey]int
}
var methodCache = &methods{cache: make(map[methodKey]int)}
var methodCache sync.Map
// GetMethodByName is the same as reflect.Value.MethodByName, but it caches the
// type lookup.
@ -147,22 +152,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 := methodCache.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
methodCache.Store(k, index)
if !ok {
return -1
@ -223,6 +222,27 @@ func AsTime(v reflect.Value, loc *time.Location) (time.Time, bool) {
return time.Time{}, false
}
// ToSliceAny converts the given value to a slice of any if possible.
func ToSliceAny(v any) ([]any, bool) {
if v == nil {
return nil, false
}
switch vv := v.(type) {
case []any:
return vv, true
default:
vvv := reflect.ValueOf(v)
if vvv.Kind() == reflect.Slice {
out := make([]any, vvv.Len())
for i := range vvv.Len() {
out[i] = vvv.Index(i).Interface()
}
return out, true
}
}
return nil, false
}
func CallMethodByName(cxt context.Context, name string, v reflect.Value) []reflect.Value {
fn := v.MethodByName(name)
var args []reflect.Value

View file

@ -50,6 +50,19 @@ func TestIsContextType(t *testing.T) {
c.Assert(IsContextType(reflect.TypeOf(valueCtx)), qt.IsTrue)
}
func TestToSliceAny(t *testing.T) {
c := qt.New(t)
checkOK := func(in any, expected []any) {
out, ok := ToSliceAny(in)
c.Assert(ok, qt.Equals, true)
c.Assert(out, qt.DeepEquals, expected)
}
checkOK([]any{1, 2, 3}, []any{1, 2, 3})
checkOK([]int{1, 2, 3}, []any{1, 2, 3})
}
func BenchmarkIsContextType(b *testing.B) {
type k string
b.Run("value", func(b *testing.B) {
@ -121,3 +134,17 @@ func BenchmarkGetMethodByName(b *testing.B) {
}
}
}
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)
}
}
})
}

View file

@ -16,6 +16,7 @@ package hstrings
import (
"fmt"
"regexp"
"slices"
"strings"
"sync"
@ -50,12 +51,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.
@ -103,12 +99,7 @@ func GetOrCompileRegexp(pattern string) (re *regexp.Regexp, err error) {
// 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 {
return true
}
}
return false
return slices.Contains(arr, el)
}
// InSlicEqualFold checks if a string is an element of a slice of strings
@ -137,7 +128,7 @@ func ToString(v any) (string, bool) {
return "", false
}
type Tuple struct {
First string
Second string
}
type (
Strings2 [2]string
Strings3 [3]string
)

View file

@ -46,18 +46,18 @@ 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.Fprintf(w, rndStr()+"abc __foobar"+rndStr())
fmt.Fprint(w, rndStr()+"abc __foobar"+rndStr())
c.Assert(h.Patterns[0].Match, qt.Equals, true)
h, w = neww()
fmt.Fprintf(w, rndStr()+"abc __f")
fmt.Fprintf(w, "oo bar"+rndStr())
fmt.Fprint(w, rndStr()+"abc __f")
fmt.Fprint(w, "oo bar"+rndStr())
c.Assert(h.Patterns[0].Match, qt.Equals, true)
h, w = neww()
fmt.Fprintf(w, rndStr()+"abc __moo bar")
fmt.Fprint(w, rndStr()+"abc __moo bar")
c.Assert(h.Patterns[0].Match, qt.Equals, false)
}

View file

@ -74,13 +74,13 @@ type StringReader interface {
ReadString() string
}
// NewReadSeekerNoOpCloserFromString uses strings.NewReader to create a new ReadSeekerNoOpCloser
// NewReadSeekerNoOpCloserFromBytes uses bytes.NewReader to create a new ReadSeekerNoOpCloser
// from the given bytes slice.
func NewReadSeekerNoOpCloserFromBytes(content []byte) readSeekerNopCloser {
return readSeekerNopCloser{bytes.NewReader(content)}
}
// NewReadSeekCloser creates a new ReadSeekCloser from the given ReadSeeker.
// NewOpenReadSeekCloser creates a new ReadSeekCloser from the given ReadSeeker.
// The ReadSeeker will be seeked to the beginning before returned.
func NewOpenReadSeekCloser(r ReadSeekCloser) OpenReadSeekCloser {
return func() (ReadSeekCloser, error) {

View file

@ -14,6 +14,7 @@
package hugo
import (
"context"
"fmt"
"html/template"
"os"
@ -24,13 +25,13 @@ import (
"sync"
"time"
godartsassv1 "github.com/bep/godartsass"
"github.com/bep/logg"
"github.com/mitchellh/mapstructure"
"github.com/bep/godartsass/v2"
"github.com/gohugoio/hugo/common/hcontext"
"github.com/gohugoio/hugo/common/hexec"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/spf13/afero"
@ -53,6 +54,8 @@ var (
vendorInfo string
)
var _ maps.StoreProvider = (*HugoInfo)(nil)
// HugoInfo contains information about the current Hugo environment
type HugoInfo struct {
CommitHash string
@ -69,6 +72,11 @@ type HugoInfo struct {
conf ConfigProvider
deps []*Dependency
store *maps.Scratch
// Context gives access to some of the context scoped variables.
Context Context
}
// Version returns the current version as a comparable version string.
@ -111,6 +119,10 @@ func (i HugoInfo) Deps() []*Dependency {
return i.deps
}
func (i HugoInfo) Store() *maps.Scratch {
return i.store
}
// Deprecated: Use hugo.IsMultihost instead.
func (i HugoInfo) IsMultiHost() bool {
Deprecate("hugo.IsMultiHost", "Use hugo.IsMultihost instead.", "v0.124.0")
@ -127,6 +139,30 @@ func (i HugoInfo) IsMultilingual() bool {
return i.conf.IsMultilingual()
}
type contextKey uint8
const (
contextKeyMarkupScope contextKey = iota
)
var markupScope = hcontext.NewContextDispatcher[string](contextKeyMarkupScope)
type Context struct{}
func (c Context) MarkupScope(ctx context.Context) string {
return GetMarkupScope(ctx)
}
// SetMarkupScope sets the markup scope in the context.
func SetMarkupScope(ctx context.Context, s string) context.Context {
return markupScope.Set(ctx, s)
}
// GetMarkupScope gets the markup scope from the context.
func GetMarkupScope(ctx context.Context) string {
return markupScope.Get(ctx)
}
// ConfigProvider represents the config options that are relevant for HugoInfo.
type ConfigProvider interface {
Environment() string
@ -160,6 +196,7 @@ func NewInfo(conf ConfigProvider, deps []*Dependency) HugoInfo {
Environment: conf.Environment(),
conf: conf,
deps: deps,
store: maps.NewScratch(),
GoVersion: goVersion,
}
}
@ -276,14 +313,14 @@ func GetDependencyListNonGo() []string {
if IsExtended {
deps = append(
deps,
formatDep("github.com/sass/libsass", "3.6.5"),
formatDep("github.com/sass/libsass", "3.6.6"),
formatDep("github.com/webmproject/libwebp", "v1.3.2"),
)
}
if dartSass := dartSassVersion(); dartSass.ProtocolVersion != "" {
dartSassPath := "github.com/sass/dart-sass-embedded"
if IsDartSassV2() {
if IsDartSassGeV2() {
dartSassPath = "github.com/sass/dart-sass"
}
deps = append(deps,
@ -330,22 +367,15 @@ type Dependency struct {
}
func dartSassVersion() godartsass.DartSassVersion {
if DartSassBinaryName == "" {
if DartSassBinaryName == "" || !IsDartSassGeV2() {
return godartsass.DartSassVersion{}
}
if IsDartSassV2() {
v, _ := godartsass.Version(DartSassBinaryName)
return v
}
v, _ := godartsassv1.Version(DartSassBinaryName)
var vv godartsass.DartSassVersion
mapstructure.WeakDecode(v, &vv)
return vv
v, _ := godartsass.Version(DartSassBinaryName)
return v
}
// DartSassBinaryName is the name of the Dart Sass binary to use.
// TODO(beop) find a better place for this.
// TODO(bep) find a better place for this.
var DartSassBinaryName string
func init() {
@ -370,7 +400,10 @@ var (
dartSassBinaryNamesV2 = []string{"dart-sass", "sass"}
)
func IsDartSassV2() bool {
// TODO(bep) we eventually want to remove this, but keep it for a while to throw an informative error.
// We stopped supporting the old binary in Hugo 0.139.0.
func IsDartSassGeV2() bool {
// dart-sass-embedded was the first version of the embedded Dart Sass before it was moved into the main project.
return !strings.Contains(DartSassBinaryName, "embedded")
}
@ -382,22 +415,39 @@ func IsDartSassV2() 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(msg)
log.WithLevel(level).WithField(loggers.FieldNameCmd, "deprecated").Logf("%s", msg)
}
// We ususally do about one minor version a month.
// 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 {
@ -405,11 +455,11 @@ func deprecationLogLevelFromVersion(ver string) logg.Level {
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

View file

@ -14,6 +14,7 @@
package hugo
import (
"context"
"fmt"
"testing"
@ -56,12 +57,29 @@ 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.
// Subtract 5 from the minor version of the first ERRORed version => 0.122.0.
c.Assert(deprecationLogLevelFromVersion("0.127.0"), qt.Equals, logg.LevelError)
}
func TestMarkupScope(t *testing.T) {
c := qt.New(t)
conf := testConfig{environment: "production", workingDir: "/mywork", running: false}
info := NewInfo(conf, nil)
ctx := context.Background()
ctx = SetMarkupScope(ctx, "foo")
c.Assert(info.Context.MarkupScope(ctx), qt.Equals, "foo")
}
type testConfig struct {

View file

@ -12,7 +12,6 @@
// limitations under the License.
//go:build extended
// +build extended
package hugo

View file

@ -12,7 +12,6 @@
// limitations under the License.
//go:build !extended
// +build !extended
package hugo

View file

@ -1,4 +1,4 @@
// Copyright 2020 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -11,4 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package js
//go:build withdeploy
package hugo
var IsWithdeploy = true

View file

@ -1,4 +1,4 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -11,4 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package tplimpl
//go:build !withdeploy
package hugo
var IsWithdeploy = false

View file

@ -152,6 +152,9 @@ func BuildVersionString() string {
if IsExtended {
version += "+extended"
}
if IsWithdeploy {
version += "+withdeploy"
}
osArch := bi.GoOS + "/" + bi.GoArch

View file

@ -17,7 +17,7 @@ package hugo
// This should be the only one.
var CurrentVersion = Version{
Major: 0,
Minor: 132,
PatchLevel: 2,
Suffix: "",
Minor: 148,
PatchLevel: 0,
Suffix: "-DEV",
}

View file

@ -18,18 +18,19 @@ package loggers
import (
"fmt"
"io"
"regexp"
"strings"
"sync"
"github.com/bep/logg"
)
// newNoColoursHandler creates a new NoColoursHandler
func newNoColoursHandler(outWriter, errWriter io.Writer, noLevelPrefix bool, predicate func(*logg.Entry) bool) *noColoursHandler {
// newNoAnsiEscapeHandler creates a new noAnsiEscapeHandler
func newNoAnsiEscapeHandler(outWriter, errWriter io.Writer, noLevelPrefix bool, predicate func(*logg.Entry) bool) *noAnsiEscapeHandler {
if predicate == nil {
predicate = func(e *logg.Entry) bool { return true }
}
return &noColoursHandler{
return &noAnsiEscapeHandler{
noLevelPrefix: noLevelPrefix,
outWriter: outWriter,
errWriter: errWriter,
@ -37,15 +38,15 @@ func newNoColoursHandler(outWriter, errWriter io.Writer, noLevelPrefix bool, pre
}
}
type noColoursHandler struct {
type noAnsiEscapeHandler struct {
mu sync.Mutex
outWriter io.Writer // Defaults to os.Stdout.
errWriter io.Writer // Defaults to os.Stderr.
outWriter io.Writer
errWriter io.Writer
predicate func(*logg.Entry) bool
noLevelPrefix bool
}
func (h *noColoursHandler) HandleLog(e *logg.Entry) error {
func (h *noAnsiEscapeHandler) HandleLog(e *logg.Entry) error {
if !h.predicate(e) {
return nil
}
@ -71,10 +72,12 @@ func (h *noColoursHandler) HandleLog(e *logg.Entry) error {
prefix = prefix + ": "
}
msg := stripANSI(e.Message)
if h.noLevelPrefix {
fmt.Fprintf(w, "%s%s", prefix, e.Message)
fmt.Fprintf(w, "%s%s", prefix, msg)
} else {
fmt.Fprintf(w, "%s %s%s", levelString[e.Level], prefix, e.Message)
fmt.Fprintf(w, "%s %s%s", levelString[e.Level], prefix, msg)
}
for _, field := range e.Fields {
@ -88,3 +91,10 @@ func (h *noColoursHandler) HandleLog(e *logg.Entry) error {
return nil
}
var ansiRe = regexp.MustCompile(`\x1b\[[0-9;]*m`)
// stripANSI removes ANSI escape codes from s.
func stripANSI(s string) string {
return ansiRe.ReplaceAllString(s, "")
}

View file

@ -0,0 +1,40 @@
// 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.
//
// 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 loggers
import (
"bytes"
"testing"
"github.com/bep/logg"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/terminal"
)
func TestNoAnsiEscapeHandler(t *testing.T) {
c := qt.New(t)
test := func(s string) {
c.Assert(stripANSI(terminal.Notice(s)), qt.Equals, s)
}
test(`error in "file.md:1:2"`)
var buf bytes.Buffer
h := newNoAnsiEscapeHandler(&buf, &buf, false, nil)
h.HandleLog(&logg.Entry{Message: terminal.Notice(`error in "file.md:1:2"`), Level: logg.LevelInfo})
c.Assert(buf.String(), qt.Equals, "INFO error in \"file.md:1:2\"\n")
}

View file

@ -38,8 +38,8 @@ var (
// Options defines options for the logger.
type Options struct {
Level logg.Level
Stdout io.Writer
Stderr io.Writer
StdOut io.Writer
StdErr io.Writer
DistinctLevel logg.Level
StoreErrors bool
HandlerPost func(e *logg.Entry) error
@ -48,21 +48,22 @@ type Options struct {
// New creates a new logger with the given options.
func New(opts Options) Logger {
if opts.Stdout == nil {
opts.Stdout = os.Stdout
if opts.StdOut == nil {
opts.StdOut = os.Stdout
}
if opts.Stderr == nil {
opts.Stderr = os.Stdout
if opts.StdErr == nil {
opts.StdErr = os.Stderr
}
if opts.Level == 0 {
opts.Level = logg.LevelWarn
}
var logHandler logg.Handler
if terminal.PrintANSIColors(os.Stdout) {
logHandler = newDefaultHandler(opts.Stdout, opts.Stderr)
if terminal.PrintANSIColors(os.Stderr) {
logHandler = newDefaultHandler(opts.StdErr, opts.StdErr)
} else {
logHandler = newNoColoursHandler(opts.Stdout, opts.Stderr, false, nil)
logHandler = newNoAnsiEscapeHandler(opts.StdErr, opts.StdErr, false, nil)
}
errorsw := &strings.Builder{}
@ -95,7 +96,7 @@ func New(opts Options) Logger {
}
if opts.StoreErrors {
h := newNoColoursHandler(io.Discard, errorsw, true, func(e *logg.Entry) bool {
h := newNoAnsiEscapeHandler(io.Discard, errorsw, true, func(e *logg.Entry) bool {
return e.Level >= logg.LevelError
})
@ -137,7 +138,8 @@ func New(opts Options) Logger {
logCounters: logCounters,
errors: errorsw,
reset: reset,
out: opts.Stdout,
stdOut: opts.StdOut,
stdErr: opts.StdErr,
level: opts.Level,
logger: logger,
tracel: l.WithLevel(logg.LevelTrace),
@ -153,8 +155,6 @@ func NewDefault() Logger {
opts := Options{
DistinctLevel: logg.LevelWarn,
Level: logg.LevelWarn,
Stdout: os.Stdout,
Stderr: os.Stdout,
}
return New(opts)
}
@ -163,8 +163,6 @@ func NewTrace() Logger {
opts := Options{
DistinctLevel: logg.LevelWarn,
Level: logg.LevelTrace,
Stdout: os.Stdout,
Stderr: os.Stdout,
}
return New(opts)
}
@ -189,7 +187,8 @@ type Logger interface {
Level() logg.Level
LoggCount(logg.Level) int
Logger() logg.Logger
Out() io.Writer
StdOut() io.Writer
StdErr() io.Writer
Printf(format string, v ...any)
Println(v ...any)
PrintTimerIfDelayed(start time.Time, name string)
@ -207,7 +206,8 @@ type logAdapter struct {
logCounters *logLevelCounter
errors *strings.Builder
reset func()
out io.Writer
stdOut io.Writer
stdErr io.Writer
level logg.Level
logger logg.Logger
tracel logg.LevelLogger
@ -259,8 +259,12 @@ func (l *logAdapter) Logger() logg.Logger {
return l.logger
}
func (l *logAdapter) Out() io.Writer {
return l.out
func (l *logAdapter) StdOut() io.Writer {
return l.stdOut
}
func (l *logAdapter) StdErr() io.Writer {
return l.stdErr
}
// PrintTimerIfDelayed prints a time statement to the FEEDBACK logger
@ -271,7 +275,7 @@ func (l *logAdapter) PrintTimerIfDelayed(start time.Time, name string) {
if milli < 500 {
return
}
l.Printf("%s in %v ms", name, milli)
fmt.Fprintf(l.stdErr, "%s in %v ms", name, milli)
}
func (l *logAdapter) Printf(format string, v ...any) {
@ -279,11 +283,11 @@ func (l *logAdapter) Printf(format string, v ...any) {
if !strings.HasSuffix(format, "\n") {
format += "\n"
}
fmt.Fprintf(l.out, format, v...)
fmt.Fprintf(l.stdOut, format, v...)
}
func (l *logAdapter) Println(v ...any) {
fmt.Fprintln(l.out, v...)
fmt.Fprintln(l.stdOut, v...)
}
func (l *logAdapter) Reset() {

View file

@ -31,13 +31,13 @@ func TestLogDistinct(t *testing.T) {
opts := loggers.Options{
DistinctLevel: logg.LevelWarn,
StoreErrors: true,
Stdout: io.Discard,
Stderr: io.Discard,
StdOut: io.Discard,
StdErr: io.Discard,
}
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")
@ -54,8 +54,8 @@ func TestHookLast(t *testing.T) {
HandlerPost: func(e *logg.Entry) error {
panic(e.Message)
},
Stdout: io.Discard,
Stderr: io.Discard,
StdOut: io.Discard,
StdErr: io.Discard,
}
l := loggers.New(opts)
@ -70,8 +70,8 @@ func TestOptionStoreErrors(t *testing.T) {
opts := loggers.Options{
StoreErrors: true,
Stderr: &sb,
Stdout: &sb,
StdErr: &sb,
StdOut: &sb,
}
l := loggers.New(opts)
@ -131,13 +131,13 @@ func TestReset(t *testing.T) {
opts := loggers.Options{
StoreErrors: true,
DistinctLevel: logg.LevelWarn,
Stdout: io.Discard,
Stderr: io.Discard,
StdOut: io.Discard,
StdErr: io.Discard,
}
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")

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)
}

View file

@ -13,11 +13,14 @@
package maps
import "sync"
import (
"sync"
)
// Cache is a simple thread safe cache backed by a map.
type Cache[K comparable, T any] struct {
m map[K]T
m map[K]T
hasBeenInitialized bool
sync.RWMutex
}
@ -34,11 +37,16 @@ func (c *Cache[K, T]) Get(key K) (T, bool) {
return zero, false
}
c.RLock()
v, found := c.m[key]
v, found := c.get(key)
c.RUnlock()
return v, found
}
func (c *Cache[K, T]) get(key K) (T, bool) {
v, found := c.m[key]
return v, found
}
// GetOrCreate gets the value for the given key if it exists, or creates it if not.
func (c *Cache[K, T]) GetOrCreate(key K, create func() (T, error)) (T, error) {
c.RLock()
@ -61,19 +69,77 @@ func (c *Cache[K, T]) GetOrCreate(key K, create func() (T, error)) (T, error) {
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) {
var v T
c.RLock()
if !c.hasBeenInitialized {
c.RUnlock()
if err := func() error {
c.Lock()
defer c.Unlock()
// Double check in case another goroutine has initialized it in the meantime.
if !c.hasBeenInitialized {
err := init(c.get, c.set)
if err != nil {
return err
}
c.hasBeenInitialized = true
}
return nil
}(); err != nil {
return v, err
}
// Reacquire the read lock.
c.RLock()
}
v = c.m[key]
c.RUnlock()
return v, nil
}
// Set sets the given key to the given value.
func (c *Cache[K, T]) Set(key K, value T) {
c.Lock()
c.m[key] = value
c.set(key, value)
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]) set(key K, value T) {
c.m[key] = value
}
// ForEeach calls the given function for each key/value pair in the cache.
func (c *Cache[K, T]) ForEeach(f func(K, T)) {
// If the function returns false, the iteration stops.
func (c *Cache[K, T]) ForEeach(f func(K, T) bool) {
c.RLock()
defer c.RUnlock()
for k, v := range c.m {
f(k, v)
if !f(k, v) {
return
}
}
}
@ -81,6 +147,7 @@ func (c *Cache[K, T]) Drain() map[K]T {
c.Lock()
m := c.m
c.m = make(map[K]T)
c.hasBeenInitialized = false
c.Unlock()
return m
}
@ -93,7 +160,8 @@ 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()
}

View file

@ -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)
}
})
}
}

144
common/maps/ordered.go Normal file
View file

@ -0,0 +1,144 @@
// 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 maps
import (
"slices"
"github.com/gohugoio/hugo/common/hashing"
)
// Ordered is a map that can be iterated in the order of insertion.
// Note that insertion order is not affected if a key is re-inserted into the map.
// In a nil map, all operations are no-ops.
// This is not thread safe.
type Ordered[K comparable, T any] struct {
// The keys in the order they were added.
keys []K
// The values.
values map[K]T
}
// NewOrdered creates a new Ordered map.
func NewOrdered[K comparable, T any]() *Ordered[K, T] {
return &Ordered[K, T]{values: make(map[K]T)}
}
// Set sets the value for the given key.
// Note that insertion order is not affected if a key is re-inserted into the map.
func (m *Ordered[K, T]) Set(key K, value T) {
if m == nil {
return
}
// Check if key already exists.
if _, found := m.values[key]; !found {
m.keys = append(m.keys, key)
}
m.values[key] = value
}
// Get gets the value for the given key.
func (m *Ordered[K, T]) Get(key K) (T, bool) {
if m == nil {
var v T
return v, false
}
value, found := m.values[key]
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 {
return
}
delete(m.values, key)
for i, k := range m.keys {
if k == key {
m.keys = slices.Delete(m.keys, i, i+1)
break
}
}
}
// Clone creates a shallow copy of the map.
func (m *Ordered[K, T]) Clone() *Ordered[K, T] {
if m == nil {
return nil
}
clone := NewOrdered[K, T]()
for _, k := range m.keys {
clone.Set(k, m.values[k])
}
return clone
}
// Keys returns the keys in the order they were added.
func (m *Ordered[K, T]) Keys() []K {
if m == nil {
return nil
}
return m.keys
}
// Values returns the values in the order they were added.
func (m *Ordered[K, T]) Values() []T {
if m == nil {
return nil
}
var values []T
for _, k := range m.keys {
values = append(values, m.values[k])
}
return values
}
// Len returns the number of items in the map.
func (m *Ordered[K, T]) Len() int {
if m == nil {
return 0
}
return len(m.keys)
}
// Range calls f sequentially for each key and value present in the map.
// If f returns false, range stops the iteration.
// TODO(bep) replace with iter.Seq2 when we bump go Go 1.24.
func (m *Ordered[K, T]) Range(f func(key K, value T) bool) {
if m == nil {
return
}
for _, k := range m.keys {
if !f(k, m.values[k]) {
return
}
}
}
// Hash calculates a hash from the values.
func (m *Ordered[K, T]) Hash() (uint64, error) {
if m == nil {
return 0, nil
}
return hashing.Hash(m.values)
}

View file

@ -0,0 +1,99 @@
// 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 maps
import (
"testing"
qt "github.com/frankban/quicktest"
)
func TestOrdered(t *testing.T) {
c := qt.New(t)
m := NewOrdered[string, int]()
m.Set("a", 1)
m.Set("b", 2)
m.Set("c", 3)
c.Assert(m.Keys(), qt.DeepEquals, []string{"a", "b", "c"})
c.Assert(m.Values(), qt.DeepEquals, []int{1, 2, 3})
v, found := m.Get("b")
c.Assert(found, qt.Equals, true)
c.Assert(v, qt.Equals, 2)
m.Set("b", 22)
c.Assert(m.Keys(), qt.DeepEquals, []string{"a", "b", "c"})
c.Assert(m.Values(), qt.DeepEquals, []int{1, 22, 3})
m.Delete("b")
c.Assert(m.Keys(), qt.DeepEquals, []string{"a", "c"})
c.Assert(m.Values(), qt.DeepEquals, []int{1, 3})
}
func TestOrderedHash(t *testing.T) {
c := qt.New(t)
m := NewOrdered[string, int]()
m.Set("a", 1)
m.Set("b", 2)
m.Set("c", 3)
h1, err := m.Hash()
c.Assert(err, qt.IsNil)
m.Set("d", 4)
h2, err := m.Hash()
c.Assert(err, qt.IsNil)
c.Assert(h1, qt.Not(qt.Equals), h2)
m = NewOrdered[string, int]()
m.Set("b", 2)
m.Set("a", 1)
m.Set("c", 3)
h3, err := m.Hash()
c.Assert(err, qt.IsNil)
// Order does not matter.
c.Assert(h1, qt.Equals, h3)
}
func TestOrderedNil(t *testing.T) {
c := qt.New(t)
var m *Ordered[string, int]
m.Set("a", 1)
c.Assert(m.Keys(), qt.IsNil)
c.Assert(m.Values(), qt.IsNil)
v, found := m.Get("a")
c.Assert(found, qt.Equals, false)
c.Assert(v, qt.Equals, 0)
m.Delete("a")
var b bool
m.Range(func(k string, v int) bool {
b = true
return true
})
c.Assert(b, qt.Equals, false)
c.Assert(m.Len(), qt.Equals, 0)
c.Assert(m.Clone(), qt.IsNil)
h, err := m.Hash()
c.Assert(err, qt.IsNil)
c.Assert(h, qt.Equals, uint64(0))
}

View file

@ -303,7 +303,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 +343,42 @@ func PrepareParams(m Params) {
}
}
}
// 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
}

View file

@ -22,31 +22,18 @@ import (
"github.com/gohugoio/hugo/common/math"
)
// Scratch is a writable context used for stateful operations in Page/Node rendering.
type StoreProvider interface {
// Store returns a Scratch that can be used to store temporary state.
// Store is not reset on server rebuilds.
Store() *Scratch
}
// Scratch is a writable context used for stateful build operations
type Scratch struct {
values map[string]any
mu sync.RWMutex
}
// Scratcher provides a scratching service.
type Scratcher interface {
// Scratch returns a "scratch pad" that can be used to store state.
Scratch() *Scratch
}
type scratcher struct {
s *Scratch
}
func (s scratcher) Scratch() *Scratch {
return s.s
}
// NewScratcher creates a new Scratcher.
func NewScratcher() Scratcher {
return scratcher{s: NewScratch()}
}
// Add will, for single values, add (using the + operator) the addend to the existing addend (if found).
// Supports numeric values and strings.
//

View file

@ -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)
@ -185,7 +185,7 @@ func TestScratchSetInMap(t *testing.T) {
scratch.SetInMap("key", "zyx", "Zyx")
scratch.SetInMap("key", "abc", "Abc (updated)")
scratch.SetInMap("key", "def", "Def")
c.Assert(scratch.GetSortedMapValues("key"), qt.DeepEquals, []any{0: "Abc (updated)", 1: "Def", 2: "Lux", 3: "Zyx"})
c.Assert(scratch.GetSortedMapValues("key"), qt.DeepEquals, any([]any{"Abc (updated)", "Def", "Lux", "Zyx"}))
}
func TestScratchDeleteInMap(t *testing.T) {
@ -199,7 +199,7 @@ func TestScratchDeleteInMap(t *testing.T) {
scratch.DeleteInMap("key", "abc")
scratch.SetInMap("key", "def", "Def")
scratch.DeleteInMap("key", "lmn") // Do nothing
c.Assert(scratch.GetSortedMapValues("key"), qt.DeepEquals, []any{0: "Def", 1: "Lux", 2: "Zyx"})
c.Assert(scratch.GetSortedMapValues("key"), qt.DeepEquals, any([]any{"Def", "Lux", "Zyx"}))
}
func TestScratchGetSortedMapValues(t *testing.T) {

View file

@ -26,29 +26,32 @@ func DoArithmetic(a, b any, op rune) (any, error) {
var ai, bi int64
var af, bf float64
var au, bu uint64
var isInt, isFloat, isUint bool
switch av.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
ai = av.Int()
switch bv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
isInt = true
bi = bv.Int()
case reflect.Float32, reflect.Float64:
isFloat = true
af = float64(ai) // may overflow
ai = 0
bf = bv.Float()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
bu = bv.Uint()
if ai >= 0 {
isUint = true
au = uint64(ai)
ai = 0
} else {
isInt = true
bi = int64(bu) // may overflow
bu = 0
}
default:
return nil, errors.New("can't apply the operator to the values")
}
case reflect.Float32, reflect.Float64:
isFloat = true
af = av.Float()
switch bv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
@ -66,17 +69,18 @@ func DoArithmetic(a, b any, op rune) (any, error) {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
bi = bv.Int()
if bi >= 0 {
isUint = true
bu = uint64(bi)
bi = 0
} else {
isInt = true
ai = int64(au) // may overflow
au = 0
}
case reflect.Float32, reflect.Float64:
isFloat = true
af = float64(au) // may overflow
au = 0
bf = bv.Float()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
isUint = true
bu = bv.Uint()
default:
return nil, errors.New("can't apply the operator to the values")
@ -94,38 +98,32 @@ func DoArithmetic(a, b any, op rune) (any, error) {
switch op {
case '+':
if ai != 0 || bi != 0 {
if isInt {
return ai + bi, nil
} else if af != 0 || bf != 0 {
} else if isFloat {
return af + bf, nil
} else if au != 0 || bu != 0 {
return au + bu, nil
}
return 0, nil
return au + bu, nil
case '-':
if ai != 0 || bi != 0 {
if isInt {
return ai - bi, nil
} else if af != 0 || bf != 0 {
} else if isFloat {
return af - bf, nil
} else if au != 0 || bu != 0 {
return au - bu, nil
}
return 0, nil
return au - bu, nil
case '*':
if ai != 0 || bi != 0 {
if isInt {
return ai * bi, nil
} else if af != 0 || bf != 0 {
} else if isFloat {
return af * bf, nil
} else if au != 0 || bu != 0 {
return au * bu, nil
}
return 0, nil
return au * bu, nil
case '/':
if bi != 0 {
if isInt && bi != 0 {
return ai / bi, nil
} else if bf != 0 {
} else if isFloat && bf != 0 {
return af / bf, nil
} else if bu != 0 {
} else if isUint && bu != 0 {
return au / bu, nil
}
return nil, errors.New("can't divide the value by 0")

View file

@ -30,10 +30,12 @@ func TestDoArithmetic(t *testing.T) {
expect any
}{
{3, 2, '+', int64(5)},
{0, 0, '+', int64(0)},
{3, 2, '-', int64(1)},
{3, 2, '*', int64(6)},
{3, 2, '/', int64(1)},
{3.0, 2, '+', float64(5)},
{0.0, 0, '+', float64(0.0)},
{3.0, 2, '-', float64(1)},
{3.0, 2, '*', float64(6)},
{3.0, 2, '/', float64(1.5)},
@ -42,18 +44,22 @@ func TestDoArithmetic(t *testing.T) {
{3, 2.0, '*', float64(6)},
{3, 2.0, '/', float64(1.5)},
{3.0, 2.0, '+', float64(5)},
{0.0, 0.0, '+', float64(0.0)},
{3.0, 2.0, '-', float64(1)},
{3.0, 2.0, '*', float64(6)},
{3.0, 2.0, '/', float64(1.5)},
{uint(3), uint(2), '+', uint64(5)},
{uint(0), uint(0), '+', uint64(0)},
{uint(3), uint(2), '-', uint64(1)},
{uint(3), uint(2), '*', uint64(6)},
{uint(3), uint(2), '/', uint64(1)},
{uint(3), 2, '+', uint64(5)},
{uint(0), 0, '+', uint64(0)},
{uint(3), 2, '-', uint64(1)},
{uint(3), 2, '*', uint64(6)},
{uint(3), 2, '/', uint64(1)},
{3, uint(2), '+', uint64(5)},
{0, uint(0), '+', uint64(0)},
{3, uint(2), '-', uint64(1)},
{3, uint(2), '*', uint64(6)},
{3, uint(2), '/', uint64(1)},
@ -66,16 +72,15 @@ func TestDoArithmetic(t *testing.T) {
{-3, uint(2), '*', int64(-6)},
{-3, uint(2), '/', int64(-1)},
{uint(3), 2.0, '+', float64(5)},
{uint(0), 0.0, '+', float64(0)},
{uint(3), 2.0, '-', float64(1)},
{uint(3), 2.0, '*', float64(6)},
{uint(3), 2.0, '/', float64(1.5)},
{3.0, uint(2), '+', float64(5)},
{0.0, uint(0), '+', float64(0)},
{3.0, uint(2), '-', float64(1)},
{3.0, uint(2), '*', float64(6)},
{3.0, uint(2), '/', float64(1.5)},
{0, 0, '+', 0},
{0, 0, '-', 0},
{0, 0, '*', 0},
{"foo", "bar", '+', "foobar"},
{3, 0, '/', false},
{3.0, 0, '/', false},

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,7 +51,7 @@ func TestPara(t *testing.T) {
var result []int
var mu sync.Mutex
for i := 0; i < n; i++ {
for i := range n {
i := i
r.Run(func() error {
mu.Lock()
@ -78,7 +78,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)

View file

@ -23,6 +23,11 @@ import (
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/resources/kinds"
)
const (
identifierBaseof = "baseof"
)
// PathParser parses a path into a Path.
@ -33,6 +38,10 @@ 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
}
@ -83,13 +92,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 +120,101 @@ 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 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 {
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 +237,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{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 +267,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 +321,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
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 +369,18 @@ type Path struct {
posContainerHigh int
posSectionHigh int
component string
bundleType PathType
component string
pathType Type
identifiers []types.LowHigh
identifiersKnown []types.LowHigh[string]
identifiersUnknown []types.LowHigh[string]
posIdentifierLanguage int
disabled bool
posIdentifierLanguage int
posIdentifierOutputFormat int
posIdentifierKind int
posIdentifierLayout int
posIdentifierBaseof int
disabled bool
trimLeadingSlash bool
@ -293,9 +410,13 @@ 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.disabled = false
p.trimLeadingSlash = false
p.unnormalized = nil
@ -316,6 +437,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 +456,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 +483,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 +503,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 +515,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 +529,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 +568,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
@ -436,6 +588,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()
@ -462,26 +636,42 @@ func (p *Path) Base() string {
return p.base(!p.isContentPage(), p.IsBundle())
}
// 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.
func (p *Path) BaseNoLeadingSlash() string {
return p.Base()[1:]
}
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 +683,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 +692,20 @@ 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) Identifier(i int) string {
@ -515,35 +717,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 +763,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

View file

@ -18,6 +18,7 @@ import (
"testing"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/resources/kinds"
qt "github.com/frankban/quicktest"
)
@ -26,10 +27,18 @@ var testParser = &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
},
}
func TestParse(t *testing.T) {
@ -105,17 +114,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 +141,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 +174,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 +187,11 @@ func TestParse(t *testing.T) {
"Home branch cundle",
"/_index.md",
func(c *qt.C, p *Path) {
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 +202,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 +227,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 +240,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")
},
@ -355,11 +376,225 @@ func TestParse(t *testing.T) {
}
for _, test := range tests {
c.Run(test.name, func(c *qt.C) {
if test.name != "Home branch cundle" {
// return
}
test.assert(c, testParser.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", "list", "mylayout"})
c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
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", "not", "amp", "no", "list"})
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")
},
},
}
for _, test := range tests {
c.Run(test.name, func(c *qt.C) {
if test.name != "Shortcode lang layout" {
// return
}
test.assert(c, testParser.Parse(files.ComponentFolderLayouts, test.path))
})
}
}
func TestHasExt(t *testing.T) {
c := qt.New(t)

View file

@ -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
}

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]]
}

View file

@ -0,0 +1,32 @@
// Code generated by "stringer -type Type"; 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[TypeFile-0]
_ = x[TypeContentResource-1]
_ = x[TypeContentSingle-2]
_ = x[TypeLeaf-3]
_ = x[TypeBranch-4]
_ = x[TypeContentData-5]
_ = x[TypeMarkup-6]
_ = x[TypeShortcode-7]
_ = x[TypePartial-8]
_ = x[TypeBaseof-9]
}
const _Type_name = "TypeFileTypeContentResourceTypeContentSingleTypeLeafTypeBranchTypeContentDataTypeMarkupTypeShortcodeTypePartialTypeBaseof"
var _Type_index = [...]uint8{0, 8, 27, 44, 52, 62, 77, 87, 100, 111, 121}
func (i Type) String() string {
if i < 0 || i >= Type(len(_Type_index)-1) {
return "Type(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Type_name[_Type_index[i]:_Type_index[i+1]]
}

View file

@ -1,4 +1,4 @@
// Copyright 2021 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -18,6 +18,7 @@ import (
"net/url"
"path"
"path/filepath"
"runtime"
"strings"
)
@ -159,31 +160,6 @@ func Uglify(in string) string {
return path.Clean(in)
}
// UrlToFilename converts the URL s to a filename.
// If ParseRequestURI fails, the input is just converted to OS specific slashes and returned.
func UrlToFilename(s string) (string, bool) {
u, err := url.ParseRequestURI(s)
if err != nil {
return filepath.FromSlash(s), false
}
p := u.Path
if p == "" {
p, _ = url.QueryUnescape(u.Opaque)
return filepath.FromSlash(p), true
}
p = filepath.FromSlash(p)
if u.Host != "" {
// C:\data\file.txt
p = strings.ToUpper(u.Host) + ":" + p
}
return p, true
}
// URLEscape escapes unicode letters.
func URLEscape(uri string) string {
// escape unicode letters
@ -193,3 +169,105 @@ func URLEscape(uri string) string {
}
return u.String()
}
// TrimExt trims the extension from a path..
func TrimExt(in string) string {
return strings.TrimSuffix(in, path.Ext(in))
}
// From https://github.com/golang/go/blob/e0c76d95abfc1621259864adb3d101cf6f1f90fc/src/cmd/go/internal/web/url.go#L45
func UrlFromFilename(filename string) (*url.URL, error) {
if !filepath.IsAbs(filename) {
return nil, fmt.Errorf("filepath must be absolute")
}
// If filename has a Windows volume name, convert the volume to a host and prefix
// per https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/.
if vol := filepath.VolumeName(filename); vol != "" {
if strings.HasPrefix(vol, `\\`) {
filename = filepath.ToSlash(filename[2:])
i := strings.IndexByte(filename, '/')
if i < 0 {
// A degenerate case.
// \\host.example.com (without a share name)
// becomes
// file://host.example.com/
return &url.URL{
Scheme: "file",
Host: filename,
Path: "/",
}, nil
}
// \\host.example.com\Share\path\to\file
// becomes
// file://host.example.com/Share/path/to/file
return &url.URL{
Scheme: "file",
Host: filename[:i],
Path: filepath.ToSlash(filename[i:]),
}, nil
}
// C:\path\to\file
// becomes
// file:///C:/path/to/file
return &url.URL{
Scheme: "file",
Path: "/" + filepath.ToSlash(filename),
}, nil
}
// /path/to/file
// becomes
// file:///path/to/file
return &url.URL{
Scheme: "file",
Path: filepath.ToSlash(filename),
}, nil
}
// UrlStringToFilename converts the URL s to a filename.
// If ParseRequestURI fails, the input is just converted to OS specific slashes and returned.
func UrlStringToFilename(s string) (string, bool) {
u, err := url.ParseRequestURI(s)
if err != nil {
return filepath.FromSlash(s), false
}
p := u.Path
if p == "" {
p, _ = url.QueryUnescape(u.Opaque)
return filepath.FromSlash(p), false
}
if runtime.GOOS != "windows" {
return p, true
}
if len(p) == 0 || p[0] != '/' {
return filepath.FromSlash(p), false
}
p = filepath.FromSlash(p)
if len(u.Host) == 1 {
// file://c/Users/...
return strings.ToUpper(u.Host) + ":" + p, true
}
if u.Host != "" && u.Host != "localhost" {
if filepath.VolumeName(u.Host) != "" {
return "", false
}
return `\\` + u.Host + p, true
}
if vol := filepath.VolumeName(p[1:]); vol == "" || strings.HasPrefix(vol, `\\`) {
return "", false
}
return p[1:], true
}

View file

@ -51,7 +51,7 @@ func Run[T any](ctx context.Context, cfg Config[T]) Group[T] {
// Buffered for performance.
ch := make(chan T, cfg.NumWorkers)
for i := 0; i < cfg.NumWorkers; i++ {
for range cfg.NumWorkers {
g.Go(func() error {
for {
select {

View file

@ -103,10 +103,7 @@ func (r *RunEvery) Add(name string, f Func) {
f.IntervalHigh = 20 * time.Second
}
start := f.IntervalHigh / 3
if start < f.IntervalLow {
start = f.IntervalLow
}
start := max(f.IntervalHigh/3, f.IntervalLow)
f.interval = start
f.last = time.Now()

View file

@ -17,7 +17,6 @@ package terminal
import (
"fmt"
"os"
"runtime"
"strings"
isatty "github.com/mattn/go-isatty"
@ -41,10 +40,6 @@ func PrintANSIColors(f *os.File) bool {
// IsTerminal return true if the file descriptor is terminal and the TERM
// environment variable isn't a dumb one.
func IsTerminal(f *os.File) bool {
if runtime.GOOS == "windows" {
return false
}
fd := f.Fd()
return os.Getenv("TERM") != "dumb" && (isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd))
}

View file

@ -19,6 +19,13 @@ type Closer interface {
Close() error
}
// CloserFunc is a convenience type to create a Closer from a function.
type CloserFunc func() error
func (f CloserFunc) Close() error {
return f()
}
type CloseAdder interface {
Add(Closer)
}

View file

@ -69,7 +69,7 @@ func ToStringSlicePreserveStringE(v any) ([]string, error) {
switch vv.Kind() {
case reflect.Slice, reflect.Array:
result = make([]string, vv.Len())
for i := 0; i < vv.Len(); i++ {
for i := range vv.Len() {
s, err := cast.ToStringE(vv.Index(i).Interface())
if err != nil {
return nil, err

View file

@ -15,27 +15,28 @@
package types
import (
"slices"
"sync"
)
// EvictingStringQueue is a queue which automatically evicts elements from the head of
// EvictingQueue is a queue which automatically evicts elements from the head of
// the queue when attempting to add new elements onto the queue and it is full.
// This queue orders elements LIFO (last-in-first-out). It throws away duplicates.
// Note: This queue currently does not contain any remove (poll etc.) methods.
type EvictingStringQueue struct {
type EvictingQueue[T comparable] struct {
size int
vals []string
set map[string]bool
vals []T
set map[T]bool
mu sync.Mutex
zero T
}
// NewEvictingStringQueue creates a new queue with the given size.
func NewEvictingStringQueue(size int) *EvictingStringQueue {
return &EvictingStringQueue{size: size, set: make(map[string]bool)}
// NewEvictingQueue creates a new queue with the given size.
func NewEvictingQueue[T comparable](size int) *EvictingQueue[T] {
return &EvictingQueue[T]{size: size, set: make(map[T]bool)}
}
// Add adds a new string to the tail of the queue if it's not already there.
func (q *EvictingStringQueue) Add(v string) *EvictingStringQueue {
func (q *EvictingQueue[T]) Add(v T) *EvictingQueue[T] {
q.mu.Lock()
if q.set[v] {
q.mu.Unlock()
@ -45,7 +46,7 @@ func (q *EvictingStringQueue) Add(v string) *EvictingStringQueue {
if len(q.set) == q.size {
// Full
delete(q.set, q.vals[0])
q.vals = append(q.vals[:0], q.vals[1:]...)
q.vals = slices.Delete(q.vals, 0, 1)
}
q.set[v] = true
q.vals = append(q.vals, v)
@ -54,7 +55,7 @@ func (q *EvictingStringQueue) Add(v string) *EvictingStringQueue {
return q
}
func (q *EvictingStringQueue) Len() int {
func (q *EvictingQueue[T]) Len() int {
if q == nil {
return 0
}
@ -64,19 +65,22 @@ func (q *EvictingStringQueue) Len() int {
}
// Contains returns whether the queue contains v.
func (q *EvictingStringQueue) Contains(v string) bool {
func (q *EvictingQueue[T]) Contains(v T) bool {
if q == nil {
return false
}
q.mu.Lock()
defer q.mu.Unlock()
return q.set[v]
}
// Peek looks at the last element added to the queue.
func (q *EvictingStringQueue) Peek() string {
func (q *EvictingQueue[T]) Peek() T {
q.mu.Lock()
l := len(q.vals)
if l == 0 {
q.mu.Unlock()
return ""
return q.zero
}
elem := q.vals[l-1]
q.mu.Unlock()
@ -84,9 +88,12 @@ func (q *EvictingStringQueue) Peek() string {
}
// PeekAll looks at all the elements in the queue, with the newest first.
func (q *EvictingStringQueue) PeekAll() []string {
func (q *EvictingQueue[T]) PeekAll() []T {
if q == nil {
return nil
}
q.mu.Lock()
vals := make([]string, len(q.vals))
vals := make([]T, len(q.vals))
copy(vals, q.vals)
q.mu.Unlock()
for i, j := 0, len(vals)-1; i < j; i, j = i+1, j-1 {
@ -96,9 +103,9 @@ func (q *EvictingStringQueue) PeekAll() []string {
}
// PeekAllSet returns PeekAll as a set.
func (q *EvictingStringQueue) PeekAllSet() map[string]bool {
func (q *EvictingQueue[T]) PeekAllSet() map[T]bool {
all := q.PeekAll()
set := make(map[string]bool)
set := make(map[T]bool)
for _, v := range all {
set[v] = true
}

View file

@ -23,7 +23,7 @@ import (
func TestEvictingStringQueue(t *testing.T) {
c := qt.New(t)
queue := NewEvictingStringQueue(3)
queue := NewEvictingQueue[string](3)
c.Assert(queue.Peek(), qt.Equals, "")
queue.Add("a")
@ -53,9 +53,9 @@ func TestEvictingStringQueueConcurrent(t *testing.T) {
var wg sync.WaitGroup
val := "someval"
queue := NewEvictingStringQueue(3)
queue := NewEvictingQueue[string](3)
for j := 0; j < 100; j++ {
for range 100 {
wg.Add(1)
go func() {
defer wg.Done()

View file

@ -13,8 +13,24 @@
package hstring
type RenderedString string
import (
"html/template"
func (s RenderedString) String() string {
"github.com/gohugoio/hugo/common/types"
)
var _ types.PrintableValueProvider = HTML("")
// HTML is a string that represents rendered HTML.
// When printed in templates it will be rendered as template.HTML and considered safe so no need to pipe it into `safeHTML`.
// This type was introduced as a wasy to prevent a common case of inifinite recursion in the template rendering
// when the `linkify` option is enabled with a common (wrong) construct like `{{ .Text | .Page.RenderString }}` in a hook template.
type HTML string
func (s HTML) String() string {
return string(s)
}
func (s HTML) PrintableValue() any {
return template.HTML(s)
}

View file

@ -25,6 +25,6 @@ func TestRenderedString(t *testing.T) {
c := qt.New(t)
// Validate that it will behave like a string in Hugo settings.
c.Assert(cast.ToString(RenderedString("Hugo")), qt.Equals, "Hugo")
c.Assert(template.HTML(RenderedString("Hugo")), qt.Equals, template.HTML("Hugo"))
c.Assert(cast.ToString(HTML("Hugo")), qt.Equals, "Hugo")
c.Assert(template.HTML(HTML("Hugo")), qt.Equals, template.HTML("Hugo"))
}

View file

@ -28,6 +28,16 @@ type RLocker interface {
RUnlock()
}
type Locker interface {
Lock()
Unlock()
}
type RWLocker interface {
RLocker
Locker
}
// KeyValue is a interface{} tuple.
type KeyValue struct {
Key any
@ -59,7 +69,7 @@ func (k KeyValues) String() string {
// KeyValues struct.
func NewKeyValuesStrings(key string, values ...string) KeyValues {
iv := make([]any, len(values))
for i := 0; i < len(values); i++ {
for i := range values {
iv[i] = values[i]
}
return KeyValues{Key: key, Values: iv}
@ -107,12 +117,20 @@ func Unwrapv(v any) any {
return v
}
// LowHigh is typically used to represent a slice boundary.
type LowHigh struct {
// LowHigh represents a byte or slice boundary.
type LowHigh[S ~[]byte | string] struct {
Low int
High int
}
func (l LowHigh[S]) IsZero() bool {
return l.Low < 0 || (l.Low == 0 && l.High == 0)
}
func (l LowHigh[S]) Value(source S) S {
return source[l.Low:l.High]
}
// This is only used for debugging purposes.
var InvocationCounter atomic.Int64
@ -125,22 +143,3 @@ func NewBool(b bool) *bool {
type PrintableValueProvider interface {
PrintableValue() any
}
var _ PrintableValueProvider = Result[any]{}
// Result is a generic result type.
type Result[T any] struct {
// The result value.
Value T
// The error value.
Err error
}
// PrintableValue returns the value or panics if there is an error.
func (r Result[T]) PrintableValue() any {
if r.Err != nil {
panic(r.Err)
}
return r.Value
}

View file

@ -27,3 +27,25 @@ func TestKeyValues(t *testing.T) {
c.Assert(kv.KeyString(), qt.Equals, "key")
c.Assert(kv.Values, qt.DeepEquals, []any{"a1", "a2"})
}
func TestLowHigh(t *testing.T) {
c := qt.New(t)
lh := LowHigh[string]{
Low: 2,
High: 10,
}
s := "abcdefghijklmnopqrstuvwxyz"
c.Assert(lh.IsZero(), qt.IsFalse)
c.Assert(lh.Value(s), qt.Equals, "cdefghij")
lhb := LowHigh[[]byte]{
Low: 2,
High: 10,
}
sb := []byte(s)
c.Assert(lhb.IsZero(), qt.IsFalse)
c.Assert(lhb.Value(sb), qt.DeepEquals, []byte("cdefghij"))
}

View file

@ -82,7 +82,7 @@ func init() {
}
configLanguageKeys = make(map[string]bool)
addKeys := func(v reflect.Value) {
for i := 0; i < v.NumField(); i++ {
for i := range v.NumField() {
name := strings.ToLower(v.Type().Field(i).Name)
if skip[name] {
continue
@ -128,6 +128,9 @@ type Config struct {
// <docsmeta>{"identifiers": ["markup"] }</docsmeta>
Markup markup_config.Config `mapstructure:"-"`
// ContentTypes are the media types that's considered content in Hugo.
ContentTypes *config.ConfigNamespace[map[string]media.ContentTypeConfig, media.ContentTypes] `mapstructure:"-"`
// The mediatypes configuration section maps the MIME type (a string) to a configuration object for that type.
// <docsmeta>{"identifiers": ["mediatypes"], "refs": ["types:media:type"] }</docsmeta>
MediaTypes *config.ConfigNamespace[map[string]media.MediaTypeConfig, media.Types] `mapstructure:"-"`
@ -143,7 +146,7 @@ type Config struct {
// The cascade configuration section contains the top level front matter cascade configuration options,
// a slice of page matcher and params to apply to those pages.
Cascade *config.ConfigNamespace[[]page.PageMatcherParamsConfig, map[page.PageMatcher]maps.Params] `mapstructure:"-"`
Cascade *config.ConfigNamespace[[]page.PageMatcherParamsConfig, *maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig]] `mapstructure:"-"`
// The segments defines segments for the site. Used for partial/segmented builds.
Segments *config.ConfigNamespace[map[string]segments.SegmentConfig, segments.Segments] `mapstructure:"-"`
@ -182,6 +185,9 @@ type Config struct {
// Pagination configuration.
Pagination config.Pagination `mapstructure:"-"`
// Page configuration.
Page config.PageConfig `mapstructure:"-"`
// Privacy configuration.
Privacy privacy.Config `mapstructure:"-"`
@ -298,6 +304,18 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
}
}
defaultOutputFormat := outputFormats[0]
c.DefaultOutputFormat = strings.ToLower(c.DefaultOutputFormat)
if c.DefaultOutputFormat != "" {
f, found := outputFormats.GetByName(c.DefaultOutputFormat)
if !found {
return fmt.Errorf("unknown default output format %q", c.DefaultOutputFormat)
}
defaultOutputFormat = f
} else {
c.DefaultOutputFormat = defaultOutputFormat.Name
}
disabledLangs := make(map[string]bool)
for _, lang := range c.DisableLanguages {
disabledLangs[lang] = true
@ -378,32 +396,63 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
// Legacy paginate values.
if c.Paginate != 0 {
hugo.Deprecate("site config key paginate", "Use paginator.pagerSize instead.", "v0.128.0")
hugo.DeprecateWithLogger("site config key paginate", "Use pagination.pagerSize instead.", "v0.128.0", logger.Logger())
c.Pagination.PagerSize = c.Paginate
}
if c.PaginatePath != "" {
hugo.Deprecate("site config key paginatePath", "Use paginator.path instead.", "v0.128.0")
hugo.DeprecateWithLogger("site config key paginatePath", "Use pagination.path instead.", "v0.128.0", logger.Logger())
c.Pagination.Path = c.PaginatePath
}
// Legacy privacy values.
if c.Privacy.Twitter.Disable {
hugo.DeprecateWithLogger("site config key privacy.twitter.disable", "Use privacy.x.disable instead.", "v0.141.0", logger.Logger())
c.Privacy.X.Disable = c.Privacy.Twitter.Disable
}
if c.Privacy.Twitter.EnableDNT {
hugo.DeprecateWithLogger("site config key privacy.twitter.enableDNT", "Use privacy.x.enableDNT instead.", "v0.141.0", logger.Logger())
c.Privacy.X.EnableDNT = c.Privacy.Twitter.EnableDNT
}
if c.Privacy.Twitter.Simple {
hugo.DeprecateWithLogger("site config key privacy.twitter.simple", "Use privacy.x.simple instead.", "v0.141.0", logger.Logger())
c.Privacy.X.Simple = c.Privacy.Twitter.Simple
}
// Legacy services values.
if c.Services.Twitter.DisableInlineCSS {
hugo.DeprecateWithLogger("site config key services.twitter.disableInlineCSS", "Use services.x.disableInlineCSS instead.", "v0.141.0", logger.Logger())
c.Services.X.DisableInlineCSS = c.Services.Twitter.DisableInlineCSS
}
// Legacy permalink tokens
vs := fmt.Sprintf("%v", c.Permalinks)
if strings.Contains(vs, ":filename") {
hugo.DeprecateWithLogger("the \":filename\" permalink token", "Use \":contentbasename\" instead.", "0.144.0", logger.Logger())
}
if strings.Contains(vs, ":slugorfilename") {
hugo.DeprecateWithLogger("the \":slugorfilename\" permalink token", "Use \":slugorcontentbasename\" instead.", "0.144.0", logger.Logger())
}
c.C = &ConfigCompiled{
Timeout: timeout,
BaseURL: baseURL,
BaseURLLiveReload: baseURL,
DisabledKinds: disabledKinds,
DisabledLanguages: disabledLangs,
IgnoredLogs: ignoredLogIDs,
KindOutputFormats: kindOutputFormats,
ContentTypes: media.DefaultContentTypes.FromTypes(c.MediaTypes.Config),
CreateTitle: helpers.GetTitleFunc(c.TitleCaseStyle),
IsUglyURLSection: isUglyURL,
IgnoreFile: ignoreFile,
SegmentFilter: c.Segments.Config.Get(func(s string) { logger.Warnf("Render segment %q not found in configuration", s) }, c.RootConfig.RenderSegments...),
MainSections: c.MainSections,
Clock: clock,
HTTPCache: httpCache,
transientErr: transientErr,
Timeout: timeout,
BaseURL: baseURL,
BaseURLLiveReload: baseURL,
DisabledKinds: disabledKinds,
DisabledLanguages: disabledLangs,
IgnoredLogs: ignoredLogIDs,
KindOutputFormats: kindOutputFormats,
DefaultOutputFormat: defaultOutputFormat,
CreateTitle: helpers.GetTitleFunc(c.TitleCaseStyle),
IsUglyURLSection: isUglyURL,
IgnoreFile: ignoreFile,
SegmentFilter: c.Segments.Config.Get(func(s string) { logger.Warnf("Render segment %q not found in configuration", s) }, c.RootConfig.RenderSegments...),
MainSections: c.MainSections,
Clock: clock,
HTTPCache: httpCache,
transientErr: transientErr,
}
for _, s := range allDecoderSetups {
@ -427,22 +476,22 @@ func (c *Config) IsLangDisabled(lang string) bool {
// ConfigCompiled holds values and functions that are derived from the config.
type ConfigCompiled struct {
Timeout time.Duration
BaseURL urls.BaseURL
BaseURLLiveReload urls.BaseURL
ServerInterface string
KindOutputFormats map[string]output.Formats
ContentTypes media.ContentTypes
DisabledKinds map[string]bool
DisabledLanguages map[string]bool
IgnoredLogs map[string]bool
CreateTitle func(s string) string
IsUglyURLSection func(section string) bool
IgnoreFile func(filename string) bool
SegmentFilter segments.SegmentFilter
MainSections []string
Clock time.Time
HTTPCache httpcache.ConfigCompiled
Timeout time.Duration
BaseURL urls.BaseURL
BaseURLLiveReload urls.BaseURL
ServerInterface string
KindOutputFormats map[string]output.Formats
DefaultOutputFormat output.Format
DisabledKinds map[string]bool
DisabledLanguages map[string]bool
IgnoredLogs map[string]bool
CreateTitle func(s string) string
IsUglyURLSection func(section string) bool
IgnoreFile func(filename string) bool
SegmentFilter segments.SegmentFilter
MainSections []string
Clock time.Time
HTTPCache httpcache.ConfigCompiled
// This is set to the last transient error found during config compilation.
// With themes/modules we compute the configuration in multiple passes, and
@ -502,6 +551,13 @@ type RootConfig struct {
// Set this to true to put all languages below their language ID.
DefaultContentLanguageInSubdir bool
// The default output format to use for the site.
// If not set, we will use the first output format.
DefaultOutputFormat string
// Disable generation of redirect to the default language when DefaultContentLanguageInSubdir is enabled.
DisableDefaultLanguageRedirect bool
// Disable creation of alias redirect pages.
DisableAliases bool
@ -720,15 +776,16 @@ type Configs struct {
}
func (c *Configs) Validate(logger loggers.Logger) error {
for p := range c.Base.Cascade.Config {
c.Base.Cascade.Config.Range(func(p page.PageMatcher, cfg page.PageMatcherParamsConfig) bool {
page.CheckCascadePattern(logger, p)
}
return true
})
return nil
}
// transientErr returns the last transient error found during config compilation.
func (c *Configs) transientErr() error {
for _, l := range c.LanguageConfigSlice {
for _, l := range c.LanguageConfigMap {
if l.C.transientErr != nil {
return l.C.transientErr
}
@ -743,31 +800,58 @@ func (c *Configs) IsZero() bool {
func (c *Configs) Init() error {
var languages langs.Languages
defaultContentLanguage := c.Base.DefaultContentLanguage
for k, v := range c.LanguageConfigMap {
var langKeys []string
var hasEn bool
const en = "en"
for k := range c.LanguageConfigMap {
langKeys = append(langKeys, k)
if k == en {
hasEn = true
}
}
// Sort the LanguageConfigSlice by language weight (if set) or lang.
sort.Slice(langKeys, func(i, j int) bool {
ki := langKeys[i]
kj := langKeys[j]
lki := c.LanguageConfigMap[ki]
lkj := c.LanguageConfigMap[kj]
li := lki.Languages[ki]
lj := lkj.Languages[kj]
if li.Weight != lj.Weight {
return li.Weight < lj.Weight
}
return ki < kj
})
// See issue #13646.
defaultConfigLanguageFallback := en
if !hasEn {
// Pick the first one.
defaultConfigLanguageFallback = langKeys[0]
}
if c.Base.DefaultContentLanguage == "" {
c.Base.DefaultContentLanguage = defaultConfigLanguageFallback
}
for _, k := range langKeys {
v := c.LanguageConfigMap[k]
if v.DefaultContentLanguage == "" {
v.DefaultContentLanguage = defaultConfigLanguageFallback
}
c.LanguageConfigSlice = append(c.LanguageConfigSlice, v)
languageConf := v.Languages[k]
language, err := langs.NewLanguage(k, defaultContentLanguage, v.TimeZone, languageConf)
language, err := langs.NewLanguage(k, c.Base.DefaultContentLanguage, v.TimeZone, languageConf)
if err != nil {
return err
}
languages = append(languages, language)
}
// Sort the sites by language weight (if set) or lang.
sort.Slice(languages, func(i, j int) bool {
li := languages[i]
lj := languages[j]
if li.Weight != lj.Weight {
return li.Weight < lj.Weight
}
return li.Lang < lj.Lang
})
for _, l := range languages {
c.LanguageConfigSlice = append(c.LanguageConfigSlice, c.LanguageConfigMap[l.Lang])
}
// Filter out disabled languages.
var n int
for _, l := range languages {
@ -780,12 +864,12 @@ func (c *Configs) Init() error {
var languagesDefaultFirst langs.Languages
for _, l := range languages {
if l.Lang == defaultContentLanguage {
if l.Lang == c.Base.DefaultContentLanguage {
languagesDefaultFirst = append(languagesDefaultFirst, l)
}
}
for _, l := range languages {
if l.Lang != defaultContentLanguage {
if l.Lang != c.Base.DefaultContentLanguage {
languagesDefaultFirst = append(languagesDefaultFirst, l)
}
}
@ -793,7 +877,24 @@ func (c *Configs) Init() error {
c.Languages = languages
c.LanguagesDefaultFirst = languagesDefaultFirst
c.ContentPathParser = &paths.PathParser{LanguageIndex: languagesDefaultFirst.AsIndexSet(), IsLangDisabled: c.Base.IsLangDisabled, IsContentExt: c.Base.C.ContentTypes.IsContentSuffix}
c.ContentPathParser = &paths.PathParser{
LanguageIndex: languagesDefaultFirst.AsIndexSet(),
IsLangDisabled: c.Base.IsLangDisabled,
IsContentExt: c.Base.ContentTypes.Config.IsContentSuffix,
IsOutputFormat: func(name, ext string) bool {
if name == "" {
return false
}
if of, ok := c.Base.OutputFormats.Config.GetByName(name); ok {
if ext != "" && !of.MediaType.HasSuffix(ext) {
return false
}
return true
}
return false
},
}
c.configLangs = make([]config.AllProvider, len(c.Languages))
for i, l := range c.LanguagesDefaultFirst {
@ -806,7 +907,7 @@ func (c *Configs) Init() error {
}
if len(c.Modules) == 0 {
return errors.New("no modules loaded (ned at least the main module)")
return errors.New("no modules loaded (need at least the main module)")
}
// Apply default project mounts.
@ -854,17 +955,48 @@ func (c Configs) GetByLang(lang string) config.AllProvider {
return nil
}
func newDefaultConfig() *Config {
return &Config{
Taxonomies: map[string]string{"tag": "tags", "category": "categories"},
Sitemap: config.SitemapConfig{Priority: -1, Filename: "sitemap.xml"},
RootConfig: RootConfig{
Environment: hugo.EnvironmentProduction,
TitleCaseStyle: "AP",
PluralizeListTitles: true,
CapitalizeListTitles: true,
StaticDir: []string{"static"},
SummaryLength: 70,
Timeout: "60s",
CommonDirs: config.CommonDirs{
ArcheTypeDir: "archetypes",
ContentDir: "content",
ResourceDir: "resources",
PublishDir: "public",
ThemesDir: "themes",
AssetDir: "assets",
LayoutDir: "layouts",
I18nDir: "i18n",
DataDir: "data",
},
},
}
}
// fromLoadConfigResult creates a new Config from res.
func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadConfigResult) (*Configs, error) {
if !res.Cfg.IsSet("languages") {
// We need at least one
lang := res.Cfg.GetString("defaultContentLanguage")
if lang == "" {
lang = "en"
}
res.Cfg.Set("languages", maps.Params{lang: maps.Params{}})
}
bcfg := res.BaseConfig
cfg := res.Cfg
all := &Config{}
all := newDefaultConfig()
err := decodeConfigFromParams(fs, logger, bcfg, cfg, all, nil)
if err != nil {
@ -874,6 +1006,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
langConfigMap := make(map[string]*Config)
languagesConfig := cfg.GetStringMap("languages")
var isMultihost bool
if err := all.CompileConfig(logger); err != nil {
@ -885,30 +1018,17 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
var differentRootKeys []string
switch x := v.(type) {
case maps.Params:
var params maps.Params
pv, found := x["params"]
if found {
params = pv.(maps.Params)
} else {
params = maps.Params{
_, found := x["params"]
if !found {
x["params"] = maps.Params{
maps.MergeStrategyKey: maps.ParamsMergeStrategyDeep,
}
x["params"] = params
}
for kk, vv := range x {
if kk == "_merge" {
continue
}
if kk != maps.MergeStrategyKey && !configLanguageKeys[kk] {
// This should have been placed below params.
// We accidentally allowed it in the past, so we need to support it a little longer,
// But log a warning.
if _, found := params[kk]; !found {
hugo.Deprecate(fmt.Sprintf("config: languages.%s.%s: custom params on the language top level", k, kk), fmt.Sprintf("Put the value below [languages.%s.params]. See https://gohugo.io/content-management/multilingual/#changes-in-hugo-01120", k), "v0.112.0")
params[kk] = vv
}
}
if kk == "baseurl" {
// baseURL configure don the language level is a multihost setup.
isMultihost = true

View file

@ -5,8 +5,10 @@ import (
"testing"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/config/allconfig"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/media"
)
func TestDirsMount(t *testing.T) {
@ -97,37 +99,13 @@ suffixes = ["html", "xhtml"]
b := hugolib.Test(t, files)
conf := b.H.Configs.Base
contentTypes := conf.C.ContentTypes
contentTypes := conf.ContentTypes.Config
b.Assert(contentTypes.HTML.Suffixes(), qt.DeepEquals, []string{"html", "xhtml"})
b.Assert(contentTypes.Markdown.Suffixes(), qt.DeepEquals, []string{"md", "mdown", "markdown"})
}
func TestPaginationConfigOld(t *testing.T) {
files := `
-- hugo.toml --
[languages.en]
weight = 1
paginatePath = "page-en"
[languages.de]
weight = 2
paginatePath = "page-de"
paginate = 20
`
b := hugolib.Test(t, files)
confEn := b.H.Sites[0].Conf.Pagination()
confDe := b.H.Sites[1].Conf.Pagination()
b.Assert(confEn.Path, qt.Equals, "page-en")
b.Assert(confEn.PagerSize, qt.Equals, 10)
b.Assert(confDe.Path, qt.Equals, "page-de")
b.Assert(confDe.PagerSize, qt.Equals, 20)
}
func TestPaginationConfigNew(t *testing.T) {
func TestPaginationConfig(t *testing.T) {
files := `
-- hugo.toml --
[languages.en]
@ -184,3 +162,220 @@ title: "p3"
b.AssertFileExists("public/page/1/index.html", false)
b.AssertFileContent("public/page/2/index.html", "pagination-default")
}
func TestMapUglyURLs(t *testing.T) {
files := `
-- hugo.toml --
[uglyurls]
posts = true
`
b := hugolib.Test(t, files)
c := b.H.Configs.Base
b.Assert(c.C.IsUglyURLSection("posts"), qt.IsTrue)
b.Assert(c.C.IsUglyURLSection("blog"), qt.IsFalse)
}
// Issue 13199
func TestInvalidOutputFormat(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
[outputs]
home = ['html','foo']
-- layouts/index.html --
x
`
b, err := hugolib.TestE(t, files)
b.Assert(err, qt.IsNotNil)
b.Assert(err.Error(), qt.Contains, `failed to create config: unknown output format "foo" for kind "home"`)
}
// Issue 13201
func TestLanguageConfigSlice(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
[languages.en]
title = 'TITLE_EN'
weight = 2
[languages.de]
title = 'TITLE_DE'
weight = 1
[languages.fr]
title = 'TITLE_FR'
weight = 3
`
b := hugolib.Test(t, files)
b.Assert(b.H.Configs.LanguageConfigSlice[0].Title, qt.Equals, `TITLE_DE`)
}
func TestContentTypesDefault(t *testing.T) {
files := `
-- hugo.toml --
baseURL = "https://example.com"
`
b := hugolib.Test(t, files)
ct := b.H.Configs.Base.ContentTypes
c := ct.Config
s := ct.SourceStructure.(map[string]media.ContentTypeConfig)
b.Assert(c.IsContentFile("foo.md"), qt.Equals, true)
b.Assert(len(s), qt.Equals, 6)
}
func TestMergeDeep(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "https://example.com"
theme = ["theme1", "theme2"]
_merge = "deep"
-- themes/theme1/hugo.toml --
[sitemap]
filename = 'mysitemap.xml'
[services]
[services.googleAnalytics]
id = 'foo bar'
[taxonomies]
foo = 'bars'
-- themes/theme2/config/_default/hugo.toml --
[taxonomies]
bar = 'baz'
-- layouts/home.html --
GA ID: {{ site.Config.Services.GoogleAnalytics.ID }}.
`
b := hugolib.Test(t, files)
conf := b.H.Configs
base := conf.Base
b.Assert(base.Environment, qt.Equals, hugo.EnvironmentProduction)
b.Assert(base.BaseURL, qt.Equals, "https://example.com")
b.Assert(base.Sitemap.Filename, qt.Equals, "mysitemap.xml")
b.Assert(base.Taxonomies, qt.DeepEquals, map[string]string{"bar": "baz", "foo": "bars"})
b.AssertFileContent("public/index.html", "GA ID: foo bar.")
}
func TestMergeDeepBuildStats(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "https://example.com"
title = "Theme 1"
_merge = "deep"
[module]
[module.hugoVersion]
[[module.imports]]
path = "theme1"
-- themes/theme1/hugo.toml --
[build]
[build.buildStats]
disableIDs = true
enable = true
-- layouts/home.html --
Home.
`
b := hugolib.Test(t, files, hugolib.TestOptOsFs())
conf := b.H.Configs
base := conf.Base
b.Assert(base.Title, qt.Equals, "Theme 1")
b.Assert(len(base.Module.Imports), qt.Equals, 1)
b.Assert(base.Build.BuildStats.Enable, qt.Equals, true)
b.AssertFileExists("/hugo_stats.json", true)
}
func TestMergeDeepBuildStatsTheme(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "https://example.com"
_merge = "deep"
theme = ["theme1"]
-- themes/theme1/hugo.toml --
title = "Theme 1"
[build]
[build.buildStats]
disableIDs = true
enable = true
-- layouts/home.html --
Home.
`
b := hugolib.Test(t, files, hugolib.TestOptOsFs())
conf := b.H.Configs
base := conf.Base
b.Assert(base.Title, qt.Equals, "Theme 1")
b.Assert(len(base.Module.Imports), qt.Equals, 1)
b.Assert(base.Build.BuildStats.Enable, qt.Equals, true)
b.AssertFileExists("/hugo_stats.json", true)
}
func TestDefaultConfigLanguageBlankWhenNoEnglishExists(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "https://example.com"
[languages]
[languages.nn]
weight = 20
[languages.sv]
weight = 10
[languages.sv.taxonomies]
tag = "taggar"
-- layouts/all.html --
All.
`
b := hugolib.Test(t, files)
b.Assert(b.H.Conf.DefaultContentLanguage(), qt.Equals, "sv")
}
func TestDefaultConfigEnvDisableLanguagesIssue13707(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableLanguages = []
[languages]
[languages.en]
weight = 1
[languages.nn]
weight = 2
[languages.sv]
weight = 3
`
b := hugolib.Test(t, files, hugolib.TestOptWithConfig(func(conf *hugolib.IntegrationTestConfig) {
conf.Environ = []string{`HUGO_DISABLELANGUAGES=sv nn`}
}))
b.Assert(len(b.H.Sites), qt.Equals, 1)
}

View file

@ -163,6 +163,15 @@ var allDecoderSetups = map[string]decodeWeight{
return err
},
},
"contenttypes": {
key: "contenttypes",
weight: 100, // This needs to be decoded after media types.
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.ContentTypes, err = media.DecodeContentTypes(p.p.GetStringMap(d.key), p.c.MediaTypes.Config)
return err
},
},
"mediatypes": {
key: "mediatypes",
decode: func(d decodeWeight, p decodeConfig) error {
@ -240,14 +249,18 @@ var allDecoderSetups = map[string]decodeWeight{
key: "sitemap",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.Sitemap, err = config.DecodeSitemap(config.SitemapConfig{Priority: -1, Filename: "sitemap.xml"}, p.p.GetStringMap(d.key))
if p.p.IsSet(d.key) {
p.c.Sitemap, err = config.DecodeSitemap(p.c.Sitemap, p.p.GetStringMap(d.key))
}
return err
},
},
"taxonomies": {
key: "taxonomies",
decode: func(d decodeWeight, p decodeConfig) error {
p.c.Taxonomies = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
if p.p.IsSet(d.key) {
p.c.Taxonomies = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
}
return nil
},
},
@ -297,15 +310,17 @@ var allDecoderSetups = map[string]decodeWeight{
}
// Validate defaultContentLanguage.
var found bool
for lang := range p.c.Languages {
if lang == p.c.DefaultContentLanguage {
found = true
break
if p.c.DefaultContentLanguage != "" {
var found bool
for lang := range p.c.Languages {
if lang == p.c.DefaultContentLanguage {
found = true
break
}
}
if !found {
return fmt.Errorf("config value %q for defaultContentLanguage does not match any language definition", p.c.DefaultContentLanguage)
}
}
if !found {
return fmt.Errorf("config value %q for defaultContentLanguage does not match any language definition", p.c.DefaultContentLanguage)
}
return nil
@ -315,7 +330,7 @@ var allDecoderSetups = map[string]decodeWeight{
key: "cascade",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.Cascade, err = page.DecodeCascadeConfig(nil, p.p.Get(d.key))
p.c.Cascade, err = page.DecodeCascadeConfig(nil, true, p.p.Get(d.key))
return err
},
},
@ -327,6 +342,25 @@ var allDecoderSetups = map[string]decodeWeight{
return err
},
},
"page": {
key: "page",
decode: func(d decodeWeight, p decodeConfig) error {
p.c.Page = config.PageConfig{
NextPrevSortOrder: "desc",
NextPrevInSectionSortOrder: "desc",
}
if p.p.IsSet(d.key) {
if err := mapstructure.WeakDecode(p.p.Get(d.key), &p.c.Page); err != nil {
return err
}
}
return nil
},
getCompiler: func(c *Config) configCompiler {
return &c.Page
},
},
"pagination": {
key: "pagination",
decode: func(d decodeWeight, p decodeConfig) error {
@ -400,6 +434,8 @@ var allDecoderSetups = map[string]decodeWeight{
p.c.UglyURLs = vv
case string:
p.c.UglyURLs = vv == "true"
case maps.Params:
p.c.UglyURLs = cast.ToStringMapBool(maps.CleanConfigStringMap(vv))
default:
p.c.UglyURLs = cast.ToStringMapBool(v)
}

View file

@ -137,15 +137,15 @@ func (c ConfigLanguage) Watching() bool {
return c.m.Base.Internal.Watch
}
func (c ConfigLanguage) NewIdentityManager(name string) identity.Manager {
func (c ConfigLanguage) NewIdentityManager(name string, opts ...identity.ManagerOption) identity.Manager {
if !c.Watching() {
return identity.NopManager
}
return identity.NewManager(name)
return identity.NewManager(name, opts...)
}
func (c ConfigLanguage) ContentTypes() config.ContentTypesProvider {
return c.config.C.ContentTypes
return c.config.ContentTypes.Config
}
// GetConfigSection is mostly used in tests. The switch statement isn't complete, but what's in use.

View file

@ -64,7 +64,7 @@ func LoadConfig(d ConfigSourceDescriptor) (*Configs, error) {
return nil, fmt.Errorf("failed to create config from result: %w", err)
}
moduleConfig, modulesClient, err := l.loadModules(configs)
moduleConfig, modulesClient, err := l.loadModules(configs, d.IgnoreModuleDoesNotExist)
if err != nil {
return nil, fmt.Errorf("failed to load modules: %w", err)
}
@ -91,7 +91,7 @@ func LoadConfig(d ConfigSourceDescriptor) (*Configs, error) {
return nil, fmt.Errorf("failed to init config: %w", err)
}
loggers.InitGlobalLogger(d.Logger.Level(), configs.Base.PanicOnWarning)
loggers.SetGlobalLogger(d.Logger)
return configs, nil
}
@ -116,6 +116,9 @@ type ConfigSourceDescriptor struct {
// Defaults to os.Environ if not set.
Environ []string
// If set, this will be used to ignore the module does not exist error.
IgnoreModuleDoesNotExist bool
}
func (d ConfigSourceDescriptor) configFilenames() []string {
@ -156,63 +159,9 @@ func (l configLoader) applyConfigAliases() error {
func (l configLoader) applyDefaultConfig() error {
defaultSettings := maps.Params{
"baseURL": "",
"cleanDestinationDir": false,
"watch": false,
"contentDir": "content",
"resourceDir": "resources",
"publishDir": "public",
"publishDirOrig": "public",
"themesDir": "themes",
"assetDir": "assets",
"layoutDir": "layouts",
"i18nDir": "i18n",
"dataDir": "data",
"archetypeDir": "archetypes",
"configDir": "config",
"staticDir": "static",
"buildDrafts": false,
"buildFuture": false,
"buildExpired": false,
"params": maps.Params{},
"environment": hugo.EnvironmentProduction,
"uglyURLs": false,
"verbose": false,
"ignoreCache": false,
"canonifyURLs": false,
"relativeURLs": false,
"removePathAccents": false,
"titleCaseStyle": "AP",
"taxonomies": maps.Params{"tag": "tags", "category": "categories"},
"permalinks": maps.Params{},
"sitemap": maps.Params{"priority": -1, "filename": "sitemap.xml"},
"menus": maps.Params{},
"disableLiveReload": false,
"pluralizeListTitles": true,
"capitalizeListTitles": true,
"forceSyncStatic": false,
"footnoteAnchorPrefix": "",
"footnoteReturnLinkContents": "",
"newContentEditor": "",
"paginate": 0, // Moved into the paginator struct in Hugo v0.128.0.
"paginatePath": "", // Moved into the paginator struct in Hugo v0.128.0.
"summaryLength": 70,
"rssLimit": -1,
"sectionPagesMenu": "",
"disablePathToLower": false,
"hasCJKLanguage": false,
"enableEmoji": false,
"defaultContentLanguage": "en",
"defaultContentLanguageInSubdir": false,
"enableMissingTranslationPlaceholders": false,
"enableGitInfo": false,
"ignoreFiles": make([]string, 0),
"disableAliases": false,
"debug": false,
"disableFastRender": false,
"timeout": "30s",
"timeZone": "",
"enableInlineShortcodes": false,
// These dirs are used early/before we build the config struct.
"themesDir": "themes",
"configDir": "config",
}
l.cfg.SetDefaults(defaultSettings)
@ -284,40 +233,51 @@ func (l configLoader) applyOsEnvOverrides(environ []string) error {
if existing != nil {
val, err := metadecoders.Default.UnmarshalStringTo(env.Value, existing)
if err != nil {
if err == nil {
val = l.envValToVal(env.Key, val)
if owner != nil {
owner[nestedKey] = val
} else {
l.cfg.Set(env.Key, val)
}
continue
}
if owner != nil {
owner[nestedKey] = val
} else {
l.cfg.Set(env.Key, val)
}
} else {
if nestedKey != "" {
owner[nestedKey] = env.Value
} else {
var val any
key := strings.ReplaceAll(env.Key, delim, ".")
_, ok := allDecoderSetups[key]
if ok {
// A map.
if v, err := metadecoders.Default.UnmarshalStringTo(env.Value, map[string]interface{}{}); err == nil {
val = v
}
}
if val == nil {
// A string.
val = l.envStringToVal(key, env.Value)
}
l.cfg.Set(key, val)
}
}
if owner != nil && nestedKey != "" {
owner[nestedKey] = env.Value
} else {
var val any
key := strings.ReplaceAll(env.Key, delim, ".")
_, ok := allDecoderSetups[key]
if ok {
// A map.
if v, err := metadecoders.Default.UnmarshalStringTo(env.Value, map[string]any{}); err == nil {
val = v
}
}
if val == nil {
// A string.
val = l.envStringToVal(key, env.Value)
}
l.cfg.Set(key, val)
}
}
return nil
}
func (l *configLoader) envValToVal(k string, v any) any {
switch v := v.(type) {
case string:
return l.envStringToVal(k, v)
default:
return v
}
}
func (l *configLoader) envStringToVal(k, v string) any {
switch k {
case "disablekinds", "disablelanguages":
@ -453,7 +413,7 @@ func (l *configLoader) loadConfigMain(d ConfigSourceDescriptor) (config.LoadConf
return res, l.ModulesConfig, err
}
func (l *configLoader) loadModules(configs *Configs) (modules.ModulesConfig, *modules.Client, error) {
func (l *configLoader) loadModules(configs *Configs, ignoreModuleDoesNotExist bool) (modules.ModulesConfig, *modules.Client, error) {
bcfg := configs.LoadingInfo.BaseConfig
conf := configs.Base
workingDir := bcfg.WorkingDir
@ -467,7 +427,7 @@ func (l *configLoader) loadModules(configs *Configs) (modules.ModulesConfig, *mo
ignoreVendor, _ = hglob.GetGlob(hglob.NormalizePath(s))
}
ex := hexec.New(conf.Security, workingDir)
ex := hexec.New(conf.Security, workingDir, l.Logger)
hook := func(m *modules.ModulesConfig) error {
for _, tc := range m.AllModules {
@ -487,17 +447,18 @@ func (l *configLoader) loadModules(configs *Configs) (modules.ModulesConfig, *mo
}
modulesClient := modules.NewClient(modules.ClientConfig{
Fs: l.Fs,
Logger: l.Logger,
Exec: ex,
HookBeforeFinalize: hook,
WorkingDir: workingDir,
ThemesDir: themesDir,
PublishDir: publishDir,
Environment: l.Environment,
CacheDir: conf.Caches.CacheDirModules(),
ModuleConfig: conf.Module,
IgnoreVendor: ignoreVendor,
Fs: l.Fs,
Logger: l.Logger,
Exec: ex,
HookBeforeFinalize: hook,
WorkingDir: workingDir,
ThemesDir: themesDir,
PublishDir: publishDir,
Environment: l.Environment,
CacheDir: conf.Caches.CacheDirModules(),
ModuleConfig: conf.Module,
IgnoreVendor: ignoreVendor,
IgnoreModuleDoesNotExist: ignoreModuleDoesNotExist,
})
moduleConfig, err := modulesClient.Collect()

View file

@ -15,7 +15,9 @@ package config
import (
"fmt"
"net/http"
"regexp"
"slices"
"sort"
"strings"
@ -127,7 +129,7 @@ func (w BuildStats) Enabled() bool {
}
func (b BuildConfig) clone() BuildConfig {
b.CacheBusters = append([]CacheBuster{}, b.CacheBusters...)
b.CacheBusters = slices.Clone(b.CacheBusters)
return b
}
@ -226,7 +228,22 @@ type Server struct {
Redirects []Redirect
compiledHeaders []glob.Glob
compiledRedirects []glob.Glob
compiledRedirects []redirect
}
type redirect struct {
from glob.Glob
fromRe *regexp.Regexp
headers map[string]glob.Glob
}
func (r redirect) matchHeader(header http.Header) bool {
for k, v := range r.headers {
if !v.Match(header.Get(k)) {
return false
}
}
return true
}
func (s *Server) CompileConfig(logger loggers.Logger) error {
@ -234,10 +251,41 @@ func (s *Server) CompileConfig(logger loggers.Logger) error {
return nil
}
for _, h := range s.Headers {
s.compiledHeaders = append(s.compiledHeaders, glob.MustCompile(h.For))
g, err := glob.Compile(h.For)
if err != nil {
return fmt.Errorf("failed to compile Headers glob %q: %w", h.For, err)
}
s.compiledHeaders = append(s.compiledHeaders, g)
}
for _, r := range s.Redirects {
s.compiledRedirects = append(s.compiledRedirects, glob.MustCompile(r.From))
if r.From == "" && r.FromRe == "" {
return fmt.Errorf("redirects must have either From or FromRe set")
}
rd := redirect{
headers: make(map[string]glob.Glob),
}
if r.From != "" {
g, err := glob.Compile(r.From)
if err != nil {
return fmt.Errorf("failed to compile Redirect glob %q: %w", r.From, err)
}
rd.from = g
}
if r.FromRe != "" {
re, err := regexp.Compile(r.FromRe)
if err != nil {
return fmt.Errorf("failed to compile Redirect regexp %q: %w", r.FromRe, err)
}
rd.fromRe = re
}
for k, v := range r.FromHeaders {
g, err := glob.Compile(v)
if err != nil {
return fmt.Errorf("failed to compile Redirect header glob %q: %w", v, err)
}
rd.headers[k] = g
}
s.compiledRedirects = append(s.compiledRedirects, rd)
}
return nil
@ -266,22 +314,42 @@ func (s *Server) MatchHeaders(pattern string) []types.KeyValueStr {
return matches
}
func (s *Server) MatchRedirect(pattern string) Redirect {
func (s *Server) MatchRedirect(pattern string, header http.Header) Redirect {
if s.compiledRedirects == nil {
return Redirect{}
}
pattern = strings.TrimSuffix(pattern, "index.html")
for i, g := range s.compiledRedirects {
for i, r := range s.compiledRedirects {
redir := s.Redirects[i]
// No redirect to self.
if redir.To == pattern {
return Redirect{}
var found bool
if r.from != nil {
if r.from.Match(pattern) {
found = header == nil || r.matchHeader(header)
// We need to do regexp group replacements if needed.
}
}
if g.Match(pattern) {
if r.fromRe != nil {
m := r.fromRe.FindStringSubmatch(pattern)
if m != nil {
if !found {
found = header == nil || r.matchHeader(header)
}
if found {
// Replace $1, $2 etc. in To.
for i, g := range m[1:] {
redir.To = strings.ReplaceAll(redir.To, fmt.Sprintf("$%d", i+1), g)
}
}
}
}
if found {
return redir
}
}
@ -295,8 +363,22 @@ type Headers struct {
}
type Redirect struct {
// From is the Glob pattern to match.
// One of From or FromRe must be set.
From string
To string
// FromRe is the regexp to match.
// This regexp can contain group matches (e.g. $1) that can be used in the To field.
// One of From or FromRe must be set.
FromRe string
// To is the target URL.
To string
// Headers to match for the redirect.
// This maps the HTTP header name to a Glob pattern with values to match.
// If the map is empty, the redirect will always be triggered.
FromHeaders map[string]string
// HTTP status code to use for the redirect.
// A status code of 200 will trigger a URL rewrite.
@ -369,7 +451,7 @@ func (c *CacheBuster) CompileConfig(logger loggers.Logger) error {
}
func (r Redirect) IsZero() bool {
return r.From == ""
return r.From == "" && r.FromRe == ""
}
const (
@ -383,17 +465,7 @@ func DecodeServer(cfg Provider) (Server, error) {
_ = mapstructure.WeakDecode(cfg.GetStringMap("server"), s)
for i, redir := range s.Redirects {
// Get it in line with the Hugo server for OK responses.
// We currently treat the 404 as a special case, they are always "ugly", so keep them as is.
if redir.Status != 404 {
redir.To = strings.TrimSuffix(redir.To, "index.html")
if !strings.HasPrefix(redir.To, "https") && !strings.HasSuffix(redir.To, "/") {
// There are some tricky infinite loop situations when dealing
// when the target does not have a trailing slash.
// This can certainly be handled better, but not time for that now.
return Server{}, fmt.Errorf("unsupported redirect to value %q in server config; currently this must be either a remote destination or a local folder, e.g. \"/blog/\" or \"/blog/index.html\"", redir.To)
}
}
redir.To = strings.TrimSuffix(redir.To, "index.html")
s.Redirects[i] = redir
}
@ -401,7 +473,7 @@ func DecodeServer(cfg Provider) (Server, error) {
// Set up a default redirect for 404s.
s.Redirects = []Redirect{
{
From: "**",
From: "/**",
To: "/404.html",
Status: 404,
},
@ -422,3 +494,18 @@ type Pagination struct {
// Whether to disable generation of alias for the first pagination page.
DisableAliases bool
}
// PageConfig configures the behavior of pages.
type PageConfig struct {
// Sort order for Page.Next and Page.Prev. Default "desc" (the default page sort order in Hugo).
NextPrevSortOrder string
// Sort order for Page.NextInSection and Page.PrevInSection. Default "desc".
NextPrevInSectionSortOrder string
}
func (c *PageConfig) CompileConfig(loggers.Logger) error {
c.NextPrevInSectionSortOrder = strings.ToLower(c.NextPrevInSectionSortOrder)
c.NextPrevSortOrder = strings.ToLower(c.NextPrevSortOrder)
return nil
}

View file

@ -71,7 +71,28 @@ X-Content-Type-Options = "nosniff"
[[server.redirects]]
from = "/foo/**"
to = "/foo/index.html"
to = "/baz/index.html"
status = 200
[[server.redirects]]
from = "/loop/**"
to = "/loop/foo/"
status = 200
[[server.redirects]]
from = "/b/**"
fromRe = "/b/(.*)/"
to = "/baz/$1/"
status = 200
[[server.redirects]]
fromRe = "/c/(.*)/"
to = "/boo/$1/"
status = 200
[[server.redirects]]
fromRe = "/d/(.*)/"
to = "/boo/$1/"
status = 200
[[server.redirects]]
@ -79,11 +100,6 @@ from = "/google/**"
to = "https://google.com/"
status = 301
[[server.redirects]]
from = "/**"
to = "/default/index.html"
status = 301
`, "toml")
@ -100,45 +116,35 @@ status = 301
{Key: "X-XSS-Protection", Value: "1; mode=block"},
})
c.Assert(s.MatchRedirect("/foo/bar/baz"), qt.DeepEquals, Redirect{
c.Assert(s.MatchRedirect("/foo/bar/baz", nil), qt.DeepEquals, Redirect{
From: "/foo/**",
To: "/foo/",
To: "/baz/",
Status: 200,
})
c.Assert(s.MatchRedirect("/someother"), qt.DeepEquals, Redirect{
From: "/**",
To: "/default/",
Status: 301,
c.Assert(s.MatchRedirect("/foo/bar/", nil), qt.DeepEquals, Redirect{
From: "/foo/**",
To: "/baz/",
Status: 200,
})
c.Assert(s.MatchRedirect("/google/foo"), qt.DeepEquals, Redirect{
c.Assert(s.MatchRedirect("/b/c/", nil), qt.DeepEquals, Redirect{
From: "/b/**",
FromRe: "/b/(.*)/",
To: "/baz/c/",
Status: 200,
})
c.Assert(s.MatchRedirect("/c/d/", nil).To, qt.Equals, "/boo/d/")
c.Assert(s.MatchRedirect("/c/d/e/", nil).To, qt.Equals, "/boo/d/e/")
c.Assert(s.MatchRedirect("/someother", nil), qt.DeepEquals, Redirect{})
c.Assert(s.MatchRedirect("/google/foo", nil), qt.DeepEquals, Redirect{
From: "/google/**",
To: "https://google.com/",
Status: 301,
})
// No redirect loop, please.
c.Assert(s.MatchRedirect("/default/index.html"), qt.DeepEquals, Redirect{})
c.Assert(s.MatchRedirect("/default/"), qt.DeepEquals, Redirect{})
for _, errorCase := range []string{
`[[server.redirects]]
from = "/**"
to = "/file"
status = 301`,
`[[server.redirects]]
from = "/**"
to = "/foo/file.html"
status = 301`,
} {
cfg, err := FromConfigString(errorCase, "toml")
c.Assert(err, qt.IsNil)
_, err = DecodeServer(cfg)
c.Assert(err, qt.Not(qt.IsNil))
}
}
func TestBuildConfigCacheBusters(t *testing.T) {
@ -160,7 +166,7 @@ func TestBuildConfigCacheBusters(t *testing.T) {
func TestBuildConfigCacheBusterstTailwindSetup(t *testing.T) {
c := qt.New(t)
cfg := New()
cfg.Set("build", map[string]interface{}{
cfg.Set("build", map[string]any{
"cacheBusters": []map[string]string{
{
"source": "assets/watching/hugo_stats\\.json",

View file

@ -58,7 +58,7 @@ type AllProvider interface {
BuildDrafts() bool
Running() bool
Watching() bool
NewIdentityManager(name string) identity.Manager
NewIdentityManager(name string, opts ...identity.ManagerOption) identity.Manager
FastRenderMode() bool
PrintUnusedTemplates() bool
EnableMissingTranslationPlaceholders() bool
@ -76,7 +76,7 @@ type AllProvider interface {
}
// We cannot import the media package as that would create a circular dependency.
// This interface defineds a sub set of what media.ContentTypes provides.
// This interface defines a subset of what media.ContentTypes provides.
type ContentTypesProvider interface {
IsContentSuffix(suffix string) bool
IsContentFile(filename string) bool

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