mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-29 17:52:38 +00:00
Compare commits
145 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a858213b7 | |||
| 6ac7d081d1 | |||
| 24eb84f868 | |||
| 95a3678245 | |||
| 9045797d5f | |||
| 73641aeca1 | |||
| d8c0dfccf7 | |||
| 6bd2bde9d3 | |||
| 0f1c7d1200 | |||
| 10352335e0 | |||
| f23576f2fb | |||
| b5d43cdc17 | |||
| 6ef8017f60 | |||
| d67925f5a1 | |||
| 48dd4f469a | |||
| b1e1eede50 | |||
| f522a7282b | |||
| 6ac72dd3bf | |||
| 74c0bbfc6a | |||
| e66a33d369 | |||
| 8a979d54d1 | |||
| fd49df8f7a | |||
| c7b35c8704 | |||
| 5916b61be9 | |||
| 7eafef2269 | |||
| 00c4228f61 | |||
| 5f5b2f378f | |||
| b3ea2a5fab | |||
| 67b543549b | |||
| ef7a22a2ff | |||
| 8dfcece805 | |||
| 9a6bfe2667 | |||
| 7d45614c6d | |||
| 7f12c0900d | |||
| dab2c332dc | |||
| 5a64551a07 | |||
| 0fc63fbf57 | |||
| 576d55d009 | |||
| b9400b8a95 | |||
| ce0c7f42c1 | |||
| f472dd48a7 | |||
| 50973e7272 | |||
| 48566b6f37 | |||
| b441472b69 | |||
| ac2c3fb20f | |||
| ee91c707ee | |||
| 11f7f39913 | |||
| 192e3c453e | |||
| e569dd59a0 | |||
| 5ba03bf657 | |||
| c1b2e58bfb | |||
| 77214117ae | |||
| 1a94731669 | |||
| 18784711b5 | |||
| 2c611091fd | |||
| 8e9e04e395 | |||
| 32334d098e | |||
| 8e2e60dd45 | |||
| 2bc54df622 | |||
| 608ed09a0a | |||
| 94f1ede3aa | |||
| ab374e3d22 | |||
| 1a9133075c | |||
| d36a8f5601 | |||
| 487b8e3af3 | |||
| a6f99cca22 | |||
| a775488304 | |||
| 66ba63cd5f | |||
| eb06a3cd0c | |||
| a798e3e724 | |||
| fbcf0d52a1 | |||
| a2b2a5a978 | |||
| ec178ea463 | |||
| 2d80b8a741 | |||
| 5ea3e13db6 | |||
| b1c1bd019f | |||
| 866b8e5aa6 | |||
| 409669fa48 | |||
| f66d094446 | |||
| e9b9b36f46 | |||
| b8a2c10d06 | |||
| 601df38834 | |||
| 3bed212a2f | |||
| e2fd6764be | |||
| 8f3527f61c | |||
| 09048aad76 | |||
| f42c422a12 | |||
| 0b71db299a | |||
| 5c7fad23a3 | |||
| 700bb78198 | |||
| d3b5d47a43 | |||
| 86cd1838ff | |||
| 871da3370a | |||
| 2637aa1540 | |||
| bf1d20d735 | |||
| 1deec99bba | |||
| b7bb557c6a | |||
| 7c19c196c3 | |||
| 555443b64a | |||
| 1f2de189ed | |||
| c6ae33c61d | |||
| edeebf0d33 | |||
| ea9675f6e7 | |||
| b1f7e35a98 | |||
| a0d4e1fb26 | |||
| 96777d9b89 | |||
| 25126e5f87 | |||
| 179034abbb | |||
| 96ac146eef | |||
| d4c0e4452c | |||
| 418156ec3e | |||
| 798533a201 | |||
| a8c5d0d00d | |||
| a94a941fe3 | |||
| 385d1a1342 | |||
| d30bd50467 | |||
| 8e6cac8462 | |||
| 4085ee9340 | |||
| 168bf17e70 | |||
| 80ea90c880 | |||
| f740d7cfe0 | |||
| 5f46da6e2a | |||
| fe64a68225 | |||
| b4128babb8 | |||
| 790a5b7014 | |||
| 70ce2446b5 | |||
| 08e1ea5c70 | |||
| 65d43e1d0d | |||
| 65a76664e3 | |||
| da5b1fcf8f | |||
| 39649dce49 | |||
| 614fb16ec6 | |||
| 0637adb239 | |||
| 0bf61353f6 | |||
| b82e496c13 | |||
| 33542d3966 | |||
| 588d20b315 | |||
| b33730293d | |||
| e6a3f1d62b | |||
| 573ecb530f | |||
| 9937a5dcaa | |||
| 1b4514e020 | |||
| 429e57201d | |||
| fa7d37f0c8 | |||
| fae49a22f1 |
@@ -19,4 +19,4 @@ jobs:
|
||||
pr-label: AI Suspect
|
||||
fail-when-confident: false
|
||||
# CSV list.
|
||||
pr-user-logins-skip: dependabot[bot]
|
||||
pr-user-logins-skip: dependabot[bot],bep,jmooring
|
||||
|
||||
@@ -106,7 +106,10 @@ jobs:
|
||||
run: go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Run staticcheck
|
||||
run: staticcheck ./...
|
||||
run: |
|
||||
export STATICCHECK_CACHE="${{ runner.temp }}/staticcheck"
|
||||
staticcheck ./...
|
||||
rm -rf ${{ runner.temp }}/staticcheck
|
||||
- if: matrix.os != 'windows-latest'
|
||||
name: Check
|
||||
run: |
|
||||
@@ -118,16 +121,14 @@ jobs:
|
||||
# See issue #11052. We limit the build to regular test (no -race flag) on Windows for now.
|
||||
name: Test
|
||||
run: |
|
||||
mage -v test;
|
||||
mage -v test
|
||||
env:
|
||||
HUGO_BUILD_TAGS: extended,withdeploy
|
||||
- name: Build tags
|
||||
run: |
|
||||
go install -tags extended
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Build for dragonfly
|
||||
run: |
|
||||
go install
|
||||
go clean -i -cache
|
||||
env:
|
||||
GOARCH: amd64
|
||||
GOOS: dragonfly
|
||||
|
||||
+2
-1
@@ -3,4 +3,5 @@
|
||||
imports.*
|
||||
dist/
|
||||
public/
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
cache/filecache/_gen/
|
||||
@@ -0,0 +1 @@
|
||||
tpl/tplimpl/embedded/templates/**
|
||||
@@ -0,0 +1,14 @@
|
||||
* Brevity is good.
|
||||
* Assume that the maintainers and readers of the code you write are Go experts:
|
||||
* Don't use comments to explain the obvious.
|
||||
* Use self-explanatory variable and function names.
|
||||
* Use short variable names when the context is clear.
|
||||
* If you need to add temporary debug printing, use `hdebug.Printf`.[^1]
|
||||
* Never export symbols that's not needed outside of the package.
|
||||
* Avoid global state at (almost) all cost.
|
||||
* This is a project with a long history; assume that a similiar problem has been solved before, look hard for helper functions before creating new ones.
|
||||
* Use `./check.sh ./somepackage/...` when iterating.
|
||||
* Use `./check.sh` when you're done.
|
||||
|
||||
|
||||
[^1]: CI build fail if you forget to remove the debug printing.
|
||||
+1
-1
@@ -77,7 +77,7 @@ To make the contribution process as seamless as possible, we ask for the followi
|
||||
* Run `go fmt`.
|
||||
* Add documentation if you are adding new features or changing functionality. The docs site lives in `/docs`.
|
||||
* Squash your commits into a single commit. `git rebase -i`. It’s okay to force update your pull request with `git push -f`.
|
||||
* Ensure that `mage check` succeeds. [Travis CI](https://travis-ci.org/gohugoio/hugo) (Windows, Linux and macOS) will fail the build if `mage check` fails.
|
||||
* Ensure that `./check.sh` succeeds. Note that some tests are skipped when running locally, some because they are slow. To run these locally, do `CI_LOCAL=true ./check.sh ./somepackage/...`.
|
||||
* Follow the **Git Commit Message Guidelines** below.
|
||||
|
||||
## AI Assistance Notice
|
||||
|
||||
@@ -52,7 +52,7 @@ Use Hugo's embedded web server during development to instantly see changes to co
|
||||
|
||||
Hugo's fast asset pipelines include:
|
||||
|
||||
- Image processing – Convert, resize, crop, rotate, adjust colors, apply filters, overlay text and images, and extract EXIF data
|
||||
- Image processing – Convert, resize, crop, rotate, adjust colors, apply filters, overlay text and images, and extract metadata
|
||||
- JavaScript bundling – Transpile TypeScript and JSX to JavaScript, bundle, tree shake, minify, create source maps, and perform SRI hashing.
|
||||
- Sass processing – Transpile Sass to CSS, bundle, tree shake, minify, create source maps, perform SRI hashing, and integrate with PostCSS
|
||||
- Tailwind CSS processing – Compile Tailwind CSS utility classes into standard CSS, bundle, tree shake, optimize, minify, perform SRI hashing, and integrate with PostCSS
|
||||
@@ -65,8 +65,6 @@ See the [features] section of the documentation for a comprehensive summary of H
|
||||
|
||||
<p> </p>
|
||||
<p float="left">
|
||||
<a href="https://www.linode.com/?utm_campaign=hugosponsor&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/hugoDocs/master/assets/images/sponsors/linode-logo_standard_light_medium.png" width="200" alt="Linode"></a>
|
||||
|
||||
<a href="https://www.jetbrains.com/go/?utm_source=OSS&utm_medium=referral&utm_campaign=hugo" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/hugoDocs/master/assets/images/sponsors/goland.svg" width="200" alt="The complete IDE crafted for professional Go developers."></a>
|
||||
|
||||
<a href="https://cloudcannon.com/hugo-cms/?utm_campaign=HugoSponsorship&utm_source=sponsor&utm_content=gohugo" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/hugoDocs/master/assets/images/sponsors/cloudcannon-cms-logo.svg" width="200" alt="CloudCannon"></a>
|
||||
@@ -74,21 +72,17 @@ See the [features] section of the documentation for a comprehensive summary of H
|
||||
|
||||
## Editions
|
||||
|
||||
Hugo is available in three editions: standard, extended, and extended/deploy. While the standard edition provides core functionality, the extended and extended/deploy editions offer advanced features.
|
||||
Hugo offers a standard edition with core features, plus extended and extended/deploy editions with more. Use the standard edition unless you need the features below.
|
||||
|
||||
Feature|extended edition|extended/deploy edition
|
||||
:--|:-:|:-:
|
||||
Encode to the WebP format when [processing images]. You can decode WebP images with any edition.|:heavy_check_mark:|:heavy_check_mark:
|
||||
[Transpile Sass to CSS] using the embedded LibSass transpiler. You can use the [Dart Sass] transpiler with any edition.|:heavy_check_mark:|:heavy_check_mark:
|
||||
[Transpile Sass to CSS] via embedded LibSass. Note that embedded LibSass was deprecated in v0.153.0 and will be removed in a future release. Use the [Dart Sass] transpiler instead, which is compatible with any edition.|:heavy_check_mark:|:heavy_check_mark:
|
||||
Deploy your site directly to a Google Cloud Storage bucket, an AWS S3 bucket, or an Azure Storage container. See [details].|:x:|:heavy_check_mark:
|
||||
|
||||
[dart sass]: https://gohugo.io/functions/css/sass/#dart-sass
|
||||
[processing images]: https://gohugo.io/content-management/image-processing/
|
||||
[transpile sass to css]: https://gohugo.io/functions/css/sass/
|
||||
[details]: https://gohugo.io/hosting-and-deployment/hugo-deploy/
|
||||
|
||||
Unless your specific deployment needs require the extended/deploy edition, we recommend the extended edition.
|
||||
|
||||
## Installation
|
||||
|
||||
Install Hugo from a [prebuilt binary], package manager, or package repository. Please see the installation instructions for your operating system:
|
||||
@@ -162,6 +156,16 @@ If there is sufficient interest, [create a proposal]. Do not submit a pull reque
|
||||
|
||||
For a complete guide to contributing to Hugo, see the [Contribution Guide](CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
For the Hugo source code, see [LICENSE](/LICENSE).
|
||||
|
||||
We also bundle some libraries in binary/WASM form:
|
||||
|
||||
* [libwebp](https://github.com/webmproject/libwebp), [BSD-3-Clause license](https://github.com/webmproject/libwebp?tab=BSD-3-Clause-1-ov-file#readme)
|
||||
* [Katex](https://github.com/KaTeX/KaTeX), [MIT license](https://github.com/KaTeX/KaTeX?tab=MIT-1-ov-file#readme)
|
||||
* [QuickJS](https://github.com/bellard/quickjs?tab=License-1-ov-file#readme), [License](https://github.com/bellard/quickjs?tab=License-1-ov-file#readme)
|
||||
|
||||
## Dependencies
|
||||
|
||||
Hugo stands on the shoulders of great open source libraries. Run `hugo env --logLevel info` to display a list of dependencies.
|
||||
@@ -171,51 +175,55 @@ Hugo stands on the shoulders of great open source libraries. Run `hugo env --log
|
||||
|
||||
```text
|
||||
github.com/BurntSushi/locker="v0.0.0-20171006230638-a6e239ea1c69"
|
||||
github.com/PuerkitoBio/goquery="v1.10.1"
|
||||
github.com/alecthomas/chroma/v2="v2.15.0"
|
||||
github.com/andybalholm/cascadia="v1.3.3"
|
||||
github.com/armon/go-radix="v1.0.1-0.20221118154546-54df44f2176c"
|
||||
github.com/JohannesKaufmann/dom="v0.2.0"
|
||||
github.com/JohannesKaufmann/html-to-markdown/v2="v2.5.0"
|
||||
github.com/alecthomas/chroma/v2="v2.21.1"
|
||||
github.com/aymerick/douceur="v0.2.0"
|
||||
github.com/bep/clocks="v0.5.0"
|
||||
github.com/bep/debounce="v1.2.0"
|
||||
github.com/bep/gitmap="v1.6.0"
|
||||
github.com/bep/gitmap="v1.9.0"
|
||||
github.com/bep/goat="v0.5.0"
|
||||
github.com/bep/godartsass/v2="v2.3.2"
|
||||
github.com/bep/godartsass/v2="v2.5.0"
|
||||
github.com/bep/golibsass="v1.2.0"
|
||||
github.com/bep/gowebp="v0.3.0"
|
||||
github.com/bep/imagemeta="v0.8.4"
|
||||
github.com/bep/lazycache="v0.7.0"
|
||||
github.com/bep/goportabletext="v0.1.0"
|
||||
github.com/bep/helpers="v0.6.0"
|
||||
github.com/bep/imagemeta="v0.12.0"
|
||||
github.com/bep/lazycache="v0.8.0"
|
||||
github.com/bep/logg="v0.4.0"
|
||||
github.com/bep/mclib="v1.20400.20402"
|
||||
github.com/bep/overlayfs="v0.9.2"
|
||||
github.com/bep/simplecobra="v0.5.0"
|
||||
github.com/bep/overlayfs="v0.10.0"
|
||||
github.com/bep/simplecobra="v0.6.1"
|
||||
github.com/bep/textandbinarywriter="v0.0.0-20251212174530-cd9f0732f60f"
|
||||
github.com/bep/tmc="v0.5.1"
|
||||
github.com/bits-and-blooms/bitset="v1.24.4"
|
||||
github.com/cespare/xxhash/v2="v2.3.0"
|
||||
github.com/clbanning/mxj/v2="v2.7.0"
|
||||
github.com/cpuguy83/go-md2man/v2="v2.0.4"
|
||||
github.com/clipperhouse/displaywidth="v0.6.0"
|
||||
github.com/clipperhouse/stringish="v0.1.1"
|
||||
github.com/clipperhouse/uax29/v2="v2.3.0"
|
||||
github.com/cpuguy83/go-md2man/v2="v2.0.6"
|
||||
github.com/disintegration/gift="v1.2.1"
|
||||
github.com/dlclark/regexp2="v1.11.5"
|
||||
github.com/dop251/goja="v0.0.0-20250125213203-5ef83b82af17"
|
||||
github.com/evanw/esbuild="v0.24.2"
|
||||
github.com/evanw/esbuild="v0.27.2"
|
||||
github.com/fatih/color="v1.18.0"
|
||||
github.com/frankban/quicktest="v1.14.6"
|
||||
github.com/fsnotify/fsnotify="v1.8.0"
|
||||
github.com/getkin/kin-openapi="v0.129.0"
|
||||
github.com/ghodss/yaml="v1.0.0"
|
||||
github.com/fsnotify/fsnotify="v1.9.0"
|
||||
github.com/getkin/kin-openapi="v0.133.0"
|
||||
github.com/go-openapi/jsonpointer="v0.21.0"
|
||||
github.com/go-openapi/swag="v0.23.0"
|
||||
github.com/go-sourcemap/sourcemap="v2.1.4+incompatible"
|
||||
github.com/gobuffalo/flect="v1.0.3"
|
||||
github.com/gobwas/glob="v0.2.3"
|
||||
github.com/gohugoio/go-i18n/v2="v2.1.3-0.20230805085216-e63c13218d0e"
|
||||
github.com/gohugoio/hashstructure="v0.5.0"
|
||||
github.com/gohugoio/httpcache="v0.7.0"
|
||||
github.com/gohugoio/hugo-goldmark-extensions/extras="v0.2.0"
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough="v0.3.0"
|
||||
github.com/goccy/go-yaml="v1.19.1"
|
||||
github.com/gohugoio/go-i18n/v2="v2.1.3-0.20251018145728-cfcc22d823c6"
|
||||
github.com/gohugoio/go-radix="v1.2.0"
|
||||
github.com/gohugoio/hashstructure="v0.6.0"
|
||||
github.com/gohugoio/httpcache="v0.8.0"
|
||||
github.com/gohugoio/hugo-goldmark-extensions/extras="v0.5.0"
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough="v0.3.1"
|
||||
github.com/gohugoio/locales="v0.14.0"
|
||||
github.com/gohugoio/localescompressed="v1.0.1"
|
||||
github.com/golang/freetype="v0.0.0-20170609003504-e2365dfdc4a0"
|
||||
github.com/google/go-cmp="v0.6.0"
|
||||
github.com/google/pprof="v0.0.0-20250208200701-d0013a598941"
|
||||
github.com/google/go-cmp="v0.7.0"
|
||||
github.com/gorilla/css="v1.0.1"
|
||||
github.com/gorilla/websocket="v1.5.3"
|
||||
github.com/hairyhenderson/go-codeowners="v0.7.0"
|
||||
github.com/hashicorp/golang-lru/v2="v2.0.7"
|
||||
@@ -224,58 +232,57 @@ github.com/josharian/intern="v1.0.0"
|
||||
github.com/kr/pretty="v0.3.1"
|
||||
github.com/kr/text="v0.2.0"
|
||||
github.com/kyokomi/emoji/v2="v2.2.13"
|
||||
github.com/lucasb-eyer/go-colorful="v1.2.0"
|
||||
github.com/mailru/easyjson="v0.7.7"
|
||||
github.com/makeworld-the-better-one/dither/v2="v2.4.0"
|
||||
github.com/marekm4/color-extractor="v1.2.1"
|
||||
github.com/mattn/go-colorable="v0.1.13"
|
||||
github.com/mattn/go-isatty="v0.0.20"
|
||||
github.com/mattn/go-runewidth="v0.0.9"
|
||||
github.com/mazznoer/csscolorparser="v0.1.5"
|
||||
github.com/mattn/go-runewidth="v0.0.19"
|
||||
github.com/microcosm-cc/bluemonday="v1.0.27"
|
||||
github.com/mitchellh/mapstructure="v1.5.1-0.20231216201459-8508981c8b6c"
|
||||
github.com/mohae/deepcopy="v0.0.0-20170929034955-c48cc78d4826"
|
||||
github.com/muesli/smartcrop="v0.3.0"
|
||||
github.com/niklasfasching/go-org="v1.7.0"
|
||||
github.com/oasdiff/yaml3="v0.0.0-20241210130736-a94c01f36349"
|
||||
github.com/oasdiff/yaml="v0.0.0-20241210131133-6b86fb107d80"
|
||||
github.com/olekukonko/tablewriter="v0.0.5"
|
||||
github.com/niklasfasching/go-org="v1.9.1"
|
||||
github.com/oasdiff/yaml3="v0.0.0-20250309153720-d2182401db90"
|
||||
github.com/oasdiff/yaml="v0.0.0-20250309154309-f31be36b4037"
|
||||
github.com/olekukonko/cat="v0.0.0-20250911104152-50322a0618f6"
|
||||
github.com/olekukonko/errors="v1.1.0"
|
||||
github.com/olekukonko/ll="v0.1.3"
|
||||
github.com/olekukonko/tablewriter="v1.1.2"
|
||||
github.com/pbnjay/memory="v0.0.0-20210728143218-7b4eea64cf58"
|
||||
github.com/pelletier/go-toml/v2="v2.2.3"
|
||||
github.com/pelletier/go-toml/v2="v2.2.4"
|
||||
github.com/perimeterx/marshmallow="v1.1.5"
|
||||
github.com/pkg/browser="v0.0.0-20240102092130-5ac0b6a4141c"
|
||||
github.com/pkg/errors="v0.9.1"
|
||||
github.com/rivo/uniseg="v0.4.7"
|
||||
github.com/rogpeppe/go-internal="v1.13.1"
|
||||
github.com/rogpeppe/go-internal="v1.14.1"
|
||||
github.com/russross/blackfriday/v2="v2.1.0"
|
||||
github.com/sass/libsass="3.6.6"
|
||||
github.com/spf13/afero="v1.11.0"
|
||||
github.com/spf13/cast="v1.7.1"
|
||||
github.com/spf13/cobra="v1.8.1"
|
||||
github.com/sass/dart-sass/compiler="1.97.1"
|
||||
github.com/sass/dart-sass/implementation="1.97.1"
|
||||
github.com/sass/dart-sass/protocol="3.2.0"
|
||||
github.com/spf13/afero="v1.15.0"
|
||||
github.com/spf13/cast="v1.10.0"
|
||||
github.com/spf13/cobra="v1.10.2"
|
||||
github.com/spf13/fsync="v0.10.1"
|
||||
github.com/spf13/pflag="v1.0.6"
|
||||
github.com/tdewolff/minify/v2="v2.20.37"
|
||||
github.com/tdewolff/parse/v2="v2.7.15"
|
||||
github.com/tetratelabs/wazero="v1.8.2"
|
||||
github.com/webmproject/libwebp="v1.3.2"
|
||||
github.com/yuin/goldmark-emoji="v1.0.4"
|
||||
github.com/yuin/goldmark="v1.7.8"
|
||||
github.com/spf13/pflag="v1.0.9"
|
||||
github.com/tdewolff/minify/v2="v2.24.8"
|
||||
github.com/tdewolff/parse/v2="v2.8.5"
|
||||
github.com/tetratelabs/wazero="v1.10.1"
|
||||
github.com/webmproject/libwebp="v1.6.0"
|
||||
github.com/woodsbury/decimal128="v1.3.0"
|
||||
github.com/yuin/goldmark-emoji="v1.0.6"
|
||||
github.com/yuin/goldmark="v1.7.13"
|
||||
go.uber.org/automaxprocs="v1.5.3"
|
||||
golang.org/x/crypto="v0.33.0"
|
||||
golang.org/x/exp="v0.0.0-20250210185358-939b2ce775ac"
|
||||
golang.org/x/image="v0.24.0"
|
||||
golang.org/x/mod="v0.23.0"
|
||||
golang.org/x/net="v0.35.0"
|
||||
golang.org/x/sync="v0.11.0"
|
||||
golang.org/x/sys="v0.30.0"
|
||||
golang.org/x/text="v0.22.0"
|
||||
golang.org/x/tools="v0.30.0"
|
||||
golang.org/x/xerrors="v0.0.0-20240903120638-7835f813f4da"
|
||||
gonum.org/v1/plot="v0.15.0"
|
||||
google.golang.org/protobuf="v1.36.5"
|
||||
gopkg.in/yaml.v2="v2.4.0"
|
||||
go.yaml.in/yaml/v3="v3.0.4"
|
||||
golang.org/x/crypto="v0.46.0"
|
||||
golang.org/x/image="v0.34.0"
|
||||
golang.org/x/mod="v0.31.0"
|
||||
golang.org/x/net="v0.48.0"
|
||||
golang.org/x/sync="v0.19.0"
|
||||
golang.org/x/sys="v0.39.0"
|
||||
golang.org/x/text="v0.32.0"
|
||||
golang.org/x/tools="v0.40.0"
|
||||
google.golang.org/protobuf="v1.36.10"
|
||||
gopkg.in/yaml.v3="v3.0.1"
|
||||
oss.terrastruct.com/d2="v0.6.9"
|
||||
oss.terrastruct.com/util-go="v0.0.0-20241005222610-44c011a04896"
|
||||
rsc.io/qr="v0.2.0"
|
||||
software.sslmate.com/src/go-pkcs12="v0.2.0"
|
||||
```
|
||||
|
||||
Vendored
+2
-2
@@ -69,7 +69,7 @@ func New(opts Options) *Cache {
|
||||
|
||||
infol := opts.Log.InfoCommand("dynacache")
|
||||
|
||||
evictedIdentities := collections.NewStack[KeyIdentity]()
|
||||
evictedIdentities := collections.NewStackThreadSafe[KeyIdentity]()
|
||||
|
||||
onEvict := func(k, v any) {
|
||||
if !opts.Watching {
|
||||
@@ -129,7 +129,7 @@ type Cache struct {
|
||||
partitions map[string]PartitionManager
|
||||
|
||||
onEvict func(k, v any)
|
||||
evictedIdentities *collections.Stack[KeyIdentity]
|
||||
evictedIdentities *collections.StackThreadSafe[KeyIdentity]
|
||||
|
||||
opts Options
|
||||
infol logg.LevelLogger
|
||||
|
||||
Vendored
+55
-11
@@ -16,6 +16,7 @@ package filecache
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -54,7 +55,11 @@ type Cache struct {
|
||||
|
||||
nlocker *lockTracker
|
||||
|
||||
isResourceDir bool
|
||||
baseDir string
|
||||
|
||||
initOnce sync.Once
|
||||
isInited bool
|
||||
initErr error
|
||||
}
|
||||
|
||||
@@ -109,9 +114,14 @@ func (l *lockedFile) Close() error {
|
||||
}
|
||||
|
||||
func (c *Cache) init() error {
|
||||
if c == nil {
|
||||
panic("cache is nil")
|
||||
}
|
||||
c.initOnce.Do(func() {
|
||||
c.isInited = true
|
||||
// Create the base dir if it does not exist.
|
||||
if err := c.Fs.MkdirAll("", 0o777); err != nil && !os.IsExist(err) {
|
||||
err = fmt.Errorf("failled to create base cache directory: %s", err)
|
||||
c.initErr = err
|
||||
}
|
||||
})
|
||||
@@ -286,8 +296,32 @@ func (c *Cache) GetOrCreateBytes(id string, create func() ([]byte, error)) (Item
|
||||
return info, b, nil
|
||||
}
|
||||
|
||||
// SetBytes sets the file content with the given id in the cache.
|
||||
func (c *Cache) SetBytes(id string, data []byte) error {
|
||||
if err := c.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
id = cleanID(id)
|
||||
|
||||
c.nlocker.Lock(id)
|
||||
defer c.nlocker.Unlock(id)
|
||||
|
||||
if c.maxAge == 0 {
|
||||
// No caching.
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.writeReader(id, bytes.NewReader(data))
|
||||
}
|
||||
|
||||
// GetBytes gets the file content with the given id from the cache, nil if none found.
|
||||
func (c *Cache) GetBytes(id string) (ItemInfo, []byte, error) {
|
||||
func (c *Cache) GetBytes(id string) ([]byte, error) {
|
||||
_, b, err := c.GetItemBytes(id)
|
||||
return b, err
|
||||
}
|
||||
|
||||
// GetItemBytes gets the ItemInfo and file content with the given id from the cache, nil if none found.
|
||||
func (c *Cache) GetItemBytes(id string) (ItemInfo, []byte, error) {
|
||||
if err := c.init(); err != nil {
|
||||
return ItemInfo{}, nil, err
|
||||
}
|
||||
@@ -417,6 +451,17 @@ func (c *Cache) GetString(id string) string {
|
||||
// Caches is a named set of caches.
|
||||
type Caches map[string]*Cache
|
||||
|
||||
func (f Caches) SetResourceFs(fs afero.Fs) {
|
||||
for _, c := range f {
|
||||
if c.isResourceDir {
|
||||
if c.isInited {
|
||||
panic("cannot set resource fs after init")
|
||||
}
|
||||
c.Fs = hugofs.NewBasePathFs(fs, c.baseDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get gets a named cache, nil if none found.
|
||||
func (f Caches) Get(name string) *Cache {
|
||||
return f[strings.ToLower(name)]
|
||||
@@ -424,24 +469,19 @@ func (f Caches) Get(name string) *Cache {
|
||||
|
||||
// NewCaches creates a new set of file caches from the given
|
||||
// configuration.
|
||||
func NewCaches(p *helpers.PathSpec) (Caches, error) {
|
||||
dcfg := p.Cfg.GetConfigSection("caches").(Configs)
|
||||
fs := p.Fs.Source
|
||||
func NewCaches(dcfg Configs, sourceFs afero.Fs) (Caches, error) {
|
||||
// dcfg := p.Cfg.GetConfigSection("caches").(Configs)
|
||||
fs := sourceFs
|
||||
|
||||
m := make(Caches)
|
||||
for k, v := range dcfg {
|
||||
var cfs afero.Fs
|
||||
|
||||
if v.IsResourceDir {
|
||||
cfs = p.BaseFs.ResourcesCache
|
||||
cfs = nil // Set later. TODO(bep) this needs to be cleanded up.
|
||||
} else {
|
||||
cfs = fs
|
||||
}
|
||||
|
||||
if cfs == nil {
|
||||
panic("nil fs")
|
||||
}
|
||||
|
||||
baseDir := v.DirCompiled
|
||||
|
||||
bfs := hugofs.NewBasePathFs(cfs, baseDir)
|
||||
@@ -451,7 +491,11 @@ func NewCaches(p *helpers.PathSpec) (Caches, error) {
|
||||
pruneAllRootDir = "pkg"
|
||||
}
|
||||
|
||||
m[k] = NewCache(bfs, v.MaxAge, pruneAllRootDir)
|
||||
c := NewCache(bfs, v.MaxAge, pruneAllRootDir)
|
||||
c.isResourceDir = v.IsResourceDir
|
||||
c.baseDir = baseDir
|
||||
|
||||
m[k] = c
|
||||
}
|
||||
|
||||
return m, nil
|
||||
|
||||
Vendored
+42
-9
@@ -15,6 +15,7 @@
|
||||
package filecache
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
@@ -22,7 +23,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/hmaps"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
@@ -40,13 +41,14 @@ var defaultCacheConfig = FileCacheConfig{
|
||||
}
|
||||
|
||||
const (
|
||||
CacheKeyGetJSON = "getjson"
|
||||
CacheKeyGetCSV = "getcsv"
|
||||
CacheKeyImages = "images"
|
||||
CacheKeyAssets = "assets"
|
||||
CacheKeyModules = "modules"
|
||||
CacheKeyGetResource = "getresource"
|
||||
CacheKeyMisc = "misc"
|
||||
CacheKeyGetJSON = "getjson"
|
||||
CacheKeyGetCSV = "getcsv"
|
||||
CacheKeyImages = "images"
|
||||
CacheKeyAssets = "assets"
|
||||
CacheKeyModules = "modules"
|
||||
CacheKeyModuleQueries = "modulequeries"
|
||||
CacheKeyGetResource = "getresource"
|
||||
CacheKeyMisc = "misc"
|
||||
)
|
||||
|
||||
type Configs map[string]FileCacheConfig
|
||||
@@ -68,6 +70,10 @@ var defaultCacheConfigs = Configs{
|
||||
MaxAge: -1,
|
||||
Dir: ":cacheDir/modules",
|
||||
},
|
||||
CacheKeyModuleQueries: {
|
||||
MaxAge: 24 * time.Hour,
|
||||
Dir: ":cacheDir/modules",
|
||||
},
|
||||
CacheKeyGetJSON: defaultCacheConfig,
|
||||
CacheKeyGetCSV: defaultCacheConfig,
|
||||
CacheKeyImages: {
|
||||
@@ -107,6 +113,23 @@ type FileCacheConfig struct {
|
||||
IsResourceDir bool `json:"-"`
|
||||
}
|
||||
|
||||
// MarshalJSON marshals FileCacheConfig to JSON with MaxAge as a human-readable string.
|
||||
func (c FileCacheConfig) MarshalJSON() ([]byte, error) {
|
||||
var maxAge any
|
||||
if c.MaxAge == -1 {
|
||||
maxAge = -1
|
||||
} else {
|
||||
maxAge = strings.TrimSuffix(c.MaxAge.String(), "0m0s")
|
||||
}
|
||||
return json.Marshal(&struct {
|
||||
MaxAge any `json:"maxAge"`
|
||||
Dir string `json:"dir"`
|
||||
}{
|
||||
MaxAge: maxAge,
|
||||
Dir: c.Dir,
|
||||
})
|
||||
}
|
||||
|
||||
// GetJSONCache gets the file cache for getJSON.
|
||||
func (f Caches) GetJSONCache() *Cache {
|
||||
return f[CacheKeyGetJSON]
|
||||
@@ -127,6 +150,16 @@ func (f Caches) ModulesCache() *Cache {
|
||||
return f[CacheKeyModules]
|
||||
}
|
||||
|
||||
// ModuleQueriesCache gets the file cache for Hugo Module version queries.
|
||||
// Returns nil if not found.
|
||||
func (f Caches) ModuleQueriesCache() *Cache {
|
||||
c, ok := f[CacheKeyModuleQueries]
|
||||
if !ok {
|
||||
panic("module queries cache not set")
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// AssetsCache gets the file cache for assets (processed resources, SCSS etc.).
|
||||
func (f Caches) AssetsCache() *Cache {
|
||||
return f[CacheKeyAssets]
|
||||
@@ -154,7 +187,7 @@ func DecodeConfig(fs afero.Fs, bcfg config.BaseConfig, m map[string]any) (Config
|
||||
_, isOsFs := fs.(*afero.OsFs)
|
||||
|
||||
for k, v := range m {
|
||||
if _, ok := v.(maps.Params); !ok {
|
||||
if _, ok := v.(hmaps.Params); !ok {
|
||||
continue
|
||||
}
|
||||
cc := defaultCacheConfig
|
||||
|
||||
+30
-3
@@ -14,6 +14,7 @@
|
||||
package filecache_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
@@ -59,7 +60,7 @@ dir = "/path/to/c4"
|
||||
c.Assert(err, qt.IsNil)
|
||||
fs := afero.NewMemMapFs()
|
||||
decoded := testconfig.GetTestConfigs(fs, cfg).Base.Caches
|
||||
c.Assert(len(decoded), qt.Equals, 7)
|
||||
c.Assert(len(decoded), qt.Equals, 8)
|
||||
|
||||
c2 := decoded["getcsv"]
|
||||
c.Assert(c2.MaxAge.String(), qt.Equals, "11h0m0s")
|
||||
@@ -106,7 +107,7 @@ dir = "/path/to/c4"
|
||||
c.Assert(err, qt.IsNil)
|
||||
fs := afero.NewMemMapFs()
|
||||
decoded := testconfig.GetTestConfigs(fs, cfg).Base.Caches
|
||||
c.Assert(len(decoded), qt.Equals, 7)
|
||||
c.Assert(len(decoded), qt.Equals, 8)
|
||||
|
||||
for _, v := range decoded {
|
||||
c.Assert(v.MaxAge, qt.Equals, time.Duration(0))
|
||||
@@ -129,7 +130,7 @@ func TestDecodeConfigDefault(t *testing.T) {
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
decoded := testconfig.GetTestConfigs(fs, cfg).Base.Caches
|
||||
c.Assert(len(decoded), qt.Equals, 7)
|
||||
c.Assert(len(decoded), qt.Equals, 8)
|
||||
|
||||
imgConfig := decoded[filecache.CacheKeyImages]
|
||||
jsonConfig := decoded[filecache.CacheKeyGetJSON]
|
||||
@@ -144,3 +145,29 @@ func TestDecodeConfigDefault(t *testing.T) {
|
||||
c.Assert(imgConfig.IsResourceDir, qt.Equals, true)
|
||||
c.Assert(jsonConfig.IsResourceDir, qt.Equals, false)
|
||||
}
|
||||
|
||||
func TestFileCacheConfigMarshalJSON(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
cfg := config.New()
|
||||
cfg.Set("cacheDir", "/cache")
|
||||
cfg.Set("workingDir", "/my/project")
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
decoded := testconfig.GetTestConfigs(fs, cfg).Base.Caches
|
||||
|
||||
moduleQueriesConfig := decoded[filecache.CacheKeyModuleQueries]
|
||||
c.Assert(moduleQueriesConfig.MaxAge, qt.Equals, 24*time.Hour)
|
||||
|
||||
b, err := json.Marshal(moduleQueriesConfig)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
c.Assert(string(b), qt.Contains, `"maxAge":"24h"`)
|
||||
c.Assert(string(b), qt.Not(qt.Contains), "86400000000000")
|
||||
c.Assert(string(b), qt.Not(qt.Contains), "8.64e")
|
||||
|
||||
moduleQueriesConfig.MaxAge = -1
|
||||
b, err = json.Marshal(moduleQueriesConfig)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(string(b), qt.Contains, `"maxAge":-1`)
|
||||
}
|
||||
|
||||
+7
-3
@@ -55,9 +55,12 @@ dir = ":resourceDir/_gen"
|
||||
|
||||
for _, name := range []string{filecache.CacheKeyGetCSV, filecache.CacheKeyGetJSON, filecache.CacheKeyAssets, filecache.CacheKeyImages} {
|
||||
msg := qt.Commentf("cache: %s", name)
|
||||
p := newPathsSpec(t, afero.NewMemMapFs(), configStr)
|
||||
caches, err := filecache.NewCaches(p)
|
||||
fs := afero.NewMemMapFs()
|
||||
p := newPathsSpec(t, fs, configStr)
|
||||
fileCachConfig := p.Cfg.GetConfigSection("caches").(filecache.Configs)
|
||||
caches, err := filecache.NewCaches(fileCachConfig, fs)
|
||||
c.Assert(err, qt.IsNil)
|
||||
caches.SetResourceFs(fs)
|
||||
cache := caches[name]
|
||||
for i := range 10 {
|
||||
id := fmt.Sprintf("i%d", i)
|
||||
@@ -84,8 +87,9 @@ dir = ":resourceDir/_gen"
|
||||
}
|
||||
}
|
||||
|
||||
caches, err = filecache.NewCaches(p)
|
||||
caches, err = filecache.NewCaches(fileCachConfig, fs)
|
||||
c.Assert(err, qt.IsNil)
|
||||
caches.SetResourceFs(fs)
|
||||
cache = caches[name]
|
||||
// Touch one and then prune.
|
||||
cache.GetOrCreateBytes("i5", func() ([]byte, error) {
|
||||
|
||||
Vendored
+10
-4
@@ -18,6 +18,8 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/htesting"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -78,9 +80,11 @@ dir = ":cacheDir/c"
|
||||
configStr = strings.Replace(configStr, "\\", winPathSep, -1)
|
||||
|
||||
p := newPathsSpec(t, osfs, configStr)
|
||||
fileCachConfig := p.Cfg.GetConfigSection("caches").(filecache.Configs)
|
||||
|
||||
caches, err := filecache.NewCaches(p)
|
||||
caches, err := filecache.NewCaches(fileCachConfig, p.Fs.Source)
|
||||
c.Assert(err, qt.IsNil)
|
||||
caches.SetResourceFs(p.SourceFs)
|
||||
|
||||
cache := caches.Get("GetJSON")
|
||||
c.Assert(cache, qt.Not(qt.IsNil))
|
||||
@@ -149,7 +153,7 @@ dir = ":cacheDir/c"
|
||||
r.Close()
|
||||
c.Assert(string(b), qt.Equals, "Hugo is great!")
|
||||
|
||||
info, b, err = caches.ImageCache().GetBytes("mykey")
|
||||
info, b, err = caches.ImageCache().GetItemBytes("mykey")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(info.Name, qt.Equals, "mykey")
|
||||
c.Assert(string(b), qt.Equals, "Hugo is great!")
|
||||
@@ -158,6 +162,7 @@ dir = ":cacheDir/c"
|
||||
}
|
||||
|
||||
func TestFileCacheConcurrent(t *testing.T) {
|
||||
htesting.SkipSlowTestUnlessCI(t)
|
||||
t.Parallel()
|
||||
|
||||
c := qt.New(t)
|
||||
@@ -179,9 +184,10 @@ dir = "/cache/c"
|
||||
`
|
||||
|
||||
p := newPathsSpec(t, afero.NewMemMapFs(), configStr)
|
||||
|
||||
caches, err := filecache.NewCaches(p)
|
||||
fileCachConfig := p.Cfg.GetConfigSection("caches").(filecache.Configs)
|
||||
caches, err := filecache.NewCaches(fileCachConfig, p.Fs.Source)
|
||||
c.Assert(err, qt.IsNil)
|
||||
caches.SetResourceFs(p.Fs.Source)
|
||||
|
||||
const cacheName = "getjson"
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Default to all packages if none specified
|
||||
PACKAGES="${1:-./...}"
|
||||
|
||||
echo "==> Checking packages: $PACKAGES"
|
||||
|
||||
# Timing arrays
|
||||
declare -a STEP_NAMES
|
||||
declare -a STEP_TIMES
|
||||
|
||||
time_step() {
|
||||
local name="$1"
|
||||
shift
|
||||
local start=$(date +%s.%N)
|
||||
"$@"
|
||||
local end=$(date +%s.%N)
|
||||
local elapsed=$(echo "$end - $start" | bc)
|
||||
STEP_NAMES+=("$name")
|
||||
STEP_TIMES+=("$elapsed")
|
||||
}
|
||||
|
||||
# Check gofmt
|
||||
run_gofmt() {
|
||||
echo "==> Running gofmt..."
|
||||
# Convert package pattern to path (e.g., ./hugolib/... -> ./hugolib)
|
||||
local path="${PACKAGES%/...}"
|
||||
GOFMT_OUTPUT=$(gofmt -l "$path" 2>&1) || true
|
||||
if [ -n "$GOFMT_OUTPUT" ]; then
|
||||
echo "gofmt found issues in:"
|
||||
echo "$GOFMT_OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
echo " OK"
|
||||
}
|
||||
|
||||
# Run staticcheck
|
||||
run_staticcheck() {
|
||||
# Check if staticcheck is installed, install if not
|
||||
if ! command -v staticcheck &> /dev/null; then
|
||||
echo "==> Installing staticcheck..."
|
||||
go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||
fi
|
||||
echo "==> Running staticcheck..."
|
||||
staticcheck $PACKAGES
|
||||
echo " OK"
|
||||
}
|
||||
|
||||
# Run tests
|
||||
run_tests() {
|
||||
echo "==> Running tests..."
|
||||
local output
|
||||
if ! output=$(go test $PACKAGES 2>&1); then
|
||||
echo "$output"
|
||||
exit 1
|
||||
fi
|
||||
echo " OK"
|
||||
}
|
||||
|
||||
# Run all steps with timing
|
||||
TOTAL_START=$(date +%s.%N)
|
||||
|
||||
time_step "gofmt" run_gofmt
|
||||
time_step "staticcheck" run_staticcheck
|
||||
time_step "tests" run_tests
|
||||
|
||||
TOTAL_END=$(date +%s.%N)
|
||||
TOTAL_ELAPSED=$(echo "$TOTAL_END - $TOTAL_START" | bc)
|
||||
|
||||
# Print timing summary
|
||||
echo ""
|
||||
echo "==> All checks passed!"
|
||||
echo ""
|
||||
echo "Timing summary:"
|
||||
echo "---------------"
|
||||
for i in "${!STEP_NAMES[@]}"; do
|
||||
printf " %-15s %6.2fs\n" "${STEP_NAMES[$i]}" "${STEP_TIMES[$i]}"
|
||||
done
|
||||
echo "---------------"
|
||||
printf " %-15s %6.2fs\n" "Total" "$TOTAL_ELAPSED"
|
||||
@@ -331,6 +331,9 @@ func (r *rootCommand) ConfigFromProvider(key configKey, cfg config.Provider) (*c
|
||||
}
|
||||
|
||||
func (r *rootCommand) HugFromConfig(conf *commonConfig) (*hugolib.HugoSites, error) {
|
||||
if conf == nil {
|
||||
return nil, fmt.Errorf("conf must be set")
|
||||
}
|
||||
k := configKey{counter: r.configVersionID.Load()}
|
||||
h, _, err := r.hugoSites.GetOrCreate(k, func(key configKey) (*hugolib.HugoSites, error) {
|
||||
depsCfg := r.newDepsConfig(conf)
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/bep/simplecobra"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/hmaps"
|
||||
"github.com/gohugoio/hugo/config/allconfig"
|
||||
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
|
||||
"github.com/gohugoio/hugo/modules"
|
||||
@@ -95,7 +95,7 @@ func (c *configCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, arg
|
||||
if err := json.Unmarshal(buf.Bytes(), &m); err != nil {
|
||||
return err
|
||||
}
|
||||
maps.ConvertFloat64WithNoDecimalsToInt(m)
|
||||
hmaps.ConvertFloat64WithNoDecimalsToInt(m)
|
||||
switch format {
|
||||
case "yaml":
|
||||
return parser.InterfaceToConfig(m, metadecoders.YAML, os.Stdout)
|
||||
|
||||
+29
-5
@@ -31,11 +31,11 @@ import (
|
||||
"github.com/bep/simplecobra"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/hmaps"
|
||||
"github.com/gohugoio/hugo/common/hstrings"
|
||||
"github.com/gohugoio/hugo/common/htime"
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/common/terminal"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
@@ -56,8 +56,9 @@ import (
|
||||
type hugoBuilder struct {
|
||||
r *rootCommand
|
||||
|
||||
confmu sync.Mutex
|
||||
conf *commonConfig
|
||||
confmu sync.Mutex
|
||||
confOld *commonConfig
|
||||
conf *commonConfig
|
||||
|
||||
// May be nil.
|
||||
s *serverCommand
|
||||
@@ -94,6 +95,27 @@ func (c *hugoBuilder) withConf(fn func(conf *commonConfig)) {
|
||||
fn(c.conf)
|
||||
}
|
||||
|
||||
func (c *hugoBuilder) withConfOrOldConf(fn func(conf *commonConfig)) {
|
||||
c.confmu.Lock()
|
||||
defer c.confmu.Unlock()
|
||||
if c.conf != nil {
|
||||
fn(c.conf)
|
||||
} else if c.confOld != nil {
|
||||
fn(c.confOld)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *hugoBuilder) withConfOrOldConfE(fn func(conf *commonConfig) error) error {
|
||||
c.confmu.Lock()
|
||||
defer c.confmu.Unlock()
|
||||
if c.conf != nil {
|
||||
return fn(c.conf)
|
||||
} else if c.confOld != nil {
|
||||
return fn(c.confOld)
|
||||
}
|
||||
return errConfigNotSet
|
||||
}
|
||||
|
||||
type hugoBuilderErrState struct {
|
||||
mu sync.Mutex
|
||||
paused bool
|
||||
@@ -1077,7 +1099,7 @@ func (c *hugoBuilder) loadConfig(cd *simplecobra.Commandeer, running bool) error
|
||||
}
|
||||
cfg.Set("environment", c.r.environment)
|
||||
|
||||
cfg.Set("internal", maps.Params{
|
||||
cfg.Set("internal", hmaps.Params{
|
||||
"running": running,
|
||||
"watch": watch,
|
||||
"verbose": c.r.isVerbose(),
|
||||
@@ -1095,6 +1117,7 @@ func (c *hugoBuilder) loadConfig(cd *simplecobra.Commandeer, running bool) error
|
||||
}
|
||||
|
||||
c.conf = conf
|
||||
c.confOld = conf
|
||||
if c.onConfigLoaded != nil {
|
||||
if err := c.onConfigLoaded(false); err != nil {
|
||||
return err
|
||||
@@ -1158,8 +1181,9 @@ func (c *hugoBuilder) reloadConfig() error {
|
||||
c.r.resetLogs()
|
||||
c.r.configVersionID.Add(1)
|
||||
|
||||
if err := c.withConfE(func(conf *commonConfig) error {
|
||||
if err := c.withConfOrOldConfE(func(conf *commonConfig) error {
|
||||
oldConf := conf
|
||||
c.conf = nil
|
||||
newConf, err := c.r.ConfigFromConfig(configKey{counter: c.r.configVersionID.Load()}, conf)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+3
-3
@@ -29,9 +29,9 @@ import (
|
||||
"unicode"
|
||||
|
||||
"github.com/bep/simplecobra"
|
||||
"github.com/gohugoio/hugo/common/hmaps"
|
||||
"github.com/gohugoio/hugo/common/htime"
|
||||
"github.com/gohugoio/hugo/common/hugio"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/parser"
|
||||
@@ -190,7 +190,7 @@ func (c *importCommand) createSiteFromJekyll(jekyllRoot, targetDir string, jekyl
|
||||
}
|
||||
|
||||
func (c *importCommand) convertJekyllContent(m any, content string) (string, error) {
|
||||
metadata, _ := maps.ToStringMapE(m)
|
||||
metadata, _ := hmaps.ToStringMapE(m)
|
||||
|
||||
lines := strings.Split(content, "\n")
|
||||
var resultLines []string
|
||||
@@ -246,7 +246,7 @@ func (c *importCommand) convertJekyllContent(m any, content string) (string, err
|
||||
}
|
||||
|
||||
func (c *importCommand) convertJekyllMetaData(m any, postName string, postDate time.Time, draft bool) (any, error) {
|
||||
metadata, err := maps.ToStringMapE(m)
|
||||
metadata, err := hmaps.ToStringMapE(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+1
-3
@@ -28,14 +28,13 @@ func newReleaseCommand() simplecobra.Commander {
|
||||
step int
|
||||
skipPush bool
|
||||
try bool
|
||||
version string
|
||||
)
|
||||
|
||||
return &simpleCommand{
|
||||
name: "release",
|
||||
short: "Release a new version of Hugo",
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
rel, err := releaser.New(skipPush, try, step, version)
|
||||
rel, err := releaser.New(skipPush, try, step)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -48,7 +47,6 @@ func newReleaseCommand() simplecobra.Commander {
|
||||
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.PersistentFlags().StringVarP(&version, "version", "", "", "version to release (derived from branch name if not set)")
|
||||
_ = cmd.RegisterFlagCompletionFunc("step", cobra.FixedCompletions([]string{"1", "2"}, cobra.ShellCompDirectiveNoFileComp))
|
||||
},
|
||||
}
|
||||
|
||||
+1
-1
@@ -282,7 +282,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
|
||||
}
|
||||
|
||||
port = 1313
|
||||
f.c.withConf(func(conf *commonConfig) {
|
||||
f.c.withConfOrOldConf(func(conf *commonConfig) {
|
||||
if lrport := conf.configs.GetFirstLanguageConfig().BaseURLLiveReload().Port(); lrport != 0 {
|
||||
port = lrport
|
||||
}
|
||||
|
||||
+55
-11
@@ -21,24 +21,24 @@ import (
|
||||
"github.com/gohugoio/hugo/common/hiter"
|
||||
)
|
||||
|
||||
// Stack is a simple LIFO stack that is safe for concurrent use.
|
||||
type Stack[T any] struct {
|
||||
// StackThreadSafe is a simple LIFO stack that is safe for concurrent use.
|
||||
type StackThreadSafe[T any] struct {
|
||||
items []T
|
||||
zero T
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewStack[T any]() *Stack[T] {
|
||||
return &Stack[T]{}
|
||||
func NewStackThreadSafe[T any]() *StackThreadSafe[T] {
|
||||
return &StackThreadSafe[T]{}
|
||||
}
|
||||
|
||||
func (s *Stack[T]) Push(item T) {
|
||||
func (s *StackThreadSafe[T]) Push(item T) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.items = append(s.items, item)
|
||||
}
|
||||
|
||||
func (s *Stack[T]) Pop() (T, bool) {
|
||||
func (s *StackThreadSafe[T]) Pop() (T, bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if len(s.items) == 0 {
|
||||
@@ -49,7 +49,7 @@ func (s *Stack[T]) Pop() (T, bool) {
|
||||
return item, true
|
||||
}
|
||||
|
||||
func (s *Stack[T]) Peek() (T, bool) {
|
||||
func (s *StackThreadSafe[T]) Peek() (T, bool) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
if len(s.items) == 0 {
|
||||
@@ -58,18 +58,18 @@ func (s *Stack[T]) Peek() (T, bool) {
|
||||
return s.items[len(s.items)-1], true
|
||||
}
|
||||
|
||||
func (s *Stack[T]) Len() int {
|
||||
func (s *StackThreadSafe[T]) Len() int {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return len(s.items)
|
||||
}
|
||||
|
||||
// All returns all items in the stack, from bottom to top.
|
||||
func (s *Stack[T]) All() iter.Seq2[int, T] {
|
||||
func (s *StackThreadSafe[T]) All() iter.Seq2[int, T] {
|
||||
return hiter.Lock2(slices.All(s.items), s.mu.RLock, s.mu.RUnlock)
|
||||
}
|
||||
|
||||
func (s *Stack[T]) Drain() []T {
|
||||
func (s *StackThreadSafe[T]) Drain() []T {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
items := s.items
|
||||
@@ -77,7 +77,7 @@ func (s *Stack[T]) Drain() []T {
|
||||
return items
|
||||
}
|
||||
|
||||
func (s *Stack[T]) DrainMatching(predicate func(T) bool) []T {
|
||||
func (s *StackThreadSafe[T]) DrainMatching(predicate func(T) bool) []T {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
var items []T
|
||||
@@ -89,3 +89,47 @@ func (s *Stack[T]) DrainMatching(predicate func(T) bool) []T {
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
// Stack is a simple LIFO stack that is not safe for concurrent use.
|
||||
type Stack[T any] struct {
|
||||
items []T
|
||||
zero T
|
||||
}
|
||||
|
||||
func NewStack[T any]() *Stack[T] {
|
||||
return &Stack[T]{}
|
||||
}
|
||||
|
||||
func (s *Stack[T]) Push(item T) {
|
||||
s.items = append(s.items, item)
|
||||
}
|
||||
|
||||
func (s *Stack[T]) Pop() (T, bool) {
|
||||
if len(s.items) == 0 {
|
||||
return s.zero, false
|
||||
}
|
||||
item := s.items[len(s.items)-1]
|
||||
s.items = s.items[:len(s.items)-1]
|
||||
return item, true
|
||||
}
|
||||
|
||||
func (s *Stack[T]) Peek() (T, bool) {
|
||||
if len(s.items) == 0 {
|
||||
return s.zero, false
|
||||
}
|
||||
return s.items[len(s.items)-1], true
|
||||
}
|
||||
|
||||
func (s *Stack[T]) Len() int {
|
||||
return len(s.items)
|
||||
}
|
||||
|
||||
func (s *Stack[T]) All() iter.Seq2[int, T] {
|
||||
return slices.All(s.items)
|
||||
}
|
||||
|
||||
func (s *Stack[T]) Drain() []T {
|
||||
items := s.items
|
||||
s.items = nil
|
||||
return items
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ func TestNewStack(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
s := NewStack[int]()
|
||||
s := NewStackThreadSafe[int]()
|
||||
|
||||
c.Assert(s, qt.IsNotNil)
|
||||
}
|
||||
@@ -19,7 +19,7 @@ func TestStackBasic(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
s := NewStack[int]()
|
||||
s := NewStackThreadSafe[int]()
|
||||
|
||||
c.Assert(s.Len(), qt.Equals, 0)
|
||||
|
||||
@@ -50,7 +50,7 @@ func TestStackDrain(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
s := NewStack[string]()
|
||||
s := NewStackThreadSafe[string]()
|
||||
s.Push("a")
|
||||
s.Push("b")
|
||||
|
||||
@@ -64,7 +64,7 @@ func TestStackDrainMatching(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
s := NewStack[int]()
|
||||
s := NewStackThreadSafe[int]()
|
||||
s.Push(1)
|
||||
s.Push(2)
|
||||
s.Push(3)
|
||||
|
||||
@@ -87,11 +87,13 @@ func XXHashFromString(s string) (uint64, error) {
|
||||
return h.Sum64(), nil
|
||||
}
|
||||
|
||||
// XxHashFromStringHexEncoded calculates the xxHash for the given string
|
||||
// XxHashFromStringHexEncoded calculates the xxHash for the given strings
|
||||
// and returns the hash as a hex encoded string.
|
||||
func XxHashFromStringHexEncoded(f string) string {
|
||||
func XxHashFromStringHexEncoded(s ...string) string {
|
||||
h := xxhash.New()
|
||||
h.WriteString(f)
|
||||
for _, f := range s {
|
||||
h.WriteString(f)
|
||||
}
|
||||
hash := h.Sum(nil)
|
||||
return hex.EncodeToString(hash)
|
||||
}
|
||||
|
||||
@@ -288,6 +288,50 @@ func Unwrap(err error) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// UnwrapFileErrors returns all FileError contained in err.
|
||||
func UnwrapFileErrors(err error) []FileError {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
errs := Errors(err)
|
||||
var fileErrors []FileError
|
||||
for _, e := range errs {
|
||||
if v, ok := e.(FileError); ok {
|
||||
fileErrors = append(fileErrors, v)
|
||||
}
|
||||
fileErrors = append(fileErrors, UnwrapFileErrors(errors.Unwrap(e))...)
|
||||
}
|
||||
return fileErrors
|
||||
}
|
||||
|
||||
// UnwrapFileErrorsWithErrorContext tries to unwrap all FileError in err that has an ErrorContext.
|
||||
func UnwrapFileErrorsWithErrorContext(err error) []FileError {
|
||||
errs := UnwrapFileErrors(err)
|
||||
var n int
|
||||
for _, e := range errs {
|
||||
if e.ErrorContext() != nil {
|
||||
errs[n] = e
|
||||
n++
|
||||
}
|
||||
}
|
||||
return errs[:n]
|
||||
}
|
||||
|
||||
// Errors returns the list of errors contained in err.
|
||||
func Errors(err error) []error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
type unwrapper interface {
|
||||
Unwrap() []error
|
||||
}
|
||||
if u, ok := err.(unwrapper); ok {
|
||||
return u.Unwrap()
|
||||
}
|
||||
return []error{err}
|
||||
}
|
||||
|
||||
func extractFileTypePos(err error) (string, text.Position) {
|
||||
err = Unwrap(err)
|
||||
|
||||
@@ -350,30 +394,6 @@ func UnwrapFileError(err error) FileError {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnwrapFileErrors tries to unwrap all FileError.
|
||||
func UnwrapFileErrors(err error) []FileError {
|
||||
var errs []FileError
|
||||
for err != nil {
|
||||
if v, ok := err.(FileError); ok {
|
||||
errs = append(errs, v)
|
||||
}
|
||||
err = errors.Unwrap(err)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// UnwrapFileErrorsWithErrorContext tries to unwrap all FileError in err that has an ErrorContext.
|
||||
func UnwrapFileErrorsWithErrorContext(err error) []FileError {
|
||||
var errs []FileError
|
||||
for err != nil {
|
||||
if v, ok := err.(FileError); ok && v.ErrorContext() != nil {
|
||||
errs = append(errs, v)
|
||||
}
|
||||
err = errors.Unwrap(err)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func extractOffsetAndType(e error) (int, string) {
|
||||
switch v := e.(type) {
|
||||
case *json.UnmarshalTypeError:
|
||||
|
||||
@@ -27,8 +27,8 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/bep/logg"
|
||||
"github.com/gohugoio/hugo/common/hmaps"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/config/security"
|
||||
)
|
||||
@@ -102,7 +102,7 @@ func New(cfg security.Config, workingDir string, log loggers.Logger) *Exec {
|
||||
workingDir: workingDir,
|
||||
infol: log.InfoCommand("exec"),
|
||||
baseEnviron: baseEnviron,
|
||||
newNPXRunnerCache: maps.NewCache[string, func(arg ...any) (Runner, error)](),
|
||||
newNPXRunnerCache: hmaps.NewCache[string, func(arg ...any) (Runner, error)](),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ type Exec struct {
|
||||
// os.Environ filtered by the Exec.OsEnviron whitelist filter.
|
||||
baseEnviron []string
|
||||
|
||||
newNPXRunnerCache *maps.Cache[string, func(arg ...any) (Runner, error)]
|
||||
newNPXRunnerCache *hmaps.Cache[string, func(arg ...any) (Runner, error)]
|
||||
npxInit sync.Once
|
||||
npxAvailable bool
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2025 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package himage provides some high level image types and interfaces.
|
||||
package himage
|
||||
|
||||
import "image"
|
||||
|
||||
// AnimatedImage represents an animated image.
|
||||
// This is currently supported for GIF and WebP images.
|
||||
type AnimatedImage interface {
|
||||
image.Image // The first frame.
|
||||
GetRaw() any // *gif.GIF or *WEBP.
|
||||
GetLoopCount() int // Number of times to loop the animation. 0 means infinite.
|
||||
ImageFrames
|
||||
}
|
||||
|
||||
// ImageFrames provides access to the frames of an animated image.
|
||||
type ImageFrames interface {
|
||||
GetFrames() []image.Image
|
||||
|
||||
// Frame durations in milliseconds.
|
||||
// Note that Gif frame durations are in 100ths of a second,
|
||||
// so they need to be multiplied by 10 to get milliseconds and vice versa.
|
||||
GetFrameDurations() []int
|
||||
|
||||
SetFrames(frames []image.Image)
|
||||
SetWidthHeight(width, height int)
|
||||
}
|
||||
|
||||
// ImageConfigProvider provides access to the image.Config of an image.
|
||||
type ImageConfigProvider interface {
|
||||
GetImageConfig() image.Config
|
||||
}
|
||||
|
||||
// FrameDurationsToGifDelays converts frame durations in milliseconds to
|
||||
// GIF delays in 100ths of a second.
|
||||
func FrameDurationsToGifDelays(frameDurations []int) []int {
|
||||
delays := make([]int, len(frameDurations))
|
||||
for i, fd := range frameDurations {
|
||||
delays[i] = fd / 10
|
||||
if delays[i] == 0 && fd > 0 {
|
||||
delays[i] = 1
|
||||
}
|
||||
}
|
||||
return delays
|
||||
}
|
||||
|
||||
// GifDelaysToFrameDurations converts GIF delays in 100ths of a second to
|
||||
// frame durations in milliseconds.
|
||||
func GifDelaysToFrameDurations(delays []int) []int {
|
||||
frameDurations := make([]int, len(delays))
|
||||
for i, d := range delays {
|
||||
frameDurations[i] = d * 10
|
||||
}
|
||||
return frameDurations
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2026 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package maps
|
||||
package hmaps
|
||||
|
||||
import (
|
||||
"sync"
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2025 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2026 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package maps
|
||||
package hmaps
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2025 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2026 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package maps
|
||||
package hmaps
|
||||
|
||||
import (
|
||||
"iter"
|
||||
@@ -73,11 +73,23 @@ func (m *Map[K, T]) Set(key K, value T) {
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// WithWriteLock executes the given function with a write lock on the map.
|
||||
func (m *Map[K, T]) WithWriteLock(f func(m map[K]T)) {
|
||||
// Delete deletes the given key from the map.
|
||||
// It returns true if the key was found and deleted, false otherwise.
|
||||
func (m *Map[K, T]) Delete(key K) bool {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.m)
|
||||
if _, found := m.m[key]; found {
|
||||
delete(m.m, key)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// WithWriteLock executes the given function with a write lock on the map.
|
||||
func (m *Map[K, T]) WithWriteLock(f func(m map[K]T) error) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
return f(m.m)
|
||||
}
|
||||
|
||||
// SetIfAbsent sets the given key to the given value if the key does not already exist in the map.
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2025 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2026 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package maps
|
||||
package hmaps
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -62,8 +62,9 @@ func TestMap(t *testing.T) {
|
||||
c.Assert(found, qt.Equals, true)
|
||||
c.Assert(v, qt.Equals, 300)
|
||||
|
||||
m.WithWriteLock(func(m map[string]int) {
|
||||
m.WithWriteLock(func(m map[string]int) error {
|
||||
m["f"] = 500
|
||||
return nil
|
||||
})
|
||||
v, found = m.Lookup("f")
|
||||
c.Assert(found, qt.Equals, true)
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2026 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package maps
|
||||
package hmaps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2026 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package maps
|
||||
package hmaps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2026 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package maps
|
||||
package hmaps
|
||||
|
||||
import (
|
||||
"slices"
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2026 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package maps
|
||||
package hmaps
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2025 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2026 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package maps
|
||||
package hmaps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2025 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2026 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package maps
|
||||
package hmaps
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2026 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package maps
|
||||
package hmaps
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2026 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package maps
|
||||
package hmaps
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hmaps"
|
||||
"github.com/gohugoio/hugo/common/htime"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
)
|
||||
|
||||
@@ -355,7 +355,7 @@ func IsNil(v reflect.Value) bool {
|
||||
|
||||
var contextInterface = reflect.TypeOf((*context.Context)(nil)).Elem()
|
||||
|
||||
var isContextCache = maps.NewCache[reflect.Type, bool]()
|
||||
var isContextCache = hmaps.NewCache[reflect.Type, bool]()
|
||||
|
||||
type k string
|
||||
|
||||
|
||||
@@ -32,6 +32,30 @@ type ReadSeekCloser interface {
|
||||
io.Closer
|
||||
}
|
||||
|
||||
// Sizer provides the size of, typically, a io.Reader.
|
||||
// As implemented by e.g. os.File and io.SectionReader.
|
||||
type Sizer interface {
|
||||
Size() int64
|
||||
}
|
||||
|
||||
type SizeReader interface {
|
||||
io.Reader
|
||||
Sizer
|
||||
}
|
||||
|
||||
// ToSizeReader converts the given io.Reader to a SizeReader.
|
||||
// Note that if r is not a SizeReader, the entire content will be read into memory
|
||||
func ToSizeReader(r io.Reader) (SizeReader, error) {
|
||||
if sr, ok := r.(SizeReader); ok {
|
||||
return sr, nil
|
||||
}
|
||||
b, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bytes.NewReader(b), nil
|
||||
}
|
||||
|
||||
// CloserFunc is an adapter to allow the use of ordinary functions as io.Closers.
|
||||
type CloserFunc func() error
|
||||
|
||||
|
||||
+1
-2
@@ -310,13 +310,12 @@ func GetDependencyList() []string {
|
||||
|
||||
// GetDependencyListNonGo returns a list of non-Go dependencies.
|
||||
func GetDependencyListNonGo() []string {
|
||||
var deps []string
|
||||
deps := []string{formatDep("github.com/webmproject/libwebp", "v1.6.0")} // via WASM. TODO(bep) get versions from the plugin setup.
|
||||
|
||||
if IsExtended {
|
||||
deps = append(
|
||||
deps,
|
||||
formatDep("github.com/sass/libsass", "3.6.6"),
|
||||
formatDep("github.com/webmproject/libwebp", "v1.3.2"),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import "github.com/gohugoio/hugo/common/version"
|
||||
// This should be the only one.
|
||||
var CurrentVersion = version.Version{
|
||||
Major: 0,
|
||||
Minor: 153,
|
||||
PatchLevel: 0,
|
||||
Suffix: "-DEV",
|
||||
Minor: 155,
|
||||
PatchLevel: 3,
|
||||
Suffix: "",
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bep/logg"
|
||||
@@ -123,8 +124,11 @@ func New(opts Options) Logger {
|
||||
)
|
||||
|
||||
l := logger.WithLevel(opts.Level)
|
||||
logMu := &sync.Mutex{}
|
||||
|
||||
reset := func() {
|
||||
logMu.Lock()
|
||||
defer logMu.Unlock()
|
||||
logCounters.mu.Lock()
|
||||
defer logCounters.mu.Unlock()
|
||||
logCounters.counters = make(map[logg.Level]int)
|
||||
@@ -135,6 +139,7 @@ func New(opts Options) Logger {
|
||||
}
|
||||
|
||||
return &logAdapter{
|
||||
mu: logMu,
|
||||
logCounters: logCounters,
|
||||
errors: errorsw,
|
||||
reset: reset,
|
||||
@@ -173,6 +178,7 @@ func LevelLoggerToWriter(l logg.LevelLogger) io.Writer {
|
||||
|
||||
type Logger interface {
|
||||
Debug() logg.LevelLogger
|
||||
DebugCommand(command string) logg.LevelLogger
|
||||
Debugf(format string, v ...any)
|
||||
Debugln(v ...any)
|
||||
Error() logg.LevelLogger
|
||||
@@ -203,6 +209,7 @@ type Logger interface {
|
||||
}
|
||||
|
||||
type logAdapter struct {
|
||||
mu *sync.Mutex
|
||||
logCounters *logLevelCounter
|
||||
errors *strings.Builder
|
||||
reset func()
|
||||
@@ -221,6 +228,10 @@ func (l *logAdapter) Debug() logg.LevelLogger {
|
||||
return l.debugl
|
||||
}
|
||||
|
||||
func (l *logAdapter) DebugCommand(command string) logg.LevelLogger {
|
||||
return l.debugl.WithField(FieldNameCmd, command)
|
||||
}
|
||||
|
||||
func (l *logAdapter) Debugf(format string, v ...any) {
|
||||
l.debugl.Logf(format, v...)
|
||||
}
|
||||
@@ -323,6 +334,8 @@ func (l *logAdapter) Errorln(v ...any) {
|
||||
}
|
||||
|
||||
func (l *logAdapter) Errors() string {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
return l.errors.String()
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/hmaps"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
|
||||
@@ -54,12 +54,12 @@ type PathParser struct {
|
||||
|
||||
// Below gets created on demand.
|
||||
initOnce sync.Once
|
||||
sitesMatrixCache *maps.Cache[string, sitesmatrix.VectorStore] // Maps language index to sites matrix vector store.
|
||||
sitesMatrixCache *hmaps.Cache[string, sitesmatrix.VectorStore] // Maps language index to sites matrix vector store.
|
||||
}
|
||||
|
||||
func (pp *PathParser) init() {
|
||||
pp.initOnce.Do(func() {
|
||||
pp.sitesMatrixCache = maps.NewCache[string, sitesmatrix.VectorStore]()
|
||||
pp.sitesMatrixCache = hmaps.NewCache[string, sitesmatrix.VectorStore]()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -153,6 +153,35 @@ func (p PR[T]) FilterCopy(s []T) []T {
|
||||
return result
|
||||
}
|
||||
|
||||
const (
|
||||
rangeOpNone = iota
|
||||
rangeOpLT
|
||||
rangeOpLTE
|
||||
rangeOpGT
|
||||
rangeOpGTE
|
||||
rangeOpEQ
|
||||
rangeOpNE
|
||||
)
|
||||
|
||||
func cutRangeOp(s string) (op int, rest string) {
|
||||
switch {
|
||||
case strings.HasPrefix(s, ">= "):
|
||||
return rangeOpGTE, s[3:]
|
||||
case strings.HasPrefix(s, "<= "):
|
||||
return rangeOpLTE, s[3:]
|
||||
case strings.HasPrefix(s, "== "):
|
||||
return rangeOpEQ, s[3:]
|
||||
case strings.HasPrefix(s, "!= "):
|
||||
return rangeOpNE, s[3:]
|
||||
case strings.HasPrefix(s, "> "):
|
||||
return rangeOpGT, s[2:]
|
||||
case strings.HasPrefix(s, "< "):
|
||||
return rangeOpLT, s[2:]
|
||||
default:
|
||||
return rangeOpNone, s
|
||||
}
|
||||
}
|
||||
|
||||
// NewStringPredicateFromGlobs creates a string predicate from the given glob patterns.
|
||||
// A glob pattern starting with "!" is a negation pattern which will be ANDed with the rest.
|
||||
func NewStringPredicateFromGlobs(patterns []string, getGlob func(pattern string) (glob.Glob, error)) (P[string], error) {
|
||||
@@ -187,6 +216,86 @@ func NewStringPredicateFromGlobs(patterns []string, getGlob func(pattern string)
|
||||
return p.BoolFunc(), nil
|
||||
}
|
||||
|
||||
type IndexMatcher interface {
|
||||
IndexMatch(match P[string]) (iter.Seq[int], error)
|
||||
// NewIndexStringPredicateFromGlobsAndRanges creates an IndexString predicate from the given glob patterns and range patterns.
|
||||
// A glob pattern starting with "!" is a negation pattern which will be ANDed with the rest.
|
||||
// A range pattern is one of "> value", ">= value", "< value" or "<= value".
|
||||
func NewIndexStringPredicateFromGlobsAndRanges(patterns []string, getIndex func(s string) int, getGlob func(pattern string) (glob.Glob, error)) (P[IndexString], error) {
|
||||
var p PR[IndexString]
|
||||
for _, pattern := range patterns {
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
if pattern == "" {
|
||||
continue
|
||||
}
|
||||
negate := strings.HasPrefix(pattern, hglob.NegationPrefix)
|
||||
if negate {
|
||||
pattern = pattern[2:]
|
||||
g, err := getGlob(pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p = p.And(func(s IndexString) Match {
|
||||
return BoolMatch(!g.Match(s.String))
|
||||
})
|
||||
} else {
|
||||
// This can be either a glob or a value prefixed with one of >, >=, < or <=.
|
||||
o, v := cutRangeOp(pattern)
|
||||
if o != rangeOpNone {
|
||||
i := getIndex(v)
|
||||
if i == -1 {
|
||||
// No match possible.
|
||||
p = p.And(func(s IndexString) Match {
|
||||
return BoolMatch(false)
|
||||
})
|
||||
continue
|
||||
}
|
||||
switch o {
|
||||
// The greater values starts at the top with index 0.
|
||||
case rangeOpGT:
|
||||
p = p.And(func(s IndexString) Match {
|
||||
return BoolMatch(s.Index < i)
|
||||
})
|
||||
case rangeOpGTE:
|
||||
p = p.And(func(s IndexString) Match {
|
||||
return BoolMatch(s.Index <= i)
|
||||
})
|
||||
case rangeOpLT:
|
||||
p = p.And(func(s IndexString) Match {
|
||||
return BoolMatch(s.Index > i)
|
||||
})
|
||||
case rangeOpLTE:
|
||||
p = p.And(func(s IndexString) Match {
|
||||
return BoolMatch(s.Index >= i)
|
||||
})
|
||||
case rangeOpEQ:
|
||||
p = p.And(func(s IndexString) Match {
|
||||
return BoolMatch(s.Index == i)
|
||||
})
|
||||
case rangeOpNE:
|
||||
p = p.And(func(s IndexString) Match {
|
||||
return BoolMatch(s.Index != i)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
g, err := getGlob(pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p = p.Or(func(s IndexString) Match {
|
||||
return BoolMatch(g.Match(s.String))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return p.BoolFunc(), nil
|
||||
}
|
||||
|
||||
type IndexString struct {
|
||||
Index int
|
||||
String string
|
||||
}
|
||||
|
||||
type IndexMatcher interface {
|
||||
IndexMatch(match P[IndexString]) (iter.Seq[int], error)
|
||||
}
|
||||
|
||||
@@ -146,6 +146,107 @@ func TestNewStringPredicateFromGlobs(t *testing.T) {
|
||||
c.Assert(m("anything"), qt.IsFalse)
|
||||
}
|
||||
|
||||
func TestNewIndexStringPredicateFromGlobsAndRanges(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
// Simulate versions: v4.0.0=0, v3.0.0=1, v2.0.0=2, v1.0.0=3
|
||||
// Lower index = greater value.
|
||||
versions := []string{"v4.0.0", "v3.0.0", "v2.0.0", "v1.0.0"}
|
||||
getIndex := func(s string) int {
|
||||
for i, v := range versions {
|
||||
if v == s {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
getGlob := func(pattern string) (glob.Glob, error) {
|
||||
return glob.Compile(pattern)
|
||||
}
|
||||
|
||||
n := func(patterns ...string) predicate.P[predicate.IndexString] {
|
||||
p, err := predicate.NewIndexStringPredicateFromGlobsAndRanges(patterns, getIndex, getGlob)
|
||||
c.Assert(err, qt.IsNil)
|
||||
return p
|
||||
}
|
||||
|
||||
is := func(i int) predicate.IndexString {
|
||||
return predicate.IndexString{Index: i, String: versions[i]}
|
||||
}
|
||||
|
||||
// Test >= v2.0.0 (index 2): should match indices <= 2
|
||||
m := n(">= v2.0.0")
|
||||
c.Assert(m(is(0)), qt.IsTrue) // v4.0.0
|
||||
c.Assert(m(is(1)), qt.IsTrue) // v3.0.0
|
||||
c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
|
||||
c.Assert(m(is(3)), qt.IsFalse) // v1.0.0
|
||||
|
||||
// Test > v2.0.0 (index 2): should match indices < 2
|
||||
m = n("> v2.0.0")
|
||||
c.Assert(m(is(0)), qt.IsTrue) // v4.0.0
|
||||
c.Assert(m(is(1)), qt.IsTrue) // v3.0.0
|
||||
c.Assert(m(is(2)), qt.IsFalse) // v2.0.0
|
||||
c.Assert(m(is(3)), qt.IsFalse) // v1.0.0
|
||||
|
||||
// Test < v3.0.0 (index 1): should match indices > 1
|
||||
m = n("< v3.0.0")
|
||||
c.Assert(m(is(0)), qt.IsFalse) // v4.0.0
|
||||
c.Assert(m(is(1)), qt.IsFalse) // v3.0.0
|
||||
c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
|
||||
c.Assert(m(is(3)), qt.IsTrue) // v1.0.0
|
||||
|
||||
// Test range: >= v2.0.0 AND <= v3.0.0
|
||||
m = n(">= v2.0.0", "<= v3.0.0")
|
||||
c.Assert(m(is(0)), qt.IsFalse) // v4.0.0 - too high
|
||||
c.Assert(m(is(1)), qt.IsTrue) // v3.0.0
|
||||
c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
|
||||
c.Assert(m(is(3)), qt.IsFalse) // v1.0.0 - too low
|
||||
|
||||
// Test glob pattern
|
||||
m = n("v2.*.*")
|
||||
c.Assert(m(is(0)), qt.IsFalse) // v4.0.0
|
||||
c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
|
||||
|
||||
// Test glob with negation
|
||||
m = n("v*.*.*", "! v3.*.*")
|
||||
c.Assert(m(is(0)), qt.IsTrue) // v4.0.0
|
||||
c.Assert(m(is(1)), qt.IsFalse) // v3.0.0 - negated
|
||||
c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
|
||||
|
||||
// Test range with negation: >= v2.0.0 but not v3.0.0
|
||||
m = n(">= v2.0.0", "! v3.0.0")
|
||||
c.Assert(m(is(0)), qt.IsTrue) // v4.0.0
|
||||
c.Assert(m(is(1)), qt.IsFalse) // v3.0.0 - negated
|
||||
c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
|
||||
c.Assert(m(is(3)), qt.IsFalse) // v1.0.0 - out of range
|
||||
|
||||
// Test unknown value in range returns no match
|
||||
m = n(">= v99.0.0")
|
||||
c.Assert(m(is(0)), qt.IsFalse)
|
||||
c.Assert(m(is(3)), qt.IsFalse)
|
||||
|
||||
// Test == v2.0.0: should only match v2.0.0
|
||||
m = n("== v2.0.0")
|
||||
c.Assert(m(is(0)), qt.IsFalse) // v4.0.0
|
||||
c.Assert(m(is(1)), qt.IsFalse) // v3.0.0
|
||||
c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
|
||||
c.Assert(m(is(3)), qt.IsFalse) // v1.0.0
|
||||
|
||||
// Test != v2.0.0: should match everything except v2.0.0
|
||||
m = n("!= v2.0.0")
|
||||
c.Assert(m(is(0)), qt.IsTrue) // v4.0.0
|
||||
c.Assert(m(is(1)), qt.IsTrue) // v3.0.0
|
||||
c.Assert(m(is(2)), qt.IsFalse) // v2.0.0
|
||||
c.Assert(m(is(3)), qt.IsTrue) // v1.0.0
|
||||
|
||||
// Test != with range: >= v2.0.0 AND != v3.0.0
|
||||
m = n(">= v2.0.0", "!= v3.0.0")
|
||||
c.Assert(m(is(0)), qt.IsTrue) // v4.0.0
|
||||
c.Assert(m(is(1)), qt.IsFalse) // v3.0.0 - excluded by !=
|
||||
c.Assert(m(is(2)), qt.IsTrue) // v2.0.0
|
||||
c.Assert(m(is(3)), qt.IsFalse) // v1.0.0 - out of range
|
||||
}
|
||||
|
||||
func BenchmarkPredicate(b *testing.B) {
|
||||
b.Run("and or no match", func(b *testing.B) {
|
||||
var p predicate.PR[int] = intP1
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2026 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package predicate
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestCutRangeOp(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
op, rest := cutRangeOp(">= value")
|
||||
c.Assert(op, qt.Equals, rangeOpGTE)
|
||||
c.Assert(rest, qt.Equals, "value")
|
||||
|
||||
op, rest = cutRangeOp("<= value")
|
||||
c.Assert(op, qt.Equals, rangeOpLTE)
|
||||
c.Assert(rest, qt.Equals, "value")
|
||||
|
||||
op, rest = cutRangeOp("> value")
|
||||
c.Assert(op, qt.Equals, rangeOpGT)
|
||||
c.Assert(rest, qt.Equals, "value")
|
||||
|
||||
op, rest = cutRangeOp("< value")
|
||||
c.Assert(op, qt.Equals, rangeOpLT)
|
||||
c.Assert(rest, qt.Equals, "value")
|
||||
|
||||
op, rest = cutRangeOp("== value")
|
||||
c.Assert(op, qt.Equals, rangeOpEQ)
|
||||
c.Assert(rest, qt.Equals, "value")
|
||||
|
||||
op, rest = cutRangeOp("!= value")
|
||||
c.Assert(op, qt.Equals, rangeOpNE)
|
||||
c.Assert(rest, qt.Equals, "value")
|
||||
|
||||
op, rest = cutRangeOp("value")
|
||||
c.Assert(op, qt.Equals, rangeOpNone)
|
||||
c.Assert(rest, qt.Equals, "value")
|
||||
}
|
||||
@@ -37,6 +37,9 @@ type Version struct {
|
||||
// HugoVersionSuffix is the suffix used in the Hugo version string.
|
||||
// It will be blank for release versions.
|
||||
Suffix string
|
||||
|
||||
// Set when parsed from a string,
|
||||
source string
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -44,11 +47,8 @@ var (
|
||||
_ compare.Comparer = (*VersionString)(nil)
|
||||
)
|
||||
|
||||
// IsAlphaBetaOrRC returns whether this version is an alpha, beta, or release candidate.
|
||||
func (v Version) IsAlphaBetaOrRC() bool {
|
||||
s := strings.ToLower(v.Suffix)
|
||||
// e.g. "alpha.1", "beta.2", "rc.3"
|
||||
return strings.Contains(s, "alpha.") || strings.Contains(s, "beta.") || strings.Contains(s, "rc.")
|
||||
func (v Version) IsZero() bool {
|
||||
return v.Major == 0 && v.Minor == 0 && v.PatchLevel == 0 && v.Suffix == ""
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
@@ -109,6 +109,7 @@ func ParseVersion(s string) (Version, error) {
|
||||
vv.Suffix = suffix
|
||||
}
|
||||
vv.Major, vv.Minor, vv.PatchLevel = parseVersion(s)
|
||||
vv.source = s
|
||||
|
||||
return vv, nil
|
||||
}
|
||||
@@ -176,6 +177,10 @@ func CompareVersions(v1 Version, v2 any) int {
|
||||
case int64:
|
||||
c = compareFloatWithVersion(float64(d), v1)
|
||||
case Version:
|
||||
if v1.IsZero() && d.IsZero() {
|
||||
// Fall back to source comparison.
|
||||
return strings.Compare(v1.source, d.source)
|
||||
}
|
||||
if d.Major == v1.Major && d.Minor == v1.Minor && d.PatchLevel == v1.PatchLevel {
|
||||
return strings.Compare(v1.Suffix, d.Suffix)
|
||||
}
|
||||
@@ -200,11 +205,13 @@ func CompareVersions(v1 Version, v2 any) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
v, err := ParseVersion(s)
|
||||
if err != nil {
|
||||
return -1
|
||||
v2v, _ := ParseVersion(s)
|
||||
if v1.IsZero() && v2v.IsZero() {
|
||||
// Fall back to source comparison.
|
||||
return strings.Compare(v1.source, v2v.source)
|
||||
}
|
||||
return v1.Compare(v)
|
||||
|
||||
return v1.Compare(v2v)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,11 @@ func TestHugoVersion(t *testing.T) {
|
||||
func TestCompareVersions(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
parseIgnoreErr := func(s string) Version {
|
||||
v, _ := ParseVersion(s)
|
||||
return v
|
||||
}
|
||||
|
||||
c.Assert(CompareVersions(MustParseVersion("0.20.0"), 0.20), qt.Equals, 0)
|
||||
c.Assert(CompareVersions(MustParseVersion("0.20.0"), float32(0.20)), qt.Equals, 0)
|
||||
c.Assert(CompareVersions(MustParseVersion("0.20.0"), float64(0.20)), qt.Equals, 0)
|
||||
@@ -69,6 +74,15 @@ func TestCompareVersions(t *testing.T) {
|
||||
c.Assert(CompareVersions(MustParseVersion("0.22.0-DEV"), "0.22"), qt.Equals, 1)
|
||||
c.Assert(CompareVersions(MustParseVersion("0.22.1-DEV"), "0.22"), qt.Equals, -1)
|
||||
c.Assert(CompareVersions(MustParseVersion("0.22.1-DEV"), "0.22.1-DEV"), qt.Equals, 0)
|
||||
|
||||
c.Assert(CompareVersions(parseIgnoreErr("foobar"), "v1.0.0"), qt.Equals, 1)
|
||||
c.Assert(CompareVersions(parseIgnoreErr("v1.0.0"), "foobar"), qt.Equals, -1)
|
||||
c.Assert(CompareVersions(parseIgnoreErr("foobar"), "foobar"), qt.Equals, 0)
|
||||
c.Assert(CompareVersions(parseIgnoreErr("foobar"), parseIgnoreErr("foobar")), qt.Equals, 0)
|
||||
c.Assert(CompareVersions(parseIgnoreErr("a"), "b"), qt.Equals, -1)
|
||||
c.Assert(CompareVersions(parseIgnoreErr("a"), parseIgnoreErr("b")), qt.Equals, -1)
|
||||
c.Assert(CompareVersions(parseIgnoreErr("b"), "a"), qt.Equals, 1)
|
||||
c.Assert(CompareVersions(parseIgnoreErr("b"), parseIgnoreErr("a")), qt.Equals, 1)
|
||||
}
|
||||
|
||||
func TestParseHugoVersion(t *testing.T) {
|
||||
|
||||
@@ -29,10 +29,10 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/cache/filecache"
|
||||
"github.com/gohugoio/hugo/cache/httpcache"
|
||||
"github.com/gohugoio/hugo/common/hmaps"
|
||||
"github.com/gohugoio/hugo/common/hstrings"
|
||||
"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/urls"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
@@ -212,7 +212,7 @@ type Config struct {
|
||||
|
||||
// User provided parameters.
|
||||
// <docsmeta>{"refs": ["config:languages:params"] }</docsmeta>
|
||||
Params maps.Params `mapstructure:"-"`
|
||||
Params hmaps.Params `mapstructure:"-"`
|
||||
|
||||
// UglyURLs configuration. Either a boolean or a sections map.
|
||||
UglyURLs any `mapstructure:"-"`
|
||||
@@ -471,10 +471,10 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
|
||||
gc.RenderHookUseEmbeddedNever,
|
||||
}
|
||||
if !slices.Contains(renderHookUseEmbeddedModes, c.Markup.Goldmark.RenderHooks.Image.UseEmbedded) {
|
||||
return fmt.Errorf("site config markup.goldmark.renderHooks.image must be one of %s", helpers.StringSliceToList(renderHookUseEmbeddedModes, "or"))
|
||||
return fmt.Errorf("site config markup.goldmark.renderHooks.image.useEmbedded must be one of %s", helpers.StringSliceToList(renderHookUseEmbeddedModes, "or"))
|
||||
}
|
||||
if !slices.Contains(renderHookUseEmbeddedModes, c.Markup.Goldmark.RenderHooks.Link.UseEmbedded) {
|
||||
return fmt.Errorf("site config markup.goldmark.renderHooks.link must be one of %s", helpers.StringSliceToList(renderHookUseEmbeddedModes, "or"))
|
||||
return fmt.Errorf("site config markup.goldmark.renderHooks.link.useEmbedded must be one of %s", helpers.StringSliceToList(renderHookUseEmbeddedModes, "or"))
|
||||
}
|
||||
|
||||
c.C = &ConfigCompiled{
|
||||
@@ -607,8 +607,14 @@ type RootConfig struct {
|
||||
DefaultOutputFormat string
|
||||
|
||||
// Disable generation of redirect to the default language when DefaultContentLanguageInSubdir is enabled.
|
||||
// Note that this currently is an alias for DisableDefaultSiteRedirect introduced in v0.154.5.
|
||||
// It's not obvious how a more fine grained setup would work.
|
||||
DisableDefaultLanguageRedirect bool
|
||||
|
||||
// Disable generation of redirects to the default site when DefaultContentRoleInSubdir or DefaultContentVersionInSubdir or DefaultContentLanguageInSubdir is enabled.
|
||||
// The default site is the site is the combination of defaultContentLanguage, defaultContentVersion and defaultContentRole.
|
||||
DisableDefaultSiteRedirect bool
|
||||
|
||||
// Disable creation of alias redirect pages.
|
||||
DisableAliases bool
|
||||
|
||||
@@ -816,6 +822,7 @@ type Configs struct {
|
||||
|
||||
Modules modules.Modules
|
||||
ModulesClient *modules.Client
|
||||
FileCaches filecache.Caches
|
||||
|
||||
// All below is set in Init.
|
||||
Languages langs.Languages
|
||||
@@ -846,7 +853,7 @@ func (c *Configs) IsZero() bool {
|
||||
return c == nil || len(c.Languages) == 0
|
||||
}
|
||||
|
||||
func (c *Configs) Init(logger loggers.Logger) error {
|
||||
func (c *Configs) Init(sourceFs afero.Fs, logger loggers.Logger) error {
|
||||
var languages langs.Languages
|
||||
|
||||
for _, f := range c.Base.Languages.Config.Sorted {
|
||||
@@ -915,10 +922,9 @@ func (c *Configs) Init(logger loggers.Logger) error {
|
||||
// avoid initializing the same config more than once.
|
||||
for i, l := range c.Languages {
|
||||
langConfig := c.LanguageConfigMap[l.Lang]
|
||||
sitesMatrix := sitesmatrix.NewIntSetsBuilder(c.ConfiguredDimensions).WithLanguageIndices(i).WithAllIfNotSet().Build()
|
||||
for _, s := range allDecoderSetups {
|
||||
if getInitializer := s.getInitializer; getInitializer != nil {
|
||||
if err := getInitializer(langConfig).InitConfig(logger, sitesMatrix, c.ConfiguredDimensions); err != nil {
|
||||
if err := getInitializer(langConfig).InitConfig(logger, nil, c.ConfiguredDimensions); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1018,7 +1024,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
if lang == "" {
|
||||
lang = "en"
|
||||
}
|
||||
res.Cfg.Set("languages", maps.Params{lang: maps.Params{}})
|
||||
res.Cfg.Set("languages", hmaps.Params{lang: hmaps.Params{}})
|
||||
}
|
||||
bcfg := res.BaseConfig
|
||||
cfg := res.Cfg
|
||||
@@ -1045,11 +1051,11 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
var differentRootKeys []string
|
||||
switch x := v.(type) {
|
||||
case nil:
|
||||
case maps.Params:
|
||||
case hmaps.Params:
|
||||
_, found := x["params"]
|
||||
if !found {
|
||||
x["params"] = maps.Params{
|
||||
maps.MergeStrategyKey: maps.ParamsMergeStrategyDeep,
|
||||
x["params"] = hmaps.Params{
|
||||
hmaps.MergeStrategyKey: hmaps.ParamsMergeStrategyDeep,
|
||||
}
|
||||
}
|
||||
for kk, vv := range x {
|
||||
@@ -1061,16 +1067,16 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
isMultihost = true
|
||||
}
|
||||
|
||||
if p, ok := vv.(maps.Params); ok {
|
||||
if p, ok := vv.(hmaps.Params); ok {
|
||||
// With the introduction of YAML anchor and alias support, language config entries
|
||||
// may be contain shared references.
|
||||
// This also break potential cycles.
|
||||
vv = maps.CloneParamsDeep(p)
|
||||
vv = hmaps.CloneParamsDeep(p)
|
||||
}
|
||||
|
||||
if kk == "cascade" {
|
||||
// If not set, add the current language to make sure it does not get applied to other languages.
|
||||
page.AddLangToCascadeTargetMap(k, vv.(maps.Params))
|
||||
page.AddLangToCascadeTargetMap(k, vv.(hmaps.Params))
|
||||
// Always clone cascade config to get the sites matrix right.
|
||||
differentRootKeys = append(differentRootKeys, kk)
|
||||
}
|
||||
@@ -1081,14 +1087,14 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
// This overrides a root key and potentially needs a merge.
|
||||
if !reflect.DeepEqual(rootv, vv) {
|
||||
switch vvv := vv.(type) {
|
||||
case maps.Params:
|
||||
case hmaps.Params:
|
||||
differentRootKeys = append(differentRootKeys, kk)
|
||||
|
||||
// Use the language value as base.
|
||||
// Note that this is already cloned above.
|
||||
mergedConfigEntry := vvv
|
||||
// Merge in the root value.
|
||||
maps.MergeParams(mergedConfigEntry, rootv.(maps.Params))
|
||||
hmaps.MergeParams(mergedConfigEntry, rootv.(hmaps.Params))
|
||||
|
||||
mergedConfig.Set(kk, mergedConfigEntry)
|
||||
default:
|
||||
@@ -1098,7 +1104,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
}
|
||||
} else {
|
||||
switch vv.(type) {
|
||||
case maps.Params:
|
||||
case hmaps.Params:
|
||||
differentRootKeys = append(differentRootKeys, kk)
|
||||
default:
|
||||
// Apply new values to the root.
|
||||
@@ -1134,7 +1140,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
}
|
||||
|
||||
langConfigMap[k] = clone
|
||||
case maps.ParamsMergeStrategy:
|
||||
case hmaps.ParamsMergeStrategy:
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown type in languages config: %T", v))
|
||||
@@ -1149,10 +1155,16 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
l.CommonDirs.CacheDir = bcfg.CacheDir
|
||||
}
|
||||
|
||||
caches, err := filecache.NewCaches(all.Caches, fs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create file caches from configuration: %w", err)
|
||||
}
|
||||
|
||||
cm := &Configs{
|
||||
Base: all,
|
||||
LanguageConfigMap: langConfigMap,
|
||||
LoadingInfo: res,
|
||||
FileCaches: caches,
|
||||
IsMultihost: isMultihost,
|
||||
}
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ import (
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
|
||||
"github.com/gohugoio/hugo/cache/httpcache"
|
||||
"github.com/gohugoio/hugo/common/hmaps"
|
||||
"github.com/gohugoio/hugo/common/hstrings"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/config/privacy"
|
||||
@@ -196,7 +196,7 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
key: "outputs",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
defaults := createDefaultOutputFormats(p.c.OutputFormats.Config)
|
||||
m := maps.CleanConfigStringMap(p.p.GetStringMap("outputs"))
|
||||
m := hmaps.CleanConfigStringMap(p.p.GetStringMap("outputs"))
|
||||
p.c.Outputs = make(map[string][]string)
|
||||
for k, v := range m {
|
||||
s := types.ToStringSlicePreserveString(v)
|
||||
@@ -225,13 +225,13 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
"languages": {
|
||||
key: "languages",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
m := maps.CleanConfigStringMap(p.p.GetStringMap(d.key))
|
||||
m := hmaps.CleanConfigStringMap(p.p.GetStringMap(d.key))
|
||||
if len(m) == 1 {
|
||||
// In v0.112.4 we moved this to the language config, but it's very commmon for mono language sites to have this at the top level.
|
||||
var first maps.Params
|
||||
var first hmaps.Params
|
||||
var ok bool
|
||||
for _, v := range m {
|
||||
first, ok = v.(maps.Params)
|
||||
first, ok = v.(hmaps.Params)
|
||||
if ok {
|
||||
break
|
||||
}
|
||||
@@ -266,7 +266,7 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
key: "versions",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
m := maps.CleanConfigStringMap(p.p.GetStringMap(d.key))
|
||||
m := hmaps.CleanConfigStringMap(p.p.GetStringMap(d.key))
|
||||
p.c.Versions, err = versions.DecodeConfig(p.c.RootConfig.DefaultContentVersion, m)
|
||||
return err
|
||||
},
|
||||
@@ -278,7 +278,7 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
err error
|
||||
defaultContentRole string
|
||||
)
|
||||
m := maps.CleanConfigStringMap(p.p.GetStringMap(d.key))
|
||||
m := hmaps.CleanConfigStringMap(p.p.GetStringMap(d.key))
|
||||
p.c.Roles, defaultContentRole, err = roles.DecodeConfig(p.c.RootConfig.DefaultContentRole, m)
|
||||
p.c.RootConfig.DefaultContentRole = defaultContentRole
|
||||
|
||||
@@ -289,7 +289,7 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
"params": {
|
||||
key: "params",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
p.c.Params = maps.CleanConfigStringMap(p.p.GetStringMap("params"))
|
||||
p.c.Params = hmaps.CleanConfigStringMap(p.p.GetStringMap("params"))
|
||||
if p.c.Params == nil {
|
||||
p.c.Params = make(map[string]any)
|
||||
}
|
||||
@@ -335,7 +335,7 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
key: "taxonomies",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
if p.p.IsSet(d.key) {
|
||||
p.c.Taxonomies = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
|
||||
p.c.Taxonomies = hmaps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
@@ -447,7 +447,7 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
"author": {
|
||||
key: "author",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
p.c.Author = maps.CleanConfigStringMap(p.p.GetStringMap(d.key))
|
||||
p.c.Author = hmaps.CleanConfigStringMap(p.p.GetStringMap(d.key))
|
||||
return nil
|
||||
},
|
||||
internalOrDeprecated: true,
|
||||
@@ -455,7 +455,7 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
"social": {
|
||||
key: "social",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
p.c.Social = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
|
||||
p.c.Social = hmaps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
|
||||
return nil
|
||||
},
|
||||
internalOrDeprecated: true,
|
||||
@@ -469,8 +469,8 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
p.c.UglyURLs = vv
|
||||
case string:
|
||||
p.c.UglyURLs = vv == "true"
|
||||
case maps.Params:
|
||||
p.c.UglyURLs = cast.ToStringMapBool(maps.CleanConfigStringMap(vv))
|
||||
case hmaps.Params:
|
||||
p.c.UglyURLs = cast.ToStringMapBool(hmaps.CleanConfigStringMap(vv))
|
||||
default:
|
||||
p.c.UglyURLs = cast.ToStringMapBool(v)
|
||||
}
|
||||
|
||||
@@ -25,9 +25,8 @@ import (
|
||||
)
|
||||
|
||||
type ConfigLanguage struct {
|
||||
config *Config
|
||||
baseConfig config.BaseConfig
|
||||
|
||||
config *Config
|
||||
baseConfig config.BaseConfig
|
||||
m *Configs
|
||||
language *langs.Language
|
||||
languageIndex int
|
||||
@@ -123,6 +122,10 @@ func (c ConfigLanguage) BaseConfig() config.BaseConfig {
|
||||
return c.baseConfig
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) FileCaches() any {
|
||||
return c.m.FileCaches
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) Dirs() config.CommonDirs {
|
||||
return c.config.CommonDirs
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
package allconfig
|
||||
|
||||
import (
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/hmaps"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/docshelper"
|
||||
)
|
||||
@@ -27,12 +27,12 @@ func init() {
|
||||
if v.internalOrDeprecated {
|
||||
continue
|
||||
}
|
||||
cfg.Set(configRoot, make(maps.Params))
|
||||
cfg.Set(configRoot, make(hmaps.Params))
|
||||
}
|
||||
lang := maps.Params{
|
||||
"en": maps.Params{
|
||||
"menus": maps.Params{},
|
||||
"params": maps.Params{},
|
||||
lang := hmaps.Params{
|
||||
"en": hmaps.Params{
|
||||
"menus": hmaps.Params{},
|
||||
"params": hmaps.Params{},
|
||||
},
|
||||
}
|
||||
cfg.Set("languages", lang)
|
||||
|
||||
@@ -25,9 +25,9 @@ import (
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/hexec"
|
||||
"github.com/gohugoio/hugo/common/hmaps"
|
||||
"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/config"
|
||||
@@ -95,7 +95,7 @@ func LoadConfig(d ConfigSourceDescriptor) (configs *Configs, err error) {
|
||||
configs.Modules = moduleConfig.AllModules
|
||||
configs.ModulesClient = modulesClient
|
||||
|
||||
if err := configs.Init(d.Logger); err != nil {
|
||||
if err := configs.Init(d.Fs, d.Logger); err != nil {
|
||||
return nil, fmt.Errorf("failed to init config: %w", err)
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ func (l configLoader) applyConfigAliases() error {
|
||||
}
|
||||
|
||||
func (l configLoader) applyDefaultConfig() error {
|
||||
defaultSettings := maps.Params{
|
||||
defaultSettings := hmaps.Params{
|
||||
// These dirs are used early/before we build the config struct.
|
||||
"themesDir": "themes",
|
||||
"configDir": "config",
|
||||
@@ -186,7 +186,7 @@ func (l configLoader) normalizeCfg(cfg config.Provider) error {
|
||||
} else if b, ok := cfg.Get("minify").(bool); ok {
|
||||
hugo.Deprecate("site config minify", "Use minify.minifyOutput instead.", "v0.150.0")
|
||||
if b {
|
||||
cfg.Set("minify", maps.Params{"minifyOutput": true})
|
||||
cfg.Set("minify", hmaps.Params{"minifyOutput": true})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ func (l configLoader) applyOsEnvOverrides(environ []string) error {
|
||||
}
|
||||
|
||||
for _, env := range hugoEnv {
|
||||
existing, nestedKey, owner, err := maps.GetNestedParamFn(env.Key, delim, l.cfg.Get)
|
||||
existing, nestedKey, owner, err := hmaps.GetNestedParamFn(env.Key, delim, l.cfg.Get)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -477,6 +477,7 @@ func (l *configLoader) loadModules(configs *Configs, ignoreModuleDoesNotExist bo
|
||||
PublishDir: publishDir,
|
||||
Environment: l.Environment,
|
||||
CacheDir: conf.Caches.CacheDirModules(),
|
||||
ModuleQueriesCache: configs.FileCaches.ModuleQueriesCache(),
|
||||
ModuleConfig: conf.Module,
|
||||
IgnoreVendor: ignoreVendor,
|
||||
IgnoreModuleDoesNotExist: ignoreModuleDoesNotExist,
|
||||
@@ -547,7 +548,7 @@ func (l configLoader) loadConfig(configName string) (string, error) {
|
||||
}
|
||||
|
||||
func (l configLoader) deleteMergeStrategies() (err error) {
|
||||
l.cfg.WalkParams(func(params ...maps.KeyParams) bool {
|
||||
l.cfg.WalkParams(func(params ...hmaps.KeyParams) bool {
|
||||
params[len(params)-1].Params.DeleteMergeStrategy()
|
||||
return false
|
||||
})
|
||||
|
||||
@@ -437,7 +437,7 @@ func (c *CacheBuster) CompileConfig(logger loggers.Logger) error {
|
||||
return nil
|
||||
}
|
||||
return func(ss string) bool {
|
||||
match = targetRe.MatchString(ss)
|
||||
match := targetRe.MatchString(ss)
|
||||
matchString := "no match"
|
||||
if match {
|
||||
matchString = "match!"
|
||||
|
||||
@@ -20,11 +20,11 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/hmaps"
|
||||
"github.com/gohugoio/hugo/parser"
|
||||
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/parser/metadecoders"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
@@ -222,11 +222,11 @@ func LoadConfigFromDir(sourceFs afero.Fs, configDir, environment string) (Provid
|
||||
return cfg, dirnames, nil
|
||||
}
|
||||
|
||||
var keyAliases maps.KeyRenamer
|
||||
var keyAliases hmaps.KeyRenamer
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
keyAliases, err = maps.NewKeyRenamer(
|
||||
keyAliases, err = hmaps.NewKeyRenamer(
|
||||
// Before 0.53 we used singular for "menu".
|
||||
"{menu,languages/*/menu}", "menus",
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ package config
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/hmaps"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/common/urls"
|
||||
@@ -41,6 +41,7 @@ type AllProvider interface {
|
||||
Dirs() CommonDirs
|
||||
Quiet() bool
|
||||
DirsBase() CommonDirs
|
||||
FileCaches() any
|
||||
ContentTypes() ContentTypesProvider
|
||||
GetConfigSection(string) any
|
||||
GetConfig() any
|
||||
@@ -96,7 +97,7 @@ type Provider interface {
|
||||
GetString(key string) string
|
||||
GetInt(key string) int
|
||||
GetBool(key string) bool
|
||||
GetParams(key string) maps.Params
|
||||
GetParams(key string) hmaps.Params
|
||||
GetStringMap(key string) map[string]any
|
||||
GetStringMapString(key string) map[string]string
|
||||
GetStringSlice(key string) []string
|
||||
@@ -104,9 +105,9 @@ type Provider interface {
|
||||
Set(key string, value any)
|
||||
Keys() []string
|
||||
Merge(key string, value any)
|
||||
SetDefaults(params maps.Params)
|
||||
SetDefaults(params hmaps.Params)
|
||||
SetDefaultMergeStrategy()
|
||||
WalkParams(walkFn func(params ...maps.KeyParams) bool)
|
||||
WalkParams(walkFn func(params ...hmaps.KeyParams) bool)
|
||||
IsSet(key string) bool
|
||||
}
|
||||
|
||||
|
||||
@@ -20,21 +20,20 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hmaps"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
)
|
||||
|
||||
// New creates a Provider backed by an empty maps.Params.
|
||||
// New creates a Provider backed by an empty hmaps.Params.
|
||||
func New() Provider {
|
||||
return &defaultConfigProvider{
|
||||
root: make(maps.Params),
|
||||
root: make(hmaps.Params),
|
||||
}
|
||||
}
|
||||
|
||||
// NewFrom creates a Provider backed by params.
|
||||
func NewFrom(params maps.Params) Provider {
|
||||
maps.PrepareParams(params)
|
||||
func NewFrom(params hmaps.Params) Provider {
|
||||
hmaps.PrepareParams(params)
|
||||
return &defaultConfigProvider{
|
||||
root: params,
|
||||
}
|
||||
@@ -44,7 +43,7 @@ func NewFrom(params maps.Params) Provider {
|
||||
// All methods are thread safe.
|
||||
type defaultConfigProvider struct {
|
||||
mu sync.RWMutex
|
||||
root maps.Params
|
||||
root hmaps.Params
|
||||
|
||||
keyCache sync.Map
|
||||
}
|
||||
@@ -90,22 +89,22 @@ func (c *defaultConfigProvider) GetString(k string) string {
|
||||
return cast.ToString(v)
|
||||
}
|
||||
|
||||
func (c *defaultConfigProvider) GetParams(k string) maps.Params {
|
||||
func (c *defaultConfigProvider) GetParams(k string) hmaps.Params {
|
||||
v := c.Get(k)
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
return v.(maps.Params)
|
||||
return v.(hmaps.Params)
|
||||
}
|
||||
|
||||
func (c *defaultConfigProvider) GetStringMap(k string) map[string]any {
|
||||
v := c.Get(k)
|
||||
return maps.ToStringMap(v)
|
||||
return hmaps.ToStringMap(v)
|
||||
}
|
||||
|
||||
func (c *defaultConfigProvider) GetStringMapString(k string) map[string]string {
|
||||
v := c.Get(k)
|
||||
return maps.ToStringMapString(v)
|
||||
return hmaps.ToStringMapString(v)
|
||||
}
|
||||
|
||||
func (c *defaultConfigProvider) GetStringSlice(k string) []string {
|
||||
@@ -120,9 +119,9 @@ func (c *defaultConfigProvider) Set(k string, v any) {
|
||||
k = strings.ToLower(k)
|
||||
|
||||
if k == "" {
|
||||
if p, err := maps.ToParamsAndPrepare(v); err == nil {
|
||||
if p, err := hmaps.ToParamsAndPrepare(v); err == nil {
|
||||
// Set the values directly in root.
|
||||
maps.SetParams(c.root, p)
|
||||
hmaps.SetParams(c.root, p)
|
||||
} else {
|
||||
c.root[k] = v
|
||||
}
|
||||
@@ -132,7 +131,7 @@ func (c *defaultConfigProvider) Set(k string, v any) {
|
||||
|
||||
switch vv := v.(type) {
|
||||
case map[string]any, map[any]any, map[string]string:
|
||||
p := maps.MustToParamsAndPrepare(vv)
|
||||
p := hmaps.MustToParamsAndPrepare(vv)
|
||||
v = p
|
||||
}
|
||||
|
||||
@@ -142,9 +141,9 @@ func (c *defaultConfigProvider) Set(k string, v any) {
|
||||
}
|
||||
|
||||
if existing, found := m[key]; found {
|
||||
if p1, ok := existing.(maps.Params); ok {
|
||||
if p2, ok := v.(maps.Params); ok {
|
||||
maps.SetParams(p1, p2)
|
||||
if p1, ok := existing.(hmaps.Params); ok {
|
||||
if p2, ok := v.(hmaps.Params); ok {
|
||||
hmaps.SetParams(p1, p2)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -154,8 +153,8 @@ func (c *defaultConfigProvider) Set(k string, v any) {
|
||||
}
|
||||
|
||||
// SetDefaults will set values from params if not already set.
|
||||
func (c *defaultConfigProvider) SetDefaults(params maps.Params) {
|
||||
maps.PrepareParams(params)
|
||||
func (c *defaultConfigProvider) SetDefaults(params hmaps.Params) {
|
||||
hmaps.PrepareParams(params)
|
||||
for k, v := range params {
|
||||
if _, found := c.root[k]; !found {
|
||||
c.root[k] = v
|
||||
@@ -170,28 +169,28 @@ func (c *defaultConfigProvider) Merge(k string, v any) {
|
||||
|
||||
if k == "" {
|
||||
rs, f := c.root.GetMergeStrategy()
|
||||
if f && rs == maps.ParamsMergeStrategyNone {
|
||||
if f && rs == hmaps.ParamsMergeStrategyNone {
|
||||
// The user has set a "no merge" strategy on this,
|
||||
// nothing more to do.
|
||||
return
|
||||
}
|
||||
|
||||
if p, err := maps.ToParamsAndPrepare(v); err == nil {
|
||||
if p, err := hmaps.ToParamsAndPrepare(v); err == nil {
|
||||
// As there may be keys in p not in root, we need to handle
|
||||
// those as a special case.
|
||||
var keysToDelete []string
|
||||
for kk, vv := range p {
|
||||
if pp, ok := vv.(maps.Params); ok {
|
||||
if pp, ok := vv.(hmaps.Params); ok {
|
||||
if pppi, ok := c.root[kk]; ok {
|
||||
ppp := pppi.(maps.Params)
|
||||
maps.MergeParamsWithStrategy("", ppp, pp)
|
||||
ppp := pppi.(hmaps.Params)
|
||||
hmaps.MergeParamsWithStrategy("", ppp, pp)
|
||||
} else {
|
||||
// We need to use the default merge strategy for
|
||||
// this key.
|
||||
np := make(maps.Params)
|
||||
strategy := c.determineMergeStrategy(maps.KeyParams{Key: "", Params: c.root}, maps.KeyParams{Key: kk, Params: np})
|
||||
np := make(hmaps.Params)
|
||||
strategy := c.determineMergeStrategy(hmaps.KeyParams{Key: "", Params: c.root}, hmaps.KeyParams{Key: kk, Params: np})
|
||||
np.SetMergeStrategy(strategy)
|
||||
maps.MergeParamsWithStrategy("", np, pp)
|
||||
hmaps.MergeParamsWithStrategy("", np, pp)
|
||||
c.root[kk] = np
|
||||
if np.IsZero() {
|
||||
// Just keep it until merge is done.
|
||||
@@ -201,7 +200,7 @@ func (c *defaultConfigProvider) Merge(k string, v any) {
|
||||
}
|
||||
}
|
||||
// Merge the rest.
|
||||
maps.MergeParams(c.root, p)
|
||||
hmaps.MergeParams(c.root, p)
|
||||
for _, k := range keysToDelete {
|
||||
delete(c.root, k)
|
||||
}
|
||||
@@ -214,7 +213,7 @@ func (c *defaultConfigProvider) Merge(k string, v any) {
|
||||
|
||||
switch vv := v.(type) {
|
||||
case map[string]any, map[any]any, map[string]string:
|
||||
p := maps.MustToParamsAndPrepare(vv)
|
||||
p := hmaps.MustToParamsAndPrepare(vv)
|
||||
v = p
|
||||
}
|
||||
|
||||
@@ -224,9 +223,9 @@ func (c *defaultConfigProvider) Merge(k string, v any) {
|
||||
}
|
||||
|
||||
if existing, found := m[key]; found {
|
||||
if p1, ok := existing.(maps.Params); ok {
|
||||
if p2, ok := v.(maps.Params); ok {
|
||||
maps.MergeParamsWithStrategy("", p1, p2)
|
||||
if p1, ok := existing.(hmaps.Params); ok {
|
||||
if p2, ok := v.(hmaps.Params); ok {
|
||||
hmaps.MergeParamsWithStrategy("", p1, p2)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -245,10 +244,10 @@ func (c *defaultConfigProvider) Keys() []string {
|
||||
return keys
|
||||
}
|
||||
|
||||
func (c *defaultConfigProvider) WalkParams(walkFn func(params ...maps.KeyParams) bool) {
|
||||
func (c *defaultConfigProvider) WalkParams(walkFn func(params ...hmaps.KeyParams) bool) {
|
||||
maxDepth := 1000
|
||||
var walk func(depth int, params ...maps.KeyParams)
|
||||
walk = func(depth int, params ...maps.KeyParams) {
|
||||
var walk func(depth int, params ...hmaps.KeyParams)
|
||||
walk = func(depth int, params ...hmaps.KeyParams) {
|
||||
if depth > maxDepth {
|
||||
panic(errors.New("max depth exceeded"))
|
||||
}
|
||||
@@ -258,24 +257,24 @@ func (c *defaultConfigProvider) WalkParams(walkFn func(params ...maps.KeyParams)
|
||||
p1 := params[len(params)-1]
|
||||
i := len(params)
|
||||
for k, v := range p1.Params {
|
||||
if p2, ok := v.(maps.Params); ok {
|
||||
paramsplus1 := make([]maps.KeyParams, i+1)
|
||||
if p2, ok := v.(hmaps.Params); ok {
|
||||
paramsplus1 := make([]hmaps.KeyParams, i+1)
|
||||
copy(paramsplus1, params)
|
||||
paramsplus1[i] = maps.KeyParams{Key: k, Params: p2}
|
||||
paramsplus1[i] = hmaps.KeyParams{Key: k, Params: p2}
|
||||
walk(depth+1, paramsplus1...)
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(0, maps.KeyParams{Key: "", Params: c.root})
|
||||
walk(0, hmaps.KeyParams{Key: "", Params: c.root})
|
||||
}
|
||||
|
||||
func (c *defaultConfigProvider) determineMergeStrategy(params ...maps.KeyParams) maps.ParamsMergeStrategy {
|
||||
func (c *defaultConfigProvider) determineMergeStrategy(params ...hmaps.KeyParams) hmaps.ParamsMergeStrategy {
|
||||
if len(params) == 0 {
|
||||
return maps.ParamsMergeStrategyNone
|
||||
return hmaps.ParamsMergeStrategyNone
|
||||
}
|
||||
|
||||
var (
|
||||
strategy maps.ParamsMergeStrategy
|
||||
strategy hmaps.ParamsMergeStrategy
|
||||
prevIsRoot bool
|
||||
curr = params[len(params)-1]
|
||||
)
|
||||
@@ -299,10 +298,10 @@ func (c *defaultConfigProvider) determineMergeStrategy(params ...maps.KeyParams)
|
||||
// Don't set a merge strategy on the root unless set by user.
|
||||
// This will be handled as a special case.
|
||||
case "params":
|
||||
strategy = maps.ParamsMergeStrategyDeep
|
||||
strategy = hmaps.ParamsMergeStrategyDeep
|
||||
case "outputformats", "mediatypes":
|
||||
if prevIsRoot {
|
||||
strategy = maps.ParamsMergeStrategyShallow
|
||||
strategy = hmaps.ParamsMergeStrategyShallow
|
||||
}
|
||||
case "menus":
|
||||
isMenuKey := prevIsRoot
|
||||
@@ -314,11 +313,11 @@ func (c *defaultConfigProvider) determineMergeStrategy(params ...maps.KeyParams)
|
||||
}
|
||||
}
|
||||
if isMenuKey {
|
||||
strategy = maps.ParamsMergeStrategyShallow
|
||||
strategy = hmaps.ParamsMergeStrategyShallow
|
||||
}
|
||||
default:
|
||||
if strategy == "" {
|
||||
strategy = maps.ParamsMergeStrategyNone
|
||||
strategy = hmaps.ParamsMergeStrategyNone
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,7 +325,7 @@ func (c *defaultConfigProvider) determineMergeStrategy(params ...maps.KeyParams)
|
||||
}
|
||||
|
||||
func (c *defaultConfigProvider) SetDefaultMergeStrategy() {
|
||||
c.WalkParams(func(params ...maps.KeyParams) bool {
|
||||
c.WalkParams(func(params ...hmaps.KeyParams) bool {
|
||||
if len(params) == 0 {
|
||||
return false
|
||||
}
|
||||
@@ -344,7 +343,7 @@ func (c *defaultConfigProvider) SetDefaultMergeStrategy() {
|
||||
})
|
||||
}
|
||||
|
||||
func (c *defaultConfigProvider) getNestedKeyAndMap(key string, create bool) (string, maps.Params) {
|
||||
func (c *defaultConfigProvider) getNestedKeyAndMap(key string, create bool) (string, hmaps.Params) {
|
||||
var parts []string
|
||||
v, ok := c.keyCache.Load(key)
|
||||
if ok {
|
||||
@@ -358,14 +357,14 @@ func (c *defaultConfigProvider) getNestedKeyAndMap(key string, create bool) (str
|
||||
next, found := current[parts[i]]
|
||||
if !found {
|
||||
if create {
|
||||
next = make(maps.Params)
|
||||
next = make(hmaps.Params)
|
||||
current[parts[i]] = next
|
||||
} else {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
var ok bool
|
||||
current, ok = next.(maps.Params)
|
||||
current, ok = next.(hmaps.Params)
|
||||
if !ok {
|
||||
// E.g. a string, not a map that we can store values in.
|
||||
return "", nil
|
||||
|
||||
@@ -22,10 +22,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hmaps"
|
||||
"github.com/gohugoio/hugo/common/para"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
@@ -48,7 +47,7 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
c.Assert(cfg.Get(k), qt.Equals, v)
|
||||
c.Assert(cfg.GetInt(k), qt.Equals, v)
|
||||
|
||||
c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
|
||||
c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
|
||||
"foo": 42,
|
||||
})
|
||||
})
|
||||
@@ -60,7 +59,7 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
"bar": "baz",
|
||||
})
|
||||
|
||||
c.Assert(cfg.Get("foo"), qt.DeepEquals, maps.Params{
|
||||
c.Assert(cfg.Get("foo"), qt.DeepEquals, hmaps.Params{
|
||||
"bar": "baz",
|
||||
})
|
||||
|
||||
@@ -76,14 +75,14 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
})
|
||||
cfg.Set("a.c", "cv")
|
||||
|
||||
c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
|
||||
c.Assert(cfg.Get("a"), qt.DeepEquals, hmaps.Params{
|
||||
"b": "bv",
|
||||
"c": "cv",
|
||||
})
|
||||
c.Assert(cfg.Get("a.c"), qt.Equals, "cv")
|
||||
|
||||
cfg.Set("b.a", "av")
|
||||
c.Assert(cfg.Get("b"), qt.DeepEquals, maps.Params{
|
||||
c.Assert(cfg.Get("b"), qt.DeepEquals, hmaps.Params{
|
||||
"a": "av",
|
||||
})
|
||||
|
||||
@@ -91,7 +90,7 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
"b": "bv",
|
||||
})
|
||||
|
||||
c.Assert(cfg.Get("b"), qt.DeepEquals, maps.Params{
|
||||
c.Assert(cfg.Get("b"), qt.DeepEquals, hmaps.Params{
|
||||
"a": "av",
|
||||
"b": "bv",
|
||||
})
|
||||
@@ -105,7 +104,7 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
"b": "bv2",
|
||||
})
|
||||
|
||||
c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
|
||||
c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
|
||||
"a": "av2",
|
||||
"b": "bv2",
|
||||
})
|
||||
@@ -118,7 +117,7 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
"b": "bv2",
|
||||
})
|
||||
|
||||
c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
|
||||
c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
|
||||
"a": "av",
|
||||
"b": "bv2",
|
||||
})
|
||||
@@ -137,7 +136,7 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
c.Assert(cfg.Get("foo"), qt.DeepEquals, maps.Params{
|
||||
c.Assert(cfg.Get("foo"), qt.DeepEquals, hmaps.Params{
|
||||
"a": "av",
|
||||
"b": "bv2",
|
||||
})
|
||||
@@ -155,7 +154,7 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
"c": "cv2",
|
||||
})
|
||||
|
||||
c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
|
||||
c.Assert(cfg.Get("a"), qt.DeepEquals, hmaps.Params{
|
||||
"b": "bv",
|
||||
"c": "cv2",
|
||||
})
|
||||
@@ -169,7 +168,7 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
"b": "bv2",
|
||||
})
|
||||
|
||||
c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
|
||||
c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
|
||||
"a": "av",
|
||||
})
|
||||
})
|
||||
@@ -192,11 +191,11 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
"e": "ev2",
|
||||
})
|
||||
|
||||
c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
|
||||
c.Assert(cfg.Get("a"), qt.DeepEquals, hmaps.Params{
|
||||
"e": "ev2",
|
||||
"_merge": maps.ParamsMergeStrategyShallow,
|
||||
"_merge": hmaps.ParamsMergeStrategyShallow,
|
||||
"b": "bv",
|
||||
"c": maps.Params{
|
||||
"c": hmaps.Params{
|
||||
"b": "bv",
|
||||
},
|
||||
})
|
||||
@@ -221,15 +220,15 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
"b": left,
|
||||
})
|
||||
|
||||
cfg.Merge("", maps.Params{
|
||||
"b": maps.Params{
|
||||
cfg.Merge("", hmaps.Params{
|
||||
"b": hmaps.Params{
|
||||
"c": "cv2",
|
||||
"d": "dv2",
|
||||
},
|
||||
})
|
||||
|
||||
c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
|
||||
"b": maps.Params{
|
||||
c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
|
||||
"b": hmaps.Params{
|
||||
"c": "cv1",
|
||||
"d": "dv2",
|
||||
},
|
||||
@@ -267,8 +266,8 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
|
||||
cfg.Merge("a", right)
|
||||
|
||||
c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
|
||||
"a": maps.Params{
|
||||
c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
|
||||
"a": hmaps.Params{
|
||||
"b": "bv1",
|
||||
"c": "cv2",
|
||||
},
|
||||
@@ -292,7 +291,7 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
|
||||
c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
|
||||
"b": "bv",
|
||||
})
|
||||
})
|
||||
@@ -341,7 +340,7 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
return err
|
||||
}
|
||||
|
||||
m := maps.Params{
|
||||
m := hmaps.Params{
|
||||
"new": 42,
|
||||
}
|
||||
|
||||
@@ -371,8 +370,8 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
cfg := New()
|
||||
k := "foo"
|
||||
|
||||
cfg.Set(k, maps.Params{k: true})
|
||||
c.Assert(cfg.GetParams(k), qt.DeepEquals, maps.Params{
|
||||
cfg.Set(k, hmaps.Params{k: true})
|
||||
c.Assert(cfg.GetParams(k), qt.DeepEquals, hmaps.Params{
|
||||
k: true,
|
||||
})
|
||||
|
||||
@@ -384,8 +383,8 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
k := "foo"
|
||||
k2 := "bar"
|
||||
|
||||
cfg.Set(k, maps.Params{k: struct{}{}})
|
||||
cfg.Set(k2, maps.Params{k2: struct{}{}})
|
||||
cfg.Set(k, hmaps.Params{k: struct{}{}})
|
||||
cfg.Set(k2, hmaps.Params{k2: struct{}{}})
|
||||
|
||||
c.Assert(len(cfg.Keys()), qt.Equals, 2)
|
||||
|
||||
@@ -401,11 +400,11 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
c.Run("WalkParams", func(c *qt.C) {
|
||||
cfg := New()
|
||||
|
||||
cfg.Set("x", maps.Params{})
|
||||
cfg.Set("y", maps.Params{})
|
||||
cfg.Set("x", hmaps.Params{})
|
||||
cfg.Set("y", hmaps.Params{})
|
||||
|
||||
var got []string
|
||||
cfg.WalkParams(func(params ...maps.KeyParams) bool {
|
||||
cfg.WalkParams(func(params ...hmaps.KeyParams) bool {
|
||||
got = append(got, params[len(params)-1].Key)
|
||||
return false
|
||||
})
|
||||
@@ -417,7 +416,7 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
c.Assert(got, qt.DeepEquals, want)
|
||||
|
||||
cfg = New()
|
||||
cfg.WalkParams(func(params ...maps.KeyParams) bool {
|
||||
cfg.WalkParams(func(params ...hmaps.KeyParams) bool {
|
||||
return true
|
||||
})
|
||||
|
||||
@@ -429,7 +428,7 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
c.Run("SetDefaults", func(c *qt.C) {
|
||||
cfg := New()
|
||||
|
||||
cfg.SetDefaults(maps.Params{
|
||||
cfg.SetDefaults(hmaps.Params{
|
||||
"foo": "bar",
|
||||
"bar": "baz",
|
||||
})
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/hmaps"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
@@ -32,7 +32,7 @@ func TestNamespace(t *testing.T) {
|
||||
map[string]any{"foo": "bar"},
|
||||
func(v any) (*tstNsExt, any, error) {
|
||||
t := &tstNsExt{}
|
||||
m, err := maps.ToStringMapE(v)
|
||||
m, err := hmaps.ToStringMapE(v)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -17,11 +17,12 @@ package testconfig
|
||||
import (
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/hmaps"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/config/allconfig"
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/internal/warpc"
|
||||
toml "github.com/pelletier/go-toml/v2"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
@@ -60,6 +61,14 @@ func GetTestDeps(fs afero.Fs, cfg config.Provider, beforeInit ...func(*deps.Deps
|
||||
d := &deps.Deps{
|
||||
Conf: conf,
|
||||
Fs: hugofs.NewFrom(fs, conf.BaseConfig()),
|
||||
WasmDispatchers: warpc.AllDispatchers(
|
||||
warpc.Options{
|
||||
PoolSize: 1,
|
||||
},
|
||||
warpc.Options{
|
||||
PoolSize: 1,
|
||||
},
|
||||
),
|
||||
}
|
||||
for _, f := range beforeInit {
|
||||
f(d)
|
||||
@@ -75,7 +84,7 @@ func GetTestConfigSectionFromStruct(section string, v any) config.AllProvider {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
p := maps.Params{
|
||||
p := hmaps.Params{
|
||||
section: config.FromTOMLConfigString(string(data)).Get(""),
|
||||
}
|
||||
cfg := config.NewFrom(p)
|
||||
|
||||
Vendored
+25
-12
@@ -1,3 +1,16 @@
|
||||
// Copyright 2025 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package deps
|
||||
|
||||
import (
|
||||
@@ -15,8 +28,8 @@ import (
|
||||
"github.com/gohugoio/hugo/cache/dynacache"
|
||||
"github.com/gohugoio/hugo/cache/filecache"
|
||||
"github.com/gohugoio/hugo/common/hexec"
|
||||
"github.com/gohugoio/hugo/common/hmaps"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/config/allconfig"
|
||||
@@ -160,8 +173,8 @@ func (d *Deps) Init() error {
|
||||
d.BuildState.DeferredExecutionsGroupedByRenderingContext = make(map[tpl.RenderingContext]*DeferredExecutions)
|
||||
}
|
||||
d.BuildState.DeferredExecutions = &DeferredExecutions{
|
||||
Executions: maps.NewCache[string, *tpl.DeferredExecution](),
|
||||
FilenamesWithPostPrefix: maps.NewCache[string, bool](),
|
||||
Executions: hmaps.NewCache[string, *tpl.DeferredExecution](),
|
||||
FilenamesWithPostPrefix: hmaps.NewCache[string, bool](),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,12 +260,12 @@ func (d *Deps) Init() error {
|
||||
common = d.ResourceSpec.SpecCommon
|
||||
}
|
||||
|
||||
fileCaches, err := filecache.NewCaches(d.PathSpec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create file caches from configuration: %w", err)
|
||||
}
|
||||
d.Cfg.BaseConfig()
|
||||
|
||||
resourceSpec, err := resources.NewSpec(d.PathSpec, common, fileCaches, d.MemCache, d.BuildState, d.Log, d, d.ExecHelper, d.BuildClosers, d.BuildState)
|
||||
fileCaches := d.Cfg.FileCaches().(filecache.Caches)
|
||||
fileCaches.SetResourceFs(d.BaseFs.ResourcesCache)
|
||||
|
||||
resourceSpec, err := resources.NewSpec(d.PathSpec, common, d.WasmDispatchers, fileCaches, d.MemCache, d.BuildState, d.Log, d, d.ExecHelper, d.BuildClosers, d.BuildState)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create resource spec: %w", err)
|
||||
}
|
||||
@@ -451,10 +464,10 @@ type Counters struct {
|
||||
type DeferredExecutions struct {
|
||||
// A set of filenames in /public that
|
||||
// contains a post-processing prefix.
|
||||
FilenamesWithPostPrefix *maps.Cache[string, bool]
|
||||
FilenamesWithPostPrefix *hmaps.Cache[string, bool]
|
||||
|
||||
// Maps a placeholder to a deferred execution.
|
||||
Executions *maps.Cache[string, *tpl.DeferredExecution]
|
||||
Executions *hmaps.Cache[string, *tpl.DeferredExecution]
|
||||
}
|
||||
|
||||
var _ identity.SignalRebuilder = (*BuildState)(nil)
|
||||
@@ -467,8 +480,8 @@ func (b *BuildState) StartStageRender(stage tpl.RenderingContext) {
|
||||
func (b *BuildState) StopStageRender(stage tpl.RenderingContext) {
|
||||
b.DeferredExecutionsGroupedByRenderingContext[stage] = b.DeferredExecutions
|
||||
b.DeferredExecutions = &DeferredExecutions{
|
||||
Executions: maps.NewCache[string, *tpl.DeferredExecution](),
|
||||
FilenamesWithPostPrefix: maps.NewCache[string, bool](),
|
||||
Executions: hmaps.NewCache[string, *tpl.DeferredExecution](),
|
||||
FilenamesWithPostPrefix: hmaps.NewCache[string, bool](),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-3
@@ -23,7 +23,8 @@
|
||||
"**/emojis.md",
|
||||
"**/commands/*",
|
||||
"**/showcase/*",
|
||||
"**/tools/*"
|
||||
"**/tools/*",
|
||||
"data/docs.yaml"
|
||||
],
|
||||
"ignoreRegExpList": [
|
||||
"# cspell: ignore fenced code blocks",
|
||||
@@ -59,12 +60,14 @@
|
||||
"marshal",
|
||||
"marshaling",
|
||||
"multihost",
|
||||
"multiplatfom",
|
||||
"multiplatform",
|
||||
"performantly",
|
||||
"preconfigured",
|
||||
"prerendering",
|
||||
"redirection",
|
||||
"redirections",
|
||||
"slugified",
|
||||
"slugify",
|
||||
"subexpression",
|
||||
"suppressible",
|
||||
"synchronisation",
|
||||
@@ -113,7 +116,6 @@
|
||||
"libros",
|
||||
"mercredi",
|
||||
"miesiąc",
|
||||
"miesiąc",
|
||||
"miesiąca",
|
||||
"miesiące",
|
||||
"miesięcy",
|
||||
@@ -144,6 +146,7 @@
|
||||
"Samsa",
|
||||
"Stucki",
|
||||
"Thénardier",
|
||||
"Vitter",
|
||||
"WASI",
|
||||
"# ----------------------------------------------------------------------",
|
||||
"# cspell: ignore operating systems and software packages",
|
||||
|
||||
@@ -77,7 +77,7 @@ config:
|
||||
full: true
|
||||
inline: true
|
||||
shortcut: true
|
||||
url_inline: true
|
||||
url_inline: false
|
||||
MD055:
|
||||
style: consistent
|
||||
MD056: true
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
**/news/**
|
||||
**/showcase/**
|
||||
**/zh/**
|
||||
@@ -129,3 +129,8 @@ body {
|
||||
text-decoration: none;
|
||||
padding-left: .0625em;
|
||||
}
|
||||
|
||||
/* Code spans within paragraphs, tables cells, list items, etc. */
|
||||
:not(pre) > code {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 13 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 13 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 13 KiB |
@@ -1,6 +0,0 @@
|
||||
import { initColorScheme } from './alpinejs/stores/index';
|
||||
|
||||
(function () {
|
||||
// This allows us to initialize the color scheme before AlpineJS etc. is loaded.
|
||||
initColorScheme();
|
||||
})();
|
||||
@@ -1,6 +1,10 @@
|
||||
import { scrollToActive } from 'js/helpers/index';
|
||||
import { initColorScheme } from './alpinejs/stores/index';
|
||||
|
||||
(function () {
|
||||
// This allows us to initialize the color scheme before AlpineJS etc. is loaded.
|
||||
initColorScheme();
|
||||
|
||||
// Now we know that the browser has JS enabled.
|
||||
document.documentElement.classList.remove('no-js');
|
||||
|
||||
|
||||
@@ -8,14 +8,6 @@ import focus from '@alpinejs/focus';
|
||||
|
||||
var debug = 0 ? console.log.bind(console, '[index]') : function () {};
|
||||
|
||||
// Turbolinks init.
|
||||
(function () {
|
||||
document.addEventListener('turbo:render', function (e) {
|
||||
// This is also called right after the body start. This is added to prevent flicker on navigation.
|
||||
initColorScheme();
|
||||
});
|
||||
})();
|
||||
|
||||
// Set up and start Alpine.
|
||||
(function () {
|
||||
// Register AlpineJS plugins.
|
||||
|
||||
@@ -29,6 +29,7 @@ Please refer to the relevant documentation for installation instructions:
|
||||
|
||||
[cloudcannon]: https://cloudcannon.com/
|
||||
[cloudflare pages]: https://pages.cloudflare.com/
|
||||
[commit information]: /methods/page/GitInfo
|
||||
[dart sass install]: /functions/css/sass/#dart-sass
|
||||
[dart sass]: https://sass-lang.com/dart-sass
|
||||
[git install]: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
|
||||
@@ -37,4 +38,5 @@ Please refer to the relevant documentation for installation instructions:
|
||||
[gitlab pages]: https://docs.gitlab.com/ee/user/project/pages/
|
||||
[go install]: https://go.dev/doc/install
|
||||
[go]: https://go.dev/
|
||||
[hugo modules]: /hugo-modules/
|
||||
[netlify]: https://www.netlify.com/
|
||||
|
||||
@@ -15,3 +15,5 @@ Prebuilt binaries are available for a variety of operating systems and architect
|
||||
Please consult your operating system documentation if you need help setting file permissions or modifying your PATH environment variable.
|
||||
|
||||
If you do not see a prebuilt binary for the desired edition, operating system, and architecture, install Hugo using one of the methods described below.
|
||||
|
||||
[latest release]: https://github.com/gohugoio/hugo/releases/latest
|
||||
|
||||
@@ -7,7 +7,7 @@ _comment: Do not remove front matter.
|
||||
To build the extended or extended/deploy edition from source you must:
|
||||
|
||||
1. Install [Git]
|
||||
1. Install [Go] version 1.23.0 or later
|
||||
1. Install [Go] version 1.24.0 or later
|
||||
1. Install a C compiler, either [GCC] or [Clang]
|
||||
1. Update your `PATH` environment variable as described in the [Go documentation]
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ _comment: Do not remove front matter.
|
||||
|
||||
### Homebrew
|
||||
|
||||
[Homebrew] is a free and open-source package manager for macOS and Linux. To install the extended edition of Hugo:
|
||||
[Homebrew] is a free and open-source package manager for macOS and Linux. To install the extended/deploy edition of Hugo:
|
||||
|
||||
```sh
|
||||
brew install hugo
|
||||
|
||||
@@ -27,12 +27,14 @@ _comment: Do not remove front matter.
|
||||
: The content's section.
|
||||
|
||||
`:sectionslug`
|
||||
: {{< new-in 0.149.0 />}}
|
||||
: The content's section using slugified section name. The slugified section name is the `slug` as defined in front matter, else the `title` as defined in front matter, else the automatic title.
|
||||
|
||||
`:sections`
|
||||
: The content's sections hierarchy. You can use a selection of the sections using _slice syntax_: `:sections[1:]` includes all but the first, `:sections[:last]` includes all but the last, `:sections[last]` includes only the last, `:sections[1:2]` includes section 2 and 3. Note that this slice access will not throw any out-of-bounds errors, so you don't have to be exact.
|
||||
|
||||
`:sectionslugs`
|
||||
: {{< new-in 0.149.0 />}}
|
||||
: The content's sections hierarchy using slugified section names. The slugified section name is the `slug` as defined in front matter, else the `title` as defined in front matter, else the automatic title. You can use a selection of the sections using _slice syntax_: `:sectionslugs[1:]` includes all but the first, `:sectionslugs[:last]` includes all but the last, `:sectionslugs[last]` includes only the last, `:sectionslugs[1:2]` includes section 2 and 3. Note that this slice access will not throw any out-of-bounds errors, so you don't have to be exact.
|
||||
|
||||
`:title`
|
||||
|
||||
@@ -43,5 +43,5 @@ As a practical example, Hugo's embedded link and image render hooks use the `Pag
|
||||
|
||||
[`RenderShortcodes`]: /methods/page/rendershortcodes/
|
||||
[Markdown notation]: /content-management/shortcodes/#notation
|
||||
[Embedded link render hook]: {{% eturl render-link %}}
|
||||
[Embedded image render hook]: {{% eturl render-image %}}
|
||||
[Embedded link render hook]: <{{% eturl render-link %}}>
|
||||
[Embedded image render hook]: <{{% eturl render-image %}}>
|
||||
|
||||
@@ -28,7 +28,7 @@ Learn more about Hugo's [features], [privacy protections], and [security model].
|
||||
[Hugo Modules]: /hugo-modules/
|
||||
[static site generator]: https://en.wikipedia.org/wiki/Static_site_generator
|
||||
[features]: /about/features/
|
||||
[security model]: about/security/
|
||||
[security model]: /about/security/
|
||||
[privacy protections]: /configuration/privacy
|
||||
|
||||
{{< youtube 0RKpf3rK57I >}}
|
||||
|
||||
@@ -24,6 +24,8 @@ hugo gen chromastyles [flags] [args]
|
||||
--highlightStyle string foreground and background colors for highlighted lines, e.g. --highlightStyle "#fff000 bg:#000fff"
|
||||
--lineNumbersInlineStyle string foreground and background colors for inline line numbers, e.g. --lineNumbersInlineStyle "#fff000 bg:#000fff"
|
||||
--lineNumbersTableStyle string foreground and background colors for table line numbers, e.g. --lineNumbersTableStyle "#fff000 bg:#000fff"
|
||||
--omitClassComments omit CSS class comment prefixes in the generated CSS
|
||||
--omitEmpty omit empty CSS rules (deprecated, no longer needed)
|
||||
--style string highlighter style (see https://xyproto.github.io/splash/docs/) (default "friendly")
|
||||
```
|
||||
|
||||
|
||||
@@ -45,6 +45,6 @@ Ensure you run this within the root directory of your site.
|
||||
|
||||
* [hugo](/commands/hugo/) - Build your site
|
||||
* [hugo new content](/commands/hugo_new_content/) - Create new content
|
||||
* [hugo new site](/commands/hugo_new_site/) - Create a new site (skeleton)
|
||||
* [hugo new theme](/commands/hugo_new_theme/) - Create a new theme (skeleton)
|
||||
* [hugo new site](/commands/hugo_new_site/) - Create a new site
|
||||
* [hugo new theme](/commands/hugo_new_theme/) - Create a new theme
|
||||
|
||||
|
||||
@@ -5,13 +5,11 @@ url: /commands/hugo_new_site/
|
||||
---
|
||||
## hugo new site
|
||||
|
||||
Create a new site (skeleton)
|
||||
Create a new site
|
||||
|
||||
### Synopsis
|
||||
|
||||
Create a new site in the provided directory.
|
||||
The new site will have the correct structure, but no content or theme yet.
|
||||
Use `hugo new [contentPath]` to create new content.
|
||||
Create a new site at the specified path.
|
||||
|
||||
```
|
||||
hugo new site [path] [flags]
|
||||
|
||||
@@ -5,14 +5,12 @@ url: /commands/hugo_new_theme/
|
||||
---
|
||||
## hugo new theme
|
||||
|
||||
Create a new theme (skeleton)
|
||||
Create a new theme
|
||||
|
||||
### Synopsis
|
||||
|
||||
Create a new theme (skeleton) called [name] in ./themes.
|
||||
New theme is a skeleton. Please add content to the touched files. Add your
|
||||
name to the copyright line in the license and adjust the theme.toml file
|
||||
according to your needs.
|
||||
Create a new theme with the specified name in the ./themes directory.
|
||||
This generates a functional theme including template examples and sample content.
|
||||
|
||||
```
|
||||
hugo new theme [name] [flags]
|
||||
@@ -21,7 +19,8 @@ hugo new theme [name] [flags]
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for theme
|
||||
--format string preferred file format (toml, yaml or json) (default "toml")
|
||||
-h, --help help for theme
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -47,7 +47,7 @@ cascade
|
||||
: See [configure cascade](/configuration/cascade/).
|
||||
|
||||
cleanDestinationDir
|
||||
: (`bool`) Whether to remove files from the site's destination directory that do not have corresponding files in the `static` directory during the build. Default is `false`.
|
||||
: (`bool`) Whether to remove files from the [`publishDir`](#publishdir) that do not exist in the [`staticDir`](#staticdir) when building the site. This setting will not take effect if the `staticDir` does not exist. Note that `.gitignore` and `.gitattributes` files, along with directories named `.git`, are always preserved in the `publishDir`. Default is `false`.
|
||||
|
||||
contentDir
|
||||
: (`string`) The designated directory for content files. Default is `content`. {{% module-mounts-note %}}
|
||||
@@ -262,7 +262,7 @@ staticDir
|
||||
: (`string`) The designated directory for static files. Default is `static`. {{% module-mounts-note %}}
|
||||
|
||||
summaryLength
|
||||
: (`int`) Applicable to [automatic summaries], the minimum number of words returned by the [`Summary`] method on a `Page` object. The `Summary` method will return content truncated at the paragraph boundary closest to the specified `summaryLength`, but at least this minimum number of words.
|
||||
: (`int`) Applicable to [automatic summaries], the minimum number of words returned by the [`Summary`] method on a `Page` object. The `Summary` method will return content truncated at the paragraph boundary closest to the specified `summaryLength`, but at least this minimum number of words. Default is `70`.
|
||||
|
||||
taxonomies
|
||||
: See [configure taxonomies](/configuration/taxonomies/).
|
||||
@@ -350,9 +350,9 @@ Some configuration settings, such as menus and custom parameters, can be defined
|
||||
[Chicago Manual of Style]: https://www.chicagomanualofstyle.org/home.html
|
||||
[default front matter configuration]: /configuration/front-matter/
|
||||
[duration]: https://pkg.go.dev/time#Duration
|
||||
[embedded alias template]: {{% eturl alias %}}
|
||||
[embedded Open Graph template]: {{% eturl opengraph %}}
|
||||
[embedded RSS template]: {{% eturl rss %}}
|
||||
[embedded alias template]: <{{% eturl alias %}}>
|
||||
[embedded Open Graph template]: <{{% eturl opengraph %}}>
|
||||
[embedded RSS template]: <{{% eturl rss %}}>
|
||||
[IANA Time Zone Database]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
[module mounts]: /configuration/module/#mounts
|
||||
[os.UserCacheDir]: https://pkg.go.dev/os#UserCacheDir
|
||||
|
||||
@@ -14,7 +14,6 @@ You can configure your site to cascade front matter values to the home page and
|
||||
For example, to cascade a "color" parameter to the home page and all its descendants:
|
||||
|
||||
{{< code-toggle file=hugo >}}
|
||||
title = 'Home'
|
||||
[cascade.params]
|
||||
color = 'red'
|
||||
{{< /code-toggle >}}
|
||||
@@ -61,14 +60,14 @@ Define an array of cascade parameters to apply different values to different tar
|
||||
[cascade.params]
|
||||
color = 'red'
|
||||
[cascade.target]
|
||||
path = '{/books/**}'
|
||||
path = '/books/**'
|
||||
kind = 'page'
|
||||
lang = '{en,de}'
|
||||
[[cascade]]
|
||||
[cascade.params]
|
||||
color = 'blue'
|
||||
[cascade.target]
|
||||
path = '{/films/**}'
|
||||
path = '/films/**'
|
||||
kind = 'page'
|
||||
environment = 'production'
|
||||
{{< /code-toggle >}}
|
||||
|
||||
@@ -90,10 +90,11 @@ Consider this site configuration:
|
||||
{{< code-toggle file=hugo >}}
|
||||
[frontmatter]
|
||||
date = [':filename', ':default']
|
||||
publishDate = [':filename', ':default']
|
||||
lastmod = ['lastmod', ':fileModTime']
|
||||
{{< /code-toggle >}}
|
||||
|
||||
To determine `date`, Hugo tries to extract the date from the file name, falling back to the default ordered sequence of date fields.
|
||||
To determine `date` and `publishDate`, Hugo tries to extract the value from the file name, falling back to the default ordered sequence of date fields.
|
||||
|
||||
To determine `lastmod`, Hugo looks for a `lastmod` field in front matter, falling back to the file's last modification timestamp.
|
||||
|
||||
|
||||
@@ -44,20 +44,42 @@ The HTTP cache involves two key aspects: determining which content to cache (the
|
||||
|
||||
The HTTP cache behavior is defined for a configured set of resources. Stale resources will be refreshed from the file cache, even if their configured Time-To-Live (TTL) has not expired. If HTTP caching is disabled for a resource, Hugo will bypass the cache and access the file directly.
|
||||
|
||||
The default configuration disables everything:
|
||||
This is the default configuration for HTTP caching:
|
||||
|
||||
{{< code-toggle file=hugo >}}
|
||||
[HTTPCache.cache.for]
|
||||
excludes = ['**']
|
||||
includes = []
|
||||
{{< /code-toggle >}}
|
||||
{{< code-toggle config=HTTPCache />}}
|
||||
|
||||
respectCacheControlNoStoreInRequest
|
||||
: {{< new-in 0.151.0 />}}
|
||||
: (`bool`) Whether to respect the `no-store` directive in the server's `Cache-Control` request header when fetching remote resources via the [`resources.GetRemote`][] function. Default is `true`.
|
||||
|
||||
respectCacheControlNoStoreInResponse
|
||||
: {{< new-in 0.151.0 />}}
|
||||
: (`bool`) Whether to respect the `no-store` directive in the server's `Cache-Control` response header when fetching remote resources via the [`resources.GetRemote`][] function. Default is `false`.
|
||||
|
||||
cache.for.excludes
|
||||
: (`string`) A list of [glob](g) patterns to exclude from caching.
|
||||
: (`string`) A list of [glob](g) patterns to exclude from caching. In its default configuration HTTP caching excludes all files.
|
||||
|
||||
cache.for.includes
|
||||
: (`string`) A list of [glob](g) patterns to cache.
|
||||
|
||||
polls
|
||||
: A slice of polling configurations.
|
||||
|
||||
polls.disable
|
||||
: (`bool`) Whether to disable polling for this configuration. Default is `true`.
|
||||
|
||||
polls.high
|
||||
: (`string`) The maximum polling interval expressed as a [duration](g). This is used when the resource is considered stable. Default is `0s`.
|
||||
|
||||
polls.low
|
||||
: (`string`) The minimum polling interval expressed as a [duration](g). This is used after a recent change and gradually increases towards `polls.high`. Default is `0s`.
|
||||
|
||||
polls.for.excludes
|
||||
: (`string`) A list of [glob](g) patterns to exclude from polling for this configuration.
|
||||
|
||||
polls.for.includes
|
||||
: (`string`) A list of [glob](g) patterns to include in polling for this configuration.
|
||||
|
||||
## HTTP polling
|
||||
|
||||
Polling is used in watch mode (e.g., `hugo server`) to detect changes in remote resources. Polling can be enabled even if HTTP caching is disabled. Detected changes trigger a rebuild of pages using the affected resource. Polling can be disabled for specific resources, typically those known to be static.
|
||||
@@ -78,13 +100,13 @@ polls
|
||||
: A slice of polling configurations.
|
||||
|
||||
polls.disable
|
||||
: (`bool`) Whether to disable polling for this configuration.
|
||||
|
||||
polls.low
|
||||
: (`string`) The minimum polling interval expressed as a [duration](g). This is used after a recent change and gradually increases towards `polls.high`.
|
||||
: (`bool`) Whether to disable polling for this configuration. Default is `true`.
|
||||
|
||||
polls.high
|
||||
: (`string`) The maximum polling interval expressed as a [duration](g). This is used when the resource is considered stable.
|
||||
: (`string`) The maximum polling interval expressed as a [duration](g). This is used when the resource is considered stable. Default is `0s`.
|
||||
|
||||
polls.low
|
||||
: (`string`) The minimum polling interval expressed as a [duration](g). This is used after a recent change and gradually increases towards `polls.high`. Default is `0s`.
|
||||
|
||||
polls.for.excludes
|
||||
: (`string`) A list of [glob](g) patterns to exclude from polling for this configuration.
|
||||
|
||||
@@ -185,9 +185,9 @@ public
|
||||
[`Language.LanguageName`]: /methods/site/language/#languagename
|
||||
[`Language.Weight`]: /methods/site/language/#weight
|
||||
[`Title`]: /methods/site/title/
|
||||
[embedded alias template]: {{% eturl alias %}}
|
||||
[embedded OpenGraph template]: {{% eturl opengraph %}}
|
||||
[embedded RSS template]: {{% eturl rss %}}
|
||||
[embedded alias template]: <{{% eturl alias %}}>
|
||||
[embedded OpenGraph template]: <{{% eturl opengraph %}}>
|
||||
[embedded RSS template]: <{{% eturl rss %}}>
|
||||
[RFC 5646]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.1
|
||||
[RFC 5646 § 2.2.7]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.2.7
|
||||
[translating by file name]: /content-management/multilingual/#translation-by-file-name
|
||||
|
||||
@@ -94,9 +94,23 @@ enable = true
|
||||
|
||||
With this configuration, to format text as deleted, wrap it with double-tildes.
|
||||
|
||||
#### Passthrough
|
||||
#### Footnote
|
||||
|
||||
{{< new-in 0.122.0 />}}
|
||||
Enabled by default, the Footnote extension enables inclusion of footnotes in Markdown.
|
||||
|
||||
enable
|
||||
: {{< new-in 0.151.0 />}}
|
||||
: (`bool`) Whether to enable the Footnotes extension. Default is `true`.
|
||||
|
||||
backlinkHTML
|
||||
: {{< new-in 0.151.0 />}}
|
||||
: (`string`) The HTML to be displayed at the end of a footnote that links the user back to the corresponding reference in the main text. The default is ↩︎ (a return arrow symbol).
|
||||
|
||||
enableAutoIDPrefix
|
||||
: {{< new-in 0.151.0 />}}
|
||||
: (`bool`) Whether to prepend a unique prefix to footnote IDs, preventing clashes when multiple documents are rendered together. This prefix is unique to each logical path, which means that the prefix is not unique across content dimensions such as language. Default is `false`.
|
||||
|
||||
#### Passthrough
|
||||
|
||||
Enable the Passthrough extension to include mathematical equations and expressions in Markdown using LaTeX markup. See [mathematics in Markdown] for details.
|
||||
|
||||
@@ -202,7 +216,7 @@ backend
|
||||
: (`string`) The backend output file format. Default is `html5`.
|
||||
|
||||
extensions
|
||||
: (`string array`) An array of enabled extensions, one or more of `asciidoctor-html5s`, `asciidoctor-bibtex`, `asciidoctor-diagram`, `asciidoctor-interdoc-reftext`, `asciidoctor-katex`, `asciidoctor-latex`, `asciidoctor-mathematical`, or `asciidoctor-question`.
|
||||
: (`string array`) An array of enabled extensions, such as `asciidoctor-html5s`, `asciidoctor-bibtex`, or `asciidoctor-diagram`.
|
||||
|
||||
> [!note]
|
||||
> To mitigate security risks, entries in the extension array may not contain forward slashes (`/`), backslashes (`\`), or periods. Due to this restriction, extensions must be in Ruby's `$LOAD_PATH`.
|
||||
|
||||
@@ -10,6 +10,11 @@ This is the default configuration:
|
||||
|
||||
{{< code-toggle config=minify />}}
|
||||
|
||||
See the [tdewolff/minify] project page for details.
|
||||
See the [tdewolff/minify] project page for details, but note the following:
|
||||
|
||||
- `css.inline` is for internal use. Changing this setting has no effect.
|
||||
- `css.keepCSS2` has been deprecated. Use `css.version` instead.
|
||||
- `html.keepConditionalComments` has been deprecated. Use `html.keepSpecialComments` instead.
|
||||
- `svg.inline` is for internal use. Changing this setting has no effect.
|
||||
|
||||
[tdewolff/minify]: https://github.com/tdewolff/minify
|
||||
|
||||
@@ -7,6 +7,8 @@ keywords: []
|
||||
aliases: [/hugo-modules/configuration/]
|
||||
---
|
||||
|
||||
{{% include "/_common/gomodules-info.md" %}}
|
||||
|
||||
## Top-level options
|
||||
|
||||
This is the default configuration:
|
||||
@@ -40,7 +42,7 @@ proxy
|
||||
: (`string`) The proxy server to use to download remote modules. Default is `direct`, which means `git clone` and similar.
|
||||
|
||||
replacements
|
||||
: (`string`) Primarily useful for local module development, a comma-separated list of mappings from module paths to directories. Paths may be absolute or relative to the [`themesDir`].
|
||||
: (`string`) Primarily useful for local module development, a comma-separated list of mappings from module paths to directories. Paths may be absolute or relative to the [`themesDir`][].
|
||||
|
||||
{{< code-toggle file=hugo >}}
|
||||
[module]
|
||||
@@ -61,8 +63,6 @@ export HUGO_MODULE_REPLACEMENTS="github.com/bep/my-theme -> ../.."
|
||||
export HUGO_MODULE_WORKSPACE="/my/hugo.work"
|
||||
```
|
||||
|
||||
{{% include "/_common/gomodules-info.md" %}}
|
||||
|
||||
## Hugo version
|
||||
|
||||
You can specify a required Hugo version for your module in the `module` section. Users will then receive a warning if their Hugo version is incompatible.
|
||||
@@ -77,7 +77,7 @@ extended
|
||||
: (`bool`) Whether the extended edition of Hugo is required, satisfied by installing either the extended or extended/deploy edition.
|
||||
|
||||
max
|
||||
: (`string`) The maximum Hugo version supported, for example `0.148.0`.
|
||||
: (`string`) The maximum Hugo version supported, for example `0.152.2`.
|
||||
|
||||
min
|
||||
: (`string`) The minimum Hugo version supported, for example `0.102.0`.
|
||||
@@ -110,32 +110,36 @@ noVendor
|
||||
: (`bool`) Whether to disable vendoring for this import. This setting is restricted to the main project. Default is `false`.
|
||||
|
||||
path
|
||||
: (`string`) The module path, either a valid Go module path (e.g., `github.com/gohugoio/myShortcodes`) or the directory name if stored in the [`themesDir`].
|
||||
: (`string`) The module path, either a valid Go module path (e.g., `github.com/gohugoio/myShortcodes`) or the directory name if stored in the [`themesDir`][].
|
||||
|
||||
{{% include "/_common/gomodules-info.md" %}}
|
||||
version
|
||||
: {{< new-in 0.150.0 />}}
|
||||
: If set to a [version query](https://go.dev/ref/mod#version-queries), this import becomes a direct dependency, in contrast to dependencies managed by Go Modules. See [this issue](https://github.com/gohugoio/hugo/pull/13966) for more information.
|
||||
|
||||
## Mounts
|
||||
|
||||
Before Hugo v0.56.0, custom component paths could only be configured by setting [`archetypeDir`], [`assetDir`], [`contentDir`], [`dataDir`], [`i18nDir`], [`layoutDi`], or [`staticDir`] in the site configuration. Module mounts offer greater flexibility than these legacy settings, but
|
||||
you cannot use both.
|
||||
{{% glossary-term mount %}}
|
||||
|
||||
[`archetypeDir`]: /configuration/all/
|
||||
[`assetDir`]: /configuration/all/
|
||||
[`contentDir`]: /configuration/all/
|
||||
[`dataDir`]: /configuration/all/
|
||||
[`i18nDir`]: /configuration/all/
|
||||
[`layoutDi`]: /configuration/all/
|
||||
[`staticDir`]: /configuration/all/
|
||||
> [!important]
|
||||
> If you define one or more mounts to map a file system path to a component path, do not use these legacy configuration settings: [`archetypeDir`][], [`assetDir`][], [`contentDir`][], [`dataDir`][], [`i18nDir`][], [`layoutDir`][], or [`staticDir`][].
|
||||
|
||||
> [!note]
|
||||
> If you use module mounts do not use the legacy settings.
|
||||
[`archetypeDir`]: /configuration/all/#archetypedir
|
||||
[`assetDir`]: /configuration/all/#assetdir
|
||||
[`contentDir`]: /configuration/all/#contentdir
|
||||
[`dataDir`]: /configuration/all/#datadir
|
||||
[`i18nDir`]: /configuration/all/#i18ndir
|
||||
[`layoutDir`]: /configuration/all/#layoutdir
|
||||
[`staticDir`]: /configuration/all/#staticdir
|
||||
|
||||
### Default mounts
|
||||
|
||||
> [!note]
|
||||
> Adding a new mount to a target root will cause the existing default mount for that root to be ignored. If you still need the default mount, you must explicitly add it along with the new mount.
|
||||
Within a project, if you define a mount to map a file system path to a component path, the corresponding default mount for that component will be removed. This action essentially overwrites the standard, automatic mapping for that specific component with your custom one.
|
||||
|
||||
The are the default mounts:
|
||||
Within a module, if you define a mount to map a file system path to a component path, all of the default mounts will be removed. Defining a mount at the module level is a more sweeping change, causing all default mappings within that module to be discarded.
|
||||
|
||||
In either case, if you still need one of the default mounts, you must explicitly add it along with the new mount. Because custom mounts override defaults, any necessary default mappings must be re-added manually after you introduce your custom configuration.
|
||||
|
||||
These are the default mounts:
|
||||
|
||||
{{< code-toggle config=module.mounts />}}
|
||||
|
||||
@@ -143,7 +147,7 @@ source
|
||||
: (`string`) The source directory of the mount. For the main project, this can be either project-relative or absolute. For other modules it must be project-relative.
|
||||
|
||||
target
|
||||
: (`string`) Where the mount will reside within Hugo's virtual file system. It must begin with one of Hugo's component directories: `archetypes`, `assets`, `content`, `data`, `i18n`, `layouts`, or `static`. For example, `content/blog`.
|
||||
: (`string`) Where the mount will reside within Hugo's [unified file system](g). It must begin with one of Hugo's [component](g) directories: archetypes, assets, content, data, i18n, layouts, or static. For example, content/blog.
|
||||
|
||||
disableWatch
|
||||
: {{< new-in 0.128.0 />}}
|
||||
|
||||
@@ -44,7 +44,7 @@ archetypes/
|
||||
|
||||
Hugo looks for archetypes in the `archetypes` directory in the root of your project, falling back to the `archetypes` directory in themes or installed modules. An archetype for a specific content type takes precedence over the default archetype.
|
||||
|
||||
For example, with this command:
|
||||
For example, if you have enabled a theme named `my-theme` and you run this command:
|
||||
|
||||
```sh
|
||||
hugo new content posts/my-first-post.md
|
||||
@@ -53,8 +53,8 @@ hugo new content posts/my-first-post.md
|
||||
The archetype lookup order is:
|
||||
|
||||
1. `archetypes/posts.md`
|
||||
1. `archetypes/default.md`
|
||||
1. `themes/my-theme/archetypes/posts.md`
|
||||
1. `archetypes/default.md`
|
||||
1. `themes/my-theme/archetypes/default.md`
|
||||
|
||||
If none of these exists, Hugo uses a built-in default archetype.
|
||||
|
||||
@@ -256,5 +256,5 @@ Created from <https://arthursonzogni.com/Diagon/#Tree>
|
||||
```
|
||||
|
||||
[code block render hook]: /render-hooks/code-blocks/
|
||||
[embedded code block render hook]: {{% eturl render-codeblock-goat %}}
|
||||
[embedded code block render hook]: <{{% eturl render-codeblock-goat %}}>
|
||||
[GoAT]: https://github.com/bep/goat
|
||||
|
||||
@@ -47,7 +47,7 @@ build
|
||||
: (`map`) A map of [build options].
|
||||
|
||||
cascade
|
||||
: (`map`) A map of front matter keys whose values are passed down to the page's descendants unless overwritten by self or a closer ancestor's cascade. See the [cascade] section for details.
|
||||
: (`map`) A map (or a slice of maps) of front matter keys whose values are passed down to the page's descendants unless overwritten by self or a closer ancestor's cascade. See the [cascade] section for details.
|
||||
|
||||
date
|
||||
: (`string`) The date associated with the page, typically the creation date. Note that the TOML format also supports unquoted date/time values. See the [dates](#dates) section for examples. Access this value from a template using the [`Date`] method on a `Page` object.
|
||||
@@ -322,18 +322,18 @@ To override the default time zone, set the [`timeZone`](/configuration/all/#time
|
||||
[`lastmod`]: /methods/page/date/
|
||||
[`layout`]: /methods/page/layout/
|
||||
[`linktitle`]: /methods/page/linktitle/
|
||||
[`opengraph.html`]: {{% eturl opengraph %}}
|
||||
[`opengraph.html`]: <{{% eturl opengraph %}}>
|
||||
[`Param`]: /methods/page/param/
|
||||
[`Params`]: /methods/page/params/
|
||||
[`publishdate`]: /methods/page/publishdate/
|
||||
[`readingtime`]: /methods/page/readingtime/
|
||||
[`schema.html`]: {{% eturl schema %}}
|
||||
[`schema.html`]: <{{% eturl schema %}}>
|
||||
[`sitemap`]: /methods/page/sitemap/
|
||||
[`slug`]: /methods/page/slug/
|
||||
[`Summary`]: /methods/page/summary/
|
||||
[`title`]: /methods/page/title/
|
||||
[`translationkey`]: /methods/page/translationkey/
|
||||
[`twitter_cards.html`]: {{% eturl twitter_cards %}}
|
||||
[`twitter_cards.html`]: <{{% eturl twitter_cards %}}>
|
||||
[`type`]: /methods/page/type/
|
||||
[`weight`]: /methods/page/weight/
|
||||
[`wordcount`]: /methods/page/wordcount/
|
||||
|
||||
@@ -105,8 +105,6 @@ The `image` resource implements the [`Process`], [`Resize`], [`Fit`], [`Fill`]
|
||||
|
||||
### Process
|
||||
|
||||
{{< new-in 0.119.0 />}}
|
||||
|
||||
> [!note]
|
||||
> The `Process` method is also available as a filter, which is more effective if you need to apply multiple filters to an image. See [Process filter](/functions/images/process).
|
||||
|
||||
|
||||
@@ -6,13 +6,11 @@ categories: []
|
||||
keywords: []
|
||||
---
|
||||
|
||||
{{< new-in 0.122.0 />}}
|
||||
|
||||
## Overview
|
||||
|
||||
Mathematical equations and expressions written in [LaTeX] are common in academic and scientific publications. Your browser typically renders this mathematical markup using an open-source JavaScript display engine such as [MathJax] or [KaTeX].
|
||||
Mathematical equations and expressions written in [LaTeX][] are common in academic and scientific publications. Your browser typically renders this mathematical markup using an open-source JavaScript display engine such as [MathJax][] or [KaTeX][].
|
||||
|
||||
For example, with this LaTeX markup:
|
||||
For example, this LaTeX markup:
|
||||
|
||||
```text
|
||||
\[
|
||||
@@ -23,7 +21,7 @@ JS(\hat{y} || y) &= \frac{1}{2}(KL(y||\frac{y+\hat{y}}{2}) + KL(\hat{y}||\frac{y
|
||||
\]
|
||||
```
|
||||
|
||||
The MathJax display engine renders this:
|
||||
Is rendered to:
|
||||
|
||||
\[
|
||||
\begin{aligned}
|
||||
@@ -37,7 +35,7 @@ Equations and expressions can be displayed inline with other text, or as standal
|
||||
Whether an equation or expression appears inline, or as a block, depends on the delimiters that surround the mathematical markup. Delimiters are defined in pairs, where each pair consists of an opening and closing delimiter. The opening and closing delimiters may be the same, or different.
|
||||
|
||||
> [!note]
|
||||
> You can configure Hugo to render mathematical markup on the client side using the MathJax or KaTeX display engine, or you can render the markup with the [`transform.ToMath`] function while building your site.
|
||||
> You can configure Hugo to render mathematical markup on the client side using the MathJax or KaTeX display engine, or you can render the markup with the [`transform.ToMath`][] function while building your site.
|
||||
>
|
||||
> The first approach is described below.
|
||||
|
||||
@@ -46,7 +44,7 @@ Whether an equation or expression appears inline, or as a block, depends on the
|
||||
Follow these instructions to include mathematical equations and expressions in your Markdown using LaTeX markup.
|
||||
|
||||
Step 1
|
||||
: Enable and configure the Goldmark [passthrough extension] in your site configuration. The passthrough extension preserves raw Markdown within delimited snippets of text, including the delimiters themselves.
|
||||
: Enable and configure the Goldmark [passthrough extension][] in your site configuration. The passthrough extension preserves raw Markdown within delimited snippets of text, including the delimiters themselves.
|
||||
|
||||
{{< code-toggle file=hugo copy=true >}}
|
||||
[markup.goldmark.extensions.passthrough]
|
||||
@@ -60,12 +58,12 @@ Step 1
|
||||
math = true
|
||||
{{< /code-toggle >}}
|
||||
|
||||
The configuration above enables mathematical rendering on every page unless you set the `math` parameter to `false` in front matter. To enable mathematical rendering as needed, set the `math` parameter to `false` in your site configuration, and set the `math` parameter to `true` in front matter. Use this parameter in your base template as shown in [Step 3](#step-3).
|
||||
The configuration above enables mathematical rendering on every page unless you set the `math` parameter to `false` in front matter. To enable mathematical rendering as needed, set the `math` parameter to `false` in your site configuration, and set the `math` parameter to `true` in front matter. Use this parameter in your base template as shown in [Step 3][].
|
||||
|
||||
> [!note]
|
||||
> The configuration above precludes the use of the `$...$` delimiter pair for inline equations. Although you can add this delimiter pair to the configuration and JavaScript, you must double-escape the `$` symbol when used outside of math contexts to avoid unintended formatting.
|
||||
>
|
||||
> See the [inline delimiters](#inline-delimiters) section for details.
|
||||
> See the [inline delimiters][] section for details.
|
||||
|
||||
To disable passthrough of inline snippets, omit the `inline` key from the configuration:
|
||||
|
||||
@@ -74,7 +72,7 @@ Step 1
|
||||
block = [['\[', '\]'], ['$$', '$$']]
|
||||
{{< /code-toggle >}}
|
||||
|
||||
You can define your own opening and closing delimiters, provided they match the delimiters that you set in [Step 2].
|
||||
You can define your own opening and closing delimiters, provided they match the delimiters that you set in [Step 2][].
|
||||
|
||||
{{< code-toggle file=hugo >}}
|
||||
[markup.goldmark.extensions.passthrough.delimiters]
|
||||
@@ -83,10 +81,11 @@ Step 1
|
||||
{{< /code-toggle >}}
|
||||
|
||||
Step 2
|
||||
: Create a _partial_ template to load MathJax or KaTeX. The example below loads MathJax, or you can use KaTeX as described in the [engines](#engines) section.
|
||||
: Create a _partial_ template to load MathJax or KaTeX. The example below loads MathJax, or you can use KaTeX as described in the [engines][] section.
|
||||
|
||||
```go-html-template {file="layouts/_partials/math.html" copy=true}
|
||||
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script>
|
||||
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@4/tex-mml-chtml.js"></script>
|
||||
|
||||
<script>
|
||||
MathJax = {
|
||||
tex: {
|
||||
@@ -165,35 +164,35 @@ I will give you \\$2 if you can solve $y = x^2$.
|
||||
```
|
||||
|
||||
> [!note]
|
||||
> If you use the `$...$` delimiter pair for inline equations, and occasionally use the `$` symbol outside of math contexts, you must use MathJax instead of KaTeX to avoid unintended formatting caused by [this KaTeX limitation](https://github.com/KaTeX/KaTeX/issues/437).
|
||||
> If you use the `$...$` delimiter pair for inline equations, and occasionally use the `$` symbol outside of math contexts, you must use MathJax instead of KaTeX to avoid unintended formatting caused by [this KaTeX limitation][].
|
||||
|
||||
## Engines
|
||||
|
||||
MathJax and KaTeX are open-source JavaScript display engines. Both engines are fast, but at the time of this writing MathJax v3.2.2 is slightly faster than KaTeX v0.16.11.
|
||||
MathJax and KaTeX are open-source JavaScript display engines.
|
||||
|
||||
> [!note]
|
||||
> If you use the `$...$` delimiter pair for inline equations, and occasionally use the `$` symbol outside of math contexts, you must use MathJax instead of KaTeX to avoid unintended formatting caused by [this KaTeX limitation](https://github.com/KaTeX/KaTeX/issues/437).
|
||||
> If you use the `$...$` delimiter pair for inline equations, and occasionally use the `$` symbol outside of math contexts, you must use MathJax instead of KaTeX to avoid unintended formatting caused by [this KaTeX limitation][].
|
||||
>
|
||||
>See the [inline delimiters](#inline-delimiters) section for details.
|
||||
>See the [inline delimiters][] section for details.
|
||||
|
||||
To use KaTeX instead of MathJax, replace the _partial_ template from [Step 2] with this:
|
||||
To use KaTeX instead of MathJax, replace the _partial_ template from [Step 2][] with this:
|
||||
|
||||
```go-html-template {file="layouts/_partials/math.html" copy=true}
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.css"
|
||||
integrity="sha384-zh0CIslj+VczCZtlzBcjt5ppRcsAmDnRem7ESsYwWwg3m/OaJ2l4x7YBZl9Kxxib"
|
||||
href="https://cdn.jsdelivr.net/npm/katex@0.16.25/dist/katex.min.css"
|
||||
integrity="sha384-WcoG4HRXMzYzfCgiyfrySxx90XSl2rxY5mnVY5TwtWE6KLrArNKn0T/mOgNL0Mmi"
|
||||
crossorigin="anonymous"
|
||||
>
|
||||
<script
|
||||
defer
|
||||
src="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.js"
|
||||
integrity="sha384-Rma6DA2IPUwhNxmrB/7S3Tno0YY7sFu9WSYMCuulLhIqYSGZ2gKCJWIqhBWqMQfh"
|
||||
src="https://cdn.jsdelivr.net/npm/katex@0.16.25/dist/katex.min.js"
|
||||
integrity="sha384-J+9dG2KMoiR9hqcFao0IBLwxt6zpcyN68IgwzsCSkbreXUjmNVRhPFTssqdSGjwQ"
|
||||
crossorigin="anonymous">
|
||||
</script>
|
||||
<script
|
||||
defer
|
||||
src="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/contrib/auto-render.min.js"
|
||||
src="https://cdn.jsdelivr.net/npm/katex@0.16.25/dist/contrib/auto-render.min.js"
|
||||
integrity="sha384-hCXGrW6PitJEwbkoStFjeJxv+fSOOQKOPbJxSfM6G5sWZjAyWhXiTIIAmQqnlLlh"
|
||||
crossorigin="anonymous"
|
||||
onload="renderMathInElement(document.body);">
|
||||
@@ -224,10 +223,15 @@ $$C_p[\ce{H2O(l)}] = \pu{75.3 J // mol K}$$
|
||||
|
||||
$$C_p[\ce{H2O(l)}] = \pu{75.3 J // mol K}$$
|
||||
|
||||
As shown in [Step 2](#step-2) above, MathJax supports chemical equations without additional configuration. To add chemistry support to KaTeX, enable the mhchem extension as described in the KaTeX [documentation](https://katex.org/docs/libs).
|
||||
As shown in [Step 2][] above, MathJax supports chemical equations without additional configuration. To add chemistry support to KaTeX, enable the mhchem extension as described in the KaTeX [documentation](https://katex.org/docs/libs).
|
||||
|
||||
[`transform.ToMath`]: /functions/transform/tomath/
|
||||
[engines]: #engines
|
||||
[inline delimiters]: #inline-delimiters
|
||||
[KaTeX]: https://katex.org/
|
||||
[LaTeX]: https://www.latex-project.org/
|
||||
[MathJax]: https://www.mathjax.org/
|
||||
[passthrough extension]: /configuration/markup/#passthrough
|
||||
[Step 2]: #step-2
|
||||
[Step 3]: #step-3
|
||||
[this KaTeX limitation]: https://github.com/KaTeX/KaTeX/issues/437
|
||||
|
||||
@@ -9,7 +9,7 @@ aliases: [/content/sections/]
|
||||
|
||||
## Page bundles
|
||||
|
||||
Hugo `0.32` announced page-relative images and other resources packaged into `Page Bundles`.
|
||||
Hugo supports page-relative images and other resources packaged into `Page Bundles`.
|
||||
|
||||
These terms are connected, and you also need to read about [Page Resources](/content-management/page-resources) and [Image Processing](/content-management/image-processing) to get the full picture.
|
||||
|
||||
|
||||
@@ -33,6 +33,35 @@ This is the first paragraph.
|
||||
This is the second paragraph.
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Place the summary divider on its own line. Do not place it inline with other content.
|
||||
|
||||
Correct placement:
|
||||
|
||||
```text {file="content/example.md"}
|
||||
---
|
||||
title: 'Example'
|
||||
---
|
||||
|
||||
This is an example of **strong text** in a sentence. This is another sentence.
|
||||
|
||||
<!--more-->
|
||||
|
||||
This is another paragraph.
|
||||
```
|
||||
|
||||
Incorrect placement:
|
||||
|
||||
```text {file="content/example.md"}
|
||||
---
|
||||
title: 'Example'
|
||||
---
|
||||
|
||||
This is an example of **strong text** <!--more--> in a sentence. This is another sentence.
|
||||
|
||||
This is another paragraph.
|
||||
```
|
||||
|
||||
When using the Emacs Org Mode [content format], use a `# more` divider to indicate the end of the summary.
|
||||
|
||||
[content format]: /content-management/formats/
|
||||
@@ -79,6 +108,9 @@ For example, with a `summaryLength` of 7, the automatic summary will be:
|
||||
<p>This is the second paragraph.</p>
|
||||
```
|
||||
|
||||
> [!warning]
|
||||
> Automatic `.Summary` may cut block tags (e.g., `blockquote`) in the middle when `summaryLength` is reached, causing the browser to recover the end tag (the end tag will be inserted before the parent's end tag), resulting in unexpected rendering behavior. To avoid this, wrap `.Summary` in a `<div>`; alternatively, wrap it together with the heading tag using `<section>`. You can avoid this entirely by using a manual summary. See issue [#14044] for details.
|
||||
|
||||
## Comparison
|
||||
|
||||
Each summary type has different characteristics:
|
||||
@@ -121,3 +153,5 @@ Instead of calling the `Summary` method on a `Page` object, use the [`strings.Tr
|
||||
</div>
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
[#14044]: https://github.com/gohugoio/hugo/issues/14044
|
||||
|
||||
@@ -90,23 +90,20 @@ tags = ['Tag A','Tag B']
|
||||
categories = ['Category A','Category B']
|
||||
{{< /code-toggle >}}
|
||||
|
||||
## Order taxonomies
|
||||
## Taxonomic weight
|
||||
|
||||
A content file can assign weight for each of its associate taxonomies. Taxonomic weight can be used for sorting or ordering content in taxonomy templates and is declared in a content file's front matter. The convention for declaring taxonomic weight is `taxonomyname_weight`.
|
||||
{{% glossary-term "taxonomic weight" %}}
|
||||
|
||||
The following show a piece of content that has a weight of 22, which can be used for ordering purposes when rendering the pages assigned to the "a", "b" and "c" values of the `tags` taxonomy. It has also been assigned the weight of 44 when rendering the "d" category page.
|
||||
Assign a taxonomic weight using a front matter key named `[taxonomy_name]_weight`.
|
||||
|
||||
### Example: taxonomic `weight`
|
||||
|
||||
{{< code-toggle file=hugo >}}
|
||||
title = "foo"
|
||||
tags = [ "a", "b", "c" ]
|
||||
tags_weight = 22
|
||||
categories = ["d"]
|
||||
categories_weight = 44
|
||||
{{< code-toggle file="content/courses/organic-chemistry.md" fm=true >}}
|
||||
title = 'Organic Chemistry'
|
||||
weight = 10
|
||||
tags_weight = 1000
|
||||
tags = ['chemistry','science']
|
||||
{{</ code-toggle >}}
|
||||
|
||||
By using taxonomic weight, the same piece of content can appear in different positions in different taxonomies.
|
||||
With the front matter above, the "Organic Chemistry" page will float towards the top of the list on section and home pages, and it will sink towards the bottom of the list on the "chemistry" and "science" term pages.
|
||||
|
||||
## Metadata
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user