Compare commits

...

340 Commits

Author SHA1 Message Date
Bjørn Erik Pedersen b1163db2c8 commands: Make the server flag --renderToDisk into --renderToMemory (note)
Fixes #11987
2024-02-05 10:16:23 +01:00
Bjørn Erik Pedersen 609d798e34 Handle resource changes when the resources is already evicted from cache
Also fix a logical flaw in the cache resizer that made it too aggressive. After this I haven't been able to reproduce #11988, but I need to look closer.

Closes #11973
Updates #11988
2024-02-04 16:55:06 +01:00
Bjørn Erik Pedersen 53f204310e all: Rename Unmormalized => Unnormalized 2024-02-03 18:10:38 +01:00
Joe Mooring 7f82461407 hugolib: Formally deprecate .Page.NextPage .Page.PrevPage
Use .Page.Next and .Page.Prev instead.
2024-02-03 17:28:52 +01:00
Bjørn Erik Pedersen b72f909725 hugio: Rename strigReadSeeker => stringReadSeeker 2024-02-03 17:25:34 +01:00
Joe Mooring 3a665ddbf9 resources/page: Formally deprecate .Site.LastChange
Use .Site.Lastmod instead.
2024-02-03 17:11:06 +01:00
Bjørn Erik Pedersen 46575baa02 Preserve file/dir name case when loading data
Fixes #11979
2024-02-03 16:30:10 +01:00
Bjørn Erik Pedersen 058f230a1b Detect now invalid path patterns in cascade
Closes #11977
2024-02-03 15:07:59 +01:00
Bjørn Erik Pedersen a66480f70c Handle build vs _build in front matter
* Throw a detailed error message in the mentioned case
* Also fixed a dropped error

Fixes #11970
2024-02-03 15:07:59 +01:00
Bjørn Erik Pedersen e33a632551 resources: Optimize reading resource Content when it's already a string 2024-02-03 15:07:59 +01:00
Bjørn Erik Pedersen 2873324898 Misc resource fixes/improvements
* Add --pprof flag to server to enable profile debugging.
* Don't cache the resource content, it seem to eat memory on bigger sites.
* Keep --printMemoryUsag running in server

Fixes #11974
2024-02-02 18:46:23 +01:00
Joe Mooring d0788b96ae tpl/tplimpl: Update embedded instagram, twitter, and vimeo shortcodes
- Replace data.GetJSON calls with resources.GetRemote
- Remove usage of Facebook’s oEmbed Read feature

