Compare commits

..

318 Commits

Author SHA1 Message Date
Bjørn Erik Pedersen 0c8232e3bd github: Add a "free space" step on Ubuntu 2024-04-14 16:14:16 +02:00
Bjørn Erik Pedersen bfc3122f8e helpers: Add BenchmarkTrimShortHTML 2024-04-14 15:54:49 +02:00
Bjørn Erik Pedersen 00ae8e8c72 github: Update actions 2024-04-14 13:40:46 +02:00
Bjørn Erik Pedersen e423e56273 github: Format GitHub actions files 2024-04-14 13:40:46 +02:00
Joe Mooring 09eb822822 hugolib: Display server address after each rebuild
Closes #12359
2024-04-13 22:16:00 +02:00
Joe Mooring a6e8439176 resources/page: Add taxonomies Page method
Closes #12316
2024-04-12 16:26:02 +02:00
Bjørn Erik Pedersen 38f68cd162 commands: Adjust completions 2024-04-11 15:34:26 +02:00
Ville Skyttä a67650b6f7 completion: Improve existing argument completions, add many more
Do not offer filenames to arguments not taking one, complete arguments
of options taking resource kinds, directory names, --logLevel, release
--step, config and new --format.

As an internal refactoring, use higher level functions to set flag
completions.  SetAnnotation works, but is more verbose than
alternatives, and uses bash specific wording.

While at it, move setting completions next to flag definitions
consistently.

