Compare commits

...

77 Commits

Author SHA1 Message Date
hugoreleaser 2fd4a7d3d6 releaser: Bump versions for release of 0.96.0
[ci skip]
2022-03-26 09:15:58 +00:00
hugoreleaser 5a4ac2dac1 releaser: Add release notes for 0.96.0
[ci skip]
2022-03-26 09:15:58 +00:00
Bjørn Erik Pedersen db1562e134 docs: Regen docshelper 2022-03-26 11:06:04 +02:00
Bjørn Erik Pedersen 5b18e10841 docs: Regen CLI docs 2022-03-26 11:05:30 +02:00
Bjørn Erik Pedersen d7497b28c1 Merge commit 'd276e901b36d2576ef8350ed96b17f66254eac1b' 2022-03-26 11:04:57 +02:00
Bjørn Erik Pedersen d276e901b3 Squashed 'docs/' changes from a393f4cf4..63386081c
63386081c update cSpell config update
15c76494b Update cSpell custom dictionary (#1694)
34f3167b7 Update image processing (#1625)
7462cc798 fix: pipes in sample code break table creation (#1686)
48736447e Update anchorize.md
2ff0bd10b netlify: Hugo 0.95.0
0fc1d21b2 Update configuration.md
41855e372 Fix #1682
8c663433e Update related.md
7aa072eab netlify: Hugo 0.94.2
1682c7ee7 Update render-hooks.md
ce1283cc4 Move the Render Hooks doc to its own page
bbbbfbfc6 Update configuration-markup.md
92d91a316 Update configuration-markup.md
2e8068823 Update configuration-markup.md
ff2dbca60 Update configuration-markup.md
89d8e5d65 Add code block documenation
e993539f0 Update shortcodes.md
c1b28dbfe netlify: Hugo 0.94.1
81b8c9b83 Merge branch 'tempv0.94.1'
4763b3d50 docs: Regenerate CLI docs
b18463971 netlify: Bump to Hugo 0.94.0
4152ebc1d Merge branch 'tempv0.94.0'
ba3a11ac2 docs: Regenerate docshelper
e64016d13 docs: Regenerate docshelper
29180e4d2 add `.html` suffix to partial usage and references
3213e00f2 Docs tidy-up
6cfcae4b7 docs: Regenerate CLI docs
8a6cd0b4d docs: Regenerate docshelper
b20ab262f Merge commit 'd706529720b3b2ccb99719ccd578062ca25a0cc2'

git-subtree-dir: docs
git-subtree-split: 63386081c5
2022-03-26 11:04:57 +02:00
Sara Op den Orth 94459680ba Deprecate .File.Extension
Fixes #9352
2022-03-26 10:03:36 +01:00
Bjørn Erik Pedersen 9202117ba0 resources: Add more details to .Err
This commit adds a .Data object (a map with `Body`, `StatusCode` etc.) to the .Err returned from `resources.GetRemote`, which means you can now do:

```
{{ with .Err }}
{{ range $k, $v := .Data }}
{{ end }}
{{ end }}
```

Fixes #9708
2022-03-25 16:40:36 +01:00
Joe Mooring a6fa290f67 commands: Change link protocol to https 2022-03-25 15:29:50 +01:00
dependabot[bot] 0bbc2fb523 build(deps): bump github.com/getkin/kin-openapi from 0.91.0 to 0.93.0
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.91.0 to 0.93.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.91.0...v0.93.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>
2022-03-25 11:08:23 +01:00
Joe Mooring 94e8a90769 tpl/crypto: Add optional encoding arg to hmac function
Closes #9709
2022-03-23 19:42:56 +01:00
Panagiotis Koursaris a461e9d01a Fix typo 2022-03-22 00:39:33 -06:00
cuishuang 48c98a8d24 Fix some typos
Signed-off-by: cuishuang <imcusg@gmail.com>
[foka@debian.org: Resolve merge conflict and squash 2 commits]
Signed-off-by: Anthony Fok <foka@debian.org>
2022-03-22 00:38:23 -06:00
Anthony Fok bbd455fe79 Update CONTRIBUTING.md to use "go install" to install mage
In Go 1.18, "go get" no longer builds packages.
2022-03-21 21:01:24 -06:00
Anthony Fok 8309a2b1c1 Dockerfile: Make it build with Go 1.18 2022-03-21 21:01:24 -06:00
Anthony Fok 2b7231097f snap: Make it build with Go 1.18 2022-03-21 21:01:24 -06:00
dependabot[bot] 13ff4ded76 build(deps): bump github.com/yuin/goldmark from 1.4.10 to 1.4.11
Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.4.10 to 1.4.11.
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.4.10...v1.4.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-21 17:31:27 +01:00
dependabot[bot] c3289eb5b8 build(deps): bump github.com/spf13/cobra from 1.3.0 to 1.4.0
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.3.0 to 1.4.0.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Changelog](https://github.com/spf13/cobra/blob/master/CHANGELOG.md)
- [Commits](https://github.com/spf13/cobra/compare/v1.3.0...v1.4.0)

---
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>
2022-03-21 14:08:38 +01:00
Bjørn Erik Pedersen 9539069f5e commands: Improve server startup to make tests less flaky
Do this by announcing/listen on the local address before we start the server.
2022-03-21 09:32:35 +01:00
Bjørn Erik Pedersen 0e305d6958 all: Use strings.Cut
Updates #9687
2022-03-21 09:32:35 +01:00
Luke de Waal 5adb81ce39 Support '-u=patch' in hugo mod get
The control-flow for running `hugo mod get` was adapted to allow for passing `-u=patch`
instead of only being able to pass `-u`.

Fixes #9127
2022-03-20 22:07:26 +01:00
Bjørn Erik Pedersen 1c0e7c1ae1 Make sure file mounts higher up wins
Fixes #9693
2022-03-20 20:23:03 +01:00
Joe Mooring cad2d8cc70 resources/images: Require width and height for Crop, Fill, and Fit
Closes #9696
2022-03-20 19:44:41 +01:00
Bjørn Erik Pedersen b80853de90 all: gofmt -w -r 'interface{} -> any' .
Updates #9687
2022-03-17 22:03:27 +01:00
Bjørn Erik Pedersen 423594e03a dartsass: Enable deprecation, @warn and @debug logging
* @warn and Sass deprecations are printed as WARN
* @debug is currently logged as INFO (needs the `--verbose` flag). We may adjust this if it gets too chatty.

Fixes #9683
2022-03-17 21:45:43 +01:00
Bjørn Erik Pedersen 64afb7ca51 Use revision etc. from debug.BuildInfo
Fixes #9680
2022-03-17 17:14:38 +01:00
hugoreleaser 004bec2e9a releaser: Prepare repository for 0.96.0-DEV
[ci skip]
2022-03-16 15:00:05 +00:00
hugoreleaser 9f2e76af65 releaser: Bump versions for release of 0.95.0
[ci skip]
2022-03-16 14:20:17 +00:00
hugoreleaser f1d1576822 releaser: Add release notes for 0.95.0
[ci skip]
2022-03-16 14:20:17 +00:00
Bjørn Erik Pedersen 5930173cd0 readme: Add note about Go 1.18 2022-03-16 13:20:30 +01:00
Bjørn Erik Pedersen 3476b53343 tpl: Pull in Go 1.18 patch that fixes the "no space in {{ continue }} and {{ break }}" bug 2022-03-16 13:14:23 +01:00
Bjørn Erik Pedersen e792d27017 readme: Add a contribution note 2022-03-16 13:09:17 +01:00
Bjørn Erik Pedersen 9d6495d774 github: Make it build with Go 1.18
I also ran some benchmarks comparing it to Go 1.17.8:

```bash
name                                      old time/op    new time/op    delta
SiteNew/Regular_Bundle_with_image-10        5.13ms ± 7%    4.23ms ± 7%  -17.47%  (p=0.029 n=4+4)
SiteNew/Regular_Bundle_with_JSON_file-10    5.12ms ± 1%    4.16ms ± 1%  -18.65%  (p=0.029 n=4+4)
SiteNew/Regular_Tags_and_categories-10      17.3ms ± 2%    15.1ms ± 1%  -12.53%  (p=0.029 n=4+4)
SiteNew/Regular_Canonify_URLs-10             106ms ± 1%      90ms ± 0%  -15.86%  (p=0.029 n=4+4)
SiteNew/Regular_Deep_content_tree-10        34.0ms ± 1%    30.0ms ± 2%  -11.72%  (p=0.029 n=4+4)
SiteNew/Regular_TOML_front_matter-10        22.7ms ± 2%    20.2ms ± 1%  -10.93%  (p=0.029 n=4+4)
SiteNew/Regular_Many_HTML_templates-10      16.7ms ± 0%    14.6ms ± 1%  -12.89%  (p=0.029 n=4+4)
SiteNew/Regular_Page_collections-10         22.2ms ± 1%    19.5ms ± 1%  -12.07%  (p=0.029 n=4+4)
SiteNew/Regular_List_terms-10               7.71ms ± 0%    6.59ms ± 1%  -14.52%  (p=0.029 n=4+4)

name                                      old alloc/op   new alloc/op   delta
SiteNew/Regular_Bundle_with_image-10        2.07MB ± 0%    2.04MB ± 0%   -1.40%  (p=0.029 n=4+4)
SiteNew/Regular_Bundle_with_JSON_file-10    1.85MB ± 0%    1.83MB ± 0%   -1.54%  (p=0.029 n=4+4)
SiteNew/Regular_Tags_and_categories-10      11.8MB ± 0%    11.1MB ± 0%   -6.11%  (p=0.029 n=4+4)
SiteNew/Regular_Canonify_URLs-10            95.8MB ± 0%    95.2MB ± 0%   -0.63%  (p=0.029 n=4+4)
SiteNew/Regular_Deep_content_tree-10        27.4MB ± 0%    26.9MB ± 0%   -1.68%  (p=0.029 n=4+4)
SiteNew/Regular_TOML_front_matter-10        15.2MB ± 0%    14.4MB ± 0%   -4.93%  (p=0.029 n=4+4)
SiteNew/Regular_Many_HTML_templates-10      8.45MB ± 0%    8.24MB ± 0%   -2.43%  (p=0.029 n=4+4)
SiteNew/Regular_Page_collections-10         16.5MB ± 0%    16.2MB ± 0%   -1.78%  (p=0.029 n=4+4)
SiteNew/Regular_List_terms-10               3.73MB ± 0%    3.64MB ± 0%   -2.30%  (p=0.029 n=4+4)

name                                      old allocs/op  new allocs/op  delta
SiteNew/Regular_Bundle_with_image-10         25.0k ± 0%     24.1k ± 0%   -3.60%  (p=0.029 n=4+4)
SiteNew/Regular_Bundle_with_JSON_file-10     25.1k ± 0%     24.1k ± 0%   -3.60%  (p=0.029 n=4+4)
SiteNew/Regular_Tags_and_categories-10        237k ± 0%      234k ± 0%   -1.29%  (p=0.029 n=4+4)
SiteNew/Regular_Canonify_URLs-10              365k ± 0%      363k ± 0%   -0.47%  (p=0.029 n=4+4)
SiteNew/Regular_Deep_content_tree-10          284k ± 0%      279k ± 0%   -1.98%  (p=0.029 n=4+4)
SiteNew/Regular_TOML_front_matter-10          252k ± 0%      249k ± 0%   -1.48%  (p=0.029 n=4+4)
SiteNew/Regular_Many_HTML_templates-10        117k ± 0%      115k ± 0%   -1.68%  (p=0.029 n=4+4)
SiteNew/Regular_Page_collections-10           184k ± 0%      181k ± 0%   -1.74%  (p=0.029 n=4+4)
SiteNew/Regular_List_terms-10                49.9k ± 0%     48.5k ± 0%   -2.79%  (p=0.029 n=4+4)
```
2022-03-16 11:32:39 +01:00
Bjørn Erik Pedersen 42cc5f88b6 tpl: Adjustments and an integration test for Go 1.18
Updates #9677
2022-03-16 10:36:46 +01:00
Bjørn Erik Pedersen a6488e7bad Remove Go 1.17 support
Go 1.18 had some breaking changes on source level meaning we cannot build on older Go versions anymore.

The improvements in Go 1.18 (e.g. `break` and `continue`)  were to good to pass on.

Note that you don't need Go (or Go 1.18) to run a pre-built binary.

Updates #9677
2022-03-16 08:57:58 +01:00
Bjørn Erik Pedersen 65a78cae1e tpl: Sync go_templates for Go 1.18
Using Go tag go1.18 4aa1efed4853ea067d665a952eee77c52faac774

Updates #9677
2022-03-16 08:54:25 +01:00
Bjørn Erik Pedersen 4d6d1d08da build: Bump to Go 1.18
Updates #9677
2022-03-16 08:43:18 +01:00
Bjørn Erik Pedersen b60e1bbdfe dartsass: Improve error message when no read access
Fixes #9662
2022-03-15 16:34:54 +01:00
Cathrine Paulsen 61cf3c9f63 Fix and refactor typos 2022-03-14 19:39:07 +01:00
Bjørn Erik Pedersen 31fbc081c9 Improve server startup/shutdown
Closes #9671
2022-03-14 19:38:17 +01:00
Bjørn Erik Pedersen cebd886ac1 commands: Improve server tests
Updates #9647
2022-03-14 09:16:33 +01:00
hugoreleaser 38f778cfcc releaser: Prepare repository for 0.95.0-DEV
[ci skip]
2022-03-12 11:02:38 +00:00
hugoreleaser 48fb9e4de3 releaser: Bump versions for release of 0.94.2
[ci skip]
2022-03-12 10:28:41 +00:00
hugoreleaser 0958167b38 releaser: Add release notes for 0.94.2
[ci skip]
2022-03-12 10:28:41 +00:00
Bjørn Erik Pedersen b37183e48d deps: Update github.com/yuin/goldmark v1.4.9 => v1.4.10
Fixes #9658
2022-03-12 11:24:11 +01:00
hugoreleaser 04ccde3e94 releaser: Prepare repository for 0.95.0-DEV
[ci skip]
2022-03-11 14:30:03 +00:00
hugoreleaser 0fcd9a5d87 releaser: Bump versions for release of 0.94.1
[ci skip]
2022-03-11 13:53:32 +00:00
hugoreleaser 4146084367 releaser: Add release notes for 0.94.1
[ci skip]
2022-03-11 13:53:32 +00:00
Bjørn Erik Pedersen 3bc742bea0 docs: Regenerate CLI docs 2022-03-11 14:47:27 +01:00
Bjørn Erik Pedersen 1a796d723c deps: Fix Goldmark regression with HTML comments
Fixes #9650
2022-03-11 14:44:01 +01:00
Bjørn Erik Pedersen 64b7b7a897 Revert "Allow rendering static files to disk and dynamic to memory in server mode"
This reverts commit 7d8011ed63.

Updates #9647
2022-03-11 13:10:47 +01:00
Bjørn Erik Pedersen 5ef8a9f32c Revert "Some minor adjustments to the new static filesystem logic"
This reverts commit 63bb2a5b18.
2022-03-11 13:10:47 +01:00
hugoreleaser 3bc34666c8 releaser: Prepare repository for 0.95.0-DEV
[ci skip]
2022-03-10 10:25:00 +00:00
hugoreleaser 63b236603e releaser: Bump versions for release of 0.94.0
[ci skip]
2022-03-10 09:46:35 +00:00
hugoreleaser b107b4b291 releaser: Add release notes for 0.94.0
[ci skip]
2022-03-10 09:46:35 +00:00
Bjørn Erik Pedersen a4ac188c19 docs: Regenerate docshelper 2022-03-10 10:08:04 +01:00
Bjørn Erik Pedersen b82d95575d Revert "markup/highlight: Add hl_inline option"
This reverts commit a360cab75a.
2022-03-10 10:06:22 +01:00
Bjørn Erik Pedersen 4e14cf7607 Fail with error when double-rendering text in markdownify/RenderString
This commit prevents the most commons case of infinite recursion in link render hooks when the `linkify` option is enabled (see below). This is always a user error, but getting a `stack overflow` (the current stack limit in Go is 1 GB on 64-bit, 250 MB on 32-bit) error isn't very helpful. This fix will not prevent all such errors, though, but we may do better once #9570 is in place.

So, these will fail:

```
<a href="{{ .Destination | safeURL }}" >{{ .Text | markdownify }}</a>
<a href="{{ .Destination | safeURL }}" >{{ .Text | .Page.RenderString }}</a>
```

`.Text` is already rendered to `HTML`. The above needs to be rewritten to:

```
<a href="{{ .Destination | safeURL }}" >{{ .Text | safeHTML }}</a>
<a href="{{ .Destination | safeURL }}" >{{ .Text | safeHTML }}</a>
```

Fixes #8959
2022-03-10 08:19:03 +01:00
Bjørn Erik Pedersen 5697348e17 markup/goldmark: Default to https for linkify
Fixes #9639
2022-03-09 22:30:10 +01:00
Joe Mooring f98e570b17 Add lang attribute to internal alias template
Closes #9586
2022-03-09 19:06:59 +01:00
Bjørn Erik Pedersen cdb8b0842b docs: Regenerate docshelper 2022-03-09 11:33:53 +01:00
Bjørn Erik Pedersen a360cab75a markup/highlight: Add hl_inline option
If set to true, the highlighted code will not be wrapped in any div.

Closes #9442
2022-03-09 11:33:13 +01:00
Joe Mooring c97fed08f9 minifiers: Make keepWhitespace = false default for HTML (note)
Closes #9456
2022-03-09 07:48:18 +01:00
Bjørn Erik Pedersen 53a6210d82 markup/goldmark/codeblocks: Fix slice bounds out of range
For the Position in code blocks we try to match the .Inner with the original source. This isn't always possible.

This commits avoids panics in these situations.

Fixes #9627
2022-03-08 21:50:21 +01:00
Bjørn Erik Pedersen 7182809d96 docs: Regenerate CLI docs 2022-03-08 19:40:50 +01:00
Bjørn Erik Pedersen 1f8cd2614d docs: Regenerate docshelper 2022-03-08 19:40:07 +01:00
Bjørn Erik Pedersen 9d76b8fa34 Merge commit 'd706529720b3b2ccb99719ccd578062ca25a0cc2' 2022-03-08 19:39:33 +01:00
Bjørn Erik Pedersen d706529720 Squashed 'docs/' changes from 3f95a2ace..a393f4cf4
a393f4cf4 Add a Spellcheck GitHub Action and config
8b6b1c381 netlify: Bump to Hugo 0.93.3
84515c183 Delete deployment-with-nanobox.md
dd45f9899 Fix typos in docs
e69de81a9 Update build-options.md
7745b7891 netlify: Hubo 0.93.2
037d63364 Clarify GitHub Pages Branches
94660c34b add missing %s
325de15e2 fix link to latest release note since the release notes were moved to GitHub: https://gohugo.io/news/no-more-releasenotes-here/
dbff41d01 Update introduction.md
0ecd627f7 netlify: Hugo 0.93.1
a74e16582 Update diagrams.md
33e310956 Add Goat example to test styling
fa0100a5b Update diagrams.md
64ac75367 Adjust diagram docs
f1d600044 Update theme
95bedff1a netlify: Bump to Hugo 0.93.0
849a8437f Merge commit 'c1398b91a9f4c67876b31feb67516b252e654d3c'
c0c60c43c docs: Regenerate docs helper
2c63fe518 cod: Regen CLI docs
f33ba4e5a CodeblockContext method renames
979b47968 Move the Goat template to the correct place
2df37e9e8 Add Markdown diagrams and render hooks for code blocks
bd8037d43 Allow images to be cropped without being resized
8b2af4b49 modules: Add modules.Workspace config for Go 1.18
46b99dea1 Add --printUnusedTemplates
1285302c9 commands: Rename --i18n-warnings to printI18nWarnings
dea2242c6 commands: Rename --path-warnings, --print-men to --printPathWarnings, --printMemoryUsage
db782ea46 deps: Update github.com/alecthomas/chroma v0.9.4 => v0.10.0

git-subtree-dir: docs
git-subtree-split: a393f4cf43
2022-03-08 19:37:17 +01:00
Bjørn Erik Pedersen 4576c82ed4 Cache reflect.MethodByName
The isolated benchmark for the function is obviously much faster:

```bash
name                old time/op    new time/op    delta
GetMethodByName-10    1.21µs ± 7%    0.23µs ± 5%   -81.42%  (p=0.029 n=4+4)

name                old alloc/op   new alloc/op   delta
GetMethodByName-10      680B ± 0%        0B       -100.00%  (p=0.029 n=4+4)

name                old allocs/op  new allocs/op  delta
GetMethodByName-10      20.0 ± 0%       0.0       -100.00%  (p=0.029 n=4+4)
```

But more pleasing is the overall performance looking at the site benchmarks:

```bash
name                                      old time/op    new time/op    delta
SiteNew/Regular_Bundle_with_image-10        6.25ms ± 2%    6.10ms ± 2%     ~     (p=0.057 n=4+4)
SiteNew/Regular_Bundle_with_JSON_file-10    6.30ms ± 2%    5.66ms ±11%     ~     (p=0.057 n=4+4)
SiteNew/Regular_Tags_and_categories-10      22.2ms ± 2%    17.4ms ± 1%  -21.88%  (p=0.029 n=4+4)
SiteNew/Regular_Canonify_URLs-10             108ms ± 0%     107ms ± 0%   -1.20%  (p=0.029 n=4+4)
SiteNew/Regular_Deep_content_tree-10        36.1ms ± 1%    33.8ms ± 1%   -6.44%  (p=0.029 n=4+4)
SiteNew/Regular_TOML_front_matter-10        24.9ms ± 1%    22.6ms ± 1%   -9.30%  (p=0.029 n=4+4)
SiteNew/Regular_Many_HTML_templates-10      17.9ms ± 1%    16.7ms ± 1%   -6.43%  (p=0.029 n=4+4)
SiteNew/Regular_Page_collections-10         23.3ms ± 1%    22.0ms ± 0%   -5.58%  (p=0.029 n=4+4)
SiteNew/Regular_List_terms-10               8.00ms ± 1%    7.63ms ± 0%   -4.62%  (p=0.029 n=4+4)

name                                      old alloc/op   new alloc/op   delta
SiteNew/Regular_Bundle_with_image-10        2.10MB ± 0%    2.07MB ± 0%   -1.46%  (p=0.029 n=4+4)
SiteNew/Regular_Bundle_with_JSON_file-10    1.88MB ± 0%    1.85MB ± 0%   -1.76%  (p=0.029 n=4+4)
SiteNew/Regular_Tags_and_categories-10      13.5MB ± 0%    11.6MB ± 0%  -13.99%  (p=0.029 n=4+4)
SiteNew/Regular_Canonify_URLs-10            96.1MB ± 0%    95.8MB ± 0%   -0.40%  (p=0.029 n=4+4)
SiteNew/Regular_Deep_content_tree-10        28.4MB ± 0%    27.3MB ± 0%   -3.83%  (p=0.029 n=4+4)
SiteNew/Regular_TOML_front_matter-10        16.9MB ± 0%    15.1MB ± 0%  -10.58%  (p=0.029 n=4+4)
SiteNew/Regular_Many_HTML_templates-10      8.98MB ± 0%    8.44MB ± 0%   -6.04%  (p=0.029 n=4+4)
SiteNew/Regular_Page_collections-10         17.1MB ± 0%    16.5MB ± 0%   -3.91%  (p=0.029 n=4+4)
SiteNew/Regular_List_terms-10               3.92MB ± 0%    3.72MB ± 0%   -5.03%  (p=0.029 n=4+4)

name                                      old allocs/op  new allocs/op  delta
SiteNew/Regular_Bundle_with_image-10         25.8k ± 0%     24.9k ± 0%   -3.49%  (p=0.029 n=4+4)
SiteNew/Regular_Bundle_with_JSON_file-10     25.8k ± 0%     24.9k ± 0%   -3.49%  (p=0.029 n=4+4)
SiteNew/Regular_Tags_and_categories-10        288k ± 0%      233k ± 0%  -18.90%  (p=0.029 n=4+4)
SiteNew/Regular_Canonify_URLs-10              375k ± 0%      364k ± 0%   -2.80%  (p=0.029 n=4+4)
SiteNew/Regular_Deep_content_tree-10          314k ± 0%      283k ± 0%   -9.77%  (p=0.029 n=4+4)
SiteNew/Regular_TOML_front_matter-10          302k ± 0%      252k ± 0%  -16.55%  (p=0.029 n=4+4)
SiteNew/Regular_Many_HTML_templates-10        133k ± 0%      117k ± 0%  -11.81%  (p=0.029 n=4+4)
SiteNew/Regular_Page_collections-10           202k ± 0%      183k ± 0%   -9.55%  (p=0.029 n=4+4)
SiteNew/Regular_List_terms-10                55.6k ± 0%     49.8k ± 0%  -10.40%  (p=0.029 n=4+4)
```

Thanks to @quasilyte for the suggestion.

Fixes 9386
2022-03-08 19:36:55 +01:00
Bjørn Erik Pedersen ff02d41721 releaser: Remove the GitHub link syntax around release contributors
Fixes #9581
2022-03-08 19:36:31 +01:00
Bjørn Erik Pedersen 63bb2a5b18 Some minor adjustments to the new static filesystem logic
Closes #9625
2022-03-08 19:27:54 +01:00
SatowTakeshi 7d8011ed63 Allow rendering static files to disk and dynamic to memory in server mode
Updates #9625
2022-03-08 19:27:54 +01:00
dependabot[bot] b9a1be2f99 build(deps): bump github.com/kyokomi/emoji/v2 from 2.2.8 to 2.2.9
Bumps [github.com/kyokomi/emoji/v2](https://github.com/kyokomi/emoji) from 2.2.8 to 2.2.9.
- [Release notes](https://github.com/kyokomi/emoji/releases)
- [Commits](https://github.com/kyokomi/emoji/compare/v2.2.8...v2.2.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-08 16:10:04 +01:00
dependabot[bot] 0978245039 build(deps): bump github.com/evanw/esbuild from 0.14.23 to 0.14.25
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.14.23 to 0.14.25.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/master/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.14.23...v0.14.25)

---
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>
2022-03-08 16:09:17 +01:00
dependabot[bot] ff37df830c build(deps): bump github.com/niklasfasching/go-org from 1.6.0 to 1.6.2
Bumps [github.com/niklasfasching/go-org](https://github.com/niklasfasching/go-org) from 1.6.0 to 1.6.2.
- [Release notes](https://github.com/niklasfasching/go-org/releases)
- [Commits](https://github.com/niklasfasching/go-org/compare/v1.6.0...v1.6.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-08 16:08:32 +01:00
dependabot[bot] 5857d5524a build(deps): bump github.com/getkin/kin-openapi from 0.90.0 to 0.91.0
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.90.0 to 0.91.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.90.0...v0.91.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>
2022-03-08 16:05:40 +01:00
hugoreleaser d86eca5bb2 releaser: Prepare repository for 0.94.0-DEV
[ci skip]
2022-03-08 10:50:30 +00:00
488 changed files with 5312 additions and 3600 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
defaults: &defaults
docker:
- image: bepsays/ci-goreleaser:1.1700.600
- image: bepsays/ci-goreleaser:1.1800.000
environment:
CGO_ENABLED: "0"
+6 -2
View File
@@ -7,7 +7,11 @@ jobs:
GO111MODULE: on
strategy:
matrix:
go-version: [1.16.x, 1.17.x]
# Note: We upgraded to Go 1.18 in Hugo v0.95.0
# Go 1.18 had some breaking changes on the source level which means Hugo cannot be built
# with older Go versions, but the improvements in Go 1.18 were too good to pass on (e.g. break and continue).
# Note that you don't need Go (or Go 1.18) to run a pre-built binary.
go-version: [1.18.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
@@ -24,7 +28,7 @@ jobs:
with:
python-version: '3.x'
- name: Install Mage
run: go get github.com/magefile/mage@07afc7d24f4d6d6442305d49552f04fbda5ccb3e
run: go install github.com/magefile/mage@07afc7d24f4d6d6442305d49552f04fbda5ccb3e
- name: Install asciidoctor
uses: reitzig/actions-asciidoctor@7570212ae20b63653481675fb1ff62d1073632b0
- name: Checkout code
+3 -1
View File
@@ -1,5 +1,7 @@
# Contributing to Hugo
**Note March 16th 2022:** We are currently very constrained on human resources to do code reviews, so we currently require any new Pull Requests to be limited to bug fixes closing an existing issue. Also, we have updated to Go 1.18, but we will currently not accept any generic rewrites, "interface{} to any" replacements and similar.
We welcome contributions to Hugo of any kind including documentation, themes,
organization, tutorials, blog posts, bug reports, issues, feature requests,
feature implementations, pull requests, answering questions on the forum,
@@ -124,7 +126,7 @@ go install
For some convenient build and test targets, you also will want to install Mage:
```bash
go get github.com/magefile/mage
go install github.com/magefile/mage
```
Now, to make a change to Hugo's source:
+2 -2
View File
@@ -2,7 +2,7 @@
# Twitter: https://twitter.com/gohugoio
# Website: https://gohugo.io/
FROM golang:1.16-alpine AS build
FROM golang:1.18-alpine AS build
# Optionally set HUGO_BUILD_TAGS to "extended" or "nodeploy" when building like so:
# docker build --build-arg HUGO_BUILD_TAGS=extended .
@@ -20,7 +20,7 @@ COPY . /go/src/github.com/gohugoio/hugo/
# gcc/g++ are required to build SASS libraries for extended version
RUN apk update && \
apk add --no-cache gcc g++ musl-dev git && \
go get github.com/magefile/mage
go install github.com/magefile/mage
RUN mage hugo && mage install
+4 -2
View File
@@ -54,11 +54,11 @@ Use the [installation instructions in the Hugo documentation](https://gohugo.io/
#### Prerequisite Tools
* [Git](https://git-scm.com/)
* [Go (we test it with the last 2 major versions; but note that Hugo 0.81.0 only builds with >= Go 1.16.)](https://golang.org/dl/)
* [Go (we test it with the last 2 major versions; but note that Hugo 0.95.0 only builds with >= Go 1.18.)](https://golang.org/dl/)
#### Fetch from GitHub
Since Hugo 0.48, Hugo uses the Go Modules support built into Go 1.11 to build. The easiest is to clone Hugo in a directory outside of `GOPATH`, as in the following example:
To fetch and build the source from GitHub:
```bash
mkdir $HOME/src
@@ -85,6 +85,8 @@ git clone git@github.com:gohugoio/hugo.git
```
## Contributing to Hugo
**Note March 16th 2022:** We are currently very constrained on human resources to do code reviews, so we currently require any new Pull Requests to be limited to bug fixes closing an existing issue. Also, we have updated to Go 1.18, but we will currently not accept any generic rewrites, "interface{} to any" replacements and similar.
For a complete guide to contributing to Hugo, see the [Contribution Guide](CONTRIBUTING.md).
We welcome contributions to Hugo of any kind including documentation, themes,
+1 -1
View File
@@ -20,7 +20,7 @@ import (
)
var bufferPool = &sync.Pool{
New: func() interface{} {
New: func() any {
return &bytes.Buffer{}
},
}
+2 -2
View File
@@ -30,7 +30,7 @@ type Cache struct {
}
type cacheEntry struct {
value interface{}
value any
err error
}
@@ -55,7 +55,7 @@ func (c *Cache) Clear() {
// create will be called and cached.
// This method is thread safe. It also guarantees that the create func for a given
// key is invoked only once for this cache.
func (c *Cache) GetOrCreate(key string, create func() (interface{}, error)) (interface{}, error) {
func (c *Cache) GetOrCreate(key string, create func() (any, error)) (any, error) {
c.mu.RLock()
entry, found := c.cache[key]
c.mu.RUnlock()
+3 -3
View File
@@ -28,7 +28,7 @@ func TestNamedCache(t *testing.T) {
cache := New()
counter := 0
create := func() (interface{}, error) {
create := func() (any, error) {
counter++
return counter, nil
}
@@ -58,8 +58,8 @@ func TestNamedCacheConcurrent(t *testing.T) {
cache := New()
create := func(i int) func() (interface{}, error) {
return func() (interface{}, error) {
create := func(i int) func() (any, error) {
return func() (any, error) {
return i, nil
}
}
+1 -1
View File
@@ -505,7 +505,7 @@ func typeName(name, pkg string) string {
func uniqueNonEmptyStrings(s []string) []string {
var unique []string
set := map[string]interface{}{}
set := map[string]any{}
for _, val := range s {
if val == "" {
continue
+1 -1
View File
@@ -85,7 +85,7 @@ type I interface {
Method3(myint int, mystring string)
Method5() (string, error)
Method6() *net.IP
Method7() interface{}
Method7() any
Method8() herrors.ErrorContext
method2()
method9() os.FileInfo
+12 -4
View File
@@ -17,6 +17,7 @@ import (
"bytes"
"errors"
"io/ioutil"
"net"
"os"
"path/filepath"
"regexp"
@@ -88,7 +89,8 @@ type commandeer struct {
// Used in cases where we get flooded with events in server mode.
debounce func(f func())
serverPorts []int
serverPorts []serverPortListener
languagesConfigured bool
languages langs.Languages
doLiveReload bool
@@ -105,6 +107,11 @@ type commandeer struct {
buildErr error
}
type serverPortListener struct {
p int
ln net.Listener
}
func newCommandeerHugoState() *commandeerHugoState {
return &commandeerHugoState{
created: make(chan struct{}),
@@ -120,14 +127,14 @@ func (c *commandeer) errCount() int {
return int(c.logger.LogCounters().ErrorCounter.Count())
}
func (c *commandeer) getErrorWithContext() interface{} {
func (c *commandeer) getErrorWithContext() any {
errCount := c.errCount()
if errCount == 0 {
return nil
}
m := make(map[string]interface{})
m := make(map[string]any)
m["Error"] = errors.New(removeErrorPrefixFromLog(c.logger.Errors()))
m["Version"] = hugo.BuildVersionString()
@@ -146,7 +153,7 @@ func (c *commandeer) getErrorWithContext() interface{} {
return m
}
func (c *commandeer) Set(key string, value interface{}) {
func (c *commandeer) Set(key string, value any) {
if c.configured {
panic("commandeer cannot be changed")
}
@@ -420,6 +427,7 @@ func (c *commandeer) loadConfig() error {
if h == nil || c.failOnInitErr {
err = createErr
}
c.hugoSites = h
// TODO(bep) improve.
if c.buildLock == nil && h != nil {
+2 -2
View File
@@ -150,7 +150,7 @@ func (b *commandsBuilder) newHugoCmd() *hugoCmd {
Hugo is a Fast and Flexible Static Site Generator
built with love by spf13 and friends in Go.
Complete documentation is available at http://gohugo.io/.`,
Complete documentation is available at https://gohugo.io/.`,
RunE: func(cmd *cobra.Command, args []string) error {
defer cc.timeTrack(time.Now(), "Total")
cfgInit := func(c *commandeer) error {
@@ -294,7 +294,7 @@ func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) {
cmd.Flags().BoolP("ignoreCache", "", false, "ignores the cache directory")
cmd.Flags().StringP("destination", "d", "", "filesystem path to write files to")
cmd.Flags().StringSliceP("theme", "t", []string{}, "themes to use (located in /themes/THEMENAME/)")
cmd.Flags().StringVarP(&cc.baseURL, "baseURL", "b", "", "hostname (and path) to the root, e.g. http://spf13.com/")
cmd.Flags().StringVarP(&cc.baseURL, "baseURL", "b", "", "hostname (and path) to the root, e.g. https://spf13.com/")
cmd.Flags().Bool("enableGitInfo", false, "add Git revision, date, author, and CODEOWNERS info to the pages")
cmd.Flags().BoolVar(&cc.gc, "gc", false, "enable to run some cleanup tasks (remove unused cache files) after the build")
cmd.Flags().StringVar(&cc.poll, "poll", "", "set this to a poll interval, e.g --poll 700ms, to use a poll based approach to watch for file system changes")
+2
View File
@@ -366,6 +366,8 @@ URL = "hugocloud://hugotestbucket"
writeFile(t, filepath.Join(d, "config", "testing", "params.toml"), `myparam="paramtesting"`)
writeFile(t, filepath.Join(d, "config", "production", "params.toml"), `myparam="paramproduction"`)
writeFile(t, filepath.Join(d, "static", "myfile.txt"), `Hello World!`)
writeFile(t, filepath.Join(d, contentDir, "p1.md"), `
---
title: "P1"
+7 -7
View File
@@ -145,13 +145,13 @@ func (m *modMounts) MarshalJSON() ([]byte, error) {
if m.verbose {
config := m.m.Config()
return json.Marshal(&struct {
Path string `json:"path"`
Version string `json:"version"`
Time time.Time `json:"time"`
Owner string `json:"owner"`
Dir string `json:"dir"`
Meta map[string]interface{} `json:"meta"`
HugoVersion modules.HugoVersion `json:"hugoVersion"`
Path string `json:"path"`
Version string `json:"version"`
Time time.Time `json:"time"`
Owner string `json:"owner"`
Dir string `json:"dir"`
Meta map[string]any `json:"meta"`
HugoVersion modules.HugoVersion `json:"hugoVersion"`
Mounts []modMount `json:"mounts"`
}{
+1 -1
View File
@@ -202,7 +202,7 @@ func (cc *convertCmd) convertAndSavePage(p page.Page, site *hugolib.Site, target
type parsedFile struct {
frontMatterFormat metadecoders.Format
frontMatterSource []byte
frontMatter map[string]interface{}
frontMatter map[string]any
// Everything after Front Matter
content []byte
+1
View File
@@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !nodeploy
// +build !nodeploy
package commands
+1 -1
View File
@@ -49,7 +49,7 @@ url: %s
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 http://gohugo.io/.
of Hugo's command-line interface for https://gohugo.io/.
It creates one Markdown file per command with front matter suitable
for rendering in Hugo.`,
+3 -3
View File
@@ -53,15 +53,15 @@ func (c commandError) isUserError() bool {
return c.userError
}
func newUserError(a ...interface{}) commandError {
func newUserError(a ...any) commandError {
return commandError{s: fmt.Sprintln(a...), userError: true}
}
func newSystemError(a ...interface{}) commandError {
func newSystemError(a ...any) commandError {
return commandError{s: fmt.Sprintln(a...), userError: false}
}
func newSystemErrorF(format string, a ...interface{}) commandError {
func newSystemErrorF(format string, a ...any) commandError {
return commandError{s: fmt.Sprintf(format, a...), userError: false}
}
+5 -5
View File
@@ -235,7 +235,7 @@ func (i *importCmd) createSiteFromJekyll(jekyllRoot, targetDir string, jekyllPos
return nil
}
func (i *importCmd) loadJekyllConfig(fs afero.Fs, jekyllRoot string) map[string]interface{} {
func (i *importCmd) loadJekyllConfig(fs afero.Fs, jekyllRoot string) map[string]any {
path := filepath.Join(jekyllRoot, "_config.yml")
exists, err := helpers.Exists(path, fs)
@@ -265,7 +265,7 @@ func (i *importCmd) loadJekyllConfig(fs afero.Fs, jekyllRoot string) map[string]
return c
}
func (i *importCmd) createConfigFromJekyll(fs afero.Fs, inpath string, kind metadecoders.Format, jekyllConfig map[string]interface{}) (err error) {
func (i *importCmd) createConfigFromJekyll(fs afero.Fs, inpath string, kind metadecoders.Format, jekyllConfig map[string]any) (err error) {
title := "My New Hugo Site"
baseURL := "http://example.org/"
@@ -285,7 +285,7 @@ func (i *importCmd) createConfigFromJekyll(fs afero.Fs, inpath string, kind meta
}
}
in := map[string]interface{}{
in := map[string]any{
"baseURL": baseURL,
"title": title,
"languageCode": "en-us",
@@ -423,7 +423,7 @@ func convertJekyllPost(path, relPath, targetDir string, draft bool) error {
return nil
}
func convertJekyllMetaData(m interface{}, postName string, postDate time.Time, draft bool) (interface{}, error) {
func convertJekyllMetaData(m any, postName string, postDate time.Time, draft bool) (any, error) {
metadata, err := maps.ToStringMapE(m)
if err != nil {
return nil, err
@@ -475,7 +475,7 @@ func convertJekyllMetaData(m interface{}, postName string, postDate time.Time, d
return metadata, nil
}
func convertJekyllContent(m interface{}, content string) (string, error) {
func convertJekyllContent(m any, content string) (string, error) {
metadata, _ := maps.ToStringMapE(m)
lines := strings.Split(content, "\n")
+23 -23
View File
@@ -47,44 +47,44 @@ func TestParseJekyllFilename(t *testing.T) {
func TestConvertJekyllMetadata(t *testing.T) {
c := qt.New(t)
testDataList := []struct {
metadata interface{}
metadata any
postName string
postDate time.Time
draft bool
expect string
}{
{
map[interface{}]interface{}{},
map[any]any{},
"testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false,
`{"date":"2015-10-01T00:00:00Z"}`,
},
{
map[interface{}]interface{}{},
map[any]any{},
"testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), true,
`{"date":"2015-10-01T00:00:00Z","draft":true}`,
},
{
map[interface{}]interface{}{"Permalink": "/permalink.html", "layout": "post"},
map[any]any{"Permalink": "/permalink.html", "layout": "post"},
"testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false,
`{"date":"2015-10-01T00:00:00Z","url":"/permalink.html"}`,
},
{
map[interface{}]interface{}{"permalink": "/permalink.html"},
map[any]any{"permalink": "/permalink.html"},
"testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false,
`{"date":"2015-10-01T00:00:00Z","url":"/permalink.html"}`,
},
{
map[interface{}]interface{}{"category": nil, "permalink": 123},
map[any]any{"category": nil, "permalink": 123},
"testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false,
`{"date":"2015-10-01T00:00:00Z"}`,
},
{
map[interface{}]interface{}{"Excerpt_Separator": "sep"},
map[any]any{"Excerpt_Separator": "sep"},
"testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false,
`{"date":"2015-10-01T00:00:00Z","excerpt_separator":"sep"}`,
},
{
map[interface{}]interface{}{"category": "book", "layout": "post", "Others": "Goods", "Date": "2015-10-01 12:13:11"},
map[any]any{"category": "book", "layout": "post", "Others": "Goods", "Date": "2015-10-01 12:13:11"},
"testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false,
`{"Others":"Goods","categories":["book"],"date":"2015-10-01T12:13:11Z"}`,
},
@@ -102,69 +102,69 @@ func TestConvertJekyllMetadata(t *testing.T) {
func TestConvertJekyllContent(t *testing.T) {
c := qt.New(t)
testDataList := []struct {
metadata interface{}
metadata any
content string
expect string
}{
{
map[interface{}]interface{}{},
map[any]any{},
"Test content\r\n<!-- more -->\npart2 content", "Test content\n<!--more-->\npart2 content",
},
{
map[interface{}]interface{}{},
map[any]any{},
"Test content\n<!-- More -->\npart2 content", "Test content\n<!--more-->\npart2 content",
},
{
map[interface{}]interface{}{"excerpt_separator": "<!--sep-->"},
map[any]any{"excerpt_separator": "<!--sep-->"},
"Test content\n<!--sep-->\npart2 content",
"---\nexcerpt_separator: <!--sep-->\n---\nTest content\n<!--more-->\npart2 content",
},
{map[interface{}]interface{}{}, "{% raw %}text{% endraw %}", "text"},
{map[interface{}]interface{}{}, "{%raw%} text2 {%endraw %}", "text2"},
{map[any]any{}, "{% raw %}text{% endraw %}", "text"},
{map[any]any{}, "{%raw%} text2 {%endraw %}", "text2"},
{
map[interface{}]interface{}{},
map[any]any{},
"{% highlight go %}\nvar s int\n{% endhighlight %}",
"{{< highlight go >}}\nvar s int\n{{< / highlight >}}",
},
{
map[interface{}]interface{}{},
map[any]any{},
"{% highlight go linenos hl_lines=\"1 2\" %}\nvar s string\nvar i int\n{% endhighlight %}",
"{{< highlight go \"linenos=table,hl_lines=1 2\" >}}\nvar s string\nvar i int\n{{< / highlight >}}",
},
// Octopress image tag
{
map[interface{}]interface{}{},
map[any]any{},
"{% img http://placekitten.com/890/280 %}",
"{{< figure src=\"http://placekitten.com/890/280\" >}}",
},
{
map[interface{}]interface{}{},
map[any]any{},
"{% img left http://placekitten.com/320/250 Place Kitten #2 %}",
"{{< figure class=\"left\" src=\"http://placekitten.com/320/250\" title=\"Place Kitten #2\" >}}",
},
{
map[interface{}]interface{}{},
map[any]any{},
"{% img right http://placekitten.com/300/500 150 250 'Place Kitten #3' %}",
"{{< figure class=\"right\" src=\"http://placekitten.com/300/500\" width=\"150\" height=\"250\" title=\"Place Kitten #3\" >}}",
},
{
map[interface{}]interface{}{},
map[any]any{},
"{% img right http://placekitten.com/300/500 150 250 'Place Kitten #4' 'An image of a very cute kitten' %}",
"{{< figure class=\"right\" src=\"http://placekitten.com/300/500\" width=\"150\" height=\"250\" title=\"Place Kitten #4\" alt=\"An image of a very cute kitten\" >}}",
},
{
map[interface{}]interface{}{},
map[any]any{},
"{% img http://placekitten.com/300/500 150 250 'Place Kitten #4' 'An image of a very cute kitten' %}",
"{{< figure src=\"http://placekitten.com/300/500\" width=\"150\" height=\"250\" title=\"Place Kitten #4\" alt=\"An image of a very cute kitten\" >}}",
},
{
map[interface{}]interface{}{},
map[any]any{},
"{% img right /placekitten/300/500 'Place Kitten #4' 'An image of a very cute kitten' %}",
"{{< figure class=\"right\" src=\"/placekitten/300/500\" title=\"Place Kitten #4\" alt=\"An image of a very cute kitten\" >}}",
},
{
map[interface{}]interface{}{"category": "book", "layout": "post", "Date": "2015-10-01 12:13:11"},
map[any]any{"category": "book", "layout": "post", "Date": "2015-10-01 12:13:11"},
"somecontent",
"---\nDate: \"2015-10-01 12:13:11\"\ncategory: book\nlayout: post\n---\nsomecontent",
},
+1
View File
@@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !darwin
// +build !darwin
package commands
+5 -5
View File
@@ -32,7 +32,7 @@ type listCmd struct {
*baseBuilderCmd
}
func (lc *listCmd) buildSites(config map[string]interface{}) (*hugolib.HugoSites, error) {
func (lc *listCmd) buildSites(config map[string]any) (*hugolib.HugoSites, error) {
cfgInit := func(c *commandeer) error {
for key, value := range config {
c.Set(key, value)
@@ -75,7 +75,7 @@ List requires a subcommand, e.g. ` + "`hugo list drafts`.",
Short: "List all drafts",
Long: `List all of the drafts in your content directory.`,
RunE: func(cmd *cobra.Command, args []string) error {
sites, err := cc.buildSites(map[string]interface{}{"buildDrafts": true})
sites, err := cc.buildSites(map[string]any{"buildDrafts": true})
if err != nil {
return newSystemError("Error building sites", err)
}
@@ -94,7 +94,7 @@ List requires a subcommand, e.g. ` + "`hugo list drafts`.",
Short: "List all posts dated in the future",
Long: `List all of the posts in your content directory which will be posted in the future.`,
RunE: func(cmd *cobra.Command, args []string) error {
sites, err := cc.buildSites(map[string]interface{}{"buildFuture": true})
sites, err := cc.buildSites(map[string]any{"buildFuture": true})
if err != nil {
return newSystemError("Error building sites", err)
}
@@ -122,7 +122,7 @@ List requires a subcommand, e.g. ` + "`hugo list drafts`.",
Short: "List all posts already expired",
Long: `List all of the posts in your content directory which has already expired.`,
RunE: func(cmd *cobra.Command, args []string) error {
sites, err := cc.buildSites(map[string]interface{}{"buildExpired": true})
sites, err := cc.buildSites(map[string]any{"buildExpired": true})
if err != nil {
return newSystemError("Error building sites", err)
}
@@ -150,7 +150,7 @@ List requires a subcommand, e.g. ` + "`hugo list drafts`.",
Short: "List all posts",
Long: `List all of the posts in your content directory, include drafts, future and expired pages.`,
RunE: func(cmd *cobra.Command, args []string) error {
sites, err := cc.buildSites(map[string]interface{}{
sites, err := cc.buildSites(map[string]any{
"buildExpired": true,
"buildDrafts": true,
"buildFuture": true,
+1
View File
@@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build nodeploy
// +build nodeploy
package commands
+1
View File
@@ -1,3 +1,4 @@
//go:build release
// +build release
// Copyright 2017-present The Hugo Authors. All rights reserved.
+1
View File
@@ -1,3 +1,4 @@
//go:build !release
// +build !release
// Copyright 2018 The Hugo Authors. All rights reserved.
+68 -38
View File
@@ -15,6 +15,7 @@ package commands
import (
"bytes"
"context"
"fmt"
"io"
"net"
@@ -32,6 +33,7 @@ import (
"time"
"github.com/gohugoio/hugo/common/paths"
"golang.org/x/sync/errgroup"
"github.com/pkg/errors"
@@ -46,7 +48,7 @@ import (
type serverCmd struct {
// Can be used to stop the server. Useful in tests
stop <-chan bool
stop chan bool
disableLiveReload bool
navigateToChanged bool
@@ -68,7 +70,7 @@ func (b *commandsBuilder) newServerCmd() *serverCmd {
return b.newServerCmdSignaled(nil)
}
func (b *commandsBuilder) newServerCmdSignaled(stop <-chan bool) *serverCmd {
func (b *commandsBuilder) newServerCmdSignaled(stop chan bool) *serverCmd {
cc := &serverCmd{stop: stop}
cc.baseBuilderCmd = b.newBuilderCmd(&cobra.Command{
@@ -87,7 +89,13 @@ By default hugo will also watch your files for any changes you make and
automatically rebuild the site. It will then live reload any open browser pages
and push the latest content to them. As most Hugo sites are built in a fraction
of a second, you will be able to save and see your changes nearly instantly.`,
RunE: cc.server,
RunE: func(cmd *cobra.Command, args []string) error {
err := cc.server(cmd, args)
if err != nil && cc.stop != nil {
cc.stop <- true
}
return err
},
})
cc.cmd.Flags().IntVarP(&cc.serverPort, "port", "p", 1313, "port on which the server will listen")
@@ -128,8 +136,6 @@ func (f noDirFile) Readdir(count int) ([]os.FileInfo, error) {
return nil, nil
}
var serverPorts []int
func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
// If a Destination is provided via flag write to disk
destination, _ := cmd.Flags().GetString("destination")
@@ -139,7 +145,7 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
var serverCfgInit sync.Once
cfgInit := func(c *commandeer) error {
cfgInit := func(c *commandeer) (rerr error) {
c.Set("renderToMemory", !sc.renderToDisk)
if cmd.Flags().Changed("navigateToChanged") {
c.Set("navigateToChanged", sc.navigateToChanged)
@@ -162,59 +168,60 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
return nil
}
var err error
// We can only do this once.
serverCfgInit.Do(func() {
serverPorts = make([]int, 1)
c.serverPorts = make([]serverPortListener, 1)
if c.languages.IsMultihost() {
if !sc.serverAppend {
err = newSystemError("--appendPort=false not supported when in multihost mode")
rerr = newSystemError("--appendPort=false not supported when in multihost mode")
}
serverPorts = make([]int, len(c.languages))
c.serverPorts = make([]serverPortListener, len(c.languages))
}
currentServerPort := sc.serverPort
for i := 0; i < len(serverPorts); i++ {
for i := 0; i < len(c.serverPorts); i++ {
l, err := net.Listen("tcp", net.JoinHostPort(sc.serverInterface, strconv.Itoa(currentServerPort)))
if err == nil {
l.Close()
serverPorts[i] = currentServerPort
c.serverPorts[i] = serverPortListener{ln: l, p: currentServerPort}
} else {
if i == 0 && sc.cmd.Flags().Changed("port") {
// port set explicitly by user -- he/she probably meant it!
err = newSystemErrorF("Server startup failed: %s", err)
rerr = newSystemErrorF("Server startup failed: %s", err)
return
}
c.logger.Println("port", sc.serverPort, "already in use, attempting to use an available port")
sp, err := helpers.FindAvailablePort()
l, sp, err := helpers.TCPListen()
if err != nil {
err = newSystemError("Unable to find alternative port to use:", err)
rerr = newSystemError("Unable to find alternative port to use:", err)
return
}
serverPorts[i] = sp.Port
c.serverPorts[i] = serverPortListener{ln: l, p: sp.Port}
}
currentServerPort = serverPorts[i] + 1
currentServerPort = c.serverPorts[i].p + 1
}
})
c.serverPorts = serverPorts
if rerr != nil {
return
}
c.Set("port", sc.serverPort)
if sc.liveReloadPort != -1 {
c.Set("liveReloadPort", sc.liveReloadPort)
} else {
c.Set("liveReloadPort", serverPorts[0])
c.Set("liveReloadPort", c.serverPorts[0].p)
}
isMultiHost := c.languages.IsMultihost()
for i, language := range c.languages {
var serverPort int
if isMultiHost {
serverPort = serverPorts[i]
serverPort = c.serverPorts[i].p
} else {
serverPort = serverPorts[0]
serverPort = c.serverPorts[0].p
}
baseURL, err := sc.fixURL(language, sc.baseURL, serverPort)
@@ -229,7 +236,7 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
}
}
return err
return
}
if err := memStats(); err != nil {
@@ -298,7 +305,7 @@ func getRootWatchDirsStr(baseDir string, watchDirs []string) string {
type fileServer struct {
baseURLs []string
roots []string
errorTemplate func(err interface{}) (io.Reader, error)
errorTemplate func(err any) (io.Reader, error)
c *commandeer
s *serverCmd
}
@@ -314,10 +321,11 @@ func (f *fileServer) rewriteRequest(r *http.Request, toPath string) *http.Reques
return r2
}
func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, error) {
func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string, string, error) {
baseURL := f.baseURLs[i]
root := f.roots[i]
port := f.c.serverPorts[i]
port := f.c.serverPorts[i].p
listener := f.c.serverPorts[i].ln
publishDir := f.c.Cfg.GetString("publishDir")
@@ -347,7 +355,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
// We're only interested in the path
u, err := url.Parse(baseURL)
if err != nil {
return nil, "", "", errors.Wrap(err, "Invalid baseURL")
return nil, nil, "", "", errors.Wrap(err, "Invalid baseURL")
}
decorate := func(h http.Handler) http.Handler {
@@ -453,7 +461,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, string, erro
endpoint := net.JoinHostPort(f.s.serverInterface, strconv.Itoa(port))
return mu, u.String(), endpoint, nil
return mu, listener, u.String(), endpoint, nil
}
var logErrorRe = regexp.MustCompile(`(?s)ERROR \d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} `)
@@ -491,7 +499,7 @@ func (c *commandeer) serve(s *serverCmd) error {
roots: roots,
c: c,
s: s,
errorTemplate: func(ctx interface{}) (io.Reader, error) {
errorTemplate: func(ctx any) (io.Reader, error) {
b := &bytes.Buffer{}
err := c.hugo().Tmpl().Execute(templ, b, ctx)
return b, err
@@ -506,9 +514,17 @@ func (c *commandeer) serve(s *serverCmd) error {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
var servers []*http.Server
wg1, ctx := errgroup.WithContext(context.Background())
for i := range baseURLs {
mu, serverURL, endpoint, err := srv.createEndpoint(i)
mu, listener, serverURL, endpoint, err := srv.createEndpoint(i)
srv := &http.Server{
Addr: endpoint,
Handler: mu,
}
servers = append(servers, srv)
if doLiveReload {
u, err := url.Parse(helpers.SanitizeURL(baseURLs[i]))
@@ -520,13 +536,13 @@ func (c *commandeer) serve(s *serverCmd) error {
mu.HandleFunc(u.Path+"/livereload", livereload.Handler)
}
jww.FEEDBACK.Printf("Web Server is available at %s (bind address %s)\n", serverURL, s.serverInterface)
go func() {
err = http.ListenAndServe(endpoint, mu)
if err != nil {
c.logger.Errorf("Error: %s\n", err.Error())
os.Exit(1)
wg1.Go(func() error {
err = srv.Serve(listener)
if err != nil && err != http.ErrServerClosed {
return err
}
}()
return nil
})
}
jww.FEEDBACK.Println("Press Ctrl+C to stop")
@@ -542,7 +558,21 @@ func (c *commandeer) serve(s *serverCmd) error {
c.hugo().Close()
return nil
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
wg2, ctx := errgroup.WithContext(ctx)
for _, srv := range servers {
srv := srv
wg2.Go(func() error {
return srv.Shutdown(ctx)
})
}
err1, err2 := wg1.Wait(), wg2.Wait()
if err1 != nil {
return err1
}
return err2
}
// fixURL massages the baseURL into a form needed for serving
+94 -31
View File
@@ -17,6 +17,7 @@ import (
"fmt"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
@@ -24,6 +25,8 @@ import (
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"golang.org/x/net/context"
"golang.org/x/sync/errgroup"
qt "github.com/frankban/quicktest"
)
@@ -31,11 +34,11 @@ import (
func TestServer(t *testing.T) {
c := qt.New(t)
homeContent, err := runServerTestAndGetHome(c, "")
r := runServerTest(c, true, "")
c.Assert(err, qt.IsNil)
c.Assert(homeContent, qt.Contains, "List: Hugo Commands")
c.Assert(homeContent, qt.Contains, "Environment: development")
c.Assert(r.err, qt.IsNil)
c.Assert(r.homeContent, qt.Contains, "List: Hugo Commands")
c.Assert(r.homeContent, qt.Contains, "Environment: development")
}
// Issue 9518
@@ -48,58 +51,118 @@ func TestServerPanicOnConfigError(t *testing.T) {
linenos='table'
`
_, err := runServerTestAndGetHome(c, config)
r := runServerTest(c, false, config)
c.Assert(err, qt.IsNotNil)
c.Assert(err.Error(), qt.Contains, "cannot parse 'Highlight.LineNos' as bool:")
c.Assert(r.err, qt.IsNotNil)
c.Assert(r.err.Error(), qt.Contains, "cannot parse 'Highlight.LineNos' as bool:")
}
func runServerTestAndGetHome(c *qt.C, config string) (string, error) {
func TestServerFlags(t *testing.T) {
c := qt.New(t)
assertPublic := func(c *qt.C, r serverTestResult, renderStaticToDisk bool) {
c.Assert(r.err, qt.IsNil)
c.Assert(r.homeContent, qt.Contains, "Environment: development")
c.Assert(r.publicDirnames["myfile.txt"], qt.Equals, renderStaticToDisk)
}
for _, test := range []struct {
flag string
assert func(c *qt.C, r serverTestResult)
}{
{"", func(c *qt.C, r serverTestResult) {
assertPublic(c, r, false)
}},
{"--renderToDisk", func(c *qt.C, r serverTestResult) {
assertPublic(c, r, true)
}},
} {
c.Run(test.flag, func(c *qt.C) {
config := `
baseURL="https://example.org"
`
var args []string
if test.flag != "" {
args = strings.Split(test.flag, "=")
}
r := runServerTest(c, true, config, args...)
test.assert(c, r)
})
}
}
type serverTestResult struct {
err error
homeContent string
publicDirnames map[string]bool
}
func runServerTest(c *qt.C, getHome bool, config string, args ...string) (result serverTestResult) {
dir, clean, err := createSimpleTestSite(c, testSiteConfig{configTOML: config})
defer clean()
c.Assert(err, qt.IsNil)
// Let us hope that this port is available on all systems ...
port := 1331
sp, err := helpers.FindAvailablePort()
c.Assert(err, qt.IsNil)
port := sp.Port
defer func() {
os.RemoveAll(dir)
}()
errors := make(chan error)
stop := make(chan bool)
b := newCommandsBuilder()
scmd := b.newServerCmdSignaled(stop)
cmd := scmd.getCommand()
cmd.SetArgs([]string{"-s=" + dir, fmt.Sprintf("-p=%d", port)})
args = append([]string{"-s=" + dir, fmt.Sprintf("-p=%d", port)}, args...)
cmd.SetArgs(args)
go func() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
wg, ctx := errgroup.WithContext(ctx)
wg.Go(func() error {
_, err := cmd.ExecuteC()
if err != nil {
errors <- err
}
}()
return err
})
select {
// There is no way to know exactly when the server is ready for connections.
// We could improve by something like https://golang.org/pkg/net/http/httptest/#Server
// But for now, let us sleep and pray!
case <-time.After(2 * time.Second):
case err := <-errors:
return "", err
if getHome {
// Esp. on slow CI machines, we need to wait a little before the web
// server is ready.
time.Sleep(567 * time.Millisecond)
resp, err := http.Get(fmt.Sprintf("http://localhost:%d/", port))
c.Check(err, qt.IsNil)
if err == nil {
defer resp.Body.Close()
result.homeContent = helpers.ReaderToString(resp.Body)
}
}
resp, err := http.Get("http://localhost:1331/")
c.Assert(err, qt.IsNil)
defer resp.Body.Close()
homeContent := helpers.ReaderToString(resp.Body)
select {
case <-stop:
case stop <- true:
}
// Stop the server.
stop <- true
pubFiles, err := os.ReadDir(filepath.Join(dir, "public"))
c.Check(err, qt.IsNil)
result.publicDirnames = make(map[string]bool)
for _, f := range pubFiles {
result.publicDirnames[f.Name()] = true
}
result.err = wg.Wait()
return
return homeContent, nil
}
func TestFixURL(t *testing.T) {
+5 -5
View File
@@ -21,7 +21,7 @@ import (
// Append appends from to a slice to and returns the resulting slice.
// If length of from is one and the only element is a slice of same type as to,
// it will be appended.
func Append(to interface{}, from ...interface{}) (interface{}, error) {
func Append(to any, from ...any) (any, error) {
tov, toIsNil := indirect(reflect.ValueOf(to))
toIsNil = toIsNil || to == nil
@@ -73,8 +73,8 @@ func Append(to interface{}, from ...interface{}) (interface{}, error) {
return tov.Interface(), nil
}
func appendToInterfaceSliceFromValues(slice1, slice2 reflect.Value) ([]interface{}, error) {
var tos []interface{}
func appendToInterfaceSliceFromValues(slice1, slice2 reflect.Value) ([]any, error) {
var tos []any
for _, slice := range []reflect.Value{slice1, slice2} {
for i := 0; i < slice.Len(); i++ {
@@ -85,8 +85,8 @@ func appendToInterfaceSliceFromValues(slice1, slice2 reflect.Value) ([]interface
return tos, nil
}
func appendToInterfaceSlice(tov reflect.Value, from ...interface{}) ([]interface{}, error) {
var tos []interface{}
func appendToInterfaceSlice(tov reflect.Value, from ...any) ([]any, error) {
var tos []any
for i := 0; i < tov.Len(); i++ {
tos = append(tos, tov.Index(i).Interface())
+19 -19
View File
@@ -25,25 +25,25 @@ func TestAppend(t *testing.T) {
c := qt.New(t)
for _, test := range []struct {
start interface{}
addend []interface{}
expected interface{}
start any
addend []any
expected any
}{
{[]string{"a", "b"}, []interface{}{"c"}, []string{"a", "b", "c"}},
{[]string{"a", "b"}, []interface{}{"c", "d", "e"}, []string{"a", "b", "c", "d", "e"}},
{[]string{"a", "b"}, []interface{}{[]string{"c", "d", "e"}}, []string{"a", "b", "c", "d", "e"}},
{[]string{"a"}, []interface{}{"b", template.HTML("c")}, []interface{}{"a", "b", template.HTML("c")}},
{nil, []interface{}{"a", "b"}, []string{"a", "b"}},
{nil, []interface{}{nil}, []interface{}{nil}},
{[]interface{}{}, []interface{}{[]string{"c", "d", "e"}}, []string{"c", "d", "e"}},
{[]string{"a", "b"}, []any{"c"}, []string{"a", "b", "c"}},
{[]string{"a", "b"}, []any{"c", "d", "e"}, []string{"a", "b", "c", "d", "e"}},
{[]string{"a", "b"}, []any{[]string{"c", "d", "e"}}, []string{"a", "b", "c", "d", "e"}},
{[]string{"a"}, []any{"b", template.HTML("c")}, []any{"a", "b", template.HTML("c")}},
{nil, []any{"a", "b"}, []string{"a", "b"}},
{nil, []any{nil}, []any{nil}},
{[]any{}, []any{[]string{"c", "d", "e"}}, []string{"c", "d", "e"}},
{
tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}},
[]interface{}{&tstSlicer{"c"}},
[]any{&tstSlicer{"c"}},
tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}, &tstSlicer{"c"}},
},
{
&tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}},
[]interface{}{&tstSlicer{"c"}},
[]any{&tstSlicer{"c"}},
tstSlicers{
&tstSlicer{"a"},
&tstSlicer{"b"},
@@ -52,26 +52,26 @@ func TestAppend(t *testing.T) {
},
{
testSlicerInterfaces{&tstSlicerIn1{"a"}, &tstSlicerIn1{"b"}},
[]interface{}{&tstSlicerIn1{"c"}},
[]any{&tstSlicerIn1{"c"}},
testSlicerInterfaces{&tstSlicerIn1{"a"}, &tstSlicerIn1{"b"}, &tstSlicerIn1{"c"}},
},
//https://github.com/gohugoio/hugo/issues/5361
{
[]string{"a", "b"},
[]interface{}{tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}}},
[]interface{}{"a", "b", &tstSlicer{"a"}, &tstSlicer{"b"}},
[]any{tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}}},
[]any{"a", "b", &tstSlicer{"a"}, &tstSlicer{"b"}},
},
{
[]string{"a", "b"},
[]interface{}{&tstSlicer{"a"}},
[]interface{}{"a", "b", &tstSlicer{"a"}},
[]any{&tstSlicer{"a"}},
[]any{"a", "b", &tstSlicer{"a"}},
},
// Errors
{"", []interface{}{[]string{"a", "b"}}, false},
{"", []any{[]string{"a", "b"}}, false},
// No string concatenation.
{
"ab",
[]interface{}{"c"},
[]any{"c"},
false,
},
} {
+1 -1
View File
@@ -17,5 +17,5 @@ package collections
// Grouper defines a very generic way to group items by a given key.
type Grouper interface {
Group(key interface{}, items interface{}) (interface{}, error)
Group(key any, items any) (any, error)
}
+4 -4
View File
@@ -21,11 +21,11 @@ import (
// in collections.Slice template func to get types such as Pages, PageGroups etc.
// instead of the less useful []interface{}.
type Slicer interface {
Slice(items interface{}) (interface{}, error)
Slice(items any) (any, error)
}
// Slice returns a slice of all passed arguments.
func Slice(args ...interface{}) interface{} {
func Slice(args ...any) any {
if len(args) == 0 {
return args
}
@@ -66,8 +66,8 @@ func Slice(args ...interface{}) interface{} {
}
// StringSliceToInterfaceSlice converts ss to []interface{}.
func StringSliceToInterfaceSlice(ss []string) []interface{} {
result := make([]interface{}, len(ss))
func StringSliceToInterfaceSlice(ss []string) []any {
result := make([]any, len(ss))
for i, s := range ss {
result[i] = s
}
+16 -16
View File
@@ -46,8 +46,8 @@ type tstSlicer struct {
TheName string
}
func (p *tstSlicerIn1) Slice(in interface{}) (interface{}, error) {
items := in.([]interface{})
func (p *tstSlicerIn1) Slice(in any) (any, error) {
items := in.([]any)
result := make(testSlicerInterfaces, len(items))
for i, v := range items {
switch vv := v.(type) {
@@ -60,8 +60,8 @@ func (p *tstSlicerIn1) Slice(in interface{}) (interface{}, error) {
return result, nil
}
func (p *tstSlicerIn2) Slice(in interface{}) (interface{}, error) {
items := in.([]interface{})
func (p *tstSlicerIn2) Slice(in any) (any, error) {
items := in.([]any)
result := make(testSlicerInterfaces, len(items))
for i, v := range items {
switch vv := v.(type) {
@@ -82,8 +82,8 @@ func (p *tstSlicerIn2) Name() string {
return p.TheName
}
func (p *tstSlicer) Slice(in interface{}) (interface{}, error) {
items := in.([]interface{})
func (p *tstSlicer) Slice(in any) (any, error) {
items := in.([]any)
result := make(tstSlicers, len(items))
for i, v := range items {
switch vv := v.(type) {
@@ -103,17 +103,17 @@ func TestSlice(t *testing.T) {
c := qt.New(t)
for i, test := range []struct {
args []interface{}
expected interface{}
args []any
expected any
}{
{[]interface{}{"a", "b"}, []string{"a", "b"}},
{[]interface{}{&tstSlicer{"a"}, &tstSlicer{"b"}}, tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}}},
{[]interface{}{&tstSlicer{"a"}, "b"}, []interface{}{&tstSlicer{"a"}, "b"}},
{[]interface{}{}, []interface{}{}},
{[]interface{}{nil}, []interface{}{nil}},
{[]interface{}{5, "b"}, []interface{}{5, "b"}},
{[]interface{}{&tstSlicerIn1{"a"}, &tstSlicerIn2{"b"}}, testSlicerInterfaces{&tstSlicerIn1{"a"}, &tstSlicerIn2{"b"}}},
{[]interface{}{&tstSlicerIn1{"a"}, &tstSlicer{"b"}}, []interface{}{&tstSlicerIn1{"a"}, &tstSlicer{"b"}}},
{[]any{"a", "b"}, []string{"a", "b"}},
{[]any{&tstSlicer{"a"}, &tstSlicer{"b"}}, tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}}},
{[]any{&tstSlicer{"a"}, "b"}, []any{&tstSlicer{"a"}, "b"}},
{[]any{}, []any{}},
{[]any{nil}, []any{nil}},
{[]any{5, "b"}, []any{5, "b"}},
{[]any{&tstSlicerIn1{"a"}, &tstSlicerIn2{"b"}}, testSlicerInterfaces{&tstSlicerIn1{"a"}, &tstSlicerIn2{"b"}}},
{[]any{&tstSlicerIn1{"a"}, &tstSlicer{"b"}}, []any{&tstSlicerIn1{"a"}, &tstSlicer{"b"}}},
} {
errMsg := qt.Commentf("[%d] %v", i, test.args)
+1 -1
View File
@@ -65,7 +65,7 @@ type ErrorSender interface {
// Recover is a helper function that can be used to capture panics.
// Put this at the top of a method/function that crashes in a template:
// defer herrors.Recover()
func Recover(args ...interface{}) {
func Recover(args ...any) {
if r := recover(); r != nil {
fmt.Println("ERR:", r)
args = append(args, "stacktrace from panic: \n"+string(debug.Stack()), "\n")
+4 -4
View File
@@ -128,7 +128,7 @@ type Exec struct {
// New will fail if name is not allowed according to the configured security policy.
// Else a configured Runner will be returned ready to be Run.
func (e *Exec) New(name string, arg ...interface{}) (Runner, error) {
func (e *Exec) New(name string, arg ...any) (Runner, error) {
if err := e.sc.CheckAllowedExec(name); err != nil {
return nil, err
}
@@ -146,8 +146,8 @@ func (e *Exec) New(name string, arg ...interface{}) (Runner, error) {
}
// Npx is a convenience method to create a Runner running npx --no-install <name> <args.
func (e *Exec) Npx(name string, arg ...interface{}) (Runner, error) {
arg = append(arg[:0], append([]interface{}{"--no-install", name}, arg[0:]...)...)
func (e *Exec) Npx(name string, arg ...any) (Runner, error) {
arg = append(arg[:0], append([]any{"--no-install", name}, arg[0:]...)...)
return e.New("npx", arg...)
}
@@ -205,7 +205,7 @@ type commandeer struct {
env []string
}
func (c *commandeer) command(arg ...interface{}) (*cmdWrapper, error) {
func (c *commandeer) command(arg ...any) (*cmdWrapper, error) {
if c == nil {
return nil, nil
}
+54 -1
View File
@@ -19,6 +19,7 @@ package hreflect
import (
"context"
"reflect"
"sync"
"github.com/gohugoio/hugo/common/types"
)
@@ -61,7 +62,7 @@ func IsFloat(kind reflect.Kind) bool {
// IsTruthful returns whether in represents a truthful value.
// See IsTruthfulValue
func IsTruthful(in interface{}) bool {
func IsTruthful(in any) bool {
switch v := in.(type) {
case reflect.Value:
return IsTruthfulValue(v)
@@ -115,6 +116,58 @@ func IsTruthfulValue(val reflect.Value) (truth bool) {
return
}
type methodKey struct {
typ reflect.Type
name string
}
type methods struct {
sync.RWMutex
cache map[methodKey]int
}
var methodCache = &methods{cache: make(map[methodKey]int)}
// GetMethodByName is the same as reflect.Value.MethodByName, but it caches the
// type lookup.
func GetMethodByName(v reflect.Value, name string) reflect.Value {
index := GetMethodIndexByName(v.Type(), name)
if index == -1 {
return reflect.Value{}
}
return v.Method(index)
}
// GetMethodIndexByName returns the index of the method with the given name, or
// -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()
if found {
return index
}
methodCache.Lock()
defer methodCache.Unlock()
m, ok := tp.MethodByName(name)
index = m.Index
if !ok {
index = -1
}
methodCache.cache[k] = index
if !ok {
return -1
}
return m.Index
}
// Based on: https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L931
func indirectInterface(v reflect.Value) reflect.Value {
if v.Kind() != reflect.Interface {
+44
View File
@@ -30,6 +30,16 @@ func TestIsTruthful(t *testing.T) {
c.Assert(IsTruthful(time.Time{}), qt.Equals, false)
}
func TestGetMethodByName(t *testing.T) {
c := qt.New(t)
v := reflect.ValueOf(&testStruct{})
tp := v.Type()
c.Assert(GetMethodIndexByName(tp, "Method1"), qt.Equals, 0)
c.Assert(GetMethodIndexByName(tp, "Method3"), qt.Equals, 2)
c.Assert(GetMethodIndexByName(tp, "Foo"), qt.Equals, -1)
}
func BenchmarkIsTruthFul(b *testing.B) {
v := reflect.ValueOf("Hugo")
@@ -40,3 +50,37 @@ func BenchmarkIsTruthFul(b *testing.B) {
}
}
}
type testStruct struct{}
func (t *testStruct) Method1() string {
return "Hugo"
}
func (t *testStruct) Method2() string {
return "Hugo"
}
func (t *testStruct) Method3() string {
return "Hugo"
}
func (t *testStruct) Method4() string {
return "Hugo"
}
func (t *testStruct) Method5() string {
return "Hugo"
}
func BenchmarkGetMethodByName(b *testing.B) {
v := reflect.ValueOf(&testStruct{})
methods := []string{"Method1", "Method2", "Method3", "Method4", "Method5"}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, method := range methods {
_ = GetMethodByName(v, method)
}
}
}
+1 -1
View File
@@ -134,7 +134,7 @@ func (f TimeFormatter) Format(t time.Time, layout string) string {
return s
}
func ToTimeInDefaultLocationE(i interface{}, location *time.Location) (tim time.Time, err error) {
func ToTimeInDefaultLocationE(i any, location *time.Location) (tim time.Time, err error) {
switch vv := i.(type) {
case toml.LocalDate:
return vv.AsTime(location), nil
+60 -9
View File
@@ -21,6 +21,7 @@ import (
"runtime/debug"
"sort"
"strings"
"sync"
"time"
"github.com/gohugoio/hugo/hugofs/files"
@@ -37,13 +38,6 @@ const (
)
var (
// commitHash contains the current Git revision.
// Use mage to build to make sure this gets set.
commitHash string
// buildDate contains the date of the current build.
buildDate string
// vendorInfo contains vendor notes about the current build.
vendorInfo string
)
@@ -90,6 +84,17 @@ func NewInfo(environment string, deps []*Dependency) Info {
if environment == "" {
environment = EnvironmentProduction
}
var (
commitHash string
buildDate string
)
bi := getBuildInfo()
if bi != nil {
commitHash = bi.Revision
buildDate = bi.RevisionTime
}
return Info{
CommitHash: commitHash,
BuildDate: buildDate,
@@ -125,6 +130,52 @@ func GetExecEnviron(workDir string, cfg config.Provider, fs afero.Fs) []string {
return env
}
type buildInfo struct {
VersionControlSystem string
Revision string
RevisionTime string
Modified bool
GoOS string
GoArch string
*debug.BuildInfo
}
var bInfo *buildInfo
var bInfoInit sync.Once
func getBuildInfo() *buildInfo {
bInfoInit.Do(func() {
bi, ok := debug.ReadBuildInfo()
if !ok {
return
}
bInfo = &buildInfo{BuildInfo: bi}
for _, s := range bInfo.Settings {
switch s.Key {
case "vcs":
bInfo.VersionControlSystem = s.Value
case "vcs.revision":
bInfo.Revision = s.Value
case "vcs.time":
bInfo.RevisionTime = s.Value
case "vcs.modified":
bInfo.Modified = s.Value == "true"
case "GOOS":
bInfo.GoOS = s.Value
case "GOARCH":
bInfo.GoArch = s.Value
}
}
})
return bInfo
}
// GetDependencyList returns a sorted dependency list on the format package="version".
// It includes both Go dependencies and (a manually maintained) list of C(++) dependencies.
func GetDependencyList() []string {
@@ -143,8 +194,8 @@ func GetDependencyList() []string {
)
}
bi, ok := debug.ReadBuildInfo()
if !ok {
bi := getBuildInfo()
if bi == nil {
return deps
}
+6 -2
View File
@@ -27,8 +27,12 @@ func TestHugoInfo(t *testing.T) {
c.Assert(hugoInfo.Version(), qt.Equals, CurrentVersion.Version())
c.Assert(fmt.Sprintf("%T", VersionString("")), qt.Equals, fmt.Sprintf("%T", hugoInfo.Version()))
c.Assert(hugoInfo.CommitHash, qt.Equals, commitHash)
c.Assert(hugoInfo.BuildDate, qt.Equals, buildDate)
bi := getBuildInfo()
if bi != nil {
c.Assert(hugoInfo.CommitHash, qt.Equals, bi.Revision)
c.Assert(hugoInfo.BuildDate, qt.Equals, bi.RevisionTime)
}
c.Assert(hugoInfo.Environment, qt.Equals, "production")
c.Assert(string(hugoInfo.Generator()), qt.Contains, fmt.Sprintf("Hugo %s", hugoInfo.Version()))
c.Assert(hugoInfo.IsProduction(), qt.Equals, true)
+1
View File
@@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build extended
// +build extended
package hugo
+1
View File
@@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !extended
// +build !extended
package hugo
+14 -9
View File
@@ -58,13 +58,13 @@ func (h VersionString) String() string {
}
// Compare implements the compare.Comparer interface.
func (h VersionString) Compare(other interface{}) int {
func (h VersionString) Compare(other any) int {
v := MustParseVersion(h.String())
return compareVersionsWithSuffix(v.Number, v.PatchLevel, v.Suffix, other)
}
// Eq implements the compare.Eqer interface.
func (h VersionString) Eq(other interface{}) bool {
func (h VersionString) Eq(other any) bool {
s, err := cast.ToStringE(other)
if err != nil {
return false
@@ -131,16 +131,21 @@ func BuildVersionString() string {
program := "hugo"
version := "v" + CurrentVersion.String()
if commitHash != "" {
version += "-" + strings.ToUpper(commitHash)
bi := getBuildInfo()
if bi == nil {
return version
}
if bi.Revision != "" {
version += "-" + bi.Revision
}
if IsExtended {
version += "+extended"
}
osArch := runtime.GOOS + "/" + runtime.GOARCH
osArch := bi.GoOS + "/" + bi.GoArch
date := buildDate
date := bi.RevisionTime
if date == "" {
date = "unknown"
}
@@ -166,15 +171,15 @@ func version(version float32, patchVersion int, suffix string) string {
// running Hugo version.
// It returns -1 if the given version is less than, 0 if equal and 1 if greater than
// the running version.
func CompareVersion(version interface{}) int {
func CompareVersion(version any) int {
return compareVersionsWithSuffix(CurrentVersion.Number, CurrentVersion.PatchLevel, CurrentVersion.Suffix, version)
}
func compareVersions(inVersion float32, inPatchVersion int, in interface{}) int {
func compareVersions(inVersion float32, inPatchVersion int, in any) int {
return compareVersionsWithSuffix(inVersion, inPatchVersion, "", in)
}
func compareVersionsWithSuffix(inVersion float32, inPatchVersion int, suffix string, in interface{}) int {
func compareVersionsWithSuffix(inVersion float32, inPatchVersion int, suffix string, in any) int {
var c int
switch d := in.(type) {
case float64:
+2 -2
View File
@@ -16,7 +16,7 @@ package hugo
// CurrentVersion represents the current build version.
// This should be the only one.
var CurrentVersion = Version{
Number: 0.93,
PatchLevel: 3,
Number: 0.96,
PatchLevel: 0,
Suffix: "",
}
+2 -2
View File
@@ -21,7 +21,7 @@ import (
// IgnorableLogger is a logger that ignores certain log statements.
type IgnorableLogger interface {
Logger
Errorsf(statementID, format string, v ...interface{})
Errorsf(statementID, format string, v ...any)
Apply(logger Logger) IgnorableLogger
}
@@ -43,7 +43,7 @@ func NewIgnorableLogger(logger Logger, statements ...string) IgnorableLogger {
}
// Errorsf logs statementID as an ERROR if not configured as ignoreable.
func (l ignorableLogger) Errorsf(statementID, format string, v ...interface{}) {
func (l ignorableLogger) Errorsf(statementID, format string, v ...any) {
if l.statements[statementID] {
// Ignore.
return
+20 -20
View File
@@ -58,21 +58,21 @@ func (w prefixWriter) Write(p []byte) (n int, err error) {
}
type Logger interface {
Printf(format string, v ...interface{})
Println(v ...interface{})
Printf(format string, v ...any)
Println(v ...any)
PrintTimerIfDelayed(start time.Time, name string)
Debug() *log.Logger
Debugf(format string, v ...interface{})
Debugln(v ...interface{})
Debugf(format string, v ...any)
Debugln(v ...any)
Info() *log.Logger
Infof(format string, v ...interface{})
Infoln(v ...interface{})
Infof(format string, v ...any)
Infoln(v ...any)
Warn() *log.Logger
Warnf(format string, v ...interface{})
Warnln(v ...interface{})
Warnf(format string, v ...any)
Warnln(v ...any)
Error() *log.Logger
Errorf(format string, v ...interface{})
Errorln(v ...interface{})
Errorf(format string, v ...any)
Errorln(v ...any)
Errors() string
Out() io.Writer
@@ -101,11 +101,11 @@ type logger struct {
errors *bytes.Buffer
}
func (l *logger) Printf(format string, v ...interface{}) {
func (l *logger) Printf(format string, v ...any) {
l.FEEDBACK.Printf(format, v...)
}
func (l *logger) Println(v ...interface{}) {
func (l *logger) Println(v ...any) {
l.FEEDBACK.Println(v...)
}
@@ -113,19 +113,19 @@ func (l *logger) Debug() *log.Logger {
return l.DEBUG
}
func (l *logger) Debugf(format string, v ...interface{}) {
func (l *logger) Debugf(format string, v ...any) {
l.DEBUG.Printf(format, v...)
}
func (l *logger) Debugln(v ...interface{}) {
func (l *logger) Debugln(v ...any) {
l.DEBUG.Println(v...)
}
func (l *logger) Infof(format string, v ...interface{}) {
func (l *logger) Infof(format string, v ...any) {
l.INFO.Printf(format, v...)
}
func (l *logger) Infoln(v ...interface{}) {
func (l *logger) Infoln(v ...any) {
l.INFO.Println(v...)
}
@@ -135,14 +135,14 @@ func (l *logger) Info() *log.Logger {
const panicOnWarningMessage = "Warning trapped. Remove the --panicOnWarning flag to continue."
func (l *logger) Warnf(format string, v ...interface{}) {
func (l *logger) Warnf(format string, v ...any) {
l.WARN.Printf(format, v...)
if PanicOnWarning {
panic(panicOnWarningMessage)
}
}
func (l *logger) Warnln(v ...interface{}) {
func (l *logger) Warnln(v ...any) {
l.WARN.Println(v...)
if PanicOnWarning {
panic(panicOnWarningMessage)
@@ -153,11 +153,11 @@ func (l *logger) Warn() *log.Logger {
return l.WARN
}
func (l *logger) Errorf(format string, v ...interface{}) {
func (l *logger) Errorf(format string, v ...any) {
l.ERROR.Printf(format, v...)
}
func (l *logger) Errorln(v ...interface{}) {
func (l *logger) Errorln(v ...any) {
l.ERROR.Println(v...)
}
+19 -19
View File
@@ -24,12 +24,12 @@ import (
)
// ToStringMapE converts in to map[string]interface{}.
func ToStringMapE(in interface{}) (map[string]interface{}, error) {
func ToStringMapE(in any) (map[string]any, error) {
switch vv := in.(type) {
case Params:
return vv, nil
case map[string]string:
var m = map[string]interface{}{}
var m = map[string]any{}
for k, v := range vv {
m[k] = v
}
@@ -43,7 +43,7 @@ func ToStringMapE(in interface{}) (map[string]interface{}, error) {
// ToParamsAndPrepare converts in to Params and prepares it for use.
// If in is nil, an empty map is returned.
// See PrepareParams.
func ToParamsAndPrepare(in interface{}) (Params, bool) {
func ToParamsAndPrepare(in any) (Params, bool) {
if types.IsNil(in) {
return Params{}, true
}
@@ -56,7 +56,7 @@ func ToParamsAndPrepare(in interface{}) (Params, bool) {
}
// MustToParamsAndPrepare calls ToParamsAndPrepare and panics if it fails.
func MustToParamsAndPrepare(in interface{}) Params {
func MustToParamsAndPrepare(in any) Params {
if p, ok := ToParamsAndPrepare(in); ok {
return p
} else {
@@ -65,13 +65,13 @@ func MustToParamsAndPrepare(in interface{}) Params {
}
// ToStringMap converts in to map[string]interface{}.
func ToStringMap(in interface{}) map[string]interface{} {
func ToStringMap(in any) map[string]any {
m, _ := ToStringMapE(in)
return m
}
// ToStringMapStringE converts in to map[string]string.
func ToStringMapStringE(in interface{}) (map[string]string, error) {
func ToStringMapStringE(in any) (map[string]string, error) {
m, err := ToStringMapE(in)
if err != nil {
return nil, err
@@ -80,26 +80,26 @@ func ToStringMapStringE(in interface{}) (map[string]string, error) {
}
// ToStringMapString converts in to map[string]string.
func ToStringMapString(in interface{}) map[string]string {
func ToStringMapString(in any) map[string]string {
m, _ := ToStringMapStringE(in)
return m
}
// ToStringMapBool converts in to bool.
func ToStringMapBool(in interface{}) map[string]bool {
func ToStringMapBool(in any) map[string]bool {
m, _ := ToStringMapE(in)
return cast.ToStringMapBool(m)
}
// ToSliceStringMap converts in to []map[string]interface{}.
func ToSliceStringMap(in interface{}) ([]map[string]interface{}, error) {
func ToSliceStringMap(in any) ([]map[string]any, error) {
switch v := in.(type) {
case []map[string]interface{}:
case []map[string]any:
return v, nil
case []interface{}:
var s []map[string]interface{}
case []any:
var s []map[string]any
for _, entry := range v {
if vv, ok := entry.(map[string]interface{}); ok {
if vv, ok := entry.(map[string]any); ok {
s = append(s, vv)
}
}
@@ -146,7 +146,7 @@ func (r KeyRenamer) getNewKey(keyPath string) string {
// Rename renames the keys in the given map according
// to the patterns in the current KeyRenamer.
func (r KeyRenamer) Rename(m map[string]interface{}) {
func (r KeyRenamer) Rename(m map[string]any) {
r.renamePath("", m)
}
@@ -158,15 +158,15 @@ func (KeyRenamer) keyPath(k1, k2 string) string {
return k1 + "/" + k2
}
func (r KeyRenamer) renamePath(parentKeyPath string, m map[string]interface{}) {
func (r KeyRenamer) renamePath(parentKeyPath string, m map[string]any) {
for key, val := range m {
keyPath := r.keyPath(parentKeyPath, key)
switch val.(type) {
case map[interface{}]interface{}:
case map[any]any:
val = cast.ToStringMap(val)
r.renamePath(keyPath, val.(map[string]interface{}))
case map[string]interface{}:
r.renamePath(keyPath, val.(map[string]interface{}))
r.renamePath(keyPath, val.(map[string]any))
case map[string]any:
r.renamePath(keyPath, val.(map[string]any))
}
newKey := r.getNewKey(keyPath)
+21 -21
View File
@@ -27,7 +27,7 @@ func TestPrepareParams(t *testing.T) {
expected Params
}{
{
map[string]interface{}{
map[string]any{
"abC": 32,
},
Params{
@@ -35,16 +35,16 @@ func TestPrepareParams(t *testing.T) {
},
},
{
map[string]interface{}{
map[string]any{
"abC": 32,
"deF": map[interface{}]interface{}{
"deF": map[any]any{
23: "A value",
24: map[string]interface{}{
24: map[string]any{
"AbCDe": "A value",
"eFgHi": "Another value",
},
},
"gHi": map[string]interface{}{
"gHi": map[string]any{
"J": 25,
},
"jKl": map[string]string{
@@ -85,23 +85,23 @@ func TestToSliceStringMap(t *testing.T) {
c := qt.New(t)
tests := []struct {
input interface{}
expected []map[string]interface{}
input any
expected []map[string]any
}{
{
input: []map[string]interface{}{
input: []map[string]any{
{"abc": 123},
},
expected: []map[string]interface{}{
expected: []map[string]any{
{"abc": 123},
},
}, {
input: []interface{}{
map[string]interface{}{
input: []any{
map[string]any{
"def": 456,
},
},
expected: []map[string]interface{}{
expected: []map[string]any{
{"def": 456},
},
},
@@ -116,7 +116,7 @@ func TestToSliceStringMap(t *testing.T) {
func TestToParamsAndPrepare(t *testing.T) {
c := qt.New(t)
_, ok := ToParamsAndPrepare(map[string]interface{}{"A": "av"})
_, ok := ToParamsAndPrepare(map[string]any{"A": "av"})
c.Assert(ok, qt.IsTrue)
params, ok := ToParamsAndPrepare(nil)
@@ -127,33 +127,33 @@ func TestToParamsAndPrepare(t *testing.T) {
func TestRenameKeys(t *testing.T) {
c := qt.New(t)
m := map[string]interface{}{
m := map[string]any{
"a": 32,
"ren1": "m1",
"ren2": "m1_2",
"sub": map[string]interface{}{
"subsub": map[string]interface{}{
"sub": map[string]any{
"subsub": map[string]any{
"REN1": "m2",
"ren2": "m2_2",
},
},
"no": map[string]interface{}{
"no": map[string]any{
"ren1": "m2",
"ren2": "m2_2",
},
}
expected := map[string]interface{}{
expected := map[string]any{
"a": 32,
"new1": "m1",
"new2": "m1_2",
"sub": map[string]interface{}{
"subsub": map[string]interface{}{
"sub": map[string]any{
"subsub": map[string]any{
"new1": "m2",
"ren2": "m2_2",
},
},
"no": map[string]interface{}{
"no": map[string]any{
"ren1": "m2",
"ren2": "m2_2",
},
+11 -11
View File
@@ -21,11 +21,11 @@ import (
)
// Params is a map where all keys are lower case.
type Params map[string]interface{}
type Params map[string]any
// Get does a lower case and nested search in this map.
// It will return nil if none found.
func (p Params) Get(indices ...string) interface{} {
func (p Params) Get(indices ...string) any {
v, _, _ := getNested(p, indices)
return v
}
@@ -142,7 +142,7 @@ func (p Params) SetDefaultMergeStrategy(s ParamsMergeStrategy) {
p[mergeStrategyKey] = s
}
func getNested(m map[string]interface{}, indices []string) (interface{}, string, map[string]interface{}) {
func getNested(m map[string]any, indices []string) (any, string, map[string]any) {
if len(indices) == 0 {
return nil, "", nil
}
@@ -164,7 +164,7 @@ func getNested(m map[string]interface{}, indices []string) (interface{}, string,
switch m2 := v.(type) {
case Params:
return getNested(m2, indices[1:])
case map[string]interface{}:
case map[string]any:
return getNested(m2, indices[1:])
default:
return nil, "", nil
@@ -175,7 +175,7 @@ func getNested(m map[string]interface{}, indices []string) (interface{}, string,
// It will first try the exact match and then try to find it as a nested map value,
// using the given separator, e.g. "mymap.name".
// It assumes that all the maps given have lower cased keys.
func GetNestedParam(keyStr, separator string, candidates ...Params) (interface{}, error) {
func GetNestedParam(keyStr, separator string, candidates ...Params) (any, error) {
keyStr = strings.ToLower(keyStr)
// Try exact match first
@@ -195,7 +195,7 @@ func GetNestedParam(keyStr, separator string, candidates ...Params) (interface{}
return nil, nil
}
func GetNestedParamFn(keyStr, separator string, lookupFn func(key string) interface{}) (interface{}, string, map[string]interface{}, error) {
func GetNestedParamFn(keyStr, separator string, lookupFn func(key string) any) (any, string, map[string]any, error) {
keySegments := strings.Split(keyStr, separator)
if len(keySegments) == 0 {
return nil, "", nil, nil
@@ -211,7 +211,7 @@ func GetNestedParamFn(keyStr, separator string, lookupFn func(key string) interf
}
switch m := first.(type) {
case map[string]interface{}:
case map[string]any:
v, key, owner := getNested(m, keySegments[1:])
return v, key, owner, nil
case Params:
@@ -236,7 +236,7 @@ const (
mergeStrategyKey = "_merge"
)
func toMergeStrategy(v interface{}) ParamsMergeStrategy {
func toMergeStrategy(v any) ParamsMergeStrategy {
s := ParamsMergeStrategy(cast.ToString(v))
switch s {
case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow:
@@ -260,13 +260,13 @@ func PrepareParams(m Params) {
retyped = true
} else {
switch vv := v.(type) {
case map[interface{}]interface{}:
case map[any]any:
var p Params = cast.ToStringMap(v)
v = p
PrepareParams(p)
retyped = true
case map[string]interface{}:
var p Params = v.(map[string]interface{})
case map[string]any:
var p Params = v.(map[string]any)
v = p
PrepareParams(p)
retyped = true
+7 -7
View File
@@ -20,13 +20,13 @@ import (
)
func TestGetNestedParam(t *testing.T) {
m := map[string]interface{}{
m := map[string]any{
"string": "value",
"first": 1,
"with_underscore": 2,
"nested": map[string]interface{}{
"nested": map[string]any{
"color": "blue",
"nestednested": map[string]interface{}{
"nestednested": map[string]any{
"color": "green",
},
},
@@ -34,7 +34,7 @@ func TestGetNestedParam(t *testing.T) {
c := qt.New(t)
must := func(keyStr, separator string, candidates ...Params) interface{} {
must := func(keyStr, separator string, candidates ...Params) any {
v, err := GetNestedParam(keyStr, separator, candidates...)
c.Assert(err, qt.IsNil)
return v
@@ -53,14 +53,14 @@ func TestGetNestedParam(t *testing.T) {
func TestGetNestedParamFnNestedNewKey(t *testing.T) {
c := qt.New(t)
nested := map[string]interface{}{
nested := map[string]any{
"color": "blue",
}
m := map[string]interface{}{
m := map[string]any{
"nested": nested,
}
existing, nestedKey, owner, err := GetNestedParamFn("nested.new", ".", func(key string) interface{} {
existing, nestedKey, owner, err := GetNestedParamFn("nested.new", ".", func(key string) any {
return m[key]
})
+14 -14
View File
@@ -24,7 +24,7 @@ import (
// Scratch is a writable context used for stateful operations in Page/Node rendering.
type Scratch struct {
values map[string]interface{}
values map[string]any
mu sync.RWMutex
}
@@ -50,8 +50,8 @@ func NewScratcher() Scratcher {
// Supports numeric values and strings.
//
// If the first add for a key is an array or slice, then the next value(s) will be appended.
func (c *Scratch) Add(key string, newAddend interface{}) (string, error) {
var newVal interface{}
func (c *Scratch) Add(key string, newAddend any) (string, error) {
var newVal any
c.mu.RLock()
existingAddend, found := c.values[key]
c.mu.RUnlock()
@@ -82,7 +82,7 @@ func (c *Scratch) Add(key string, newAddend interface{}) (string, error) {
// Set stores a value with the given key in the Node context.
// This value can later be retrieved with Get.
func (c *Scratch) Set(key string, value interface{}) string {
func (c *Scratch) Set(key string, value any) string {
c.mu.Lock()
c.values[key] = value
c.mu.Unlock()
@@ -98,7 +98,7 @@ func (c *Scratch) Delete(key string) string {
}
// Get returns a value previously set by Add or Set.
func (c *Scratch) Get(key string) interface{} {
func (c *Scratch) Get(key string) any {
c.mu.RLock()
val := c.values[key]
c.mu.RUnlock()
@@ -109,7 +109,7 @@ func (c *Scratch) Get(key string) interface{} {
// Values returns the raw backing map. Note that you should just use
// this method on the locally scoped Scratch instances you obtain via newScratch, not
// .Page.Scratch etc., as that will lead to concurrency issues.
func (c *Scratch) Values() map[string]interface{} {
func (c *Scratch) Values() map[string]any {
c.mu.RLock()
defer c.mu.RUnlock()
return c.values
@@ -117,14 +117,14 @@ func (c *Scratch) Values() map[string]interface{} {
// SetInMap stores a value to a map with the given key in the Node context.
// This map can later be retrieved with GetSortedMapValues.
func (c *Scratch) SetInMap(key string, mapKey string, value interface{}) string {
func (c *Scratch) SetInMap(key string, mapKey string, value any) string {
c.mu.Lock()
_, found := c.values[key]
if !found {
c.values[key] = make(map[string]interface{})
c.values[key] = make(map[string]any)
}
c.values[key].(map[string]interface{})[mapKey] = value
c.values[key].(map[string]any)[mapKey] = value
c.mu.Unlock()
return ""
}
@@ -134,14 +134,14 @@ func (c *Scratch) DeleteInMap(key string, mapKey string) string {
c.mu.Lock()
_, found := c.values[key]
if found {
delete(c.values[key].(map[string]interface{}), mapKey)
delete(c.values[key].(map[string]any), mapKey)
}
c.mu.Unlock()
return ""
}
// GetSortedMapValues returns a sorted map previously filled with SetInMap.
func (c *Scratch) GetSortedMapValues(key string) interface{} {
func (c *Scratch) GetSortedMapValues(key string) any {
c.mu.RLock()
if c.values[key] == nil {
@@ -149,7 +149,7 @@ func (c *Scratch) GetSortedMapValues(key string) interface{} {
return nil
}
unsortedMap := c.values[key].(map[string]interface{})
unsortedMap := c.values[key].(map[string]any)
c.mu.RUnlock()
var keys []string
for mapKey := range unsortedMap {
@@ -158,7 +158,7 @@ func (c *Scratch) GetSortedMapValues(key string) interface{} {
sort.Strings(keys)
sortedArray := make([]interface{}, len(unsortedMap))
sortedArray := make([]any, len(unsortedMap))
for i, mapKey := range keys {
sortedArray[i] = unsortedMap[mapKey]
}
@@ -168,5 +168,5 @@ func (c *Scratch) GetSortedMapValues(key string) interface{} {
// NewScratch returns a new instance of Scratch.
func NewScratch() *Scratch {
return &Scratch{values: make(map[string]interface{})}
return &Scratch{values: make(map[string]any)}
}
+4 -4
View File
@@ -90,7 +90,7 @@ func TestScratchAddTypedSliceToInterfaceSlice(t *testing.T) {
c := qt.New(t)
scratch := NewScratch()
scratch.Set("slice", []interface{}{})
scratch.Set("slice", []any{})
_, err := scratch.Add("slice", []int{1, 2})
c.Assert(err, qt.IsNil)
@@ -107,7 +107,7 @@ func TestScratchAddDifferentTypedSliceToInterfaceSlice(t *testing.T) {
_, err := scratch.Add("slice", []int{1, 2})
c.Assert(err, qt.IsNil)
c.Assert(scratch.Get("slice"), qt.DeepEquals, []interface{}{"foo", 1, 2})
c.Assert(scratch.Get("slice"), qt.DeepEquals, []any{"foo", 1, 2})
}
func TestScratchSet(t *testing.T) {
@@ -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, []interface{}{0: "Abc (updated)", 1: "Def", 2: "Lux", 3: "Zyx"})
c.Assert(scratch.GetSortedMapValues("key"), qt.DeepEquals, []any{0: "Abc (updated)", 1: "Def", 2: "Lux", 3: "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, []interface{}{0: "Def", 1: "Lux", 2: "Zyx"})
c.Assert(scratch.GetSortedMapValues("key"), qt.DeepEquals, []any{0: "Def", 1: "Lux", 2: "Zyx"})
}
func TestScratchGetSortedMapValues(t *testing.T) {
+1 -1
View File
@@ -20,7 +20,7 @@ import (
// DoArithmetic performs arithmetic operations (+,-,*,/) using reflection to
// determine the type of the two terms.
func DoArithmetic(a, b interface{}, op rune) (interface{}, error) {
func DoArithmetic(a, b any, op rune) (any, error) {
av := reflect.ValueOf(a)
bv := reflect.ValueOf(b)
var ai, bi int64
+3 -3
View File
@@ -24,10 +24,10 @@ func TestDoArithmetic(t *testing.T) {
c := qt.New(t)
for _, test := range []struct {
a interface{}
b interface{}
a any
b any
op rune
expect interface{}
expect any
}{
{3, 2, '+', int64(5)},
{3, 2, '-', int64(1)},
+1 -1
View File
@@ -24,7 +24,7 @@ func TestGetRelativePath(t *testing.T) {
tests := []struct {
path string
base string
expect interface{}
expect any
}{
{filepath.FromSlash("/a/b"), filepath.FromSlash("/a"), filepath.FromSlash("b")},
{filepath.FromSlash("/a/b/c/"), filepath.FromSlash("/a"), filepath.FromSlash("b/c/")},
+1 -1
View File
@@ -69,7 +69,7 @@ func createPositionStringFormatter(formatStr string) func(p Position) string {
format := replacer.Replace(formatStr)
f := func(pos Position) string {
args := make([]interface{}, len(identifiersFound))
args := make([]any, len(identifiersFound))
for i, id := range identifiersFound {
switch id {
case ":file":
+1 -1
View File
@@ -24,7 +24,7 @@ import (
)
var accentTransformerPool = &sync.Pool{
New: func() interface{} {
New: func() any {
return transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
},
}
+7 -7
View File
@@ -25,13 +25,13 @@ import (
// ToDuration converts v to time.Duration.
// See ToDurationE if you need to handle errors.
func ToDuration(v interface{}) time.Duration {
func ToDuration(v any) time.Duration {
d, _ := ToDurationE(v)
return d
}
// ToDurationE converts v to time.Duration.
func ToDurationE(v interface{}) (time.Duration, error) {
func ToDurationE(v any) (time.Duration, error) {
if n := cast.ToInt(v); n > 0 {
return time.Duration(n) * time.Millisecond, nil
}
@@ -44,14 +44,14 @@ func ToDurationE(v interface{}) (time.Duration, error) {
// ToStringSlicePreserveString is the same as ToStringSlicePreserveStringE,
// but it never fails.
func ToStringSlicePreserveString(v interface{}) []string {
func ToStringSlicePreserveString(v any) []string {
vv, _ := ToStringSlicePreserveStringE(v)
return vv
}
// ToStringSlicePreserveStringE converts v to a string slice.
// If v is a string, it will be wrapped in a string slice.
func ToStringSlicePreserveStringE(v interface{}) ([]string, error) {
func ToStringSlicePreserveStringE(v any) ([]string, error) {
if v == nil {
return nil, nil
}
@@ -86,7 +86,7 @@ func ToStringSlicePreserveStringE(v interface{}) ([]string, error) {
// TypeToString converts v to a string if it's a valid string type.
// Note that this will not try to convert numeric values etc.,
// use ToString for that.
func TypeToString(v interface{}) (string, bool) {
func TypeToString(v any) (string, bool) {
switch s := v.(type) {
case string:
return s, true
@@ -110,13 +110,13 @@ func TypeToString(v interface{}) (string, bool) {
}
// ToString converts v to a string.
func ToString(v interface{}) string {
func ToString(v any) string {
s, _ := ToStringE(v)
return s
}
// ToStringE converts v to a string.
func ToStringE(v interface{}) (string, error) {
func ToStringE(v any) (string, error) {
if s, ok := TypeToString(v); ok {
return s, nil
}
+1 -1
View File
@@ -26,7 +26,7 @@ func TestToStringSlicePreserveString(t *testing.T) {
c.Assert(ToStringSlicePreserveString("Hugo"), qt.DeepEquals, []string{"Hugo"})
c.Assert(ToStringSlicePreserveString(qt.Commentf("Hugo")), qt.DeepEquals, []string{"Hugo"})
c.Assert(ToStringSlicePreserveString([]interface{}{"A", "B"}), qt.DeepEquals, []string{"A", "B"})
c.Assert(ToStringSlicePreserveString([]any{"A", "B"}), qt.DeepEquals, []string{"A", "B"})
c.Assert(ToStringSlicePreserveString([]int{1, 3}), qt.DeepEquals, []string{"1", "3"})
c.Assert(ToStringSlicePreserveString(nil), qt.IsNil)
}
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2022 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 hstring
type RenderedString string
func (s RenderedString) String() string {
return string(s)
}
+30
View File
@@ -0,0 +1,30 @@
// Copyright 2022 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 hstring
import (
"html/template"
"testing"
qt "github.com/frankban/quicktest"
"github.com/spf13/cast"
)
func TestStringTypes(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"))
}
+6 -6
View File
@@ -29,8 +29,8 @@ type RLocker interface {
// KeyValue is a interface{} tuple.
type KeyValue struct {
Key interface{}
Value interface{}
Key any
Value any
}
// KeyValueStr is a string tuple.
@@ -41,8 +41,8 @@ type KeyValueStr struct {
// KeyValues holds an key and a slice of values.
type KeyValues struct {
Key interface{}
Values []interface{}
Key any
Values []any
}
// KeyString returns the key as a string, an empty string if conversion fails.
@@ -57,7 +57,7 @@ func (k KeyValues) String() string {
// NewKeyValuesStrings takes a given key and slice of values and returns a new
// KeyValues struct.
func NewKeyValuesStrings(key string, values ...string) KeyValues {
iv := make([]interface{}, len(values))
iv := make([]any, len(values))
for i := 0; i < len(values); i++ {
iv[i] = values[i]
}
@@ -71,7 +71,7 @@ type Zeroer interface {
}
// IsNil reports whether v is nil.
func IsNil(v interface{}) bool {
func IsNil(v any) bool {
if v == nil {
return true
}
+1 -1
View File
@@ -25,5 +25,5 @@ func TestKeyValues(t *testing.T) {
kv := NewKeyValuesStrings("key", "a1", "a2")
c.Assert(kv.KeyString(), qt.Equals, "key")
c.Assert(kv.Values, qt.DeepEquals, []interface{}{"a1", "a2"})
c.Assert(kv.Values, qt.DeepEquals, []any{"a1", "a2"})
}
+2 -2
View File
@@ -17,6 +17,6 @@ package urls
// args must contain a path, but can also point to the target
// language or output format.
type RefLinker interface {
Ref(args map[string]interface{}) (string, error)
RelRef(args map[string]interface{}) (string, error)
Ref(args map[string]any) (string, error)
RelRef(args map[string]any) (string, error)
}
+3 -3
View File
@@ -17,13 +17,13 @@ package compare
// The semantics of equals is that the two value are interchangeable
// in the Hugo templates.
type Eqer interface {
Eq(other interface{}) bool
Eq(other any) bool
}
// ProbablyEqer is an equal check that may return false positives, but never
// a false negative.
type ProbablyEqer interface {
ProbablyEq(other interface{}) bool
ProbablyEq(other any) bool
}
// Comparer can be used to compare two values.
@@ -31,5 +31,5 @@ type ProbablyEqer interface {
// Compare returns -1 if the given version is less than, 0 if equal and 1 if greater than
// the running version.
type Comparer interface {
Compare(other interface{}) int
Compare(other any) int
}
+2 -2
View File
@@ -87,7 +87,7 @@ type Sitemap struct {
Filename string
}
func DecodeSitemap(prototype Sitemap, input map[string]interface{}) Sitemap {
func DecodeSitemap(prototype Sitemap, input map[string]any) Sitemap {
for key, value := range input {
switch key {
case "changefreq":
@@ -177,7 +177,7 @@ func (s *Server) MatchRedirect(pattern string) Redirect {
type Headers struct {
For string
Values map[string]interface{}
Values map[string]any
}
type Redirect struct {
+2 -2
View File
@@ -27,7 +27,7 @@ func TestBuild(t *testing.T) {
c := qt.New(t)
v := New()
v.Set("build", map[string]interface{}{
v.Set("build", map[string]any{
"useResourceCacheWhen": "always",
})
@@ -35,7 +35,7 @@ func TestBuild(t *testing.T) {
c.Assert(b.UseResourceCacheWhen, qt.Equals, "always")
v.Set("build", map[string]interface{}{
v.Set("build", map[string]any{
"useResourceCacheWhen": "foo",
})
+4 -4
View File
@@ -47,7 +47,7 @@ func (c *compositeConfig) GetInt(key string) int {
return c.base.GetInt(key)
}
func (c *compositeConfig) Merge(key string, value interface{}) {
func (c *compositeConfig) Merge(key string, value any) {
c.layer.Merge(key, value)
}
@@ -58,7 +58,7 @@ func (c *compositeConfig) GetParams(key string) maps.Params {
return c.base.GetParams(key)
}
func (c *compositeConfig) GetStringMap(key string) map[string]interface{} {
func (c *compositeConfig) GetStringMap(key string) map[string]any {
if c.layer.IsSet(key) {
return c.layer.GetStringMap(key)
}
@@ -79,7 +79,7 @@ func (c *compositeConfig) GetStringSlice(key string) []string {
return c.base.GetStringSlice(key)
}
func (c *compositeConfig) Get(key string) interface{} {
func (c *compositeConfig) Get(key string) any {
if c.layer.IsSet(key) {
return c.layer.Get(key)
}
@@ -100,7 +100,7 @@ func (c *compositeConfig) GetString(key string) string {
return c.base.GetString(key)
}
func (c *compositeConfig) Set(key string, value interface{}) {
func (c *compositeConfig) Set(key string, value any) {
c.layer.Set(key, value)
}
+6 -6
View File
@@ -67,11 +67,11 @@ func FromFile(fs afero.Fs, filename string) (Provider, error) {
// FromFileToMap is the same as FromFile, but it returns the config values
// as a simple map.
func FromFileToMap(fs afero.Fs, filename string) (map[string]interface{}, error) {
func FromFileToMap(fs afero.Fs, filename string) (map[string]any, error) {
return loadConfigFromFile(fs, filename)
}
func readConfig(format metadecoders.Format, data []byte) (map[string]interface{}, error) {
func readConfig(format metadecoders.Format, data []byte) (map[string]any, error) {
m, err := metadecoders.Default.UnmarshalToMap(data, format)
if err != nil {
return nil, err
@@ -82,7 +82,7 @@ func readConfig(format metadecoders.Format, data []byte) (map[string]interface{}
return m, nil
}
func loadConfigFromFile(fs afero.Fs, filename string) (map[string]interface{}, error) {
func loadConfigFromFile(fs afero.Fs, filename string) (map[string]any, error) {
m, err := metadecoders.Default.UnmarshalFileToMap(fs, filename)
if err != nil {
return nil, err
@@ -156,13 +156,13 @@ func LoadConfigFromDir(sourceFs afero.Fs, configDir, environment string) (Provid
root := item
if len(keyPath) > 0 {
root = make(map[string]interface{})
root = make(map[string]any)
m := root
for i, key := range keyPath {
if i >= len(keyPath)-1 {
m[key] = item
} else {
nm := make(map[string]interface{})
nm := make(map[string]any)
m[key] = nm
m = nm
}
@@ -203,6 +203,6 @@ func init() {
// RenameKeys renames config keys in m recursively according to a global Hugo
// alias definition.
func RenameKeys(m map[string]interface{}) {
func RenameKeys(m map[string]any) {
keyAliases.Rename(m)
}
+4 -4
View File
@@ -24,12 +24,12 @@ type Provider interface {
GetInt(key string) int
GetBool(key string) bool
GetParams(key string) maps.Params
GetStringMap(key string) map[string]interface{}
GetStringMap(key string) map[string]any
GetStringMapString(key string) map[string]string
GetStringSlice(key string) []string
Get(key string) interface{}
Set(key string, value interface{})
Merge(key string, value interface{})
Get(key string) any
Set(key string, value any)
Merge(key string, value any)
SetDefaults(params maps.Params)
SetDefaultMergeStrategy()
WalkParams(walkFn func(params ...KeyParams) bool)
+6 -6
View File
@@ -84,7 +84,7 @@ type defaultConfigProvider struct {
keyCache sync.Map
}
func (c *defaultConfigProvider) Get(k string) interface{} {
func (c *defaultConfigProvider) Get(k string) any {
if k == "" {
return c.root
}
@@ -133,7 +133,7 @@ func (c *defaultConfigProvider) GetParams(k string) maps.Params {
return v.(maps.Params)
}
func (c *defaultConfigProvider) GetStringMap(k string) map[string]interface{} {
func (c *defaultConfigProvider) GetStringMap(k string) map[string]any {
v := c.Get(k)
return maps.ToStringMap(v)
}
@@ -148,7 +148,7 @@ func (c *defaultConfigProvider) GetStringSlice(k string) []string {
return cast.ToStringSlice(v)
}
func (c *defaultConfigProvider) Set(k string, v interface{}) {
func (c *defaultConfigProvider) Set(k string, v any) {
c.mu.Lock()
defer c.mu.Unlock()
@@ -166,7 +166,7 @@ func (c *defaultConfigProvider) Set(k string, v interface{}) {
}
switch vv := v.(type) {
case map[string]interface{}, map[interface{}]interface{}, map[string]string:
case map[string]any, map[any]any, map[string]string:
p := maps.MustToParamsAndPrepare(vv)
v = p
}
@@ -198,7 +198,7 @@ func (c *defaultConfigProvider) SetDefaults(params maps.Params) {
}
}
func (c *defaultConfigProvider) Merge(k string, v interface{}) {
func (c *defaultConfigProvider) Merge(k string, v any) {
c.mu.Lock()
defer c.mu.Unlock()
k = strings.ToLower(k)
@@ -289,7 +289,7 @@ func (c *defaultConfigProvider) Merge(k string, v interface{}) {
}
switch vv := v.(type) {
case map[string]interface{}, map[interface{}]interface{}, map[string]string:
case map[string]any, map[any]any, map[string]string:
p := maps.MustToParamsAndPrepare(vv)
v = p
}
+38 -38
View File
@@ -34,7 +34,7 @@ func TestDefaultConfigProvider(t *testing.T) {
c.Run("Set and get", func(c *qt.C) {
cfg := New()
var k string
var v interface{}
var v any
k, v = "foo", "bar"
cfg.Set(k, v)
@@ -55,7 +55,7 @@ func TestDefaultConfigProvider(t *testing.T) {
c.Run("Set and get map", func(c *qt.C) {
cfg := New()
cfg.Set("foo", map[string]interface{}{
cfg.Set("foo", map[string]any{
"bar": "baz",
})
@@ -63,14 +63,14 @@ func TestDefaultConfigProvider(t *testing.T) {
"bar": "baz",
})
c.Assert(cfg.GetStringMap("foo"), qt.DeepEquals, map[string]interface{}{"bar": string("baz")})
c.Assert(cfg.GetStringMap("foo"), qt.DeepEquals, map[string]any{"bar": string("baz")})
c.Assert(cfg.GetStringMapString("foo"), qt.DeepEquals, map[string]string{"bar": string("baz")})
})
c.Run("Set and get nested", func(c *qt.C) {
cfg := New()
cfg.Set("a", map[string]interface{}{
cfg.Set("a", map[string]any{
"B": "bv",
})
cfg.Set("a.c", "cv")
@@ -86,7 +86,7 @@ func TestDefaultConfigProvider(t *testing.T) {
"a": "av",
})
cfg.Set("b", map[string]interface{}{
cfg.Set("b", map[string]any{
"b": "bv",
})
@@ -99,7 +99,7 @@ func TestDefaultConfigProvider(t *testing.T) {
cfg.Set("a", "av")
cfg.Set("", map[string]interface{}{
cfg.Set("", map[string]any{
"a": "av2",
"b": "bv2",
})
@@ -113,7 +113,7 @@ func TestDefaultConfigProvider(t *testing.T) {
cfg.Set("a", "av")
cfg.Set("", map[string]interface{}{
cfg.Set("", map[string]any{
"b": "bv2",
})
@@ -124,14 +124,14 @@ func TestDefaultConfigProvider(t *testing.T) {
cfg = New()
cfg.Set("", map[string]interface{}{
"foo": map[string]interface{}{
cfg.Set("", map[string]any{
"foo": map[string]any{
"a": "av",
},
})
cfg.Set("", map[string]interface{}{
"foo": map[string]interface{}{
cfg.Set("", map[string]any{
"foo": map[string]any{
"b": "bv2",
},
})
@@ -145,11 +145,11 @@ func TestDefaultConfigProvider(t *testing.T) {
c.Run("Merge default strategy", func(c *qt.C) {
cfg := New()
cfg.Set("a", map[string]interface{}{
cfg.Set("a", map[string]any{
"B": "bv",
})
cfg.Merge("a", map[string]interface{}{
cfg.Merge("a", map[string]any{
"B": "bv2",
"c": "cv2",
})
@@ -163,7 +163,7 @@ func TestDefaultConfigProvider(t *testing.T) {
cfg.Set("a", "av")
cfg.Merge("", map[string]interface{}{
cfg.Merge("", map[string]any{
"a": "av2",
"b": "bv2",
})
@@ -176,16 +176,16 @@ func TestDefaultConfigProvider(t *testing.T) {
c.Run("Merge shallow", func(c *qt.C) {
cfg := New()
cfg.Set("a", map[string]interface{}{
cfg.Set("a", map[string]any{
"_merge": "shallow",
"B": "bv",
"c": map[string]interface{}{
"c": map[string]any{
"b": "bv",
},
})
cfg.Merge("a", map[string]interface{}{
"c": map[string]interface{}{
cfg.Merge("a", map[string]any{
"c": map[string]any{
"d": "dv2",
},
"e": "ev2",
@@ -203,20 +203,20 @@ func TestDefaultConfigProvider(t *testing.T) {
// Issue #8679
c.Run("Merge typed maps", func(c *qt.C) {
for _, left := range []interface{}{
for _, left := range []any{
map[string]string{
"c": "cv1",
},
map[string]interface{}{
map[string]any{
"c": "cv1",
},
map[interface{}]interface{}{
map[any]any{
"c": "cv1",
},
} {
cfg := New()
cfg.Set("", map[string]interface{}{
cfg.Set("", map[string]any{
"b": left,
})
@@ -235,27 +235,27 @@ func TestDefaultConfigProvider(t *testing.T) {
})
}
for _, left := range []interface{}{
for _, left := range []any{
map[string]string{
"b": "bv1",
},
map[string]interface{}{
map[string]any{
"b": "bv1",
},
map[interface{}]interface{}{
map[any]any{
"b": "bv1",
},
} {
for _, right := range []interface{}{
for _, right := range []any{
map[string]string{
"b": "bv2",
"c": "cv2",
},
map[string]interface{}{
map[string]any{
"b": "bv2",
"c": "cv2",
},
map[interface{}]interface{}{
map[any]any{
"b": "bv2",
"c": "cv2",
},
@@ -280,12 +280,12 @@ func TestDefaultConfigProvider(t *testing.T) {
c.Run("Prevent _merge only maps", func(c *qt.C) {
cfg := New()
cfg.Set("", map[string]interface{}{
cfg.Set("", map[string]any{
"B": "bv",
})
cfg.Merge("", map[string]interface{}{
"c": map[string]interface{}{
cfg.Merge("", map[string]any{
"c": map[string]any{
"_merge": "shallow",
"d": "dv2",
},
@@ -299,7 +299,7 @@ func TestDefaultConfigProvider(t *testing.T) {
c.Run("IsSet", func(c *qt.C) {
cfg := New()
cfg.Set("a", map[string]interface{}{
cfg.Set("a", map[string]any{
"B": "bv",
})
@@ -357,15 +357,15 @@ func TestDefaultConfigProvider(t *testing.T) {
func BenchmarkDefaultConfigProvider(b *testing.B) {
type cfger interface {
Get(key string) interface{}
Set(key string, value interface{})
Get(key string) any
Set(key string, value any)
IsSet(key string) bool
}
newMap := func() map[string]interface{} {
return map[string]interface{}{
"a": map[string]interface{}{
"b": map[string]interface{}{
newMap := func() map[string]any {
return map[string]any{
"a": map[string]any{
"b": map[string]any{
"c": 32,
"d": 43,
},
+1 -1
View File
@@ -35,7 +35,7 @@ func init() {
cfg.Set("languages", lang)
cfg.SetDefaultMergeStrategy()
configHelpers := map[string]interface{}{
configHelpers := map[string]any{
"mergeStrategy": cfg.Get(""),
}
return docshelper.DocProvider{"config": configHelpers}
+2 -2
View File
@@ -41,8 +41,8 @@ func SetEnvVars(oldVars *[]string, keyValues ...string) {
}
func SplitEnvVar(v string) (string, string) {
parts := strings.SplitN(v, "=", 2)
return parts[0], parts[1]
name, value, _ := strings.Cut(v, "=")
return name, value
}
func setEnvVar(vars *[]string, key, value string) {
+4 -4
View File
@@ -147,15 +147,15 @@ func (c Config) CheckAllowedHTTPMethod(method string) error {
}
// ToSecurityMap converts c to a map with 'security' as the root key.
func (c Config) ToSecurityMap() map[string]interface{} {
func (c Config) ToSecurityMap() map[string]any {
// Take it to JSON and back to get proper casing etc.
asJson, err := json.Marshal(c)
herrors.Must(err)
m := make(map[string]interface{})
m := make(map[string]any)
herrors.Must(json.Unmarshal(asJson, &m))
// Add the root
sec := map[string]interface{}{
sec := map[string]any{
"security": m,
}
return sec
@@ -196,7 +196,7 @@ func stringSliceToWhitelistHook() mapstructure.DecodeHookFuncType {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
data any) (any, error) {
if t != reflect.TypeOf(Whitelist{}) {
return data, nil
-1
View File
@@ -18,7 +18,6 @@ import (
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
)
func TestDecodeConfigFromTOML(t *testing.T) {
+3 -3
View File
@@ -283,10 +283,10 @@ func (b *contentBuilder) applyArcheType(contentFilename, archetypeFilename strin
defer f.Close()
if archetypeFilename == "" {
return b.cf.AppplyArchetypeTemplate(f, p, b.kind, DefaultArchetypeTemplateTemplate)
return b.cf.ApplyArchetypeTemplate(f, p, b.kind, DefaultArchetypeTemplateTemplate)
}
return b.cf.AppplyArchetypeFilename(f, p, b.kind, archetypeFilename)
return b.cf.ApplyArchetypeFilename(f, p, b.kind, archetypeFilename)
}
@@ -346,7 +346,7 @@ func (b *contentBuilder) openInEditorIfConfigured(filename string) error {
editorExec := strings.Fields(editor)[0]
editorFlags := strings.Fields(editor)[1:]
var args []interface{}
var args []any
for _, editorFlag := range editorFlags {
args = append(args, editorFlag)
}
+2 -2
View File
@@ -40,7 +40,7 @@ func TestNewContentFromFile(t *testing.T) {
name string
kind string
path string
expected interface{}
expected any
}{
{"Post", "post", "post/sample-1.md", []string{`title = "Post Arch title"`, `test = "test1"`, "date = \"2015-01-12T19:20:04-07:00\""}},
{"Post org-mode", "post", "post/org-1.org", []string{`#+title: ORG-1`}},
@@ -368,7 +368,7 @@ Some text.
return nil
}
func cContains(c *qt.C, v interface{}, matches ...string) {
func cContains(c *qt.C, v any, matches ...string) {
for _, m := range matches {
c.Assert(v, qt.Contains, m)
}
+1
View File
@@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !nodeploy
// +build !nodeploy
package deploy
+1
View File
@@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !nodeploy
// +build !nodeploy
package deploy
+1 -1
View File
@@ -115,7 +115,7 @@ func (m *matcher) Matches(path string) bool {
// decode creates a config from a given Hugo configuration.
func decodeConfig(cfg config.Provider) (deployConfig, error) {
var (
mediaTypesConfig []map[string]interface{}
mediaTypesConfig []map[string]any
dcfg deployConfig
)
+1
View File
@@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !solaris && !nodeploy
// +build !solaris,!nodeploy
package deploy
+5 -4
View File
@@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !nodeploy
// +build !nodeploy
package deploy
@@ -252,7 +253,7 @@ func TestWalkLocal(t *testing.T) {
if got, err := walkLocal(fs, nil, nil, nil, media.DefaultTypes); err != nil {
t.Fatal(err)
} else {
expect := map[string]interface{}{}
expect := map[string]any{}
for _, path := range tc.Expect {
if _, ok := got[path]; !ok {
t.Errorf("expected %q in results, but was not found", path)
@@ -290,7 +291,7 @@ func TestLocalFile(t *testing.T) {
Description string
Path string
Matcher *matcher
MediaTypesConfig []map[string]interface{}
MediaTypesConfig []map[string]any
WantContent []byte
WantSize int64
WantMD5 []byte
@@ -351,9 +352,9 @@ func TestLocalFile(t *testing.T) {
{
Description: "Custom MediaType",
Path: "foo.hugo",
MediaTypesConfig: []map[string]interface{}{
MediaTypesConfig: []map[string]any{
{
"hugo/custom": map[string]interface{}{
"hugo/custom": map[string]any{
"suffixes": []string{"hugo"},
},
},
+1
View File
@@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !nodeploy
// +build !nodeploy
package deploy
+3 -3
View File
@@ -68,7 +68,7 @@ type Deps struct {
FileCaches filecache.Caches
// The translation func to use
Translate func(translationID string, templateData interface{}) string `json:"-"`
Translate func(translationID string, templateData any) string `json:"-"`
// The language in use. TODO(bep) consolidate with site
Language *langs.Language
@@ -83,7 +83,7 @@ type Deps struct {
WithTemplate func(templ tpl.TemplateManager) error `json:"-"`
// Used in tests
OverloadedTemplateFuncs map[string]interface{}
OverloadedTemplateFuncs map[string]any
translationProvider ResourceProvider
@@ -392,7 +392,7 @@ type DepsCfg struct {
TemplateProvider ResourceProvider
WithTemplate func(templ tpl.TemplateManager) error
// Used in tests
OverloadedTemplateFuncs map[string]interface{}
OverloadedTemplateFuncs map[string]any
// i18n handling.
TranslationProvider ResourceProvider
+276
View File
@@ -0,0 +1,276 @@
{
"version": "0.2",
"words": [
"aaabaab",
"aabb",
"aabba",
"aabbaa",
"aabbaabb",
"abourget",
"absurl",
"adoc",
"algolia",
"allowfullscreen",
"ananke",
"anchorize",
"anthonyfok",
"asciidoctor",
"attrlink",
"azblob",
"baseof",
"beevelop",
"bibtex",
"Bjørn",
"blackfriday",
"blogue",
"bogem",
"Bootcamp",
"brlink",
"Brotli",
"Browsersync",
"canonify",
"Catmull",
"Catwoman",
"Cheatsheet",
"choco",
"chromastyles",
"clockoon",
"Cloudinary",
"CNAME",
"Codecademy's",
"CODEOWNERS",
"Coen",
"Commento",
"Cond",
"Contentful",
"copyrighthtml",
"corejs",
"countrunes",
"countwords",
"crossreferences",
"datatable",
"DATOCMS",
"debugconfig",
"DELIM",
"dhersam",
"digitalcraftsman",
"Disqus",
"Dmdh",
"doas",
"dokuwiki",
"dpkg",
"DRING",
"Emojify",
"Enwrite",
"eopkg",
"eparis",
"errorf",
"erroridf",
"Evernote",
"exitwp",
"Feminella",
"firstpost",
"Formspree",
"fpath",
"Francia",
"freenode",
"frontmatter",
"funcs",
"funcsig",
"Garen",
"gcloud",
"Getenv",
"getjson",
"getpage",
"Gmfc",
"Goel",
"Gohugo",
"gohugoio",
"goldenbridge",
"Goldmark",
"gomodules",
"GOPATH",
"govendor",
"Gowans",
"Grayscale",
"Gruber",
"gtag",
"hidecaption",
"Hokus",
"hola",
"hügó",
"hugodeps",
"hugodoc",
"Hugofy",
"hugolang",
"hugoversion",
"Hyas",
"Hyvor",
"iframes",
"ifttt",
"iife",
"imgproc",
"importr",
"IMWQ",
"indice",
"innershortcode",
"Intelli",
"interdoc",
"IPTC",
"ismenucurrent",
"Isset",
"Isso",
"Jaco",
"johnpatitucci",
"Joomla",
"JRBR",
"jsonify",
"katex",
"keycdn",
"KEYVALS",
"kubernetes",
"Lanczos",
"langformatnumber",
"lastmod",
"libwebp",
"linktitle",
"Lipi",
"lrwxr",
"maingo",
"markdownified",
"markdownify",
"mathjax",
"mdhender",
"mdshortcode",
"mercredi",
"Mittwoch",
"mkdir",
"mmark",
"monokai",
"Morling",
"mspowerpoint",
"Multihost",
"Muut",
"myclass",
"mydeployment",
"myindex",
"mylayout",
"mypage",
"mypartials",
"mypost",
"mysite",
"myspa",
"mystyle",
"mytheme",
"NDJSON",
"needsexample",
"Netravali",
"newparam",
"Nikhil",
"Njjy",
"nlist",
"nobr",
"nocopy",
"Norsk",
"nosniff",
"NOSQL",
"notoc",
"novembre",
"NUMWORKERMULTIPLIER",
"Obhu",
"octohug",
"Octopress",
"oldparam",
"onrender",
"opengraph",
"OWASP",
"Pandoc",
"partialcached",
"Pastorius",
"Patitucci",
"PCRE",
"peaceiris",
"Pedersen",
"permalinkable",
"plainify",
"POSIX",
"postprocess",
"println",
"publishdate",
"Pygments",
"querify",
"QVOMC",
"rdwatters",
"readfile",
"rebinded",
"REDIR",
"reftext",
"relatedfuncs",
"relref",
"relurl",
"remarkjs",
"rgba",
"rlimit",
"roboto",
"rssxml",
"rwxrwxrwx",
"safehtml",
"safejs",
"Samsa",
"Shekhar",
"Shortcode",
"Shortcodes",
"Sindre",
"sitemapindex",
"sitemapxml",
"Smartcrop",
"Sprintf",
"Startseite",
"strconv",
"stringifier",
"struct",
"structs",
"subdir",
"Talkyard",
"taxo",
"tbody",
"tdewolff",
"testshortcodes",
"thead",
"Thinkful",
"TLDR",
"TMPDIR",
"tojson",
"Torikian",
"totoml",
"toyaml",
"twitteruser",
"Unmarshal",
"Unmarshal",
"urlize",
"urlset",
"vimrc",
"wanghc",
"Wappalyzer",
"warnf",
"webp",
"Wercker",
"wibble",
"wordcount",
"workson",
"xvzf",
"yoyoyo",
"Zgotmpl",
"zzbbaabb"
],
"language": "en,en-GB,en-US,de,fr",
"files": [
"**/*.md"
],
"ignorePaths": [
".cspell.json",
"**/node_modules/**",
"*.min.*"
],
"useGitignore": true
}
+17
View File
@@ -0,0 +1,17 @@
name: 'Check spelling'
on: # rebuild any PRs and main branch changes
push:
branches-ignore:
- "dependabot/**"
pull_request:
jobs:
spellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: streetsidesoftware/cspell-action@3bc52c39528d0214a947ff7f8b5c23ad3273435b
with:
inline: warning
strict: false
incremental_files_only: true
@@ -58,6 +58,7 @@
</head>
<body class="ma0 sans-serif bg-primary-color-light{{ with getenv "HUGO_ENV" }} {{ . }}{{ end }}">
{{ partial "hooks/after-body-start" . }}
{{ block "nav" . }}{{ partial "site-nav.html" . }}{{ end }}
{{ block "header" . }}{{ end }}
<main role="main" class="content-with-sidebar min-vh-100 pb7 pb0-ns">
@@ -65,6 +66,9 @@
</main>
{{ block "footer" . }}{{ partialCached "site-footer.html" . }}{{ end }}
{{ partial "hooks/before-body-end" . }}
{{ if .Page.Store.Get "hasMermaid" }}
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
@@ -0,0 +1 @@
{{/* Deliberately empty */}}
@@ -0,0 +1 @@
{{/* Deliberately empty */}}
@@ -8,7 +8,7 @@
HUGO
</a>
</h1>
<ul class="list ma0 pa0 dn dib-l">
<ul class="list ma0 pa0 dn dib-l" role="menu">
{{ range .Site.Menus.global }}
<li class="f5 dib mr4" role="menuitem">
{{/* TODO: Create an "Global" active class to show which site one is currently at */}}
+1 -1
View File
@@ -1 +1 @@
# github.com/gohugoio/gohugoioTheme v0.0.0-20211211134334-0fe25799bb58
# github.com/gohugoio/gohugoioTheme v0.0.0-20220228085601-7cfbda06d135
@@ -42,7 +42,7 @@ The default configuration is listed below. Any build using features not in the a
Note that these and other config settings in Hugo can be overridden by the OS environment. If you want to block all remote HTTP fetching of data:
```
HUGO_SECURITY_HTTP_URLS=none hugo
HUGO_SECURITY_HTTP_URLS=none hugo
```
## Dependency Security
+2 -2
View File
@@ -14,7 +14,7 @@ hugo 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 http://gohugo.io/.
Complete documentation is available at https://gohugo.io/.
```
hugo [flags]
@@ -23,7 +23,7 @@ hugo [flags]
### Options
```
-b, --baseURL string hostname (and path) to the root, e.g. http://spf13.com/
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
-D, --buildDrafts include content marked as draft
-E, --buildExpired include expired content
-F, --buildFuture include content with publishdate in the future
+1 -1
View File
@@ -12,7 +12,7 @@ Generate Markdown documentation for the Hugo CLI.
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 http://gohugo.io/.
of Hugo's command-line interface for https://gohugo.io/.
It creates one Markdown file per command with front matter suitable
for rendering in Hugo.
+1 -1
View File
@@ -26,7 +26,7 @@ See https://gohugo.io/hugo-modules/ for more information.
### Options
```
-b, --baseURL string hostname (and path) to the root, e.g. http://spf13.com/
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
-D, --buildDrafts include content marked as draft
-E, --buildExpired include expired content
-F, --buildFuture include content with publishdate in the future
+1 -1
View File
@@ -25,7 +25,7 @@ hugo new [path] [flags]
### Options
```
-b, --baseURL string hostname (and path) to the root, e.g. http://spf13.com/
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
-D, --buildDrafts include content marked as draft
-E, --buildExpired include expired content
-F, --buildFuture include content with publishdate in the future
+1 -1
View File
@@ -30,7 +30,7 @@ hugo server [flags]
```
--appendPort append port to baseURL (default true)
-b, --baseURL string hostname (and path) to the root, e.g. http://spf13.com/
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
--bind string interface to which the server will bind (default "127.0.0.1")
-D, --buildDrafts include content marked as draft
-E, --buildExpired include expired content
@@ -18,12 +18,12 @@ toc: true
They are stored in a reserved Front Matter object named `_build` with the following defaults:
```yaml
{{< code-toggle >}}
_build:
render: always
list: always
publishResources: true
```
{{< /code-toggle >}}
#### render
If `always`, the page will be treated as a published page, holding its dedicated output files (`index.html`, etc...) and permalink.
@@ -30,7 +30,7 @@ Hugo comes with all the code you need to load Disqus into your templates. Before
Disqus comments require you set a single value in your [site's configuration file][configuration] like so:
{{< code-toggle copy="false" >}}
disqusShortname = "yourdiscussshortname"
disqusShortname = "yourDisqusShortname"
{{</ code-toggle >}}
For many websites, this is enough configuration. However, you also have the option to set the following in the [front matter][] of a single content file:
@@ -12,8 +12,68 @@ toc: true
---
{{< new-in "0.93.0" >}}
## GoAT Diagrams (Ascii)
Hugo! supports [GoAT](https://github.com/bep/goat) natively. This means that this code block:
````
```goat
. . . .--- 1 .-- 1 / 1
/ \ | | .---+ .-+ +
/ \ .---+---. .--+--. | '--- 2 | '-- 2 / \ 2
+ + | | | | ---+ ---+ +
/ \ / \ .-+-. .-+-. .+. .+. | .--- 3 | .-- 3 \ / 3
/ \ / \ | | | | | | | | '---+ '-+ +
1 2 3 4 1 2 3 4 1 2 3 4 '--- 4 '-- 4 \ 4
```
````
Will be rendered as:
```goat
. . . .--- 1 .-- 1 / 1
/ \ | | .---+ .-+ +
/ \ .---+---. .--+--. | '--- 2 | '-- 2 / \ 2
+ + | | | | ---+ ---+ +
/ \ / \ .-+-. .-+-. .+. .+. | .--- 3 | .-- 3 \ / 3
/ \ / \ | | | | | | | | '---+ '-+ +
1 2 3 4 1 2 3 4 1 2 3 4 '--- 4 '-- 4 \ 4
```
## Mermaid Diagrams
Hugo currently does not provide default templates for Mermaid diagrams. But you can easily add your own. One way to do it would be to create ` layouts/_default/_markup/render-codeblock-mermaid.html`:
```go-html-template
<div class="mermaid">
{{- .Inner | safeHTML }}
</div>
{{ .Page.Store.Set "hasMermaid" true }}
```
And then include this snippet at the bottom of the content template (below `.Content`):
```go-html-template
{{ if .Page.Store.Get "hasMermaid" }}
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>
mermaid.initialize({ startOnLoad: true });
</script>
{{ end }}
```
With that you can use the `mermaid` language in Markdown code blocks:
```mermaid
sequenceDiagram
participant Alice

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