Fixes #11971
2024-02-02 08:01:45 +01:00
Bjørn Erik Pedersen 034fbef50d Add some more context to error
Updates #11970
2024-02-01 21:40:32 +01:00
dependabot[bot] 54ad51e8a6 build(deps): bump github.com/getkin/kin-openapi from 0.122.0 to 0.123.0
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.122.0 to 0.123.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.122.0...v0.123.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>
2024-02-01 19:56:04 +01:00
dependabot[bot] bd1bcc0f91 build(deps): bump github.com/tdewolff/minify/v2 from 2.20.13 to 2.20.16
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.20.13 to 2.20.16.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.20.13...v2.20.16)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-01 19:55:31 +01:00
Bjørn Erik Pedersen 8d42a7942a Improve nilpointer error message 2024-02-01 19:53:51 +01:00
Bjørn Erik Pedersen 4174a7866b Fix disabled languages regression
Fixes #11959
2024-02-01 19:42:55 +01:00
Joe Mooring 5dd06b4136 tpl/data: Fix GetCSV deprecation message 2024-02-01 19:21:15 +01:00
Bjørn Erik Pedersen f5ec75db36 hugolib: Adjust a test case 2024-02-01 18:20:13 +01:00
Joe Mooring 6cb3bda3d1 hugolib: Revert deprecation of .Page.Lang
Deprecation message was also emitted when calling .Page.Language.Lang.
Reverting for now, but will remove all references to .Page.Lang from
documentation.
2024-02-01 18:00:50 +01:00
Joe Mooring 963cecc12c all: Deprecate .Page.Lang and .Page.File.Lang
Use .Page.Language.Lang instead.
2024-02-01 17:12:52 +01:00
Bjørn Erik Pedersen d8f0e30715 Fix failing test on Windows 2024-02-01 15:10:52 +01:00
Joe Mooring b6def61727 config/security: Add SYSTEMDRIVE to OsEnv allowlist 2024-02-01 15:10:05 +01:00
Bjørn Erik Pedersen 1891d5e6b5 hugolib: Add some more details to the "paginator not supported" error
See #11949
2024-02-01 08:18:11 +01:00
Bjørn Erik Pedersen 156f08de35 Run go mod tidy 2024-02-01 08:08:58 +01:00
dependabot[bot] b332f243fd build(deps): bump github.com/evanw/esbuild from 0.19.12 to 0.20.0
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.19.12 to 0.20.0.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.19.12...v0.20.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-31 22:13:09 +01:00
Bjørn Erik Pedersen d8c2734178 deploy: Fix CloudFront invalidation with AWS SDK2 2024-01-31 22:12:48 +01:00
Bjørn Erik Pedersen d8e1e82188 deps: Update gocloud.dev/aws 2024-01-31 22:12:48 +01:00
Franky W a1c64989df Upgrade to deploy to use AWS SDK V2 2024-01-31 22:12:48 +01:00
Bjørn Erik Pedersen 6c3b6ba3e6 Improve error message when attempting to paginate from a single page template
Fixes #11953
2024-01-31 16:54:34 +01:00
dependabot[bot] 4d98b0ed6a build(deps): bump github.com/aws/aws-sdk-go from 1.48.6 to 1.50.7
Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.48.6 to 1.50.7.
- [Release notes](https://github.com/aws/aws-sdk-go/releases)
- [Commits](https://github.com/aws/aws-sdk-go/compare/v1.48.6...v1.50.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-31 13:43:46 +01:00
dependabot[bot] 15b9976b7a build(deps): bump golang.org/x/image from 0.14.0 to 0.15.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.14.0 to 0.15.0.
- [Commits](https://github.com/golang/image/compare/v0.14.0...v0.15.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-31 13:42:24 +01:00
Bjørn Erik Pedersen 34d63c8d1c Fix build error 2024-01-31 13:00:56 +01:00
Bjørn Erik Pedersen 51615440bf docs: Make null booleans falsy in the docs helper 2024-01-31 12:34:28 +01:00
Bjørn Erik Pedersen bd66d30295 Filter out duplicate content resource files
We do a slight normalisation of the content paths (lower case, replacing " " with "-") and remove andy language identifier before inserting them into the content tree.

This means that, given that that the default content language is `en`:

```
index.md
index.html
Foo Bar.txt
foo-bar.txt
foo-bar.en.txt
Foo-Bar.txt
```

The bundle above will be reduced to one content file with one resource (`foo-bar.txt`).

Before this commit, what version of the `foo-bar.txt` you ended up with was undeterministic. No  we pick the first determined by sort order.

Note that the sort order is stable, but we recommend avoiding situations like the above.

Closes #11946
2024-01-31 10:06:04 +01:00
Bjørn Erik Pedersen 7caa5b3e50 docs: Regen docs helper 2024-01-31 09:45:35 +01:00
Bjørn Erik Pedersen 309d61b220 output: Prevent setting Name directly in new output formats
Name is derived from the map key.

Closes #11947
2024-01-31 09:43:02 +01:00
Bjørn Erik Pedersen 5b7cb258ec Create default link and image render hooks
Fixes #11933
2024-01-30 20:12:19 +01:00
Bjørn Erik Pedersen 80595bbe3e Fix recent regression .Resources.Get for resources with spaces in filename
Fixes #11944
2024-01-30 20:12:03 +01:00
Bjørn Erik Pedersen afee781f03 Emit a warning that can be turned off when overwriting built-in .Params values
Fixes #11941
2024-01-30 20:12:03 +01:00
Bjørn Erik Pedersen 4e84f57efb Add warnidf template function
Also rename config `ignoreErrors` => `ignoreLogs`

But the old still works.

Closes #9189
2024-01-30 20:12:03 +01:00
Bjørn Erik Pedersen f31a6db797 Add path, kind and lang to content front matter
Note that none of these can be set via cascade (you will get an error)

Fixes #11544
2024-01-30 20:12:03 +01:00
Lars Lehtonen ec22bb31a8 hugofs/glob: Fix dropped test error 2024-01-28 23:31:10 +01:00
Bjørn Erik Pedersen a795acbcd8 all: Run gofumpt -l -w . 2024-01-28 23:14:09 +01:00
Bjørn Erik Pedersen 982d9513e7 testing: Simplify some integration tests 2024-01-28 22:17:22 +01:00
Bjørn Erik Pedersen 6dedb4efc7 Add the [params] concept to front matter
This is deliberately very simple, but should not break anything. We need to introduce this in baby steps, but this should allow us to introduce this in the documentation.

Note that the `params` section's key/values will be added to `.Params` last. This means that you can have different values for "Hugo's summary" and the custom ".Params.summary" if you want to.

Updates #11055
2024-01-28 21:38:40 +01:00
Bjørn Erik Pedersen 292626e679 tpl/data: Deprecate data.GetJSON and data.GetCSV 2024-01-28 16:37:36 +01:00
razonyang 60d954c785 modules: Print required Hugo version for incompatible modules 2024-01-28 15:23:21 +01:00
Bjørn Erik Pedersen 63e0a92894 hugolib: Remove unused test image 2024-01-28 15:14:53 +01:00
Kandula Naveen ce7daa6156 navigation: Improve menu cache 2024-01-28 15:11:57 +01:00
Bjørn Erik Pedersen 2a0329423c testing: Rename integration_test.go to PACKAGE_integration_test.go
Primary motivation making them easier to find in the code editor.
2024-01-28 11:41:59 +01:00
Bjørn Erik Pedersen 50dc327d1a Port some integration tests to new test setup
The method I'm currently using (if other want to help) is:

* Add fmt.Println(b.DumpTxtar()) after the Build step
* Add that to a files var and pass that to Test(t, files) or similar
* Then, if possible, try to reduce the files/content down to what's needed in test.

Note that if the test is small, it's probably faster just to manually re-create the test.
2024-01-28 11:29:23 +01:00
Bjørn Erik Pedersen 7285e74090 all: Rework page store, add a dynacache, improve partial rebuilds, and some general spring cleaning
There are some breaking changes in this commit, see #11455.

Closes #11455
Closes #11549

This fixes a set of bugs (see issue list) and it is also paying some technical debt accumulated over the years. We now build with Staticcheck enabled in the CI build.

The performance should be about the same as before for regular sized Hugo sites, but it should perform and scale much better to larger data sets, as objects that uses lots of memory (e.g. rendered Markdown, big JSON files read into maps with transform.Unmarshal etc.) will now get automatically garbage collected if needed. Performance on partial rebuilds when running the server in fast render mode should be the same, but the change detection should be much more accurate.

A list of the notable new features:

* A new dependency tracker that covers (almost) all of Hugo's API and is used to do fine grained partial rebuilds when running the server.
* A new and simpler tree document store which allows fast lookups and prefix-walking in all dimensions (e.g. language) concurrently.
* You can now configure an upper memory limit allowing for much larger data sets and/or running on lower specced PCs.
We have lifted the "no resources in sub folders" restriction for branch bundles (e.g. sections).
Memory Limit
* Hugos will, by default, set aside a quarter of the total system memory, but you can set this via the OS environment variable HUGO_MEMORYLIMIT (in gigabytes). This is backed by a partitioned LRU cache used throughout Hugo. A cache that gets dynamically resized in low memory situations, allowing Go's Garbage Collector to free the memory.

New Dependency Tracker: Hugo has had a rule based coarse grained approach to server rebuilds that has worked mostly pretty well, but there have been some surprises (e.g. stale content). This is now revamped with a new dependency tracker that can quickly calculate the delta given a changed resource (e.g. a content file, template, JS file etc.). This handles transitive relations, e.g. $page -> js.Build -> JS import, or $page1.Content -> render hook -> site.GetPage -> $page2.Title, or $page1.Content -> shortcode -> partial -> site.RegularPages -> $page2.Content -> shortcode ..., and should also handle changes to aggregated values (e.g. site.Lastmod) effectively.

This covers all of Hugo's API with 2 known exceptions (a list that may not be fully exhaustive):

Changes to files loaded with template func os.ReadFile may not be handled correctly. We recommend loading resources with resources.Get
Changes to Hugo objects (e.g. Page) passed in the template context to lang.Translate may not be detected correctly. We recommend having simple i18n templates without too much data context passed in other than simple types such as strings and numbers.
Note that the cachebuster configuration (when A changes then rebuild B) works well with the above, but we recommend that you revise that configuration, as it in most situations should not be needed. One example where it is still needed is with TailwindCSS and using changes to hugo_stats.json to trigger new CSS rebuilds.

Document Store: Previously, a little simplified, we split the document store (where we store pages and resources) in a tree per language. This worked pretty well, but the structure made some operations harder than they needed to be. We have now restructured it into one Radix tree for all languages. Internally the language is considered to be a dimension of that tree, and the tree can be viewed in all dimensions concurrently. This makes some operations re. language simpler (e.g. finding translations is just a slice range), but the idea is that it should also be relatively inexpensive to add more dimensions if needed (e.g. role).

Fixes #10169
Fixes #10364
Fixes #10482
Fixes #10630
Fixes #10656
Fixes #10694
Fixes #10918
Fixes #11262
Fixes #11439
Fixes #11453
Fixes #11457
Fixes #11466
Fixes #11540
Fixes #11551
Fixes #11556
Fixes #11654
Fixes #11661
Fixes #11663
Fixes #11664
Fixes #11669
Fixes #11671
Fixes #11807
Fixes #11808
Fixes #11809
Fixes #11815
Fixes #11840
Fixes #11853
Fixes #11860
Fixes #11883
Fixes #11904
Fixes #7388
Fixes #7425
Fixes #7436
Fixes #7544
Fixes #7882
Fixes #7960
Fixes #8255
Fixes #8307
Fixes #8863
Fixes #8927
Fixes #9192
Fixes #9324
2024-01-27 16:28:14 +01:00
Bjørn Erik Pedersen 5fd1e74903 Merge commit '9b0050e9aabe4be65c78ccf292a348f309d50ccd' as 'docs'
```
git subtree add --prefix=docs/ https://github.com/gohugoio/hugoDocs.git master --squash
```

Closes #11925
2024-01-27 10:48:57 +01:00
Bjørn Erik Pedersen 9b0050e9aa Squashed 'docs/' content from commit 5c085a37b
git-subtree-dir: docs
git-subtree-split: 5c085a37b2
2024-01-27 10:48:33 +01:00
Bjørn Erik Pedersen fc7de7136a docs: Prepare for new sub tree
See #11925
2024-01-27 10:47:28 +01:00
hugoreleaser 1083bf7c08 releaser: Prepare repository for 0.123.0-DEV
[ci skip]
2024-01-26 16:06:12 +00:00
hugoreleaser b9a03bd59d releaser: Bump versions for release of 0.122.0
[ci skip]
2024-01-26 15:54:24 +00:00
dependabot[bot] e0021f4961 build(deps): bump golang.org/x/tools from 0.16.0 to 0.17.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.16.0 to 0.17.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.16.0...v0.17.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-26 13:19:29 +01:00
dependabot[bot] d25902c0d8 build(deps): bump github.com/rogpeppe/go-internal from 1.11.0 to 1.12.0
Bumps [github.com/rogpeppe/go-internal](https://github.com/rogpeppe/go-internal) from 1.11.0 to 1.12.0.
- [Release notes](https://github.com/rogpeppe/go-internal/releases)
- [Commits](https://github.com/rogpeppe/go-internal/compare/v1.11.0...v1.12.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-26 12:55:48 +01:00
Bjørn Erik Pedersen 50042ee1f4 docs: Regen docshelper 2024-01-26 08:23:37 +01:00
Bjørn Erik Pedersen 3758456b31 Merge commit '7125ad401ad043e46262afc7eca8dceb6d54bb9e' 2024-01-26 08:22:42 +01:00
Bjørn Erik Pedersen 7125ad401a Squashed 'docs/' changes from 4dd2d6415..3b1a8579d
3b1a8579d Update theme
b1f04fd47 params: Adjust the twitter handle
2c8ba029f Fix typo
4421e46ec Update Where.md
5f9fec6a5 Update glossary.md
fb16945cf collections.Where - Clarify description of "collection"
4d6d7018b Update Include.md - improve return statement example
037151073 Update IncludeCached - improve return statement example
b053fa09c Improve return types
f7d615267 Describe minimum required Windows version
e97762b2a Update front matter in math.Sub
9e4d03a79 Fix data.GetJSON examples
9029ee123 Update spellcheck GitHub Action
c852fe421 Add version verification step to quick start guide
8fafca49c Correct front matter example
b412fdf65 Fix closing tag in notes docs
9a64d9579 Add hreflang Tag to Translation Link example
2d2a82101 Fix inverted examples in Permalink and RelPermalink
ff3e67e3f Correct spelling
3904b72e3 Fix documentation contribution steps enumeration
348fd3c02 Fix some typos in the content summaries page
a5823459a Fix two links, one to bonsai.io and one to digital.gov
c36978686 Fix typo
0ee3ff44f Vendor theme
6b0a9b75d Fix typo
44e4cb5c6 Fix typos
815071742 Update min Go version to build from source
192d14df5 Fix typo
e4a18946e Miscellaneous corrections
f12b7b74e netlify: Hugo 0.121.2
97dbc1ea1 Merge branch 'tempv0.121.2'
dc1c0aeeb Add cache note to resources.Concat
a8d1a6020 Correct return type
eca22e97c Fix front matter
7de247a03 Correct return type
5058bc520 collections.Seq: Specify max slice size
e24d81a4b Fix typo on FAQ page
ff8cf5a88 Correct 'related' front matter values
88b21fae0 Adjust deprecation handling
f7e19a25b Update terminology on shortcode templates page
01e10942a Adjust formatting
6ed2e19c2 tpl/math: Add math.Rand template function
b494a8d8d Update js.md
6d78035f4 Add interval description to glossary (#2378)
f9b612b64 Templates Lookup Order: Add missing parenthesis
741ab1bb2 Show vendorClosest in module default config
eb8cb878f Fix Babel headings
cdcdfc8e8 Change project name in Codeowners example
f31fe0d63 Update link to image filters
9dee1ee0e Update Solus installation instructions
716636fee Add TOC to collections.Sort
f7c64143c Update collections.Sort
4b8f6b991 Add id attributes to Scratch and Store methods
2a1adea79 Add id attributes to GitInfo, File, and Resources methods
b12d9cb8f Add Alpine Linux installation instructions
dfbebeec7 Change writeStats to buildStats
175b8aac8 resources/images: Create AutoOrient image filter
ba8e05c31 Update img shortcode
7fc15a6cc Fix default value of  pluralizeListTitles
a0be4fd8a Add Comentario to available comment engines
71aa7d8ab Update GitInfo.md
348441491 EXIF: describe applicable image types
743497eda Disable TOC for lang.Translate
b3b1df75d Describe lang.Translate fallback behavior
a6d9c362c Categorize CLI commands to improve search results
08cc2da4b Document canonical config for enabling inline shortcodes
8945be9c6 Update description of the humanize ordinal conversion
b8b4ccb6f Fix grammatical error
f53f9f936 Fix description of strings.HasSuffix
cdbe19b5b Update Params.md
e00c4eea2 Reformat site config descriptions
6f50c0755 Enumerate root configuration keys
1f2e7b9b2 Use canonical key when documenting menu configs
7d5a13d8d Update images.Text filter
315d2f8c3 Add math.Counter example to warnf
8ed9cdaa9 Miscellaneous edits
2bda491eb Fix front matter example
5c5bbbe3a netlify: Bump to Hugo 0.121.1
ed08f5943 Update Gentoo installation instructions
e4eb48457 Miscellaneous edits
8c3700d12 netlify: Bump to Hugo 0.121.0
1bb5a377f Merge branch 'tempv0.121.0'
8cba89504 Improve where function
0dfc00b0f docs: Regen docshelper
028076a66 Fix link to Gentoo package
52d2db4ff Fix typo
120025c19 Improve Gentooo installation instructions
0c31a4b45 Update GitHub hosting workflow example
dee99c36a Defend against missing aliases in quick-reference shortcode
d305278f1 Add Gentoo installation instructions
be98a37e1 docs: Adjust last merge from docs repository
a703dab53 docs: Regen docs helper
2e9c3532f Merge commit '35dec7c96f7ee3eb17dd444f7067f0c776fb56ae'
f57fdb44b deps: Update github.com/tdewolff/minify/v2 v2.20.7 => v2.20.8
50cad5e38 tpl/transform: Add transform.XMLEscape template function

git-subtree-dir: docs
git-subtree-split: 3b1a8579d2
2024-01-26 08:11:52 +01:00
Jeremy Kun d0d2c6795e markup/goldmark: Support passthrough extension
Fixes #10894
2024-01-26 08:11:35 +01:00
dependabot[bot] 2dd608378d build(deps): bump github.com/pelletier/go-toml/v2 from 2.1.0 to 2.1.1
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.1.0 to 2.1.1.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml)
- [Commits](https://github.com/pelletier/go-toml/compare/v2.1.0...v2.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-25 19:48:59 +01:00
dependabot[bot] 45f52be7f8 build(deps): bump github.com/evanw/esbuild from 0.19.8 to 0.19.12
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.19.8 to 0.19.12.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.19.8...v0.19.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-25 19:48:32 +01:00
Daniel Terhorst-North 87bf2b9688 tpl/tplimpl: Fix incorrect lastBuildDate
Fixes #11600

Co-authored-by: Joe Mooring <joe@mooring.com>
2024-01-25 19:46:52 +01:00
Daniel Terhorst-North f281ef8a4a tpl: fix incorrect lastBuildDate
Set the `<lastBuildDate>` field to be the most recent Lastmod date
of all the posts in the current selection.

Fixes #11600
2024-01-25 19:46:52 +01:00
August Feng 46f618756f parser/metadecoders: Accumulate org keywords into arrays
Closes #11743
2024-01-25 19:34:12 +01:00
James Tatum 8915343075 deps: Update github.com/tdewolff/minify/v2 v2.20.9 => v2.20.13
KeepConditionalComments was deprecated in the upstream library and replaced with KeepSpecialComments. This new option reflects that both conditional comments and Server Side Include comments can be optionally stripped by the minifier. As with KeepConditionalComments, the minifier is configured not to strip them by default.
2024-01-25 19:33:14 +01:00
Bjørn Erik Pedersen a541e3b4d4 Upgrade to Go 1.21.6
Closes #11892
2024-01-16 10:49:03 +01:00
Joe Mooring 912c6576bb parser/metadecoders: Add CSV lazyQuotes option to transform.Unmarshal
If true, a quote may appear in an unquoted field and a non-doubled quote
may appear in a quoted field. It defaults to false.

Closes #11884
2024-01-16 09:26:44 +01:00
Joe Mooring 911bc60a7a README: Update minimum Go version to 1.20 2024-01-06 05:31:49 -08:00
hugoreleaser 5ff6323320 releaser: Prepare repository for 0.122.0-DEV
[ci skip]
2024-01-05 12:34:12 +00:00
hugoreleaser 6d5b44305e releaser: Bump versions for release of 0.121.2
[ci skip]
2024-01-05 12:21:15 +00:00
dependabot[bot] 1ccd3147a0 build(deps): bump golang.org/x/crypto from 0.16.0 to 0.17.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.16.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.16.0...v0.17.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-05 13:17:17 +01:00
Joe Mooring e40b9fbbcf tpl/math: Add math.Rand template function
Closes #11833
2023-12-29 19:37:27 +01:00
Bjørn Erik Pedersen 9cd8fbb332 Adjust site benchmark 2023-12-29 16:42:33 +01:00
Bjørn Erik Pedersen abcc61002a Simplify baseline benchmark 2023-12-29 15:56:02 +01:00
Joe Mooring 648d00c7d8 resources/images: Create AutoOrient image filter
Closes #11717
2023-12-20 14:18:24 +01:00
Bjørn Erik Pedersen 8adba648cc all: Remove unused code
Using x/tools/cmd/deadcode
2023-12-18 19:51:26 +01:00
hugoreleaser 6f13430d4a releaser: Prepare repository for 0.122.0-DEV
[ci skip]
2023-12-08 09:01:24 +00:00
hugoreleaser 00b46fed8e releaser: Bump versions for release of 0.121.1
[ci skip]
2023-12-08 08:47:45 +00:00
Bjørn Erik Pedersen eb9f1eb657 Upgrade to Go 1.21.5
Closes #11786
2023-12-07 12:40:10 +01:00
hugoreleaser 5186e762ac releaser: Prepare repository for 0.122.0-DEV
[ci skip]
2023-12-05 15:36:07 +00:00
hugoreleaser e321c3502a releaser: Bump versions for release of 0.121.0
[ci skip]
2023-12-05 15:22:31 +00:00
Bjørn Erik Pedersen 255e0a9718 docs: Regen docshelper 2023-12-05 16:19:43 +01:00
dependabot[bot] 558f3258a7 build(deps): bump github.com/alecthomas/chroma/v2 from 2.11.1 to 2.12.0
Bumps [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) from 2.11.1 to 2.12.0.
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.11.1...v2.12.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-05 09:42:04 +01:00
Joe Mooring 6580cd30ad docs: Adjust last merge from docs repository 2023-12-04 16:57:15 +01:00
Bjørn Erik Pedersen 7617de86cd docs: Regen docs helper 2023-12-04 15:24:34 +01:00
Bjørn Erik Pedersen d19ed4d4e6 Merge commit '35dec7c96f7ee3eb17dd444f7067f0c776fb56ae' 2023-12-04 15:24:01 +01:00
Bjørn Erik Pedersen 35dec7c96f Squashed 'docs/' changes from 4d936aee6..4dd2d6415
4dd2d6415 Fix erroridf example
9ae8e9199 Clarify highlight options
f3943f9c8 Fix typo
b57ea5ac8 Add field to glossary of terms
3191e35b4 Clarify comment in the new-in shortcode
870c8d35c Correct description of Ref and RelRef page methods
c9df50e6e Specify encoding in description of crypto functions
749bb37e2 Add Ref, RelRef, and Site shortcode methods
994d4374b Update fmt functions
740f5ef96 Misc additions
77acdcdb6 Remove rssLimit from root config documentation
df84a1795 Document the openapi3.Unmarshal function
24236f57d Miscellaneous edits and corrections
41b54d421 Hide the "new in" button after some period of time
3c1ebac31 Update Ancestors.md
e699eb313 Add new-in tags to select functions and methods
5c41f0bf8 Clarify time.Format method
f7bd43ae1 Add link to built-in render hook for GoAT diagrams
06f9cd4c9 Document the diagrams.Goat function
5e432e12d Adjust quick reference weights
fc915efd6 Update FAQ
5bccf8b19 Use LinkTitle consistently in examples
36f207f3a Add page collection quick reference
699de883d Inlcude example of newScratch.Values
9bb7f8c78 Include RenderString example for emoji shortcodes
783fdd3ac Fix typo
26d5a4399 Fix typo
c7e86f0cf Fix typo
ad2a82fbd Clarify data type returned by partial and partialCached
1de5a52dd Miscellaneous corrections
3509e1b4d Update configuration-markup.md
8c6a9bf02 Clairfy figure shortcode output
c9d0dc8fb Update Markdownify.md
0c4bc1447 Update theme
0f0ab2ade Add methods deprecated in v0.120.0
e1c6ecd0f Miscellaneous edits
ec53b55b9 netlify: Hugo 0.120.4
23a1f3fd5 templates/internal.md: Correct GoogleAnalytics key name
6dcfa9a82 Update troubleshooting section
0c8857e8f Adjust code and code-toggle shortcodes
8820264d3 Reduce number of site audit warnings
526d06b90 Clarify hint option in image processing spec
310849daa Image Processing: Improve sentence and fix code sample
5bb67bfd4 Revert "Image Processing: Improve sentence and fix code sample" (#2318)
77c926fde Image Processing: Improve sentence and fix code sample
52179fb18 Miscellaneous edits
f4e886715 Revert change to hugo.work
c410fefa8 Update theme
8b72dfedd Rework where function documentation
f35a7126f Minor edits to global page function
903b42ebc Cleanup shortcode calls
c9247e98d Update documentation.md
dff3c4abb Clean up build option descriptions and examples
f46d31308 Use consistent signatures
2af2470b5 Minor correction to resources.GetRemote
45ec53fe0 Remove superfluous shortcode param
2a0544757 Improve deployment documentation
3cf36a7fc Clarify lang keyword
f10d6495d Update front-matter.md
dc94e20be Add Sveltia to CMS list
b15d6d670 Update front-ends.md
bb41588b2 Update FormatCurrency.md
b40fff396 Add showcase for hidden sections without index.html
71316e181 Improve image filter examples
d46f0b1d8 Miscellaneous edits
ad3f9cdb6 Adjust quick reference guides
8657804ba Update theme
d9e981147 Miscellaneous corrections
508666575 Miscellaneous updates
80b2241f9 Miscellaneous updates
723a827fd Namespace functions and methods
40212779a netlify: Hugo 0.120.3
66017c704 Bump GitHub workflows to latest versions
db0f1e682 Update related.md
7e758c23b netlify: Hugo 0.120.2
641ba3976 Update configuration.md
d2a9909d9 Update rss.md
7eb59d7a2 netlify: Hugo 0.120.1
708c351c4 Document debug.Timer
28e2388c2 Add new-in tag to images.Padding
ee24cffb5 Add new-in tags to hugo.IsDevelopment and hugo.IsServer
aa47ca023 netlify: Hugo 0.120.0
9c3e606fc docs: Regen docshelper
159fd971e commands/new: Remove format flag from new content cmd
9f7878092 Merge commit 'aaaf1c8df5d6aa061609d20510f7fa6c42e5cc1a'
65b2dd324 docs: Regenerate docshelper
2be620663 resources/images: Create padding image filter
c777d6c5e Merge commit '3710a5ec7efe6baca6e452f43632c05d22bab3c4'
24f2afeb2 Merge commit 'e509cac533600cf4fa8382c9cdab78ddd82db688'
3f947c19a hugolib: Deprecate .Site.DisqusShortname
6bd1af892 hugolib: Deprecate .Site.GoogleAnalytics
78becc6ee tpl/tplimpl: Deprecate .Site.Social usage with internal templates
e3ec2a4f2 common/hugo: Add hugo.IsServer and hugo.IsDevelopment

git-subtree-dir: docs
git-subtree-split: 4dd2d64153
2023-12-04 15:14:18 +01:00
Bjørn Erik Pedersen 9f978d387f Pull in the latest code from Go's template packages (#11771)
Fixes #10707
Fixes #11507
2023-12-04 12:07:54 +01:00
Razon Yang 14d85ec136 tpl: Allow using page resources on the images page parameter for opengraph, schema and twitter_cards templates
The page images selection order as follows:

1. Page's images parameter, image resources are supported.
2. Page's image resources that naming in *feature*, *cover* or *thumbnail* pattern.
3. If no page images specified, then the first one of site's images will be used as the fallback, supports site resources.
2023-12-04 12:05:41 +01:00
Joe Mooring 171836cdfa hugolib: Apply titleCaseStyle to automatic section pages
Fixes #11547
2023-12-04 11:42:06 +01:00
Joe Mooring 9ea7103db7 tpl/urls: Retain query and fragment with absURL and absLangURL
Closes #11772
2023-12-04 11:41:30 +01:00
Joe Mooring 3fc42da3d0 markup: Add Level to Heading struct
Closes #10776
2023-12-04 11:41:01 +01:00
Joe Mooring d24da1712f tpl/fmt: Print suppression help with erroridf
Closes #11506
2023-12-04 11:39:27 +01:00
Joe Mooring 4583b41305 tpl/transform: Display Chroma highlighting errors
Closes #9642
2023-12-04 11:38:23 +01:00
dependabot[bot] 507f4e356b build(deps): bump github.com/tdewolff/minify/v2 from 2.20.8 to 2.20.9
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.20.8 to 2.20.9.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.20.8...v2.20.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-04 11:36:50 +01:00
dependabot[bot] a7e721e02d build(deps): bump github.com/spf13/cast from 1.5.1 to 1.6.0
Bumps [github.com/spf13/cast](https://github.com/spf13/cast) from 1.5.1 to 1.6.0.
- [Release notes](https://github.com/spf13/cast/releases)
- [Commits](https://github.com/spf13/cast/compare/v1.5.1...v1.6.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-01 11:04:04 +01:00
dependabot[bot] 2627b91d3a build(deps): bump github.com/getkin/kin-openapi from 0.121.0 to 0.122.0
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.121.0 to 0.122.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.121.0...v0.122.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>
2023-12-01 08:35:21 +01:00
Bjørn Erik Pedersen 6d4b012412 github: Fix CI build on Windows 2023-11-30 19:22:29 +01:00
dependabot[bot] e536d461a5 build(deps): bump golang.org/x/image from 0.13.0 to 0.14.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.13.0 to 0.14.0.
- [Commits](https://github.com/golang/image/compare/v0.13.0...v0.14.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-30 10:55:58 +01:00
Joe Mooring bfc325f561 deps: Update github.com/tdewolff/minify/v2 v2.20.7 => v2.20.8
Closes #5748
2023-11-30 10:13:40 +01:00
dependabot[bot] 36a60f65d0 build(deps): bump github.com/spf13/afero from 1.10.0 to 1.11.0
Bumps [github.com/spf13/afero](https://github.com/spf13/afero) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/spf13/afero/releases)
- [Commits](https://github.com/spf13/afero/compare/v1.10.0...v1.11.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-30 10:05:41 +01:00
dependabot[bot] de2fcc5e1d build(deps): bump github.com/evanw/esbuild from 0.19.7 to 0.19.8
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.19.7 to 0.19.8.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.19.7...v0.19.8)

---
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>
2023-11-30 10:04:51 +01:00
Lars Lehtonen 26a8ec207f Fix handling of dropped error in test 2023-11-29 09:37:26 +01:00
dependabot[bot] 9ca889ba49 build(deps): bump google.golang.org/api from 0.151.0 to 0.152.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.151.0 to 0.152.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.151.0...v0.152.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-28 16:53:57 +01:00
Bjørn Erik Pedersen e2a624dd6c common/para: Skip flaky test on CI 2023-11-28 12:23:38 +01:00
Bjørn Erik Pedersen 4fb40ee873 deps: Upgrade to libwebp 1.3.2
Closes #11746
2023-11-28 12:23:38 +01:00
dependabot[bot] bc93a36133 build(deps): bump github.com/aws/aws-sdk-go from 1.48.4 to 1.48.6
Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.48.4 to 1.48.6.
- [Release notes](https://github.com/aws/aws-sdk-go/releases)
- [Commits](https://github.com/aws/aws-sdk-go/compare/v1.48.4...v1.48.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-28 10:57:53 +01:00
dependabot[bot] 3e5bc6f3b3 build(deps): bump golang.org/x/tools from 0.15.0 to 0.16.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.15.0 to 0.16.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.15.0...v0.16.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-28 10:57:23 +01:00
dependabot[bot] 7c47036f18 build(deps): bump github.com/getkin/kin-openapi from 0.120.0 to 0.121.0
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.120.0 to 0.121.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.120.0...v0.121.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>
2023-11-28 10:57:02 +01:00
dependabot[bot] 4d07e1fe8a build(deps): bump github.com/bep/logg from 0.3.0 to 0.4.0
Bumps [github.com/bep/logg](https://github.com/bep/logg) from 0.3.0 to 0.4.0.
- [Release notes](https://github.com/bep/logg/releases)
- [Commits](https://github.com/bep/logg/compare/v0.3.0...v0.4.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-28 10:56:09 +01:00
Bjørn Erik Pedersen 1c41232e61 deps: Upgrade to github.com/bep/simplecobra v0.4.0 2023-11-28 09:15:23 +01:00
dependabot[bot] f11ca0fad4 build(deps): bump github.com/aws/aws-sdk-go from 1.48.2 to 1.48.4
Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.48.2 to 1.48.4.
- [Release notes](https://github.com/aws/aws-sdk-go/releases)
- [Commits](https://github.com/aws/aws-sdk-go/compare/v1.48.2...v1.48.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-27 23:38:35 +01:00
Bjørn Erik Pedersen 30a18e8825 watcher: Skip flaky test for now 2023-11-27 17:54:57 +01:00
dependabot[bot] d7a2f3f980 build(deps): bump golang.org/x/tools from 0.14.0 to 0.15.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.14.0 to 0.15.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.14.0...v0.15.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-27 10:53:02 +01:00
Joe Mooring b4c5df42ff tpl/transform: Add transform.XMLEscape template function
Fixes #3268
2023-11-27 10:52:33 +01:00
dependabot[bot] ef12d169c8 build(deps): bump github.com/gorilla/websocket from 1.5.0 to 1.5.1
Bumps [github.com/gorilla/websocket](https://github.com/gorilla/websocket) from 1.5.0 to 1.5.1.
- [Release notes](https://github.com/gorilla/websocket/releases)
- [Commits](https://github.com/gorilla/websocket/compare/v1.5.0...v1.5.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-27 10:48:08 +01:00
dependabot[bot] a62bbfa9ea build(deps): bump github.com/fatih/color from 1.15.0 to 1.16.0
Bumps [github.com/fatih/color](https://github.com/fatih/color) from 1.15.0 to 1.16.0.
- [Release notes](https://github.com/fatih/color/releases)
- [Commits](https://github.com/fatih/color/compare/v1.15.0...v1.16.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-27 10:47:37 +01:00
Joe Mooring 8d32ca223d tpl/tplimpl: Remove superfluous type attr on script elements
Closes #6379
2023-11-27 10:45:53 +01:00
dependabot[bot] 5887230b7d build(deps): bump golang.org/x/net from 0.17.0 to 0.18.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.18.0.
- [Commits](https://github.com/golang/net/compare/v0.17.0...v0.18.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-23 11:14:19 +01:00
dependabot[bot] a4a66b8216 build(deps): bump github.com/evanw/esbuild from 0.19.5 to 0.19.7
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.19.5 to 0.19.7.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.19.5...v0.19.7)

---
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>
2023-11-23 11:13:10 +01:00
dependabot[bot] 813390b5a2 build(deps): bump github.com/alecthomas/chroma/v2 from 2.10.0 to 2.11.1
Bumps [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) from 2.10.0 to 2.11.1.
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.10.0...v2.11.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-23 11:12:18 +01:00
dependabot[bot] d528bbd6d0 build(deps): bump github.com/tdewolff/minify/v2 from 2.20.5 to 2.20.7
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.20.5 to 2.20.7.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.20.5...v2.20.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-22 18:56:02 +01:00
dependabot[bot] af7f6c8b3e build(deps): bump google.golang.org/api from 0.138.0 to 0.151.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.138.0 to 0.151.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.138.0...v0.151.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-22 12:51:06 +01:00
dependabot[bot] e70849ea79 build(deps): bump github.com/aws/aws-sdk-go from 1.45.14 to 1.48.2 (#11724)
Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.45.14 to 1.48.2.
- [Release notes](https://github.com/aws/aws-sdk-go/releases)
- [Commits](https://github.com/aws/aws-sdk-go/compare/v1.45.14...v1.48.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Updates #11723
2023-11-22 12:19:01 +01:00
Joe Mooring dd6cd6268d resources/resource: Fix GroupByParamDate with raw TOML dates
Closes #11563
2023-11-22 12:09:48 +01:00
Bjørn Erik Pedersen 27620daa20 common/para: Skip flaky tests on Windows 2023-11-16 18:24:20 +01:00
Joe Mooring 80d2fdbaa0 navigation: Unexport menu entry methods
- MenuEntry.IsEqual
- MenuEntry.IsSameResource

Closes #11670
2023-11-16 18:22:31 +01:00
Joe Mooring 805cc1773b markup/goldmark: Sync image render hook code with Goldmark
Fixes #11681
2023-11-16 18:21:32 +01:00
Joe Mooring 0bde6931ac helpers: Fix TrimShortHTML used by markdownify and RenderString
Closes #11698
2023-11-16 18:21:01 +01:00
hugoreleaser ac7cffa7e2 releaser: Prepare repository for 0.121.0-DEV
[ci skip]
2023-11-08 11:32:02 +00:00
hugoreleaser f11bca5fec releaser: Bump versions for release of 0.120.4
[ci skip]
2023-11-08 11:18:07 +00:00
Bjørn Erik Pedersen 9315a2d2c4 Upgrade to go 1.21.4
Closes #11685
2023-11-08 11:53:26 +01:00
Bjørn Erik Pedersen cee3a56a91 Add a new test helper 2023-11-02 09:50:18 +01:00
hugoreleaser da2a8e6402 releaser: Prepare repository for 0.121.0-DEV
[ci skip]
2023-11-01 18:11:30 +00:00
hugoreleaser a4892a07b4 releaser: Bump versions for release of 0.120.3
[ci skip]
2023-11-01 17:57:00 +00:00
Joe Mooring cb98e9061b tpl/tplimpl: Fix deprecation logic in embedded templates
Closes #11658
2023-11-01 17:42:45 +01:00
Bjørn Erik Pedersen 5fa97ee964 Remove some old and unused deprecation code 2023-11-01 16:40:26 +01:00
Bjørn Erik Pedersen 4d38f47250 Add a field prefix to the deprecated log statements
To make theme easier to spot, esp. on INFO level.
2023-11-01 16:40:26 +01:00
Bjørn Erik Pedersen 80f793c38d Avoid double printing INFO deprecation messages
Fixes #11645
2023-11-01 16:40:26 +01:00
dependabot[bot] a9079d7a63 build(deps): bump github.com/tdewolff/parse/v2 from 2.7.1 to 2.7.3
Bumps [github.com/tdewolff/parse/v2](https://github.com/tdewolff/parse) from 2.7.1 to 2.7.3.
- [Release notes](https://github.com/tdewolff/parse/releases)
- [Commits](https://github.com/tdewolff/parse/compare/v2.7.1...v2.7.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-01 16:07:37 +01:00
dependabot[bot] 4914b7f18c build(deps): bump github.com/tdewolff/minify/v2 from 2.20.1 to 2.20.5
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.20.1 to 2.20.5.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.20.1...v2.20.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-01 10:32:33 +01:00
hugoreleaser 9e06fd3394 releaser: Prepare repository for 0.121.0-DEV
[ci skip]
2023-10-31 16:40:29 +00:00
hugoreleaser 9c2b2414d2 releaser: Bump versions for release of 0.120.2
[ci skip]
2023-10-31 16:27:18 +00:00
Bjørn Erik Pedersen ab21433689 Fix deprecation printing on info level
Fixes #11638
2023-10-31 10:42:23 +01:00
Joe Mooring 23fcfb7f74 tpl/tplimpl: Fix deprecation logic in RSS template
Closes #11639
2023-10-31 08:34:30 +01:00
hugoreleaser 47bf2fcbe2 releaser: Prepare repository for 0.121.0-DEV
[ci skip]
2023-10-30 16:57:41 +00:00
hugoreleaser 16fb2cae88 releaser: Bump versions for release of 0.120.1
[ci skip]
2023-10-30 16:44:31 +00:00
Joe Mooring 2bedcf3d18 deps: Update github.com/tdewolff/minify/v2 v2.20.0 => v2.20.1
Fixes #11633
2023-10-30 17:41:33 +01:00
hugoreleaser 935999e2fa releaser: Prepare repository for 0.121.0-DEV
[ci skip]
2023-10-30 14:20:34 +00:00
hugoreleaser 20c3bae2c4 releaser: Bump versions for release of 0.120.0
[ci skip]
2023-10-30 14:06:47 +00:00
Bjørn Erik Pedersen 29b6e133a8 docs: Regen docshelper 2023-10-30 15:03:10 +01:00
Bjørn Erik Pedersen 7f8ab74681 Add Solaris build
Closes #3500
2023-10-30 12:43:36 +01:00
dependabot[bot] 59bcc098cf build(deps): bump github.com/fsnotify/fsnotify from 1.6.0 to 1.7.0
Bumps [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify) from 1.6.0 to 1.7.0.
- [Release notes](https://github.com/fsnotify/fsnotify/releases)
- [Changelog](https://github.com/fsnotify/fsnotify/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fsnotify/fsnotify/compare/v1.6.0...v1.7.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-30 12:42:17 +01:00
dependabot[bot] e26ba752d2 build(deps): bump github.com/google/go-cmp from 0.5.9 to 0.6.0
Bumps [github.com/google/go-cmp](https://github.com/google/go-cmp) from 0.5.9 to 0.6.0.
- [Release notes](https://github.com/google/go-cmp/releases)
- [Commits](https://github.com/google/go-cmp/compare/v0.5.9...v0.6.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-30 10:31:21 +01:00
dependabot[bot] bcf07fa639 build(deps): bump github.com/alecthomas/chroma/v2 from 2.9.1 to 2.10.0
Bumps [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) from 2.9.1 to 2.10.0.
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.9.1...v2.10.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-30 10:28:42 +01:00
Bjørn Erik Pedersen b6a7568131 Make site.BaseURL and $pager.URL a string
Was template.URL.
2023-10-30 10:26:06 +01:00
Joe Mooring acf01bfb78 create/skeletons: Fix menu template
Check identifier existence before calling lang.Translate to prevent
empty warnings when using the --printI18nWarnings flag.

Closes #11519
2023-10-30 09:02:49 +01:00
Joe Mooring 27b22cd87e commands/new: Remove format flag from new content cmd
Fixes #11462
2023-10-30 09:02:26 +01:00
Joe Mooring a2488b1c95 hugolib: Display correct markup identifier in error message
Fixes #11538
2023-10-30 09:01:34 +01:00
Bjørn Erik Pedersen 8f60c0c1ec livereloadinject: Save some allocations
As suggested by @DominoPivot

```
name                 old time/op    new time/op    delta
LiveReloadInject-10    1.11µs ± 2%    1.04µs ± 2%   -6.45%  (p=0.029 n=4+4)

name                 old alloc/op   new alloc/op   delta
LiveReloadInject-10    1.23kB ± 0%    0.89kB ± 0%  -27.69%  (p=0.029 n=4+4)

name                 old allocs/op  new allocs/op  delta
LiveReloadInject-10      12.0 ± 0%      10.0 ± 0%  -16.67%  (p=0.029 n=4+4)
```
2023-10-29 18:37:05 +01:00
Domino Goupil 9dc608084b livereloadinject: Use more robust injection method 2023-10-29 18:37:05 +01:00
Joe Mooring a349aafb7f tpl/urls: Return strings from URL functions
Closes #11511
2023-10-29 18:12:24 +01:00
Bjørn Erik Pedersen b8fbd4a578 transform/livereloadinject: Add benchmark 2023-10-29 17:50:55 +01:00
dependabot[bot] e2b2092ce6 build(deps): bump golang.org/x/tools from 0.13.0 to 0.14.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.13.0 to 0.14.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.13.0...v0.14.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-29 16:38:06 +01:00
dependabot[bot] f4df7b88b3 build(deps): bump github.com/tdewolff/minify/v2 from 2.12.9 to 2.20.0
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.12.9 to 2.20.0.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.12.9...v2.20.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-29 11:36:23 +01:00
dependabot[bot] 3d9bd404ee build(deps): bump github.com/mattn/go-isatty from 0.0.19 to 0.0.20
Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.19 to 0.0.20.
- [Commits](https://github.com/mattn/go-isatty/compare/v0.0.19...v0.0.20)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-29 11:35:16 +01:00
dependabot[bot] 5f5e55aa8d build(deps): bump golang.org/x/image from 0.12.0 to 0.13.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.12.0 to 0.13.0.
- [Commits](https://github.com/golang/image/compare/v0.12.0...v0.13.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-29 11:34:13 +01:00
Bjørn Erik Pedersen aaaf1c8df5 Squashed 'docs/' changes from 417593493..4d936aee6
4d936aee6 Fix function signatures
8182a27ed Simplify example usage of apply function

git-subtree-dir: docs
git-subtree-split: 4d936aee62
2023-10-29 11:33:05 +01:00
Bjørn Erik Pedersen 0baa4f9837 Merge commit 'aaaf1c8df5d6aa061609d20510f7fa6c42e5cc1a' 2023-10-29 11:33:05 +01:00
Bjørn Erik Pedersen d3d4ab41a9 docs: Regenerate docshelper 2023-10-29 11:31:51 +01:00
Bjørn Erik Pedersen 28d844642a Revert "modules: Throttle the "downloading modules …" log entries"
This reverts commit 66904097e0.
2023-10-29 10:57:27 +01:00
Bjørn Erik Pedersen eb5fd3127f Revert "modules: Adjust the log throttle logic a little"
This reverts commit 3f64b5a3de.
2023-10-29 10:57:25 +01:00
Joe Mooring 3ed28e4bfe resources/images: Create padding image filter
Closes #11599
2023-10-29 10:16:37 +01:00
OMOTO Tsukasa db14238ba3 markup/goldmark: Update the CJK extension to allow specifying line break styles
This commit follows https://github.com/yuin/goldmark/pull/411
2023-10-29 10:13:56 +01:00
Bjørn Erik Pedersen 3f64b5a3de modules: Adjust the log throttle logic a little 2023-10-28 17:40:07 +02:00
Bjørn Erik Pedersen 66904097e0 modules: Throttle the "downloading modules …" log entries 2023-10-28 17:15:34 +02:00
Bjørn Erik Pedersen e54139c85b tpl/collections: Make delimit return a string
Closes #10876
Closes #11502
2023-10-28 11:54:23 +02:00
Bjørn Erik Pedersen 3710a5ec7e Squashed 'docs/' changes from cb18a5183..417593493
417593493 Fix broken link
9829e2862 Mention Go variable naming rules
11ce3a59c Fix typo
0d62feed5 Update glossary
5e9ecb217 Prepare for pending deprecations
c5f76c776 Update Twitter Cards URL

git-subtree-dir: docs
git-subtree-split: 4175934933
2023-10-27 10:06:44 +02:00
Bjørn Erik Pedersen 705e3cd5f5 Merge commit '3710a5ec7efe6baca6e452f43632c05d22bab3c4' 2023-10-27 10:06:44 +02:00
dependabot[bot] 8c61fd2541 build(deps): bump golang.org/x/net from 0.15.0 to 0.17.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.15.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.15.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-27 09:57:12 +02:00
dependabot[bot] d3145e4e5d build(deps): bump github.com/evanw/esbuild from 0.19.3 to 0.19.5
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.19.3 to 0.19.5.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.19.3...v0.19.5)

---
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>
2023-10-27 09:56:40 +02:00
dependabot[bot] 743a1da3ef build(deps): bump github.com/spf13/afero from 1.9.5 to 1.10.0
Bumps [github.com/spf13/afero](https://github.com/spf13/afero) from 1.9.5 to 1.10.0.
- [Release notes](https://github.com/spf13/afero/releases)
- [Commits](https://github.com/spf13/afero/compare/v1.9.5...v1.10.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-27 09:56:05 +02:00
dependabot[bot] 123901b741 build(deps): bump github.com/bep/logg from 0.2.0 to 0.3.0
Bumps [github.com/bep/logg](https://github.com/bep/logg) from 0.2.0 to 0.3.0.
- [Release notes](https://github.com/bep/logg/releases)
- [Commits](https://github.com/bep/logg/compare/v0.2.0...v0.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-27 09:55:05 +02:00
Bjørn Erik Pedersen 71fd79a3f4 Revise the deprecation logging
This introduces a more automatic way of increasing the log levels for deprecation log statements based on the version it was deprecated.

The thresholds are a little arbitrary, but

* We log INFO for 6 releases
* We log WARN for another 6 releases
* THen ERROR (failing the build)

This should give theme authors plenty of time to catch up without having the log filled with warnings.
2023-10-26 20:41:19 +02:00
Bjørn Erik Pedersen c4a530f104 Remove rest of the now unused emoji code
See #11598
2023-10-26 09:20:56 +02:00
Joe Mooring 272484f8bf markdown: Pass emoji codes to yuin/goldmark-emoji
Removes emoji code conversion from the page and shortcode parsers. Emoji
codes in markdown are now passed to Goldmark, where the goldmark-emoji
extension converts them to decimal numeric character references.

This disables emoji rendering for the alternate content formats: html,
asciidoc, org, pandoc, and rst.

Fixes #7332
Fixes #11587
Closes #11598
2023-10-24 12:04:13 +02:00
Razon Yang de4e466036 Fix so hugo get -u updates transitively 2023-10-22 16:32:01 +02:00
Oleksandr Redko c23a0c4a0f watcher/filenotify: Remove redundant duplicated comments 2023-10-21 18:57:35 +02:00
Oleksandr Redko a95670b981 docs: Remove outdated note from CONTRIBUTING.md 2023-10-21 18:57:01 +02:00
Bjørn Erik Pedersen 46bdc03885 tpl/debug: Add average and median to timer output 2023-10-21 15:41:21 +02:00
Bjørn Erik Pedersen 5160c7efa5 tpl/debug: Add debug.Timer
Closes #11580
2023-10-20 09:46:45 +02:00
Bjørn Erik Pedersen e2dd4cd05f Merge commit 'e509cac533600cf4fa8382c9cdab78ddd82db688' 2023-10-20 09:43:56 +02:00
Bjørn Erik Pedersen e509cac533 Squashed 'docs/' changes from 7ef2dbce4..cb18a5183
cb18a5183 Fix broken link
07a0198bf Config: Place Google Analytics tag ID under the services key
4bf0c719f Fix typo
50d8ad1af Fix muiltilingual menu definition instructions
1a32519a9 Fix typos
6f34ca8e0 Explain usage of front matter to target a template
5bd977257 Improve goldmark config docs
447632938 Remove Docker notes from installation instructions
84741d173 Update reference to hugo.work
0338d7c71 Fix menu template
f5d2f5ed4 Fix typos in content/en/functions/fmt
a3a40ff99 Add return type to functions
85ac3e779 Remove outdated feature image
d47d889e4 Fix signatures
7551ba28f Document safe.JSStr function
e77993be0 Document keyVals function
4dba20db3 Update theme
babf91544 Update echoparam
8c8203efa Adjust related functions
4cb1b30fc Fix example
ba95eca64 Improve showcase prose
5d3dcf366 Add Overmind Studios showcase
8d634ac70 Change code blocks from indented to fenced
cfab978e6 Add missing code fences
407dd5c47 Limit related pages for functions to other functions
9fa67d981 Fix .Site.LastChange doc
393aa16d0 netlify: Hugo 0.119.0
f864af97a docs: Even more about images.Process
9d772d5f0 docs: More about images.Process
bc655f869 docs: Regen docshelper
41c3536d1 Merge commit '9aec42c5452b3eb224888c50ba1c3f3b68a447e9'
918ed53f4 Add images.Process filter
573645883 Add $image.Process
a1151b0fd Add images.Opacity filter

git-subtree-dir: docs
git-subtree-split: cb18a5183f
2023-10-20 09:42:39 +02:00
Bjørn Erik Pedersen fd38171810 Add some convenient integration test helpers 2023-10-18 18:54:15 +02:00
Joe Mooring 2eca1b3cc1 hugolib: Deprecate .Site.DisqusShortname
Use .Site.Config.Services.Disqus.Shortname instead.
2023-10-18 17:35:54 +02:00
Lars Lehtonen 625162674d tpl/tplimpl: Fix dropped error 2023-10-18 17:33:00 +02:00
Joe Mooring a692278bc6 hugolib: Deprecate .Site.GoogleAnalytics
Use .Site.Config.Services.GoogleAnalytics.ID instead.
2023-10-17 20:06:42 +02:00
Joe Mooring d4016dd5cd tpl/tplimpl: Deprecate .Site.Author usage in RSS template 2023-10-17 20:04:49 +02:00
Joe Mooring 4910312ee7 tpl/tplimpl: Deprecate .Site.Social usage with internal templates 2023-10-17 20:04:24 +02:00
Joe Mooring 1b5f78b6b7 markup/tableofcontents: Return template.HTML from .Fragments.ToHTML
Closes #11545
2023-10-09 18:42:03 +02:00
Joe Mooring d5d0f420d8 deps: Update github.com/tdewolff/minify/v2 v2.12.7 => v2.12.9
Fixes #11533
2023-10-06 19:44:25 +02:00
Joe Mooring 5993afa4c5 commands: Update message displayed when running CLI from GUI
Fixes #11525
2023-10-06 19:39:35 +02:00
Joe Mooring d1b4458536 common/hugo: Add hugo.IsServer and hugo.IsDevelopment
And deprecate site.IsServer.

Closes #11510
2023-10-06 16:26:51 +02:00
Oleksandr Redko 274852bcf2 all: Format files with gofmt 2023-10-05 14:45:55 +02:00
abdullah-alaadine 37a2d5eb4e magefile: Update isGoLatest to check for Go 1.21 2023-10-04 16:07:40 +02:00
Joe Mooring 4c95389c25 resources/integrity: Return string instead of template.HTMLAttr
Closes #11513
2023-10-02 20:14:50 +02:00
Bjørn Erik Pedersen 3af8bded26 Update README.md 2023-09-27 19:32:41 +02:00
Joe Mooring 46da0b7aaa tpl/lang: Formally deprecate lang.NumFmt
Use lang.FormatNumberCustom instead.
2023-09-27 17:34:45 +02:00
Joe Mooring 75f56b4ce6 tpl/collections: Fix and deprecate echoParams
Fixes #11498
2023-09-27 17:34:13 +02:00
hugoreleaser d234a963eb releaser: Prepare repository for 0.120.0-DEV
[ci skip]
2023-09-24 15:32:33 +00:00
hugoreleaser b84644c008 releaser: Bump versions for release of 0.119.0
[ci skip]
2023-09-24 15:20:17 +00:00
Bjørn Erik Pedersen a9d19dbdd6 docs: Even more about images.Process 2023-09-24 12:26:00 +02:00
Bjørn Erik Pedersen 12d713176f docs: More about images.Process 2023-09-24 12:11:03 +02:00
Oleksandr Redko 6b65b2fae6 common: Remove unused constants 2023-09-24 11:59:49 +02:00
Bjørn Erik Pedersen 1768684d8b docs: Regen docshelper 2023-09-24 11:56:43 +02:00
Bjørn Erik Pedersen 9aec42c545 Squashed 'docs/' changes from 686c7b6eb..7ef2dbce4
7ef2dbce4 Cleanup RSS templates page
0e23d5e1a Fix multilingual permalinks example
b3c342b07 Update faq.md
f73109ae2 Regen CLI docs
ac6ee2027 Fix minor nitpicks (#2233)
f24fcffe1 Mention the Giscus comments system (#2234)
418fa981e Remove reference to Ghost migration tool
94a76df29 Adjust table layout
e263ad6b9 content: Fix typos
fd1eb26b5 Update featured image in digital.gov showcase
d3d946555 Correct directory description
bbe1dff56 Use sentence case for heading
acfa0be54 Add hosting provider fetch depth to GitInfo variables page
d78c427e6 Fix typo
751517fea Fix spelling mistake
cdd77aa9a Update content for v0.118.0
c77d09b1c Format netlify.toml
d5a6c4f51 netlify: Hugo 0.118.1
8c717301a Revert "netlify: Hugo 0.118.0"
a9b2ef5c2 netlify: Hugo 0.118.0
61959591c Merge commit '77b976dd92b4f66657d83d875aef0c617df492d9'
95f2029a1 docs: Regen docs helper
fd57ba960 markup/goldmark: Add CJK extension
2bfb3bf4c docshelper: Improve template lookup order descriptions
b0baa890e cache: Hide IsResourceDir from the exported config
b4b071ea0 docs: Replace docs.json with docs.yaml

git-subtree-dir: docs
git-subtree-split: 7ef2dbce4a
2023-09-24 11:54:45 +02:00
Bjørn Erik Pedersen eb0ed33dfd Merge commit '9aec42c5452b3eb224888c50ba1c3f3b68a447e9' 2023-09-24 11:54:45 +02:00
Bjørn Erik Pedersen 6a246d1152 Add images.Process filter
This allows for constructs like:

```
{{ $filters := slice (images.GaussianBlur 8) (images.Grayscale) (images.Process "jpg q30 resize 200x") }}
{{ $img = $img | images.Filter $filters }}
```

Note that the `action` option in `images.Process` is optional (`resize` in the example above), so you can use the above to just set the target format, e.g.:

```
{{ $filters := slice (images.GaussianBlur 8) (images.Grayscale) (images.Process "jpg") }}
{{ $img = $img | images.Filter $filters }}
```

Fixes #8439
2023-09-24 11:54:29 +02:00
Bjørn Erik Pedersen ef0e7149d6 Add $image.Process
Which supports all the existing actions: resize, crop, fit, fill.

But it also allows plain format conversions:

```
{{ $img = $img.Process "webp" }}
```

Which will be a simple re-encoding of the source image.

Fixes #11483
2023-09-24 11:54:29 +02:00
Carl Johnson c32094ace1 google_analytics_async.html: Add deprecation warning 2023-09-23 18:57:19 +02:00
dependabot[bot] a262fd4ddb build(deps): bump golang.org/x/tools from 0.12.0 to 0.13.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.12.0 to 0.13.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.12.0...v0.13.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-22 16:34:14 +02:00
dependabot[bot] f0d32455d2 build(deps): bump github.com/alecthomas/chroma/v2 from 2.8.0 to 2.9.1
Bumps [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) from 2.8.0 to 2.9.1.
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.8.0...v2.9.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-22 16:33:44 +02:00
dependabot[bot] e8bc8e6d02 build(deps): bump github.com/evanw/esbuild from 0.19.2 to 0.19.3
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.19.2 to 0.19.3.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.19.2...v0.19.3)

---
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>
2023-09-22 16:32:39 +02:00
Bjørn Erik Pedersen f9b3c0f486 Add images.Opacity filter
Fixes #11471
2023-09-22 15:03:16 +02:00
dependabot[bot] 11fcda971c build(deps): bump github.com/aws/aws-sdk-go from 1.44.314 to 1.45.14
Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.314 to 1.45.14.
- [Release notes](https://github.com/aws/aws-sdk-go/releases)
- [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.314...v1.45.14)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-21 21:42:32 +02:00
dependabot[bot] f31375d4cc build(deps): bump github.com/getkin/kin-openapi from 0.118.0 to 0.120.0
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.118.0 to 0.120.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.118.0...v0.120.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>
2023-09-21 21:41:30 +02:00
dependabot[bot] 6415b599be build(deps): bump golang.org/x/image from 0.11.0 to 0.12.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.11.0 to 0.12.0.
- [Commits](https://github.com/golang/image/compare/v0.11.0...v0.12.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-21 21:40:20 +02:00
Bjørn Erik Pedersen 1e9b87f760 Upgrade to Go 1.21.1
See https://github.com/golang/go/issues?q=milestone%3AGo1.21.1+label%3ACherryPickApproved

Fixes #11474
Fixes #11414
2023-09-21 21:38:41 +02:00
Bjørn Erik Pedersen 275c0acbf6 commands: Update CLI docs with the important -u flag in hugo mod get 2023-09-20 18:01:52 +02:00
Joe Mooring f9163155d3 create/skeletons: Improve viewport meta tag
Remove initial-scale=1.0 (this is the browser default)
Remove minimum-scale=1.0 (opinionated, can throw WCAG warning)
2023-09-19 11:46:05 +02:00
Bjørn Erik Pedersen 79a17d9e55 Fix tests for Go 1.21.1
Fixes #11450
2023-09-14 13:09:45 +02:00
Joe Mooring 75c0f88289 commands/gen: Remove default highlight style
Fixes #11445
2023-09-14 12:08:52 +02:00
Bjørn Erik Pedersen 18ce854626 Fix recently broken benchmark 2023-09-12 11:50:31 +02:00
Bjørn Erik Pedersen 69f5bad40f Adjust baseline benchmarks 2023-09-12 10:48:04 +02:00
ilmari-lauhakangas 525bed9919 commands: Print language code after web server address info 2023-09-11 11:38:24 +02:00
hugoreleaser 2ae4786ca1 releaser: Prepare repository for 0.119.0-DEV
[ci skip]
2023-08-31 11:35:48 +00:00
hugoreleaser da7983ac4b releaser: Bump versions for release of 0.118.2
[ci skip]
2023-08-31 11:23:51 +00:00
Bjørn Erik Pedersen df5d76fed9 release: Revert to building with Go 1.20
We need to investigate these issues, but that needs to wait.

Updates #11414
2023-08-31 13:20:35 +02:00
hugoreleaser 7362ba2204 releaser: Prepare repository for 0.119.0-DEV
[ci skip]
2023-08-31 09:56:08 +00:00
hugoreleaser 0eb480aa1d releaser: Bump versions for release of 0.118.1
[ci skip]
2023-08-31 09:42:31 +00:00
Bjørn Erik Pedersen 7e9092eeeb Revert to bullseye as the release build image
This was not available when I upgraded to Go 1.21, but is now, for some odd reason.

See https://github.com/bep/dockerfiles/commit/7761572995bed74c71ead9f3c3a71f624a4900ee

Fixes #11414
2023-08-31 11:37:36 +02:00
hugoreleaser 0200eaf58f releaser: Prepare repository for 0.119.0-DEV
[ci skip]
2023-08-31 07:49:58 +00:00
hugoreleaser 28b640a220 releaser: Bump versions for release of 0.118.0
[ci skip]
2023-08-31 07:36:55 +00:00
Bjørn Erik Pedersen db45dbbee8 Merge commit '77b976dd92b4f66657d83d875aef0c617df492d9' 2023-08-30 19:24:34 +02:00
Bjørn Erik Pedersen 77b976dd92 Squashed 'docs/' changes from a7e1e9be8..686c7b6eb
686c7b6eb ci(Netlify): specify `HUGO_VERSION` environment variable once
da99a356f fix: change heading level
e57da3f00 Update taxonomy methods
746172490 Update description of rendered breadcrumb navigation
6bc52fd40 Clarify term
dab07dcb0 Fix typo
e50fa452a Fix typo
6c1ea83c2 Update template overview page
a5dc97845 Clarify the append function
a135e52a0 Update GitHub hosting instructions
a51bf9f4f Update sections page
ed35fc6c4 Update archetypes and glossary
1a4522b3e Format examples
a70f20094 Use "hugo new content" to create content
673846ff9 Remove comment
b7febf0c5 Fix link
6f6fe2133 Miscellaneous edits
99227dd18 Remove lookup order table from output formats page
bc8870657 tools/editors: Add Prettier Plugin for Go Templates
157b169eb Update docs.yaml
1c8f514e0 Update cond function
e5f1f8113 Add assumptions to taxonomy and term template lookup order examples
475b406e2 Update postprocess
2d6cb8dfc glossary: Update content type
03b514bac Add descriptions to template lookup order example sections
06678f919 glossary: Fix broken link
4cd505612 Simplify news listing
fadb980db Update glossary of terms
491bacd78 Change order of example sections for template lookup order
04b8f39ec Create glossary of terms
12e896bc0 Remove reference to asciidoctor-rouge extension
055f7bb37 Insert missing words
8cd6ac387 Miscellaneous edits
2cbe17f41 Update configuration.md
529615373 Update data-templates.md
853154e65 Update theme
45f08627a resources.getRemote: Fix definition list
29a51dac1 Update docshelper
3bdfe88c6 Remove link to gitter from site footer
cacd0e461 Use "map" instead of "dictionary"
704dd5da6 Document the transform.Remarshal template function
e8d744951 Populate news section via GitHub releases API
3ff1118c7 Replace docs.json with docs.yaml
7726bbcac Use docs.json to generate default config throughout the site
57dca93df Use docs.json to generate default config for related content
74d5082c7 Add some .RenderShortcodes docs
cf5ab5062 netlify: Hugo 0.117.0
420f7aa69 Add all config to docshelper.json

git-subtree-dir: docs
git-subtree-split: 686c7b6eb1
2023-08-30 19:23:47 +02:00
Bjørn Erik Pedersen e847a98db6 docs: Regen docs helper 2023-08-30 19:23:34 +02:00
Bjørn Erik Pedersen 45c9bbc6ca Don't use the OS environment when creating config for docs 2023-08-30 19:22:44 +02:00
Bjørn Erik Pedersen 94fbab2a88 Delay the creation of cache directories until they're used
Fixes #11390
2023-08-30 16:39:06 +03:00
Matt Brown c3f273b2d7 deploy: Create AWS session for CloudFront invalidation via Go CDK
This allows the AWS credentials to be picked up from the configured
target URL (like blob does) rather than the current behaviour of only
relying on the defaults.

Relying on the defaults here means having to specify credentials twice
(once in the URL for the blob, once in the environment for this code
path) when non-default AWS credentials are in used (e.g. via a profile).
2023-08-30 16:38:20 +03:00
OMOTO Tsukasa d7dcc76d27 markup/goldmark: Add CJK extension
Fixes #10472
2023-08-30 13:08:45 +02:00
dependabot[bot] 9bf76fd7e8 build(deps): bump google.golang.org/api from 0.134.0 to 0.138.0
Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.134.0 to 0.138.0.
- [Release notes](https://github.com/googleapis/google-api-go-client/releases)
- [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md)
- [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.134.0...v0.138.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-30 13:44:19 +03:00
Bjørn Erik Pedersen 15d3e48cec Fix RegularPagesRecursive for the home page
Fixes #11396
2023-08-30 13:44:04 +03:00
dependabot[bot] b2a02c3c5a build(deps): bump golang.org/x/tools from 0.11.1 to 0.12.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.11.1 to 0.12.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.11.1...v0.12.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-30 12:17:42 +03:00
dependabot[bot] 6821d6f159 build(deps): bump gocloud.dev from 0.33.0 to 0.34.0
Bumps [gocloud.dev](https://github.com/google/go-cloud) from 0.33.0 to 0.34.0.
- [Release notes](https://github.com/google/go-cloud/releases)
- [Commits](https://github.com/google/go-cloud/compare/v0.33.0...v0.34.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-30 12:17:10 +03:00
dependabot[bot] 93c7ad12ec build(deps): bump github.com/pelletier/go-toml/v2 from 2.0.9 to 2.1.0
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.0.9 to 2.1.0.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml)
- [Commits](https://github.com/pelletier/go-toml/compare/v2.0.9...v2.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-30 12:16:46 +03:00
Bjørn Erik Pedersen d2ae9e1363 testscripts: Move hugo new tests to where they belong 2023-08-30 10:31:08 +02:00
Bjørn Erik Pedersen 3a8aad6b19 Fix .RawContent for empty content pages (#11407)
Fixes #11406
2023-08-30 10:11:20 +02:00
Lars Lehtonen a7b93e6564 hugolib: Handle dropped error 2023-08-24 19:57:44 +03:00
Oleksandr Redko 65871d5cf4 common/loggers: Fix typo in option name 2023-08-23 22:52:37 +02:00
Joe Mooring 2e4bf89ec7 misc: Change dart-sass labels in workflows and snapcraft 2023-08-23 22:51:46 +02:00
Bjørn Erik Pedersen ebaa733d45 Make sure resources directory isn't created in hugo new theme
Fixes #11382
2023-08-23 22:50:35 +02:00
Bjørn Erik Pedersen 24b1be45c1 Go 1.21 Upgrade
Fixes #11351
2023-08-23 21:49:27 +02:00
Bjørn Erik Pedersen 111f02db2a testscripts: Make mod vendor test stable 2023-08-23 20:52:09 +02:00
michalbiesek 8a08f91d5b dockerfile: Update Docker images
- `golang:1.21-alpine`
- `alpine:3.18`
2023-08-23 18:06:52 +02:00
Bjørn Erik Pedersen dcf425c846 Fix it so disable a module does not disable transitive dependency required by others
The motivation behind the original implementation was probably to show disabled modules when running `hugo mod graph`.

Fixes #11376
2023-08-23 18:05:18 +02:00
Joe Mooring 9a8c84d600 create/skeletons: Move theme's site config to top level
Closes #11380
2023-08-23 18:05:00 +02:00
dependabot[bot] a19d03b0e6 build(deps): bump github.com/yuin/goldmark from 1.5.5 to 1.5.6
Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.5.5 to 1.5.6.
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.5.5...v1.5.6)

---
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>
2023-08-23 13:19:49 +02:00
Joe Mooring cdf0b3b7a6 modules: Make new cache directories read/write
Leave newly-created directories in the module cache read-write instead
of making them read-only.

Closes #11369
2023-08-22 08:47:02 +02:00
Oleksandr Redko d979831dbd deploy: Update InvalidateCloudFront to use Go CDK helper 2023-08-21 16:57:41 +02:00
Bjørn Erik Pedersen bcf7421ea5 Avoid escaping HTML chars inside hugo_stats.json
Fixes #11371
2023-08-21 13:17:21 +02:00
Joe Mooring b6538532f4 commands/new: Embed site and theme skeletons
The skeletons are used when creating new sites and themes with the CLI.

Closes #11358
2023-08-21 10:38:22 +02:00
Joe Mooring 90944aa261 docshelper: Improve template lookup order descriptions
- Always include example for "with layout/type set"
- Use lowercase for "set to" examples (lookup is case sensitive)
- Consistent use of taxonomy and term

Fixes gohugoio/hugoDocs#1734
2023-08-15 12:53:18 +02:00
dependabot[bot] db7bc49690 build(deps): bump github.com/evanw/esbuild from 0.18.17 to 0.19.2
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.18.17 to 0.19.2.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.18.17...v0.19.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-14 11:46:23 +02:00
Bjørn Erik Pedersen a2f6400d65 cache: Hide IsResourceDir from the exported config 2023-08-09 23:49:08 +02:00
Bjørn Erik Pedersen 7d74cd0cc8 commands: Handle floats without decimals in hugo config
Updates #11345
2023-08-08 09:13:17 +02:00
Bjørn Erik Pedersen d139f30234 docs: Replace docs.json with docs.yaml
Updates #11345
2023-08-08 09:13:17 +02:00
Bjørn Erik Pedersen b1b6912412 config: Add a type value for the tags related config entry
Which doesn't have any practical difference, but it looks cleaner in the generated docs.
2023-08-07 18:01:46 +02:00
dependabot[bot] 0de81c6430 build(deps): bump golang.org/x/net from 0.13.0 to 0.14.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.13.0 to 0.14.0.
- [Commits](https://github.com/golang/net/compare/v0.13.0...v0.14.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-07 14:20:55 +02:00
Bjørn Erik Pedersen 851bf3515e Add all config to docshelper.json
Also consolidate so the mediaTypes and outputFormats are listed once only.

Fixes #11328
2023-08-07 13:42:54 +02:00
dependabot[bot] d4a6c16c17 build(deps): bump golang.org/x/image from 0.10.0 to 0.11.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.10.0 to 0.11.0.
- [Commits](https://github.com/golang/image/compare/v0.10.0...v0.11.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-07 12:18:03 +02:00
Bjørn Erik Pedersen 5d5fb22ead Merge commit '7c62d6ef1654c0383eae474d3bd9ddf7754c1f30' 2023-08-07 10:38:12 +02:00
Bjørn Erik Pedersen 7c62d6ef16 Squashed 'docs/' changes from c43daf45f..a7e1e9be8
a7e1e9be8 Clarify front matter date fields
69df4fc22 Clarify how to determine if .Inner is populated
9046bf424 Document strings.ContainsNonSpace
8dbe5df90 Fix indentation and broken image
48ad4124e Typo: functions/after.md
d4c01b57b Link to detailed descriptions of canonfiyURLs and relativeURLs
4d9597302 Explain behaviour when appending to a slice containing other slices
69e24e44e Standardize right arrow usage
01b378726 Remove references to Google's Universal Analytics and the async template
d415bae24 Use shared file to describe regex syntax
e75dee6b8 snap: How to enable or revoke access to SSH keys
feed2d1c0 Remove hasPrefix and hasSuffix in favor of namespaced versions
3c6d2cfe5 security: Use default execution settings
461b5fcaf netlify: Hugo 0.116.1
95fac27a5 configuration: correct cacheDir description
cd9f1f929 configuration: Fix broken link
605394de4 netlify: Upgrade to Hugo 0.116.0
baf2a0f7b Merge branch 'tempv0.116.0'
ee51a9323 Update requirements for building from source
40189956d Editor tools: Remove duplicate sentence
fb0ff2621 docs: Regenerate CLI docs
e8a5665c4 Update where.md
7bc5cf15d Update hosting instructions
018a04314 docs: Update where
d33ae91cf docs: Update where function operators
9a108a664 docs: Rework the cacheDir documentation

git-subtree-dir: docs
git-subtree-split: a7e1e9be85
2023-08-07 10:35:12 +02:00
Bjørn Erik Pedersen 641390f8f5 Try to make test more stable 2023-08-05 11:40:18 +02:00
Bjørn Erik Pedersen 2e6191b2e0 deps: Sync go-i18n with upstream
Closes #11336
2023-08-05 11:37:04 +02:00
Bjørn Erik Pedersen 22861cb4dc Return original error on resources.GetRemote retry timeouts
See #11327
2023-08-04 21:12:04 +02:00
Bjørn Erik Pedersen 16da1ade70 testing: Write test caches to /tmp
Fixes #11327
2023-08-04 18:46:43 +02:00
Bjørn Erik Pedersen a3d42a277d Add retry in resources.GetRemote for temporary HTTP errors
Fixes #11312
2023-08-04 17:16:52 +02:00
dependabot[bot] 2c20fd557a build(deps): bump gocloud.dev from 0.24.0 to 0.33.0
Bumps [gocloud.dev](https://github.com/google/go-cloud) from 0.24.0 to 0.33.0.
- [Release notes](https://github.com/google/go-cloud/releases)
- [Commits](https://github.com/google/go-cloud/compare/v0.24.0...v0.33.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-04 14:54:26 +02:00
dependabot[bot] 243736e751 build(deps): bump golang.org/x/net from 0.11.0 to 0.13.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.11.0 to 0.13.0.
- [Commits](https://github.com/golang/net/compare/v0.11.0...v0.13.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-04 14:53:33 +02:00
dependabot[bot] bf891c2259 build(deps): bump github.com/marekm4/color-extractor from 1.2.0 to 1.2.1
Bumps [github.com/marekm4/color-extractor](https://github.com/marekm4/color-extractor) from 1.2.0 to 1.2.1.
- [Commits](https://github.com/marekm4/color-extractor/compare/v1.2.0...v1.2.1)

---
updated-dependencies:
- dependency-name: github.com/marekm4/color-extractor
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-04 12:37:05 +02:00
dependabot[bot] da0df0ada3 build(deps): bump github.com/frankban/quicktest from 1.14.5 to 1.14.6
Bumps [github.com/frankban/quicktest](https://github.com/frankban/quicktest) from 1.14.5 to 1.14.6.
- [Commits](https://github.com/frankban/quicktest/compare/v1.14.5...v1.14.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-04 12:36:48 +02:00
dependabot[bot] 0885f8ec2b build(deps): bump golang.org/x/image from 0.9.0 to 0.10.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.9.0 to 0.10.0.
- [Commits](https://github.com/golang/image/compare/v0.9.0...v0.10.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-04 12:36:29 +02:00
dependabot[bot] 61be050a95 build(deps): bump github.com/clbanning/mxj/v2 from 2.5.7 to 2.7.0
Bumps [github.com/clbanning/mxj/v2](https://github.com/clbanning/mxj) from 2.5.7 to 2.7.0.
- [Release notes](https://github.com/clbanning/mxj/releases)
- [Commits](https://github.com/clbanning/mxj/compare/2.5.7...v2.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-04 09:54:06 +02:00
dependabot[bot] 65af75fb00 build(deps): bump golang.org/x/tools from 0.9.3 to 0.11.1
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.9.3 to 0.11.1.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.9.3...v0.11.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-04 09:53:24 +02:00
dependabot[bot] 2d75f74b86 build(deps): bump go.uber.org/automaxprocs from 1.5.2 to 1.5.3
Bumps [go.uber.org/automaxprocs](https://github.com/uber-go/automaxprocs) from 1.5.2 to 1.5.3.
- [Release notes](https://github.com/uber-go/automaxprocs/releases)
- [Changelog](https://github.com/uber-go/automaxprocs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uber-go/automaxprocs/compare/v1.5.2...v1.5.3)

---
updated-dependencies:
- dependency-name: go.uber.org/automaxprocs
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-04 09:52:50 +02:00
dependabot[bot] 2ac3d61604 build(deps): bump github.com/hairyhenderson/go-codeowners
Bumps [github.com/hairyhenderson/go-codeowners](https://github.com/hairyhenderson/go-codeowners) from 0.3.0 to 0.4.0.
- [Release notes](https://github.com/hairyhenderson/go-codeowners/releases)
- [Commits](https://github.com/hairyhenderson/go-codeowners/compare/v0.3.0...v0.4.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-04 09:52:33 +02:00
Bjørn Erik Pedersen ade7ec8187 Add Page.RenderShortcodes
A layouts/shortcodes/include.html shortcode may look like this:

```html
{{ $p := site.GetPage (.Get 0) }}
{{ $p.RenderShortcodes }}
```

Fixes #7297
2023-08-03 20:00:57 +02:00
Joe Mooring 8fa8ce3e48 Update GitHub issue template 2023-08-02 18:07:31 +02:00
dependabot[bot] 9dce45c25d build(deps): bump github.com/pelletier/go-toml/v2 from 2.0.8 to 2.0.9
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.0.8 to 2.0.9.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml)
- [Commits](https://github.com/pelletier/go-toml/compare/v2.0.8...v2.0.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-02 11:23:57 +02:00
hugoreleaser 239f2e2c99 releaser: Prepare repository for 0.117.0-DEV
[ci skip]
2023-08-01 07:36:32 +00:00
hugoreleaser 3e1ea030a5 releaser: Bump versions for release of 0.116.1
[ci skip]
2023-08-01 07:24:54 +00:00
Bjørn Erik Pedersen 30885a6c5f Fix module config watch regression
Fixes #11313
2023-08-01 09:05:53 +02:00
Joe Mooring 58da8554cb deps: Fix Chroma dependency version
Fixes #11311
2023-07-31 21:01:43 +02:00
hugoreleaser 92c1594372 releaser: Prepare repository for 0.117.0-DEV
[ci skip]
2023-07-31 10:40:27 +00:00
hugoreleaser 5a7e0da84e releaser: Bump versions for release of 0.116.0
[ci skip]
2023-07-31 10:28:28 +00:00
dependabot[bot] d7db096a9d build(deps): bump github.com/evanw/esbuild from 0.18.11 to 0.18.17
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.18.11 to 0.18.17.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.18.11...v0.18.17)

---
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>
2023-07-31 11:30:01 +02:00
Bjørn Erik Pedersen c1df5b1b0b config: Do not fail on unknown config keys
Make it a warning.

One such example I found in an old site is `blackfriday`. These should be fixed/removed, but it's not important enough
to fail the build.
2023-07-31 11:20:37 +02:00
Bjørn Erik Pedersen be8e2de597 resources: Fix spelling in method name 2023-07-30 21:12:33 +02:00
Bjørn Erik Pedersen d297c8e1b5 docs: Regenerate CLI docs 2023-07-30 18:54:08 +02:00
Bjørn Erik Pedersen fbb8eb39ec Fix so temporary images do not get published
Fixes #10255
2023-07-30 18:52:34 +02:00
Sojon 87d9bffe74 readme: Fix link 2023-07-30 11:06:31 +02:00
Joe Mooring d9fdcbe939 commands: Update cacheDir description 2023-07-29 22:09:55 +02:00
Bjørn Erik Pedersen 295d733883 Update where.md 2023-07-29 22:06:22 +02:00
Bjørn Erik Pedersen d5247788ee docs: Update where
Go doesn't use the RE2 (C++) library, it's a pure Go implementation.
2023-07-29 19:27:30 +02:00
Joe Mooring 036e260d8d docs: Update where function operators
Related to #11281
2023-07-29 16:11:53 +02:00
Bjørn Erik Pedersen a50356b9a2 docs: Rework the cacheDir documentation 2023-07-29 11:59:37 +02:00
Bjørn Erik Pedersen 8859be1c01 Merge commit '87de22d7464e239c775fbd48ebce1665d5b1e80d' 2023-07-29 11:17:28 +02:00
Bjørn Erik Pedersen 87de22d746 Squashed 'docs/' changes from 85befbb4d..c43daf45f
c43daf45f Update build-options.md
3ebbfedd4 Build options: Improve readability
5091bf6a0 Improve safeHTMLAttr explanation
b64cbce2e Fix description of collections.Apply
6ea264b9c netlify: Hugo 0.115.4
b42e7c542 Revert "config: Remove disableLiveReload"
35ce2290e Remove excess spaces in configuration docs
2edf761de Update listed titleCaseStyle default value
887f6fb97 config: Remove disableLiveReload
c9f49fb26 Fix typo
37d8569ac Remove tools associated with Atom
871d11b72 Fix URL in postprocess docs
bbb17d29f Update GitLab workflow
bc53ea5ce Use sentence-style capitalization for headings
7ca578786 netlify: Hugo 0.115.3
c5e010bd0 Merge branch 'tempv0.115.3'
c885604bf Remove starter kits page
4c0fe269e Update mention of Netlify CMS to Decap CMS
05067175c Consistently use file name instead of filename
763dd6404 Improve multilingual config example and descriptions
e5aa61ec5 Use lowercase when referring to front matter (#2132)
7ba3d0c72 docs: Refresh docs.json
de8bddedf Update description of timeout configuration value
e1245d9f8 netify: Hugo 0.115.2
153a36bdf Merge branch 'tempv0.115.2'
707cec754 Fix typo in figure example in shortcodes.md
128cbe1e5 Improve taxonomy template examples
4e743ec36 Improve highlight function example
f96fa6805 transpile sass: Fixes typo
e4a8a21f7 Compile Sass to CSS, not SCSS
c1538bd00 docs: Regenerate CLI docs
bd4e33436 Add titleCaseStyle none and firstupper
6ff93d478 Update quick-start.md
5c6653cb1 Update build config examples and explanation
1458d9a43 Remove the `url` parameter
6a1e92044 netlify: Hugo 0.115.1
a9d5d6f2f Merge branch 'tempv0.115.1'
4c4882384 docs: Regen docs helper
d1aa1c1f5 Add link to PowerShell vs Windows PowerShell documentation
6e3b70c21 Fix link to Git installation instructions
4f8a9ca38 Clarify resources.Copy arguments
ee86dd121 Update theme
dc7c305cf Update theme
60c23920b Clarify caching for resources.FromString (#2120)
5bf2fef6d netlify: Hugo 0.115.0
46bde87c5 Merge branch 'tempv0.115.0'
42cc48c16 Specify target path caching for resources.ExecuteAsTemplate (#2027)
a54bf4cd0 Correct the sample code of mermaid (#2119)
8c49b06fc docs: Update permalinks documentation
a4818d99b Page bundles: link to info about single vs. list page templates (#2116)
3fc7744d7 snap: Document removable media access
dbd08f58a Update theme
df5b88633 netlify: Hugo 0.114.1
6b859834a Fix typo
9ec92cf68 Improve Dart Sass example for Netlify
2d294ece9 Add Dart Sass installation and usage documentation
4c6b77d6c Fix placement of curly braces
897812a50 Update template-debugging.md to include a jsonify example
22bca519b Update GitHub Pages hosting instructions (#2109)
a964d93ce Document math functions new in v0.114.0 (#2108)
9f4cb040e netlify: Hugo 0.114.0
55b4d9221 Merge branch 'tempv0.114.0'
93c4dcf93 docs: Regen docshelper
96f03c77f docs: Regen CLI docs
8e22a228a Clarify resource media type variables (#2106)
2652da8d4 Update transform.Unmarshal.md (#2105)
92657177a Update theme
4601c1d65 Update theme
a216f3145 Merge commit '3c1deaf201a35de08d23cc58f8f03682cace3349'
eed8794f5 cache: Set default cache path based on $USER

git-subtree-dir: docs
git-subtree-split: c43daf45fd
2023-07-29 11:15:54 +02:00
Bjørn Erik Pedersen bec9b80d95 Deprecate taxonomyTerm
In favour of 'taxonomy'

Closes #11256
2023-07-28 15:14:24 +02:00
Bjørn Erik Pedersen 1c97095ac0 Warn about unknown kinds in disableKinds
See #11256
2023-07-28 15:14:23 +02:00
Bjørn Erik Pedersen b3cb6788b2 Move all Kind constants to its own package
See #11256
2023-07-28 15:14:23 +02:00
dependabot[bot] 5542f02fbc build(deps): bump github.com/rogpeppe/go-internal
Bumps [github.com/rogpeppe/go-internal](https://github.com/rogpeppe/go-internal) from 1.10.1-0.20230508101108-a4f6fabd84c5 to 1.11.0.
- [Release notes](https://github.com/rogpeppe/go-internal/releases)
- [Commits](https://github.com/rogpeppe/go-internal/commits/v1.11.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-28 14:50:56 +02:00
dependabot[bot] 0bc7ed9a17 build(deps): bump golang.org/x/image from 0.8.0 to 0.9.0
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.8.0 to 0.9.0.
- [Commits](https://github.com/golang/image/compare/v0.8.0...v0.9.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-28 14:49:34 +02:00
Bjørn Erik Pedersen 36b5126050 Remove unused autogenerated method 2023-07-28 10:31:37 +02:00
Oleksandr Redko 2589b1295c commands: Replace deprecated ioutil with os 2023-07-28 10:23:20 +02:00
Bjørn Erik Pedersen ef6e813ca8 tpl/collections: Add BenchmarkWhereOps
```
BenchmarkWhereOps/eq-10 	    8702	    120410 ns/op	   52280 B/op	    2515 allocs/op
BenchmarkWhereOps/ne-10 	    9829	    120759 ns/op	   52280 B/op	    2515 allocs/op
BenchmarkWhereOps/like-10           6754	    176377 ns/op	   52917 B/op	    2515 allocs/op
```
2023-07-28 10:08:13 +02:00
Joe Mooring f4598a0986 tpl/collections: Add like operator to where function
Closes #11279
2023-07-28 09:53:00 +02:00
Joe Mooring dc2a544fac tpl/collections: Fix description of apply function
The collections.Apply template function cannot be used with maps.
2023-07-28 09:51:01 +02:00
Joe Mooring 916397320b snap: Set cache location to $HOME/.cache/hugo_cache 2023-07-27 12:43:52 -07:00
Bjørn Erik Pedersen b3f10556f0 Use os.UserCacheDir as first fallback if cacheDir is not set
We will now try

1. cacheDir (or, commonly set in environment as `HUGO_CACHEDIR`)
2. if on Netlify we use `/opt/build/cache/hugo_cache/`
3. os.UserCacheDir
4. A temp dir

Storing the cache, especially the module cache, in a temporary idea has had lots of hard to debug issues, especially on MacOS,
which this commit tries to fix.

This should also make it easier to locate the Hugo cache:

>UserCacheDir returns the default root directory to use for user-specific cached data. Users should create their own
application-specific subdirectory within this one and use that.
>
>On Unix systems, it returns $XDG_CACHE_HOME as specified by
https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html if non-empty, else $HOME/.cache. On Darwin, it
returns $HOME/Library/Caches. On Windows, it returns %LocalAppData%. On Plan 9, it returns $home/lib/cache.
>
>If the location cannot be determined (for example, $HOME is not defined), then it will return an error.

Fixes #11286
Fixes #11291
2023-07-27 20:59:47 +02:00
Bjørn Erik Pedersen 4d7af757c9 Add a common regexp cache
```
BenchmarkGetOrCompileRegexp-10    	73959368	        13.71 ns/op	       0 B/op	       0 allocs/op
BenchmarkCompileRegexp-10         	 3143529	       380.1 ns/op	     872 B/op	      10 allocs/op
```
2023-07-27 19:23:12 +02:00
Bjørn Erik Pedersen 7f058b8bab Fix multiple languages in HUGO_DISABLELANGUAGES
Fixes #11278
2023-07-27 15:51:25 +02:00
Joe Mooring 575d7f8068 snap: Allow access to SSH keys and $HOME/.config/hugo
Closes #11288
2023-07-26 22:15:21 -07:00
Joe Mooring 739d10e8bd deps: Upgrade github.com/yuin/goldmark v1.5.4 => v1.5.5 2023-07-26 21:05:02 +02:00
hugoreleaser 0dbe0f1a07 releaser: Prepare repository for 0.116.0-DEV
[ci skip]
2023-07-20 07:02:18 +00:00
1896 changed files with 62545 additions and 57963 deletions
+2 -2
View File
@@ -4,7 +4,7 @@ parameters:
defaults: &defaults
resource_class: large
docker:
- image: bepsays/ci-hugoreleaser:1.22000.20100
- image: bepsays/ci-hugoreleaser:1.22100.20600
environment: &buildenv
GOMODCACHE: /root/project/gomodcache
version: 2
@@ -60,7 +60,7 @@ jobs:
environment:
<<: [*buildenv]
docker:
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22000.20100
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22100.20600
steps:
- *restore-cache
- &attach-workspace
+6 -1
View File
@@ -5,7 +5,12 @@ assignees: ''
about: Create a report to help us improve
---
<!--
Please do not use the issue queue for questions or troubleshooting. Unless you are certain that your issue is a software defect, use the forum:
https://discourse.gohugo.io
-->
<!-- Please answer these questions before submitting your issue. Thanks! -->
### What version of Hugo are you using (`hugo version`)?
+2 -2
View File
@@ -14,7 +14,7 @@ jobs:
test:
strategy:
matrix:
go-version: [1.20.x]
go-version: [1.21.x]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
@@ -30,7 +30,7 @@ jobs:
**/go.sum
**/go.mod
- name: Install Ruby
uses: ruby/setup-ruby@ee2113536afb7f793eed4ce60e8d3b26db912da4
uses: ruby/setup-ruby@036ef458ddccddb148a2b9fb67e95a22fdbf728b
with:
ruby-version: '2.7'
bundler-cache: true #
+14 -8
View File
@@ -16,7 +16,7 @@ jobs:
test:
strategy:
matrix:
go-version: [1.19.x,1.20.x]
go-version: [1.20.x,1.21.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
@@ -32,9 +32,9 @@ jobs:
**/go.sum
**/go.mod
- name: Install Ruby
uses: ruby/setup-ruby@ee2113536afb7f793eed4ce60e8d3b26db912da4
uses: ruby/setup-ruby@036ef458ddccddb148a2b9fb67e95a22fdbf728b
with:
ruby-version: '2.7'
ruby-version: '2.7'
bundler-cache: true #
- name: Install Python
uses: actions/setup-python@3105fb18c05ddd93efea5f9e0bef7a03a6e9e7df
@@ -58,13 +58,13 @@ jobs:
brew install pandoc
- if: matrix.os == 'windows-latest'
run: |
Choco-Install -PackageName pandoc
choco install pandoc
- run: pandoc -v
- if: matrix.os == 'windows-latest'
run: |
Choco-Install -PackageName mingw -ArgumentList "--version","12.2.0","--allow-downgrade"
choco install mingw
- if: matrix.os == 'ubuntu-latest'
name: Install dart-sass-embedded Linux
name: Install dart-sass Linux
run: |
echo "Install Dart Sass version ${SASS_VERSION} ..."
curl -LJO "https://github.com/sass/dart-sass/releases/download/${SASS_VERSION}/dart-sass-${SASS_VERSION}-linux-x64.tar.gz";
@@ -73,7 +73,7 @@ jobs:
echo "$GOBIN"
echo "$GITHUB_WORKSPACE/dart-sass/" >> $GITHUB_PATH
- if: matrix.os == 'macos-latest'
name: Install dart-sass-embedded MacOS
name: Install dart-sass MacOS
run: |
echo "Install Dart Sass version ${SASS_VERSION} ..."
curl -LJO "https://github.com/sass/dart-sass/releases/download/${SASS_VERSION}/dart-sass-${SASS_VERSION}-macos-x64.tar.gz";
@@ -81,12 +81,18 @@ jobs:
tar -xvf "dart-sass-${SASS_VERSION}-macos-x64.tar.gz";
echo "$GITHUB_WORKSPACE/dart-sass/" >> $GITHUB_PATH
- if: matrix.os == 'windows-latest'
name: Install dart-sass-embedded Windows
name: Install dart-sass Windows
run: |
echo "Install Dart Sass version ${env:SASS_VERSION} ..."
curl -LJO "https://github.com/sass/dart-sass/releases/download/${env:SASS_VERSION}/dart-sass-${env:SASS_VERSION}-windows-x64.zip";
Expand-Archive -Path "dart-sass-${env:SASS_VERSION}-windows-x64.zip" -DestinationPath .;
echo "$env:GITHUB_WORKSPACE/dart-sass/" | Out-File -FilePath $Env:GITHUB_PATH -Encoding utf-8 -Append
- if: matrix.os == 'ubuntu-latest'
name: Install staticcheck
run: go install honnef.co/go/tools/cmd/staticcheck@latest
- if: matrix.os == 'ubuntu-latest'
name: Run staticcheck
run: staticcheck ./...
- if: matrix.os != 'windows-latest'
name: Check
run: |
-2
View File
@@ -122,8 +122,6 @@ cd hugo
go install
```
>Note: Some Go tools may not be fully updated to support Go Modules yet. One example would be LiteIDE. Follow [this workaround](https://github.com/visualfc/liteide/issues/986#issuecomment-428117702) for how to continue to work with Hugo below `GOPATH`.
For some convenient build and test targets, you also will want to install Mage:
```bash
+2 -2
View File
@@ -2,7 +2,7 @@
# Twitter: https://twitter.com/gohugoio
# Website: https://gohugo.io/
FROM golang:1.19-alpine AS build
FROM golang:1.21-alpine AS build
# Optionally set HUGO_BUILD_TAGS to "extended" or "nodeploy" when building like so:
# docker build --build-arg HUGO_BUILD_TAGS=extended .
@@ -26,7 +26,7 @@ RUN mage hugo && mage install
# ---
FROM alpine:3.16
FROM alpine:3.18
COPY --from=build /go/bin/hugo /usr/bin/hugo
+82 -80
View File
@@ -1,4 +1,4 @@
[bep]: https://github.com/spf13
[bep]: https://github.com/bep
[bugs]: https://github.com/gohugoio/hugo/issues?q=is%3Aopen+is%3Aissue+label%3ABug
[contributing]: CONTRIBUTING.md
[create a proposal]: https://github.com/gohugoio/hugo/issues/new?labels=Proposal%2C+NeedsTriage&template=feature_request.md
@@ -33,7 +33,7 @@ A fast and flexible static site generator built with love by [bep], [spf13], and
[![Tests on Linux, MacOS and Windows](https://github.com/gohugoio/hugo/workflows/Test/badge.svg)](https://github.com/gohugoio/hugo/actions?query=workflow%3ATest)
[![Go Report Card](https://goreportcard.com/badge/github.com/gohugoio/hugo)](https://goreportcard.com/report/github.com/gohugoio/hugo)
[Website] | [Installation] | [Documentation] | [Support] | [Contributing] | [Twitter]
[Website] | [Installation] | [Documentation] | [Support] | [Contributing] | <a rel="me" href="https://fosstodon.org/@gohugoio">Mastodon</a>
## Overview
@@ -84,8 +84,8 @@ Hugo is available in two editions: standard and extended. With the extended edit
Prerequisites to build Hugo from source:
- Standard edition: Go 1.19 or later
- Extended edition: Go 1.19 or later, and GCC
- Standard edition: Go 1.20 or later
- Extended edition: Go 1.20 or later, and GCC
Build the standard edition:
@@ -142,40 +142,40 @@ Hugo stands on the shoulders of great open source libraries. Run `hugo env --log
```text
cloud.google.com/go/compute/metadata="v0.2.3"
cloud.google.com/go/iam="v1.1.0"
cloud.google.com/go/storage="v1.30.1"
cloud.google.com/go="v0.110.2"
github.com/Azure/azure-sdk-for-go/sdk/azcore="v1.6.1"
cloud.google.com/go/iam="v1.1.3"
cloud.google.com/go/storage="v1.31.0"
cloud.google.com/go="v0.110.8"
github.com/Azure/azure-sdk-for-go/sdk/azcore="v1.7.0"
github.com/Azure/azure-sdk-for-go/sdk/azidentity="v1.3.0"
github.com/Azure/azure-sdk-for-go/sdk/internal="v1.3.0"
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob="v1.0.0"
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob="v1.1.0"
github.com/Azure/go-autorest/autorest/to="v0.4.0"
github.com/AzureAD/microsoft-authentication-library-for-go="v1.0.0"
github.com/BurntSushi/locker="v0.0.0-20171006230638-a6e239ea1c69"
github.com/PuerkitoBio/purell="v1.1.1"
github.com/PuerkitoBio/urlesc="v0.0.0-20170810143723-de5bf2ad4578"
github.com/alecthomas/chroma/v2="v2.7.0"
github.com/alecthomas/chroma/v2="v2.11.1"
github.com/armon/go-radix="v1.0.0"
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream="v1.4.10"
github.com/aws/aws-sdk-go-v2/config="v1.18.27"
github.com/aws/aws-sdk-go-v2/credentials="v1.13.26"
github.com/aws/aws-sdk-go-v2/feature/ec2/imds="v1.13.4"
github.com/aws/aws-sdk-go-v2/feature/s3/manager="v1.11.70"
github.com/aws/aws-sdk-go-v2/internal/configsources="v1.1.34"
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2="v2.4.28"
github.com/aws/aws-sdk-go-v2/internal/ini="v1.3.35"
github.com/aws/aws-sdk-go-v2/internal/v4a="v1.0.26"
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding="v1.9.11"
github.com/aws/aws-sdk-go-v2/service/internal/checksum="v1.1.29"
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url="v1.9.28"
github.com/aws/aws-sdk-go-v2/service/internal/s3shared="v1.14.3"
github.com/aws/aws-sdk-go-v2/service/s3="v1.35.0"
github.com/aws/aws-sdk-go-v2/service/sso="v1.12.12"
github.com/aws/aws-sdk-go-v2/service/ssooidc="v1.14.12"
github.com/aws/aws-sdk-go-v2/service/sts="v1.19.2"
github.com/aws/aws-sdk-go-v2="v1.18.1"
github.com/aws/aws-sdk-go="v1.44.284"
github.com/aws/smithy-go="v1.13.5"
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream="v1.4.11"
github.com/aws/aws-sdk-go-v2/config="v1.18.32"
github.com/aws/aws-sdk-go-v2/credentials="v1.13.31"
github.com/aws/aws-sdk-go-v2/feature/ec2/imds="v1.13.7"
github.com/aws/aws-sdk-go-v2/feature/s3/manager="v1.11.76"
github.com/aws/aws-sdk-go-v2/internal/configsources="v1.1.37"
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2="v2.4.31"
github.com/aws/aws-sdk-go-v2/internal/ini="v1.3.38"
github.com/aws/aws-sdk-go-v2/internal/v4a="v1.1.0"
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding="v1.9.12"
github.com/aws/aws-sdk-go-v2/service/internal/checksum="v1.1.32"
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url="v1.9.31"
github.com/aws/aws-sdk-go-v2/service/internal/s3shared="v1.15.0"
github.com/aws/aws-sdk-go-v2/service/s3="v1.38.1"
github.com/aws/aws-sdk-go-v2/service/sso="v1.13.1"
github.com/aws/aws-sdk-go-v2/service/ssooidc="v1.15.1"
github.com/aws/aws-sdk-go-v2/service/sts="v1.21.1"
github.com/aws/aws-sdk-go-v2="v1.20.0"
github.com/aws/aws-sdk-go="v1.48.2"
github.com/aws/smithy-go="v1.14.0"
github.com/bep/clocks="v0.5.0"
github.com/bep/debounce="v1.2.0"
github.com/bep/gitmap="v1.1.2"
@@ -183,45 +183,45 @@ github.com/bep/goat="v0.5.0"
github.com/bep/godartsass/v2="v2.0.0"
github.com/bep/godartsass="v1.2.0"
github.com/bep/golibsass="v1.1.1"
github.com/bep/gowebp="v0.2.0"
github.com/bep/gowebp="v0.3.0"
github.com/bep/lazycache="v0.2.0"
github.com/bep/logg="v0.2.0"
github.com/bep/logg="v0.3.0"
github.com/bep/mclib="v1.20400.20402"
github.com/bep/overlayfs="v0.6.0"
github.com/bep/simplecobra="v0.3.2"
github.com/bep/tmc="v0.5.1"
github.com/clbanning/mxj/v2="v2.5.7"
github.com/clbanning/mxj/v2="v2.7.0"
github.com/cli/safeexec="v1.0.1"
github.com/cpuguy83/go-md2man/v2="v2.0.2"
github.com/disintegration/gift="v1.2.1"
github.com/dlclark/regexp2="v1.10.0"
github.com/dustin/go-humanize="v1.0.1"
github.com/evanw/esbuild="v0.18.5"
github.com/fatih/color="v1.15.0"
github.com/frankban/quicktest="v1.14.5"
github.com/fsnotify/fsnotify="v1.6.0"
github.com/getkin/kin-openapi="v0.118.0"
github.com/evanw/esbuild="v0.19.7"
github.com/fatih/color="v1.16.0"
github.com/frankban/quicktest="v1.14.6"
github.com/fsnotify/fsnotify="v1.7.0"
github.com/getkin/kin-openapi="v0.120.0"
github.com/ghodss/yaml="v1.0.0"
github.com/go-openapi/jsonpointer="v0.19.6"
github.com/go-openapi/swag="v0.22.3"
github.com/go-openapi/swag="v0.22.4"
github.com/gobuffalo/flect="v1.0.2"
github.com/gobwas/glob="v0.2.3"
github.com/gohugoio/go-i18n/v2="v2.1.3-0.20210430103248-4c28c89f8013"
github.com/gohugoio/go-i18n/v2="v2.1.3-0.20230805085216-e63c13218d0e"
github.com/gohugoio/locales="v0.14.0"
github.com/gohugoio/localescompressed="v1.0.1"
github.com/golang-jwt/jwt/v4="v4.5.0"
github.com/golang/groupcache="v0.0.0-20210331224755-41bb18bfe9da"
github.com/golang/protobuf="v1.5.3"
github.com/google/go-cmp="v0.5.9"
github.com/google/s2a-go="v0.1.4"
github.com/google/uuid="v1.3.0"
github.com/google/go-cmp="v0.6.0"
github.com/google/s2a-go="v0.1.7"
github.com/google/uuid="v1.4.0"
github.com/google/wire="v0.5.0"
github.com/googleapis/enterprise-certificate-proxy="v0.2.5"
github.com/googleapis/gax-go/v2="v2.11.0"
github.com/gorilla/websocket="v1.5.0"
github.com/hairyhenderson/go-codeowners="v0.3.0"
github.com/googleapis/enterprise-certificate-proxy="v0.3.2"
github.com/googleapis/gax-go/v2="v2.12.0"
github.com/gorilla/websocket="v1.5.1"
github.com/hairyhenderson/go-codeowners="v0.4.0"
github.com/hashicorp/golang-lru/v2="v2.0.1"
github.com/invopop/yaml="v0.1.0"
github.com/invopop/yaml="v0.2.0"
github.com/jdkato/prose="v1.2.1"
github.com/jmespath/go-jmespath="v0.4.0"
github.com/josharian/intern="v1.0.0"
@@ -230,9 +230,9 @@ github.com/kr/text="v0.2.0"
github.com/kylelemons/godebug="v1.1.0"
github.com/kyokomi/emoji/v2="v2.2.12"
github.com/mailru/easyjson="v0.7.7"
github.com/marekm4/color-extractor="v1.2.0"
github.com/marekm4/color-extractor="v1.2.1"
github.com/mattn/go-colorable="v0.1.13"
github.com/mattn/go-isatty="v0.0.19"
github.com/mattn/go-isatty="v0.0.20"
github.com/mattn/go-runewidth="v0.0.9"
github.com/mitchellh/hashstructure="v1.1.0"
github.com/mitchellh/mapstructure="v1.5.0"
@@ -240,51 +240,53 @@ github.com/mohae/deepcopy="v0.0.0-20170929034955-c48cc78d4826"
github.com/muesli/smartcrop="v0.3.0"
github.com/niklasfasching/go-org="v1.7.0"
github.com/olekukonko/tablewriter="v0.0.5"
github.com/pelletier/go-toml/v2="v2.0.8"
github.com/perimeterx/marshmallow="v1.1.4"
github.com/pelletier/go-toml/v2="v2.1.0"
github.com/perimeterx/marshmallow="v1.1.5"
github.com/pkg/browser="v0.0.0-20210911075715-681adbf594b8"
github.com/pkg/errors="v0.9.1"
github.com/rogpeppe/go-internal="v1.10.1-0.20230508101108-a4f6fabd84c5"
github.com/rogpeppe/go-internal="v1.11.0"
github.com/russross/blackfriday/v2="v2.1.0"
github.com/rwcarlsen/goexif="v0.0.0-20190401172101-9e8deecbddbd"
github.com/sanity-io/litter="v1.5.5"
github.com/sass/dart-sass/compiler="1.63.6"
github.com/sass/dart-sass/implementation="1.63.6"
github.com/sass/dart-sass/protocol="2.1.0"
github.com/sass/dart-sass/compiler="1.63.2"
github.com/sass/dart-sass/implementation="1.63.2"
github.com/sass/dart-sass/protocol="2.0.0"
github.com/sass/libsass="3.6.5"
github.com/spf13/afero="v1.9.5"
github.com/spf13/afero="v1.10.0"
github.com/spf13/cast="v1.5.1"
github.com/spf13/cobra="v1.7.0"
github.com/spf13/fsync="v0.9.0"
github.com/spf13/pflag="v1.0.5"
github.com/tdewolff/minify/v2="v2.12.7"
github.com/tdewolff/parse/v2="v2.6.6"
github.com/webmproject/libwebp="v1.2.4"
github.com/yuin/goldmark="v1.5.4"
github.com/tdewolff/minify/v2="v2.20.7"
github.com/tdewolff/parse/v2="v2.7.5"
github.com/webmproject/libwebp="v1.3.2"
github.com/yuin/goldmark-emoji="v1.0.2"
github.com/yuin/goldmark="v1.6.0"
go.opencensus.io="v0.24.0"
go.uber.org/atomic="v1.11.0"
go.uber.org/automaxprocs="v1.5.2"
gocloud.dev="v0.30.0"
golang.org/x/crypto="v0.10.0"
golang.org/x/exp="v0.0.0-20230321023759-10a507213a29"
golang.org/x/image="v0.8.0"
golang.org/x/mod="v0.10.0"
golang.org/x/net="v0.11.0"
golang.org/x/oauth2="v0.9.0"
golang.org/x/sync="v0.3.0"
golang.org/x/sys="v0.9.0"
golang.org/x/text="v0.10.0"
golang.org/x/tools="v0.9.3"
go.uber.org/automaxprocs="v1.5.3"
gocloud.dev="v0.34.0"
golang.org/x/crypto="v0.15.0"
golang.org/x/exp="v0.0.0-20221031165847-c99f073a8326"
golang.org/x/image="v0.13.0"
golang.org/x/mod="v0.14.0"
golang.org/x/net="v0.18.0"
golang.org/x/oauth2="v0.13.0"
golang.org/x/sync="v0.5.0"
golang.org/x/sys="v0.14.0"
golang.org/x/text="v0.14.0"
golang.org/x/time="v0.3.0"
golang.org/x/tools="v0.15.0"
golang.org/x/xerrors="v0.0.0-20220907171357-04be3eba64a2"
google.golang.org/api="v0.128.0"
google.golang.org/appengine="v1.6.7"
google.golang.org/genproto/googleapis/api="v0.0.0-20230530153820-e85fd2cbaebc"
google.golang.org/genproto/googleapis/rpc="v0.0.0-20230530153820-e85fd2cbaebc"
google.golang.org/genproto="v0.0.0-20230530153820-e85fd2cbaebc"
google.golang.org/grpc="v1.56.0"
google.golang.org/protobuf="v1.30.0"
google.golang.org/api="v0.151.0"
google.golang.org/genproto/googleapis/api="v0.0.0-20231016165738-49dd2c1f3d0b"
google.golang.org/genproto/googleapis/rpc="v0.0.0-20231030173426-d783a09b4405"
google.golang.org/genproto="v0.0.0-20231016165738-49dd2c1f3d0b"
google.golang.org/grpc="v1.59.0"
google.golang.org/protobuf="v1.31.0"
gopkg.in/yaml.v2="v2.4.0"
gopkg.in/yaml.v3="v3.0.1"
howett.net/plist="v1.0.0"
software.sslmate.com/src/go-pkcs12="v0.2.0"
```
</details>
+582
View File
@@ -0,0 +1,582 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dynacache
import (
"context"
"fmt"
"math"
"path"
"regexp"
"runtime"
"sync"
"time"
"github.com/bep/lazycache"
"github.com/bep/logg"
"github.com/gohugoio/hugo/common/collections"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/rungroup"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/resources/resource"
)
const minMaxSize = 10
// New creates a new cache.
func New(opts Options) *Cache {
if opts.CheckInterval == 0 {
opts.CheckInterval = time.Second * 2
}
if opts.MaxSize == 0 {
opts.MaxSize = 100000
}
if opts.Log == nil {
panic("nil Log")
}
if opts.MinMaxSize == 0 {
opts.MinMaxSize = 30
}
stats := &stats{
opts: opts,
adjustmentFactor: 1.0,
currentMaxSize: opts.MaxSize,
availableMemory: config.GetMemoryLimit(),
}
infol := opts.Log.InfoCommand("dynacache")
evictedIdentities := collections.NewStack[identity.Identity]()
onEvict := func(k, v any) {
if !opts.Running {
return
}
identity.WalkIdentitiesShallow(v, func(level int, id identity.Identity) bool {
evictedIdentities.Push(id)
return false
})
resource.MarkStale(v)
}
c := &Cache{
partitions: make(map[string]PartitionManager),
onEvict: onEvict,
evictedIdentities: evictedIdentities,
opts: opts,
stats: stats,
infol: infol,
}
c.stop = c.start()
return c
}
// Options for the cache.
type Options struct {
Log loggers.Logger
CheckInterval time.Duration
MaxSize int
MinMaxSize int
Running bool
}
// Options for a partition.
type OptionsPartition struct {
// When to clear the this partition.
ClearWhen ClearWhen
// Weight is a number between 1 and 100 that indicates how, in general, how big this partition may get.
Weight int
}
func (o OptionsPartition) WeightFraction() float64 {
return float64(o.Weight) / 100
}
func (o OptionsPartition) CalculateMaxSize(maxSizePerPartition int) int {
return int(math.Floor(float64(maxSizePerPartition) * o.WeightFraction()))
}
// A dynamic partitioned cache.
type Cache struct {
mu sync.RWMutex
partitions map[string]PartitionManager
onEvict func(k, v any)
evictedIdentities *collections.Stack[identity.Identity]
opts Options
infol logg.LevelLogger
stats *stats
stopOnce sync.Once
stop func()
}
// DrainEvictedIdentities drains the evicted identities from the cache.
func (c *Cache) DrainEvictedIdentities() []identity.Identity {
return c.evictedIdentities.Drain()
}
// ClearMatching clears all partition for which the predicate returns true.
func (c *Cache) ClearMatching(predicate func(k, v any) bool) {
g := rungroup.Run[PartitionManager](context.Background(), rungroup.Config[PartitionManager]{
NumWorkers: len(c.partitions),
Handle: func(ctx context.Context, partition PartitionManager) error {
partition.clearMatching(predicate)
return nil
},
})
for _, p := range c.partitions {
g.Enqueue(p)
}
g.Wait()
}
// ClearOnRebuild prepares the cache for a new rebuild taking the given changeset into account.
func (c *Cache) ClearOnRebuild(changeset ...identity.Identity) {
g := rungroup.Run[PartitionManager](context.Background(), rungroup.Config[PartitionManager]{
NumWorkers: len(c.partitions),
Handle: func(ctx context.Context, partition PartitionManager) error {
partition.clearOnRebuild(changeset...)
return nil
},
})
for _, p := range c.partitions {
g.Enqueue(p)
}
g.Wait()
// Clear any entries marked as stale above.
g = rungroup.Run[PartitionManager](context.Background(), rungroup.Config[PartitionManager]{
NumWorkers: len(c.partitions),
Handle: func(ctx context.Context, partition PartitionManager) error {
partition.clearStale()
return nil
},
})
for _, p := range c.partitions {
g.Enqueue(p)
}
g.Wait()
}
type keysProvider interface {
Keys() []string
}
// Keys returns a list of keys in all partitions.
func (c *Cache) Keys(predicate func(s string) bool) []string {
if predicate == nil {
predicate = func(s string) bool { return true }
}
var keys []string
for pn, g := range c.partitions {
pkeys := g.(keysProvider).Keys()
for _, k := range pkeys {
p := path.Join(pn, k)
if predicate(p) {
keys = append(keys, p)
}
}
}
return keys
}
func calculateMaxSizePerPartition(maxItemsTotal, totalWeightQuantity, numPartitions int) int {
if numPartitions == 0 {
panic("numPartitions must be > 0")
}
if totalWeightQuantity == 0 {
panic("totalWeightQuantity must be > 0")
}
avgWeight := float64(totalWeightQuantity) / float64(numPartitions)
return int(math.Floor(float64(maxItemsTotal) / float64(numPartitions) * (100.0 / avgWeight)))
}
// Stop stops the cache.
func (c *Cache) Stop() {
c.stopOnce.Do(func() {
c.stop()
})
}
func (c *Cache) adjustCurrentMaxSize() {
c.mu.RLock()
defer c.mu.RUnlock()
if len(c.partitions) == 0 {
return
}
var m runtime.MemStats
runtime.ReadMemStats(&m)
s := c.stats
s.memstatsCurrent = m
// fmt.Printf("\n\nAvailable = %v\nAlloc = %v\nTotalAlloc = %v\nSys = %v\nNumGC = %v\nMaxSize = %d\nAdjustmentFactor=%f\n\n", helpers.FormatByteCount(s.availableMemory), helpers.FormatByteCount(m.Alloc), helpers.FormatByteCount(m.TotalAlloc), helpers.FormatByteCount(m.Sys), m.NumGC, c.stats.currentMaxSize, s.adjustmentFactor)
if s.availableMemory >= s.memstatsCurrent.Alloc {
if s.adjustmentFactor <= 1.0 {
s.adjustmentFactor += 0.2
}
} else {
// We're low on memory.
s.adjustmentFactor -= 0.4
}
if s.adjustmentFactor <= 0 {
s.adjustmentFactor = 0.05
}
if !s.adjustCurrentMaxSize() {
return
}
totalWeight := 0
for _, pm := range c.partitions {
totalWeight += pm.getOptions().Weight
}
maxSizePerPartition := calculateMaxSizePerPartition(c.stats.currentMaxSize, totalWeight, len(c.partitions))
evicted := 0
for _, p := range c.partitions {
evicted += p.adjustMaxSize(p.getOptions().CalculateMaxSize(maxSizePerPartition))
}
if evicted > 0 {
c.infol.
WithFields(
logg.Fields{
{Name: "evicted", Value: evicted},
{Name: "numGC", Value: m.NumGC},
{Name: "limit", Value: helpers.FormatByteCount(c.stats.availableMemory)},
{Name: "alloc", Value: helpers.FormatByteCount(m.Alloc)},
{Name: "totalAlloc", Value: helpers.FormatByteCount(m.TotalAlloc)},
},
).Logf("adjusted partitions' max size")
}
}
func (c *Cache) start() func() {
ticker := time.NewTicker(c.opts.CheckInterval)
quit := make(chan struct{})
go func() {
for {
select {
case <-ticker.C:
c.adjustCurrentMaxSize()
case <-quit:
ticker.Stop()
return
}
}
}()
return func() {
close(quit)
}
}
var partitionNameRe = regexp.MustCompile(`^\/[a-zA-Z0-9]{4}(\/[a-zA-Z0-9]+)?(\/[a-zA-Z0-9]+)?`)
// GetOrCreatePartition gets or creates a partition with the given name.
func GetOrCreatePartition[K comparable, V any](c *Cache, name string, opts OptionsPartition) *Partition[K, V] {
if c == nil {
panic("nil Cache")
}
if opts.Weight < 1 || opts.Weight > 100 {
panic("invalid Weight, must be between 1 and 100")
}
if partitionNameRe.FindString(name) != name {
panic(fmt.Sprintf("invalid partition name %q", name))
}
c.mu.RLock()
p, found := c.partitions[name]
c.mu.RUnlock()
if found {
return p.(*Partition[K, V])
}
c.mu.Lock()
defer c.mu.Unlock()
// Double check.
p, found = c.partitions[name]
if found {
return p.(*Partition[K, V])
}
// At this point, we don't know the the number of partitions or their configuration, but
// this will be re-adjusted later.
const numberOfPartitionsEstimate = 10
maxSize := opts.CalculateMaxSize(c.opts.MaxSize / numberOfPartitionsEstimate)
onEvict := func(k K, v V) {
c.onEvict(k, v)
}
// Create a new partition and cache it.
partition := &Partition[K, V]{
c: lazycache.New(lazycache.Options[K, V]{MaxEntries: maxSize, OnEvict: onEvict}),
maxSize: maxSize,
trace: c.opts.Log.Logger().WithLevel(logg.LevelTrace).WithField("partition", name),
opts: opts,
}
c.partitions[name] = partition
return partition
}
// Partition is a partition in the cache.
type Partition[K comparable, V any] struct {
c *lazycache.Cache[K, V]
zero V
trace logg.LevelLogger
opts OptionsPartition
maxSize int
}
// GetOrCreate gets or creates a value for the given key.
func (p *Partition[K, V]) GetOrCreate(key K, create func(key K) (V, error)) (V, error) {
v, _, err := p.c.GetOrCreate(key, create)
return v, err
}
// GetOrCreateWitTimeout gets or creates a value for the given key and times out if the create function
// takes too long.
func (p *Partition[K, V]) GetOrCreateWitTimeout(key K, duration time.Duration, create func(key K) (V, error)) (V, error) {
resultch := make(chan V, 1)
errch := make(chan error, 1)
go func() {
v, _, err := p.c.GetOrCreate(key, create)
if err != nil {
errch <- err
return
}
resultch <- v
}()
select {
case v := <-resultch:
return v, nil
case err := <-errch:
return p.zero, err
case <-time.After(duration):
return p.zero, &herrors.TimeoutError{
Duration: duration,
}
}
}
func (p *Partition[K, V]) clearMatching(predicate func(k, v any) bool) {
p.c.DeleteFunc(func(key K, v V) bool {
if predicate(key, v) {
p.trace.Log(
logg.StringFunc(
func() string {
return fmt.Sprintf("clearing cache key %v", key)
},
),
)
return true
}
return false
})
}
func (p *Partition[K, V]) clearOnRebuild(changeset ...identity.Identity) {
opts := p.getOptions()
if opts.ClearWhen == ClearNever {
return
}
if opts.ClearWhen == ClearOnRebuild {
// Clear all.
p.Clear()
return
}
depsFinder := identity.NewFinder(identity.FinderConfig{})
shouldDelete := func(key K, v V) bool {
// We always clear elements marked as stale.
if resource.IsStaleAny(v) {
return true
}
// Now check if this entry has changed based on the changeset
// based on filesystem events.
if len(changeset) == 0 {
// Nothing changed.
return false
}
var probablyDependent bool
identity.WalkIdentitiesShallow(v, func(level int, id2 identity.Identity) bool {
for _, id := range changeset {
if r := depsFinder.Contains(id, id2, -1); r > 0 {
// It's probably dependent, evict from cache.
probablyDependent = true
return true
}
}
return false
})
return probablyDependent
}
// First pass.
// Second pass needs to be done in a separate loop to catch any
// elements marked as stale in the other partitions.
p.c.DeleteFunc(func(key K, v V) bool {
if shouldDelete(key, v) {
p.trace.Log(
logg.StringFunc(
func() string {
return fmt.Sprintf("first pass: clearing cache key %v", key)
},
),
)
return true
}
return false
})
}
func (p *Partition[K, V]) Keys() []K {
var keys []K
p.c.DeleteFunc(func(key K, v V) bool {
keys = append(keys, key)
return false
})
return keys
}
func (p *Partition[K, V]) clearStale() {
p.c.DeleteFunc(func(key K, v V) bool {
isStale := resource.IsStaleAny(v)
if isStale {
p.trace.Log(
logg.StringFunc(
func() string {
return fmt.Sprintf("second pass: clearing cache key %v", key)
},
),
)
}
return isStale
})
}
// adjustMaxSize adjusts the max size of the and returns the number of items evicted.
func (p *Partition[K, V]) adjustMaxSize(newMaxSize int) int {
if newMaxSize < minMaxSize {
newMaxSize = minMaxSize
}
oldMaxSize := p.maxSize
if newMaxSize == oldMaxSize {
return 0
}
p.maxSize = newMaxSize
// fmt.Println("Adjusting max size of partition from", oldMaxSize, "to", newMaxSize)
return p.c.Resize(newMaxSize)
}
func (p *Partition[K, V]) getMaxSize() int {
return p.maxSize
}
func (p *Partition[K, V]) getOptions() OptionsPartition {
return p.opts
}
func (p *Partition[K, V]) Clear() {
p.c.DeleteFunc(func(key K, v V) bool {
return true
})
}
func (p *Partition[K, V]) Get(ctx context.Context, key K) (V, bool) {
return p.c.Get(key)
}
type PartitionManager interface {
adjustMaxSize(addend int) int
getMaxSize() int
getOptions() OptionsPartition
clearOnRebuild(changeset ...identity.Identity)
clearMatching(predicate func(k, v any) bool)
clearStale()
}
const (
ClearOnRebuild ClearWhen = iota + 1
ClearOnChange
ClearNever
)
type ClearWhen int
type stats struct {
opts Options
memstatsCurrent runtime.MemStats
currentMaxSize int
availableMemory uint64
adjustmentFactor float64
}
func (s *stats) adjustCurrentMaxSize() bool {
newCurrentMaxSize := int(math.Floor(float64(s.opts.MaxSize) * s.adjustmentFactor))
if newCurrentMaxSize < s.opts.MinMaxSize {
newCurrentMaxSize = int(s.opts.MinMaxSize)
}
changed := newCurrentMaxSize != s.currentMaxSize
s.currentMaxSize = newCurrentMaxSize
return changed
}
// CleanKey turns s into a format suitable for a cache key for this package.
// The key will be a Unix-styled path with a leading slash but no trailing slash.
func CleanKey(s string) string {
return path.Clean(paths.ToSlashPreserveLeading(s))
}
+175
View File
@@ -0,0 +1,175 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dynacache
import (
"path/filepath"
"testing"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/resources/resource"
)
var (
_ resource.StaleInfo = (*testItem)(nil)
_ identity.Identity = (*testItem)(nil)
)
type testItem struct {
name string
isStale bool
}
func (t testItem) IsStale() bool {
return t.isStale
}
func (t testItem) IdentifierBase() string {
return t.name
}
func TestCache(t *testing.T) {
t.Parallel()
c := qt.New(t)
cache := New(Options{
Log: loggers.NewDefault(),
})
c.Cleanup(func() {
cache.Stop()
})
opts := OptionsPartition{Weight: 30}
c.Assert(cache, qt.Not(qt.IsNil))
p1 := GetOrCreatePartition[string, testItem](cache, "/aaaa/bbbb", opts)
c.Assert(p1, qt.Not(qt.IsNil))
p2 := GetOrCreatePartition[string, testItem](cache, "/aaaa/bbbb", opts)
c.Assert(func() { GetOrCreatePartition[string, testItem](cache, "foo bar", opts) }, qt.PanicMatches, ".*invalid partition name.*")
c.Assert(func() { GetOrCreatePartition[string, testItem](cache, "/aaaa/cccc", OptionsPartition{Weight: 1234}) }, qt.PanicMatches, ".*invalid Weight.*")
c.Assert(p2, qt.Equals, p1)
p3 := GetOrCreatePartition[string, testItem](cache, "/aaaa/cccc", opts)
c.Assert(p3, qt.Not(qt.IsNil))
c.Assert(p3, qt.Not(qt.Equals), p1)
c.Assert(func() { New(Options{}) }, qt.PanicMatches, ".*nil Log.*")
}
func TestCalculateMaxSizePerPartition(t *testing.T) {
t.Parallel()
c := qt.New(t)
c.Assert(calculateMaxSizePerPartition(1000, 500, 5), qt.Equals, 200)
c.Assert(calculateMaxSizePerPartition(1000, 250, 5), qt.Equals, 400)
c.Assert(func() { calculateMaxSizePerPartition(1000, 250, 0) }, qt.PanicMatches, ".*must be > 0.*")
c.Assert(func() { calculateMaxSizePerPartition(1000, 0, 1) }, qt.PanicMatches, ".*must be > 0.*")
}
func TestCleanKey(t *testing.T) {
c := qt.New(t)
c.Assert(CleanKey("a/b/c"), qt.Equals, "/a/b/c")
c.Assert(CleanKey("/a/b/c"), qt.Equals, "/a/b/c")
c.Assert(CleanKey("a/b/c/"), qt.Equals, "/a/b/c")
c.Assert(CleanKey(filepath.FromSlash("/a/b/c/")), qt.Equals, "/a/b/c")
}
func newTestCache(t *testing.T) *Cache {
cache := New(
Options{
Log: loggers.NewDefault(),
},
)
p1 := GetOrCreatePartition[string, testItem](cache, "/aaaa/bbbb", OptionsPartition{Weight: 30, ClearWhen: ClearOnRebuild})
p2 := GetOrCreatePartition[string, testItem](cache, "/aaaa/cccc", OptionsPartition{Weight: 30, ClearWhen: ClearOnChange})
p1.GetOrCreate("clearOnRebuild", func(string) (testItem, error) {
return testItem{}, nil
})
p2.GetOrCreate("clearBecauseStale", func(string) (testItem, error) {
return testItem{
isStale: true,
}, nil
})
p2.GetOrCreate("clearBecauseIdentityChanged", func(string) (testItem, error) {
return testItem{
name: "changed",
}, nil
})
p2.GetOrCreate("clearNever", func(string) (testItem, error) {
return testItem{
isStale: false,
}, nil
})
t.Cleanup(func() {
cache.Stop()
})
return cache
}
func TestClear(t *testing.T) {
t.Parallel()
c := qt.New(t)
predicateAll := func(string) bool {
return true
}
cache := newTestCache(t)
c.Assert(cache.Keys(predicateAll), qt.HasLen, 4)
cache.ClearOnRebuild()
// Stale items are always cleared.
c.Assert(cache.Keys(predicateAll), qt.HasLen, 2)
cache = newTestCache(t)
cache.ClearOnRebuild(identity.StringIdentity("changed"))
c.Assert(cache.Keys(nil), qt.HasLen, 1)
cache = newTestCache(t)
cache.ClearMatching(func(k, v any) bool {
return k.(string) == "clearOnRebuild"
})
c.Assert(cache.Keys(predicateAll), qt.HasLen, 3)
cache.adjustCurrentMaxSize()
}
func TestAdjustCurrentMaxSize(t *testing.T) {
t.Parallel()
c := qt.New(t)
cache := newTestCache(t)
alloc := cache.stats.memstatsCurrent.Alloc
cache.adjustCurrentMaxSize()
c.Assert(cache.stats.memstatsCurrent.Alloc, qt.Not(qt.Equals), alloc)
}
+37 -6
View File
@@ -24,6 +24,7 @@ import (
"time"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/helpers"
@@ -51,6 +52,9 @@ type Cache struct {
pruneAllRootDir string
nlocker *lockTracker
initOnce sync.Once
initErr error
}
type lockTracker struct {
@@ -103,9 +107,23 @@ func (l *lockedFile) Close() error {
return l.File.Close()
}
func (c *Cache) init() error {
c.initOnce.Do(func() {
// Create the base dir if it does not exist.
if err := c.Fs.MkdirAll("", 0o777); err != nil && !os.IsExist(err) {
c.initErr = err
}
})
return c.initErr
}
// WriteCloser returns a transactional writer into the cache.
// It's important that it's closed when done.
func (c *Cache) WriteCloser(id string) (ItemInfo, io.WriteCloser, error) {
if err := c.init(); err != nil {
return ItemInfo{}, nil, err
}
id = cleanID(id)
c.nlocker.Lock(id)
@@ -129,7 +147,12 @@ func (c *Cache) WriteCloser(id string) (ItemInfo, io.WriteCloser, error) {
// it when done.
func (c *Cache) ReadOrCreate(id string,
read func(info ItemInfo, r io.ReadSeeker) error,
create func(info ItemInfo, w io.WriteCloser) error) (info ItemInfo, err error) {
create func(info ItemInfo, w io.WriteCloser) error,
) (info ItemInfo, err error) {
if err := c.init(); err != nil {
return ItemInfo{}, err
}
id = cleanID(id)
c.nlocker.Lock(id)
@@ -163,6 +186,9 @@ func (c *Cache) ReadOrCreate(id string,
// be invoked and the result cached.
// This method is protected by a named lock using the given id as identifier.
func (c *Cache) GetOrCreate(id string, create func() (io.ReadCloser, error)) (ItemInfo, io.ReadCloser, error) {
if err := c.init(); err != nil {
return ItemInfo{}, nil, err
}
id = cleanID(id)
c.nlocker.Lock(id)
@@ -197,6 +223,9 @@ func (c *Cache) GetOrCreate(id string, create func() (io.ReadCloser, error)) (It
// GetOrCreateBytes is the same as GetOrCreate, but produces a byte slice.
func (c *Cache) GetOrCreateBytes(id string, create func() ([]byte, error)) (ItemInfo, []byte, error) {
if err := c.init(); err != nil {
return ItemInfo{}, nil, err
}
id = cleanID(id)
c.nlocker.Lock(id)
@@ -232,6 +261,9 @@ func (c *Cache) GetOrCreateBytes(id string, create func() ([]byte, error)) (Item
// GetBytes gets the file content with the given id from the cache, nil if none found.
func (c *Cache) GetBytes(id string) (ItemInfo, []byte, error) {
if err := c.init(); err != nil {
return ItemInfo{}, nil, err
}
id = cleanID(id)
c.nlocker.Lock(id)
@@ -250,6 +282,9 @@ func (c *Cache) GetBytes(id string) (ItemInfo, []byte, error) {
// Get gets the file with the given id from the cache, nil if none found.
func (c *Cache) Get(id string) (ItemInfo, io.ReadCloser, error) {
if err := c.init(); err != nil {
return ItemInfo{}, nil, err
}
id = cleanID(id)
c.nlocker.Lock(id)
@@ -347,11 +382,7 @@ func NewCaches(p *helpers.PathSpec) (Caches, error) {
baseDir := v.DirCompiled
if err := cfs.MkdirAll(baseDir, 0777); err != nil && !os.IsExist(err) {
return nil, err
}
bfs := afero.NewBasePathFs(cfs, baseDir)
bfs := hugofs.NewBasePathFs(cfs, baseDir)
var pruneAllRootDir string
if k == CacheKeyModules {
+2 -4
View File
@@ -15,6 +15,7 @@
package filecache
import (
"errors"
"fmt"
"path"
"path/filepath"
@@ -24,8 +25,6 @@ import (
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/config"
"errors"
"github.com/mitchellh/mapstructure"
"github.com/spf13/afero"
)
@@ -93,7 +92,7 @@ type FileCacheConfig struct {
// Will resources/_gen will get its own composite filesystem that
// also checks any theme.
IsResourceDir bool
IsResourceDir bool `json:"-"`
}
// GetJSONCache gets the file cache for getJSON.
@@ -225,7 +224,6 @@ func DecodeConfig(fs afero.Fs, bcfg config.BaseConfig, m map[string]any) (Config
// Resolves :resourceDir => /myproject/resources etc., :cacheDir => ...
func resolveDirPlaceholder(fs afero.Fs, bcfg config.BaseConfig, placeholder string) (cacheDir string, isResource bool, err error) {
switch strings.ToLower(placeholder) {
case ":resourcedir":
return "", true, nil
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,7 +15,6 @@ package filecache_test
import (
"path/filepath"
"testing"
"time"
@@ -47,7 +46,6 @@ title: "Home"
_, err := b.H.BaseFs.ResourcesCache.Stat(filepath.Join("_gen", "images"))
b.Assert(err, qt.IsNil)
}
func TestPruneImages(t *testing.T) {
@@ -55,6 +53,7 @@ func TestPruneImages(t *testing.T) {
// TODO(bep)
t.Skip("skip flaky test on CI server")
}
t.Skip("skip flaky test")
files := `
-- hugo.toml --
baseURL = "https://example.com"
@@ -92,7 +91,7 @@ iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAA
// TODO(bep) we need a way to test full rebuilds.
// For now, just sleep a little so the cache elements expires.
time.Sleep(300 * time.Millisecond)
time.Sleep(500 * time.Millisecond)
b.RenameFile("assets/a/pixel.png", "assets/b/pixel2.png").Build()
@@ -104,5 +103,4 @@ iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAA
b.Assert(err, qt.Not(qt.IsNil))
_, err = b.H.BaseFs.ResourcesCache.Stat(imagesCacheDir)
b.Assert(err, qt.IsNil)
}
+6 -2
View File
@@ -53,11 +53,13 @@ func (c *Cache) Prune(force bool) (int, error) {
if c.pruneAllRootDir != "" {
return c.pruneRootDir(force)
}
if err := c.init(); err != nil {
return 0, err
}
counter := 0
err := afero.Walk(c.Fs, "", func(name string, info os.FileInfo, err error) error {
if info == nil {
return nil
}
@@ -66,7 +68,6 @@ func (c *Cache) Prune(force bool) (int, error) {
if info.IsDir() {
f, err := c.Fs.Open(name)
if err != nil {
// This cache dir may not exist.
return nil
@@ -117,6 +118,9 @@ func (c *Cache) Prune(force bool) (int, error) {
}
func (c *Cache) pruneRootDir(force bool) (int, error) {
if err := c.init(); err != nil {
return 0, err
}
info, err := c.Fs.Stat(c.pruneAllRootDir)
if err != nil {
if herrors.IsNotExist(err) {
+1 -11
View File
@@ -1,4 +1,4 @@
// Copyright 2018 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@ import (
"errors"
"fmt"
"io"
"path/filepath"
"strings"
"sync"
"testing"
@@ -86,17 +85,8 @@ dir = ":cacheDir/c"
cache := caches.Get("GetJSON")
c.Assert(cache, qt.Not(qt.IsNil))
bfs, ok := cache.Fs.(*afero.BasePathFs)
c.Assert(ok, qt.Equals, true)
filename, err := bfs.RealPath("key")
c.Assert(err, qt.IsNil)
cache = caches.Get("Images")
c.Assert(cache, qt.Not(qt.IsNil))
bfs, ok = cache.Fs.(*afero.BasePathFs)
c.Assert(ok, qt.Equals, true)
filename, _ = bfs.RealPath("key")
c.Assert(filename, qt.Equals, filepath.FromSlash("_gen/images/key"))
rf := func(s string) func() (io.ReadCloser, error) {
return func() (io.ReadCloser, error) {
-78
View File
@@ -1,78 +0,0 @@
// Copyright 2018 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 namedmemcache provides a memory cache with a named lock. This is suitable
// for situations where creating the cached resource can be time consuming or otherwise
// resource hungry, or in situations where a "once only per key" is a requirement.
package namedmemcache
import (
"sync"
"github.com/BurntSushi/locker"
)
// Cache holds the cached values.
type Cache struct {
nlocker *locker.Locker
cache map[string]cacheEntry
mu sync.RWMutex
}
type cacheEntry struct {
value any
err error
}
// New creates a new cache.
func New() *Cache {
return &Cache{
nlocker: locker.NewLocker(),
cache: make(map[string]cacheEntry),
}
}
// Clear clears the cache state.
func (c *Cache) Clear() {
c.mu.Lock()
defer c.mu.Unlock()
c.cache = make(map[string]cacheEntry)
c.nlocker = locker.NewLocker()
}
// GetOrCreate tries to get the value with the given cache key, if not found
// 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() (any, error)) (any, error) {
c.mu.RLock()
entry, found := c.cache[key]
c.mu.RUnlock()
if found {
return entry.value, entry.err
}
c.nlocker.Lock(key)
defer c.nlocker.Unlock(key)
// Create it.
value, err := create()
c.mu.Lock()
c.cache[key] = cacheEntry{value: value, err: err}
c.mu.Unlock()
return value, err
}
-80
View File
@@ -1,80 +0,0 @@
// Copyright 2018 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 namedmemcache
import (
"fmt"
"sync"
"testing"
qt "github.com/frankban/quicktest"
)
func TestNamedCache(t *testing.T) {
t.Parallel()
c := qt.New(t)
cache := New()
counter := 0
create := func() (any, error) {
counter++
return counter, nil
}
for i := 0; i < 5; i++ {
v1, err := cache.GetOrCreate("a1", create)
c.Assert(err, qt.IsNil)
c.Assert(v1, qt.Equals, 1)
v2, err := cache.GetOrCreate("a2", create)
c.Assert(err, qt.IsNil)
c.Assert(v2, qt.Equals, 2)
}
cache.Clear()
v3, err := cache.GetOrCreate("a2", create)
c.Assert(err, qt.IsNil)
c.Assert(v3, qt.Equals, 3)
}
func TestNamedCacheConcurrent(t *testing.T) {
t.Parallel()
c := qt.New(t)
var wg sync.WaitGroup
cache := New()
create := func(i int) func() (any, error) {
return func() (any, error) {
return i, nil
}
}
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 100; j++ {
id := fmt.Sprintf("id%d", j)
v, err := cache.GetOrCreate(id, create(j))
c.Assert(err, qt.IsNil)
c.Assert(v, qt.Equals, j)
}
}()
}
wg.Wait()
}
Executable
+2
View File
@@ -0,0 +1,2 @@
#!/usr/bin/env bash
diff <(gofmt -d .) <(printf '')
-1
View File
@@ -461,7 +461,6 @@ func collectMethodsRecursive(pkg string, f []*ast.Field) []string {
pkg,
tt.Methods.List)...)
}
} else {
// Embedded, but in a different file/package. Return the
// package.Name and deal with that later.
+34 -32
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import (
"os"
"os/signal"
"path/filepath"
"runtime"
"strings"
"sync"
"sync/atomic"
@@ -38,6 +39,7 @@ import (
"github.com/gohugoio/hugo/common/hstrings"
"github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/config"
@@ -50,9 +52,7 @@ import (
"github.com/spf13/cobra"
)
var (
errHelp = errors.New("help requested")
)
var errHelp = errors.New("help requested")
// Execute executes a command.
func Execute(args []string) error {
@@ -182,11 +182,9 @@ func (r *rootCommand) ConfigFromConfig(key int32, oldConf *commonConfig) (*commo
cfg: oldConf.cfg,
fs: fs,
}, nil
})
return cc, err
}
func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commonConfig, error) {
@@ -205,13 +203,10 @@ func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commo
cfg = config.New()
}
if !cfg.IsSet("renderToDisk") {
cfg.Set("renderToDisk", true)
}
if !cfg.IsSet("workingDir") {
cfg.Set("workingDir", dir)
} else {
if err := os.MkdirAll(cfg.GetString("workingDir"), 0777); err != nil {
if err := os.MkdirAll(cfg.GetString("workingDir"), 0o777); err != nil {
return nil, fmt.Errorf("failed to create workingDir: %w", err)
}
}
@@ -241,9 +236,7 @@ func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commo
sourceFs := hugofs.Os
var destinationFs afero.Fs
if cfg.GetBool("renderToDisk") {
destinationFs = hugofs.Os
} else {
if cfg.GetBool("renderToMemory") {
destinationFs = afero.NewMemMapFs()
if renderStaticToDisk {
// Hybrid, render dynamic content to Root.
@@ -253,6 +246,8 @@ func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commo
cfg.Set("publishDirDynamic", "/")
cfg.Set("publishDirStatic", "/")
}
} else {
destinationFs = hugofs.Os
}
fs := hugofs.NewFromSourceAndDestination(sourceFs, destinationFs, cfg)
@@ -262,7 +257,7 @@ func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commo
publishDirStatic := cfg.GetString("publishDirStatic")
workingDir := cfg.GetString("workingDir")
absPublishDirStatic := paths.AbsPathify(workingDir, publishDirStatic)
staticFs := afero.NewBasePathFs(afero.NewOsFs(), absPublishDirStatic)
staticFs := hugofs.NewBasePathFs(afero.NewOsFs(), absPublishDirStatic)
// Serve from both the static and dynamic fs,
// the first will take priority.
@@ -303,7 +298,6 @@ func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commo
})
return cc, err
}
func (r *rootCommand) HugFromConfig(conf *commonConfig) (*hugolib.HugoSites, error) {
@@ -345,10 +339,13 @@ func (r *rootCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args
if r.buildWatch {
defer r.timeTrack(time.Now(), "Built")
}
err := b.build()
return err
close, err := b.build()
if err != nil {
return err
}
close()
return nil
}()
if err != nil {
return err
}
@@ -410,8 +407,15 @@ func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
return err
}
r.commonConfigs = lazycache.New[int32, *commonConfig](lazycache.Options{MaxEntries: 5})
r.hugoSites = lazycache.New[int32, *hugolib.HugoSites](lazycache.Options{MaxEntries: 5})
r.commonConfigs = lazycache.New(lazycache.Options[int32, *commonConfig]{MaxEntries: 5})
// We don't want to keep stale HugoSites in memory longer than needed.
r.hugoSites = lazycache.New(lazycache.Options[int32, *hugolib.HugoSites]{
MaxEntries: 1,
OnEvict: func(key int32, value *hugolib.HugoSites) {
value.Close()
runtime.GC()
},
})
return nil
}
@@ -434,26 +438,26 @@ func (r *rootCommand) createLogger(running bool) (loggers.Logger, error) {
}
} else {
if r.verbose {
helpers.Deprecated("--verbose", "use --logLevel info", false)
hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0")
hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0")
level = logg.LevelInfo
}
if r.debug {
helpers.Deprecated("--debug", "use --logLevel debug", false)
hugo.Deprecate("--debug", "use --logLevel debug", "v0.114.0")
level = logg.LevelDebug
}
}
optsLogger := loggers.Options{
Distinct: true,
Level: level,
Stdout: r.Out,
Stderr: r.Out,
StoreErrors: running,
DistinctLevel: logg.LevelWarn,
Level: level,
Stdout: r.Out,
Stderr: r.Out,
StoreErrors: running,
}
return loggers.New(optsLogger), nil
}
func (r *rootCommand) Reset() {
@@ -491,6 +495,7 @@ Complete documentation is available at https://gohugo.io/.`
cmd.PersistentFlags().StringVar(&r.cfgFile, "config", "", "config file (default is hugo.yaml|json|toml)")
cmd.PersistentFlags().StringVar(&r.cfgDir, "configDir", "config", "config dir")
cmd.PersistentFlags().BoolVar(&r.quiet, "quiet", false, "build in quiet mode")
cmd.PersistentFlags().BoolVar(&r.renderToMemory, "renderToMemory", false, "render to memory (mostly useful when running the server)")
// Set bash-completion
_ = cmd.PersistentFlags().SetAnnotation("config", cobra.BashCompFilenameExt, config.ValidConfigFileExtensions)
@@ -499,7 +504,6 @@ Complete documentation is available at https://gohugo.io/.`
cmd.PersistentFlags().BoolVarP(&r.debug, "debug", "", false, "debug output")
cmd.PersistentFlags().StringVar(&r.logLevel, "logLevel", "", "log level (debug|info|warn|error)")
cmd.Flags().BoolVarP(&r.buildWatch, "watch", "w", false, "watch filesystem for changes and recreate as needed")
cmd.Flags().BoolVar(&r.renderToMemory, "renderToMemory", false, "render to memory (only useful for benchmark testing)")
// Configure local flags
applyLocalFlagsBuild(cmd, r)
@@ -515,11 +519,10 @@ Complete documentation is available at https://gohugo.io/.`
func applyLocalFlagsBuildConfig(cmd *cobra.Command, r *rootCommand) {
cmd.Flags().StringSliceP("theme", "t", []string{}, "themes to use (located in /themes/THEMENAME/)")
cmd.Flags().StringVarP(&r.baseURL, "baseURL", "b", "", "hostname (and path) to the root, e.g. https://spf13.com/")
cmd.Flags().StringP("cacheDir", "", "", "filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache_$USER/")
cmd.Flags().StringP("cacheDir", "", "", "filesystem path to cache directory")
_ = cmd.Flags().SetAnnotation("cacheDir", cobra.BashCompSubdirsInDir, []string{})
cmd.Flags().StringP("contentDir", "c", "", "filesystem path to content directory")
_ = cmd.Flags().SetAnnotation("theme", cobra.BashCompSubdirsInDir, []string{"themes"})
}
// Flags needed to do a build (used by hugo and hugo server commands)
@@ -558,7 +561,6 @@ func applyLocalFlagsBuild(cmd *cobra.Command, r *rootCommand) {
cmd.Flags().StringSlice("disableKinds", []string{}, "disable different kind of pages (home, RSS etc.)")
cmd.Flags().Bool("minify", false, "minify any supported output format (HTML, XML etc.)")
_ = cmd.Flags().SetAnnotation("destination", cobra.BashCompSubdirsInDir, []string{})
}
func (r *rootCommand) timeTrack(start time.Time, name string) {
+1 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -37,5 +37,4 @@ func newExec() (*simplecobra.Exec, error) {
}
return simplecobra.New(rootCmd)
}
+3 -3
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import (
"time"
"github.com/bep/simplecobra"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/config/allconfig"
"github.com/gohugoio/hugo/modules"
"github.com/gohugoio/hugo/parser"
@@ -36,7 +37,6 @@ func newConfigCommand() *configCommand {
&configMountsCommand{},
},
}
}
type configCommand struct {
@@ -92,6 +92,7 @@ func (c *configCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, arg
if err := json.Unmarshal(buf.Bytes(), &m); err != nil {
return err
}
maps.ConvertFloat64WithNoDecimalsToInt(m)
switch format {
case "yaml":
return parser.InterfaceToConfig(m, metadecoders.YAML, os.Stdout)
@@ -188,7 +189,6 @@ func (m *configModMounts) MarshalJSON() ([]byte, error) {
Dir: m.m.Dir(),
Mounts: mounts,
})
}
type configMountsCommand struct {
+3 -3
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -134,7 +134,7 @@ func (c *convertCommand) convertAndSavePage(p page.Page, site *hugolib.Site, tar
}
}
if p.File().IsZero() {
if p.File() == nil {
// No content file.
return nil
}
@@ -209,7 +209,7 @@ func (c *convertCommand) convertContents(format metadecoders.Format) error {
var pagesBackedByFile page.Pages
for _, p := range site.AllPages() {
if p.File().IsZero() {
if p.File() == nil {
continue
}
pagesBackedByFile = append(pagesBackedByFile, p)
+2 -3
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
//go:build !nodeploy
// +build !nodeploy
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -38,7 +38,6 @@ import (
)
func newDeployCommand() simplecobra.Commander {
return &simpleCommand{
name: "deploy",
short: "Deploy your site to a Cloud provider.",
+2 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
//go:build nodeploy
// +build nodeploy
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
+33 -13
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,6 +14,7 @@
package commands
import (
"bytes"
"context"
"encoding/json"
"fmt"
@@ -30,8 +31,11 @@ import (
"github.com/gohugoio/hugo/docshelper"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/parser"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
"gopkg.in/yaml.v2"
)
func newGenCommand() *genCommand {
@@ -72,7 +76,7 @@ See https://xyproto.github.io/splash/docs/all.html for a preview of the availabl
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.PersistentFlags().StringVar(&style, "style", "friendly", "highlighter style (see https://xyproto.github.io/splash/docs/)")
cmd.PersistentFlags().StringVar(&highlightStyle, "highlightStyle", "bg:#ffffcc", "style used for highlighting lines (see https://github.com/alecthomas/chroma)")
cmd.PersistentFlags().StringVar(&highlightStyle, "highlightStyle", "", "style used for highlighting lines (see https://github.com/alecthomas/chroma)")
cmd.PersistentFlags().StringVar(&linesStyle, "linesStyle", "", "style used for line numbers (see https://github.com/alecthomas/chroma)")
},
}
@@ -97,7 +101,7 @@ See https://xyproto.github.io/splash/docs/all.html for a preview of the availabl
}
if found, _ := helpers.Exists(genmandir, hugofs.Os); !found {
r.Println("Directory", genmandir, "does not exist, creating...")
if err := hugofs.Os.MkdirAll(genmandir, 0777); err != nil {
if err := hugofs.Os.MkdirAll(genmandir, 0o777); err != nil {
return err
}
}
@@ -132,7 +136,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 https://gohugo.io/.
It creates one Markdown file per command with front matter suitable
for rendering in Hugo.`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
@@ -146,7 +150,7 @@ url: %s
}
if found, _ := helpers.Exists(gendocdir, hugofs.Os); !found {
r.Println("Directory", gendocdir, "does not exist, creating...")
if err := hugofs.Os.MkdirAll(gendocdir, 0777); err != nil {
if err := hugofs.Os.MkdirAll(gendocdir, 0o777); err != nil {
return err
}
}
@@ -173,7 +177,6 @@ url: %s
cmd.PersistentFlags().SetAnnotation("dir", cobra.BashCompSubdirsInDir, []string{})
},
}
}
var docsHelperTarget string
@@ -186,18 +189,36 @@ url: %s
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
r.Println("Generate docs data to", docsHelperTarget)
targetFile := filepath.Join(docsHelperTarget, "docs.json")
var buf bytes.Buffer
jsonEnc := json.NewEncoder(&buf)
configProvider := func() docshelper.DocProvider {
conf := hugolib.DefaultConfig()
conf.CacheDir = "" // The default value does not make sense in the docs.
defaultConfig := parser.NullBoolJSONMarshaller{Wrapped: parser.LowerCaseCamelJSONMarshaller{Value: conf}}
return docshelper.DocProvider{"config": defaultConfig}
}
docshelper.AddDocProviderFunc(configProvider)
if err := jsonEnc.Encode(docshelper.GetDocProvider()); err != nil {
return err
}
// Decode the JSON to a map[string]interface{} and then unmarshal it again to the correct format.
var m map[string]interface{}
if err := json.Unmarshal(buf.Bytes(), &m); err != nil {
return err
}
targetFile := filepath.Join(docsHelperTarget, "docs.yaml")
f, err := os.Create(targetFile)
if err != nil {
return err
}
defer f.Close()
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
if err := enc.Encode(docshelper.GetDocProvider()); err != nil {
yamlEnc := yaml.NewEncoder(f)
if err := yamlEnc.Encode(m); err != nil {
return err
}
@@ -219,7 +240,6 @@ url: %s
newDocsHelper(),
},
}
}
type genCommand struct {
+2 -14
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,7 +14,6 @@
package commands
import (
"bytes"
"errors"
"fmt"
"log"
@@ -24,8 +23,6 @@ import (
"github.com/bep/simplecobra"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"github.com/spf13/afero"
"github.com/spf13/pflag"
)
@@ -113,20 +110,11 @@ func flagsToCfgWithAdditionalConfigBase(cd *simplecobra.Commandeer, cfg config.P
})
return cfg
}
func mkdir(x ...string) {
p := filepath.Join(x...)
err := os.MkdirAll(p, 0777) // before umask
if err != nil {
log.Fatal(err)
}
}
func touchFile(fs afero.Fs, filename string) {
mkdir(filepath.Dir(filename))
err := helpers.WriteToDisk(filename, bytes.NewReader([]byte{}), fs)
err := os.MkdirAll(p, 0o777) // before umask
if err != nil {
log.Fatal(err)
}
+4 -4
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -25,9 +25,9 @@ func init() {
// This message to show to Windows users if Hugo is opened from explorer.exe
cobra.MousetrapHelpText = `
Hugo is a command-line tool for generating static website.
Hugo is a command-line tool for generating static websites.
You need to open PowerShell and run Hugo from there.
You need to open cmd.exe and run Hugo from there.
Visit https://gohugo.io/ for more information.`
}
+60 -110
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ import (
"runtime/trace"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/bep/logg"
@@ -34,6 +35,7 @@ import (
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/terminal"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
@@ -83,7 +85,6 @@ func (c *hugoBuilder) withConf(fn func(conf *commonConfig)) {
c.confmu.Lock()
defer c.confmu.Unlock()
fn(c.conf)
}
type hugoBuilderErrState struct {
@@ -135,46 +136,12 @@ func (c *hugoBuilder) errCount() int {
// getDirList provides NewWatcher() with a list of directories to watch for changes.
func (c *hugoBuilder) getDirList() ([]string, error) {
var filenames []string
walkFn := func(path string, fi hugofs.FileMetaInfo, err error) error {
if err != nil {
c.r.logger.Errorln("walker: ", err)
return nil
}
if fi.IsDir() {
if fi.Name() == ".git" ||
fi.Name() == "node_modules" || fi.Name() == "bower_components" {
return filepath.SkipDir
}
filenames = append(filenames, fi.Meta().Filename)
}
return nil
}
h, err := c.hugo()
if err != nil {
return nil, err
}
watchFiles := h.PathSpec.BaseFs.WatchDirs()
for _, fi := range watchFiles {
if !fi.IsDir() {
filenames = append(filenames, fi.Meta().Filename)
continue
}
w := hugofs.NewWalkway(hugofs.WalkwayConfig{Logger: c.r.logger, Info: fi, WalkFn: walkFn})
if err := w.Walk(); err != nil {
c.r.logger.Errorln("walker: ", err)
}
}
filenames = helpers.UniqueStringsSorted(filenames)
return filenames, nil
return helpers.UniqueStringsSorted(h.PathSpec.BaseFs.WatchFilenames()), nil
}
func (c *hugoBuilder) initCPUProfile() (func(), error) {
@@ -394,34 +361,32 @@ func (c *hugoBuilder) newWatcher(pollIntervalStr string, dirList ...string) (*wa
return watcher, nil
}
func (c *hugoBuilder) build() error {
func (c *hugoBuilder) build() (func(), error) {
stopProfiling, err := c.initProfiling()
if err != nil {
return err
return nil, err
}
defer func() {
if stopProfiling != nil {
stopProfiling()
}
}()
if err := c.fullBuild(false); err != nil {
return err
return nil, err
}
if !c.r.quiet {
c.r.Println()
h, err := c.hugo()
if err != nil {
return err
return nil, err
}
h.PrintProcessingStats(os.Stdout)
c.r.Println()
}
return nil
return func() {
if stopProfiling != nil {
stopProfiling()
}
}, nil
}
func (c *hugoBuilder) buildSites(noBuildLock bool) (err error) {
@@ -441,7 +406,7 @@ func (c *hugoBuilder) copyStatic() (map[string]uint64, error) {
}
func (c *hugoBuilder) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint64, error) {
infol := c.r.logger.InfoCommand("copy static")
infol := c.r.logger.InfoCommand("static")
publishDir := helpers.FilePathSeparator
if sourceFs.PublishFolder != "" {
@@ -467,11 +432,11 @@ func (c *hugoBuilder) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint
if syncer.Delete {
infol.Logf("removing all files from destination that don't exist in static dirs")
syncer.DeleteFilter = func(f os.FileInfo) bool {
syncer.DeleteFilter = func(f fsync.FileInfo) bool {
return f.IsDir() && strings.HasPrefix(f.Name(), ".")
}
}
infol.Logf("syncing static files to %s", publishDir)
start := time.Now()
// because we are using a baseFs (to get the union right).
// set sync src to root
@@ -479,9 +444,10 @@ func (c *hugoBuilder) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint
if err != nil {
return 0, err
}
loggers.TimeTrackf(infol, start, nil, "syncing static files to %s", publishDir)
// Sync runs Stat 3 times for every source file (which sounds much)
numFiles := fs.statCounter / 3
// Sync runs Stat 2 times for every source file.
numFiles := fs.statCounter / 2
return numFiles, err
}
@@ -652,13 +618,31 @@ func (c *hugoBuilder) handleBuildErr(err error, msg string) {
func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
staticSyncer *staticSyncer,
evs []fsnotify.Event,
configSet map[string]bool) {
configSet map[string]bool,
) {
defer func() {
c.errState.setWasErr(false)
}()
var isHandled bool
// Filter out ghost events (from deleted, renamed directories).
// This seems to be a bug in fsnotify, or possibly MacOS.
var n int
for _, ev := range evs {
keep := true
if ev.Has(fsnotify.Create) || ev.Has(fsnotify.Write) {
if _, err := os.Stat(ev.Name); err != nil {
keep = false
}
}
if keep {
evs[n] = ev
n++
}
}
evs = evs[:n]
for _, ev := range evs {
isConfig := configSet[ev.Name]
configChangeType := configChangeConfig
@@ -726,48 +710,25 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
return
}
c.r.logger.Infoln("Received System Events:", evs)
c.r.logger.Debugln("Received System Events:", evs)
staticEvents := []fsnotify.Event{}
dynamicEvents := []fsnotify.Event{}
filtered := []fsnotify.Event{}
h, err := c.hugo()
if err != nil {
c.r.logger.Errorln("Error getting the Hugo object:", err)
return
}
n = 0
for _, ev := range evs {
if h.ShouldSkipFileChangeEvent(ev) {
continue
}
// Check the most specific first, i.e. files.
contentMapped := h.ContentChanges.GetSymbolicLinkMappings(ev.Name)
if len(contentMapped) > 0 {
for _, mapped := range contentMapped {
filtered = append(filtered, fsnotify.Event{Name: mapped, Op: ev.Op})
}
continue
}
// Check for any symbolic directory mapping.
dir, name := filepath.Split(ev.Name)
contentMapped = h.ContentChanges.GetSymbolicLinkMappings(dir)
if len(contentMapped) == 0 {
filtered = append(filtered, ev)
continue
}
for _, mapped := range contentMapped {
mappedFilename := filepath.Join(mapped, name)
filtered = append(filtered, fsnotify.Event{Name: mappedFilename, Op: ev.Op})
}
evs[n] = ev
n++
}
evs = filtered
evs = evs[:n]
for _, ev := range evs {
ext := filepath.Ext(ev.Name)
@@ -788,6 +749,7 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
if istemp {
continue
}
if h.Deps.SourceSpec.IgnoreFile(ev.Name) {
continue
}
@@ -811,7 +773,7 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
continue
}
walkAdder := func(path string, f hugofs.FileMetaInfo, err error) error {
walkAdder := func(path string, f hugofs.FileMetaInfo) error {
if f.IsDir() {
c.r.logger.Println("adding created directory to watchlist", path)
if err := watcher.Add(path); err != nil {
@@ -827,11 +789,10 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
}
// recursively add new directories to watch list
// When mkdir -p is used, only the top directory triggers an event (at least on OSX)
if ev.Op&fsnotify.Create == fsnotify.Create {
if ev.Has(fsnotify.Create) || ev.Has(fsnotify.Rename) {
c.withConf(func(conf *commonConfig) {
if s, err := conf.fs.Source.Stat(ev.Name); err == nil && s.Mode().IsDir() {
_ = helpers.SymbolicWalk(conf.fs.Source, ev.Name, walkAdder)
_ = helpers.Walk(conf.fs.Source, ev.Name, walkAdder)
}
})
}
@@ -872,7 +833,7 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
return
}
path := h.BaseFs.SourceFilesystems.MakeStaticPathRelative(ev.Name)
path = h.RelURL(helpers.ToSlashTrimLeading(path), false)
path = h.RelURL(paths.ToSlashTrimLeading(path), false)
livereload.RefreshPath(path)
} else {
@@ -909,7 +870,7 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
// Nothing has changed.
return
} else if len(changed) == 1 {
pathToRefresh := h.PathSpec.RelURL(helpers.ToSlashTrimLeading(changed[0]), false)
pathToRefresh := h.PathSpec.RelURL(paths.ToSlashTrimLeading(changed[0]), false)
livereload.RefreshPath(pathToRefresh)
} else {
livereload.ForceRefresh()
@@ -944,7 +905,6 @@ func (c *hugoBuilder) hugo() (*hugolib.HugoSites, error) {
var err error
h, err = c.r.HugFromConfig(conf)
return err
}); err != nil {
return nil, err
}
@@ -968,7 +928,7 @@ func (c *hugoBuilder) hugoTry() *hugolib.HugoSites {
func (c *hugoBuilder) loadConfig(cd *simplecobra.Commandeer, running bool) error {
cfg := config.New()
cfg.Set("renderToDisk", (c.s == nil && !c.r.renderToMemory) || (c.s != nil && c.s.renderToDisk))
cfg.Set("renderToMemory", c.r.renderToMemory)
watch := c.r.buildWatch || (c.s != nil && c.s.serverWatch)
if c.r.environment == "" {
// We need to set the environment as early as possible because we need it to load the correct config.
@@ -989,9 +949,10 @@ func (c *hugoBuilder) loadConfig(cd *simplecobra.Commandeer, running bool) error
cfg.Set("environment", c.r.environment)
cfg.Set("internal", maps.Params{
"running": running,
"watch": watch,
"verbose": c.r.isVerbose(),
"running": running,
"watch": watch,
"verbose": c.r.isVerbose(),
"fastRenderMode": c.fastRenderMode,
})
conf, err := c.r.ConfigFromProvider(c.r.configVersionID.Load(), flagsToCfg(cd, cfg))
@@ -1000,6 +961,7 @@ func (c *hugoBuilder) loadConfig(cd *simplecobra.Commandeer, running bool) error
}
if len(conf.configs.LoadingInfo.ConfigFiles) == 0 {
//lint:ignore ST1005 end user message.
return errors.New("Unable to locate config file or config directory. Perhaps you need to create a new site.\nRun `hugo help new` for details.")
}
@@ -1011,15 +973,16 @@ func (c *hugoBuilder) loadConfig(cd *simplecobra.Commandeer, running bool) error
}
return nil
}
var rebuildCounter atomic.Uint64
func (c *hugoBuilder) printChangeDetected(typ string) {
msg := "\nChange"
if typ != "" {
msg += " of " + typ
}
msg += " detected, rebuilding site."
msg += fmt.Sprintf(" detected, rebuilding site (#%d).", rebuildCounter.Add(1))
c.r.logger.Println(msg)
const layout = "2006-01-02 15:04:05.000 -0700"
@@ -1034,25 +997,12 @@ func (c *hugoBuilder) rebuildSites(events []fsnotify.Event) error {
}
}
c.errState.setBuildErr(nil)
visited := c.visitedURLs.PeekAllSet()
h, err := c.hugo()
if err != nil {
return err
}
if c.fastRenderMode {
c.withConf(func(conf *commonConfig) {
// Make sure we always render the home pages
for _, l := range conf.configs.ConfigLangs() {
langPath := l.LanguagePrefix()
if langPath != "" {
langPath = langPath + "/"
}
home := h.PrependBasePath("/"+langPath, false)
visited[home] = true
}
})
}
return h.Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyVisited: visited, ErrRecovery: c.errState.wasErr()}, events...)
return h.Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyVisited: c.visitedURLs, ErrRecovery: c.errState.wasErr()}, events...)
}
func (c *hugoBuilder) reloadConfig() error {
+4 -11
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -23,7 +23,6 @@ import (
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
@@ -66,7 +65,6 @@ Import from Jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root
}
return c
}
type importCommand struct {
@@ -312,7 +310,7 @@ func (c *importCommand) convertJekyllPost(path, relPath, targetDir string, draft
targetFile := filepath.Join(targetDir, relPath)
targetParentDir := filepath.Dir(targetFile)
os.MkdirAll(targetParentDir, 0777)
os.MkdirAll(targetParentDir, 0o777)
contentBytes, err := os.ReadFile(path)
if err != nil {
@@ -398,7 +396,6 @@ func (c *importCommand) copyJekyllFilesAndFolders(jekyllRoot, dest string, jekyl
}
func (c *importCommand) importFromJekyll(args []string) error {
jekyllRoot, err := filepath.Abs(filepath.Clean(args[0]))
if err != nil {
return newUserError("path error:", args[0])
@@ -429,11 +426,7 @@ func (c *importCommand) importFromJekyll(args []string) error {
c.r.Println("Importing...")
fileCount := 0
callback := func(path string, fi hugofs.FileMetaInfo, err error) error {
if err != nil {
return err
}
callback := func(path string, fi hugofs.FileMetaInfo) error {
if fi.IsDir() {
return nil
}
@@ -462,7 +455,7 @@ func (c *importCommand) importFromJekyll(args []string) error {
for jekyllPostDir, hasAnyPostInDir := range jekyllPostDirs {
if hasAnyPostInDir {
if err = helpers.SymbolicWalk(hugofs.Os, filepath.Join(jekyllRoot, jekyllPostDir), callback); err != nil {
if err = helpers.Walk(hugofs.Os, filepath.Join(jekyllRoot, jekyllPostDir), callback); err != nil {
return err
}
}
+5 -10
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -31,7 +31,6 @@ import (
// newListCommand creates a new list command and its subcommands.
func newListCommand() *listCommand {
createRecord := func(workingDir string, p page.Page) []string {
return []string{
filepath.ToSlash(strings.TrimPrefix(p.File().Filename(), workingDir+string(os.PathSeparator))),
@@ -83,7 +82,6 @@ func newListCommand() *listCommand {
}
return nil
}
return &listCommand{
@@ -94,11 +92,10 @@ func newListCommand() *listCommand {
long: `List all of the drafts in your content directory.`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
shouldInclude := func(p page.Page) bool {
if !p.Draft() || p.File().IsZero() {
if !p.Draft() || p.File() == nil {
return false
}
return true
}
return list(cd, r, shouldInclude,
"buildDrafts", true,
@@ -113,11 +110,10 @@ func newListCommand() *listCommand {
long: `List all of the posts in your content directory which will be posted in the future.`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
shouldInclude := func(p page.Page) bool {
if !resource.IsFuture(p) || p.File().IsZero() {
if !resource.IsFuture(p) || p.File() == nil {
return false
}
return true
}
return list(cd, r, shouldInclude,
"buildFuture", true,
@@ -131,7 +127,7 @@ func newListCommand() *listCommand {
long: `List all of the posts in your content directory which has already expired.`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
shouldInclude := func(p page.Page) bool {
if !resource.IsExpired(p) || p.File().IsZero() {
if !resource.IsExpired(p) || p.File() == nil {
return false
}
return true
@@ -148,14 +144,13 @@ func newListCommand() *listCommand {
long: `List all of the posts in your content directory, include drafts, future and expired pages.`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
shouldInclude := func(p page.Page) bool {
return !p.File().IsZero()
return p.File() != nil
}
return list(cd, r, shouldInclude, "buildDrafts", true, "buildFuture", true, "buildExpired", true)
},
},
},
}
}
type listCommand struct {
+9 -5
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -69,7 +69,7 @@ so this may/will change in future versions of Hugo.
if err != nil {
return err
}
return npm.Pack(h.BaseFs.SourceFs, h.BaseFs.Assets.Dirs)
return npm.Pack(h.BaseFs.ProjectSourceFs, h.BaseFs.AssetsWithDuplicatesPreserved.Fs)
},
},
},
@@ -211,7 +211,12 @@ Install a specific version:
hugo mod get github.com/gohugoio/testshortcodes@v0.3.0
Install the latest versions of all module dependencies:
Install the latest versions of all direct module dependencies:
hugo mod get
hugo mod get ./... (recursive)
Install the latest versions of all module dependencies (direct and indirect):
hugo mod get -u
hugo mod get -u ./... (recursive)
@@ -281,7 +286,6 @@ Run "go help get" for more information. All flags available for "go get" is also
npmCommand,
},
}
}
type modCommands struct {
@@ -303,7 +307,7 @@ func (c *modCommands) Run(ctx context.Context, cd *simplecobra.Commandeer, args
if err != nil {
return err
}
//config := conf.configs.Base
// config := conf.configs.Base
return nil
}
+27 -195
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,19 +16,14 @@ package commands
import (
"bytes"
"context"
"errors"
"fmt"
"path/filepath"
"strings"
"github.com/bep/simplecobra"
"github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/create"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/parser"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/spf13/afero"
"github.com/gohugoio/hugo/create/skeletons"
"github.com/spf13/cobra"
)
@@ -68,9 +63,7 @@ Ensure you run this within the root directory of your site.`,
cmd.Flags().StringVarP(&contentType, "kind", "k", "", "content type to create")
cmd.Flags().String("editor", "", "edit new content with this editor, if provided")
cmd.Flags().BoolVarP(&force, "force", "f", false, "overwrite file if it already exists")
cmd.Flags().StringVar(&format, "format", "toml", "preferred file format (toml, yaml or json)")
applyLocalFlagsBuildConfig(cmd, r)
},
},
&simpleCommand{
@@ -99,52 +92,13 @@ Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
}
sourceFs := conf.fs.Source
archeTypePath := filepath.Join(createpath, "archetypes")
dirs := []string{
archeTypePath,
filepath.Join(createpath, "assets"),
filepath.Join(createpath, "content"),
filepath.Join(createpath, "data"),
filepath.Join(createpath, "layouts"),
filepath.Join(createpath, "static"),
filepath.Join(createpath, "themes"),
err = skeletons.CreateSite(createpath, sourceFs, force, format)
if err != nil {
return err
}
if exists, _ := helpers.Exists(createpath, sourceFs); exists {
if isDir, _ := helpers.IsDir(createpath, sourceFs); !isDir {
return errors.New(createpath + " already exists but not a directory")
}
isEmpty, _ := helpers.IsEmpty(createpath, sourceFs)
switch {
case !isEmpty && !force:
return errors.New(createpath + " already exists and is not empty. See --force.")
case !isEmpty && force:
all := append(dirs, filepath.Join(createpath, "hugo."+format))
for _, path := range all {
if exists, _ := helpers.Exists(path, sourceFs); exists {
return errors.New(path + " already exists")
}
}
}
}
for _, dir := range dirs {
if err := sourceFs.MkdirAll(dir, 0777); err != nil {
return fmt.Errorf("failed to create dir: %w", err)
}
}
c.newSiteCreateConfig(sourceFs, createpath, format)
// Create a default archetype file.
helpers.SafeWriteToDisk(filepath.Join(archeTypePath, "default.md"),
strings.NewReader(create.DefaultArchetypeTemplateTemplate), sourceFs)
r.Printf("Congratulations! Your new Hugo site is created in %s.\n\n", createpath)
r.Println(c.newSiteNextStepsText())
r.Printf("Congratulations! Your new Hugo site was created in %s.\n\n", createpath)
r.Println(c.newSiteNextStepsText(createpath, format))
return nil
},
@@ -165,91 +119,22 @@ according to your needs.`,
if len(args) < 1 {
return newUserError("theme name needs to be provided")
}
h, err := r.Hugo(flagsToCfg(cd, nil))
cfg := config.New()
cfg.Set("publishDir", "public")
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, cfg))
if err != nil {
return err
}
ps := h.PathSpec
sourceFs := ps.Fs.Source
themesDir := h.Configs.LoadingInfo.BaseConfig.ThemesDir
createpath := ps.AbsPathify(filepath.Join(themesDir, args[0]))
r.Println("Creating theme at", createpath)
sourceFs := conf.fs.Source
createpath := paths.AbsPathify(conf.configs.Base.WorkingDir, filepath.Join(conf.configs.Base.ThemesDir, args[0]))
r.Println("Creating new theme in", createpath)
if x, _ := helpers.Exists(createpath, sourceFs); x {
return errors.New(createpath + " already exists")
}
for _, filename := range []string{
"index.html",
"404.html",
"_default/list.html",
"_default/single.html",
"partials/head.html",
"partials/header.html",
"partials/footer.html",
} {
touchFile(sourceFs, filepath.Join(createpath, "layouts", filename))
}
baseofDefault := []byte(`<!DOCTYPE html>
<html>
{{- partial "head.html" . -}}
<body>
{{- partial "header.html" . -}}
<div id="content">
{{- block "main" . }}{{- end }}
</div>
{{- partial "footer.html" . -}}
</body>
</html>
`)
err = helpers.WriteToDisk(filepath.Join(createpath, "layouts", "_default", "baseof.html"), bytes.NewReader(baseofDefault), sourceFs)
err = skeletons.CreateTheme(createpath, sourceFs)
if err != nil {
return err
}
mkdir(createpath, "archetypes")
archDefault := []byte("+++\n+++\n")
err = helpers.WriteToDisk(filepath.Join(createpath, "archetypes", "default.md"), bytes.NewReader(archDefault), sourceFs)
if err != nil {
return err
}
mkdir(createpath, "static", "js")
mkdir(createpath, "static", "css")
by := []byte(`The MIT License (MIT)
Copyright (c) ` + htime.Now().Format("2006") + ` YOUR_NAME_HERE
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
`)
err = helpers.WriteToDisk(filepath.Join(createpath, "LICENSE"), bytes.NewReader(by), sourceFs)
if err != nil {
return err
}
c.createThemeMD(ps.Fs.Source, createpath)
return nil
},
},
@@ -257,7 +142,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
}
return c
}
type newCommand struct {
@@ -299,77 +183,25 @@ func (c *newCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
return nil
}
func (c *newCommand) newSiteCreateConfig(fs afero.Fs, inpath string, kind string) (err error) {
in := map[string]string{
"baseURL": "http://example.org/",
"title": "My New Hugo Site",
"languageCode": "en-us",
}
var buf bytes.Buffer
err = parser.InterfaceToConfig(in, metadecoders.FormatFromString(kind), &buf)
if err != nil {
return err
}
return helpers.WriteToDisk(filepath.Join(inpath, "hugo."+kind), &buf, fs)
}
func (c *newCommand) newSiteNextStepsText() string {
func (c *newCommand) newSiteNextStepsText(path string, format string) string {
format = strings.ToLower(format)
var nextStepsText bytes.Buffer
nextStepsText.WriteString(`Just a few more steps and you're ready to go:
nextStepsText.WriteString(`Just a few more steps...
1. Download a theme into the same-named folder.
Choose a theme from https://themes.gohugo.io/ or
create your own with the "hugo new theme <THEMENAME>" command.
2. Perhaps you want to add some content. You can add single files
with "hugo new `)
1. Change the current directory to ` + path + `.
2. Create or install a theme:
- Create a new theme with the command "hugo new theme <THEMENAME>"
- Install a theme from https://themes.gohugo.io/
3. Edit hugo.` + format + `, setting the "theme" property to the theme name.
4. Create new content with the command "hugo new content `)
nextStepsText.WriteString(filepath.Join("<SECTIONNAME>", "<FILENAME>.<FORMAT>"))
nextStepsText.WriteString(`".
3. Start the built-in live server via "hugo server".
5. Start the embedded web server with the command "hugo server --buildDrafts".
Visit https://gohugo.io/ for quickstart guide and full documentation.`)
See documentation at https://gohugo.io/.`)
return nextStepsText.String()
}
func (c *newCommand) createThemeMD(fs afero.Fs, inpath string) (err error) {
by := []byte(`# theme.toml template for a Hugo theme
# See https://github.com/gohugoio/hugoThemes#themetoml for an example
name = "` + strings.Title(helpers.MakeTitle(filepath.Base(inpath))) + `"
license = "MIT"
licenselink = "https://github.com/yourname/yourtheme/blob/master/LICENSE"
description = ""
homepage = "http://example.com/"
tags = []
features = []
min_version = "0.115.4"
[author]
name = ""
homepage = ""
# If porting an existing theme
[original]
name = ""
homepage = ""
repo = ""
`)
err = helpers.WriteToDisk(filepath.Join(inpath, "theme.toml"), bytes.NewReader(by), fs)
if err != nil {
return
}
err = helpers.WriteToDisk(filepath.Join(inpath, "hugo.toml"), strings.NewReader("# Theme config.\n"), fs)
if err != nil {
return
}
return nil
}
+1 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -24,7 +24,6 @@ import (
// Note: This is a command only meant for internal use and must be run
// via "go run -tags release main.go release" on the actual code base that is in the release.
func newReleaseCommand() simplecobra.Commander {
var (
step int
skipPush bool
+63 -56
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -23,25 +23,24 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
_ "net/http/pprof"
"net/url"
"os"
"sync"
"sync/atomic"
"github.com/bep/mclib"
"os/signal"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
"github.com/bep/mclib"
"github.com/bep/debounce"
"github.com/bep/simplecobra"
"github.com/fsnotify/fsnotify"
@@ -84,10 +83,14 @@ const (
)
func newHugoBuilder(r *rootCommand, s *serverCommand, onConfigLoaded ...func(reloaded bool) error) *hugoBuilder {
var visitedURLs *types.EvictingStringQueue
if s != nil && !s.disableFastRender {
visitedURLs = types.NewEvictingStringQueue(20)
}
return &hugoBuilder{
r: r,
s: s,
visitedURLs: types.NewEvictingStringQueue(100),
visitedURLs: visitedURLs,
fullRebuildSem: semaphore.NewWeighted(1),
debounce: debounce.New(4 * time.Second),
onConfigLoaded: func(reloaded bool) error {
@@ -121,7 +124,6 @@ func newServerCommand() *serverCommand {
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.Flags().BoolVar(&uninstall, "uninstall", false, "Uninstall the local CA (but do not delete it).")
},
},
},
@@ -220,7 +222,7 @@ func (f *fileChangeDetector) filterIrrelevant(in []string) []string {
}
type fileServer struct {
baseURLs []string
baseURLs []urls.BaseURL
roots []string
errorTemplate func(err any) (io.Reader, error)
c *serverCommand
@@ -237,12 +239,14 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
r.Printf("Environment: %q\n", f.c.hugoTry().Deps.Site.Hugo().Environment)
if i == 0 {
if f.c.renderToDisk {
r.Println("Serving pages from disk")
} else if f.c.renderStaticToDisk {
r.Println("Serving pages from memory and static files from disk")
mainTarget := "disk"
if f.c.r.renderToMemory {
mainTarget = "memory"
}
if f.c.renderStaticToDisk {
r.Printf("Serving pages from %s and static files from disk\n", mainTarget)
} else {
r.Println("Serving pages from memory")
r.Printf("Serving pages from %s\n", mainTarget)
}
}
@@ -256,12 +260,6 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
r.Println("Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender")
}
// We're only interested in the path
u, err := url.Parse(baseURL)
if err != nil {
return nil, nil, "", "", fmt.Errorf("invalid baseURL: %w", err)
}
decorate := func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if f.c.showErrorInBrowser {
@@ -281,7 +279,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
port = lrport
}
})
lr := *u
lr := baseURL.URL()
lr.Host = fmt.Sprintf("%s:%d", lr.Hostname(), port)
fmt.Fprint(w, injectLiveReloadScript(r, lr))
@@ -312,7 +310,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
// This matches Netlify's behaviour and is needed for SPA behaviour.
// See https://docs.netlify.com/routing/redirects/rewrites-proxies/
if !redirect.Force {
path := filepath.Clean(strings.TrimPrefix(requestURI, u.Path))
path := filepath.Clean(strings.TrimPrefix(requestURI, baseURL.Path()))
if root != "" {
path = filepath.Join(root, path)
}
@@ -339,7 +337,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
switch redirect.Status {
case 404:
w.WriteHeader(404)
file, err := fs.Open(strings.TrimPrefix(redirect.To, u.Path))
file, err := fs.Open(strings.TrimPrefix(redirect.To, baseURL.Path()))
if err == nil {
defer file.Close()
io.Copy(w, file)
@@ -348,7 +346,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
}
return
case 200:
if r2 := f.rewriteRequest(r, strings.TrimPrefix(redirect.To, u.Path)); r2 != nil {
if r2 := f.rewriteRequest(r, strings.TrimPrefix(redirect.To, baseURL.Path())); r2 != nil {
requestURI = redirect.To
r = r2
}
@@ -386,10 +384,10 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
fileserver := decorate(http.FileServer(fs))
mu := http.NewServeMux()
if u.Path == "" || u.Path == "/" {
if baseURL.Path() == "" || baseURL.Path() == "/" {
mu.Handle("/", fileserver)
} else {
mu.Handle(u.Path, http.StripPrefix(u.Path, fileserver))
mu.Handle(baseURL.Path(), http.StripPrefix(baseURL.Path(), fileserver))
}
if r.IsTestRun() {
var shutDownOnce sync.Once
@@ -402,7 +400,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
endpoint := net.JoinHostPort(f.c.serverInterface, strconv.Itoa(port))
return mu, listener, u.String(), endpoint, nil
return mu, listener, baseURL.String(), endpoint, nil
}
func (f *fileServer) rewriteRequest(r *http.Request, toPath string) *http.Request {
@@ -448,7 +446,6 @@ type serverCommand struct {
doLiveReload bool
// Flags.
renderToDisk bool
renderStaticToDisk bool
navigateToChanged bool
serverAppend bool
@@ -456,6 +453,7 @@ type serverCommand struct {
tlsCertFile string
tlsKeyFile string
tlsAuto bool
pprof bool
serverPort int
liveReloadPort int
serverWatch bool
@@ -470,7 +468,11 @@ func (c *serverCommand) Name() string {
}
func (c *serverCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
if c.pprof {
go func() {
http.ListenAndServe("localhost:8080", nil)
}()
}
// Watch runs its own server as part of the routine
if c.serverWatch {
@@ -493,15 +495,19 @@ func (c *serverCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, arg
}
var close func()
err := func() error {
defer c.r.timeTrack(time.Now(), "Built")
err := c.build()
var err error
close, err = c.build()
return err
}()
if err != nil {
return err
}
defer close()
return c.serve()
}
@@ -511,8 +517,9 @@ func (c *serverCommand) Init(cd *simplecobra.Commandeer) error {
cmd.Long = `Hugo provides its own webserver which builds and serves the site.
While hugo server is high performance, it is a webserver with limited options.
'hugo server' will avoid writing the rendered and served content to disk,
preferring to store it in memory.
'hugo server' will by default write and server files from disk, but you can
render to memory by using the '--renderToMemory' flag. This can be faster
in some cases, but it will consume more memory.
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
@@ -526,12 +533,12 @@ of a second, you will be able to save and see your changes nearly instantly.`
cmd.Flags().StringVarP(&c.tlsCertFile, "tlsCertFile", "", "", "path to TLS certificate file")
cmd.Flags().StringVarP(&c.tlsKeyFile, "tlsKeyFile", "", "", "path to TLS key file")
cmd.Flags().BoolVar(&c.tlsAuto, "tlsAuto", false, "generate and use locally-trusted certificates.")
cmd.Flags().BoolVar(&c.pprof, "pprof", false, "enable the pprof server (port 8080)")
cmd.Flags().BoolVarP(&c.serverWatch, "watch", "w", true, "watch filesystem for changes and recreate as needed")
cmd.Flags().BoolVar(&c.noHTTPCache, "noHTTPCache", false, "prevent HTTP caching")
cmd.Flags().BoolVarP(&c.serverAppend, "appendPort", "", true, "append port to baseURL")
cmd.Flags().BoolVar(&c.disableLiveReload, "disableLiveReload", false, "watch without enabling live browser reload on rebuild")
cmd.Flags().BoolVar(&c.navigateToChanged, "navigateToChanged", false, "navigate to changed content file on live browser reload")
cmd.Flags().BoolVar(&c.renderToDisk, "renderToDisk", false, "serve all files from disk (default is from memory)")
cmd.Flags().BoolVar(&c.renderStaticToDisk, "renderStaticToDisk", false, "serve static files from disk and dynamic files from memory")
cmd.Flags().BoolVar(&c.disableFastRender, "disableFastRender", false, "enables full re-renders on changes")
cmd.Flags().BoolVar(&c.disableBrowserError, "disableBrowserError", false, "do not show build errors in the browser")
@@ -583,7 +590,9 @@ func (c *serverCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
)
destinationFlag := cd.CobraCommand.Flags().Lookup("destination")
c.renderToDisk = c.renderToDisk || (destinationFlag != nil && destinationFlag.Changed)
if c.r.renderToMemory && (destinationFlag != nil && destinationFlag.Changed) {
return fmt.Errorf("cannot use --renderToMemory with --destination")
}
c.doLiveReload = !c.disableLiveReload
c.fastRenderMode = !c.disableFastRender
c.showErrorInBrowser = c.doLiveReload && !c.disableBrowserError
@@ -677,7 +686,7 @@ func (c *serverCommand) createCertificates(conf *commonConfig) error {
// Create the directory if it doesn't exist.
if _, err := os.Stat(keyDir); os.IsNotExist(err) {
if err := os.MkdirAll(keyDir, 0777); err != nil {
if err := os.MkdirAll(keyDir, 0o777); err != nil {
return err
}
}
@@ -686,9 +695,9 @@ func (c *serverCommand) createCertificates(conf *commonConfig) error {
c.tlsKeyFile = filepath.Join(keyDir, fmt.Sprintf("%s-key.pem", hostname))
// Check if the certificate already exists and is valid.
certPEM, err := ioutil.ReadFile(c.tlsCertFile)
certPEM, err := os.ReadFile(c.tlsCertFile)
if err == nil {
rootPem, err := ioutil.ReadFile(filepath.Join(mclib.GetCAROOT(), "rootCA.pem"))
rootPem, err := os.ReadFile(filepath.Join(mclib.GetCAROOT(), "rootCA.pem"))
if err == nil {
if err := c.verifyCert(rootPem, certPEM, hostname); err == nil {
c.r.Println("Using existing", c.tlsCertFile, "and", c.tlsKeyFile)
@@ -702,7 +711,6 @@ func (c *serverCommand) createCertificates(conf *commonConfig) error {
// Yes, this is unfortunate, but it's currently the only way to use Mkcert as a library.
os.Args = []string{"-cert-file", c.tlsCertFile, "-key-file", c.tlsKeyFile, hostname}
return mclib.RunMain()
}
func (c *serverCommand) verifyCert(rootPEM, certPEM []byte, name string) error {
@@ -832,9 +840,9 @@ func (c *serverCommand) partialReRender(urls ...string) error {
c.errState.setWasErr(false)
}()
c.errState.setBuildErr(nil)
visited := make(map[string]bool)
visited := types.NewEvictingStringQueue(len(urls))
for _, url := range urls {
visited[url] = true
visited.Add(url)
}
h, err := c.hugo()
@@ -847,7 +855,7 @@ func (c *serverCommand) partialReRender(urls ...string) error {
func (c *serverCommand) serve() error {
var (
baseURLs []string
baseURLs []urls.BaseURL
roots []string
h *hugolib.HugoSites
)
@@ -864,18 +872,17 @@ func (c *serverCommand) serve() error {
if isMultiHost {
for _, l := range conf.configs.ConfigLangs() {
baseURLs = append(baseURLs, l.BaseURL().String())
baseURLs = append(baseURLs, l.BaseURL())
roots = append(roots, l.Language().Lang)
}
} else {
l := conf.configs.GetFirstLanguageConfig()
baseURLs = []string{l.BaseURL().String()}
baseURLs = []urls.BaseURL{l.BaseURL()}
roots = []string{""}
}
return nil
})
if err != nil {
return err
}
@@ -947,15 +954,11 @@ func (c *serverCommand) serve() error {
servers = append(servers, srv)
if doLiveReload {
u, err := url.Parse(helpers.SanitizeURL(baseURLs[i]))
if err != nil {
return err
}
mu.HandleFunc(u.Path+"/livereload.js", livereload.ServeJS)
mu.HandleFunc(u.Path+"/livereload", livereload.Handler)
baseURL := baseURLs[i]
mu.HandleFunc(baseURL.Path()+"livereload.js", livereload.ServeJS)
mu.HandleFunc(baseURL.Path()+"livereload", livereload.Handler)
}
c.r.Printf("Web Server is available at %s (bind address %s)\n", serverURL, c.serverInterface)
c.r.Printf("Web Server is available at %s (bind address %s) %s\n", serverURL, c.serverInterface, roots[i])
wg1.Go(func() error {
if c.tlsCertFile != "" && c.tlsKeyFile != "" {
err = srv.ServeTLS(listener, c.tlsCertFile, c.tlsKeyFile)
@@ -972,8 +975,12 @@ func (c *serverCommand) serve() error {
if c.r.IsTestRun() {
// Write a .ready file to disk to signal ready status.
// This is where the test is run from.
var baseURLs []string
for _, baseURL := range srv.baseURLs {
baseURLs = append(baseURLs, baseURL.String())
}
testInfo := map[string]any{
"baseURLs": srv.baseURLs,
"baseURLs": baseURLs,
}
dir := os.Getenv("WORK")
@@ -984,7 +991,7 @@ func (c *serverCommand) serve() error {
if err != nil {
return err
}
err = os.WriteFile(readyFile, b, 0777)
err = os.WriteFile(readyFile, b, 0o777)
if err != nil {
return err
}
@@ -1168,7 +1175,7 @@ func cleanErrorLog(content string) string {
return strings.Join(keep, ": ")
}
func injectLiveReloadScript(src io.Reader, baseURL url.URL) string {
func injectLiveReloadScript(src io.Reader, baseURL *url.URL) string {
var b bytes.Buffer
chain := transform.Chain{livereloadinject.New(baseURL)}
chain.Apply(&b, src)
+7 -5
View File
@@ -55,7 +55,7 @@ func TestAppend(t *testing.T) {
[]any{&tstSlicerIn1{"c"}},
testSlicerInterfaces{&tstSlicerIn1{"a"}, &tstSlicerIn1{"b"}, &tstSlicerIn1{"c"}},
},
//https://github.com/gohugoio/hugo/issues/5361
// https://github.com/gohugoio/hugo/issues/5361
{
[]string{"a", "b"},
[]any{tstSlicers{&tstSlicer{"a"}, &tstSlicer{"b"}}},
@@ -102,14 +102,16 @@ func TestAppendToMultiDimensionalSlice(t *testing.T) {
from []any
expected any
}{
{[][]string{{"a", "b"}},
{
[][]string{{"a", "b"}},
[]any{[]string{"c", "d"}},
[][]string{
{"a", "b"},
{"c", "d"},
},
},
{[][]string{{"a", "b"}},
{
[][]string{{"a", "b"}},
[]any{[]string{"c", "d"}, []string{"e", "f"}},
[][]string{
{"a", "b"},
@@ -117,7 +119,8 @@ func TestAppendToMultiDimensionalSlice(t *testing.T) {
{"e", "f"},
},
},
{[][]string{{"a", "b"}},
{
[][]string{{"a", "b"}},
[]any{[]int{1, 2}},
false,
},
@@ -130,7 +133,6 @@ func TestAppendToMultiDimensionalSlice(t *testing.T) {
c.Assert(result, qt.DeepEquals, test.expected)
}
}
}
func TestAppendShouldMakeACopyOfTheInputSlice(t *testing.T) {
-1
View File
@@ -73,7 +73,6 @@ func StringSliceToInterfaceSlice(ss []string) []any {
result[i] = s
}
return result
}
type SortedStringSlice []string
-1
View File
@@ -135,5 +135,4 @@ func TestSortedStringSlice(t *testing.T) {
c.Assert(s.Count("b"), qt.Equals, 3)
c.Assert(s.Count("z"), qt.Equals, 0)
c.Assert(s.Count("a"), qt.Equals, 1)
}
+67
View File
@@ -0,0 +1,67 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package collections
import "sync"
// Stack is a simple LIFO stack that is safe for concurrent use.
type Stack[T any] struct {
items []T
zero T
mu sync.RWMutex
}
func NewStack[T any]() *Stack[T] {
return &Stack[T]{}
}
func (s *Stack[T]) Push(item T) {
s.mu.Lock()
defer s.mu.Unlock()
s.items = append(s.items, item)
}
func (s *Stack[T]) Pop() (T, bool) {
s.mu.Lock()
defer s.mu.Unlock()
if len(s.items) == 0 {
return s.zero, false
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item, true
}
func (s *Stack[T]) Peek() (T, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
if len(s.items) == 0 {
return s.zero, false
}
return s.items[len(s.items)-1], true
}
func (s *Stack[T]) Len() int {
s.mu.RLock()
defer s.mu.RUnlock()
return len(s.items)
}
func (s *Stack[T]) Drain() []T {
s.mu.Lock()
defer s.mu.Unlock()
items := s.items
s.items = nil
return items
}
+24 -4
View File
@@ -13,13 +13,33 @@
package constants
// Error IDs.
// Error/Warning IDs.
// Do not change these values.
const (
ErrIDAmbigousDisableKindTaxonomy = "error-disable-taxonomy"
ErrIDAmbigousOutputKindTaxonomy = "error-output-taxonomy"
// IDs for remote errors in tpl/data.
ErrRemoteGetJSON = "error-remote-getjson"
ErrRemoteGetCSV = "error-remote-getcsv"
WarnFrontMatterParamsOverrides = "warning-frontmatter-params-overrides"
)
// Field/method names with special meaning.
const (
FieldRelPermalink = "RelPermalink"
FieldPermalink = "Permalink"
)
// IsFieldRelOrPermalink returns whether the given name is a RelPermalink or Permalink.
func IsFieldRelOrPermalink(name string) bool {
return name == FieldRelPermalink || name == FieldPermalink
}
// Resource transformations.
const (
ResourceTransformationFingerprint = "fingerprint"
)
// IsResourceTransformationLinkChange returns whether the given name is a resource transformation that changes the permalink based on the content.
func IsResourceTransformationPermalinkHash(name string) bool {
return name == ResourceTransformationFingerprint
}
+46
View File
@@ -0,0 +1,46 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hcontext
import "context"
// ContextDispatcher is a generic interface for setting and getting values from a context.
type ContextDispatcher[T any] interface {
Set(ctx context.Context, value T) context.Context
Get(ctx context.Context) T
}
// NewContextDispatcher creates a new ContextDispatcher with the given key.
func NewContextDispatcher[T any, R comparable](key R) ContextDispatcher[T] {
return keyInContext[T, R]{
id: key,
}
}
type keyInContext[T any, R comparable] struct {
zero T
id R
}
func (f keyInContext[T, R]) Get(ctx context.Context) T {
v := ctx.Value(f.id)
if v == nil {
return f.zero
}
return v.(T)
}
func (f keyInContext[T, R]) Set(ctx context.Context, value T) context.Context {
return context.WithValue(ctx, f.id, value)
}
+1 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2022 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -74,7 +74,6 @@ func ContainsMatcher(text string) func(m LineMatcher) int {
// ErrorContext contains contextual information about an error. This will
// typically be the lines surrounding some problem in a file.
type ErrorContext struct {
// If a match will contain the matched line and up to 2 lines before and after.
// Will be empty if no match.
Lines []string
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2022 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
+39 -11
View File
@@ -1,4 +1,4 @@
// Copyright 2022 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,14 +15,15 @@
package herrors
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"regexp"
"runtime"
"runtime/debug"
"strconv"
"strings"
"time"
)
// PrintStackTrace prints the current stacktrace to w.
@@ -49,14 +50,22 @@ func Recover(args ...any) {
}
}
// GetGID the current goroutine id. Used only for debugging.
func GetGID() uint64 {
b := make([]byte, 64)
b = b[:runtime.Stack(b, false)]
b = bytes.TrimPrefix(b, []byte("goroutine "))
b = b[:bytes.IndexByte(b, ' ')]
n, _ := strconv.ParseUint(string(b), 10, 64)
return n
// IsTimeoutError returns true if the given error is or contains a TimeoutError.
func IsTimeoutError(err error) bool {
return errors.Is(err, &TimeoutError{})
}
type TimeoutError struct {
Duration time.Duration
}
func (e *TimeoutError) Error() string {
return fmt.Sprintf("timeout after %s", e.Duration)
}
func (e *TimeoutError) Is(target error) bool {
_, ok := target.(*TimeoutError)
return ok
}
// IsFeatureNotAvailableError returns true if the given error is or contains a FeatureNotAvailableError.
@@ -109,3 +118,22 @@ func IsNotExist(err error) bool {
return false
}
var nilPointerErrRe = regexp.MustCompile(`at <(.*)>: error calling (.*?): runtime error: invalid memory address or nil pointer dereference`)
func ImproveIfNilPointer(inErr error) (outErr error) {
outErr = inErr
m := nilPointerErrRe.FindStringSubmatch(inErr.Error())
if len(m) == 0 {
return
}
call := m[1]
field := m[2]
parts := strings.Split(call, ".")
receiverName := parts[len(parts)-2]
receiver := strings.Join(parts[:len(parts)-1], ".")
s := fmt.Sprintf(" %s is nil; wrap it in if or with: {{ with %s }}{{ .%s }}{{ end }}", receiverName, receiver, field)
outErr = errors.New(nilPointerErrRe.ReplaceAllString(inErr.Error(), s))
return
}
+1 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2022 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -42,5 +42,4 @@ func TestIsFeatureNotAvailableError(t *testing.T) {
c.Assert(IsFeatureNotAvailableError(ErrFeatureNotAvailable), qt.Equals, true)
c.Assert(IsFeatureNotAvailableError(&FeatureNotAvailableError{}), qt.Equals, true)
c.Assert(IsFeatureNotAvailableError(errors.New("asdf")), qt.Equals, false)
}
+12 -10
View File
@@ -1,4 +1,4 @@
// Copyright 2022 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,13 +15,13 @@ package herrors
import (
"encoding/json"
godartsassv1 "github.com/bep/godartsass"
"errors"
"fmt"
"io"
"path/filepath"
godartsassv1 "github.com/bep/godartsass"
"github.com/bep/godartsass/v2"
"github.com/bep/golibsass/libsass/libsasserrors"
"github.com/gohugoio/hugo/common/paths"
@@ -29,8 +29,6 @@ import (
"github.com/pelletier/go-toml/v2"
"github.com/spf13/afero"
"github.com/tdewolff/parse/v2"
"errors"
)
// FileError represents an error when handling a file: Parsing a config file,
@@ -48,6 +46,9 @@ type FileError interface {
// UpdateContent updates the error with a new ErrorContext from the content of the file.
UpdateContent(r io.Reader, linematcher LineMatcherFn) FileError
// SetFilename sets the filename of the error.
SetFilename(filename string) FileError
}
// Unwrapper can unwrap errors created with fmt.Errorf.
@@ -60,6 +61,11 @@ var (
_ Unwrapper = (*fileError)(nil)
)
func (fe *fileError) SetFilename(filename string) FileError {
fe.position.Filename = filename
return fe
}
func (fe *fileError) UpdatePosition(pos text.Position) FileError {
oldFilename := fe.Position().Filename
if pos.Filename != "" && fe.fileType == "" {
@@ -115,7 +121,6 @@ func (fe *fileError) UpdateContent(r io.Reader, linematcher LineMatcherFn) FileE
}
return fe
}
type fileError struct {
@@ -181,7 +186,6 @@ func NewFileErrorFromName(err error, name string) FileError {
}
return &fileError{cause: err, fileType: fileType, position: pos}
}
// NewFileErrorFromPos will use the filename and line number from pos to create a new FileError, wrapping err.
@@ -192,7 +196,6 @@ func NewFileErrorFromPos(err error, pos text.Position) FileError {
_, fileType = paths.FileAndExtNoDelimiter(filepath.Clean(pos.Filename))
}
return &fileError{cause: err, fileType: fileType, position: pos}
}
func NewFileErrorFromFileInErr(err error, fs afero.Fs, linematcher LineMatcherFn) FileError {
@@ -249,7 +252,6 @@ func openFile(filename string, fs afero.Fs) (afero.File, string, error) {
}); ok {
realFilename = s.Filename()
}
}
f, err2 := fs.Open(filename)
+2 -4
View File
@@ -1,4 +1,4 @@
// Copyright 2022 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,12 +14,11 @@
package herrors
import (
"errors"
"fmt"
"strings"
"testing"
"errors"
"github.com/gohugoio/hugo/common/text"
qt "github.com/frankban/quicktest"
@@ -48,7 +47,6 @@ func TestNewFileError(t *testing.T) {
c.Assert(errorContext.Lines, qt.DeepEquals, []string{"line 30", "line 31", "line 32", "line 33", "line 34"})
c.Assert(errorContext.LinesPos, qt.Equals, 2)
c.Assert(errorContext.ChromaLexer, qt.Equals, "go-html-template")
}
func TestNewFileErrorExtractFromMessage(t *testing.T) {
+2 -4
View File
@@ -19,11 +19,10 @@ import (
"errors"
"fmt"
"io"
"regexp"
"strings"
"os"
"os/exec"
"regexp"
"strings"
"github.com/cli/safeexec"
"github.com/gohugoio/hugo/config"
@@ -142,7 +141,6 @@ func (e *Exec) New(name string, arg ...any) (Runner, error) {
}
return cm.command(arg...)
}
// Npx is a convenience method to create a Runner running npx --no-install <name> <args.
+38 -3
View File
@@ -1,4 +1,4 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
// Some functions in this file (see comments) is based on the Go source code,
// copyright The Go Authors and governed by a BSD-style license.
//
@@ -23,6 +23,7 @@ import (
"time"
"github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/types"
)
@@ -188,6 +189,20 @@ func IsTime(tp reflect.Type) bool {
return false
}
// IsValid returns whether v is not nil and a valid value.
func IsValid(v reflect.Value) bool {
if !v.IsValid() {
return false
}
switch v.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
return !v.IsNil()
}
return true
}
// AsTime returns v as a time.Time if possible.
// The given location is only used if the value implements AsTimeProvider (e.g. go-toml local).
// A zero Time and false is returned if this isn't possible.
@@ -217,7 +232,7 @@ func CallMethodByName(cxt context.Context, name string, v reflect.Value) []refle
panic("not supported")
}
first := tp.In(0)
if first.Implements(ContextInterface) {
if IsContextType(first) {
args = append(args, reflect.ValueOf(cxt))
}
}
@@ -236,4 +251,24 @@ func indirectInterface(v reflect.Value) reflect.Value {
return v.Elem()
}
var ContextInterface = reflect.TypeOf((*context.Context)(nil)).Elem()
var contextInterface = reflect.TypeOf((*context.Context)(nil)).Elem()
var isContextCache = maps.NewCache[reflect.Type, bool]()
type k string
var contextTypeValue = reflect.TypeOf(context.WithValue(context.Background(), k("key"), 32))
// IsContextType returns whether tp is a context.Context type.
func IsContextType(tp reflect.Type) bool {
if tp == contextTypeValue {
return true
}
if tp == contextInterface {
return true
}
return isContextCache.GetOrCreate(tp, func() bool {
return tp.Implements(contextInterface)
})
}
+37
View File
@@ -14,6 +14,7 @@
package hreflect
import (
"context"
"reflect"
"testing"
"time"
@@ -40,6 +41,42 @@ func TestGetMethodByName(t *testing.T) {
c.Assert(GetMethodIndexByName(tp, "Foo"), qt.Equals, -1)
}
func TestIsContextType(t *testing.T) {
c := qt.New(t)
type k string
ctx := context.Background()
valueCtx := context.WithValue(ctx, k("key"), 32)
c.Assert(IsContextType(reflect.TypeOf(ctx)), qt.IsTrue)
c.Assert(IsContextType(reflect.TypeOf(valueCtx)), qt.IsTrue)
}
func BenchmarkIsContextType(b *testing.B) {
type k string
b.Run("value", func(b *testing.B) {
ctx := context.Background()
ctxs := make([]reflect.Type, b.N)
for i := 0; i < b.N; i++ {
ctxs[i] = reflect.TypeOf(context.WithValue(ctx, k("key"), i))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if !IsContextType(ctxs[i]) {
b.Fatal("not context")
}
}
})
b.Run("background", func(b *testing.B) {
var ctxt reflect.Type = reflect.TypeOf(context.Background())
for i := 0; i < b.N; i++ {
if !IsContextType(ctxt) {
b.Fatal("not context")
}
}
})
}
func BenchmarkIsTruthFul(b *testing.B) {
v := reflect.ValueOf("Hugo")
+73 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,7 +15,9 @@ package hstrings
import (
"fmt"
"regexp"
"strings"
"sync"
"github.com/gohugoio/hugo/compare"
)
@@ -55,3 +57,73 @@ func EqualAny(a string, b ...string) bool {
}
return false
}
// regexpCache represents a cache of regexp objects protected by a mutex.
type regexpCache struct {
mu sync.RWMutex
re map[string]*regexp.Regexp
}
func (rc *regexpCache) getOrCompileRegexp(pattern string) (re *regexp.Regexp, err error) {
var ok bool
if re, ok = rc.get(pattern); !ok {
re, err = regexp.Compile(pattern)
if err != nil {
return nil, err
}
rc.set(pattern, re)
}
return re, nil
}
func (rc *regexpCache) get(key string) (re *regexp.Regexp, ok bool) {
rc.mu.RLock()
re, ok = rc.re[key]
rc.mu.RUnlock()
return
}
func (rc *regexpCache) set(key string, re *regexp.Regexp) {
rc.mu.Lock()
rc.re[key] = re
rc.mu.Unlock()
}
var reCache = regexpCache{re: make(map[string]*regexp.Regexp)}
// GetOrCompileRegexp retrieves a regexp object from the cache based upon the pattern.
// If the pattern is not found in the cache, the pattern is compiled and added to
// the cache.
func GetOrCompileRegexp(pattern string) (re *regexp.Regexp, err error) {
return reCache.getOrCompileRegexp(pattern)
}
// InSlice checks if a string is an element of a slice of strings
// and returns a boolean value.
func InSlice(arr []string, el string) bool {
for _, v := range arr {
if v == el {
return true
}
}
return false
}
// InSlicEqualFold checks if a string is an element of a slice of strings
// and returns a boolean value.
// It uses strings.EqualFold to compare.
func InSlicEqualFold(arr []string, el string) bool {
for _, v := range arr {
if strings.EqualFold(v, el) {
return true
}
}
return false
}
type Tuple struct {
First string
Second string
}
+22 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,6 +14,7 @@
package hstrings
import (
"regexp"
"testing"
qt "github.com/frankban/quicktest"
@@ -32,5 +33,24 @@ func TestStringEqualFold(t *testing.T) {
c.Assert(StringEqualFold(s1).EqualFold("b"), qt.Equals, false)
c.Assert(StringEqualFold(s1).Eq(s2), qt.Equals, true)
c.Assert(StringEqualFold(s1).Eq("b"), qt.Equals, false)
}
func TestGetOrCompileRegexp(t *testing.T) {
c := qt.New(t)
re, err := GetOrCompileRegexp(`\d+`)
c.Assert(err, qt.IsNil)
c.Assert(re.MatchString("123"), qt.Equals, true)
}
func BenchmarkGetOrCompileRegexp(b *testing.B) {
for i := 0; i < b.N; i++ {
GetOrCompileRegexp(`\d+`)
}
}
func BenchmarkCompileRegexp(b *testing.B) {
for i := 0; i < b.N; i++ {
regexp.MustCompile(`\d+`)
}
}
@@ -1,4 +1,4 @@
// Copyright 2022 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -47,12 +47,7 @@ defaultContentLanguage = 'it'
{{ end }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b := hugolib.Test(t, files)
b.AssertFileContent("public/index.html", `
month: _gennaio_ weekday: _lunedì_
-4
View File
@@ -53,7 +53,6 @@ func TestTimeFormatter(t *testing.T) {
c.Assert(f.Format(june06, ":time_long"), qt.Equals, "02:09:37 UTC")
c.Assert(f.Format(june06, ":time_medium"), qt.Equals, "02:09:37")
c.Assert(f.Format(june06, ":time_short"), qt.Equals, "02:09")
})
c.Run("Custom layouts English", func(c *qt.C) {
@@ -68,7 +67,6 @@ func TestTimeFormatter(t *testing.T) {
c.Assert(f.Format(june06, ":time_long"), qt.Equals, "2:09:37 am UTC")
c.Assert(f.Format(june06, ":time_medium"), qt.Equals, "2:09:37 am")
c.Assert(f.Format(june06, ":time_short"), qt.Equals, "2:09 am")
})
c.Run("English", func(c *qt.C) {
@@ -107,9 +105,7 @@ func TestTimeFormatter(t *testing.T) {
c.Assert(tr.MonthWide(date.Month()), qt.Equals, monthWideNorway)
c.Assert(f.Format(date, "January"), qt.Equals, monthWideNorway)
}
})
}
func BenchmarkTimeFormatter(b *testing.B) {
+7 -2
View File
@@ -16,6 +16,7 @@ package hugio
import (
"fmt"
"io"
iofs "io/fs"
"path/filepath"
"github.com/spf13/afero"
@@ -60,12 +61,16 @@ func CopyDir(fs afero.Fs, from, to string, shouldCopy func(filename string) bool
return fmt.Errorf("%q is not a directory", from)
}
err = fs.MkdirAll(to, 0777) // before umask
err = fs.MkdirAll(to, 0o777) // before umask
if err != nil {
return err
}
entries, _ := afero.ReadDir(fs, from)
d, err := fs.Open(from)
if err != nil {
return err
}
entries, _ := d.(iofs.ReadDirFile).ReadDir(-1)
for _, entry := range entries {
fromFilename := filepath.Join(from, entry.Name())
toFilename := filepath.Join(to, entry.Name())
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2022 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2022 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
+55 -8
View File
@@ -14,6 +14,7 @@
package hugio
import (
"bytes"
"io"
"strings"
)
@@ -36,24 +37,70 @@ type ReadSeekCloserProvider interface {
ReadSeekCloser() (ReadSeekCloser, error)
}
// ReadSeekerNoOpCloser implements ReadSeekCloser by doing nothing in Close.
// TODO(bep) rename this and similar to ReadSeekerNopCloser, naming used in stdlib, which kind of makes sense.
type ReadSeekerNoOpCloser struct {
// readSeekerNopCloser implements ReadSeekCloser by doing nothing in Close.
type readSeekerNopCloser struct {
ReadSeeker
}
// Close does nothing.
func (r ReadSeekerNoOpCloser) Close() error {
func (r readSeekerNopCloser) Close() error {
return nil
}
// NewReadSeekerNoOpCloser creates a new ReadSeekerNoOpCloser with the given ReadSeeker.
func NewReadSeekerNoOpCloser(r ReadSeeker) ReadSeekerNoOpCloser {
return ReadSeekerNoOpCloser{r}
func NewReadSeekerNoOpCloser(r ReadSeeker) ReadSeekCloser {
return readSeekerNopCloser{r}
}
// NewReadSeekerNoOpCloserFromString uses strings.NewReader to create a new ReadSeekerNoOpCloser
// from the given string.
func NewReadSeekerNoOpCloserFromString(content string) ReadSeekerNoOpCloser {
return ReadSeekerNoOpCloser{strings.NewReader(content)}
func NewReadSeekerNoOpCloserFromString(content string) ReadSeekCloser {
return stringReadSeeker{s: content, readSeekerNopCloser: readSeekerNopCloser{strings.NewReader(content)}}
}
var _ StringReader = (*stringReadSeeker)(nil)
type stringReadSeeker struct {
s string
readSeekerNopCloser
}
func (s *stringReadSeeker) ReadString() string {
return s.s
}
// StringReader provides a way to read a string.
type StringReader interface {
ReadString() string
}
// NewReadSeekerNoOpCloserFromString uses strings.NewReader to create a new ReadSeekerNoOpCloser
// from the given bytes slice.
func NewReadSeekerNoOpCloserFromBytes(content []byte) readSeekerNopCloser {
return readSeekerNopCloser{bytes.NewReader(content)}
}
// NewReadSeekCloser creates a new ReadSeekCloser from the given ReadSeeker.
// The ReadSeeker will be seeked to the beginning before returned.
func NewOpenReadSeekCloser(r ReadSeekCloser) OpenReadSeekCloser {
return func() (ReadSeekCloser, error) {
r.Seek(0, io.SeekStart)
return r, nil
}
}
// OpenReadSeekCloser allows setting some other way (than reading from a filesystem)
// to open or create a ReadSeekCloser.
type OpenReadSeekCloser func() (ReadSeekCloser, error)
// ReadString reads from the given reader and returns the content as a string.
func ReadString(r io.Reader) (string, error) {
if sr, ok := r.(StringReader); ok {
return sr.ReadString(), nil
}
b, err := io.ReadAll(r)
if err != nil {
return "", err
}
return string(b), nil
}
+72 -8
View File
@@ -22,18 +22,21 @@ import (
"sort"
"strings"
"sync"
"time"
godartsassv1 "github.com/bep/godartsass"
"github.com/bep/logg"
"github.com/mitchellh/mapstructure"
"time"
"github.com/bep/godartsass/v2"
"github.com/gohugoio/hugo/common/hexec"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/spf13/afero"
iofs "io/fs"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/hugofs"
)
@@ -78,10 +81,22 @@ func (i HugoInfo) Generator() template.HTML {
return template.HTML(fmt.Sprintf(`<meta name="generator" content="Hugo %s">`, CurrentVersion.String()))
}
// IsDevelopment reports whether the current running environment is "development".
func (i HugoInfo) IsDevelopment() bool {
return i.Environment == EnvironmentDevelopment
}
// IsProduction reports whether the current running environment is "production".
func (i HugoInfo) IsProduction() bool {
return i.Environment == EnvironmentProduction
}
// IsServer reports whether the built-in server is running.
func (i HugoInfo) IsServer() bool {
return i.conf.Running()
}
// IsExtended reports whether the Hugo binary is the extended version.
func (i HugoInfo) IsExtended() bool {
return IsExtended
}
@@ -99,6 +114,7 @@ func (i HugoInfo) Deps() []*Dependency {
// ConfigProvider represents the config options that are relevant for HugoInfo.
type ConfigProvider interface {
Environment() string
Running() bool
WorkingDir() string
}
@@ -145,7 +161,12 @@ func GetExecEnviron(workDir string, cfg config.AllProvider, fs afero.Fs) []strin
config.SetEnvVars(&env, "HUGO_PUBLISHDIR", filepath.Join(workDir, cfg.BaseConfig().PublishDir))
if fs != nil {
fis, err := afero.ReadDir(fs, files.FolderJSConfig)
var fis []iofs.DirEntry
d, err := fs.Open(files.FolderJSConfig)
if err == nil {
fis, err = d.(iofs.ReadDirFile).ReadDir(-1)
}
if err == nil {
for _, fi := range fis {
key := fmt.Sprintf("HUGO_FILE_%s", strings.ReplaceAll(strings.ToUpper(fi.Name()), ".", "_"))
@@ -170,8 +191,10 @@ type buildInfo struct {
*debug.BuildInfo
}
var bInfo *buildInfo
var bInfoInit sync.Once
var (
bInfo *buildInfo
bInfoInit sync.Once
)
func getBuildInfo() *buildInfo {
bInfoInit.Do(func() {
@@ -198,7 +221,6 @@ func getBuildInfo() *buildInfo {
bInfo.GoArch = s.Value
}
}
})
return bInfo
@@ -237,12 +259,12 @@ func GetDependencyListNonGo() []string {
deps = append(
deps,
formatDep("github.com/sass/libsass", "3.6.5"),
formatDep("github.com/webmproject/libwebp", "v1.2.4"),
formatDep("github.com/webmproject/libwebp", "v1.3.2"),
)
}
if dartSass := dartSassVersion(); dartSass.ProtocolVersion != "" {
var dartSassPath = "github.com/sass/dart-sass-embedded"
dartSassPath := "github.com/sass/dart-sass-embedded"
if IsDartSassV2() {
dartSassPath = "github.com/sass/dart-sass"
}
@@ -333,3 +355,45 @@ var (
func IsDartSassV2() bool {
return !strings.Contains(DartSassBinaryName, "embedded")
}
// Deprecate informs about a deprecation starting at the given version.
//
// A deprecation typically needs a simple change in the template, but doing so will make the template incompatible with older versions.
// Theme maintainers generally want
// 1. No warnings or errors in the console when building a Hugo site.
// 2. Their theme to work for at least the last few Hugo versions.
func Deprecate(item, alternative string, version string) {
level := deprecationLogLevelFromVersion(version)
DeprecateLevel(item, alternative, version, level)
}
// DeprecateLevel informs about a deprecation logging at the given level.
func DeprecateLevel(item, alternative, version string, level logg.Level) {
var msg string
if level == logg.LevelError {
msg = fmt.Sprintf("%s was deprecated in Hugo %s and will be removed in Hugo %s. %s", item, version, CurrentVersion.Next().ReleaseVersion(), alternative)
} else {
msg = fmt.Sprintf("%s was deprecated in Hugo %s and will be removed in a future release. %s", item, version, alternative)
}
loggers.Log().Logger().WithLevel(level).WithField(loggers.FieldNameCmd, "deprecated").Logf(msg)
}
// We ususally do about one minor version a month.
// We want people to run at least the current and previous version without any warnings.
// We want people who don't update Hugo that often to see the warnings and errors before we remove the feature.
func deprecationLogLevelFromVersion(ver string) logg.Level {
from := MustParseVersion(ver)
to := CurrentVersion
minorDiff := to.Minor - from.Minor
switch {
case minorDiff >= 12:
// Start failing the build after about a year.
return logg.LevelError
case minorDiff >= 6:
// Start printing warnings after about six months.
return logg.LevelWarn
default:
return logg.LevelInfo
}
}
+26 -2
View File
@@ -17,13 +17,14 @@ import (
"fmt"
"testing"
"github.com/bep/logg"
qt "github.com/frankban/quicktest"
)
func TestHugoInfo(t *testing.T) {
c := qt.New(t)
conf := testConfig{environment: "production", workingDir: "/mywork"}
conf := testConfig{environment: "production", workingDir: "/mywork", running: false}
hugoInfo := NewInfo(conf, nil)
c.Assert(hugoInfo.Version(), qt.Equals, CurrentVersion.Version())
@@ -38,15 +39,34 @@ func TestHugoInfo(t *testing.T) {
}
c.Assert(hugoInfo.Environment, qt.Equals, "production")
c.Assert(string(hugoInfo.Generator()), qt.Contains, fmt.Sprintf("Hugo %s", hugoInfo.Version()))
c.Assert(hugoInfo.IsDevelopment(), qt.Equals, false)
c.Assert(hugoInfo.IsProduction(), qt.Equals, true)
c.Assert(hugoInfo.IsExtended(), qt.Equals, IsExtended)
c.Assert(hugoInfo.IsServer(), qt.Equals, false)
devHugoInfo := NewInfo(testConfig{environment: "development"}, nil)
devHugoInfo := NewInfo(testConfig{environment: "development", running: true}, nil)
c.Assert(devHugoInfo.IsDevelopment(), qt.Equals, true)
c.Assert(devHugoInfo.IsProduction(), qt.Equals, false)
c.Assert(devHugoInfo.IsServer(), qt.Equals, true)
}
func TestDeprecationLogLevelFromVersion(t *testing.T) {
c := qt.New(t)
c.Assert(deprecationLogLevelFromVersion("0.55.0"), qt.Equals, logg.LevelError)
ver := CurrentVersion
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelInfo)
ver.Minor -= 1
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelInfo)
ver.Minor -= 6
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelWarn)
ver.Minor -= 6
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelError)
}
type testConfig struct {
environment string
running bool
workingDir string
}
@@ -54,6 +74,10 @@ func (c testConfig) Environment() string {
return c.environment
}
func (c testConfig) Running() bool {
return c.running
}
func (c testConfig) WorkingDir() string {
return c.workingDir
}
+5 -4
View File
@@ -67,8 +67,11 @@ func (h VersionString) String() string {
// Compare implements the compare.Comparer interface.
func (h VersionString) Compare(other any) int {
v := MustParseVersion(h.String())
return compareVersions(v, other)
return compareVersions(h.Version(), other)
}
func (h VersionString) Version() Version {
return MustParseVersion(h.String())
}
// Eq implements the compare.Eqer interface.
@@ -264,7 +267,6 @@ func compareFloatWithVersion(v1 float64, v2 Version) int {
if v1maj > v2.Major {
return 1
}
if v1maj < v2.Major {
@@ -276,7 +278,6 @@ func compareFloatWithVersion(v1 float64, v2 Version) int {
}
return -1
}
func GoMinorVersion() int {
+3 -3
View File
@@ -17,7 +17,7 @@ package hugo
// This should be the only one.
var CurrentVersion = Version{
Major: 0,
Minor: 115,
PatchLevel: 4,
Suffix: "",
Minor: 123,
PatchLevel: 0,
Suffix: "-DEV",
}
+3 -3
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
// Some functions in this file (see comments) is based on the Go source code,
// copyright The Go Authors and governed by a BSD-style license.
//
@@ -27,10 +27,9 @@ import (
"github.com/fatih/color"
)
var bold = color.New(color.Bold)
// levelColor mapping.
var levelColor = [...]*color.Color{
logg.LevelTrace: color.New(color.FgWhite),
logg.LevelDebug: color.New(color.FgWhite),
logg.LevelInfo: color.New(color.FgBlue),
logg.LevelWarn: color.New(color.FgYellow),
@@ -39,6 +38,7 @@ var levelColor = [...]*color.Color{
// levelString mapping.
var levelString = [...]string{
logg.LevelTrace: "TRACE",
logg.LevelDebug: "DEBUG",
logg.LevelInfo: "INFO ",
logg.LevelWarn: "WARN ",
+5 -18
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
// Some functions in this file (see comments) is based on the Go source code,
// copyright The Go Authors and governed by a BSD-style license.
//
@@ -69,7 +69,7 @@ func (h *logLevelCounter) HandleLog(e *logg.Entry) error {
return nil
}
var stopError = fmt.Errorf("stop")
var errStop = fmt.Errorf("stop")
type logOnceHandler struct {
threshold logg.Level
@@ -87,7 +87,7 @@ func (h *logOnceHandler) HandleLog(e *logg.Entry) error {
defer h.mu.Unlock()
hash := identity.HashUint64(e.Level, e.Message, e.Fields)
if h.seen[hash] {
return stopError
return errStop
}
h.seen[hash] = true
return nil
@@ -107,7 +107,7 @@ type stopHandler struct {
func (h *stopHandler) HandleLog(e *logg.Entry) error {
for _, handler := range h.handlers {
if err := handler.HandleLog(e); err != nil {
if err == stopError {
if err == errStop {
return nil
}
return err
@@ -124,26 +124,13 @@ func (h *suppressStatementsHandler) HandleLog(e *logg.Entry) error {
for _, field := range e.Fields {
if field.Name == FieldNameStatementID {
if h.statements[field.Value.(string)] {
return stopError
return errStop
}
}
}
return nil
}
// replacer creates a new log handler that does string replacement in log messages.
func replacer(repl *strings.Replacer) logg.Handler {
return logg.HandlerFunc(func(e *logg.Entry) error {
e.Message = repl.Replace(e.Message)
for i, field := range e.Fields {
if s, ok := field.Value.(string); ok {
e.Fields[i].Value = repl.Replace(s)
}
}
return nil
})
}
// whiteSpaceTrimmer creates a new log handler that trims whitespace from log messages and string fields.
func whiteSpaceTrimmer() logg.Handler {
return logg.HandlerFunc(func(e *logg.Entry) error {
+2 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
// Some functions in this file (see comments) is based on the Go source code,
// copyright The Go Authors and governed by a BSD-style license.
//
@@ -81,7 +81,7 @@ func (h *noColoursHandler) HandleLog(e *logg.Entry) error {
if strings.HasPrefix(field.Name, reservedFieldNamePrefix) {
continue
}
fmt.Fprintf(w, " %s %q", field.Name, field.Value)
fmt.Fprintf(w, " %s %v", field.Name, field.Value)
}
fmt.Fprintln(w)
+92 -20
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
// Some functions in this file (see comments) is based on the Go source code,
// copyright The Go Authors and governed by a BSD-style license.
//
@@ -37,13 +37,13 @@ var (
// Options defines options for the logger.
type Options struct {
Level logg.Level
Stdout io.Writer
Stderr io.Writer
Distinct bool
StoreErrors bool
HandlerPost func(e *logg.Entry) error
SuppresssStatements map[string]bool
Level logg.Level
Stdout io.Writer
Stderr io.Writer
DistinctLevel logg.Level
StoreErrors bool
HandlerPost func(e *logg.Entry) error
SuppressStatements map[string]bool
}
// New creates a new logger with the given options.
@@ -68,11 +68,24 @@ func New(opts Options) Logger {
errorsw := &strings.Builder{}
logCounters := newLogLevelCounter()
handlers := []logg.Handler{
whiteSpaceTrimmer(),
logHandler,
logCounters,
}
if opts.Level == logg.LevelTrace {
// Trace is used during development only, and it's useful to
// only see the trace messages.
handlers = append(handlers,
logg.HandlerFunc(func(e *logg.Entry) error {
if e.Level != logg.LevelTrace {
return logg.ErrStopLogEntry
}
return nil
}),
)
}
handlers = append(handlers, whiteSpaceTrimmer(), logHandler)
if opts.HandlerPost != nil {
var hookHandler logg.HandlerFunc = func(e *logg.Entry) error {
opts.HandlerPost(e)
@@ -92,13 +105,13 @@ func New(opts Options) Logger {
logHandler = multi.New(handlers...)
var logOnce *logOnceHandler
if opts.Distinct {
logOnce = newLogOnceHandler(logg.LevelWarn)
if opts.DistinctLevel != 0 {
logOnce = newLogOnceHandler(opts.DistinctLevel)
logHandler = newStopHandler(logOnce, logHandler)
}
if opts.SuppresssStatements != nil && len(opts.SuppresssStatements) > 0 {
logHandler = newStopHandler(newSuppressStatementsHandler(opts.SuppresssStatements), logHandler)
if opts.SuppressStatements != nil && len(opts.SuppressStatements) > 0 {
logHandler = newStopHandler(newSuppressStatementsHandler(opts.SuppressStatements), logHandler)
}
logger := logg.New(
@@ -127,6 +140,7 @@ func New(opts Options) Logger {
out: opts.Stdout,
level: opts.Level,
logger: logger,
tracel: l.WithLevel(logg.LevelTrace),
debugl: l.WithLevel(logg.LevelDebug),
infol: l.WithLevel(logg.LevelInfo),
warnl: l.WithLevel(logg.LevelWarn),
@@ -137,10 +151,20 @@ func New(opts Options) Logger {
// NewDefault creates a new logger with the default options.
func NewDefault() Logger {
opts := Options{
Distinct: true,
Level: logg.LevelWarn,
Stdout: os.Stdout,
Stderr: os.Stdout,
DistinctLevel: logg.LevelWarn,
Level: logg.LevelWarn,
Stdout: os.Stdout,
Stderr: os.Stdout,
}
return New(opts)
}
func NewTrace() Logger {
opts := Options{
DistinctLevel: logg.LevelWarn,
Level: logg.LevelTrace,
Stdout: os.Stdout,
Stderr: os.Stdout,
}
return New(opts)
}
@@ -150,13 +174,14 @@ func LevelLoggerToWriter(l logg.LevelLogger) io.Writer {
}
type Logger interface {
Debug() logg.LevelLogger
Debugf(format string, v ...any)
Debugln(v ...any)
Error() logg.LevelLogger
Errorf(format string, v ...any)
Erroridf(id, format string, v ...any)
Errorln(v ...any)
Errors() string
Errorsf(id, format string, v ...any)
Info() logg.LevelLogger
InfoCommand(command string) logg.LevelLogger
Infof(format string, v ...any)
@@ -172,7 +197,10 @@ type Logger interface {
Warn() logg.LevelLogger
WarnCommand(command string) logg.LevelLogger
Warnf(format string, v ...any)
Warnidf(id, format string, v ...any)
Warnln(v ...any)
Deprecatef(fail bool, format string, v ...any)
Trace(s logg.StringFunc)
}
type logAdapter struct {
@@ -182,12 +210,17 @@ type logAdapter struct {
out io.Writer
level logg.Level
logger logg.Logger
tracel logg.LevelLogger
debugl logg.LevelLogger
infol logg.LevelLogger
warnl logg.LevelLogger
errorl logg.LevelLogger
}
func (l *logAdapter) Debug() logg.LevelLogger {
return l.debugl
}
func (l *logAdapter) Debugf(format string, v ...any) {
l.debugl.Logf(format, v...)
}
@@ -289,14 +322,37 @@ func (l *logAdapter) Errors() string {
return l.errors.String()
}
func (l *logAdapter) Errorsf(id, format string, v ...any) {
func (l *logAdapter) Erroridf(id, format string, v ...any) {
format += l.idfInfoStatement("error", id, format)
l.errorl.WithField(FieldNameStatementID, id).Logf(format, v...)
}
func (l *logAdapter) Warnidf(id, format string, v ...any) {
format += l.idfInfoStatement("warning", id, format)
l.warnl.WithField(FieldNameStatementID, id).Logf(format, v...)
}
func (l *logAdapter) idfInfoStatement(what, id, format string) string {
return fmt.Sprintf("\nYou can suppress this %s by adding the following to your site configuration:\nignoreLogs = ['%s']", what, id)
}
func (l *logAdapter) Trace(s logg.StringFunc) {
l.tracel.Log(s)
}
func (l *logAdapter) sprint(v ...any) string {
return strings.TrimRight(fmt.Sprintln(v...), "\n")
}
func (l *logAdapter) Deprecatef(fail bool, format string, v ...any) {
format = "DEPRECATED: " + format
if fail {
l.errorl.Logf(format, v...)
} else {
l.warnl.Logf(format, v...)
}
}
type logWriter struct {
l logg.LevelLogger
}
@@ -305,3 +361,19 @@ func (w logWriter) Write(p []byte) (n int, err error) {
w.l.Log(logg.String(string(p)))
return len(p), nil
}
func TimeTrackf(l logg.LevelLogger, start time.Time, fields logg.Fields, format string, a ...any) {
elapsed := time.Since(start)
if fields != nil {
l = l.WithFields(fields)
}
l.WithField("duration", elapsed).Logf(format, a...)
}
func TimeTrackfn(fn func() (logg.LevelLogger, error)) error {
start := time.Now()
l, err := fn()
elapsed := time.Since(start)
l.WithField("duration", elapsed).Logf("")
return err
}
+10 -12
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
// Some functions in this file (see comments) is based on the Go source code,
// copyright The Go Authors and governed by a BSD-style license.
//
@@ -29,10 +29,10 @@ func TestLogDistinct(t *testing.T) {
c := qt.New(t)
opts := loggers.Options{
Distinct: true,
StoreErrors: true,
Stdout: io.Discard,
Stderr: io.Discard,
DistinctLevel: logg.LevelWarn,
StoreErrors: true,
Stdout: io.Discard,
Stderr: io.Discard,
}
l := loggers.New(opts)
@@ -85,7 +85,6 @@ func TestOptionStoreErrors(t *testing.T) {
c.Assert(sb.String(), qt.Contains, "error 1")
c.Assert(sb.String(), qt.Contains, "ERROR")
}
func TestLogCount(t *testing.T) {
@@ -110,7 +109,7 @@ func TestSuppressStatements(t *testing.T) {
opts := loggers.Options{
StoreErrors: true,
SuppresssStatements: map[string]bool{
SuppressStatements: map[string]bool{
"error-1": true,
},
}
@@ -124,17 +123,16 @@ func TestSuppressStatements(t *testing.T) {
c.Assert(errorsStr, qt.Not(qt.Contains), "error 1")
c.Assert(errorsStr, qt.Contains, "error 2")
c.Assert(l.LoggCount(logg.LevelError), qt.Equals, 1)
}
func TestReset(t *testing.T) {
c := qt.New(t)
opts := loggers.Options{
StoreErrors: true,
Distinct: true,
Stdout: io.Discard,
Stderr: io.Discard,
StoreErrors: true,
DistinctLevel: logg.LevelWarn,
Stdout: io.Discard,
Stderr: io.Discard,
}
l := loggers.New(opts)
+6 -5
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
// Some functions in this file (see comments) is based on the Go source code,
// copyright The Go Authors and governed by a BSD-style license.
//
@@ -21,7 +21,7 @@ import (
"github.com/bep/logg"
)
func InitGlobalLogger(panicOnWarnings bool) {
func InitGlobalLogger(level logg.Level, panicOnWarnings bool) {
logMu.Lock()
defer logMu.Unlock()
var logHookLast func(e *logg.Entry) error
@@ -31,8 +31,9 @@ func InitGlobalLogger(panicOnWarnings bool) {
log = New(
Options{
Distinct: true,
HandlerPost: logHookLast,
Level: level,
DistinctLevel: logg.LevelInfo,
HandlerPost: logHookLast,
},
)
}
@@ -49,5 +50,5 @@ func Log() Logger {
var log Logger
func init() {
InitGlobalLogger(false)
InitGlobalLogger(logg.LevelWarn, false)
}
+90
View File
@@ -0,0 +1,90 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package maps
import "sync"
// Cache is a simple thread safe cache backed by a map.
type Cache[K comparable, T any] struct {
m map[K]T
sync.RWMutex
}
// NewCache creates a new Cache.
func NewCache[K comparable, T any]() *Cache[K, T] {
return &Cache[K, T]{m: make(map[K]T)}
}
// Delete deletes the given key from the cache.
func (c *Cache[K, T]) Get(key K) (T, bool) {
c.RLock()
v, found := c.m[key]
c.RUnlock()
return v, found
}
// GetOrCreate gets the value for the given key if it exists, or creates it if not.
func (c *Cache[K, T]) GetOrCreate(key K, create func() T) T {
c.RLock()
v, found := c.m[key]
c.RUnlock()
if found {
return v
}
c.Lock()
defer c.Unlock()
v, found = c.m[key]
if found {
return v
}
v = create()
c.m[key] = v
return v
}
// Set sets the given key to the given value.
func (c *Cache[K, T]) Set(key K, value T) {
c.Lock()
c.m[key] = value
c.Unlock()
}
// SliceCache is a simple thread safe cache backed by a map.
type SliceCache[T any] struct {
m map[string][]T
sync.RWMutex
}
func NewSliceCache[T any]() *SliceCache[T] {
return &SliceCache[T]{m: make(map[string][]T)}
}
func (c *SliceCache[T]) Get(key string) ([]T, bool) {
c.RLock()
v, found := c.m[key]
c.RUnlock()
return v, found
}
func (c *SliceCache[T]) Append(key string, values ...T) {
c.Lock()
c.m[key] = append(c.m[key], values...)
c.Unlock()
}
func (c *SliceCache[T]) Reset() {
c.Lock()
c.m = make(map[string][]T)
c.Unlock()
}
+33 -9
View File
@@ -29,7 +29,7 @@ func ToStringMapE(in any) (map[string]any, error) {
case Params:
return vv, nil
case map[string]string:
var m = map[string]any{}
m := map[string]any{}
for k, v := range vv {
m[k] = v
}
@@ -192,21 +192,45 @@ func (KeyRenamer) keyPath(k1, k2 string) string {
}
func (r KeyRenamer) renamePath(parentKeyPath string, m map[string]any) {
for key, val := range m {
keyPath := r.keyPath(parentKeyPath, key)
switch val.(type) {
for k, v := range m {
keyPath := r.keyPath(parentKeyPath, k)
switch vv := v.(type) {
case map[any]any:
val = cast.ToStringMap(val)
r.renamePath(keyPath, val.(map[string]any))
r.renamePath(keyPath, cast.ToStringMap(vv))
case map[string]any:
r.renamePath(keyPath, val.(map[string]any))
r.renamePath(keyPath, vv)
}
newKey := r.getNewKey(keyPath)
if newKey != "" {
delete(m, key)
m[newKey] = val
delete(m, k)
m[newKey] = v
}
}
}
// ConvertFloat64WithNoDecimalsToInt converts float64 values with no decimals to int recursively.
func ConvertFloat64WithNoDecimalsToInt(m map[string]any) {
for k, v := range m {
switch vv := v.(type) {
case float64:
if v == float64(int64(vv)) {
m[k] = int64(vv)
}
case map[string]any:
ConvertFloat64WithNoDecimalsToInt(vv)
case []any:
for i, vvv := range vv {
switch vvvv := vvv.(type) {
case float64:
if vvv == float64(int64(vvvv)) {
vv[i] = int64(vvvv)
}
case map[string]any:
ConvertFloat64WithNoDecimalsToInt(vvvv)
}
}
}
}
}
-1
View File
@@ -192,5 +192,4 @@ func TestLookupEqualFold(t *testing.T) {
v, found = LookupEqualFold(m2, "b")
c.Assert(found, qt.IsTrue)
c.Assert(v, qt.Equals, "bv")
}
+4 -6
View File
@@ -61,7 +61,7 @@ func SetParams(dst, src Params) {
// IsZero returns true if p is considered empty.
func (p Params) IsZero() bool {
if p == nil || len(p) == 0 {
if len(p) == 0 {
return true
}
@@ -74,7 +74,6 @@ func (p Params) IsZero() bool {
}
return false
}
// MergeParamsWithStrategy transfers values from src to dst for new keys using the merge strategy given.
@@ -93,7 +92,7 @@ func MergeParams(dst, src Params) {
func (p Params) merge(ps ParamsMergeStrategy, pp Params) {
ns, found := p.GetMergeStrategy()
var ms = ns
ms := ns
if !found && ps != "" {
ms = ps
}
@@ -248,7 +247,7 @@ const (
// CleanConfigStringMapString removes any processing instructions from m,
// m will never be modified.
func CleanConfigStringMapString(m map[string]string) map[string]string {
if m == nil || len(m) == 0 {
if len(m) == 0 {
return m
}
if _, found := m[MergeStrategyKey]; !found {
@@ -267,7 +266,7 @@ func CleanConfigStringMapString(m map[string]string) map[string]string {
// CleanConfigStringMap is the same as CleanConfigStringMapString but for
// map[string]any.
func CleanConfigStringMap(m map[string]any) map[string]any {
if m == nil || len(m) == 0 {
if len(m) == 0 {
return m
}
if _, found := m[MergeStrategyKey]; !found {
@@ -291,7 +290,6 @@ func CleanConfigStringMap(m map[string]any) map[string]any {
}
return m2
}
func toMergeStrategy(v any) ParamsMergeStrategy {
-1
View File
@@ -154,7 +154,6 @@ func TestParamsSetAndMerge(t *testing.T) {
"a": "av",
"c": "cv",
})
}
func TestParamsIsZero(t *testing.T) {
+3 -2
View File
@@ -32,8 +32,9 @@ func TestPara(t *testing.T) {
t.Skipf("skip para test, CPU count is %d", runtime.NumCPU())
}
if !htesting.IsCI() {
t.Skip("skip para test when not running on CI")
// TODO(bep)
if htesting.IsCI() {
t.Skip("skip para test when running on CI")
}
c := qt.New(t)
+175 -52
View File
@@ -16,14 +16,18 @@ package paths
import (
"errors"
"fmt"
"net/url"
"path"
"path/filepath"
"regexp"
"strings"
"unicode"
)
// FilePathSeparator as defined by os.Separator.
const FilePathSeparator = string(filepath.Separator)
const (
FilePathSeparator = string(filepath.Separator)
slash = "/"
)
// filepathPathBridge is a bridge for common functionality in filepath vs path
type filepathPathBridge interface {
@@ -72,6 +76,30 @@ func AbsPathify(workingDir, inPath string) string {
return filepath.Join(workingDir, inPath)
}
// AddTrailingSlash adds a trailing Unix styled slash (/) if not already
// there.
func AddTrailingSlash(path string) string {
if !strings.HasSuffix(path, "/") {
path += "/"
}
return path
}
// AddLeadingSlash adds a leading Unix styled slash (/) if not already
// there.
func AddLeadingSlash(path string) string {
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
return path
}
// AddTrailingAndLeadingSlash adds a leading and trailing Unix styled slash (/) if not already
// there.
func AddLeadingAndTrailingSlash(path string) string {
return AddTrailingSlash(AddLeadingSlash(path))
}
// MakeTitle converts the path given to a suitable title, trimming whitespace
// and replacing hyphens with whitespace.
func MakeTitle(inpath string) string {
@@ -94,43 +122,6 @@ func makePathRelative(inPath string, possibleDirectories ...string) (string, err
return inPath, errors.New("can't extract relative path, unknown prefix")
}
// Should be good enough for Hugo.
var isFileRe = regexp.MustCompile(`.*\..{1,6}$`)
// GetDottedRelativePath expects a relative path starting after the content directory.
// It returns a relative path with dots ("..") navigating up the path structure.
func GetDottedRelativePath(inPath string) string {
inPath = path.Clean(filepath.ToSlash(inPath))
if inPath == "." {
return "./"
}
if !isFileRe.MatchString(inPath) && !strings.HasSuffix(inPath, "/") {
inPath += "/"
}
if !strings.HasPrefix(inPath, "/") {
inPath = "/" + inPath
}
dir, _ := filepath.Split(inPath)
sectionCount := strings.Count(dir, "/")
if sectionCount == 0 || dir == "/" {
return "./"
}
var dottedPath string
for i := 1; i < sectionCount; i++ {
dottedPath += "../"
}
return dottedPath
}
// ExtNoDelimiter takes a path and returns the extension, excluding the delimiter, i.e. "md".
func ExtNoDelimiter(in string) string {
return strings.TrimPrefix(Ext(in), ".")
@@ -167,12 +158,6 @@ func Filename(in string) (name string) {
return
}
// PathNoExt takes a path, strips out the extension,
// and returns the name of the file.
func PathNoExt(in string) string {
return strings.TrimSuffix(in, path.Ext(in))
}
// FileAndExt returns the filename and any extension of a file path as
// two separate strings.
//
@@ -252,16 +237,125 @@ func prettifyPath(in string, b filepathPathBridge) string {
return b.Join(b.Dir(in), name, "index"+ext)
}
type NamedSlice struct {
Name string
Slice []string
// CommonDir returns the common directory of the given paths.
func CommonDir(path1, path2 string) string {
if path1 == "" || path2 == "" {
return ""
}
p1 := strings.Split(path1, "/")
p2 := strings.Split(path2, "/")
var common []string
for i := 0; i < len(p1) && i < len(p2); i++ {
if p1[i] == p2[i] {
common = append(common, p1[i])
} else {
break
}
}
return strings.Join(common, "/")
}
func (n NamedSlice) String() string {
if len(n.Slice) == 0 {
return n.Name
// Sanitize sanitizes string to be used in Hugo's file paths and URLs, allowing only
// a predefined set of special Unicode characters.
//
// Spaces will be replaced with a single hyphen.
//
// This function is the core function used to normalize paths in Hugo.
//
// Note that this is the first common step for URL/path sanitation,
// the final URL/path may end up looking differently if the user has stricter rules defined (e.g. removePathAccents=true).
func Sanitize(s string) string {
var willChange bool
for i, r := range s {
willChange = !isAllowedPathCharacter(s, i, r)
if willChange {
break
}
}
return fmt.Sprintf("%s%s{%s}", n.Name, FilePathSeparator, strings.Join(n.Slice, ","))
if !willChange {
// Prevent allocation when nothing changes.
return s
}
target := make([]rune, 0, len(s))
var (
prependHyphen bool
wasHyphen bool
)
for i, r := range s {
isAllowed := isAllowedPathCharacter(s, i, r)
if isAllowed {
// track explicit hyphen in input; no need to add a new hyphen if
// we just saw one.
wasHyphen = r == '-'
if prependHyphen {
// if currently have a hyphen, don't prepend an extra one
if !wasHyphen {
target = append(target, '-')
}
prependHyphen = false
}
target = append(target, r)
} else if len(target) > 0 && !wasHyphen && unicode.IsSpace(r) {
prependHyphen = true
}
}
return string(target)
}
func isAllowedPathCharacter(s string, i int, r rune) bool {
if r == ' ' {
return false
}
// Check for the most likely first (faster).
isAllowed := unicode.IsLetter(r) || unicode.IsDigit(r)
isAllowed = isAllowed || r == '.' || r == '/' || r == '\\' || r == '_' || r == '#' || r == '+' || r == '~' || r == '-' || r == '@'
isAllowed = isAllowed || unicode.IsMark(r)
isAllowed = isAllowed || (r == '%' && i+2 < len(s) && ishex(s[i+1]) && ishex(s[i+2]))
return isAllowed
}
// From https://golang.org/src/net/url/url.go
func ishex(c byte) bool {
switch {
case '0' <= c && c <= '9':
return true
case 'a' <= c && c <= 'f':
return true
case 'A' <= c && c <= 'F':
return true
}
return false
}
var slashFunc = func(r rune) bool {
return r == '/'
}
// Dir behaves like path.Dir without the path.Clean step.
//
// The returned path ends in a slash only if it is the root "/".
func Dir(s string) string {
dir, _ := path.Split(s)
if len(dir) > 1 && dir[len(dir)-1] == '/' {
return dir[:len(dir)-1]
}
return dir
}
// FieldsSlash cuts s into fields separated with '/'.
func FieldsSlash(s string) []string {
f := strings.FieldsFunc(s, slashFunc)
return f
}
// DirFile holds the result from path.Split.
@@ -274,3 +368,32 @@ type DirFile struct {
func (df DirFile) String() string {
return fmt.Sprintf("%s|%s", df.Dir, df.File)
}
// PathEscape escapes unicode letters in pth.
// Use URLEscape to escape full URLs including scheme, query etc.
// This is slightly faster for the common case.
// Note, there is a url.PathEscape function, but that also
// escapes /.
func PathEscape(pth string) string {
u, err := url.Parse(pth)
if err != nil {
panic(err)
}
return u.EscapedPath()
}
// ToSlashTrimLeading is just a filepath.ToSlash with an added / prefix trimmer.
func ToSlashTrimLeading(s string) string {
return strings.TrimPrefix(filepath.ToSlash(s), "/")
}
// ToSlashTrimTrailing is just a filepath.ToSlash with an added / suffix trimmer.
func ToSlashTrimTrailing(s string) string {
return strings.TrimSuffix(filepath.ToSlash(s), "/")
}
// ToSlashPreserveLeading converts the path given to a forward slash separated path
// and preserves the leading slash if present trimming any trailing slash.
func ToSlashPreserveLeading(s string) string {
return "/" + strings.Trim(filepath.ToSlash(s), "/")
}
+75 -39
View File
@@ -1,4 +1,4 @@
// Copyright 2021 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -75,44 +75,6 @@ func TestMakePathRelative(t *testing.T) {
}
}
func TestGetDottedRelativePath(t *testing.T) {
// on Windows this will receive both kinds, both country and western ...
for _, f := range []func(string) string{filepath.FromSlash, func(s string) string { return s }} {
doTestGetDottedRelativePath(f, t)
}
}
func doTestGetDottedRelativePath(urlFixer func(string) string, t *testing.T) {
type test struct {
input, expected string
}
data := []test{
{"", "./"},
{urlFixer("/"), "./"},
{urlFixer("post"), "../"},
{urlFixer("/post"), "../"},
{urlFixer("post/"), "../"},
{urlFixer("tags/foo.html"), "../"},
{urlFixer("/tags/foo.html"), "../"},
{urlFixer("/post/"), "../"},
{urlFixer("////post/////"), "../"},
{urlFixer("/foo/bar/index.html"), "../../"},
{urlFixer("/foo/bar/foo/"), "../../../"},
{urlFixer("/foo/bar/foo"), "../../../"},
{urlFixer("foo/bar/foo/"), "../../../"},
{urlFixer("foo/bar/foo/bar"), "../../../../"},
{"404.html", "./"},
{"404.xml", "./"},
{"/404.html", "./"},
}
for i, d := range data {
output := GetDottedRelativePath(d.input)
if d.expected != output {
t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
}
}
}
func TestMakeTitle(t *testing.T) {
type test struct {
input, expected string
@@ -226,3 +188,77 @@ func TestFileAndExt(t *testing.T) {
}
}
}
func TestSanitize(t *testing.T) {
c := qt.New(t)
tests := []struct {
input string
expected string
}{
{" Foo bar ", "Foo-bar"},
{"Foo.Bar/foo_Bar-Foo", "Foo.Bar/foo_Bar-Foo"},
{"fOO,bar:foobAR", "fOObarfoobAR"},
{"FOo/BaR.html", "FOo/BaR.html"},
{"FOo/Ba---R.html", "FOo/Ba---R.html"}, /// See #10104
{"FOo/Ba R.html", "FOo/Ba-R.html"},
{"трям/трям", "трям/трям"},
{"은행", "은행"},
{"Банковский кассир", "Банковский-кассир"},
// Issue #1488
{"संस्कृत", "संस्कृत"},
{"a%C3%B1ame", "a%C3%B1ame"}, // Issue #1292
{"this+is+a+test", "this+is+a+test"}, // Issue #1290
{"~foo", "~foo"}, // Issue #2177
}
for _, test := range tests {
c.Assert(Sanitize(test.input), qt.Equals, test.expected)
}
}
func BenchmarkSanitize(b *testing.B) {
const (
allAlowedPath = "foo/bar"
spacePath = "foo bar"
)
// This should not allocate any memory.
b.Run("All allowed", func(b *testing.B) {
for i := 0; i < b.N; i++ {
got := Sanitize(allAlowedPath)
if got != allAlowedPath {
b.Fatal(got)
}
}
})
// This will allocate some memory.
b.Run("Spaces", func(b *testing.B) {
for i := 0; i < b.N; i++ {
got := Sanitize(spacePath)
if got != "foo-bar" {
b.Fatal(got)
}
}
})
}
func TestDir(t *testing.T) {
c := qt.New(t)
c.Assert(Dir("/a/b/c/d"), qt.Equals, "/a/b/c")
c.Assert(Dir("/a"), qt.Equals, "/")
c.Assert(Dir("/"), qt.Equals, "/")
c.Assert(Dir(""), qt.Equals, "")
}
func TestFieldsSlash(t *testing.T) {
c := qt.New(t)
c.Assert(FieldsSlash("a/b/c"), qt.DeepEquals, []string{"a", "b", "c"})
c.Assert(FieldsSlash("/a/b/c"), qt.DeepEquals, []string{"a", "b", "c"})
c.Assert(FieldsSlash("/a/b/c/"), qt.DeepEquals, []string{"a", "b", "c"})
c.Assert(FieldsSlash("a/b/c/"), qt.DeepEquals, []string{"a", "b", "c"})
c.Assert(FieldsSlash("/"), qt.DeepEquals, []string{})
c.Assert(FieldsSlash(""), qt.DeepEquals, []string{})
}
+511
View File
@@ -0,0 +1,511 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package paths
import (
"path"
"path/filepath"
"runtime"
"strings"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/hugofs/files"
)
var defaultPathParser PathParser
// PathParser parses a path into a Path.
type PathParser struct {
// Maps the language code to its index in the languages/sites slice.
LanguageIndex map[string]int
// Reports whether the given language is disabled.
IsLangDisabled func(string) bool
}
// Parse parses component c with path s into Path using the default path parser.
func Parse(c, s string) *Path {
return defaultPathParser.Parse(c, s)
}
// NormalizePathString returns a normalized path string using the very basic Hugo rules.
func NormalizePathStringBasic(s string) string {
// All lower case.
s = strings.ToLower(s)
// Replace spaces with hyphens.
s = strings.ReplaceAll(s, " ", "-")
return s
}
// Parse parses component c with path s into Path using Hugo's content path rules.
func (parser PathParser) Parse(c, s string) *Path {
p, err := parser.parse(c, s)
if err != nil {
panic(err)
}
return p
}
func (pp *PathParser) parse(component, s string) (*Path, error) {
ss := NormalizePathStringBasic(s)
p, err := pp.doParse(component, ss)
if err != nil {
return nil, err
}
if s != ss {
var err error
// Preserve the original case for titles etc.
p.unnormalized, err = pp.doParse(component, s)
if err != nil {
return nil, err
}
} else {
p.unnormalized = p
}
return p, nil
}
func (pp *PathParser) doParse(component, s string) (*Path, error) {
p := &Path{
component: component,
posContainerLow: -1,
posContainerHigh: -1,
posSectionHigh: -1,
posIdentifierLanguage: -1,
}
hasLang := pp.LanguageIndex != nil
hasLang = hasLang && (component == files.ComponentFolderContent || component == files.ComponentFolderLayouts)
if runtime.GOOS == "windows" {
s = path.Clean(filepath.ToSlash(s))
if s == "." {
s = ""
}
}
if s == "" {
s = "/"
}
// Leading slash, no trailing slash.
if !strings.HasPrefix(s, "/") {
s = "/" + s
}
if s != "/" && s[len(s)-1] == '/' {
s = s[:len(s)-1]
}
p.s = s
slashCount := 0
for i := len(s) - 1; i >= 0; i-- {
c := s[i]
switch c {
case '.':
if p.posContainerHigh == -1 {
var high int
if len(p.identifiers) > 0 {
high = p.identifiers[len(p.identifiers)-1].Low - 1
} else {
high = len(p.s)
}
id := types.LowHigh{Low: i + 1, High: high}
if len(p.identifiers) == 0 {
p.identifiers = append(p.identifiers, id)
} else if len(p.identifiers) == 1 {
// Check for a valid language.
s := p.s[id.Low:id.High]
if hasLang {
var disabled bool
_, langFound := pp.LanguageIndex[s]
if !langFound {
disabled = pp.IsLangDisabled != nil && pp.IsLangDisabled(s)
if disabled {
p.disabled = true
langFound = true
}
}
if langFound {
p.posIdentifierLanguage = 1
p.identifiers = append(p.identifiers, id)
}
}
}
}
case '/':
slashCount++
if p.posContainerHigh == -1 {
p.posContainerHigh = i + 1
} else if p.posContainerLow == -1 {
p.posContainerLow = i + 1
}
if i > 0 {
p.posSectionHigh = i
}
}
}
isContentComponent := p.component == files.ComponentFolderContent || p.component == files.ComponentFolderArchetypes
isContent := isContentComponent && files.IsContentExt(p.Ext())
if isContent {
id := p.identifiers[len(p.identifiers)-1]
b := p.s[p.posContainerHigh : id.Low-1]
switch b {
case "index":
p.bundleType = PathTypeLeaf
case "_index":
p.bundleType = PathTypeBranch
default:
p.bundleType = PathTypeContentSingle
}
if slashCount == 2 && p.IsLeafBundle() {
p.posSectionHigh = 0
}
}
return p, nil
}
func ModifyPathBundleTypeResource(p *Path) {
if p.IsContent() {
p.bundleType = PathTypeContentResource
} else {
p.bundleType = PathTypeFile
}
}
type PathType int
const (
// A generic resource, e.g. a JSON file.
PathTypeFile PathType = iota
// All below are content files.
// A resource of a content type with front matter.
PathTypeContentResource
// E.g. /blog/my-post.md
PathTypeContentSingle
// All bewlow are bundled content files.
// Leaf bundles, e.g. /blog/my-post/index.md
PathTypeLeaf
// Branch bundles, e.g. /blog/_index.md
PathTypeBranch
)
type Path struct {
s string
posContainerLow int
posContainerHigh int
posSectionHigh int
component string
bundleType PathType
identifiers []types.LowHigh
posIdentifierLanguage int
disabled bool
trimLeadingSlash bool
unnormalized *Path
}
// TrimLeadingSlash returns a copy of the Path with the leading slash removed.
func (p Path) TrimLeadingSlash() *Path {
p.trimLeadingSlash = true
return &p
}
func (p *Path) norm(s string) string {
if p.trimLeadingSlash {
s = strings.TrimPrefix(s, "/")
}
return s
}
// IdentifierBase satifies identity.Identity.
func (p *Path) IdentifierBase() string {
return p.Base()[1:]
}
// Component returns the component for this path (e.g. "content").
func (p *Path) Component() string {
return p.component
}
// Container returns the base name of the container directory for this path.
func (p *Path) Container() string {
if p.posContainerLow == -1 {
return ""
}
return p.norm(p.s[p.posContainerLow : p.posContainerHigh-1])
}
// ContainerDir returns the container directory for this path.
// For content bundles this will be the parent directory.
func (p *Path) ContainerDir() string {
if p.posContainerLow == -1 || !p.IsBundle() {
return p.Dir()
}
return p.norm(p.s[:p.posContainerLow-1])
}
// Section returns the first path element (section).
func (p *Path) Section() string {
if p.posSectionHigh <= 0 {
return ""
}
return p.norm(p.s[1:p.posSectionHigh])
}
// IsContent returns true if the path is a content file (e.g. mypost.md).
// Note that this will also return true for content files in a bundle.
func (p *Path) IsContent() bool {
return p.BundleType() >= PathTypeContentResource
}
// isContentPage returns true if the path is a content file (e.g. mypost.md),
// but nof if inside a leaf bundle.
func (p *Path) isContentPage() bool {
return p.BundleType() >= PathTypeContentSingle
}
// Name returns the last element of path.
func (p *Path) Name() string {
if p.posContainerHigh > 0 {
return p.s[p.posContainerHigh:]
}
return p.s
}
// Name returns the last element of path withhout any extension.
func (p *Path) NameNoExt() string {
if i := p.identifierIndex(0); i != -1 {
return p.s[p.posContainerHigh : p.identifiers[i].Low-1]
}
return p.s[p.posContainerHigh:]
}
// Name returns the last element of path withhout any language identifier.
func (p *Path) NameNoLang() string {
i := p.identifierIndex(p.posIdentifierLanguage)
if i == -1 {
return p.Name()
}
return p.s[p.posContainerHigh:p.identifiers[i].Low-1] + p.s[p.identifiers[i].High:]
}
// BaseNameNoIdentifier returns the logcical base name for a resource without any idenifier (e.g. no extension).
// For bundles this will be the containing directory's name, e.g. "blog".
func (p *Path) BaseNameNoIdentifier() string {
if p.IsBundle() {
return p.Container()
}
return p.NameNoIdentifier()
}
// NameNoIdentifier returns the last element of path withhout any identifier (e.g. no extension).
func (p *Path) NameNoIdentifier() string {
if len(p.identifiers) > 0 {
return p.s[p.posContainerHigh : p.identifiers[len(p.identifiers)-1].Low-1]
}
return p.s[p.posContainerHigh:]
}
// Dir returns all but the last element of path, typically the path's directory.
func (p *Path) Dir() (d string) {
if p.posContainerHigh > 0 {
d = p.s[:p.posContainerHigh-1]
}
if d == "" {
d = "/"
}
d = p.norm(d)
return
}
// Path returns the full path.
func (p *Path) Path() (d string) {
return p.norm(p.s)
}
// Unnormalized returns the Path with the original case preserved.
func (p *Path) Unnormalized() *Path {
return p.unnormalized
}
// PathNoLang returns the Path but with any language identifier removed.
func (p *Path) PathNoLang() string {
return p.base(true, false)
}
// PathNoIdentifier returns the Path but with any identifier (ext, lang) removed.
func (p *Path) PathNoIdentifier() string {
return p.base(false, false)
}
// PathRel returns the path relativeto the given owner.
func (p *Path) PathRel(owner *Path) string {
ob := owner.Base()
if !strings.HasSuffix(ob, "/") {
ob += "/"
}
return strings.TrimPrefix(p.Path(), ob)
}
// BaseRel returns the base path relative to the given owner.
func (p *Path) BaseRel(owner *Path) string {
ob := owner.Base()
if ob == "/" {
ob = ""
}
return p.Base()[len(ob)+1:]
}
// For content files, Base returns the path without any identifiers (extension, language code etc.).
// Any 'index' as the last path element is ignored.
//
// For other files (Resources), any extension is kept.
func (p *Path) Base() string {
return p.base(!p.isContentPage(), p.IsBundle())
}
// BaseNoLeadingSlash returns the base path without the leading slash.
func (p *Path) BaseNoLeadingSlash() string {
return p.Base()[1:]
}
func (p *Path) base(preserveExt, isBundle bool) string {
if len(p.identifiers) == 0 {
return p.norm(p.s)
}
if preserveExt && len(p.identifiers) == 1 {
// Preserve extension.
return p.norm(p.s)
}
id := p.identifiers[len(p.identifiers)-1]
high := id.Low - 1
if isBundle {
high = p.posContainerHigh - 1
}
if high == 0 {
high++
}
if !preserveExt {
return p.norm(p.s[:high])
}
// For txt files etc. we want to preserve the extension.
id = p.identifiers[0]
return p.norm(p.s[:high] + p.s[id.Low-1:id.High])
}
func (p *Path) Ext() string {
return p.identifierAsString(0)
}
func (p *Path) Lang() string {
return p.identifierAsString(1)
}
func (p *Path) Identifier(i int) string {
return p.identifierAsString(i)
}
func (p *Path) Disabled() bool {
return p.disabled
}
func (p *Path) Identifiers() []string {
ids := make([]string, len(p.identifiers))
for i, id := range p.identifiers {
ids[i] = p.s[id.Low:id.High]
}
return ids
}
func (p *Path) IsHTML() bool {
return files.IsHTML(p.Ext())
}
func (p *Path) BundleType() PathType {
return p.bundleType
}
func (p *Path) IsBundle() bool {
return p.bundleType >= PathTypeLeaf
}
func (p *Path) IsBranchBundle() bool {
return p.bundleType == PathTypeBranch
}
func (p *Path) IsLeafBundle() bool {
return p.bundleType == PathTypeLeaf
}
func (p *Path) identifierAsString(i int) string {
i = p.identifierIndex(i)
if i == -1 {
return ""
}
id := p.identifiers[i]
return p.s[id.Low:id.High]
}
func (p *Path) identifierIndex(i int) int {
if i < 0 || i >= len(p.identifiers) {
return -1
}
return i
}
// HasExt returns true if the Unix styled path has an extension.
func HasExt(p string) bool {
for i := len(p) - 1; i >= 0; i-- {
if p[i] == '.' {
return true
}
if p[i] == '/' {
return false
}
}
return false
}
+351
View File
@@ -0,0 +1,351 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package paths
import (
"path/filepath"
"testing"
"github.com/gohugoio/hugo/hugofs/files"
qt "github.com/frankban/quicktest"
)
var testParser = &PathParser{
LanguageIndex: map[string]int{
"no": 0,
"en": 1,
},
}
func TestParse(t *testing.T) {
c := qt.New(t)
tests := []struct {
name string
path string
assert func(c *qt.C, p *Path)
}{
{
"Basic text file",
"/a/b.txt",
func(c *qt.C, p *Path) {
c.Assert(p.Name(), qt.Equals, "b.txt")
c.Assert(p.Base(), qt.Equals, "/a/b.txt")
c.Assert(p.Container(), qt.Equals, "a")
c.Assert(p.Dir(), qt.Equals, "/a")
c.Assert(p.Ext(), qt.Equals, "txt")
c.Assert(p.IsContent(), qt.IsFalse)
},
},
{
"Basic text file, upper case",
"/A/B.txt",
func(c *qt.C, p *Path) {
c.Assert(p.Name(), qt.Equals, "b.txt")
c.Assert(p.NameNoExt(), qt.Equals, "b")
c.Assert(p.NameNoIdentifier(), qt.Equals, "b")
c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "b")
c.Assert(p.Base(), qt.Equals, "/a/b.txt")
c.Assert(p.Ext(), qt.Equals, "txt")
},
},
{
"Basic text file, 1 space in dir",
"/a b/c.txt",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a-b/c.txt")
},
},
{
"Basic text file, 2 spaces in dir",
"/a b/c.txt",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a--b/c.txt")
},
},
{
"Basic text file, 1 space in filename",
"/a/b c.txt",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a/b-c.txt")
},
},
{
"Basic text file, 2 spaces in filename",
"/a/b c.txt",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a/b--c.txt")
},
},
{
"Basic text file, mixed case and spaces, unnormalized",
"/a/Foo BAR.txt",
func(c *qt.C, p *Path) {
pp := p.Unnormalized()
c.Assert(pp, qt.IsNotNil)
c.Assert(pp.BaseNameNoIdentifier(), qt.Equals, "Foo BAR")
},
},
{
"Basic Markdown file",
"/a/b/c.md",
func(c *qt.C, p *Path) {
c.Assert(p.IsContent(), qt.IsTrue)
c.Assert(p.IsLeafBundle(), qt.IsFalse)
c.Assert(p.Name(), qt.Equals, "c.md")
c.Assert(p.Base(), qt.Equals, "/a/b/c")
c.Assert(p.Section(), qt.Equals, "a")
c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "c")
c.Assert(p.Path(), qt.Equals, "/a/b/c.md")
c.Assert(p.Dir(), qt.Equals, "/a/b")
c.Assert(p.Container(), qt.Equals, "b")
c.Assert(p.ContainerDir(), qt.Equals, "/a/b")
c.Assert(p.Ext(), qt.Equals, "md")
},
},
{
"Content resource",
"/a/b.md",
func(c *qt.C, p *Path) {
c.Assert(p.Name(), qt.Equals, "b.md")
c.Assert(p.Base(), qt.Equals, "/a/b")
c.Assert(p.BaseNoLeadingSlash(), qt.Equals, "a/b")
c.Assert(p.Section(), qt.Equals, "a")
c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "b")
// Reclassify it as a content resource.
ModifyPathBundleTypeResource(p)
c.Assert(p.BundleType(), qt.Equals, PathTypeContentResource)
c.Assert(p.IsContent(), qt.IsTrue)
c.Assert(p.Name(), qt.Equals, "b.md")
c.Assert(p.Base(), qt.Equals, "/a/b.md")
},
},
{
"No ext",
"/a/b",
func(c *qt.C, p *Path) {
c.Assert(p.Name(), qt.Equals, "b")
c.Assert(p.NameNoExt(), qt.Equals, "b")
c.Assert(p.Base(), qt.Equals, "/a/b")
c.Assert(p.Ext(), qt.Equals, "")
},
},
{
"No ext, trailing slash",
"/a/b/",
func(c *qt.C, p *Path) {
c.Assert(p.Name(), qt.Equals, "b")
c.Assert(p.Base(), qt.Equals, "/a/b")
c.Assert(p.Ext(), qt.Equals, "")
},
},
{
"Identifiers",
"/a/b.a.b.no.txt",
func(c *qt.C, p *Path) {
c.Assert(p.Name(), qt.Equals, "b.a.b.no.txt")
c.Assert(p.NameNoIdentifier(), qt.Equals, "b.a.b")
c.Assert(p.NameNoLang(), qt.Equals, "b.a.b.txt")
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no"})
c.Assert(p.Base(), qt.Equals, "/a/b.a.b.txt")
c.Assert(p.BaseNoLeadingSlash(), qt.Equals, "a/b.a.b.txt")
c.Assert(p.PathNoLang(), qt.Equals, "/a/b.a.b.txt")
c.Assert(p.Ext(), qt.Equals, "txt")
c.Assert(p.PathNoIdentifier(), qt.Equals, "/a/b.a.b")
},
},
{
"Home branch cundle",
"/_index.md",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/")
c.Assert(p.Path(), qt.Equals, "/_index.md")
c.Assert(p.Container(), qt.Equals, "")
c.Assert(p.ContainerDir(), qt.Equals, "/")
},
},
{
"Index content file in root",
"/a/index.md",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a")
c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "a")
c.Assert(p.Container(), qt.Equals, "a")
c.Assert(p.Container(), qt.Equals, "a")
c.Assert(p.ContainerDir(), qt.Equals, "")
c.Assert(p.Dir(), qt.Equals, "/a")
c.Assert(p.Ext(), qt.Equals, "md")
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md"})
c.Assert(p.IsBranchBundle(), qt.IsFalse)
c.Assert(p.IsBundle(), qt.IsTrue)
c.Assert(p.IsLeafBundle(), qt.IsTrue)
c.Assert(p.Lang(), qt.Equals, "")
c.Assert(p.NameNoExt(), qt.Equals, "index")
c.Assert(p.NameNoIdentifier(), qt.Equals, "index")
c.Assert(p.NameNoLang(), qt.Equals, "index.md")
c.Assert(p.Section(), qt.Equals, "")
},
},
{
"Index content file with lang",
"/a/b/index.no.md",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a/b")
c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "b")
c.Assert(p.Container(), qt.Equals, "b")
c.Assert(p.ContainerDir(), qt.Equals, "/a")
c.Assert(p.Dir(), qt.Equals, "/a/b")
c.Assert(p.Ext(), qt.Equals, "md")
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "no"})
c.Assert(p.IsBranchBundle(), qt.IsFalse)
c.Assert(p.IsBundle(), qt.IsTrue)
c.Assert(p.IsLeafBundle(), qt.IsTrue)
c.Assert(p.Lang(), qt.Equals, "no")
c.Assert(p.NameNoExt(), qt.Equals, "index.no")
c.Assert(p.NameNoIdentifier(), qt.Equals, "index")
c.Assert(p.NameNoLang(), qt.Equals, "index.md")
c.Assert(p.PathNoLang(), qt.Equals, "/a/b/index.md")
c.Assert(p.Section(), qt.Equals, "a")
},
},
{
"Index branch content file",
"/a/b/_index.no.md",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a/b")
c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "b")
c.Assert(p.Container(), qt.Equals, "b")
c.Assert(p.ContainerDir(), qt.Equals, "/a")
c.Assert(p.Ext(), qt.Equals, "md")
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "no"})
c.Assert(p.IsBranchBundle(), qt.IsTrue)
c.Assert(p.IsBundle(), qt.IsTrue)
c.Assert(p.IsLeafBundle(), qt.IsFalse)
c.Assert(p.NameNoExt(), qt.Equals, "_index.no")
c.Assert(p.NameNoLang(), qt.Equals, "_index.md")
},
},
{
"Index root no slash",
"_index.md",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/")
c.Assert(p.Ext(), qt.Equals, "md")
c.Assert(p.Name(), qt.Equals, "_index.md")
},
},
{
"Index root",
"/_index.md",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/")
c.Assert(p.Ext(), qt.Equals, "md")
c.Assert(p.Name(), qt.Equals, "_index.md")
},
},
{
"Index first",
"/a/_index.md",
func(c *qt.C, p *Path) {
c.Assert(p.Section(), qt.Equals, "a")
},
},
{
"Index text file",
"/a/b/index.no.txt",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a/b/index.txt")
c.Assert(p.Ext(), qt.Equals, "txt")
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no"})
c.Assert(p.IsLeafBundle(), qt.IsFalse)
c.Assert(p.PathNoIdentifier(), qt.Equals, "/a/b/index")
},
},
{
"Empty",
"",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/")
c.Assert(p.Ext(), qt.Equals, "")
c.Assert(p.Name(), qt.Equals, "")
c.Assert(p.Path(), qt.Equals, "/")
},
},
{
"Slash",
"/",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/")
c.Assert(p.Ext(), qt.Equals, "")
c.Assert(p.Name(), qt.Equals, "")
},
},
{
"Trim Leading Slash bundle",
"foo/bar/index.no.md",
func(c *qt.C, p *Path) {
c.Assert(p.Path(), qt.Equals, "/foo/bar/index.no.md")
pp := p.TrimLeadingSlash()
c.Assert(pp.Path(), qt.Equals, "foo/bar/index.no.md")
c.Assert(pp.PathNoLang(), qt.Equals, "foo/bar/index.md")
c.Assert(pp.Base(), qt.Equals, "foo/bar")
c.Assert(pp.Dir(), qt.Equals, "foo/bar")
c.Assert(pp.ContainerDir(), qt.Equals, "foo")
c.Assert(pp.Container(), qt.Equals, "bar")
c.Assert(pp.BaseNameNoIdentifier(), qt.Equals, "bar")
},
},
{
"Trim Leading Slash file",
"foo/bar.txt",
func(c *qt.C, p *Path) {
c.Assert(p.Path(), qt.Equals, "/foo/bar.txt")
pp := p.TrimLeadingSlash()
c.Assert(pp.Path(), qt.Equals, "foo/bar.txt")
c.Assert(pp.PathNoLang(), qt.Equals, "foo/bar.txt")
c.Assert(pp.Base(), qt.Equals, "foo/bar.txt")
c.Assert(pp.Dir(), qt.Equals, "foo")
c.Assert(pp.ContainerDir(), qt.Equals, "foo")
c.Assert(pp.Container(), qt.Equals, "foo")
c.Assert(pp.BaseNameNoIdentifier(), qt.Equals, "bar")
},
},
{
"File separator",
filepath.FromSlash("/a/b/c.txt"),
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a/b/c.txt")
c.Assert(p.Ext(), qt.Equals, "txt")
c.Assert(p.Name(), qt.Equals, "c.txt")
c.Assert(p.Path(), qt.Equals, "/a/b/c.txt")
},
},
}
for _, test := range tests {
c.Run(test.name, func(c *qt.C) {
test.assert(c, testParser.Parse(files.ComponentFolderContent, test.path))
})
}
}
func TestHasExt(t *testing.T) {
c := qt.New(t)
c.Assert(HasExt("/a/b/c.txt"), qt.IsTrue)
c.Assert(HasExt("/a/b.c/d.txt"), qt.IsTrue)
c.Assert(HasExt("/a/b/c"), qt.IsFalse)
c.Assert(HasExt("/a/b.c/d"), qt.IsFalse)
}
+80
View File
@@ -0,0 +1,80 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package paths_test
import (
"testing"
"github.com/gohugoio/hugo/hugolib"
)
func TestRemovePathAccents(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ["taxonomy", "term"]
defaultContentLanguage = "en"
defaultContentLanguageInSubdir = true
[languages]
[languages.en]
weight = 1
[languages.fr]
weight = 2
removePathAccents = true
-- content/διακριτικός.md --
-- content/διακριτικός.fr.md --
-- layouts/_default/single.html --
{{ .Language.Lang }}|Single.
-- layouts/_default/list.html --
List
`
b := hugolib.Test(t, files)
b.AssertFileContent("public/en/διακριτικός/index.html", "en|Single")
b.AssertFileContent("public/fr/διακριτικος/index.html", "fr|Single")
}
func TestDisablePathToLower(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ["taxonomy", "term"]
defaultContentLanguage = "en"
defaultContentLanguageInSubdir = true
[languages]
[languages.en]
weight = 1
[languages.fr]
weight = 2
disablePathToLower = true
-- content/MySection/MyPage.md --
-- content/MySection/MyPage.fr.md --
-- content/MySection/MyBundle/index.md --
-- content/MySection/MyBundle/index.fr.md --
-- layouts/_default/single.html --
{{ .Language.Lang }}|Single.
-- layouts/_default/list.html --
{{ .Language.Lang }}|List.
`
b := hugolib.Test(t, files)
b.AssertFileContent("public/en/mysection/index.html", "en|List")
b.AssertFileContent("public/en/mysection/mypage/index.html", "en|Single")
b.AssertFileContent("public/fr/MySection/index.html", "fr|List")
b.AssertFileContent("public/fr/MySection/MyPage/index.html", "fr|Single")
b.AssertFileContent("public/en/mysection/mybundle/index.html", "en|Single")
b.AssertFileContent("public/fr/MySection/MyBundle/index.html", "fr|Single")
}
+27
View File
@@ -0,0 +1,27 @@
// Code generated by "stringer -type=PathType"; DO NOT EDIT.
package paths
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[PathTypeFile-0]
_ = x[PathTypeContentResource-1]
_ = x[PathTypeContentSingle-2]
_ = x[PathTypeLeaf-3]
_ = x[PathTypeBranch-4]
}
const _PathType_name = "PathTypeFilePathTypeContentResourcePathTypeContentSinglePathTypeLeafPathTypeBranch"
var _PathType_index = [...]uint8{0, 12, 35, 56, 68, 82}
func (i PathType) String() string {
if i < 0 || i >= PathType(len(_PathType_index)-1) {
return "PathType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _PathType_name[_PathType_index[i]:_PathType_index[i+1]]
}
+24 -10
View File
@@ -51,9 +51,10 @@ var pb pathBridge
// MakePermalink combines base URL with content path to create full URL paths.
// Example
// base: http://spf13.com/
// path: post/how-i-blog
// result: http://spf13.com/post/how-i-blog
//
// base: http://spf13.com/
// path: post/how-i-blog
// result: http://spf13.com/post/how-i-blog
func MakePermalink(host, plink string) *url.URL {
base, err := url.Parse(host)
if err != nil {
@@ -70,6 +71,8 @@ func MakePermalink(host, plink string) *url.URL {
}
base.Path = path.Join(base.Path, p.Path)
base.Fragment = p.Fragment
base.RawQuery = p.RawQuery
// path.Join will strip off the last /, so put it back if it was there.
hadTrailingSlash := (plink == "" && strings.HasSuffix(host, "/")) || strings.HasSuffix(p.Path, "/")
@@ -117,17 +120,19 @@ func PrettifyURL(in string) string {
// PrettifyURLPath takes a URL path to a content and converts it
// to enable pretty URLs.
// /section/name.html becomes /section/name/index.html
// /section/name/ becomes /section/name/index.html
// /section/name/index.html becomes /section/name/index.html
//
// /section/name.html becomes /section/name/index.html
// /section/name/ becomes /section/name/index.html
// /section/name/index.html becomes /section/name/index.html
func PrettifyURLPath(in string) string {
return prettifyPath(in, pb)
}
// Uglify does the opposite of PrettifyURLPath().
// /section/name/index.html becomes /section/name.html
// /section/name/ becomes /section/name.html
// /section/name.html becomes /section/name.html
//
// /section/name/index.html becomes /section/name.html
// /section/name/ becomes /section/name.html
// /section/name.html becomes /section/name.html
func Uglify(in string) string {
if path.Ext(in) == "" {
if len(in) < 2 {
@@ -158,7 +163,6 @@ func Uglify(in string) string {
// If ParseRequestURI fails, the input is just converted to OS specific slashes and returned.
func UrlToFilename(s string) (string, bool) {
u, err := url.ParseRequestURI(s)
if err != nil {
return filepath.FromSlash(s), false
}
@@ -179,3 +183,13 @@ func UrlToFilename(s string) (string, bool) {
return p, true
}
// URLEscape escapes unicode letters.
func URLEscape(uri string) string {
// escape unicode letters
u, err := url.Parse(uri)
if err != nil {
panic(err)
}
return u.String()
}
+1
View File
@@ -31,6 +31,7 @@ func TestMakePermalink(t *testing.T) {
{"http://abc.com", "bar", "http://abc.com/bar"},
{"http://abc.com/foo/bar", "post/bar", "http://abc.com/foo/bar/post/bar"},
{"http://abc.com/foo/bar", "post/bar/", "http://abc.com/foo/bar/post/bar/"},
{"http://abc.com/foo", "post/bar?a=b#c", "http://abc.com/foo/post/bar?a=b#c"},
}
for i, d := range data {
+72
View File
@@ -0,0 +1,72 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package predicate
// P is a predicate function that tests whether a value of type T satisfies some condition.
type P[T any] func(T) bool
// And returns a predicate that is a short-circuiting logical AND of this and the given predicates.
func (p P[T]) And(ps ...P[T]) P[T] {
return func(v T) bool {
for _, pp := range ps {
if !pp(v) {
return false
}
}
return p(v)
}
}
// Or returns a predicate that is a short-circuiting logical OR of this and the given predicates.
func (p P[T]) Or(ps ...P[T]) P[T] {
return func(v T) bool {
for _, pp := range ps {
if pp(v) {
return true
}
}
return p(v)
}
}
// Negate returns a predicate that is a logical negation of this predicate.
func (p P[T]) Negate() P[T] {
return func(v T) bool {
return !p(v)
}
}
// Filter returns a new slice holding only the elements of s that satisfy p.
// Filter modifies the contents of the slice s and returns the modified slice, which may have a smaller length.
func (p P[T]) Filter(s []T) []T {
var n int
for _, v := range s {
if p(v) {
s[n] = v
n++
}
}
return s[:n]
}
// FilterCopy returns a new slice holding only the elements of s that satisfy p.
func (p P[T]) FilterCopy(s []T) []T {
var result []T
for _, v := range s {
if p(v) {
result = append(result, v)
}
}
return result
}
+83
View File
@@ -0,0 +1,83 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package predicate_test
import (
"testing"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/predicate"
)
func TestAdd(t *testing.T) {
c := qt.New(t)
var p predicate.P[int] = intP1
c.Assert(p(1), qt.IsTrue)
c.Assert(p(2), qt.IsFalse)
neg := p.Negate()
c.Assert(neg(1), qt.IsFalse)
c.Assert(neg(2), qt.IsTrue)
and := p.And(intP2)
c.Assert(and(1), qt.IsFalse)
c.Assert(and(2), qt.IsFalse)
c.Assert(and(10), qt.IsTrue)
or := p.Or(intP2)
c.Assert(or(1), qt.IsTrue)
c.Assert(or(2), qt.IsTrue)
c.Assert(or(10), qt.IsTrue)
c.Assert(or(11), qt.IsFalse)
}
func TestFilter(t *testing.T) {
c := qt.New(t)
var p predicate.P[int] = intP1
p = p.Or(intP2)
ints := []int{1, 2, 3, 4, 1, 6, 7, 8, 2}
c.Assert(p.Filter(ints), qt.DeepEquals, []int{1, 2, 1, 2})
c.Assert(ints, qt.DeepEquals, []int{1, 2, 1, 2, 1, 6, 7, 8, 2})
}
func TestFilterCopy(t *testing.T) {
c := qt.New(t)
var p predicate.P[int] = intP1
p = p.Or(intP2)
ints := []int{1, 2, 3, 4, 1, 6, 7, 8, 2}
c.Assert(p.FilterCopy(ints), qt.DeepEquals, []int{1, 2, 1, 2})
c.Assert(ints, qt.DeepEquals, []int{1, 2, 3, 4, 1, 6, 7, 8, 2})
}
var intP1 = func(i int) bool {
if i == 10 {
return true
}
return i == 1
}
var intP2 = func(i int) bool {
if i == 10 {
return true
}
return i == 2
}
+93
View File
@@ -0,0 +1,93 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rungroup
import (
"context"
"golang.org/x/sync/errgroup"
)
// Group is a group of workers that can be used to enqueue work and wait for
// them to finish.
type Group[T any] interface {
Enqueue(T) error
Wait() error
}
type runGroup[T any] struct {
ctx context.Context
g *errgroup.Group
ch chan T
}
// Config is the configuration for a new Group.
type Config[T any] struct {
NumWorkers int
Handle func(context.Context, T) error
}
// Run creates a new Group with the given configuration.
func Run[T any](ctx context.Context, cfg Config[T]) Group[T] {
if cfg.NumWorkers <= 0 {
cfg.NumWorkers = 1
}
if cfg.Handle == nil {
panic("Handle must be set")
}
g, ctx := errgroup.WithContext(ctx)
// Buffered for performance.
ch := make(chan T, cfg.NumWorkers)
for i := 0; i < cfg.NumWorkers; i++ {
g.Go(func() error {
for {
select {
case <-ctx.Done():
return nil
case v, ok := <-ch:
if !ok {
return nil
}
if err := cfg.Handle(ctx, v); err != nil {
return err
}
}
}
})
}
return &runGroup[T]{
ctx: ctx,
g: g,
ch: ch,
}
}
// Enqueue enqueues a new item to be handled by the workers.
func (r *runGroup[T]) Enqueue(t T) error {
select {
case <-r.ctx.Done():
return nil
case r.ch <- t:
}
return nil
}
// Wait waits for all workers to finish and returns the first error.
func (r *runGroup[T]) Wait() error {
close(r.ch)
return r.g.Wait()
}
+44
View File
@@ -0,0 +1,44 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rungroup
import (
"context"
"testing"
qt "github.com/frankban/quicktest"
)
func TestNew(t *testing.T) {
c := qt.New(t)
var result int
adder := func(ctx context.Context, i int) error {
result += i
return nil
}
g := Run[int](
context.Background(),
Config[int]{
Handle: adder,
},
)
c.Assert(g, qt.IsNotNil)
g.Enqueue(32)
g.Enqueue(33)
c.Assert(g.Wait(), qt.IsNil)
c.Assert(result, qt.Equals, 65)
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2022 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
-4
View File
@@ -57,7 +57,6 @@ line 3`
c := qt.New(t)
c.Assert(collected, qt.DeepEquals, []string{"line 1\n", "line 2\n", "\n", "line 3"})
}
func BenchmarkVisitLinesAfter(b *testing.B) {
@@ -68,9 +67,6 @@ func BenchmarkVisitLinesAfter(b *testing.B) {
for i := 0; i < b.N; i++ {
VisitLinesAfter(lines, func(s string) {
})
}
}
-1
View File
@@ -80,7 +80,6 @@ func ToStringSlicePreserveStringE(v any) ([]string, error) {
default:
return nil, fmt.Errorf("failed to convert %T to a string slice", v)
}
}
// TypeToString converts v to a string if it's a valid string type.
-1
View File
@@ -45,5 +45,4 @@ func TestToDuration(t *testing.T) {
c.Assert(ToDuration("200"), qt.Equals, 200*time.Millisecond)
c.Assert(ToDuration("4m"), qt.Equals, 4*time.Minute)
c.Assert(ToDuration("asdfadf"), qt.Equals, time.Duration(0))
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
+13 -2
View File
@@ -35,11 +35,11 @@ func NewEvictingStringQueue(size int) *EvictingStringQueue {
}
// Add adds a new string to the tail of the queue if it's not already there.
func (q *EvictingStringQueue) Add(v string) {
func (q *EvictingStringQueue) Add(v string) *EvictingStringQueue {
q.mu.Lock()
if q.set[v] {
q.mu.Unlock()
return
return q
}
if len(q.set) == q.size {
@@ -50,6 +50,17 @@ func (q *EvictingStringQueue) Add(v string) {
q.set[v] = true
q.vals = append(q.vals, v)
q.mu.Unlock()
return q
}
func (q *EvictingStringQueue) Len() int {
if q == nil {
return 0
}
q.mu.Lock()
defer q.mu.Unlock()
return len(q.vals)
}
// Contains returns whether the queue contains v.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2022 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2022 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
+18
View File
@@ -92,5 +92,23 @@ type DevMarker interface {
DevOnly()
}
// Unwrapper is implemented by types that can unwrap themselves.
type Unwrapper interface {
// Unwrapv is for internal use only.
// It got its slightly odd name to prevent collisions with user types.
Unwrapv() any
}
// LowHigh is typically used to represent a slice boundary.
type LowHigh struct {
Low int
High int
}
// This is only used for debugging purposes.
var InvocationCounter atomic.Int64
// NewTrue returns a pointer to b.
func NewBool(b bool) *bool {
return &b
}
+15 -13
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -23,10 +23,12 @@ import (
// A BaseURL in Hugo is normally on the form scheme://path, but the
// form scheme: is also valid (mailto:hugo@rules.com).
type BaseURL struct {
url *url.URL
WithPath string
WithoutPath string
BasePath string
url *url.URL
WithPath string
WithPathNoTrailingSlash string
WithoutPath string
BasePath string
BasePathNoTrailingSlash string
}
func (b BaseURL) String() string {
@@ -92,19 +94,19 @@ func NewBaseURLFromString(b string) (BaseURL, error) {
return BaseURL{}, err
}
return newBaseURLFromURL(u)
}
func newBaseURLFromURL(u *url.URL) (BaseURL, error) {
baseURL := BaseURL{url: u, WithPath: u.String()}
var baseURLNoPath = baseURL.URL()
// A baseURL should always have a trailing slash, see #11669.
if !strings.HasSuffix(u.Path, "/") {
u.Path += "/"
}
baseURL := BaseURL{url: u, WithPath: u.String(), WithPathNoTrailingSlash: strings.TrimSuffix(u.String(), "/")}
baseURLNoPath := baseURL.URL()
baseURLNoPath.Path = ""
baseURL.WithoutPath = baseURLNoPath.String()
basePath := u.Path
if basePath != "" && basePath != "/" {
baseURL.BasePath = basePath
}
baseURL.BasePath = u.Path
baseURL.BasePathNoTrailingSlash = strings.TrimSuffix(u.Path, "/")
return baseURL, nil
}
+21 -7
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -21,17 +21,24 @@ import (
func TestBaseURL(t *testing.T) {
c := qt.New(t)
b, err := NewBaseURLFromString("http://example.com")
b, err := NewBaseURLFromString("http://example.com/")
c.Assert(err, qt.IsNil)
c.Assert(b.String(), qt.Equals, "http://example.com")
c.Assert(b.String(), qt.Equals, "http://example.com/")
b, err = NewBaseURLFromString("http://example.com")
c.Assert(err, qt.IsNil)
c.Assert(b.String(), qt.Equals, "http://example.com/")
c.Assert(b.WithPathNoTrailingSlash, qt.Equals, "http://example.com")
c.Assert(b.BasePath, qt.Equals, "/")
p, err := b.WithProtocol("webcal://")
c.Assert(err, qt.IsNil)
c.Assert(p.String(), qt.Equals, "webcal://example.com")
c.Assert(p.String(), qt.Equals, "webcal://example.com/")
p, err = b.WithProtocol("webcal")
c.Assert(err, qt.IsNil)
c.Assert(p.String(), qt.Equals, "webcal://example.com")
c.Assert(p.String(), qt.Equals, "webcal://example.com/")
_, err = b.WithProtocol("mailto:")
c.Assert(err, qt.Not(qt.IsNil))
@@ -57,11 +64,18 @@ func TestBaseURL(t *testing.T) {
b, err = NewBaseURLFromString("")
c.Assert(err, qt.IsNil)
c.Assert(b.String(), qt.Equals, "")
c.Assert(b.String(), qt.Equals, "/")
// BaseURL with sub path
b, err = NewBaseURLFromString("http://example.com/sub")
c.Assert(err, qt.IsNil)
c.Assert(b.String(), qt.Equals, "http://example.com/sub")
c.Assert(b.String(), qt.Equals, "http://example.com/sub/")
c.Assert(b.WithPathNoTrailingSlash, qt.Equals, "http://example.com/sub")
c.Assert(b.BasePath, qt.Equals, "/sub/")
c.Assert(b.BasePathNoTrailingSlash, qt.Equals, "/sub")
b, err = NewBaseURLFromString("http://example.com/sub/")
c.Assert(err, qt.IsNil)
c.Assert(b.String(), qt.Equals, "http://example.com/sub/")
c.Assert(b.HostURL(), qt.Equals, "http://example.com")
}
+13
View File
@@ -52,3 +52,16 @@ func Eq(v1, v2 any) bool {
return v1 == v2
}
// ProbablyEq returns whether v1 is probably equal to v2.
func ProbablyEq(v1, v2 any) bool {
if Eq(v1, v2) {
return true
}
if peqer, ok := v1.(ProbablyEqer); ok {
return peqer.ProbablyEq(v2)
}
return false
}
-1
View File
@@ -79,5 +79,4 @@ func BenchmarkStringSort(b *testing.B) {
})
}
})
}
+132 -79
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Hugo Authors. All rights reserved.
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -27,8 +27,11 @@ import (
"time"
"github.com/gohugoio/hugo/cache/filecache"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/common/urls"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/privacy"
@@ -45,6 +48,7 @@ import (
"github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/related"
"github.com/gohugoio/hugo/resources/images"
"github.com/gohugoio/hugo/resources/kinds"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/page/pagemeta"
"github.com/spf13/afero"
@@ -61,6 +65,7 @@ type InternalConfig struct {
Verbose bool
Clock string
Watch bool
FastRenderMode bool
LiveReloadPort int
}
@@ -203,7 +208,7 @@ func (c Config) cloneForLang() *Config {
x.DisableKinds = copyStringSlice(x.DisableKinds)
x.DisableLanguages = copyStringSlice(x.DisableLanguages)
x.MainSections = copyStringSlice(x.MainSections)
x.IgnoreErrors = copyStringSlice(x.IgnoreErrors)
x.IgnoreLogs = copyStringSlice(x.IgnoreLogs)
x.IgnoreFiles = copyStringSlice(x.IgnoreFiles)
x.Theme = copyStringSlice(x.Theme)
@@ -239,9 +244,14 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
disabledKinds := make(map[string]bool)
for _, kind := range c.DisableKinds {
kind = strings.ToLower(kind)
if kind == "taxonomyterm" {
if newKind := kinds.IsDeprecatedAndReplacedWith(kind); newKind != "" {
logger.Deprecatef(false, "Kind %q used in disableKinds is deprecated, use %q instead.", kind, newKind)
// Legacy config.
kind = "taxonomy"
kind = newKind
}
if kinds.GetKindAny(kind) == "" {
logger.Warnf("Unknown kind %q in disableKinds configuration.", kind)
continue
}
disabledKinds[kind] = true
}
@@ -249,9 +259,17 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
isRssDisabled := disabledKinds["rss"]
outputFormats := c.OutputFormats.Config
for kind, formats := range c.Outputs {
if newKind := kinds.IsDeprecatedAndReplacedWith(kind); newKind != "" {
logger.Deprecatef(false, "Kind %q used in outputs configuration is deprecated, use %q instead.", kind, newKind)
kind = newKind
}
if disabledKinds[kind] {
continue
}
if kinds.GetKindAny(kind) == "" {
logger.Warnf("Unknown kind %q in outputs configuration.", kind)
continue
}
for _, format := range formats {
if isRssDisabled && format == "rss" {
// Legacy config.
@@ -268,12 +286,13 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
disabledLangs := make(map[string]bool)
for _, lang := range c.DisableLanguages {
if lang == c.DefaultContentLanguage {
return fmt.Errorf("cannot disable default content language %q", lang)
}
disabledLangs[lang] = true
}
for lang, language := range c.Languages {
if !language.Disabled && disabledLangs[lang] {
language.Disabled = true
c.Languages[lang] = language
}
if language.Disabled {
disabledLangs[lang] = true
if lang == c.DefaultContentLanguage {
@@ -282,9 +301,9 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
}
}
ignoredErrors := make(map[string]bool)
for _, err := range c.IgnoreErrors {
ignoredErrors[strings.ToLower(err)] = true
ignoredLogIDs := make(map[string]bool)
for _, err := range c.IgnoreLogs {
ignoredLogIDs[strings.ToLower(err)] = true
}
baseURL, err := urls.NewBaseURLFromString(c.BaseURL)
@@ -340,7 +359,7 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
BaseURLLiveReload: baseURL,
DisabledKinds: disabledKinds,
DisabledLanguages: disabledLangs,
IgnoredErrors: ignoredErrors,
IgnoredLogs: ignoredLogIDs,
KindOutputFormats: kindOutputFormats,
CreateTitle: helpers.GetTitleFunc(c.TitleCaseStyle),
IsUglyURLSection: isUglyURL,
@@ -377,7 +396,7 @@ type ConfigCompiled struct {
KindOutputFormats map[string]output.Formats
DisabledKinds map[string]bool
DisabledLanguages map[string]bool
IgnoredErrors map[string]bool
IgnoredLogs map[string]bool
CreateTitle func(s string) string
IsUglyURLSection func(section string) bool
IgnoreFile func(filename string) bool
@@ -393,15 +412,19 @@ type ConfigCompiled struct {
}
// This may be set after the config is compiled.
func (c *ConfigCompiled) SetMainSectionsIfNotSet(sections []string) {
func (c *ConfigCompiled) SetMainSections(sections []string) {
c.mu.Lock()
defer c.mu.Unlock()
if c.MainSections != nil {
return
}
c.MainSections = sections
}
// IsMainSectionsSet returns whether the main sections have been set.
func (c *ConfigCompiled) IsMainSectionsSet() bool {
c.mu.Lock()
defer c.mu.Unlock()
return c.MainSections != nil
}
// This is set after the config is compiled by the server command.
func (c *ConfigCompiled) SetBaseURL(baseURL, baseURLLiveReload urls.BaseURL) {
c.BaseURL = baseURL
@@ -410,7 +433,6 @@ func (c *ConfigCompiled) SetBaseURL(baseURL, baseURLLiveReload urls.BaseURL) {
// RootConfig holds all the top-level configuration options in Hugo
type RootConfig struct {
// The base URL of the site.
// Note that the default value is empty, but Hugo requires a valid URL (e.g. "https://example.com/") to work properly.
// <docsmeta>{"identifiers": ["URL"] }</docsmeta>
@@ -481,8 +503,8 @@ type RootConfig struct {
// Enable to disable the build lock file.
NoBuildLock bool
// A list of error IDs to ignore.
IgnoreErrors []string
// A list of log IDs to ignore.
IgnoreLogs []string
// A list of regexps that match paths to ignore.
// Deprecated: Use the settings on module imports.
@@ -633,13 +655,16 @@ type Configs struct {
LanguageConfigMap map[string]*Config
LanguageConfigSlice []*Config
IsMultihost bool
Languages langs.Languages
LanguagesDefaultFirst langs.Languages
IsMultihost bool
Modules modules.Modules
ModulesClient *modules.Client
// All below is set in Init.
Languages langs.Languages
LanguagesDefaultFirst langs.Languages
ContentPathParser paths.PathParser
configLangs []config.AllProvider
}
@@ -659,6 +684,59 @@ func (c *Configs) IsZero() bool {
}
func (c *Configs) Init() error {
var languages langs.Languages
defaultContentLanguage := c.Base.DefaultContentLanguage
for k, v := range c.LanguageConfigMap {
c.LanguageConfigSlice = append(c.LanguageConfigSlice, v)
languageConf := v.Languages[k]
language, err := langs.NewLanguage(k, defaultContentLanguage, v.TimeZone, languageConf)
if err != nil {
return err
}
languages = append(languages, language)
}
// Sort the sites by language weight (if set) or lang.
sort.Slice(languages, func(i, j int) bool {
li := languages[i]
lj := languages[j]
if li.Weight != lj.Weight {
return li.Weight < lj.Weight
}
return li.Lang < lj.Lang
})
for _, l := range languages {
c.LanguageConfigSlice = append(c.LanguageConfigSlice, c.LanguageConfigMap[l.Lang])
}
// Filter out disabled languages.
var n int
for _, l := range languages {
if !l.Disabled {
languages[n] = l
n++
}
}
languages = languages[:n]
var languagesDefaultFirst langs.Languages
for _, l := range languages {
if l.Lang == defaultContentLanguage {
languagesDefaultFirst = append(languagesDefaultFirst, l)
}
}
for _, l := range languages {
if l.Lang != defaultContentLanguage {
languagesDefaultFirst = append(languagesDefaultFirst, l)
}
}
c.Languages = languages
c.LanguagesDefaultFirst = languagesDefaultFirst
c.ContentPathParser = paths.PathParser{LanguageIndex: languagesDefaultFirst.AsIndexSet(), IsLangDisabled: c.Base.IsLangDisabled}
c.configLangs = make([]config.AllProvider, len(c.Languages))
for i, l := range c.LanguagesDefaultFirst {
c.configLangs[i] = ConfigLanguage{
@@ -729,13 +807,13 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
cfg := res.Cfg
all := &Config{}
err := decodeConfigFromParams(fs, bcfg, cfg, all, nil)
err := decodeConfigFromParams(fs, logger, bcfg, cfg, all, nil)
if err != nil {
return nil, err
}
langConfigMap := make(map[string]*Config)
var langConfigs []*Config
languagesConfig := cfg.GetStringMap("languages")
var isMultiHost bool
@@ -769,7 +847,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
// We accidentally allowed it in the past, so we need to support it a little longer,
// But log a warning.
if _, found := params[kk]; !found {
helpers.Deprecated(fmt.Sprintf("config: languages.%s.%s: custom params on the language top level", k, kk), fmt.Sprintf("Put the value below [languages.%s.params]. See https://gohugo.io/content-management/multilingual/#changes-in-hugo-01120", k), false)
hugo.Deprecate(fmt.Sprintf("config: languages.%s.%s: custom params on the language top level", k, kk), fmt.Sprintf("Put the value below [languages.%s.params]. See https://gohugo.io/content-management/multilingual/#changes-in-hugo-01120", k), "v0.112.0")
params[kk] = vv
}
}
@@ -817,13 +895,25 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
// Create a copy of the complete config and replace the root keys with the language specific ones.
clone := all.cloneForLang()
if err := decodeConfigFromParams(fs, bcfg, mergedConfig, clone, differentRootKeys); err != nil {
if err := decodeConfigFromParams(fs, logger, bcfg, mergedConfig, clone, differentRootKeys); err != nil {
return nil, fmt.Errorf("failed to decode config for language %q: %w", k, err)
}
if err := clone.CompileConfig(logger); err != nil {
return nil, err
}
// Adjust Goldmark config defaults for multilingual, single-host sites.
if len(languagesConfig) > 1 && !isMultiHost && !clone.Markup.Goldmark.DuplicateResourceFiles {
if !clone.Markup.Goldmark.DuplicateResourceFiles {
if clone.Markup.Goldmark.RenderHooks.Link.EnableDefault == nil {
clone.Markup.Goldmark.RenderHooks.Link.EnableDefault = types.NewBool(true)
}
if clone.Markup.Goldmark.RenderHooks.Image.EnableDefault == nil {
clone.Markup.Goldmark.RenderHooks.Image.EnableDefault = types.NewBool(true)
}
}
}
langConfigMap[k] = clone
case maps.ParamsMergeStrategy:
default:
@@ -832,61 +922,24 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
}
}
var languages langs.Languages
defaultContentLanguage := all.DefaultContentLanguage
for k, v := range langConfigMap {
languageConf := v.Languages[k]
language, err := langs.NewLanguage(k, defaultContentLanguage, v.TimeZone, languageConf)
if err != nil {
return nil, err
}
languages = append(languages, language)
}
// Sort the sites by language weight (if set) or lang.
sort.Slice(languages, func(i, j int) bool {
li := languages[i]
lj := languages[j]
if li.Weight != lj.Weight {
return li.Weight < lj.Weight
}
return li.Lang < lj.Lang
})
for _, l := range languages {
langConfigs = append(langConfigs, langConfigMap[l.Lang])
}
var languagesDefaultFirst langs.Languages
for _, l := range languages {
if l.Lang == defaultContentLanguage {
languagesDefaultFirst = append(languagesDefaultFirst, l)
}
}
for _, l := range languages {
if l.Lang != defaultContentLanguage {
languagesDefaultFirst = append(languagesDefaultFirst, l)
}
}
bcfg.PublishDir = all.PublishDir
res.BaseConfig = bcfg
all.CommonDirs.CacheDir = bcfg.CacheDir
for _, l := range langConfigMap {
l.CommonDirs.CacheDir = bcfg.CacheDir
}
cm := &Configs{
Base: all,
LanguageConfigMap: langConfigMap,
LanguageConfigSlice: langConfigs,
LoadingInfo: res,
IsMultihost: isMultiHost,
Languages: languages,
LanguagesDefaultFirst: languagesDefaultFirst,
Base: all,
LanguageConfigMap: langConfigMap,
LoadingInfo: res,
IsMultihost: isMultiHost,
}
return cm, nil
}
func decodeConfigFromParams(fs afero.Fs, bcfg config.BaseConfig, p config.Provider, target *Config, keys []string) error {
func decodeConfigFromParams(fs afero.Fs, logger loggers.Logger, bcfg config.BaseConfig, p config.Provider, target *Config, keys []string) error {
var decoderSetups []decodeWeight
if len(keys) == 0 {
@@ -898,7 +951,7 @@ func decodeConfigFromParams(fs afero.Fs, bcfg config.BaseConfig, p config.Provid
if v, found := allDecoderSetups[key]; found {
decoderSetups = append(decoderSetups, v)
} else {
return fmt.Errorf("unknown config key %q", key)
logger.Warnf("Skip unknown config key %q", key)
}
}
}
@@ -913,7 +966,7 @@ func decodeConfigFromParams(fs afero.Fs, bcfg config.BaseConfig, p config.Provid
})
for _, v := range decoderSetups {
p := decodeConfig{p: p, c: target, fs: fs, bcfg: bcfg}
p := decodeConfig{p: p, c: target, fs: fs, logger: logger, bcfg: bcfg}
if err := v.decode(v, p); err != nil {
return fmt.Errorf("failed to decode %q: %w", v.key, err)
}
@@ -935,11 +988,11 @@ func createDefaultOutputFormats(allFormats output.Formats) map[string][]string {
}
m := map[string][]string{
page.KindPage: {htmlOut.Name},
page.KindHome: defaultListTypes,
page.KindSection: defaultListTypes,
page.KindTerm: defaultListTypes,
page.KindTaxonomy: defaultListTypes,
kinds.KindPage: {htmlOut.Name},
kinds.KindHome: defaultListTypes,
kinds.KindSection: defaultListTypes,
kinds.KindTerm: defaultListTypes,
kinds.KindTaxonomy: defaultListTypes,
}
// May be disabled

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