Remove superfluous --destination completer setting, which is already
set elsewhere.
2024-04-11 15:34:26 +02:00
Bjørn Erik Pedersen 2a060b37a3 Upgrade to Go 1.22.2
Closes #12351
2024-04-11 15:14:28 +02:00
dependabot[bot] 97df6be59f build(deps): bump golang.org/x/tools from 0.19.0 to 0.20.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.19.0 to 0.20.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.19.0...v0.20.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-04-11 14:18:55 +02:00
Bjørn Erik Pedersen 9323376dfa github: Fix CI build 2024-04-11 13:29:19 +02:00
Eitan Adler bf0b140364 all: Fix duplicate words in comments 2024-04-11 09:31:33 +02:00
dependabot[bot] e9b8bec433 build(deps): bump golang.org/x/net from 0.23.0 to 0.24.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.23.0 to 0.24.0.
- [Commits](https://github.com/golang/net/compare/v0.23.0...v0.24.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-11 09:29:14 +02:00
dependabot[bot] 888cc1e61e build(deps): bump github.com/getkin/kin-openapi from 0.123.0 to 0.124.0
Bumps [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi) from 0.123.0 to 0.124.0.
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.123.0...v0.124.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-04-11 09:28:50 +02:00
Christian Oliff 17765a7451 all: Typo fixes 2024-04-11 09:23:17 +02:00
Bjørn Erik Pedersen 92de8625c7 babel: Run go fmt 2024-04-08 18:28:04 +02:00
guangwu 7907935a42 babel: Close file before removing 2024-04-08 15:23:08 +02:00
dependabot[bot] 02d5ec14f3 bump golang.org/x/mod from 0.16.0 to 0.17.0
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.16.0 to 0.17.0.
- [Commits](https://github.com/golang/mod/compare/v0.16.0...v0.17.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-05 17:44:24 +02:00
curegit 26640525a3 hugolib: Fix regression for blank summaries
Fix regression in content summarization so that we can use empty
summary by using the manual summary divider. Since v0.123, there
has been the regression that causes Hugo to use automatic summary
generation when the manual summary results in an empty string,
even if there is a `<!--more-->` summary divider.
2024-04-05 17:43:55 +02:00
Bjørn Erik Pedersen 488b21d15b Fix sectionPagesMenu for pages in root level
Fixes #12306
2024-04-05 17:38:08 +02:00
Soren L. Hansen 4500b0e423 resources/page: Escape hash sign in permalinks
When creating a link to a file with a `#` in the filename, the link gets
truncated. This happens because the filename is eventaully passed to
`url.Parse` which (correctly!) interprets the `#` as fragment separator.

This commit escapes the `#` in the filename before creating the link.

Fixes #4926
Fixes #8232
Fixes #12342

Co-authored-by: Joe Mooring <joe.mooring@veriphor.com>
2024-04-05 15:57:02 +02:00
dependabot[bot] 060cce0a91 build(deps): bump github.com/pelletier/go-toml/v2 from 2.1.1 to 2.2.0
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.1.1 to 2.2.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.1.1...v2.2.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>
2024-04-05 11:28:58 +02:00
dependabot[bot] 5608ba1f75 build(deps): bump github.com/yuin/goldmark from 1.7.0 to 1.7.1
Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.7.0 to 1.7.1.
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.7.0...v1.7.1)

---
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>
2024-04-05 11:27:59 +02:00
Joe Mooring 7bf1abfc55 tpl/strings: Improve type checking 2024-04-04 18:34:55 +02:00
dependabot[bot] 2fedca6c8a build(deps): bump github.com/aws/aws-sdk-go-v2/service/cloudfront
Bumps [github.com/aws/aws-sdk-go-v2/service/cloudfront](https://github.com/aws/aws-sdk-go-v2) from 1.32.6 to 1.35.4.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/fsx/v1.32.6...service/ecs/v1.35.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-04 11:16:30 +02:00
dependabot[bot] 07873b74bd build(deps): bump golang.org/x/net from 0.22.0 to 0.23.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.22.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.22.0...v0.23.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-04 11:15:10 +02:00
Joe Mooring 8a0ea12d8a tpl/tplimpl: Improve youtube shortcode
Changes:

- Add query string params for controls, loop, mute, start, and end
- Add iframe loading attribute
- Obtain default iframe title from YouTube oEmbed API
- Fix autoplay feature
- Improve readability

Closes #3694
Closes #9213
Closes #10520
Closes #10575
Closes #10576

Co-authored-by: sgharms <sgharms@stevengharms.com>
2024-04-04 11:08:30 +02:00
seiya 6f07e5976d errors: Return error from cast.ToStringE() consistently 2024-04-04 11:00:14 +02:00
Joe Mooring 2da4ec5738 tpl/tplimpl: Improve embedded opengraph template
Changes:

- Add tags per documentation
- Prefer site.Title over site.Params.title
- Plainify titles, tags, and descriptions
- Add fallback values for locale
- Fix pages related by series
- Improve readability

Closes #8296
Closes #8698
Closes #8991
Closes #9818
Closes #9866
Closes #10647

Co-authored-by: tomy0000000 <git@tomy.me>
Co-authored-by: sean-au <sean@powerfulwebdesign.com.au>
2024-04-02 18:35:43 +02:00
Joe Mooring 6624979e1b tpl/strings: Create strings.Diff template function
Closes #12330
2024-04-02 18:25:44 +02:00
Bjørn Erik Pedersen 983b8d537c Fix resource bundling for overlapping page.md vs page.txt
Fixes #12320
2024-04-02 14:32:05 +02:00
Joe Mooring 6738a3e79d tpl/tplimpl: Optionally exclude content from sitemap
Define global inclusion/exclusion in site configuration, and override
via front matter. For example, to exclude a page from the sitemap:

    [sitemap]
    disable = true # default is false

Closes #653
Closes #12282

Co-authored-by: kolappannathan <kolappannathan@users.noreply.github.com>
Co-authored-by: felicianotech <FelicianoTech@gmail.com>
2024-04-02 11:21:03 +02:00
Joe Mooring 2f7df4b926 tpl/tplimpl: Remove trailing slash from void elements
Closes #11867
2024-04-01 08:07:02 -07:00
Joe Mooring f0a26cf58e tpl/tplimpl: Update RSS template
- Use publication date for pubdate
- Include version in generator element

Closes #3918
Closes #11692
2024-03-30 18:32:58 +01:00
Joe Mooring 74ce5dc841 tpl/tplimpl: Update schema template
Changes:

- Remove trailing comma from list of keywords.
- Improve keywords precedence:
  1. Use "keywords" term page titles.
  2. Use "keywords" from front matter if "keywords" is not a taxonomy.
  3. Use "tags" term page titles.
  4. Use term page titles from all taxonomies.
- Enable schema for all page kinds, previously limited to kind = page.
- Remove trailing slashes from void elements.
- Improve readability.

Closes #7570

Co-authored by: 0urobor0s <0urobor0s@users.noreply.github.com>
2024-03-28 14:56:02 +01:00
Joe Mooring 54a8f0ce21 resources: Use different cache key when copying resources
Closes #10412
Closes #12310
2024-03-27 09:59:59 +01:00
Bjørn Erik Pedersen 38e05bd3c7 Fix panic with debug.Dump with Page when running the server
This replaces the current implementation with `json.MarshalIndent` which doesn't produce the same output, but at least it doesn't crash.

There's a bug in the upstream `litter` library. This can probably be fixed, but that needs to wait.

I have tested `go-spew` which does not crash, but it is very data racy in this context.

FIxes #12309
2024-03-26 20:41:30 +01:00
Joe Mooring ebfca61ac4 tpl/tplimpl: Update Google Analytics template and config
Google Analytics 4 (GA4) replaced Google Universal Analytics (UA)
effective 1 July 2023.

See https://support.google.com/analytics/answer/11583528.

Changes:

- Update tpl/tplimpl/embedded/templates/google_analytics.html
- Remove tpl/tplimpl/embedded/templates/google_analytics_async.html
- Remove extraneous config settings

Closes #11802
Closes #10093
2024-03-26 15:40:51 +01:00
Joe Mooring e1917740af hugolib: Conditionally suppress .Site.Author deprecation notice
Suppress the .Site.Author deprecation notice unless the Author key
is present and not empty in the site configuration.

Closes #12297
2024-03-26 10:28:03 +01:00
George Ma 27414d43a0 resources/page: Fix GoDoc comment 2024-03-22 08:56:10 +01:00
Joe Mooring c837f36ab4 markup/asciidocext: Add Level to Heading struct
Closes #12291
2024-03-21 19:14:49 +01:00
hugoreleaser a2f67152b3 releaser: Prepare repository for 0.125.0-DEV
[ci skip]
2024-03-20 11:53:43 +00:00
hugoreleaser db083b05f1 releaser: Bump versions for release of 0.124.1
[ci skip]
2024-03-20 11:40:10 +00:00
Bjørn Erik Pedersen 758a876f90 Fix potential deadlock in Translations
Fixes #12129
2024-03-20 12:33:05 +01:00
Bjørn Erik Pedersen 19937a20ad Fix rebuild when changing mixed case named templates
Fixes #12165
2024-03-20 12:33:05 +01:00
Anthony Fok c1ea22a232 testing: Set usesFMA as true for riscv64 too
This fixes TestImageOperationsGolden "values are not deep equal" error on riscv64 with Go 1.22 and above.
2024-03-20 08:59:13 +01:00
Bjørn Erik Pedersen 0750a9ec91 Fix regression for outputs defined in front matter for term pages
Fixes #12275
2024-03-19 15:07:48 +01:00
hugoreleaser 90bc1f802a releaser: Prepare repository for 0.125.0-DEV
[ci skip]
2024-03-16 15:57:20 +00:00
hugoreleaser 629f84e8ed releaser: Bump versions for release of 0.124.0
[ci skip]
2024-03-16 15:44:32 +00:00
Bjørn Erik Pedersen 76ef3f42fa docs: Regen CLI docs 2024-03-16 15:54:33 +01:00
Bjørn Erik Pedersen 0ccb6cdc04 docs: Regen docshelper 2024-03-16 15:54:02 +01:00
Bjørn Erik Pedersen 1f1c62e6c7 Add segments config + --renderSegments flag
Named segments can be defined in `hugo.toml`.

* Eeach segment consists of zero or more `exclude` filters and zero or more `include` filters.
* Eeach filter consists of one or more field Glob matchers.
* Eeach filter in a section (`exclude` or `include`) is ORed together, each matcher in a filter is ANDed together.

The current list of fields that can be filtered are:

* path as defined in https://gohugo.io/methods/page/path/
* kind
* lang
* output (output format, e.g. html).

It is recommended to put coarse grained filters (e.g. for language and output format) in the excludes section, e.g.:

```toml
[segments.segment1]
  [[segments.segment1.excludes]]
    lang = "n*"
  [[segments.segment1.excludes]]
    no     = "en"
    output = "rss"
  [[segments.segment1.includes]]
    term = "{home,term,taxonomy}"
  [[segments.segment1.includes]]
    path = "{/docs,/docs/**}"
```

By default, Hugo will render all segments, but you can enable filters by setting the `renderSegments` option or `--renderSegments` flag, e.g:

```
hugo --renderSegments segment1,segment2
```

For segment `segment1` in the configuration above, this will:

* Skip rendering of all languages matching `n*`, e.g. `no`.
* Skip rendering of the output format `rss` for the `en` language.
* It will render all pages of kind `home`, `term` or `taxonomy`
* It will render the `/docs` section and all pages below.

Fixes #10106
2024-03-16 15:53:26 +01:00
Bjørn Erik Pedersen f1d755965f Fix .Parent when there are overlapping regular pages inbetween
Fixes #12263
2024-03-16 14:48:04 +01:00
Bjørn Erik Pedersen 558f74f009 hugolib: Remove Site.HomeAbsURL
It's not in use and after #12266 it's also not corret to use on its own (use .Site.Home.Permalink).
2024-03-16 12:49:40 +01:00
Bjørn Erik Pedersen ba03114aa9 deps: Upgrade github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.1.0 => v0.2.0 2024-03-16 11:50:11 +01:00
Joe Mooring 3935faa417 hugolib: Fix sitemap index with monolingual site
Fixes #12266
2024-03-16 11:49:00 +01:00
Joe Mooring d4d49e0f0e hugolib: Deprecate site methods Author, Authors, and Social
Closes #12228
2024-03-15 17:26:45 +01:00
Christian Oliff 78178d0c2a all: Typo fixes 2024-03-15 17:25:52 +01:00
Bjørn Erik Pedersen 68d92ef9dd Fix translationKey handling for term pages
Fixes #12261
2024-03-15 16:54:18 +01:00
Bjørn Erik Pedersen b40f3c7df6 Fix intersect and similar for term entry page collections
Fixes #12254
2024-03-15 16:45:53 +01:00
Bjørn Erik Pedersen 57206e7274 Upgrade to Go 1.22.1
Closes #12250
2024-03-15 16:40:36 +01:00
dependabot[bot] b1f8676347 build(deps): bump github.com/evanw/esbuild from 0.20.1 to 0.20.2
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.20.1 to 0.20.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.20.1...v0.20.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-15 15:43:18 +01:00
Bjørn Erik Pedersen 07b2e535be Fix server rebuilds when adding sub sections especially on Windows
This commit also optimizes for the case where change events for both file (e.g. `_index.md`) and the container directory comes in the same event batch.

While testing this on Windows 11 (ARM64), I notice that Windows behaves a little oddly when dumping a folder of files into the content tree; it works (at least after this commit), but it seems like the event batching behaves differently compared to other OSes (even older Win versions).

A related tip would be to try starting the server with polling, to see if that improves the situation, e.g.:

```
hugo server --poll 700ms
```

Fixes #12230
2024-03-15 15:42:56 +01:00
Joe Mooring f038a51b3e tpl/tplimpl: Remove deprecated method from sitemapindex.xml 2024-03-14 21:03:19 +01:00
dependabot[bot] b4bff6190c build(deps): bump golang.org/x/tools from 0.18.0 to 0.19.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.18.0 to 0.19.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.18.0...v0.19.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-03-14 11:16:50 +01:00
dependabot[bot] d2cebee273 build(deps): bump github.com/tdewolff/minify/v2 from 2.20.17 to 2.20.19
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.20.17 to 2.20.19.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.20.17...v2.20.19)

---
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-03-14 11:16:07 +01:00
Paul Jolly be914ff34d deps: Upgrade github.com/alecthomas/chroma/v2 to v2.13.0
Closes #11862
2024-03-14 11:04:41 +01:00
Joe Mooring 48a0fea87a tpl/tplimpl: Modify figure shortcode to look for page resource
Closes #12244
Closes #12245
2024-03-14 10:21:40 +02:00
Bjørn Erik Pedersen 9ca1de09dd Fix panic when changing archetype files when servere is running
Fixes #12195
2024-03-13 19:35:28 +02:00
dependabot[bot] e62675002e build(deps): bump golang.org/x/mod from 0.15.0 to 0.16.0
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.15.0 to 0.16.0.
- [Commits](https://github.com/golang/mod/compare/v0.15.0...v0.16.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-13 19:23:42 +02:00
Bjørn Erik Pedersen 9668759ad8 Fix front matter date location when value gets inherited from other dates
Fixes #12236
2024-03-13 19:00:50 +02:00
Bjørn Erik Pedersen 9e9b1f110c Fix Name for nested resourced fetched in resources.ByName and similar
Fixes #12214
2024-03-13 18:59:50 +02:00
Joe Mooring dc6a292133 common/hugo: Rename IsMultiHost and IsMultiLingual
hugo.IsMultiHost => hugo.IsMultihost
hugo.IsMultiLingual => hugo.IsMultilingual

Closes #12232
2024-03-13 17:34:42 +02:00
Joe Mooring 4f92f949ea hugolib: Deprecate .Site.MultiLingual in favor of hugo.IsMultiLingual
Closes #12224
2024-03-10 10:47:51 +02:00
Joe Mooring d24ffdde5b snap: Transition to from core20 to core22
Closes #12219
2024-03-09 08:47:53 -08:00
hugoreleaser ca31b95f30 releaser: Prepare repository for 0.124.0-DEV
[ci skip]
2024-03-07 13:28:40 +00:00
hugoreleaser 5fed9c591b releaser: Bump versions for release of 0.123.8
[ci skip]
2024-03-07 13:14:42 +00:00
Jonas Zeiger 134e7d1d3d markup/goldmark: TOC: render strikethrough, emojis
Configure the TOC (TableOfContents, toc.go) goldmark renderer to always
enable the Strikethrough and Emoji extensions. This allows handling
ast.KindStrikethrough and ast.KindEmoji AST nodes when rendering the TOC.

Fixes #7169
Fixes #11783
Fixes #12022
2024-03-07 14:08:29 +01:00
razonyang 1f48b717c7 Add hugo.IsMultiHost 2024-03-07 14:04:41 +01:00
Andreas Deininger ada3fceea8 docs: Fix hyphens and grammar in synopsis of command 'hugo server' 2024-03-07 14:03:49 +01:00
Joe Mooring 05e23bd55c resources/images: Retain newlines with text overlays
Closes #12206
2024-03-07 09:52:30 +01:00
Bjørn Erik Pedersen 7afac3f1ad Don't auto-create empty sections for nested taxonomies
Fixes #12188
2024-03-07 09:50:50 +01:00
Bjørn Erik Pedersen a4b17470a8 Fix resource name in resources.ByType
Fixes #12190
2024-03-07 09:50:50 +01:00
Bjørn Erik Pedersen 4d5e173cf8 Fix global resource isn't published when using an uncommon code construct
Fixes #12190
2024-03-07 09:50:50 +01:00
Bjørn Erik Pedersen 4271b6be0f Fix section page resource not published if resource filename partially matches content file name
Fixes #12198
2024-03-07 09:50:50 +01:00
Bjørn Erik Pedersen 0567a3e6f1 Fix taxonomy kind template lookup issue
Fixes #12193
2024-03-07 09:50:50 +01:00
Joe Mooring 632ad74fc5 tpl/tplimpl: Honor markdown attributes in embedded image render hook
Fixes #12203
2024-03-07 08:21:58 +01:00
hugoreleaser b1de03f398 releaser: Prepare repository for 0.124.0-DEV
[ci skip]
2024-03-01 16:30:33 +00:00
hugoreleaser 312735366b releaser: Bump versions for release of 0.123.7
[ci skip]
2024-03-01 16:16:06 +00:00
Bjørn Erik Pedersen 2b2f2b75ef hugofs: Fix vertical mount merge issue
Fixes #12175
2024-03-01 17:10:13 +01:00
Bjørn Erik Pedersen 0d6e593ffb Fix and add integration test for the Bootstrap SCSS module for both Dart Sass and Libsass
This fixes the reverse filesystem lookup (absolute filename to path relative to the composite filesystem).

The old logic had some assumptions about the locality of the actual files that didn't work in more complex scenarios.

This commit now also adds the popular Bootstrap SCSS Hugo module to the CI build (both for libsass and dartsass transpiler), so we can hopefully avoid similar future breakage.

Fixes #12178
2024-03-01 14:18:52 +01:00
Bjørn Erik Pedersen 7023cf0f07 Fix resources.GetMatch, resources.Match, and resources.ByType to they don't normalize permalinks
Fixes #12182
2024-03-01 13:34:05 +01:00
Bjørn Erik Pedersen 9dfa9e70e6 Make sure that sitemaps gets generated even if there is a content bundle with the same path
Fixes #12183
2024-03-01 13:34:05 +01:00
Joe Mooring 3f217fd664 resources/page: Make Taxonomy.Get and Taxonomy.Count case-insensitive
Closes #12177
2024-03-01 12:09:00 +01:00
hugoreleaser 92684f9a26 releaser: Bump versions for release of 0.123.6
[ci skip]
2024-02-28 18:29:40 +00:00
Bjørn Erik Pedersen fce8d82b79 Fix panic when cascading headless from site config to section that does not have an _index.md file
Fixes #12172
2024-02-28 19:26:55 +01:00
Bjørn Erik Pedersen 4a502f7eb4 Fix assets vs data issue
And possibly some other related file mount issues.

Fixes #12133
2024-02-28 19:26:55 +01:00
Bjørn Erik Pedersen be1dbba0f7 Fix draft for non-default content when content in default language does not exist
Fixes #12132
2024-02-28 19:26:55 +01:00
hugoreleaser 1007bcdf49 releaser: Prepare repository for 0.124.0-DEV
[ci skip]
2024-02-28 16:01:41 +00:00
hugoreleaser 1904ba40e8 releaser: Bump versions for release of 0.123.5
[ci skip]
2024-02-28 15:47:09 +00:00
Bjørn Erik Pedersen 6a8b25e291 Fix .Page.Pages with similary named sections
Fixes #12169
2024-02-28 16:43:19 +01:00
Bjørn Erik Pedersen a322282e70 Fix single mount rename panic
Fixes #12141
2024-02-28 13:38:12 +01:00
Bjørn Erik Pedersen 6bc0d745a5 Fix multihost processed image not copied to non-default content languages
Fixes #12163
2024-02-27 10:43:23 +01:00
dependabot[bot] 218690328c build(deps): bump github.com/bep/overlayfs from 0.9.1 to 0.9.2
Bumps [github.com/bep/overlayfs](https://github.com/bep/overlayfs) from 0.9.1 to 0.9.2.
- [Release notes](https://github.com/bep/overlayfs/releases)
- [Commits](https://github.com/bep/overlayfs/compare/v0.9.1...v0.9.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-27 09:59:15 +01:00
hugoreleaser 673c634131 releaser: Prepare repository for 0.124.0-DEV
[ci skip]
2024-02-26 16:46:48 +00:00
hugoreleaser 21a41003c4 releaser: Bump versions for release of 0.123.4
[ci skip]
2024-02-26 16:33:05 +00:00
Bjørn Erik Pedersen 1736ef7459 Fix cascade-pattern-with-extension for cascade in site config
Also clean up the log handling in the integration tester, most notably lost logs during the config loading.

Fixes #12151
2024-02-26 17:29:37 +01:00
dependabot[bot] d4be1643a0 build(deps): bump github.com/spf13/fsync from 0.10.0 to 0.10.1
Bumps [github.com/spf13/fsync](https://github.com/spf13/fsync) from 0.10.0 to 0.10.1.
- [Release notes](https://github.com/spf13/fsync/releases)
- [Commits](https://github.com/spf13/fsync/compare/v0.10.0...v0.10.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-26 17:08:34 +01:00
Bjørn Erik Pedersen f27e578718 Fix term template lookup when its backed by a content file
Closes #12146
2024-02-25 18:28:16 +01:00
Bjørn Erik Pedersen d310595a2b resource: Revert the normalization of Resource.Name
Which means that .Name now returns the same as it did in 0.122.0.

Closes #12142
2024-02-25 14:57:29 +01:00
Bjørn Erik Pedersen 049dd1d7e0 Fix panic for disableKinds page for content resources
Fixes #12144
2024-02-25 11:38:52 +01:00
Bjørn Erik Pedersen 189b72331e tocss: Fix the import resolving from absolute to relative assets paths
Fixes #12137
2024-02-24 16:41:18 +01:00
Bjørn Erik Pedersen b2b7bfdd3a Maek Resoyrce.Key as (mostly) internal 2024-02-24 15:17:15 +01:00
Joe Mooring 80e69344da config/allconfig: Fix typo
Closes #12134
2024-02-23 12:40:15 -08:00
hugoreleaser 7508a16470 releaser: Prepare repository for 0.124.0-DEV
[ci skip]
2024-02-23 17:22:38 +00:00
hugoreleaser a75a659f6f releaser: Bump versions for release of 0.123.3
[ci skip]
2024-02-23 17:09:20 +00:00
Bjørn Erik Pedersen 03b88c6ddb cache/dynacache: Reset ticker in case one cache eviction takes some time
Updates #12129
2024-02-23 17:56:29 +01:00
Bjørn Erik Pedersen 7ee0f70507 Revert "cache/dynacache: Prevent multiple concurrent resizes"
This reverts commit 564bae06f6.

Thinking about it, the above doesn't make any sense/having any effect.
2024-02-23 17:44:32 +01:00
Bjørn Erik Pedersen 564bae06f6 cache/dynacache: Prevent multiple concurrent resizes
Updates #12129
2024-02-23 17:27:52 +01:00
Bjørn Erik Pedersen bf14d0cb26 Speed up GetPage
```
name                             old time/op    new time/op    delta
GetPage-10                          413ns ± 0%     287ns ± 1%  -30.47%  (p=0.029 n=4+4)
GetPageRegular/From_root-10         757ns ± 1%     706ns ± 1%   -6.75%  (p=0.029 n=4+4)
GetPageRegular/Page_relative-10     838ns ± 1%     786ns ± 1%   -6.16%  (p=0.029 n=4+4)

name                             old alloc/op   new alloc/op   delta
GetPage-10                           312B ± 0%       24B ± 0%  -92.31%  (p=0.029 n=4+4)
GetPageRegular/From_root-10          328B ± 0%      200B ± 0%  -39.02%  (p=0.029 n=4+4)
GetPageRegular/Page_relative-10      360B ± 0%      232B ± 0%  -35.56%  (p=0.029 n=4+4)

name                             old allocs/op  new allocs/op  delta
GetPage-10                           8.00 ± 0%      2.00 ± 0%  -75.00%  (p=0.029 n=4+4)
GetPageRegular/From_root-10          7.00 ± 0%      5.00 ± 0%  -28.57%  (p=0.029 n=4+4)
GetPageRegular/Page_relative-10      9.00 ± 0%      7.00 ± 0%  -22.22%  (p=0.029 n=4+4)
```
2024-02-23 16:29:21 +01:00
Bjørn Erik Pedersen f521336c81 hugolib: Fix a .Page.GetPage from bundle case
Fixes #12120
2024-02-23 14:40:29 +01:00
Bjørn Erik Pedersen c4fe45ff4c resources: Skip the image golden tests when running locally
See #12119
2024-02-23 10:30:11 +01:00
Benjamin Altpeter 554aa58db6 js: Support JSX and JSXImportSourceOptions
Fixes #12118
2024-02-23 10:25:27 +01:00
Joe Mooring 36bf3cb98c hugolib: Add capitalizeListTitles config option
Whether to capitalize automatic page titles, applicable to section,
taxonomy, and term pages. Default is true.

Closes #9793
Closes #12115
2024-02-23 09:32:32 +01:00
hugoreleaser ea0ba9751d releaser: Prepare repository for 0.124.0-DEV
[ci skip]
2024-02-22 15:41:43 +00:00
hugoreleaser 929b91fe75 releaser: Bump versions for release of 0.123.2
[ci skip]
2024-02-22 15:27:15 +00:00
Bjørn Erik Pedersen db3bb623d4 Fix it so not all bundled resources are removed when one translation is drafted
Fixes #12105
2024-02-22 16:16:44 +01:00
Razon Yang f5fd7ec8cc commands/mod: Ignore invalid module path 2024-02-22 16:00:03 +01:00
Bjørn Erik Pedersen 16406d9d77 Fix regression on handling of overlapping file mounts
But note that the overlay file system is set up horizontally (project -> module1 -> module2), so I would not recommend too complex overlapping mount setups within the same module.

But this worked in v0.122.0, so we should fix it.

Fixes #12103
2024-02-22 15:36:27 +01:00
Bjørn Erik Pedersen e75784930d Sitemap should not use list.xml
Fixes #12101
2024-02-21 20:07:51 +01:00
Bjørn Erik Pedersen b8ab5c9be6 Fix rebuild when adding a bundle with space in name
Fixes #12092
2024-02-21 17:49:15 +01:00
Bjørn Erik Pedersen cf7023cb90 Fix relative import issue in libsass/dart sass
Fixes #12094
2024-02-21 17:27:45 +01:00
Bjørn Erik Pedersen fd0185a84a Fix relref regression with trailing slash
Fixes #12096
2024-02-21 17:27:45 +01:00
hugoreleaser 6da1d8f370 releaser: Prepare repository for 0.124.0-DEV
[ci skip]
2024-02-21 08:30:57 +00:00
hugoreleaser 3f8434a62f releaser: Bump versions for release of 0.123.1
[ci skip]
2024-02-21 08:17:45 +00:00
Joe Mooring c9f7ebf004 tpl/tplimpl: Resolve fragments in link render hook
Fixes #12084
2024-02-21 09:11:34 +01:00
Bjørn Erik Pedersen eceeb19751 Fix rebuilding of pages without default content language
Fixes #12082
2024-02-20 18:42:14 +01:00
Bjørn Erik Pedersen 621194a319 Fix dart sass import regression
Fixes #12072
2024-02-20 17:00:32 +01:00
Bjørn Erik Pedersen 48eec2a4e6 Fall back to original name in Resources.GetMatch/Match
Same as we do in .Get.

Fixes #12076
2024-02-20 16:47:07 +01:00
Bjørn Erik Pedersen a118cb4138 Fix robots.txt using the built-in template regression
Fixes #12071
2024-02-20 11:17:44 +01:00
Bjørn Erik Pedersen e8cc785a58 Merge commit '2658a71e1b6fe24a8b754a62ce0398a09d270d86' 2024-02-19 18:59:28 +01:00
Bjørn Erik Pedersen 2658a71e1b Squashed 'docs/' changes from a9584e3d1..8c46b304a
8c46b304a Correct description of build options > render > link
f57932df1 Fix front matter example
a7e5fea73 Clarify pagination restriction
3a7e665db Fix typos
f60845249 List translation keys reserved by nicksnyder/go-i18n
94f2a3608 Fix typo
2da1198ac Update FNV32a.md: new-in 0.98.0
d9a4c66ae Fix typo data-templates.md
0d3c2e2c3 Update partials.md
d7e9a0878 Update partials.md

git-subtree-dir: docs
git-subtree-split: 8c46b304a0
2024-02-19 18:59:28 +01:00
hugoreleaser 8406a3e9dc releaser: Bump versions for release of 0.123.0
[ci skip]
2024-02-19 17:28:48 +00:00
dependabot[bot] f54ba6f9ef build(deps): bump github.com/tdewolff/minify/v2 from 2.20.16 to 2.20.17
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.20.16 to 2.20.17.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.20.16...v2.20.17)

---
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-19 17:21:17 +01:00
dependabot[bot] 4019b177e6 build(deps): bump github.com/evanw/esbuild from 0.20.0 to 0.20.1
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.20.0 to 0.20.1.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.20.0...v0.20.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 17:12:14 +01:00
Bjørn Erik Pedersen 5dbc29dc6c Handle rebuilds when resources passed to transform.Unmarshal etc. changes
Fixes #12065
2024-02-19 14:50:23 +01:00
Bjørn Erik Pedersen 5ada27bf65 Fix handling of build options for term pages
Fixes #12058
2024-02-18 16:50:35 +01:00
Bjørn Erik Pedersen 60b176cb57 docs: Regen docshelper 2024-02-18 13:20:15 +01:00
Bjørn Erik Pedersen afe5b6d7df Fix sample logic when adding content files in server
The partial rebuilds works by calaulating a baseline from a change set.

For new content, this doesn't work, so to avoid rebuilding everything, we first
try to collect a sample of surrounding identities (e.g. content files in the same section).

This commit fixes a flaw in that logic that in some (many...) cases would return a too small sample set.

Fixes #12054
2024-02-18 12:24:27 +01:00
Christian Oliff 168d375784 all: Fix typos and some URLs 2024-02-18 12:16:30 +01:00
dependabot[bot] 4a53fd56b7 build(deps): bump golang.org/x/tools from 0.17.0 to 0.18.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.17.0 to 0.18.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.17.0...v0.18.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-02-17 18:27:49 +01:00
Bjørn Erik Pedersen fc6aabe938 Fix handling of draft term pages
By just removing the term page and all of its page entries.

Fixes #12055
2024-02-17 18:12:25 +01:00
Bjørn Erik Pedersen 9cc65757a1 releaser: Fix archive homepage and description
Fixes #12053
2024-02-17 13:32:15 +01:00
Bjørn Erik Pedersen 4835f9e898 commands: Fix --clock with the list command
Fixes #11888
2024-02-17 13:14:15 +01:00
Bjørn Erik Pedersen 5bdda0bdbf Don't use the same value in .Data.Term.Title as in .Title
Fixes #12041
2024-02-17 12:34:50 +01:00
Bjørn Erik Pedersen 9679443c15 Fix server panic on i18n file change
Fixes #12048
2024-02-17 11:46:45 +01:00
dependabot[bot] 2d1681d915 build(deps): bump golang.org/x/net from 0.20.0 to 0.21.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.20.0 to 0.21.0.
- [Commits](https://github.com/golang/net/compare/v0.20.0...v0.21.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-17 11:16:15 +01:00
Bjørn Erik Pedersen 68f67c9aeb Fix rebuild regression on non-default content language edits
Fixes #12043
2024-02-16 19:05:18 +01:00
Fabian Kammel 43ea2cd660 Let standard library handle charset parameter to MIME types
Fixes #10734
2024-02-16 18:24:25 +01:00
Bjørn Erik Pedersen f1491c900e Fix i18n rebuild regression
Fixes #12039
2024-02-16 16:17:57 +01:00
Bjørn Erik Pedersen 639073e4fe Fix rebuild with resources.Concat
Fixes #12017
2024-02-16 13:17:53 +01:00
Joe Mooring 21d9057dbf Add images.Dither filter
Closes #8598
2024-02-11 22:51:11 +02:00
Christian Oliff 0672b5c766 all: Fix typos 2024-02-11 13:51:33 +02:00
Rosano e309f82efe resources/page: Fix typo 2024-02-10 21:28:03 +02:00
Joe Mooring 6f59d96bba markup/goldmark: Update TOC test
- Change copyright date
- Change name of site configuration file
- Reference issue numbers in failing tests
2024-02-10 20:16:59 +02:00
Bjørn Erik Pedersen caba6ba6e7 Fix taxonomy term with backing file regression
Fixes #12020
2024-02-10 18:48:53 +02:00
Joe Mooring f4575e5f29 markup/goldmark: Improve TOC tests 2024-02-10 16:03:37 +02:00
Bjørn Erik Pedersen 0851c175ad Move the duplicate page/resource filter
Move the removal of duplicate content and resource files after we have determined if we're inside a leaf bundle or not.

Note that these would eventually have been filtered out as duplicates when  inserting them into the document store, but doing it here will preserve a consistent ordering.

Fixes #12013
2024-02-08 20:11:17 +01:00
Bjørn Erik Pedersen 676e6875da Make HTML behave exactly like other content formats (note)
Fixes #11999
2024-02-07 21:37:04 +01:00
Bjørn Erik Pedersen 068ccde4c7 docs: Regen CLI docs 2024-02-07 20:49:52 +01:00
Bjørn Erik Pedersen 7cb447ab8e docs: Regenerate docshelper 2024-02-07 20:48:25 +01:00
Bjørn Erik Pedersen 6efb279bfa Squashed 'docs/' changes from 5c085a37b..a9584e3d1
a9584e3d1 ci: bump GitHub workflow actions to latest versions
f37ee374d Fix methods related to time.After
a49214057 Correct heading render hook example
4b696f936 Update README.md
b5a6e4039 Clean up the contributing section
a5d79ed19 Fixed typo: instea → instead
6150a60e3 Remove the "local file templates" page
9395743dc Update/vendor theme
33d8c10cc Remove output format "name" parameter
7200ad97d Add security section to resources.GetRemote page
3f1e056b2 Remove erroneous front matter field
07c9a7e42 Add alpha index to glossary
6619c8aa0 Fix Page.Params example
145a92359 Simplify string concat example in math.Add
42488bb09 Add string concat example to math.Add
eb20521a2 Fix flash of unstyled content with mathematical markup
74603bebe Fix typo in 'typesetting'

git-subtree-dir: docs
git-subtree-split: a9584e3d14
2024-02-07 20:46:41 +01:00
Bjørn Erik Pedersen b8d5090452 Merge commit '6efb279bfacbd7304cef994be8181c6f804e7dd4' 2024-02-07 20:46:41 +01:00
dependabot[bot] 301bafabe0 build(deps): bump golang.org/x/mod from 0.14.0 to 0.15.0
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.14.0 to 0.15.0.
- [Commits](https://github.com/golang/mod/compare/v0.14.0...v0.15.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-07 20:44:21 +01:00
Bjørn Erik Pedersen 9571246bcc Upgrade to Go 1.22
Fixes #12010
2024-02-07 20:42:42 +01:00
Bjørn Erik Pedersen 0257eb50a4 Avoid impporting deploy from config when nodeploy tag is set
Test:

```
go list -tags nodeploy ./... | grep deploy
```

Fixes #12009
2024-02-07 20:38:13 +01:00
Bjørn Erik Pedersen a65622a13e Fix rebuild of changed bundled content files
Fixes #12000
2024-02-06 21:53:33 +01:00
Bjørn Erik Pedersen 146aedd7aa Fix site.Taxonomies for taxonomies with space in name
Fixes #12001
2024-02-06 18:17:30 +01:00
Bjørn Erik Pedersen bd0200da6e commands: Remove unused memstats flag 2024-02-05 19:06:27 +01:00
Bjørn Erik Pedersen a80c3021ba source: Remove unused Filesystem struct 2024-02-05 15:03:03 +01:00
Bjørn Erik Pedersen 9df7b295bc Filter dot files etc. in i18n
Closes #11993
2024-02-05 14:54:02 +01:00
Bjørn Erik Pedersen c37bf19c89 commands: Revert the recent changes that allowed profiling on server rebuilds
There have been indications that this may freeze the server.
2024-02-05 14:27:35 +01:00
Bjørn Erik Pedersen 9c6d377872 commands: Make the server flag --renderToDisk into --renderToMemory (note)
Fixes #11987
2024-02-05 14:04:17 +01:00
dependabot[bot] 58d7f83390 build(deps): bump github.com/yuin/goldmark from 1.6.0 to 1.7.0
Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.6.0 to 1.7.0.
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.6.0...v1.7.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-05 11:48:46 +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
1507 changed files with 53987 additions and 31150 deletions
+2 -2
View File
@@ -4,7 +4,7 @@ parameters:
defaults: &defaults
resource_class: large
docker:
- image: bepsays/ci-hugoreleaser:1.22100.20400
- image: bepsays/ci-hugoreleaser:1.22200.20200
environment: &buildenv
GOMODCACHE: /root/project/gomodcache
version: 2
@@ -60,7 +60,7 @@ jobs:
environment:
<<: [*buildenv]
docker:
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22100.20400
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22200.20200
steps:
- *restore-cache
- &attach-workspace
+70 -57
View File
@@ -1,73 +1,86 @@
on:
push:
branches: [ master ]
branches: [master]
pull_request:
name: TestDartSassV1
env:
GOPROXY: https://proxy.golang.org
GO111MODULE: on
DART_SASS_VERSION: 1.62.1
DART_SASS_SHA_LINUX: 3574da75a7322a539034648b8ff84ff2cca162eb924d72b663d718cd3936f075
GOPROXY: https://proxy.golang.org
GO111MODULE: on
DART_SASS_VERSION: 1.62.1
DART_SASS_SHA_LINUX: 3574da75a7322a539034648b8ff84ff2cca162eb924d72b663d718cd3936f075
permissions:
contents: read
jobs:
test:
strategy:
matrix:
go-version: [1.21.x]
go-version: [1.22.x]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
- name: Install Go
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753
with:
go-version: ${{ matrix.go-version }}
check-latest: true
cache: true
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Install Ruby
uses: ruby/setup-ruby@ee2113536afb7f793eed4ce60e8d3b26db912da4
with:
ruby-version: '2.7'
bundler-cache: true #
- name: Install Python
uses: actions/setup-python@3105fb18c05ddd93efea5f9e0bef7a03a6e9e7df
with:
python-version: '3.x'
- name: Install Mage
run: go install github.com/magefile/mage@v1.15.0
- name: Install asciidoctor
uses: reitzig/actions-asciidoctor@7570212ae20b63653481675fb1ff62d1073632b0
- name: Install docutils
run: |
pip install docutils
rst2html.py --version
- if: matrix.os == 'ubuntu-latest'
name: Install pandoc on Linux
run: |
- if: matrix.os == 'ubuntu-latest'
name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: false
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: true
swap-storage: true
- name: Checkout code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- name: Install Go
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491
with:
go-version: ${{ matrix.go-version }}
check-latest: true
cache: true
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Install Ruby
uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899
with:
ruby-version: "2.7"
bundler-cache: true #
- name: Install Python
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d
with:
python-version: "3.x"
- name: Install Mage
run: go install github.com/magefile/mage@v1.15.0
- name: Install asciidoctor
uses: reitzig/actions-asciidoctor@03fcc74cd74880b697950c4930c9ec8a67c69ecc
- name: Install docutils
run: |
pip install docutils
rst2html --version
- if: matrix.os == 'ubuntu-latest'
name: Install pandoc on Linux
run: |
sudo apt-get update -y
sudo apt-get install -y pandoc
- if: matrix.os == 'macos-latest'
run: |
brew install pandoc
- if: matrix.os == 'windows-latest'
run: |
Choco-Install -PackageName pandoc
- run: pandoc -v
- name: Install dart-sass-embedded Linux
run: |
echo "Install Dart Sass version ${DART_SASS_VERSION} ..."
curl -LJO "https://github.com/sass/dart-sass-embedded/releases/download/${DART_SASS_VERSION}/sass_embedded-${DART_SASS_VERSION}-linux-x64.tar.gz";
echo "${DART_SASS_SHA_LINUX} sass_embedded-${DART_SASS_VERSION}-linux-x64.tar.gz" | sha256sum -c;
tar -xvf "sass_embedded-${DART_SASS_VERSION}-linux-x64.tar.gz";
echo "$GITHUB_WORKSPACE/sass_embedded/" >> $GITHUB_PATH
- name: Check
run: |
dart-sass-embedded --version
mage -v check;
env:
HUGO_BUILD_TAGS: extended
- if: matrix.os == 'macos-latest'
run: |
brew install pandoc
- if: matrix.os == 'windows-latest'
run: |
Choco-Install -PackageName pandoc
- run: pandoc -v
- name: Install dart-sass-embedded Linux
run: |
echo "Install Dart Sass version ${DART_SASS_VERSION} ..."
curl -LJO "https://github.com/sass/dart-sass-embedded/releases/download/${DART_SASS_VERSION}/sass_embedded-${DART_SASS_VERSION}-linux-x64.tar.gz";
echo "${DART_SASS_SHA_LINUX} sass_embedded-${DART_SASS_VERSION}-linux-x64.tar.gz" | sha256sum -c;
tar -xvf "sass_embedded-${DART_SASS_VERSION}-linux-x64.tar.gz";
echo "$GITHUB_WORKSPACE/sass_embedded/" >> $GITHUB_PATH
- name: Check
run: |
dart-sass-embedded --version
mage -v check;
env:
HUGO_BUILD_TAGS: extended
+116 -97
View File
@@ -1,113 +1,132 @@
on:
push:
branches: [ master ]
branches: [master]
pull_request:
name: Test
env:
GOPROXY: https://proxy.golang.org
GO111MODULE: on
SASS_VERSION: 1.63.2
DART_SASS_SHA_LINUX: 3ea33c95ad5c35fda6e9a0956199eef38a398f496cfb8750e02479d7d1dd42af
DART_SASS_SHA_MACOS: 11c70f259836b250b44a9cb57fed70e030f21f45069b467d371685855f1eb4f0
DART_SASS_SHA_WINDOWS: cd8cd36a619dd8e27f93d3186c52d70eb7d69472aa6c85f5094b29693e773f64
GOPROXY: https://proxy.golang.org
GO111MODULE: on
SASS_VERSION: 1.63.2
DART_SASS_SHA_LINUX: 3ea33c95ad5c35fda6e9a0956199eef38a398f496cfb8750e02479d7d1dd42af
DART_SASS_SHA_MACOS: 11c70f259836b250b44a9cb57fed70e030f21f45069b467d371685855f1eb4f0
DART_SASS_SHA_WINDOWS: cd8cd36a619dd8e27f93d3186c52d70eb7d69472aa6c85f5094b29693e773f64
permissions:
contents: read
jobs:
test:
strategy:
matrix:
go-version: [1.20.x,1.21.x]
go-version: [1.21.x, 1.22.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
- name: Install Go
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753
with:
go-version: ${{ matrix.go-version }}
check-latest: true
cache: true
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Install Ruby
uses: ruby/setup-ruby@ee2113536afb7f793eed4ce60e8d3b26db912da4
with:
ruby-version: '2.7'
bundler-cache: true #
- name: Install Python
uses: actions/setup-python@3105fb18c05ddd93efea5f9e0bef7a03a6e9e7df
with:
python-version: '3.x'
- name: Install Mage
run: go install github.com/magefile/mage@v1.15.0
- name: Install asciidoctor
uses: reitzig/actions-asciidoctor@7570212ae20b63653481675fb1ff62d1073632b0
- name: Install docutils
run: |
pip install docutils
rst2html.py --version
- if: matrix.os == 'ubuntu-latest'
name: Install pandoc on Linux
run: |
- if: matrix.os == 'ubuntu-latest'
name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: false
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: true
swap-storage: true
- name: Checkout code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- name: Install Go
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491
with:
go-version: ${{ matrix.go-version }}
check-latest: true
cache: true
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Install Ruby
uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899
with:
ruby-version: "2.7"
bundler-cache: true #
- name: Install Python
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d
with:
python-version: "3.x"
- name: Install Mage
run: go install github.com/magefile/mage@v1.15.0
- name: Install asciidoctor
uses: reitzig/actions-asciidoctor@03fcc74cd74880b697950c4930c9ec8a67c69ecc
- name: Install docutils
run: |
pip install docutils
rst2html --version
- if: matrix.os == 'ubuntu-latest'
name: Install pandoc on Linux
run: |
sudo apt-get update -y
sudo apt-get install -y pandoc
- if: matrix.os == 'macos-latest'
run: |
brew install pandoc
- if: matrix.os == 'windows-latest'
run: |
Choco-Install -PackageName pandoc
- run: pandoc -v
- if: matrix.os == 'windows-latest'
run: |
Choco-Install -PackageName mingw -ArgumentList "--version","12.2.0","--allow-downgrade"
- if: matrix.os == 'ubuntu-latest'
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";
echo "${DART_SASS_SHA_LINUX} dart-sass-${SASS_VERSION}-linux-x64.tar.gz" | sha256sum -c;
tar -xvf "dart-sass-${SASS_VERSION}-linux-x64.tar.gz";
echo "$GOBIN"
echo "$GITHUB_WORKSPACE/dart-sass/" >> $GITHUB_PATH
- if: matrix.os == 'macos-latest'
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";
echo "${DART_SASS_SHA_MACOS} dart-sass-${SASS_VERSION}-macos-x64.tar.gz" | shasum -a 256 -c;
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 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 != 'windows-latest'
name: Check
run: |
sass --version;
mage -v check;
env:
HUGO_BUILD_TAGS: extended
- if: matrix.os == 'windows-latest'
# See issue #11052. We limit the build to regular test (no -race flag) on Windows for now.
name: Test
run: |
mage -v test;
env:
HUGO_BUILD_TAGS: extended
- name: Build tags
run: |
go install -tags extended,nodeploy
- if: matrix.os == 'ubuntu-latest'
name: Build for dragonfly
run: |
go install
env:
GOARCH: amd64
GOOS: dragonfly
- if: matrix.os == 'macos-latest'
run: |
brew install pandoc
- if: matrix.os == 'windows-latest'
run: |
choco install pandoc
- run: pandoc -v
- if: matrix.os == 'windows-latest'
run: |
choco install mingw
- if: matrix.os == 'ubuntu-latest'
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";
echo "${DART_SASS_SHA_LINUX} dart-sass-${SASS_VERSION}-linux-x64.tar.gz" | sha256sum -c;
tar -xvf "dart-sass-${SASS_VERSION}-linux-x64.tar.gz";
echo "$GOBIN"
echo "$GITHUB_WORKSPACE/dart-sass/" >> $GITHUB_PATH
- if: matrix.os == 'macos-latest'
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";
echo "${DART_SASS_SHA_MACOS} dart-sass-${SASS_VERSION}-macos-x64.tar.gz" | shasum -a 256 -c;
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 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: |
sass --version;
mage -v check;
env:
HUGO_BUILD_TAGS: extended
- if: matrix.os == 'windows-latest'
# See issue #11052. We limit the build to regular test (no -race flag) on Windows for now.
name: Test
run: |
mage -v test;
env:
HUGO_BUILD_TAGS: extended
- name: Build tags
run: |
go install -tags extended,nodeploy
- if: matrix.os == 'ubuntu-latest'
name: Build for dragonfly
run: |
go install
env:
GOARCH: amd64
GOOS: dragonfly
+80 -78
View File
@@ -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>
+584
View File
@@ -0,0 +1,584 @@
// 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()
// Reset the ticker to avoid drift.
ticker.Reset(c.opts.CheckInterval)
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 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)
}
+5 -3
View File
@@ -24,6 +24,7 @@ import (
"time"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/helpers"
@@ -109,7 +110,7 @@ func (l *lockedFile) Close() error {
func (c *Cache) init() error {
c.initOnce.Do(func() {
// Create the base dir if it does not exist.
if err := c.Fs.MkdirAll("", 0777); err != nil && !os.IsExist(err) {
if err := c.Fs.MkdirAll("", 0o777); err != nil && !os.IsExist(err) {
c.initErr = err
}
})
@@ -146,7 +147,8 @@ 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
}
@@ -380,7 +382,7 @@ func NewCaches(p *helpers.PathSpec) (Caches, error) {
baseDir := v.DirCompiled
bfs := afero.NewBasePathFs(cfs, baseDir)
bfs := hugofs.NewBasePathFs(cfs, baseDir)
var pruneAllRootDir string
if k == CacheKeyModules {
+1 -3
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"
)
@@ -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)
}
-2
View File
@@ -60,7 +60,6 @@ func (c *Cache) Prune(force bool) (int, error) {
counter := 0
err := afero.Walk(c.Fs, "", func(name string, info os.FileInfo, err error) error {
if info == nil {
return nil
}
@@ -69,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
+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.
+36 -24
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"
@@ -47,6 +48,7 @@ import (
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/resources/kinds"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)
@@ -202,9 +204,6 @@ 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 {
@@ -238,9 +237,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.
@@ -250,6 +247,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)
@@ -259,7 +258,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.
@@ -342,7 +341,10 @@ func (r *rootCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args
defer r.timeTrack(time.Now(), "Built")
}
err := b.build()
return err
if err != nil {
return err
}
return nil
}()
if err != nil {
return err
@@ -405,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
}
@@ -474,46 +483,47 @@ Complete documentation is available at https://gohugo.io/.`
// Configure persistent flags
cmd.PersistentFlags().StringVarP(&r.source, "source", "s", "", "filesystem path to read files relative from")
cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
_ = cmd.MarkFlagDirname("source")
cmd.PersistentFlags().StringP("destination", "d", "", "filesystem path to write files to")
cmd.PersistentFlags().SetAnnotation("destination", cobra.BashCompSubdirsInDir, []string{})
_ = cmd.MarkFlagDirname("destination")
cmd.PersistentFlags().StringVarP(&r.environment, "environment", "e", "", "build environment")
_ = cmd.RegisterFlagCompletionFunc("environment", cobra.NoFileCompletions)
cmd.PersistentFlags().StringP("themesDir", "", "", "filesystem path to themes directory")
_ = cmd.MarkFlagDirname("themesDir")
cmd.PersistentFlags().StringP("ignoreVendorPaths", "", "", "ignores any _vendor for module paths matching the given Glob pattern")
_ = cmd.RegisterFlagCompletionFunc("ignoreVendorPaths", cobra.NoFileCompletions)
cmd.PersistentFlags().String("clock", "", "set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00")
_ = cmd.RegisterFlagCompletionFunc("clock", cobra.NoFileCompletions)
cmd.PersistentFlags().StringVar(&r.cfgFile, "config", "", "config file (default is hugo.yaml|json|toml)")
_ = cmd.MarkFlagFilename("config", config.ValidConfigFileExtensions...)
cmd.PersistentFlags().StringVar(&r.cfgDir, "configDir", "config", "config dir")
_ = cmd.MarkFlagDirname("configDir")
cmd.PersistentFlags().BoolVar(&r.quiet, "quiet", false, "build in quiet mode")
// Set bash-completion
_ = cmd.PersistentFlags().SetAnnotation("config", cobra.BashCompFilenameExt, config.ValidConfigFileExtensions)
cmd.PersistentFlags().BoolVar(&r.renderToMemory, "renderToMemory", false, "render to memory (mostly useful when running the server)")
cmd.PersistentFlags().BoolVarP(&r.verbose, "verbose", "v", false, "verbose output")
cmd.PersistentFlags().BoolVarP(&r.debug, "debug", "", false, "debug output")
cmd.PersistentFlags().StringVar(&r.logLevel, "logLevel", "", "log level (debug|info|warn|error)")
_ = cmd.RegisterFlagCompletionFunc("logLevel", cobra.FixedCompletions([]string{"debug", "info", "warn", "error"}, cobra.ShellCompDirectiveNoFileComp))
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)
// Set bash-completion.
// Each flag must first be defined before using the SetAnnotation() call.
_ = cmd.Flags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
return nil
}
// A sub set of the complete build flags. These flags are used by new and mod.
func applyLocalFlagsBuildConfig(cmd *cobra.Command, r *rootCommand) {
cmd.Flags().StringSliceP("theme", "t", []string{}, "themes to use (located in /themes/THEMENAME/)")
_ = cmd.MarkFlagDirname("theme")
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")
_ = cmd.Flags().SetAnnotation("cacheDir", cobra.BashCompSubdirsInDir, []string{})
_ = cmd.MarkFlagDirname("cacheDir")
cmd.Flags().StringP("contentDir", "c", "", "filesystem path to content directory")
_ = cmd.Flags().SetAnnotation("theme", cobra.BashCompSubdirsInDir, []string{"themes"})
cmd.Flags().StringSliceP("renderSegments", "", []string{}, "named segments to render (configured in the segments config)")
}
// Flags needed to do a build (used by hugo and hugo server commands)
@@ -526,8 +536,10 @@ func applyLocalFlagsBuild(cmd *cobra.Command, r *rootCommand) {
cmd.Flags().BoolP("ignoreCache", "", false, "ignores the cache directory")
cmd.Flags().Bool("enableGitInfo", false, "add Git revision, date, author, and CODEOWNERS info to the pages")
cmd.Flags().StringP("layoutDir", "l", "", "filesystem path to layout directory")
_ = cmd.MarkFlagDirname("layoutDir")
cmd.Flags().BoolVar(&r.gc, "gc", false, "enable to run some cleanup tasks (remove unused cache files) after the build")
cmd.Flags().StringVar(&r.poll, "poll", "", "set this to a poll interval, e.g --poll 700ms, to use a poll based approach to watch for file system changes")
_ = cmd.RegisterFlagCompletionFunc("poll", cobra.NoFileCompletions)
cmd.Flags().Bool("panicOnWarning", false, "panic on first WARNING log")
cmd.Flags().Bool("templateMetrics", false, "display metrics about template executions")
cmd.Flags().Bool("templateMetricsHints", false, "calculate some improvement hints when combined with --templateMetrics")
@@ -550,8 +562,8 @@ func applyLocalFlagsBuild(cmd *cobra.Command, r *rootCommand) {
cmd.Flags().MarkHidden("profile-mutex")
cmd.Flags().StringSlice("disableKinds", []string{}, "disable different kind of pages (home, RSS etc.)")
_ = cmd.RegisterFlagCompletionFunc("disableKinds", cobra.FixedCompletions(kinds.AllKinds, cobra.ShellCompDirectiveNoFileComp))
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)
}
+5 -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.
@@ -28,6 +28,7 @@ import (
"github.com/gohugoio/hugo/modules"
"github.com/gohugoio/hugo/parser"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/spf13/cobra"
)
// newConfigCommand creates a new config command and its subcommands.
@@ -37,7 +38,6 @@ func newConfigCommand() *configCommand {
&configMountsCommand{},
},
}
}
type configCommand struct {
@@ -113,7 +113,9 @@ func (c *configCommand) Init(cd *simplecobra.Commandeer) error {
cmd.Short = "Print the site configuration"
cmd.Long = `Print the site configuration, both default and custom settings.`
cmd.Flags().StringVar(&c.format, "format", "toml", "preferred file format (toml, yaml or json)")
_ = cmd.RegisterFlagCompletionFunc("format", cobra.FixedCompletions([]string{"toml", "yaml", "json"}, cobra.ShellCompDirectiveNoFileComp))
cmd.Flags().StringVar(&c.lang, "lang", "", "the language to display config for. Defaults to the first language defined.")
_ = cmd.RegisterFlagCompletionFunc("lang", cobra.NoFileCompletions)
applyLocalFlagsBuildConfig(cmd, c.r)
return nil
@@ -190,7 +192,6 @@ func (m *configModMounts) MarshalJSON() ([]byte, error) {
Dir: m.m.Dir(),
Mounts: mounts,
})
}
type configMountsCommand struct {
@@ -225,6 +226,7 @@ func (c *configMountsCommand) Init(cd *simplecobra.Commandeer) error {
c.r = cd.Root.Command.(*rootCommand)
cmd := cd.CobraCommand
cmd.Short = "Print the configured file mounts"
cmd.ValidArgsFunction = cobra.NoFileCompletions
applyLocalFlagsBuildConfig(cmd, c.r)
return nil
}
+7 -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.
@@ -46,6 +46,7 @@ to use JSON for the front matter.`,
return c.convertContents(metadecoders.JSON)
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
},
},
&simpleCommand{
@@ -57,6 +58,7 @@ to use TOML for the front matter.`,
return c.convertContents(metadecoders.TOML)
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
},
},
&simpleCommand{
@@ -68,6 +70,7 @@ to use YAML for the front matter.`,
return c.convertContents(metadecoders.YAML)
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
},
},
},
@@ -108,6 +111,7 @@ func (c *convertCommand) Init(cd *simplecobra.Commandeer) error {
See convert's subcommands toJSON, toTOML and toYAML for more information.`
cmd.PersistentFlags().StringVarP(&c.outputDir, "output", "o", "", "filesystem path to write files to")
_ = cmd.MarkFlagDirname("output")
cmd.PersistentFlags().BoolVar(&c.unsafe, "unsafe", false, "enable less safe operations, please backup first")
cmd.RunE = nil
@@ -134,7 +138,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 +213,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)
+12 -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.
@@ -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.
@@ -32,13 +32,14 @@ package commands
import (
"context"
"github.com/bep/simplecobra"
"github.com/gohugoio/hugo/deploy"
"github.com/gohugoio/hugo/deploy/deployconfig"
"github.com/bep/simplecobra"
"github.com/spf13/cobra"
)
func newDeployCommand() simplecobra.Commander {
return &simpleCommand{
name: "deploy",
short: "Deploy your site to a Cloud provider.",
@@ -59,13 +60,17 @@ documentation.
return deployer.Deploy(ctx)
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
cmd.Flags().String("target", "", "target deployment from deployments section in config file; defaults to the first one")
_ = cmd.RegisterFlagCompletionFunc("target", cobra.NoFileCompletions)
cmd.Flags().Bool("confirm", false, "ask for confirmation before making changes to the target")
cmd.Flags().Bool("dryRun", false, "dry run")
cmd.Flags().Bool("force", false, "force upload of all files")
cmd.Flags().Bool("invalidateCDN", deploy.DefaultConfig.InvalidateCDN, "invalidate the CDN cache listed in the deployment target")
cmd.Flags().Int("maxDeletes", deploy.DefaultConfig.MaxDeletes, "maximum # of files to delete, or -1 to disable")
cmd.Flags().Int("workers", deploy.DefaultConfig.Workers, "number of workers to transfer files. defaults to 10")
cmd.Flags().Bool("invalidateCDN", deployconfig.DefaultConfig.InvalidateCDN, "invalidate the CDN cache listed in the deployment target")
cmd.Flags().Int("maxDeletes", deployconfig.DefaultConfig.MaxDeletes, "maximum # of files to delete, or -1 to disable")
_ = cmd.RegisterFlagCompletionFunc("maxDeletes", cobra.NoFileCompletions)
cmd.Flags().Int("workers", deployconfig.DefaultConfig.Workers, "number of workers to transfer files. defaults to 10")
_ = cmd.RegisterFlagCompletionFunc("workers", cobra.NoFileCompletions)
},
}
}
+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.
+8 -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.
@@ -19,6 +19,7 @@ import (
"github.com/bep/simplecobra"
"github.com/gohugoio/hugo/common/hugo"
"github.com/spf13/cobra"
)
func newEnvCommand() simplecobra.Commander {
@@ -47,6 +48,9 @@ func newEnvCommand() simplecobra.Commander {
}
return nil
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
},
}
}
@@ -59,5 +63,8 @@ func newVersionCmd() simplecobra.Commander {
},
short: "Print Hugo version and environment info",
long: "Print Hugo version and environment info. This is useful in Hugo bug reports.",
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
},
}
}
+13 -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.
@@ -75,9 +75,13 @@ See https://xyproto.github.io/splash/docs/all.html for a preview of the availabl
return nil
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
cmd.PersistentFlags().StringVar(&style, "style", "friendly", "highlighter style (see https://xyproto.github.io/splash/docs/)")
_ = cmd.RegisterFlagCompletionFunc("style", cobra.NoFileCompletions)
cmd.PersistentFlags().StringVar(&highlightStyle, "highlightStyle", "", "style used for highlighting lines (see https://github.com/alecthomas/chroma)")
_ = cmd.RegisterFlagCompletionFunc("highlightStyle", cobra.NoFileCompletions)
cmd.PersistentFlags().StringVar(&linesStyle, "linesStyle", "", "style used for line numbers (see https://github.com/alecthomas/chroma)")
_ = cmd.RegisterFlagCompletionFunc("linesStyle", cobra.NoFileCompletions)
},
}
}
@@ -101,7 +105,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
}
}
@@ -115,9 +119,9 @@ See https://xyproto.github.io/splash/docs/all.html for a preview of the availabl
return nil
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
cmd.PersistentFlags().StringVar(&genmandir, "dir", "man/", "the directory to write the man pages.")
// For bash-completion
cmd.PersistentFlags().SetAnnotation("dir", cobra.BashCompSubdirsInDir, []string{})
_ = cmd.MarkFlagDirname("dir")
},
}
}
@@ -150,7 +154,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
}
}
@@ -172,12 +176,11 @@ url: %s
return nil
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
cmd.PersistentFlags().StringVar(&gendocdir, "dir", "/tmp/hugodoc/", "the directory to write the doc.")
// For bash-completion
cmd.PersistentFlags().SetAnnotation("dir", cobra.BashCompSubdirsInDir, []string{})
_ = cmd.MarkFlagDirname("dir")
},
}
}
var docsHelperTarget string
@@ -196,7 +199,7 @@ url: %s
configProvider := func() docshelper.DocProvider {
conf := hugolib.DefaultConfig()
conf.CacheDir = "" // The default value does not make sense in the docs.
defaultConfig := parser.LowerCaseCamelJSONMarshaller{Value: conf}
defaultConfig := parser.NullBoolJSONMarshaller{Wrapped: parser.LowerCaseCamelJSONMarshaller{Value: conf}}
return docshelper.DocProvider{"config": defaultConfig}
}
@@ -228,6 +231,7 @@ url: %s
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.Hidden = true
cmd.ValidArgsFunction = cobra.NoFileCompletions
cmd.PersistentFlags().StringVarP(&docsHelperTarget, "dir", "", "docs/data", "data dir")
},
}
@@ -241,7 +245,6 @@ url: %s
newDocsHelper(),
},
}
}
type genCommand struct {
+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.
@@ -110,12 +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
err := os.MkdirAll(p, 0o777) // before umask
if err != nil {
log.Fatal(err)
}
+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.
+51 -99
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) {
@@ -441,7 +408,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 +434,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 +446,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 +620,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 +712,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 +751,7 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
if istemp {
continue
}
if h.Deps.SourceSpec.IgnoreFile(ev.Name) {
continue
}
@@ -811,7 +775,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 +791,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 +835,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 +872,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 +907,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 +930,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 +951,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 +963,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 +975,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 +999,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 {
+5 -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"
@@ -59,6 +58,7 @@ Import from Jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root
return c.importFromJekyll(args)
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
cmd.Flags().BoolVar(&c.force, "force", false, "allow import into non-empty target directory")
},
},
@@ -66,7 +66,6 @@ Import from Jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root
}
return c
}
type importCommand struct {
@@ -312,7 +311,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 +397,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 +427,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 +456,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
}
}
+19 -12
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,15 +23,14 @@ import (
"time"
"github.com/bep/simplecobra"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/resource"
"github.com/spf13/cobra"
)
// 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))),
@@ -47,7 +46,7 @@ func newListCommand() *listCommand {
list := func(cd *simplecobra.Commandeer, r *rootCommand, shouldInclude func(page.Page) bool, opts ...any) error {
bcfg := hugolib.BuildCfg{SkipRender: true}
cfg := config.New()
cfg := flagsToCfg(cd, nil)
for i := 0; i < len(opts); i += 2 {
cfg.Set(opts[i].(string), opts[i+1])
}
@@ -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,
@@ -106,6 +103,9 @@ func newListCommand() *listCommand {
"buildExpired", true,
)
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
},
},
&simpleCommand{
name: "future",
@@ -113,17 +113,19 @@ 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,
"buildDrafts", true,
)
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
},
},
&simpleCommand{
name: "expired",
@@ -131,7 +133,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
@@ -141,6 +143,9 @@ func newListCommand() *listCommand {
"buildDrafts", true,
)
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
},
},
&simpleCommand{
name: "all",
@@ -148,14 +153,16 @@ 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)
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
},
},
},
}
}
type listCommand struct {
+11 -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.
@@ -62,6 +62,7 @@ removed from Hugo, but we need to test this out in "real life" to get a feel of
so this may/will change in future versions of Hugo.
`,
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
applyLocalFlagsBuildConfig(cmd, r)
},
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
@@ -69,7 +70,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)
},
},
},
@@ -89,6 +90,7 @@ so this may/will change in future versions of Hugo.
inside a subfolder on GitHub, as one example.
`,
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
applyLocalFlagsBuildConfig(cmd, r)
},
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
@@ -108,6 +110,7 @@ so this may/will change in future versions of Hugo.
short: "Verify dependencies.",
long: `Verify checks that the dependencies of the current module, which are stored in a local downloaded source cache, have not been modified since being downloaded.`,
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
applyLocalFlagsBuildConfig(cmd, r)
cmd.Flags().BoolVarP(&clean, "clean", "", false, "delete module cache for dependencies that fail verification")
},
@@ -127,6 +130,7 @@ so this may/will change in future versions of Hugo.
Note that for vendored modules, that is the version listed and not the one from go.mod.
`,
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
applyLocalFlagsBuildConfig(cmd, r)
cmd.Flags().BoolVarP(&clean, "clean", "", false, "delete module cache for dependencies that fail verification")
},
@@ -144,8 +148,10 @@ Note that for vendored modules, that is the version listed and not the one from
short: "Delete the Hugo Module cache for the current project.",
long: `Delete the Hugo Module cache for the current project.`,
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
applyLocalFlagsBuildConfig(cmd, r)
cmd.Flags().StringVarP(&pattern, "pattern", "", "", `pattern matching module paths to clean (all if not set), e.g. "**hugo*"`)
_ = cmd.RegisterFlagCompletionFunc("pattern", cobra.NoFileCompletions)
cmd.Flags().BoolVarP(&all, "all", "", false, "clean entire module cache")
},
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
@@ -167,6 +173,7 @@ Note that for vendored modules, that is the version listed and not the one from
name: "tidy",
short: "Remove unused entries in go.mod and go.sum.",
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
applyLocalFlagsBuildConfig(cmd, r)
},
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
@@ -184,6 +191,7 @@ Note that for vendored modules, that is the version listed and not the one from
If a module is vendored, that is where Hugo will look for it's dependencies.
`,
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
applyLocalFlagsBuildConfig(cmd, r)
},
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
@@ -225,6 +233,7 @@ Run "go help get" for more information. All flags available for "go get" is also
` + commonUsageMod,
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.DisableFlagParsing = true
cmd.ValidArgsFunction = cobra.NoFileCompletions
},
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
// We currently just pass on the flags we get to Go and
+18 -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.
@@ -60,11 +60,17 @@ Ensure you run this within the root directory of your site.`,
return create.NewContent(h, contentType, args[0], force)
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return []string{}, cobra.ShellCompDirectiveNoFileComp
}
return []string{}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveFilterDirs
}
cmd.Flags().StringVarP(&contentType, "kind", "k", "", "content type to create")
cmd.Flags().String("editor", "", "edit new content with this editor, if provided")
_ = cmd.RegisterFlagCompletionFunc("editor", cobra.NoFileCompletions)
cmd.Flags().BoolVarP(&force, "force", "f", false, "overwrite file if it already exists")
applyLocalFlagsBuildConfig(cmd, r)
},
},
&simpleCommand{
@@ -104,8 +110,15 @@ Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
return nil
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return []string{}, cobra.ShellCompDirectiveNoFileComp
}
return []string{}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveFilterDirs
}
cmd.Flags().BoolVarP(&force, "force", "f", false, "init inside non-empty directory")
cmd.Flags().StringVar(&format, "format", "toml", "preferred file format (toml, yaml or json)")
_ = cmd.RegisterFlagCompletionFunc("format", cobra.FixedCompletions([]string{"toml", "yaml", "json"}, cobra.ShellCompDirectiveNoFileComp))
},
},
&simpleCommand{
@@ -138,12 +151,14 @@ according to your needs.`,
return nil
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
},
},
},
}
return c
}
type newCommand struct {
+3 -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
@@ -44,9 +43,11 @@ func newReleaseCommand() simplecobra.Commander {
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.Hidden = true
cmd.ValidArgsFunction = cobra.NoFileCompletions
cmd.PersistentFlags().BoolVarP(&skipPush, "skip-push", "", false, "skip pushing to remote")
cmd.PersistentFlags().BoolVarP(&try, "try", "", false, "no changes")
cmd.PersistentFlags().IntVarP(&step, "step", "", 0, "step to run (1: set new version 2: prepare next dev version)")
_ = cmd.RegisterFlagCompletionFunc("step", cobra.FixedCompletions([]string{"1", "2"}, cobra.ShellCompDirectiveNoFileComp))
},
}
}
+75 -73
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,22 +25,22 @@ import (
"io"
"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"
@@ -83,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 {
@@ -119,8 +123,8 @@ func newServerCommand() *serverCommand {
return mclib.RunMain()
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
cmd.Flags().BoolVar(&uninstall, "uninstall", false, "Uninstall the local CA (but do not delete it).")
},
},
},
@@ -219,7 +223,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
@@ -233,15 +237,16 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
listener := f.c.serverPorts[i].ln
logger := f.c.r.logger
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")
r.Printf("Environment: %q\n", f.c.hugoTry().Deps.Site.Hugo().Environment)
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)
}
}
@@ -255,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 {
@@ -280,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))
@@ -308,10 +307,10 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
if redirect := serverConfig.MatchRedirect(requestURI); !redirect.IsZero() {
// fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))
doRedirect := true
// This matches Netlify's behaviour and is needed for SPA behaviour.
// This matches Netlify's behavior and is needed for SPA behavior.
// See https://docs.netlify.com/routing/redirects/rewrites-proxies/
if !redirect.Force {
path := filepath.Clean(strings.TrimPrefix(requestURI, u.Path))
path := filepath.Clean(strings.TrimPrefix(requestURI, baseURL.Path()))
if root != "" {
path = filepath.Join(root, path)
}
@@ -338,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)
@@ -347,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
}
@@ -385,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
@@ -401,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 {
@@ -447,7 +446,6 @@ type serverCommand struct {
doLiveReload bool
// Flags.
renderToDisk bool
renderStaticToDisk bool
navigateToChanged bool
serverAppend bool
@@ -455,6 +453,7 @@ type serverCommand struct {
tlsCertFile string
tlsKeyFile string
tlsAuto bool
pprof bool
serverPort int
liveReloadPort int
serverWatch bool
@@ -469,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 {
@@ -494,8 +497,7 @@ func (c *serverCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, arg
err := func() error {
defer c.r.timeTrack(time.Now(), "Built")
err := c.build()
return err
return c.build()
}()
if err != nil {
return err
@@ -510,8 +512,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.
The ` + "`" + `hugo server` + "`" + ` command will by default write and serve 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
@@ -520,27 +523,26 @@ of a second, you will be able to save and see your changes nearly instantly.`
cmd.Aliases = []string{"serve"}
cmd.Flags().IntVarP(&c.serverPort, "port", "p", 1313, "port on which the server will listen")
_ = cmd.RegisterFlagCompletionFunc("port", cobra.NoFileCompletions)
cmd.Flags().IntVar(&c.liveReloadPort, "liveReloadPort", -1, "port for live reloading (i.e. 443 in HTTPS proxy situations)")
_ = cmd.RegisterFlagCompletionFunc("liveReloadPort", cobra.NoFileCompletions)
cmd.Flags().StringVarP(&c.serverInterface, "bind", "", "127.0.0.1", "interface to which the server will bind")
_ = cmd.RegisterFlagCompletionFunc("bind", cobra.NoFileCompletions)
cmd.Flags().StringVarP(&c.tlsCertFile, "tlsCertFile", "", "", "path to TLS certificate file")
_ = cmd.MarkFlagFilename("tlsCertFile", "pem")
cmd.Flags().StringVarP(&c.tlsKeyFile, "tlsKeyFile", "", "", "path to TLS key file")
_ = cmd.MarkFlagFilename("tlsKeyFile", "pem")
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")
cmd.Flags().String("memstats", "", "log memory usage to this file")
cmd.Flags().String("meminterval", "100ms", "interval to poll memory usage (requires --memstats), valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\".")
cmd.Flags().SetAnnotation("tlsCertFile", cobra.BashCompSubdirsInDir, []string{})
cmd.Flags().SetAnnotation("tlsKeyFile", cobra.BashCompSubdirsInDir, []string{})
r := cd.Root.Command.(*rootCommand)
applyLocalFlagsBuild(cmd, r)
@@ -566,7 +568,7 @@ func (c *serverCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
}
}
if err := c.setBaseURLsInConfig(); err != nil {
if err := c.setServerInfoInConfig(); err != nil {
return err
}
@@ -582,7 +584,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
@@ -609,15 +613,15 @@ func (c *serverCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
return nil
}
func (c *serverCommand) setBaseURLsInConfig() error {
func (c *serverCommand) setServerInfoInConfig() error {
if len(c.serverPorts) == 0 {
panic("no server ports set")
}
return c.withConfE(func(conf *commonConfig) error {
for i, language := range conf.configs.Languages {
isMultiHost := conf.configs.IsMultihost
isMultihost := conf.configs.IsMultihost
var serverPort int
if isMultiHost {
if isMultihost {
serverPort = c.serverPorts[i].p
} else {
serverPort = c.serverPorts[0].p
@@ -636,7 +640,8 @@ func (c *serverCommand) setBaseURLsInConfig() error {
if c.liveReloadPort != -1 {
baseURLLiveReload, _ = baseURLLiveReload.WithPort(c.liveReloadPort)
}
langConfig.C.SetBaseURL(baseURL, baseURLLiveReload)
langConfig.C.SetServerInfo(baseURL, baseURLLiveReload, c.serverInterface)
}
return nil
})
@@ -676,7 +681,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
}
}
@@ -701,7 +706,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 {
@@ -736,9 +740,9 @@ func (c *serverCommand) createServerPorts(cd *simplecobra.Commandeer) error {
flags := cd.CobraCommand.Flags()
var cerr error
c.withConf(func(conf *commonConfig) {
isMultiHost := conf.configs.IsMultihost
isMultihost := conf.configs.IsMultihost
c.serverPorts = make([]serverPortListener, 1)
if isMultiHost {
if isMultihost {
if !c.serverAppend {
cerr = errors.New("--appendPort=false not supported when in multihost mode")
return
@@ -831,9 +835,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()
@@ -846,12 +850,12 @@ func (c *serverCommand) partialReRender(urls ...string) error {
func (c *serverCommand) serve() error {
var (
baseURLs []string
baseURLs []urls.BaseURL
roots []string
h *hugolib.HugoSites
)
err := c.withConfE(func(conf *commonConfig) error {
isMultiHost := conf.configs.IsMultihost
isMultihost := conf.configs.IsMultihost
var err error
h, err = c.r.HugFromConfig(conf)
if err != nil {
@@ -861,20 +865,19 @@ func (c *serverCommand) serve() error {
// We need the server to share the same logger as the Hugo build (for error counts etc.)
c.r.logger = h.Log
if isMultiHost {
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
}
@@ -946,13 +949,9 @@ 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) %s\n", serverURL, c.serverInterface, roots[i])
wg1.Go(func() error {
@@ -971,8 +970,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")
@@ -983,7 +986,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
}
@@ -1005,7 +1008,6 @@ func (c *serverCommand) serve() error {
}
}
}()
if err != nil {
c.r.Println("Error:", err)
}
@@ -1087,7 +1089,7 @@ func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
fromPath := ev.Name
relPath, found := sourceFs.MakePathRelative(fromPath)
relPath, found := sourceFs.MakePathRelative(fromPath, true)
if !found {
// Not member of this virtual host.
@@ -1167,7 +1169,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 -1
View File
@@ -13,10 +13,33 @@
package constants
// Error IDs.
// Error/Warning IDs.
// Do not change these values.
const (
// 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")
+6 -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.
@@ -122,3 +122,8 @@ func InSlicEqualFold(arr []string, el string) bool {
}
return false
}
type Tuple struct {
First string
Second string
}
+1 -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.
@@ -33,7 +33,6 @@ 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) {
@@ -42,7 +41,6 @@ func TestGetOrCompileRegexp(t *testing.T) {
re, err := GetOrCompileRegexp(`\d+`)
c.Assert(err, qt.IsNil)
c.Assert(re.MatchString("123"), qt.Equals, true)
}
func BenchmarkGetOrCompileRegexp(b *testing.B) {
@@ -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
}
+27 -2
View File
@@ -35,6 +35,8 @@ import (
"github.com/spf13/afero"
iofs "io/fs"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/hugofs"
)
@@ -109,11 +111,29 @@ func (i HugoInfo) Deps() []*Dependency {
return i.deps
}
// Deprecated: Use hugo.IsMultihost instead.
func (i HugoInfo) IsMultiHost() bool {
Deprecate("hugo.IsMultiHost", "Use hugo.IsMultihost instead.", "v0.124.0")
return i.conf.IsMultihost()
}
// IsMultihost reports whether each configured language has a unique baseURL.
func (i HugoInfo) IsMultihost() bool {
return i.conf.IsMultihost()
}
// IsMultilingual reports whether there are two or more configured languages.
func (i HugoInfo) IsMultilingual() bool {
return i.conf.IsMultilingual()
}
// ConfigProvider represents the config options that are relevant for HugoInfo.
type ConfigProvider interface {
Environment() string
Running() bool
WorkingDir() string
IsMultihost() bool
IsMultilingual() bool
}
// NewInfo creates a new Hugo Info object.
@@ -159,7 +179,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()), ".", "_"))
@@ -252,7 +277,7 @@ 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"),
)
}
+77
View File
@@ -0,0 +1,77 @@
// 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 hugo_test
import (
"strings"
"testing"
"github.com/gohugoio/hugo/hugolib"
)
func TestIsMultilingualAndIsMultihost(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
defaultContentLanguageInSubdir = true
[languages.de]
baseURL = 'https://de.example.org/'
[languages.en]
baseURL = 'https://en.example.org/'
-- content/_index.md --
---
title: home
---
-- layouts/index.html --
multilingual={{ hugo.IsMultilingual }}
multihost={{ hugo.IsMultihost }}
`
b := hugolib.Test(t, files)
b.AssertFileContent("public/de/index.html",
"multilingual=true",
"multihost=true",
)
b.AssertFileContent("public/en/index.html",
"multilingual=true",
"multihost=true",
)
files = strings.ReplaceAll(files, "baseURL = 'https://de.example.org/'", "")
files = strings.ReplaceAll(files, "baseURL = 'https://en.example.org/'", "")
b = hugolib.Test(t, files)
b.AssertFileContent("public/de/index.html",
"multilingual=true",
"multihost=false",
)
b.AssertFileContent("public/en/index.html",
"multilingual=true",
"multihost=false",
)
files = strings.ReplaceAll(files, "[languages.de]", "")
files = strings.ReplaceAll(files, "[languages.en]", "")
b = hugolib.Test(t, files)
b.AssertFileContent("public/en/index.html",
"multilingual=false",
"multihost=false",
)
}
+13 -3
View File
@@ -65,9 +65,11 @@ func TestDeprecationLogLevelFromVersion(t *testing.T) {
}
type testConfig struct {
environment string
running bool
workingDir string
environment string
running bool
workingDir string
multihost bool
multilingual bool
}
func (c testConfig) Environment() string {
@@ -81,3 +83,11 @@ func (c testConfig) Running() bool {
func (c testConfig) WorkingDir() string {
return c.workingDir
}
func (c testConfig) IsMultihost() bool {
return c.multihost
}
func (c testConfig) IsMultilingual() bool {
return c.multilingual
}
+3 -3
View File
@@ -17,7 +17,7 @@ package hugo
// This should be the only one.
var CurrentVersion = Version{
Major: 0,
Minor: 120,
PatchLevel: 4,
Suffix: "",
Minor: 125,
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)
+67 -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.
//
@@ -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)
@@ -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),
@@ -145,18 +159,29 @@ func NewDefault() Logger {
return New(opts)
}
func NewTrace() Logger {
opts := Options{
DistinctLevel: logg.LevelWarn,
Level: logg.LevelTrace,
Stdout: os.Stdout,
Stderr: os.Stdout,
}
return New(opts)
}
func LevelLoggerToWriter(l logg.LevelLogger) io.Writer {
return logWriter{l: l}
}
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,8 +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 {
@@ -183,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...)
}
@@ -290,10 +322,24 @@ 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")
}
@@ -315,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
}
+1 -1
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.
//
+1 -1
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.
//
+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()
}
+8 -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,20 @@ 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
}
}
}
-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{})
}
+577
View File
@@ -0,0 +1,577 @@
// 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"
"sync"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/identity"
)
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
}
// ParseIdentity parses component c with path s into a StringIdentity.
func (pp *PathParser) ParseIdentity(c, s string) identity.StringIdentity {
p := pp.parsePooled(c, s)
defer putPath(p)
return identity.StringIdentity(p.IdentifierBase())
}
// ParseBaseAndBaseNameNoIdentifier parses component c with path s into a base and a base name without any identifier.
func (pp *PathParser) ParseBaseAndBaseNameNoIdentifier(c, s string) (string, string) {
p := pp.parsePooled(c, s)
defer putPath(p)
return p.Base(), p.BaseNameNoIdentifier()
}
func (pp *PathParser) parsePooled(c, s string) *Path {
s = NormalizePathStringBasic(s)
p := getPath()
p.component = c
p, err := pp.doParse(c, s, p)
if err != nil {
panic(err)
}
return p
}
// Parse parses component c with path s into Path using Hugo's content path rules.
func (pp *PathParser) Parse(c, s string) *Path {
p, err := pp.parse(c, s)
if err != nil {
panic(err)
}
return p
}
func (pp *PathParser) newPath(component string) *Path {
return &Path{
component: component,
posContainerLow: -1,
posContainerHigh: -1,
posSectionHigh: -1,
posIdentifierLanguage: -1,
}
}
func (pp *PathParser) parse(component, s string) (*Path, error) {
ss := NormalizePathStringBasic(s)
p, err := pp.doParse(component, ss, pp.newPath(component))
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, pp.newPath(component))
if err != nil {
return nil, err
}
} else {
p.unnormalized = p
}
return p, nil
}
func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
hasLang := pp.LanguageIndex != nil
hasLang = hasLang && (component == files.ComponentFolderContent || component == files.ComponentFolderLayouts)
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 below 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 {
// Note: Any additions to this struct should also be added to the pathPool.
s string
posContainerLow int
posContainerHigh int
posSectionHigh int
component string
bundleType PathType
identifiers []types.LowHigh
posIdentifierLanguage int
disabled bool
trimLeadingSlash bool
unnormalized *Path
}
var pathPool = &sync.Pool{
New: func() any {
p := &Path{}
p.reset()
return p
},
}
func getPath() *Path {
return pathPool.Get().(*Path)
}
func putPath(p *Path) {
p.reset()
pathPool.Put(p)
}
func (p *Path) reset() {
p.s = ""
p.posContainerLow = -1
p.posContainerHigh = -1
p.posSectionHigh = -1
p.component = ""
p.bundleType = 0
p.identifiers = p.identifiers[:0]
p.posIdentifierLanguage = -1
p.disabled = false
p.trimLeadingSlash = false
p.unnormalized = nil
}
// 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 satisfies identity.Identity.
func (p *Path) IdentifierBase() string {
return p.Base()
}
// 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 without any extension.
func (p *Path) NameNoExt() string {
if i := p.identifierIndex(0); i != -1 {
return p.s[p.posContainerHigh : p.identifiers[i].Low-1]
}
return p.s[p.posContainerHigh:]
}
// Name returns the last element of path without 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 logical base name for a resource without any identifier (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 without any identifier (e.g. no extension).
func (p *Path) NameNoIdentifier() string {
if len(p.identifiers) > 0 {
return p.s[p.posContainerHigh : p.identifiers[len(p.identifiers)-1].Low-1]
}
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 relative to 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) ForBundleType(t PathType) *Path {
p.bundleType = t
return &p
}
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
}
+357
View File
@@ -0,0 +1,357 @@
// 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)
}
func BenchmarkParseIdentity(b *testing.B) {
for i := 0; i < b.N; i++ {
testParser.ParseIdentity(files.ComponentFolderAssets, "/a/b.css")
}
}
+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]]
}
+12 -1
View File
@@ -71,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, "/")
@@ -161,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
}
@@ -182,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.
+26
View File
@@ -92,5 +92,31 @@ 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
}
// Unwrap returns the underlying value of v if it implements Unwrapper, otherwise v is returned.
func Unwrapv(v any) any {
if u, ok := v.(Unwrapper); ok {
return u.Unwrapv()
}
return v
}
// 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) {
})
}
})
}
+130 -73
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.
@@ -30,13 +30,16 @@ 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/types"
"github.com/gohugoio/hugo/common/urls"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/privacy"
"github.com/gohugoio/hugo/config/security"
"github.com/gohugoio/hugo/config/services"
"github.com/gohugoio/hugo/deploy"
"github.com/gohugoio/hugo/deploy/deployconfig"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugolib/segments"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/markup/markup_config"
"github.com/gohugoio/hugo/media"
@@ -63,6 +66,7 @@ type InternalConfig struct {
Verbose bool
Clock string
Watch bool
FastRenderMode bool
LiveReloadPort int
}
@@ -100,9 +104,11 @@ type Config struct {
RootConfig
// Author information.
// Deprecated: Use taxonomies instead.
Author map[string]any
// Social links.
// Deprecated: Use .Site.Params instead.
Social map[string]string
// The build configuration section contains build-related configuration options.
@@ -134,12 +140,15 @@ type Config struct {
// a slice of page matcher and params to apply to those pages.
Cascade *config.ConfigNamespace[[]page.PageMatcherParamsConfig, map[page.PageMatcher]maps.Params] `mapstructure:"-"`
// The segments defines segments for the site. Used for partial/segmented builds.
Segments *config.ConfigNamespace[map[string]segments.SegmentConfig, segments.Segments] `mapstructure:"-"`
// Menu configuration.
// <docsmeta>{"refs": ["config:languages:menus"] }</docsmeta>
Menus *config.ConfigNamespace[map[string]navigation.MenuConfig, navigation.Menus] `mapstructure:"-"`
// The deployment configuration section contains for hugo deploy.
Deployment deploy.DeployConfig `mapstructure:"-"`
// The deployment configuration section contains for hugo deployconfig.
Deployment deployconfig.DeployConfig `mapstructure:"-"`
// Module configuration.
Module modules.Config `mapstructure:"-"`
@@ -205,7 +214,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)
@@ -283,12 +292,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 {
@@ -297,9 +307,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)
@@ -355,11 +365,12 @@ 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,
IgnoreFile: ignoreFile,
SegmentFilter: c.Segments.Config.Get(func(s string) { logger.Warnf("Render segment %q not found in configuration", s) }, c.RootConfig.RenderSegments...),
MainSections: c.MainSections,
Clock: clock,
transientErr: transientErr,
@@ -389,13 +400,15 @@ type ConfigCompiled struct {
Timeout time.Duration
BaseURL urls.BaseURL
BaseURLLiveReload urls.BaseURL
ServerInterface string
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
SegmentFilter segments.SegmentFilter
MainSections []string
Clock time.Time
@@ -408,24 +421,28 @@ 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) {
func (c *ConfigCompiled) SetServerInfo(baseURL, baseURLLiveReload urls.BaseURL, serverInterface string) {
c.BaseURL = baseURL
c.BaseURLLiveReload = baseURLLiveReload
c.ServerInterface = serverInterface
}
// 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>
@@ -465,6 +482,10 @@ type RootConfig struct {
// A list of languages to disable.
DisableLanguages []string
// The named segments to render.
// This needs to match the name of the segment in the segments configuration.
RenderSegments []string
// Disable the injection of the Hugo generator tag on the home page.
DisableHugoGeneratorInject bool
@@ -496,8 +517,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.
@@ -531,6 +552,9 @@ type RootConfig struct {
// Note that this currently only works for English, but you can provide your own title in the content file's front matter.
PluralizeListTitles bool
// Whether to capitalize automatic page titles, applicable to section, taxonomy, and term pages.
CapitalizeListTitles bool
// Make all relative URLs absolute using the baseURL.
// <docsmeta>{"identifiers": ["baseURL"] }</docsmeta>
CanonifyURLs bool
@@ -648,16 +672,26 @@ 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
}
func (c *Configs) Validate(logger loggers.Logger) error {
for p := range c.Base.Cascade.Config {
page.CheckCascadePattern(logger, p)
}
return nil
}
// transientErr returns the last transient error found during config compilation.
func (c *Configs) transientErr() error {
for _, l := range c.LanguageConfigSlice {
@@ -674,6 +708,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{
@@ -751,10 +838,9 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
}
langConfigMap := make(map[string]*Config)
var langConfigs []*Config
languagesConfig := cfg.GetStringMap("languages")
var isMultiHost bool
var isMultihost bool
if err := all.CompileConfig(logger); err != nil {
return nil, err
@@ -791,7 +877,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
}
if kk == "baseurl" {
// baseURL configure don the language level is a multihost setup.
isMultiHost = true
isMultihost = true
}
mergedConfig.Set(kk, vv)
rootv := cfg.Get(kk)
@@ -840,6 +926,18 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
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:
@@ -848,65 +946,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 langConfigs {
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, logger loggers.Logger, bcfg config.BaseConfig, p config.Provider, target *Config, keys []string) error {
var decoderSetups []decodeWeight
if len(keys) == 0 {
@@ -10,7 +10,6 @@ import (
)
func TestDirsMount(t *testing.T) {
files := `
-- hugo.toml --
baseURL = "https://example.com"
@@ -44,7 +43,7 @@ Title: {{ .Title }}
hugolib.IntegrationTestConfig{T: t, TxtarString: files},
).Build()
//b.AssertFileContent("public/p1/index.html", "Title: p1")
// b.AssertFileContent("public/p1/index.html", "Title: p1")
sites := b.H.Sites
b.Assert(len(sites), qt.Equals, 2)
@@ -58,7 +57,7 @@ Title: {{ .Title }}
enConcp := sites[0].Conf
enConf := enConcp.GetConfig().(*allconfig.Config)
b.Assert(enConcp.BaseURL().String(), qt.Equals, "https://example.com")
b.Assert(enConcp.BaseURL().String(), qt.Equals, "https://example.com/")
modConf := enConf.Module
b.Assert(modConf.Mounts, qt.HasLen, 8)
b.Assert(modConf.Mounts[0].Source, qt.Equals, filepath.FromSlash("content/en"))
@@ -67,11 +66,9 @@ Title: {{ .Title }}
b.Assert(modConf.Mounts[1].Source, qt.Equals, filepath.FromSlash("content/sv"))
b.Assert(modConf.Mounts[1].Target, qt.Equals, "content")
b.Assert(modConf.Mounts[1].Lang, qt.Equals, "sv")
}
func TestConfigAliases(t *testing.T) {
files := `
-- hugo.toml --
baseURL = "https://example.com"
+14 -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.
@@ -24,12 +24,14 @@ import (
"github.com/gohugoio/hugo/config/privacy"
"github.com/gohugoio/hugo/config/security"
"github.com/gohugoio/hugo/config/services"
"github.com/gohugoio/hugo/deploy"
"github.com/gohugoio/hugo/deploy/deployconfig"
"github.com/gohugoio/hugo/hugolib/segments"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/markup/markup_config"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/minifiers"
"github.com/gohugoio/hugo/modules"
"github.com/gohugoio/hugo/navigation"
"github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/related"
@@ -120,6 +122,14 @@ var allDecoderSetups = map[string]decodeWeight{
return err
},
},
"segments": {
key: "segments",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.Segments, err = segments.DecodeSegments(p.p.GetStringMap(d.key))
return err
},
},
"server": {
key: "server",
decode: func(d decodeWeight, p decodeConfig) error {
@@ -291,7 +301,7 @@ var allDecoderSetups = map[string]decodeWeight{
key: "cascade",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.Cascade, err = page.DecodeCascadeConfig(p.p.Get(d.key))
p.c.Cascade, err = page.DecodeCascadeConfig(nil, p.p.Get(d.key))
return err
},
},
@@ -331,7 +341,7 @@ var allDecoderSetups = map[string]decodeWeight{
key: "deployment",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.Deployment, err = deploy.DecodeConfig(p.p)
p.c.Deployment, err = deployconfig.DecodeConfig(p.p)
return err
},
},
+27 -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.
@@ -16,8 +16,10 @@ package allconfig
import (
"time"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/urls"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/langs"
)
@@ -41,11 +43,16 @@ func (c ConfigLanguage) LanguagesDefaultFirst() langs.Languages {
return c.m.LanguagesDefaultFirst
}
func (c ConfigLanguage) PathParser() *paths.PathParser {
return c.m.ContentPathParser
}
func (c ConfigLanguage) LanguagePrefix() string {
if c.DefaultContentLanguageInSubdir() && c.DefaultContentLanguage() == c.Language().Lang {
return c.Language().Lang
}
if !c.IsMultiLingual() || c.DefaultContentLanguage() == c.Language().Lang {
if !c.IsMultilingual() || c.DefaultContentLanguage() == c.Language().Lang {
return ""
}
return c.Language().Lang
@@ -67,7 +74,11 @@ func (c ConfigLanguage) IsMultihost() bool {
return c.m.IsMultihost
}
func (c ConfigLanguage) IsMultiLingual() bool {
func (c ConfigLanguage) FastRenderMode() bool {
return c.config.Internal.FastRenderMode
}
func (c ConfigLanguage) IsMultilingual() bool {
return len(c.m.Languages) > 1
}
@@ -83,8 +94,8 @@ func (c ConfigLanguage) IsLangDisabled(lang string) bool {
return c.config.C.DisabledLanguages[lang]
}
func (c ConfigLanguage) IgnoredErrors() map[string]bool {
return c.config.C.IgnoredErrors
func (c ConfigLanguage) IgnoredLogs() map[string]bool {
return c.config.C.IgnoredLogs
}
func (c ConfigLanguage) NoBuildLock() bool {
@@ -119,6 +130,17 @@ func (c ConfigLanguage) Quiet() bool {
return c.m.Base.Internal.Quiet
}
func (c ConfigLanguage) Watching() bool {
return c.m.Base.Internal.Watch
}
func (c ConfigLanguage) NewIdentityManager(name string) identity.Manager {
if !c.Watching() {
return identity.NopManager
}
return identity.NewManager(name)
}
// GetConfigSection is mostly used in tests. The switch statement isn't complete, but what's in use.
func (c ConfigLanguage) GetConfigSection(s string) any {
switch s {

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