mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-25 15:58:53 +00:00
Compare commits
214 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0270364a34 | |||
| 760c13a7ac | |||
| 3bd73d262d | |||
| 7104de83ce | |||
| 835579b338 | |||
| 05e067ced8 | |||
| ee48d9692a | |||
| db28695ff5 | |||
| 778f0d9002 | |||
| 13b208e2f7 | |||
| 329b2342f0 | |||
| 33b46d8a41 | |||
| 6c68142cc1 | |||
| e08d9af21e | |||
| cd7dc7a372 | |||
| c939c33fd3 | |||
| bb7b3d3cdb | |||
| 873a5cda1a | |||
| f42a4b6af5 | |||
| a5637831cd | |||
| 68586c891c | |||
| 51bb2fedab | |||
| 43307b07f8 | |||
| 346b60358d | |||
| 7f0f50b133 | |||
| 77a8e347bc | |||
| f704d75699 | |||
| 9d765e1b99 | |||
| 1f746a8724 | |||
| 6aa72acaf9 | |||
| 9885e7020d | |||
| 91101d24f2 | |||
| 637995ba8f | |||
| f1de5d2a04 | |||
| 4b0c194fb3 | |||
| 8de4ffb294 | |||
| 1f5a15aba0 | |||
| 8897113666 | |||
| e91d3cff98 | |||
| e7bd51698e | |||
| 1fad3832a9 | |||
| 8b52626915 | |||
| 81a7b63900 | |||
| 5c5508817f | |||
| 88ecc3b753 | |||
| 1191467c05 | |||
| 60c24fc5ee | |||
| 41137077bc | |||
| 5bb1564575 | |||
| 76993369a0 | |||
| de7137cc35 | |||
| a2edf04c27 | |||
| 956f915f77 | |||
| a2a4166f6d | |||
| 39f582f9f4 | |||
| 2501de7aab | |||
| 06cc8673fa | |||
| 71fae99189 | |||
| 8af04745fb | |||
| dde9d9d544 | |||
| 892b49110e | |||
| c5a63a3b4f | |||
| 61d3d20129 | |||
| b13d0a68e6 | |||
| d9594a96fc | |||
| 4a5cc2dd26 | |||
| 9cad8d38ca | |||
| cfa0801815 | |||
| f024a5050e | |||
| 80704bc9a1 | |||
| 3682027a51 | |||
| 44628616af | |||
| 0fc86783ee | |||
| e9fbadacc3 | |||
| 2a7bb1c7c9 | |||
| b7b49fb0f8 | |||
| 5f2adad2c7 | |||
| a837976731 | |||
| 0918e087ec | |||
| 4ea94c451d | |||
| 5d2cbee989 | |||
| 723e3f4342 | |||
| d913f46a8b | |||
| 2db43f841c | |||
| 46ce1f191b | |||
| aae02ca612 | |||
| 83cec785cf | |||
| 4e52be8b90 | |||
| 7888ac585c | |||
| eb1dbe0709 | |||
| 77824d704c | |||
| ec0caaec7c | |||
| 845b8885de | |||
| 43385d6aeb | |||
| a9b0b95ef4 | |||
| e229f4b387 | |||
| 020253904f | |||
| 4a5e94087b | |||
| 48a7aee961 | |||
| 6c583e3227 | |||
| 5d64b492f4 | |||
| b3f32949cb | |||
| 55ecd3a90e | |||
| 3f35721fb2 | |||
| 5c80cb0d20 | |||
| a5e5be234c | |||
| 565c30eac9 | |||
| 48dd6a918a | |||
| 744b8566ec | |||
| 7de5317aef | |||
| 4cbd4ef991 | |||
| a32c889a7b | |||
| 852d868549 | |||
| 1e34e5b26d | |||
| 641d2616c7 | |||
| a834bb9f7e | |||
| 4f130f6e4f | |||
| 9dfa112617 | |||
| ec1933f79d | |||
| 75ad9cdaab | |||
| e293e7ca6d | |||
| 157d86414d | |||
| 6be253000f | |||
| 947e4e66b5 | |||
| 989b299b55 | |||
| 5f897868c0 | |||
| e477373487 | |||
| b47376586a | |||
| 5ab38de363 | |||
| 7b69218489 | |||
| 34373407b7 | |||
| 3afe91d4b1 | |||
| 54398f8d57 | |||
| b8c15f245b | |||
| d0dc518844 | |||
| b529859008 | |||
| 487bb96474 | |||
| 2f6864387c | |||
| c1dc35dd71 | |||
| fc3d1cbadb | |||
| 7e130e34f2 | |||
| 88b7868fbd | |||
| dea158c885 | |||
| 467444ef64 | |||
| 770f548b47 | |||
| 0ab81896d9 | |||
| e00fdae456 | |||
| 21299a7a67 | |||
| aa3dd197f7 | |||
| 5a50eee9da | |||
| 8d017a60fb | |||
| 8fcd3c1487 | |||
| 2d4c0c6c8d | |||
| 59a55a1f9e | |||
| dacef0df92 | |||
| 8d92042ab9 | |||
| e917401c71 | |||
| eb298144b6 | |||
| 3b6eaf9b1f | |||
| 1fd845eee4 | |||
| 61e2ce2a49 | |||
| 2d503f82ac | |||
| 3a6b2e6b14 | |||
| bbbfeb340a | |||
| 838bd312b1 | |||
| 6bde8abaad | |||
| 66dd6ecab2 | |||
| ad43d137d5 | |||
| f7fc6ccd59 | |||
| d4de780edc | |||
| 2c54c32986 | |||
| 7a2f04ee8c | |||
| 7921777da1 | |||
| 33e964d40b | |||
| 5e3133a7d8 | |||
| 58a3c91a7f | |||
| ac6962d284 | |||
| 23d21b0d16 | |||
| 588c9019cf | |||
| ce9cf882a5 | |||
| 46e17053c8 | |||
| cb6580d008 | |||
| 8aba6dc661 | |||
| a7df536a52 | |||
| de0df119b5 | |||
| 3477d9fcec | |||
| e79ee0d516 | |||
| 2b97a2a8bf | |||
| 35afe6fe2a | |||
| ad82998d54 | |||
| 95e2d5beb8 | |||
| 2c3efc8106 | |||
| df8bd4af4f | |||
| 17e15b2148 | |||
| ca4fc587c3 | |||
| 4faaaf9c2f | |||
| 94b46c36b4 | |||
| 2b0b3b8584 | |||
| 801035bb7a | |||
| b7861e586e | |||
| 59c1158135 | |||
| fcdc454cc5 | |||
| 6cf23bf882 | |||
| 62a96cef7f | |||
| 30d9aea860 | |||
| 1f23b4949c | |||
| 5fc1639035 | |||
| 889308dd85 | |||
| 72352f205a | |||
| e3304db617 | |||
| 1ec6fa36c0 | |||
| 89bd025ebf | |||
| 62567d3820 | |||
| e10915f80a |
@@ -4,7 +4,7 @@ parameters:
|
||||
defaults: &defaults
|
||||
resource_class: large
|
||||
docker:
|
||||
- image: bepsays/ci-hugoreleaser:1.22300.20200
|
||||
- image: bepsays/ci-hugoreleaser:1.22301.20401
|
||||
environment: &buildenv
|
||||
GOMODCACHE: /root/project/gomodcache
|
||||
version: 2
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
environment:
|
||||
<<: [*buildenv]
|
||||
docker:
|
||||
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22300.20200
|
||||
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22301.20401
|
||||
steps:
|
||||
- *restore-cache
|
||||
- &attach-workspace
|
||||
|
||||
@@ -6,10 +6,10 @@ name: Test
|
||||
env:
|
||||
GOPROXY: https://proxy.golang.org
|
||||
GO111MODULE: on
|
||||
SASS_VERSION: 1.63.2
|
||||
DART_SASS_SHA_LINUX: 3ea33c95ad5c35fda6e9a0956199eef38a398f496cfb8750e02479d7d1dd42af
|
||||
DART_SASS_SHA_MACOS: 11c70f259836b250b44a9cb57fed70e030f21f45069b467d371685855f1eb4f0
|
||||
DART_SASS_SHA_WINDOWS: cd8cd36a619dd8e27f93d3186c52d70eb7d69472aa6c85f5094b29693e773f64
|
||||
SASS_VERSION: 1.80.3
|
||||
DART_SASS_SHA_LINUX: 7c933edbad0a7d389192c5b79393485c088bd2c4398e32f5754c32af006a9ffd
|
||||
DART_SASS_SHA_MACOS: 79e060b0e131c3bb3c16926bafc371dc33feab122bfa8c01aa337a072097967b
|
||||
DART_SASS_SHA_WINDOWS: 0bc4708b37cd1bac4740e83ac5e3176e66b774f77fd5dd364da5b5cfc9bfb469
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
@@ -112,17 +112,17 @@ jobs:
|
||||
sass --version;
|
||||
mage -v check;
|
||||
env:
|
||||
HUGO_BUILD_TAGS: extended
|
||||
HUGO_BUILD_TAGS: extended,withdeploy
|
||||
- if: matrix.os == 'windows-latest'
|
||||
# See issue #11052. We limit the build to regular test (no -race flag) on Windows for now.
|
||||
name: Test
|
||||
run: |
|
||||
mage -v test;
|
||||
env:
|
||||
HUGO_BUILD_TAGS: extended
|
||||
HUGO_BUILD_TAGS: extended,withdeploy
|
||||
- name: Build tags
|
||||
run: |
|
||||
go install -tags extended,nodeploy
|
||||
go install -tags extended
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Build for dragonfly
|
||||
run: |
|
||||
|
||||
+4
-1
@@ -1,3 +1,6 @@
|
||||
|
||||
*.test
|
||||
imports.*
|
||||
imports.*
|
||||
dist/
|
||||
public/
|
||||
.DS_Store
|
||||
+2
-2
@@ -21,8 +21,8 @@ COPY --from=xx / /
|
||||
ARG TARGETPLATFORM
|
||||
RUN xx-apk add musl-dev gcc g++
|
||||
|
||||
# Optionally set HUGO_BUILD_TAGS to "none" or "nodeploy" when building like so:
|
||||
# docker build --build-arg HUGO_BUILD_TAGS=nodeploy .
|
||||
# Optionally set HUGO_BUILD_TAGS to "none" or "withdeploy" when building like so:
|
||||
# docker build --build-arg HUGO_BUILD_TAGS=withdeploy .
|
||||
#
|
||||
# We build the extended version by default.
|
||||
ARG HUGO_BUILD_TAGS="extended"
|
||||
|
||||
@@ -33,8 +33,7 @@ A fast and flexible static site generator built with love by [bep], [spf13], and
|
||||
[](https://github.com/gohugoio/hugo/actions?query=workflow%3ATest)
|
||||
[](https://goreportcard.com/report/github.com/gohugoio/hugo)
|
||||
|
||||
[Website] | [Installation] | [Documentation] | [Support] | [Contributing] | <a rel="me" href="https://fosstodon.org/@gohugoio">Mastodon</a> | <a rel="me" href="https://x.com/gohugoiov2">X</a>
|
||||
|
||||
[Website] | [Installation] | [Documentation] | [Support] | [Contributing] | <a rel="me" href="https://fosstodon.org/@gohugoio">Mastodon</a>
|
||||
## Overview
|
||||
|
||||
Hugo is a [static site generator] written in [Go], optimized for speed and designed for flexibility. With its advanced templating system and fast asset pipelines, Hugo renders a complete site in seconds, often less.
|
||||
@@ -67,7 +66,7 @@ See the [features] section of the documentation for a comprehensive summary of H
|
||||
<p float="left">
|
||||
<a href="https://www.linode.com/?utm_campaign=hugosponsor&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/linode-logo_standard_light_medium.png" width="200" alt="Linode"></a>
|
||||
|
||||
<a href="https://route4me.com/" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/Route4MeLogoBlueOnWhite.svg" width="200" alt="Route Planning & Route Optimization Software"></a>
|
||||
<a href="https://www.jetbrains.com/go/?utm_source=OSS&utm_medium=referral&utm_campaign=hugo" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/goland.svg" width="200" alt="The complete IDE crafted for professional Go developers."></a>
|
||||
</p>
|
||||
|
||||
## Installation
|
||||
@@ -102,6 +101,7 @@ Build the extended edition:
|
||||
```text
|
||||
CGO_ENABLED=1 go install -tags extended github.com/gohugoio/hugo@latest
|
||||
```
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#gohugoio/hugo&Timeline)
|
||||
@@ -148,153 +148,103 @@ Hugo stands on the shoulders of great open source libraries. Run `hugo env --log
|
||||
<summary>See current dependencies</summary>
|
||||
|
||||
```text
|
||||
cloud.google.com/go/compute/metadata="v0.2.3"
|
||||
cloud.google.com/go/iam="v1.1.5"
|
||||
cloud.google.com/go/storage="v1.35.1"
|
||||
cloud.google.com/go="v0.110.10"
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore="v1.9.0"
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity="v1.4.0"
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal="v1.5.0"
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob="v1.2.0"
|
||||
github.com/Azure/go-autorest/autorest/to="v0.4.0"
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go="v1.2.0"
|
||||
github.com/BurntSushi/locker="v0.0.0-20171006230638-a6e239ea1c69"
|
||||
github.com/alecthomas/chroma/v2="v2.14.0"
|
||||
github.com/armon/go-radix="v1.0.1-0.20221118154546-54df44f2176c"
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream="v1.5.4"
|
||||
github.com/aws/aws-sdk-go-v2/config="v1.26.1"
|
||||
github.com/aws/aws-sdk-go-v2/credentials="v1.16.12"
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds="v1.14.10"
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager="v1.15.7"
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources="v1.3.5"
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2="v2.6.5"
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini="v1.7.2"
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a="v1.2.9"
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront="v1.35.4"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding="v1.10.4"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum="v1.2.9"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url="v1.10.9"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared="v1.16.9"
|
||||
github.com/aws/aws-sdk-go-v2/service/s3="v1.47.5"
|
||||
github.com/aws/aws-sdk-go-v2/service/sso="v1.18.5"
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc="v1.21.5"
|
||||
github.com/aws/aws-sdk-go-v2/service/sts="v1.26.5"
|
||||
github.com/aws/aws-sdk-go-v2="v1.26.1"
|
||||
github.com/aws/aws-sdk-go="v1.50.7"
|
||||
github.com/aws/smithy-go="v1.20.2"
|
||||
github.com/bep/clocks="v0.5.0"
|
||||
github.com/bep/debounce="v1.2.0"
|
||||
github.com/bep/gitmap="v1.1.2"
|
||||
github.com/bep/gitmap="v1.6.0"
|
||||
github.com/bep/goat="v0.5.0"
|
||||
github.com/bep/godartsass/v2="v2.0.0"
|
||||
github.com/bep/godartsass="v1.2.0"
|
||||
github.com/bep/golibsass="v1.1.1"
|
||||
github.com/bep/godartsass/v2="v2.3.2"
|
||||
github.com/bep/golibsass="v1.2.0"
|
||||
github.com/bep/gowebp="v0.3.0"
|
||||
github.com/bep/lazycache="v0.4.0"
|
||||
github.com/bep/imagemeta="v0.8.3"
|
||||
github.com/bep/lazycache="v0.7.0"
|
||||
github.com/bep/logg="v0.4.0"
|
||||
github.com/bep/mclib="v1.20400.20402"
|
||||
github.com/bep/overlayfs="v0.9.2"
|
||||
github.com/bep/simplecobra="v0.4.0"
|
||||
github.com/bep/tmc="v0.5.1"
|
||||
github.com/cespare/xxhash/v2="v2.3.0"
|
||||
github.com/clbanning/mxj/v2="v2.7.0"
|
||||
github.com/cli/safeexec="v1.0.1"
|
||||
github.com/cpuguy83/go-md2man/v2="v2.0.3"
|
||||
github.com/cpuguy83/go-md2man/v2="v2.0.4"
|
||||
github.com/disintegration/gift="v1.2.1"
|
||||
github.com/dlclark/regexp2="v1.11.0"
|
||||
github.com/dustin/go-humanize="v1.0.1"
|
||||
github.com/evanw/esbuild="v0.21.4"
|
||||
github.com/fatih/color="v1.16.0"
|
||||
github.com/evanw/esbuild="v0.24.0"
|
||||
github.com/fatih/color="v1.18.0"
|
||||
github.com/frankban/quicktest="v1.14.6"
|
||||
github.com/fsnotify/fsnotify="v1.7.0"
|
||||
github.com/fsnotify/fsnotify="v1.8.0"
|
||||
github.com/getkin/kin-openapi="v0.123.0"
|
||||
github.com/ghodss/yaml="v1.0.0"
|
||||
github.com/go-openapi/jsonpointer="v0.20.2"
|
||||
github.com/go-openapi/swag="v0.22.8"
|
||||
github.com/gobuffalo/flect="v1.0.2"
|
||||
github.com/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.1.0"
|
||||
github.com/gohugoio/httpcache="v0.7.0"
|
||||
github.com/gohugoio/hugo-goldmark-extensions/extras="v0.2.0"
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough="v0.2.0"
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough="v0.3.0"
|
||||
github.com/gohugoio/locales="v0.14.0"
|
||||
github.com/gohugoio/localescompressed="v1.0.1"
|
||||
github.com/golang-jwt/jwt/v5="v5.1.0"
|
||||
github.com/golang/groupcache="v0.0.0-20210331224755-41bb18bfe9da"
|
||||
github.com/golang/protobuf="v1.5.3"
|
||||
github.com/google/go-cmp="v0.6.0"
|
||||
github.com/google/s2a-go="v0.1.7"
|
||||
github.com/google/uuid="v1.4.0"
|
||||
github.com/google/wire="v0.5.0"
|
||||
github.com/googleapis/enterprise-certificate-proxy="v0.3.2"
|
||||
github.com/googleapis/gax-go/v2="v2.12.0"
|
||||
github.com/gorilla/websocket="v1.5.1"
|
||||
github.com/hairyhenderson/go-codeowners="v0.4.0"
|
||||
github.com/gorilla/websocket="v1.5.3"
|
||||
github.com/hairyhenderson/go-codeowners="v0.7.0"
|
||||
github.com/hashicorp/golang-lru/v2="v2.0.7"
|
||||
github.com/invopop/yaml="v0.2.0"
|
||||
github.com/jdkato/prose="v1.2.1"
|
||||
github.com/jmespath/go-jmespath="v0.4.0"
|
||||
github.com/josharian/intern="v1.0.0"
|
||||
github.com/kr/pretty="v0.3.1"
|
||||
github.com/kr/text="v0.2.0"
|
||||
github.com/kylelemons/godebug="v1.1.0"
|
||||
github.com/kyokomi/emoji/v2="v2.2.12"
|
||||
github.com/kyokomi/emoji/v2="v2.2.13"
|
||||
github.com/mailru/easyjson="v0.7.7"
|
||||
github.com/makeworld-the-better-one/dither/v2="v2.4.0"
|
||||
github.com/marekm4/color-extractor="v1.2.1"
|
||||
github.com/mattn/go-colorable="v0.1.13"
|
||||
github.com/mattn/go-isatty="v0.0.20"
|
||||
github.com/mattn/go-runewidth="v0.0.9"
|
||||
github.com/mitchellh/hashstructure="v1.1.0"
|
||||
github.com/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/olekukonko/tablewriter="v0.0.5"
|
||||
github.com/pbnjay/memory="v0.0.0-20210728143218-7b4eea64cf58"
|
||||
github.com/pelletier/go-toml/v2="v2.2.2"
|
||||
github.com/pelletier/go-toml/v2="v2.2.3"
|
||||
github.com/perimeterx/marshmallow="v1.1.5"
|
||||
github.com/pkg/browser="v0.0.0-20210911075715-681adbf594b8"
|
||||
github.com/pkg/browser="v0.0.0-20240102092130-5ac0b6a4141c"
|
||||
github.com/pkg/errors="v0.9.1"
|
||||
github.com/rogpeppe/go-internal="v1.12.0"
|
||||
github.com/rogpeppe/go-internal="v1.13.1"
|
||||
github.com/russross/blackfriday/v2="v2.1.0"
|
||||
github.com/rwcarlsen/goexif="v0.0.0-20190401172101-9e8deecbddbd"
|
||||
github.com/sass/dart-sass/compiler="1.77.5"
|
||||
github.com/sass/dart-sass/implementation="1.77.5"
|
||||
github.com/sass/dart-sass/protocol="2.7.1"
|
||||
github.com/sass/libsass="3.6.5"
|
||||
github.com/sass/dart-sass/compiler="1.81.0"
|
||||
github.com/sass/dart-sass/implementation="1.81.0"
|
||||
github.com/sass/dart-sass/protocol="3.1.0"
|
||||
github.com/sass/libsass="3.6.6"
|
||||
github.com/spf13/afero="v1.11.0"
|
||||
github.com/spf13/cast="v1.6.0"
|
||||
github.com/spf13/cobra="v1.8.0"
|
||||
github.com/spf13/cast="v1.7.1"
|
||||
github.com/spf13/cobra="v1.8.1"
|
||||
github.com/spf13/fsync="v0.10.1"
|
||||
github.com/spf13/pflag="v1.0.5"
|
||||
github.com/tdewolff/minify/v2="v2.20.20"
|
||||
github.com/tdewolff/parse/v2="v2.7.13"
|
||||
github.com/tdewolff/minify/v2="v2.20.37"
|
||||
github.com/tdewolff/parse/v2="v2.7.15"
|
||||
github.com/tetratelabs/wazero="v1.8.2"
|
||||
github.com/webmproject/libwebp="v1.3.2"
|
||||
github.com/yuin/goldmark-emoji="v1.0.3"
|
||||
github.com/yuin/goldmark="v1.7.4"
|
||||
go.opencensus.io="v0.24.0"
|
||||
github.com/yuin/goldmark-emoji="v1.0.4"
|
||||
github.com/yuin/goldmark="v1.7.8"
|
||||
go.uber.org/automaxprocs="v1.5.3"
|
||||
gocloud.dev="v0.36.0"
|
||||
golang.org/x/crypto="v0.23.0"
|
||||
golang.org/x/crypto="v0.31.0"
|
||||
golang.org/x/exp="v0.0.0-20221031165847-c99f073a8326"
|
||||
golang.org/x/image="v0.16.0"
|
||||
golang.org/x/mod="v0.17.0"
|
||||
golang.org/x/net="v0.25.0"
|
||||
golang.org/x/oauth2="v0.15.0"
|
||||
golang.org/x/sync="v0.7.0"
|
||||
golang.org/x/sys="v0.20.0"
|
||||
golang.org/x/text="v0.15.0"
|
||||
golang.org/x/time="v0.5.0"
|
||||
golang.org/x/tools="v0.20.0"
|
||||
golang.org/x/xerrors="v0.0.0-20231012003039-104605ab7028"
|
||||
google.golang.org/api="v0.152.0"
|
||||
google.golang.org/genproto/googleapis/api="v0.0.0-20231120223509-83a465c0220f"
|
||||
google.golang.org/genproto/googleapis/rpc="v0.0.0-20231120223509-83a465c0220f"
|
||||
google.golang.org/genproto="v0.0.0-20231120223509-83a465c0220f"
|
||||
google.golang.org/grpc="v1.59.0"
|
||||
google.golang.org/protobuf="v1.33.0"
|
||||
golang.org/x/image="v0.22.0"
|
||||
golang.org/x/mod="v0.22.0"
|
||||
golang.org/x/net="v0.33.0"
|
||||
golang.org/x/sync="v0.10.0"
|
||||
golang.org/x/sys="v0.28.0"
|
||||
golang.org/x/text="v0.21.0"
|
||||
golang.org/x/tools="v0.28.0"
|
||||
google.golang.org/protobuf="v1.35.2"
|
||||
gopkg.in/yaml.v2="v2.4.0"
|
||||
gopkg.in/yaml.v3="v3.0.1"
|
||||
rsc.io/qr="v0.2.0"
|
||||
software.sslmate.com/src/go-pkcs12="v0.2.0"
|
||||
```
|
||||
</details>
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# allow user to override go executable by running as GOEXE=xxx make ...
|
||||
GOEXE="${GOEXE-go}"
|
||||
|
||||
# Convenience script to
|
||||
# - For a given branch
|
||||
# - Run benchmark tests for a given package
|
||||
# - Do the same for master
|
||||
# - then compare the two runs with benchcmp
|
||||
|
||||
benchFilter=".*"
|
||||
|
||||
if (( $# < 2 ));
|
||||
then
|
||||
echo "USAGE: ./bench.sh <git-branch> <package-to-bench> (and <benchmark filter> (regexp, optional))"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
|
||||
if [ $# -eq 3 ]; then
|
||||
benchFilter=$3
|
||||
fi
|
||||
|
||||
|
||||
BRANCH=$1
|
||||
PACKAGE=$2
|
||||
|
||||
git checkout $BRANCH
|
||||
"${GOEXE}" test -test.run=NONE -bench="$benchFilter" -test.benchmem=true ./$PACKAGE > /tmp/bench-$PACKAGE-$BRANCH.txt
|
||||
|
||||
git checkout master
|
||||
"${GOEXE}" test -test.run=NONE -bench="$benchFilter" -test.benchmem=true ./$PACKAGE > /tmp/bench-$PACKAGE-master.txt
|
||||
|
||||
|
||||
benchcmp /tmp/bench-$PACKAGE-master.txt /tmp/bench-$PACKAGE-$BRANCH.txt
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# allow user to override go executable by running as GOEXE=xxx make ...
|
||||
GOEXE="${GOEXE-go}"
|
||||
|
||||
# Send in a regexp matching the benchmarks you want to run, i.e. './benchSite.sh "YAML"'.
|
||||
# Note the quotes, which will be needed for more complex expressions.
|
||||
# The above will run all variations, but only for front matter YAML.
|
||||
|
||||
echo "Running with BenchmarkSiteBuilding/${1}"
|
||||
|
||||
"${GOEXE}" test -run="NONE" -bench="BenchmarkSiteBuilding/${1}" -test.benchmem=true ./hugolib -memprofile mem.prof -count 3 -cpuprofile cpu.prof
|
||||
@@ -1 +0,0 @@
|
||||
gobench -package=./hugolib -bench="BenchmarkSiteNew/Deep_content_tree"
|
||||
@@ -1 +0,0 @@
|
||||
docker run --rm --mount type=bind,source="$(pwd)",target=/hugo -w /hugo -i -t bepsays/ci-goreleaser:1.11-2 /bin/bash
|
||||
Vendored
+30
-11
@@ -176,11 +176,12 @@ func (c *Cache) ClearMatching(predicatePartition func(k string, p PartitionManag
|
||||
}
|
||||
|
||||
// ClearOnRebuild prepares the cache for a new rebuild taking the given changeset into account.
|
||||
func (c *Cache) ClearOnRebuild(changeset ...identity.Identity) {
|
||||
// predicate is optional and will clear any entry for which it returns true.
|
||||
func (c *Cache) ClearOnRebuild(predicate func(k, v any) bool, changeset ...identity.Identity) {
|
||||
g := rungroup.Run[PartitionManager](context.Background(), rungroup.Config[PartitionManager]{
|
||||
NumWorkers: len(c.partitions),
|
||||
Handle: func(ctx context.Context, partition PartitionManager) error {
|
||||
partition.clearOnRebuild(changeset...)
|
||||
partition.clearOnRebuild(predicate, changeset...)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
@@ -430,12 +431,25 @@ func (p *Partition[K, V]) doGetOrCreateWitTimeout(key K, duration time.Duration,
|
||||
errch := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
v, _, err := p.c.GetOrCreate(key, create)
|
||||
if err != nil {
|
||||
errch <- err
|
||||
return
|
||||
}
|
||||
resultch <- v
|
||||
var (
|
||||
v V
|
||||
err error
|
||||
)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if rerr, ok := r.(error); ok {
|
||||
err = rerr
|
||||
} else {
|
||||
err = fmt.Errorf("panic: %v", r)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
errch <- err
|
||||
} else {
|
||||
resultch <- v
|
||||
}
|
||||
}()
|
||||
v, _, err = p.c.GetOrCreate(key, create)
|
||||
}()
|
||||
|
||||
select {
|
||||
@@ -466,7 +480,12 @@ func (p *Partition[K, V]) clearMatching(predicate func(k, v any) bool) {
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Partition[K, V]) clearOnRebuild(changeset ...identity.Identity) {
|
||||
func (p *Partition[K, V]) clearOnRebuild(predicate func(k, v any) bool, changeset ...identity.Identity) {
|
||||
if predicate == nil {
|
||||
predicate = func(k, v any) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
opts := p.getOptions()
|
||||
if opts.ClearWhen == ClearNever {
|
||||
return
|
||||
@@ -512,7 +531,7 @@ func (p *Partition[K, V]) clearOnRebuild(changeset ...identity.Identity) {
|
||||
// Second pass needs to be done in a separate loop to catch any
|
||||
// elements marked as stale in the other partitions.
|
||||
p.c.DeleteFunc(func(key K, v V) bool {
|
||||
if shouldDelete(key, v) {
|
||||
if predicate(key, v) || shouldDelete(key, v) {
|
||||
p.trace.Log(
|
||||
logg.StringFunc(
|
||||
func() string {
|
||||
@@ -588,7 +607,7 @@ type PartitionManager interface {
|
||||
adjustMaxSize(addend int) int
|
||||
getMaxSize() int
|
||||
getOptions() OptionsPartition
|
||||
clearOnRebuild(changeset ...identity.Identity)
|
||||
clearOnRebuild(predicate func(k, v any) bool, changeset ...identity.Identity)
|
||||
clearMatching(predicate func(k, v any) bool)
|
||||
clearStale()
|
||||
}
|
||||
|
||||
Vendored
+57
-2
@@ -14,8 +14,11 @@
|
||||
package dynacache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
@@ -144,13 +147,13 @@ func TestClear(t *testing.T) {
|
||||
|
||||
c.Assert(cache.Keys(predicateAll), qt.HasLen, 4)
|
||||
|
||||
cache.ClearOnRebuild()
|
||||
cache.ClearOnRebuild(nil)
|
||||
|
||||
// Stale items are always cleared.
|
||||
c.Assert(cache.Keys(predicateAll), qt.HasLen, 2)
|
||||
|
||||
cache = newTestCache(t)
|
||||
cache.ClearOnRebuild(identity.StringIdentity("changed"))
|
||||
cache.ClearOnRebuild(nil, identity.StringIdentity("changed"))
|
||||
|
||||
c.Assert(cache.Keys(nil), qt.HasLen, 1)
|
||||
|
||||
@@ -165,6 +168,58 @@ func TestClear(t *testing.T) {
|
||||
cache.adjustCurrentMaxSize()
|
||||
}
|
||||
|
||||
func TestPanicInCreate(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
cache := newTestCache(t)
|
||||
|
||||
p1 := GetOrCreatePartition[string, testItem](cache, "/aaaa/bbbb", OptionsPartition{Weight: 30, ClearWhen: ClearOnRebuild})
|
||||
|
||||
willPanic := func(i int) func() {
|
||||
return func() {
|
||||
p1.GetOrCreate(fmt.Sprintf("panic-%d", i), func(key string) (testItem, error) {
|
||||
panic(errors.New(key))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrCreateWitTimeout needs to recover from panics in the create func.
|
||||
willErr := func(i int) error {
|
||||
_, err := p1.GetOrCreateWitTimeout(fmt.Sprintf("error-%d", i), 10*time.Second, func(key string) (testItem, error) {
|
||||
return testItem{}, errors.New(key)
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
for j := 0; j < 3; j++ {
|
||||
c.Assert(willPanic(i), qt.PanicMatches, fmt.Sprintf("panic-%d", i))
|
||||
c.Assert(willErr(i), qt.ErrorMatches, fmt.Sprintf("error-%d", i))
|
||||
}
|
||||
}
|
||||
|
||||
// Test the same keys again without the panic.
|
||||
for i := 0; i < 3; i++ {
|
||||
for j := 0; j < 3; j++ {
|
||||
v, err := p1.GetOrCreate(fmt.Sprintf("panic-%d", i), func(key string) (testItem, error) {
|
||||
return testItem{
|
||||
name: key,
|
||||
}, nil
|
||||
})
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(v.name, qt.Equals, fmt.Sprintf("panic-%d", i))
|
||||
|
||||
v, err = p1.GetOrCreateWitTimeout(fmt.Sprintf("error-%d", i), 10*time.Second, func(key string) (testItem, error) {
|
||||
return testItem{
|
||||
name: key,
|
||||
}, nil
|
||||
})
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(v.name, qt.Equals, fmt.Sprintf("error-%d", i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdjustCurrentMaxSize(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
+15
-26
@@ -39,7 +39,6 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/common/hstrings"
|
||||
"github.com/gohugoio/hugo/common/htime"
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
@@ -104,7 +103,8 @@ type configKey struct {
|
||||
type rootCommand struct {
|
||||
Printf func(format string, v ...interface{})
|
||||
Println func(a ...interface{})
|
||||
Out io.Writer
|
||||
StdOut io.Writer
|
||||
StdErr io.Writer
|
||||
|
||||
logger loggers.Logger
|
||||
|
||||
@@ -141,8 +141,6 @@ type rootCommand struct {
|
||||
|
||||
logLevel string
|
||||
|
||||
verbose bool
|
||||
debug bool
|
||||
quiet bool
|
||||
devMode bool // Hidden flag.
|
||||
|
||||
@@ -359,7 +357,7 @@ func (r *rootCommand) getOrCreateHugo(cfg config.Provider, ignoreModuleDoesNotEx
|
||||
}
|
||||
|
||||
func (r *rootCommand) newDepsConfig(conf *commonConfig) deps.DepsCfg {
|
||||
return deps.DepsCfg{Configs: conf.configs, Fs: conf.fs, LogOut: r.logger.Out(), LogLevel: r.logger.Level(), ChangesFromBuild: r.changesFromBuild}
|
||||
return deps.DepsCfg{Configs: conf.configs, Fs: conf.fs, StdOut: r.logger.StdOut(), StdErr: r.logger.StdErr(), LogLevel: r.logger.Level(), ChangesFromBuild: r.changesFromBuild}
|
||||
}
|
||||
|
||||
func (r *rootCommand) Name() string {
|
||||
@@ -424,21 +422,23 @@ func (r *rootCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args
|
||||
}
|
||||
|
||||
func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
|
||||
r.Out = os.Stdout
|
||||
r.StdOut = os.Stdout
|
||||
r.StdErr = os.Stderr
|
||||
if r.quiet {
|
||||
r.Out = io.Discard
|
||||
r.StdOut = io.Discard
|
||||
r.StdErr = io.Discard
|
||||
}
|
||||
// Used by mkcert (server).
|
||||
log.SetOutput(r.Out)
|
||||
log.SetOutput(r.StdOut)
|
||||
|
||||
r.Printf = func(format string, v ...interface{}) {
|
||||
if !r.quiet {
|
||||
fmt.Fprintf(r.Out, format, v...)
|
||||
fmt.Fprintf(r.StdOut, format, v...)
|
||||
}
|
||||
}
|
||||
r.Println = func(a ...interface{}) {
|
||||
if !r.quiet {
|
||||
fmt.Fprintln(r.Out, a...)
|
||||
fmt.Fprintln(r.StdOut, a...)
|
||||
}
|
||||
}
|
||||
_, running := runner.Command.(*serverCommand)
|
||||
@@ -447,6 +447,8 @@ func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Set up the global logger early to allow info deprecations during config load.
|
||||
loggers.InitGlobalLogger(r.logger.Level(), false)
|
||||
|
||||
r.changesFromBuild = make(chan []identity.Identity, 10)
|
||||
|
||||
@@ -482,25 +484,14 @@ func (r *rootCommand) createLogger(running bool) (loggers.Logger, error) {
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid log level: %q, must be one of debug, warn, info or error", r.logLevel)
|
||||
}
|
||||
} else {
|
||||
if r.verbose {
|
||||
hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0")
|
||||
hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0")
|
||||
level = logg.LevelInfo
|
||||
}
|
||||
|
||||
if r.debug {
|
||||
hugo.Deprecate("--debug", "use --logLevel debug", "v0.114.0")
|
||||
level = logg.LevelDebug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
optsLogger := loggers.Options{
|
||||
DistinctLevel: logg.LevelWarn,
|
||||
Level: level,
|
||||
Stdout: r.Out,
|
||||
Stderr: r.Out,
|
||||
StdOut: r.StdOut,
|
||||
StdErr: r.StdErr,
|
||||
StoreErrors: running,
|
||||
}
|
||||
|
||||
@@ -549,6 +540,7 @@ Complete documentation is available at https://gohugo.io/.`
|
||||
cmd.PersistentFlags().StringP("themesDir", "", "", "filesystem path to themes directory")
|
||||
_ = cmd.MarkFlagDirname("themesDir")
|
||||
cmd.PersistentFlags().StringP("ignoreVendorPaths", "", "", "ignores any _vendor for module paths matching the given Glob pattern")
|
||||
cmd.PersistentFlags().BoolP("noBuildLock", "", false, "don't create .hugo_build.lock file")
|
||||
_ = cmd.RegisterFlagCompletionFunc("ignoreVendorPaths", cobra.NoFileCompletions)
|
||||
cmd.PersistentFlags().String("clock", "", "set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00")
|
||||
_ = cmd.RegisterFlagCompletionFunc("clock", cobra.NoFileCompletions)
|
||||
@@ -560,8 +552,6 @@ Complete documentation is available at https://gohugo.io/.`
|
||||
cmd.PersistentFlags().BoolVar(&r.quiet, "quiet", false, "build in quiet mode")
|
||||
cmd.PersistentFlags().BoolVarP(&r.renderToMemory, "renderToMemory", "M", false, "render to memory (mostly useful when running the server)")
|
||||
|
||||
cmd.PersistentFlags().BoolVarP(&r.verbose, "verbose", "v", false, "verbose output")
|
||||
cmd.PersistentFlags().BoolVarP(&r.debug, "debug", "", false, "debug output")
|
||||
cmd.PersistentFlags().BoolVarP(&r.devMode, "devMode", "", false, "only used for internal testing, flag hidden.")
|
||||
cmd.PersistentFlags().StringVar(&r.logLevel, "logLevel", "", "log level (debug|info|warn|error)")
|
||||
_ = cmd.RegisterFlagCompletionFunc("logLevel", cobra.FixedCompletions([]string{"debug", "info", "warn", "error"}, cobra.ShellCompDirectiveNoFileComp))
|
||||
@@ -606,7 +596,6 @@ func applyLocalFlagsBuild(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.Flags().BoolVar(&r.forceSyncStatic, "forceSyncStatic", false, "copy all files when static is changed.")
|
||||
cmd.Flags().BoolP("noTimes", "", false, "don't sync modification time of files")
|
||||
cmd.Flags().BoolP("noChmod", "", false, "don't sync permission mode of files")
|
||||
cmd.Flags().BoolP("noBuildLock", "", false, "don't create .hugo_build.lock file")
|
||||
cmd.Flags().BoolP("printI18nWarnings", "", false, "print missing translations")
|
||||
cmd.Flags().BoolP("printPathWarnings", "", false, "print warnings on duplicate target paths etc.")
|
||||
cmd.Flags().BoolP("printUnusedTemplates", "", false, "print warnings on unused templates.")
|
||||
|
||||
+5
-3
@@ -43,8 +43,9 @@ func newConfigCommand() *configCommand {
|
||||
type configCommand struct {
|
||||
r *rootCommand
|
||||
|
||||
format string
|
||||
lang string
|
||||
format string
|
||||
lang string
|
||||
printZero bool
|
||||
|
||||
commands []simplecobra.Commander
|
||||
}
|
||||
@@ -78,7 +79,7 @@ func (c *configCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, arg
|
||||
dec.SetIndent("", " ")
|
||||
dec.SetEscapeHTML(false)
|
||||
|
||||
if err := dec.Encode(parser.ReplacingJSONMarshaller{Value: config, KeysToLower: true, OmitEmpty: true}); err != nil {
|
||||
if err := dec.Encode(parser.ReplacingJSONMarshaller{Value: config, KeysToLower: true, OmitEmpty: !c.printZero}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -115,6 +116,7 @@ func (c *configCommand) Init(cd *simplecobra.Commandeer) error {
|
||||
cmd.Flags().StringVar(&c.format, "format", "toml", "preferred file format (toml, yaml or json)")
|
||||
_ = cmd.RegisterFlagCompletionFunc("format", cobra.FixedCompletions([]string{"toml", "yaml", "json"}, cobra.ShellCompDirectiveNoFileComp))
|
||||
cmd.Flags().StringVar(&c.lang, "lang", "", "the language to display config for. Defaults to the first language defined.")
|
||||
cmd.Flags().BoolVar(&c.printZero, "printZero", false, `include config options with zero values (e.g. false, 0, "") in the output`)
|
||||
_ = cmd.RegisterFlagCompletionFunc("lang", cobra.NoFileCompletions)
|
||||
applyLocalFlagsBuildConfig(cmd, c.r)
|
||||
|
||||
|
||||
+3
-27
@@ -11,21 +11,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !nodeploy
|
||||
// +build !nodeploy
|
||||
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//go:build withdeploy
|
||||
// +build withdeploy
|
||||
|
||||
package commands
|
||||
|
||||
@@ -33,7 +20,6 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/gohugoio/hugo/deploy"
|
||||
"github.com/gohugoio/hugo/deploy/deployconfig"
|
||||
|
||||
"github.com/bep/simplecobra"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -60,17 +46,7 @@ documentation.
|
||||
return deployer.Deploy(ctx)
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
cmd.Flags().String("target", "", "target deployment from deployments section in config file; defaults to the first one")
|
||||
_ = cmd.RegisterFlagCompletionFunc("target", cobra.NoFileCompletions)
|
||||
cmd.Flags().Bool("confirm", false, "ask for confirmation before making changes to the target")
|
||||
cmd.Flags().Bool("dryRun", false, "dry run")
|
||||
cmd.Flags().Bool("force", false, "force upload of all files")
|
||||
cmd.Flags().Bool("invalidateCDN", deployconfig.DefaultConfig.InvalidateCDN, "invalidate the CDN cache listed in the deployment target")
|
||||
cmd.Flags().Int("maxDeletes", deployconfig.DefaultConfig.MaxDeletes, "maximum # of files to delete, or -1 to disable")
|
||||
_ = cmd.RegisterFlagCompletionFunc("maxDeletes", cobra.NoFileCompletions)
|
||||
cmd.Flags().Int("workers", deployconfig.DefaultConfig.Workers, "number of workers to transfer files. defaults to 10")
|
||||
_ = cmd.RegisterFlagCompletionFunc("workers", cobra.NoFileCompletions)
|
||||
applyDeployFlags(cmd, r)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/gohugoio/hugo/deploy/deployconfig"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func applyDeployFlags(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
cmd.Flags().String("target", "", "target deployment from deployments section in config file; defaults to the first one")
|
||||
_ = cmd.RegisterFlagCompletionFunc("target", cobra.NoFileCompletions)
|
||||
cmd.Flags().Bool("confirm", false, "ask for confirmation before making changes to the target")
|
||||
cmd.Flags().Bool("dryRun", false, "dry run")
|
||||
cmd.Flags().Bool("force", false, "force upload of all files")
|
||||
cmd.Flags().Bool("invalidateCDN", deployconfig.DefaultConfig.InvalidateCDN, "invalidate the CDN cache listed in the deployment target")
|
||||
cmd.Flags().Int("maxDeletes", deployconfig.DefaultConfig.MaxDeletes, "maximum # of files to delete, or -1 to disable")
|
||||
_ = cmd.RegisterFlagCompletionFunc("maxDeletes", cobra.NoFileCompletions)
|
||||
cmd.Flags().Int("workers", deployconfig.DefaultConfig.Workers, "number of workers to transfer files. defaults to 10")
|
||||
_ = cmd.RegisterFlagCompletionFunc("workers", cobra.NoFileCompletions)
|
||||
}
|
||||
@@ -11,8 +11,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build nodeploy
|
||||
// +build nodeploy
|
||||
//go:build !withdeploy
|
||||
// +build !withdeploy
|
||||
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
@@ -31,6 +31,7 @@ package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/bep/simplecobra"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -40,9 +41,10 @@ func newDeployCommand() simplecobra.Commander {
|
||||
return &simpleCommand{
|
||||
name: "deploy",
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
return nil
|
||||
return errors.New("deploy not supported in this version of Hugo; install a release with 'withdeploy' in the archive filename or build yourself with the 'withdeploy' build tag. Also see https://github.com/gohugoio/hugo/pull/12995")
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
applyDeployFlags(cmd, r)
|
||||
cmd.Hidden = true
|
||||
},
|
||||
}
|
||||
|
||||
+3
-1
@@ -75,7 +75,9 @@ See https://xyproto.github.io/splash/docs/all.html for a preview of the availabl
|
||||
return err
|
||||
}
|
||||
formatter := html.New(html.WithAllClasses(true))
|
||||
formatter.WriteCSS(os.Stdout, style)
|
||||
w := os.Stdout
|
||||
fmt.Fprintf(w, "/* Generated using: hugo %s */\n\n", strings.Join(os.Args[1:], " "))
|
||||
formatter.WriteCSS(w, style)
|
||||
return nil
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
|
||||
@@ -62,7 +62,7 @@ type hugoBuilder struct {
|
||||
|
||||
// Currently only set when in "fast render mode".
|
||||
changeDetector *fileChangeDetector
|
||||
visitedURLs *types.EvictingStringQueue
|
||||
visitedURLs *types.EvictingQueue[string]
|
||||
|
||||
fullRebuildSem *semaphore.Weighted
|
||||
debounce func(f func())
|
||||
@@ -920,7 +920,11 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
||||
|
||||
changed := c.changeDetector.changed()
|
||||
if c.changeDetector != nil {
|
||||
lrl.Logf("build changed %d files", len(changed))
|
||||
if len(changed) >= 10 {
|
||||
lrl.Logf("build changed %d files", len(changed))
|
||||
} else {
|
||||
lrl.Logf("build changed %d files: %q", len(changed), changed)
|
||||
}
|
||||
if len(changed) == 0 {
|
||||
// Nothing has changed.
|
||||
return
|
||||
@@ -966,7 +970,7 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
||||
pathToRefresh := h.PathSpec.RelURL(paths.ToSlashTrimLeading(otherChanges[0]), false)
|
||||
lrl.Logf("refreshing %q", pathToRefresh)
|
||||
livereload.RefreshPath(pathToRefresh)
|
||||
} else if len(cssChanges) == 0 {
|
||||
} else if len(cssChanges) == 0 || len(otherChanges) > 1 {
|
||||
lrl.Logf("force refresh")
|
||||
livereload.ForceRefresh()
|
||||
}
|
||||
@@ -1099,7 +1103,7 @@ func (c *hugoBuilder) rebuildSites(events []fsnotify.Event) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = h.Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyVisited: c.visitedURLs, ErrRecovery: c.errState.wasErr()}, events...)
|
||||
err = h.Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyTouched: c.visitedURLs, ErrRecovery: c.errState.wasErr()}, events...)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1115,7 +1119,7 @@ func (c *hugoBuilder) rebuildSitesForChanges(ids []identity.Identity) (err error
|
||||
}
|
||||
whatChanged := &hugolib.WhatChanged{}
|
||||
whatChanged.Add(ids...)
|
||||
err = h.Build(hugolib.BuildCfg{NoBuildLock: true, WhatChanged: whatChanged, RecentlyVisited: c.visitedURLs, ErrRecovery: c.errState.wasErr()})
|
||||
err = h.Build(hugolib.BuildCfg{NoBuildLock: true, WhatChanged: whatChanged, RecentlyTouched: c.visitedURLs, ErrRecovery: c.errState.wasErr()})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ func newListCommand() *listCommand {
|
||||
return err
|
||||
}
|
||||
|
||||
writer := csv.NewWriter(r.Out)
|
||||
writer := csv.NewWriter(r.StdOut)
|
||||
defer writer.Flush()
|
||||
|
||||
writer.Write([]string{
|
||||
|
||||
+22
-7
@@ -32,6 +32,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -40,6 +41,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/bep/mclib"
|
||||
"github.com/pkg/browser"
|
||||
|
||||
"github.com/bep/debounce"
|
||||
"github.com/bep/simplecobra"
|
||||
@@ -83,9 +85,9 @@ const (
|
||||
)
|
||||
|
||||
func newHugoBuilder(r *rootCommand, s *serverCommand, onConfigLoaded ...func(reloaded bool) error) *hugoBuilder {
|
||||
var visitedURLs *types.EvictingStringQueue
|
||||
var visitedURLs *types.EvictingQueue[string]
|
||||
if s != nil && !s.disableFastRender {
|
||||
visitedURLs = types.NewEvictingStringQueue(20)
|
||||
visitedURLs = types.NewEvictingQueue[string](20)
|
||||
}
|
||||
return &hugoBuilder{
|
||||
r: r,
|
||||
@@ -209,16 +211,17 @@ func (f *fileChangeDetector) changed() []string {
|
||||
}
|
||||
}
|
||||
|
||||
return f.filterIrrelevant(c)
|
||||
return f.filterIrrelevantAndSort(c)
|
||||
}
|
||||
|
||||
func (f *fileChangeDetector) filterIrrelevant(in []string) []string {
|
||||
func (f *fileChangeDetector) filterIrrelevantAndSort(in []string) []string {
|
||||
var filtered []string
|
||||
for _, v := range in {
|
||||
if !f.irrelevantRe.MatchString(v) {
|
||||
filtered = append(filtered, v)
|
||||
}
|
||||
}
|
||||
sort.Strings(filtered)
|
||||
return filtered
|
||||
}
|
||||
|
||||
@@ -361,7 +364,10 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
|
||||
}
|
||||
|
||||
if f.c.fastRenderMode && f.c.errState.buildErr() == nil {
|
||||
if strings.HasSuffix(requestURI, "/") || strings.HasSuffix(requestURI, "html") || strings.HasSuffix(requestURI, "htm") {
|
||||
// Sec-Fetch-Mode should be sent by all recent browser versions, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Mode#navigate
|
||||
// Fall back to the file extension if not set.
|
||||
// The main take here is that we don't want to have CSS/JS files etc. partake in this logic.
|
||||
if r.Header.Get("Sec-Fetch-Mode") == "navigate" || strings.HasSuffix(requestURI, "/") || strings.HasSuffix(requestURI, "html") || strings.HasSuffix(requestURI, "htm") {
|
||||
if !f.c.visitedURLs.Contains(requestURI) {
|
||||
// If not already on stack, re-render that single page.
|
||||
if err := f.c.partialReRender(requestURI); err != nil {
|
||||
@@ -448,6 +454,7 @@ type serverCommand struct {
|
||||
// Flags.
|
||||
renderStaticToDisk bool
|
||||
navigateToChanged bool
|
||||
openBrowser bool
|
||||
serverAppend bool
|
||||
serverInterface string
|
||||
tlsCertFile string
|
||||
@@ -539,6 +546,7 @@ of a second, you will be able to save and see your changes nearly instantly.`
|
||||
cmd.Flags().BoolVarP(&c.serverAppend, "appendPort", "", true, "append port to baseURL")
|
||||
cmd.Flags().BoolVar(&c.disableLiveReload, "disableLiveReload", false, "watch without enabling live browser reload on rebuild")
|
||||
cmd.Flags().BoolVarP(&c.navigateToChanged, "navigateToChanged", "N", false, "navigate to changed content file on live browser reload")
|
||||
cmd.Flags().BoolVarP(&c.openBrowser, "openBrowser", "O", false, "open the site in a browser after server startup")
|
||||
cmd.Flags().BoolVar(&c.renderStaticToDisk, "renderStaticToDisk", false, "serve static files from disk and dynamic files from memory")
|
||||
cmd.Flags().BoolVar(&c.disableFastRender, "disableFastRender", false, "enables full re-renders on changes")
|
||||
cmd.Flags().BoolVar(&c.disableBrowserError, "disableBrowserError", false, "do not show build errors in the browser")
|
||||
@@ -833,7 +841,7 @@ func (c *serverCommand) partialReRender(urls ...string) (err error) {
|
||||
defer func() {
|
||||
c.errState.setWasErr(false)
|
||||
}()
|
||||
visited := types.NewEvictingStringQueue(len(urls))
|
||||
visited := types.NewEvictingQueue[string](len(urls))
|
||||
for _, url := range urls {
|
||||
visited.Add(url)
|
||||
}
|
||||
@@ -845,7 +853,7 @@ func (c *serverCommand) partialReRender(urls ...string) (err error) {
|
||||
}
|
||||
|
||||
// Note: We do not set NoBuildLock as the file lock is not acquired at this stage.
|
||||
err = h.Build(hugolib.BuildCfg{NoBuildLock: false, RecentlyVisited: visited, PartialReRender: true, ErrRecovery: c.errState.wasErr()})
|
||||
err = h.Build(hugolib.BuildCfg{NoBuildLock: false, RecentlyTouched: visited, PartialReRender: true, ErrRecovery: c.errState.wasErr()})
|
||||
|
||||
return
|
||||
}
|
||||
@@ -998,6 +1006,13 @@ func (c *serverCommand) serve() error {
|
||||
|
||||
c.r.Println("Press Ctrl+C to stop")
|
||||
|
||||
if c.openBrowser {
|
||||
// There may be more than one baseURL in multihost mode, open the first.
|
||||
if err := browser.OpenURL(baseURLs[0].String()); err != nil {
|
||||
c.r.logger.Warnf("Failed to open browser: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = func() error {
|
||||
for {
|
||||
select {
|
||||
|
||||
@@ -21,6 +21,8 @@ const (
|
||||
ErrRemoteGetCSV = "error-remote-getcsv"
|
||||
|
||||
WarnFrontMatterParamsOverrides = "warning-frontmatter-params-overrides"
|
||||
WarnRenderShortcodesInHTML = "warning-rendershortcodes-in-html"
|
||||
WarnGoldmarkRawHTML = "warning-goldmark-raw-html"
|
||||
)
|
||||
|
||||
// Field/method names with special meaning.
|
||||
@@ -39,7 +41,7 @@ const (
|
||||
ResourceTransformationFingerprint = "fingerprint"
|
||||
)
|
||||
|
||||
// IsResourceTransformationLinkChange returns whether the given name is a resource transformation that changes the permalink based on the content.
|
||||
// IsResourceTransformationPermalinkHash returns whether the given name is a resource transformation that changes the permalink based on the content.
|
||||
func IsResourceTransformationPermalinkHash(name string) bool {
|
||||
return name == ResourceTransformationFingerprint
|
||||
}
|
||||
|
||||
@@ -38,6 +38,19 @@ func XXHashFromReader(r io.Reader) (uint64, int64, error) {
|
||||
return h.Sum64(), size, nil
|
||||
}
|
||||
|
||||
// XxHashFromReaderHexEncoded calculates the xxHash for the given reader
|
||||
// and returns the hash as a hex encoded string.
|
||||
func XxHashFromReaderHexEncoded(r io.Reader) (string, error) {
|
||||
h := getXxHashReadFrom()
|
||||
defer putXxHashReadFrom(h)
|
||||
_, err := io.Copy(h, r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
hash := h.Sum(nil)
|
||||
return hex.EncodeToString(hash), nil
|
||||
}
|
||||
|
||||
// XXHashFromString calculates the xxHash for the given string.
|
||||
func XXHashFromString(s string) (uint64, error) {
|
||||
h := xxhash.New()
|
||||
@@ -70,6 +83,13 @@ func HashString(vs ...any) string {
|
||||
return strconv.FormatUint(hash, 10)
|
||||
}
|
||||
|
||||
// HashStringHex returns a hash from the given elements as a hex encoded string.
|
||||
// See HashString for more information.
|
||||
func HashStringHex(vs ...any) string {
|
||||
hash := HashUint64(vs...)
|
||||
return strconv.FormatUint(hash, 16)
|
||||
}
|
||||
|
||||
var hashOptsPool = sync.Pool{
|
||||
New: func() any {
|
||||
return &hashstructure.HashOptions{
|
||||
@@ -103,16 +123,24 @@ func HashUint64(vs ...any) uint64 {
|
||||
o = elements
|
||||
}
|
||||
|
||||
hashOpts := getHashOpts()
|
||||
defer putHashOpts(hashOpts)
|
||||
|
||||
hash, err := hashstructure.Hash(o, hashOpts)
|
||||
hash, err := Hash(o)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
// Hash returns a hash from vs.
|
||||
func Hash(vs ...any) (uint64, error) {
|
||||
hashOpts := getHashOpts()
|
||||
defer putHashOpts(hashOpts)
|
||||
var v any = vs
|
||||
if len(vs) == 1 {
|
||||
v = vs[0]
|
||||
}
|
||||
return hashstructure.Hash(v, hashOpts)
|
||||
}
|
||||
|
||||
type keyer interface {
|
||||
Key() string
|
||||
}
|
||||
|
||||
@@ -142,3 +142,16 @@ func BenchmarkHashString(b *testing.B) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHashMap(b *testing.B) {
|
||||
m := map[string]interface{}{}
|
||||
for i := 0; i < 1000; i++ {
|
||||
m[fmt.Sprintf("key%d", i)] = i
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
HashString(m)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +133,21 @@ func IsNotExist(err error) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsExist returns true if the error is a file exists error.
|
||||
// Unlike os.IsExist, this also considers wrapped errors.
|
||||
func IsExist(err error) bool {
|
||||
if os.IsExist(err) {
|
||||
return true
|
||||
}
|
||||
|
||||
// os.IsExist does not consider wrapped errors.
|
||||
if os.IsExist(errors.Unwrap(err)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
var nilPointerErrRe = regexp.MustCompile(`at <(.*)>: error calling (.*?): runtime error: invalid memory address or nil pointer dereference`)
|
||||
|
||||
const deferredPrefix = "__hdeferred/"
|
||||
|
||||
@@ -20,8 +20,6 @@ import (
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
godartsassv1 "github.com/bep/godartsass"
|
||||
|
||||
"github.com/bep/godartsass/v2"
|
||||
"github.com/bep/golibsass/libsass/libsasserrors"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
@@ -153,8 +151,6 @@ func (e *fileError) causeString() string {
|
||||
// Avoid repeating the file info in the error message.
|
||||
case godartsass.SassError:
|
||||
return v.Message
|
||||
case godartsassv1.SassError:
|
||||
return v.Message
|
||||
case libsasserrors.Error:
|
||||
return v.Message
|
||||
default:
|
||||
@@ -262,8 +258,27 @@ func openFile(filename string, fs afero.Fs) (afero.File, string, error) {
|
||||
return f, realFilename, nil
|
||||
}
|
||||
|
||||
// Cause returns the underlying error or itself if it does not implement Unwrap.
|
||||
// Cause returns the underlying error, that is,
|
||||
// it unwraps errors until it finds one that does not implement
|
||||
// the Unwrap method.
|
||||
// For a shallow variant, see Unwrap.
|
||||
func Cause(err error) error {
|
||||
type unwrapper interface {
|
||||
Unwrap() error
|
||||
}
|
||||
|
||||
for err != nil {
|
||||
cause, ok := err.(unwrapper)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
err = cause.Unwrap()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying error or itself if it does not implement Unwrap.
|
||||
func Unwrap(err error) error {
|
||||
if u := errors.Unwrap(err); u != nil {
|
||||
return u
|
||||
}
|
||||
@@ -271,7 +286,7 @@ func Cause(err error) error {
|
||||
}
|
||||
|
||||
func extractFileTypePos(err error) (string, text.Position) {
|
||||
err = Cause(err)
|
||||
err = Unwrap(err)
|
||||
|
||||
var fileType string
|
||||
|
||||
@@ -388,14 +403,7 @@ func extractPosition(e error) (pos text.Position) {
|
||||
case godartsass.SassError:
|
||||
span := v.Span
|
||||
start := span.Start
|
||||
filename, _ := paths.UrlToFilename(span.Url)
|
||||
pos.Filename = filename
|
||||
pos.Offset = start.Offset
|
||||
pos.ColumnNumber = start.Column
|
||||
case godartsassv1.SassError:
|
||||
span := v.Span
|
||||
start := span.Start
|
||||
filename, _ := paths.UrlToFilename(span.Url)
|
||||
filename, _ := paths.UrlStringToFilename(span.Url)
|
||||
pos.Filename = filename
|
||||
pos.Offset = start.Offset
|
||||
pos.ColumnNumber = start.Column
|
||||
|
||||
+85
-17
@@ -26,7 +26,10 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/bep/logg"
|
||||
"github.com/cli/safeexec"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/config/security"
|
||||
)
|
||||
@@ -86,7 +89,7 @@ var WithEnviron = func(env []string) func(c *commandeer) {
|
||||
}
|
||||
|
||||
// New creates a new Exec using the provided security config.
|
||||
func New(cfg security.Config, workingDir string) *Exec {
|
||||
func New(cfg security.Config, workingDir string, log loggers.Logger) *Exec {
|
||||
var baseEnviron []string
|
||||
for _, v := range os.Environ() {
|
||||
k, _ := config.SplitEnvVar(v)
|
||||
@@ -96,9 +99,11 @@ func New(cfg security.Config, workingDir string) *Exec {
|
||||
}
|
||||
|
||||
return &Exec{
|
||||
sc: cfg,
|
||||
workingDir: workingDir,
|
||||
baseEnviron: baseEnviron,
|
||||
sc: cfg,
|
||||
workingDir: workingDir,
|
||||
infol: log.InfoCommand("exec"),
|
||||
baseEnviron: baseEnviron,
|
||||
newNPXRunnerCache: maps.NewCache[string, func(arg ...any) (Runner, error)](),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,12 +129,14 @@ func SafeCommand(name string, arg ...string) (*exec.Cmd, error) {
|
||||
type Exec struct {
|
||||
sc security.Config
|
||||
workingDir string
|
||||
infol logg.LevelLogger
|
||||
|
||||
// os.Environ filtered by the Exec.OsEnviron whitelist filter.
|
||||
baseEnviron []string
|
||||
|
||||
npxInit sync.Once
|
||||
npxAvailable bool
|
||||
newNPXRunnerCache *maps.Cache[string, func(arg ...any) (Runner, error)]
|
||||
npxInit sync.Once
|
||||
npxAvailable bool
|
||||
}
|
||||
|
||||
func (e *Exec) New(name string, arg ...any) (Runner, error) {
|
||||
@@ -155,25 +162,86 @@ func (e *Exec) new(name string, fullyQualifiedName string, arg ...any) (Runner,
|
||||
return cm.command(arg...)
|
||||
}
|
||||
|
||||
type binaryLocation int
|
||||
|
||||
func (b binaryLocation) String() string {
|
||||
switch b {
|
||||
case binaryLocationNodeModules:
|
||||
return "node_modules/.bin"
|
||||
case binaryLocationNpx:
|
||||
return "npx"
|
||||
case binaryLocationPath:
|
||||
return "PATH"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
const (
|
||||
binaryLocationNodeModules binaryLocation = iota + 1
|
||||
binaryLocationNpx
|
||||
binaryLocationPath
|
||||
)
|
||||
|
||||
// Npx will in order:
|
||||
// 1. Try fo find the binary in the WORKINGDIR/node_modules/.bin directory.
|
||||
// 2. If not found, and npx is available, run npx --no-install <name> <args>.
|
||||
// 3. Fall back to the PATH.
|
||||
// If name is "tailwindcss", we will try the PATH as the second option.
|
||||
func (e *Exec) Npx(name string, arg ...any) (Runner, error) {
|
||||
// npx is slow, so first try the common case.
|
||||
nodeBinFilename := filepath.Join(e.workingDir, nodeModulesBinPath, name)
|
||||
_, err := safeexec.LookPath(nodeBinFilename)
|
||||
if err == nil {
|
||||
return e.new(name, nodeBinFilename, arg...)
|
||||
if err := e.sc.CheckAllowedExec(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.checkNpx()
|
||||
if e.npxAvailable {
|
||||
r, err := e.npx(name, arg...)
|
||||
if err == nil {
|
||||
return r, nil
|
||||
|
||||
newRunner, err := e.newNPXRunnerCache.GetOrCreate(name, func() (func(...any) (Runner, error), error) {
|
||||
type tryFunc func() func(...any) (Runner, error)
|
||||
tryFuncs := map[binaryLocation]tryFunc{
|
||||
binaryLocationNodeModules: func() func(...any) (Runner, error) {
|
||||
nodeBinFilename := filepath.Join(e.workingDir, nodeModulesBinPath, name)
|
||||
_, err := safeexec.LookPath(nodeBinFilename)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return func(arg2 ...any) (Runner, error) {
|
||||
return e.new(name, nodeBinFilename, arg2...)
|
||||
}
|
||||
},
|
||||
binaryLocationNpx: func() func(...any) (Runner, error) {
|
||||
e.checkNpx()
|
||||
if !e.npxAvailable {
|
||||
return nil
|
||||
}
|
||||
return func(arg2 ...any) (Runner, error) {
|
||||
return e.npx(name, arg2...)
|
||||
}
|
||||
},
|
||||
binaryLocationPath: func() func(...any) (Runner, error) {
|
||||
if _, err := safeexec.LookPath(name); err != nil {
|
||||
return nil
|
||||
}
|
||||
return func(arg2 ...any) (Runner, error) {
|
||||
return e.New(name, arg2...)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
locations := []binaryLocation{binaryLocationNodeModules, binaryLocationNpx, binaryLocationPath}
|
||||
if name == "tailwindcss" {
|
||||
// See https://github.com/gohugoio/hugo/issues/13221#issuecomment-2574801253
|
||||
locations = []binaryLocation{binaryLocationNodeModules, binaryLocationPath, binaryLocationNpx}
|
||||
}
|
||||
for _, loc := range locations {
|
||||
if f := tryFuncs[loc](); f != nil {
|
||||
e.infol.Logf("resolve %q using %s", name, loc)
|
||||
return f, nil
|
||||
}
|
||||
}
|
||||
return nil, &NotFoundError{name: name, method: fmt.Sprintf("in %s", locations[len(locations)-1])}
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e.New(name, arg...)
|
||||
|
||||
return newRunner(arg...)
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
@@ -223,6 +223,27 @@ func AsTime(v reflect.Value, loc *time.Location) (time.Time, bool) {
|
||||
return time.Time{}, false
|
||||
}
|
||||
|
||||
// ToSliceAny converts the given value to a slice of any if possible.
|
||||
func ToSliceAny(v any) ([]any, bool) {
|
||||
if v == nil {
|
||||
return nil, false
|
||||
}
|
||||
switch vv := v.(type) {
|
||||
case []any:
|
||||
return vv, true
|
||||
default:
|
||||
vvv := reflect.ValueOf(v)
|
||||
if vvv.Kind() == reflect.Slice {
|
||||
out := make([]any, vvv.Len())
|
||||
for i := 0; i < vvv.Len(); i++ {
|
||||
out[i] = vvv.Index(i).Interface()
|
||||
}
|
||||
return out, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func CallMethodByName(cxt context.Context, name string, v reflect.Value) []reflect.Value {
|
||||
fn := v.MethodByName(name)
|
||||
var args []reflect.Value
|
||||
|
||||
@@ -50,6 +50,19 @@ func TestIsContextType(t *testing.T) {
|
||||
c.Assert(IsContextType(reflect.TypeOf(valueCtx)), qt.IsTrue)
|
||||
}
|
||||
|
||||
func TestToSliceAny(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
checkOK := func(in any, expected []any) {
|
||||
out, ok := ToSliceAny(in)
|
||||
c.Assert(ok, qt.Equals, true)
|
||||
c.Assert(out, qt.DeepEquals, expected)
|
||||
}
|
||||
|
||||
checkOK([]any{1, 2, 3}, []any{1, 2, 3})
|
||||
checkOK([]int{1, 2, 3}, []any{1, 2, 3})
|
||||
}
|
||||
|
||||
func BenchmarkIsContextType(b *testing.B) {
|
||||
type k string
|
||||
b.Run("value", func(b *testing.B) {
|
||||
|
||||
@@ -48,16 +48,16 @@ func TestHasBytesWriter(t *testing.T) {
|
||||
|
||||
for i := 0; i < 22; i++ {
|
||||
h, w := neww()
|
||||
fmt.Fprintf(w, rndStr()+"abc __foobar"+rndStr())
|
||||
fmt.Fprint(w, rndStr()+"abc __foobar"+rndStr())
|
||||
c.Assert(h.Patterns[0].Match, qt.Equals, true)
|
||||
|
||||
h, w = neww()
|
||||
fmt.Fprintf(w, rndStr()+"abc __f")
|
||||
fmt.Fprintf(w, "oo bar"+rndStr())
|
||||
fmt.Fprint(w, rndStr()+"abc __f")
|
||||
fmt.Fprint(w, "oo bar"+rndStr())
|
||||
c.Assert(h.Patterns[0].Match, qt.Equals, true)
|
||||
|
||||
h, w = neww()
|
||||
fmt.Fprintf(w, rndStr()+"abc __moo bar")
|
||||
fmt.Fprint(w, rndStr()+"abc __moo bar")
|
||||
c.Assert(h.Patterns[0].Match, qt.Equals, false)
|
||||
}
|
||||
|
||||
|
||||
@@ -74,13 +74,13 @@ type StringReader interface {
|
||||
ReadString() string
|
||||
}
|
||||
|
||||
// NewReadSeekerNoOpCloserFromString uses strings.NewReader to create a new ReadSeekerNoOpCloser
|
||||
// NewReadSeekerNoOpCloserFromBytes uses bytes.NewReader to create a new ReadSeekerNoOpCloser
|
||||
// from the given bytes slice.
|
||||
func NewReadSeekerNoOpCloserFromBytes(content []byte) readSeekerNopCloser {
|
||||
return readSeekerNopCloser{bytes.NewReader(content)}
|
||||
}
|
||||
|
||||
// NewReadSeekCloser creates a new ReadSeekCloser from the given ReadSeeker.
|
||||
// NewOpenReadSeekCloser creates a new ReadSeekCloser from the given ReadSeeker.
|
||||
// The ReadSeeker will be seeked to the beginning before returned.
|
||||
func NewOpenReadSeekCloser(r ReadSeekCloser) OpenReadSeekCloser {
|
||||
return func() (ReadSeekCloser, error) {
|
||||
|
||||
+26
-22
@@ -25,14 +25,13 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
godartsassv1 "github.com/bep/godartsass"
|
||||
"github.com/bep/logg"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"github.com/bep/godartsass/v2"
|
||||
"github.com/gohugoio/hugo/common/hcontext"
|
||||
"github.com/gohugoio/hugo/common/hexec"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
@@ -55,6 +54,8 @@ var (
|
||||
vendorInfo string
|
||||
)
|
||||
|
||||
var _ maps.StoreProvider = (*HugoInfo)(nil)
|
||||
|
||||
// HugoInfo contains information about the current Hugo environment
|
||||
type HugoInfo struct {
|
||||
CommitHash string
|
||||
@@ -72,6 +73,8 @@ type HugoInfo struct {
|
||||
conf ConfigProvider
|
||||
deps []*Dependency
|
||||
|
||||
store *maps.Scratch
|
||||
|
||||
// Context gives access to some of the context scoped variables.
|
||||
Context Context
|
||||
}
|
||||
@@ -116,6 +119,10 @@ func (i HugoInfo) Deps() []*Dependency {
|
||||
return i.deps
|
||||
}
|
||||
|
||||
func (i HugoInfo) Store() *maps.Scratch {
|
||||
return i.store
|
||||
}
|
||||
|
||||
// Deprecated: Use hugo.IsMultihost instead.
|
||||
func (i HugoInfo) IsMultiHost() bool {
|
||||
Deprecate("hugo.IsMultiHost", "Use hugo.IsMultihost instead.", "v0.124.0")
|
||||
@@ -185,6 +192,7 @@ func NewInfo(conf ConfigProvider, deps []*Dependency) HugoInfo {
|
||||
Environment: conf.Environment(),
|
||||
conf: conf,
|
||||
deps: deps,
|
||||
store: maps.NewScratch(),
|
||||
GoVersion: goVersion,
|
||||
}
|
||||
}
|
||||
@@ -308,7 +316,7 @@ func GetDependencyListNonGo() []string {
|
||||
|
||||
if dartSass := dartSassVersion(); dartSass.ProtocolVersion != "" {
|
||||
dartSassPath := "github.com/sass/dart-sass-embedded"
|
||||
if IsDartSassV2() {
|
||||
if IsDartSassGeV2() {
|
||||
dartSassPath = "github.com/sass/dart-sass"
|
||||
}
|
||||
deps = append(deps,
|
||||
@@ -355,22 +363,15 @@ type Dependency struct {
|
||||
}
|
||||
|
||||
func dartSassVersion() godartsass.DartSassVersion {
|
||||
if DartSassBinaryName == "" {
|
||||
if DartSassBinaryName == "" || !IsDartSassGeV2() {
|
||||
return godartsass.DartSassVersion{}
|
||||
}
|
||||
if IsDartSassV2() {
|
||||
v, _ := godartsass.Version(DartSassBinaryName)
|
||||
return v
|
||||
}
|
||||
|
||||
v, _ := godartsassv1.Version(DartSassBinaryName)
|
||||
var vv godartsass.DartSassVersion
|
||||
mapstructure.WeakDecode(v, &vv)
|
||||
return vv
|
||||
v, _ := godartsass.Version(DartSassBinaryName)
|
||||
return v
|
||||
}
|
||||
|
||||
// DartSassBinaryName is the name of the Dart Sass binary to use.
|
||||
// TODO(beop) find a better place for this.
|
||||
// TODO(bep) find a better place for this.
|
||||
var DartSassBinaryName string
|
||||
|
||||
func init() {
|
||||
@@ -395,7 +396,10 @@ var (
|
||||
dartSassBinaryNamesV2 = []string{"dart-sass", "sass"}
|
||||
)
|
||||
|
||||
func IsDartSassV2() bool {
|
||||
// TODO(bep) we eventually want to remove this, but keep it for a while to throw an informative error.
|
||||
// We stopped supporting the old binary in Hugo 0.139.0.
|
||||
func IsDartSassGeV2() bool {
|
||||
// dart-sass-embedded was the first version of the embedded Dart Sass before it was moved into the main project.
|
||||
return !strings.Contains(DartSassBinaryName, "embedded")
|
||||
}
|
||||
|
||||
@@ -414,15 +418,15 @@ func Deprecate(item, alternative string, version string) {
|
||||
func DeprecateLevel(item, alternative, version string, level logg.Level) {
|
||||
var msg string
|
||||
if level == logg.LevelError {
|
||||
msg = fmt.Sprintf("%s was deprecated in Hugo %s and will be removed in Hugo %s. %s", item, version, CurrentVersion.Next().ReleaseVersion(), alternative)
|
||||
msg = fmt.Sprintf("%s was deprecated in Hugo %s and subsequently removed. %s", item, version, alternative)
|
||||
} else {
|
||||
msg = fmt.Sprintf("%s was deprecated in Hugo %s and will be removed in a future release. %s", item, version, alternative)
|
||||
}
|
||||
|
||||
loggers.Log().Logger().WithLevel(level).WithField(loggers.FieldNameCmd, "deprecated").Logf(msg)
|
||||
loggers.Log().Logger().WithLevel(level).WithField(loggers.FieldNameCmd, "deprecated").Logf("%s", msg)
|
||||
}
|
||||
|
||||
// We ususally do about one minor version a month.
|
||||
// We usually do about one minor version a month.
|
||||
// We want people to run at least the current and previous version without any warnings.
|
||||
// We want people who don't update Hugo that often to see the warnings and errors before we remove the feature.
|
||||
func deprecationLogLevelFromVersion(ver string) logg.Level {
|
||||
@@ -430,11 +434,11 @@ func deprecationLogLevelFromVersion(ver string) logg.Level {
|
||||
to := CurrentVersion
|
||||
minorDiff := to.Minor - from.Minor
|
||||
switch {
|
||||
case minorDiff >= 12:
|
||||
// Start failing the build after about a year.
|
||||
case minorDiff >= 15:
|
||||
// Start failing the build after about 15 months.
|
||||
return logg.LevelError
|
||||
case minorDiff >= 6:
|
||||
// Start printing warnings after about six months.
|
||||
case minorDiff >= 3:
|
||||
// Start printing warnings after about 3 months.
|
||||
return logg.LevelWarn
|
||||
default:
|
||||
return logg.LevelInfo
|
||||
|
||||
@@ -57,12 +57,16 @@ func TestDeprecationLogLevelFromVersion(t *testing.T) {
|
||||
c.Assert(deprecationLogLevelFromVersion("0.55.0"), qt.Equals, logg.LevelError)
|
||||
ver := CurrentVersion
|
||||
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelInfo)
|
||||
ver.Minor -= 1
|
||||
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelInfo)
|
||||
ver.Minor -= 6
|
||||
ver.Minor -= 3
|
||||
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelWarn)
|
||||
ver.Minor -= 6
|
||||
ver.Minor -= 4
|
||||
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelWarn)
|
||||
ver.Minor -= 13
|
||||
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelError)
|
||||
|
||||
// Added just to find the threshold for where we can remove deprecated items.
|
||||
// Subtract 5 from the minor version of the first ERRORed version => 0.122.0.
|
||||
c.Assert(deprecationLogLevelFromVersion("0.127.0"), qt.Equals, logg.LevelError)
|
||||
}
|
||||
|
||||
func TestMarkupScope(t *testing.T) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -11,4 +11,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package js
|
||||
//go:build withdeploy
|
||||
// +build withdeploy
|
||||
|
||||
package hugo
|
||||
|
||||
var IsWithdeploy = true
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !withdeploy
|
||||
// +build !withdeploy
|
||||
|
||||
package hugo
|
||||
|
||||
var IsWithdeploy = false
|
||||
@@ -152,6 +152,9 @@ func BuildVersionString() string {
|
||||
if IsExtended {
|
||||
version += "+extended"
|
||||
}
|
||||
if IsWithdeploy {
|
||||
version += "+withdeploy"
|
||||
}
|
||||
|
||||
osArch := bi.GoOS + "/" + bi.GoArch
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ package hugo
|
||||
// This should be the only one.
|
||||
var CurrentVersion = Version{
|
||||
Major: 0,
|
||||
Minor: 137,
|
||||
PatchLevel: 0,
|
||||
Suffix: "-DEV",
|
||||
Minor: 143,
|
||||
PatchLevel: 1,
|
||||
Suffix: "",
|
||||
}
|
||||
|
||||
@@ -18,18 +18,19 @@ package loggers
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/bep/logg"
|
||||
)
|
||||
|
||||
// newNoColoursHandler creates a new NoColoursHandler
|
||||
func newNoColoursHandler(outWriter, errWriter io.Writer, noLevelPrefix bool, predicate func(*logg.Entry) bool) *noColoursHandler {
|
||||
// newNoAnsiEscapeHandler creates a new noAnsiEscapeHandler
|
||||
func newNoAnsiEscapeHandler(outWriter, errWriter io.Writer, noLevelPrefix bool, predicate func(*logg.Entry) bool) *noAnsiEscapeHandler {
|
||||
if predicate == nil {
|
||||
predicate = func(e *logg.Entry) bool { return true }
|
||||
}
|
||||
return &noColoursHandler{
|
||||
return &noAnsiEscapeHandler{
|
||||
noLevelPrefix: noLevelPrefix,
|
||||
outWriter: outWriter,
|
||||
errWriter: errWriter,
|
||||
@@ -37,15 +38,15 @@ func newNoColoursHandler(outWriter, errWriter io.Writer, noLevelPrefix bool, pre
|
||||
}
|
||||
}
|
||||
|
||||
type noColoursHandler struct {
|
||||
type noAnsiEscapeHandler struct {
|
||||
mu sync.Mutex
|
||||
outWriter io.Writer // Defaults to os.Stdout.
|
||||
errWriter io.Writer // Defaults to os.Stderr.
|
||||
outWriter io.Writer
|
||||
errWriter io.Writer
|
||||
predicate func(*logg.Entry) bool
|
||||
noLevelPrefix bool
|
||||
}
|
||||
|
||||
func (h *noColoursHandler) HandleLog(e *logg.Entry) error {
|
||||
func (h *noAnsiEscapeHandler) HandleLog(e *logg.Entry) error {
|
||||
if !h.predicate(e) {
|
||||
return nil
|
||||
}
|
||||
@@ -71,10 +72,12 @@ func (h *noColoursHandler) HandleLog(e *logg.Entry) error {
|
||||
prefix = prefix + ": "
|
||||
}
|
||||
|
||||
msg := stripANSI(e.Message)
|
||||
|
||||
if h.noLevelPrefix {
|
||||
fmt.Fprintf(w, "%s%s", prefix, e.Message)
|
||||
fmt.Fprintf(w, "%s%s", prefix, msg)
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s %s%s", levelString[e.Level], prefix, e.Message)
|
||||
fmt.Fprintf(w, "%s %s%s", levelString[e.Level], prefix, msg)
|
||||
}
|
||||
|
||||
for _, field := range e.Fields {
|
||||
@@ -88,3 +91,10 @@ func (h *noColoursHandler) HandleLog(e *logg.Entry) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var ansiRe = regexp.MustCompile(`\x1b\[[0-9;]*m`)
|
||||
|
||||
// stripANSI removes ANSI escape codes from s.
|
||||
func stripANSI(s string) string {
|
||||
return ansiRe.ReplaceAllString(s, "")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
// Some functions in this file (see comments) is based on the Go source code,
|
||||
// copyright The Go Authors and governed by a BSD-style license.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package loggers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/bep/logg"
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/common/terminal"
|
||||
)
|
||||
|
||||
func TestNoAnsiEscapeHandler(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
test := func(s string) {
|
||||
c.Assert(stripANSI(terminal.Notice(s)), qt.Equals, s)
|
||||
}
|
||||
test(`error in "file.md:1:2"`)
|
||||
|
||||
var buf bytes.Buffer
|
||||
h := newNoAnsiEscapeHandler(&buf, &buf, false, nil)
|
||||
h.HandleLog(&logg.Entry{Message: terminal.Notice(`error in "file.md:1:2"`), Level: logg.LevelInfo})
|
||||
|
||||
c.Assert(buf.String(), qt.Equals, "INFO error in \"file.md:1:2\"\n")
|
||||
}
|
||||
+26
-22
@@ -38,8 +38,8 @@ var (
|
||||
// Options defines options for the logger.
|
||||
type Options struct {
|
||||
Level logg.Level
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
StdOut io.Writer
|
||||
StdErr io.Writer
|
||||
DistinctLevel logg.Level
|
||||
StoreErrors bool
|
||||
HandlerPost func(e *logg.Entry) error
|
||||
@@ -48,21 +48,22 @@ type Options struct {
|
||||
|
||||
// New creates a new logger with the given options.
|
||||
func New(opts Options) Logger {
|
||||
if opts.Stdout == nil {
|
||||
opts.Stdout = os.Stdout
|
||||
if opts.StdOut == nil {
|
||||
opts.StdOut = os.Stdout
|
||||
}
|
||||
if opts.Stderr == nil {
|
||||
opts.Stderr = os.Stdout
|
||||
if opts.StdErr == nil {
|
||||
opts.StdErr = os.Stderr
|
||||
}
|
||||
|
||||
if opts.Level == 0 {
|
||||
opts.Level = logg.LevelWarn
|
||||
}
|
||||
|
||||
var logHandler logg.Handler
|
||||
if terminal.PrintANSIColors(os.Stdout) {
|
||||
logHandler = newDefaultHandler(opts.Stdout, opts.Stderr)
|
||||
if terminal.PrintANSIColors(os.Stderr) {
|
||||
logHandler = newDefaultHandler(opts.StdErr, opts.StdErr)
|
||||
} else {
|
||||
logHandler = newNoColoursHandler(opts.Stdout, opts.Stderr, false, nil)
|
||||
logHandler = newNoAnsiEscapeHandler(opts.StdErr, opts.StdErr, false, nil)
|
||||
}
|
||||
|
||||
errorsw := &strings.Builder{}
|
||||
@@ -95,7 +96,7 @@ func New(opts Options) Logger {
|
||||
}
|
||||
|
||||
if opts.StoreErrors {
|
||||
h := newNoColoursHandler(io.Discard, errorsw, true, func(e *logg.Entry) bool {
|
||||
h := newNoAnsiEscapeHandler(io.Discard, errorsw, true, func(e *logg.Entry) bool {
|
||||
return e.Level >= logg.LevelError
|
||||
})
|
||||
|
||||
@@ -137,7 +138,8 @@ func New(opts Options) Logger {
|
||||
logCounters: logCounters,
|
||||
errors: errorsw,
|
||||
reset: reset,
|
||||
out: opts.Stdout,
|
||||
stdOut: opts.StdOut,
|
||||
stdErr: opts.StdErr,
|
||||
level: opts.Level,
|
||||
logger: logger,
|
||||
tracel: l.WithLevel(logg.LevelTrace),
|
||||
@@ -153,8 +155,6 @@ func NewDefault() Logger {
|
||||
opts := Options{
|
||||
DistinctLevel: logg.LevelWarn,
|
||||
Level: logg.LevelWarn,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stdout,
|
||||
}
|
||||
return New(opts)
|
||||
}
|
||||
@@ -163,8 +163,6 @@ func NewTrace() Logger {
|
||||
opts := Options{
|
||||
DistinctLevel: logg.LevelWarn,
|
||||
Level: logg.LevelTrace,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stdout,
|
||||
}
|
||||
return New(opts)
|
||||
}
|
||||
@@ -189,7 +187,8 @@ type Logger interface {
|
||||
Level() logg.Level
|
||||
LoggCount(logg.Level) int
|
||||
Logger() logg.Logger
|
||||
Out() io.Writer
|
||||
StdOut() io.Writer
|
||||
StdErr() io.Writer
|
||||
Printf(format string, v ...any)
|
||||
Println(v ...any)
|
||||
PrintTimerIfDelayed(start time.Time, name string)
|
||||
@@ -207,7 +206,8 @@ type logAdapter struct {
|
||||
logCounters *logLevelCounter
|
||||
errors *strings.Builder
|
||||
reset func()
|
||||
out io.Writer
|
||||
stdOut io.Writer
|
||||
stdErr io.Writer
|
||||
level logg.Level
|
||||
logger logg.Logger
|
||||
tracel logg.LevelLogger
|
||||
@@ -259,8 +259,12 @@ func (l *logAdapter) Logger() logg.Logger {
|
||||
return l.logger
|
||||
}
|
||||
|
||||
func (l *logAdapter) Out() io.Writer {
|
||||
return l.out
|
||||
func (l *logAdapter) StdOut() io.Writer {
|
||||
return l.stdOut
|
||||
}
|
||||
|
||||
func (l *logAdapter) StdErr() io.Writer {
|
||||
return l.stdErr
|
||||
}
|
||||
|
||||
// PrintTimerIfDelayed prints a time statement to the FEEDBACK logger
|
||||
@@ -271,7 +275,7 @@ func (l *logAdapter) PrintTimerIfDelayed(start time.Time, name string) {
|
||||
if milli < 500 {
|
||||
return
|
||||
}
|
||||
l.Printf("%s in %v ms", name, milli)
|
||||
fmt.Fprintf(l.stdErr, "%s in %v ms", name, milli)
|
||||
}
|
||||
|
||||
func (l *logAdapter) Printf(format string, v ...any) {
|
||||
@@ -279,11 +283,11 @@ func (l *logAdapter) Printf(format string, v ...any) {
|
||||
if !strings.HasSuffix(format, "\n") {
|
||||
format += "\n"
|
||||
}
|
||||
fmt.Fprintf(l.out, format, v...)
|
||||
fmt.Fprintf(l.stdOut, format, v...)
|
||||
}
|
||||
|
||||
func (l *logAdapter) Println(v ...any) {
|
||||
fmt.Fprintln(l.out, v...)
|
||||
fmt.Fprintln(l.stdOut, v...)
|
||||
}
|
||||
|
||||
func (l *logAdapter) Reset() {
|
||||
|
||||
@@ -31,8 +31,8 @@ func TestLogDistinct(t *testing.T) {
|
||||
opts := loggers.Options{
|
||||
DistinctLevel: logg.LevelWarn,
|
||||
StoreErrors: true,
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
StdOut: io.Discard,
|
||||
StdErr: io.Discard,
|
||||
}
|
||||
|
||||
l := loggers.New(opts)
|
||||
@@ -54,8 +54,8 @@ func TestHookLast(t *testing.T) {
|
||||
HandlerPost: func(e *logg.Entry) error {
|
||||
panic(e.Message)
|
||||
},
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
StdOut: io.Discard,
|
||||
StdErr: io.Discard,
|
||||
}
|
||||
|
||||
l := loggers.New(opts)
|
||||
@@ -70,8 +70,8 @@ func TestOptionStoreErrors(t *testing.T) {
|
||||
|
||||
opts := loggers.Options{
|
||||
StoreErrors: true,
|
||||
Stderr: &sb,
|
||||
Stdout: &sb,
|
||||
StdErr: &sb,
|
||||
StdOut: &sb,
|
||||
}
|
||||
|
||||
l := loggers.New(opts)
|
||||
@@ -131,8 +131,8 @@ func TestReset(t *testing.T) {
|
||||
opts := loggers.Options{
|
||||
StoreErrors: true,
|
||||
DistinctLevel: logg.LevelWarn,
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
StdOut: io.Discard,
|
||||
StdErr: io.Discard,
|
||||
}
|
||||
|
||||
l := loggers.New(opts)
|
||||
|
||||
+55
-6
@@ -13,11 +13,14 @@
|
||||
|
||||
package maps
|
||||
|
||||
import "sync"
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Cache is a simple thread safe cache backed by a map.
|
||||
type Cache[K comparable, T any] struct {
|
||||
m map[K]T
|
||||
m map[K]T
|
||||
hasBeenInitialized bool
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
@@ -34,11 +37,16 @@ func (c *Cache[K, T]) Get(key K) (T, bool) {
|
||||
return zero, false
|
||||
}
|
||||
c.RLock()
|
||||
v, found := c.m[key]
|
||||
v, found := c.get(key)
|
||||
c.RUnlock()
|
||||
return v, found
|
||||
}
|
||||
|
||||
func (c *Cache[K, T]) get(key K) (T, bool) {
|
||||
v, found := c.m[key]
|
||||
return v, found
|
||||
}
|
||||
|
||||
// GetOrCreate gets the value for the given key if it exists, or creates it if not.
|
||||
func (c *Cache[K, T]) GetOrCreate(key K, create func() (T, error)) (T, error) {
|
||||
c.RLock()
|
||||
@@ -61,19 +69,58 @@ func (c *Cache[K, T]) GetOrCreate(key K, create func() (T, error)) (T, error) {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// InitAndGet initializes the cache if not already done and returns the value for the given key.
|
||||
// The init state will be reset on Reset or Drain.
|
||||
func (c *Cache[K, T]) InitAndGet(key K, init func(get func(key K) (T, bool), set func(key K, value T)) error) (T, error) {
|
||||
var v T
|
||||
c.RLock()
|
||||
if !c.hasBeenInitialized {
|
||||
c.RUnlock()
|
||||
if err := func() error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
// Double check in case another goroutine has initialized it in the meantime.
|
||||
if !c.hasBeenInitialized {
|
||||
err := init(c.get, c.set)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.hasBeenInitialized = true
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return v, err
|
||||
}
|
||||
// Reacquire the read lock.
|
||||
c.RLock()
|
||||
}
|
||||
|
||||
v = c.m[key]
|
||||
c.RUnlock()
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Set sets the given key to the given value.
|
||||
func (c *Cache[K, T]) Set(key K, value T) {
|
||||
c.Lock()
|
||||
c.m[key] = value
|
||||
c.set(key, value)
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
func (c *Cache[K, T]) set(key K, value T) {
|
||||
c.m[key] = value
|
||||
}
|
||||
|
||||
// ForEeach calls the given function for each key/value pair in the cache.
|
||||
func (c *Cache[K, T]) ForEeach(f func(K, T)) {
|
||||
// If the function returns false, the iteration stops.
|
||||
func (c *Cache[K, T]) ForEeach(f func(K, T) bool) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
for k, v := range c.m {
|
||||
f(k, v)
|
||||
if !f(k, v) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +128,7 @@ func (c *Cache[K, T]) Drain() map[K]T {
|
||||
c.Lock()
|
||||
m := c.m
|
||||
c.m = make(map[K]T)
|
||||
c.hasBeenInitialized = false
|
||||
c.Unlock()
|
||||
return m
|
||||
}
|
||||
@@ -94,6 +142,7 @@ func (c *Cache[K, T]) Len() int {
|
||||
func (c *Cache[K, T]) Reset() {
|
||||
c.Lock()
|
||||
c.m = make(map[K]T)
|
||||
c.hasBeenInitialized = false
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
|
||||
@@ -73,10 +73,14 @@ func TestPrepareParams(t *testing.T) {
|
||||
for i, test := range tests {
|
||||
t.Run(fmt.Sprint(i), func(t *testing.T) {
|
||||
// PrepareParams modifies input.
|
||||
prepareClone := PrepareParamsClone(test.input)
|
||||
PrepareParams(test.input)
|
||||
if !reflect.DeepEqual(test.expected, test.input) {
|
||||
t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, test.input)
|
||||
}
|
||||
if !reflect.DeepEqual(test.expected, prepareClone) {
|
||||
t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, prepareClone)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package maps
|
||||
|
||||
import (
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
)
|
||||
|
||||
// Ordered is a map that can be iterated in the order of insertion.
|
||||
// Note that insertion order is not affected if a key is re-inserted into the map.
|
||||
// In a nil map, all operations are no-ops.
|
||||
// This is not thread safe.
|
||||
type Ordered[K comparable, T any] struct {
|
||||
// The keys in the order they were added.
|
||||
keys []K
|
||||
// The values.
|
||||
values map[K]T
|
||||
}
|
||||
|
||||
// NewOrdered creates a new Ordered map.
|
||||
func NewOrdered[K comparable, T any]() *Ordered[K, T] {
|
||||
return &Ordered[K, T]{values: make(map[K]T)}
|
||||
}
|
||||
|
||||
// Set sets the value for the given key.
|
||||
// Note that insertion order is not affected if a key is re-inserted into the map.
|
||||
func (m *Ordered[K, T]) Set(key K, value T) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
// Check if key already exists.
|
||||
if _, found := m.values[key]; !found {
|
||||
m.keys = append(m.keys, key)
|
||||
}
|
||||
m.values[key] = value
|
||||
}
|
||||
|
||||
// Get gets the value for the given key.
|
||||
func (m *Ordered[K, T]) Get(key K) (T, bool) {
|
||||
if m == nil {
|
||||
var v T
|
||||
return v, false
|
||||
}
|
||||
value, found := m.values[key]
|
||||
return value, found
|
||||
}
|
||||
|
||||
// Delete deletes the value for the given key.
|
||||
func (m *Ordered[K, T]) Delete(key K) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
delete(m.values, key)
|
||||
for i, k := range m.keys {
|
||||
if k == key {
|
||||
m.keys = append(m.keys[:i], m.keys[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clone creates a shallow copy of the map.
|
||||
func (m *Ordered[K, T]) Clone() *Ordered[K, T] {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
clone := NewOrdered[K, T]()
|
||||
for _, k := range m.keys {
|
||||
clone.Set(k, m.values[k])
|
||||
}
|
||||
return clone
|
||||
}
|
||||
|
||||
// Keys returns the keys in the order they were added.
|
||||
func (m *Ordered[K, T]) Keys() []K {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
return m.keys
|
||||
}
|
||||
|
||||
// Values returns the values in the order they were added.
|
||||
func (m *Ordered[K, T]) Values() []T {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
var values []T
|
||||
for _, k := range m.keys {
|
||||
values = append(values, m.values[k])
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// Len returns the number of items in the map.
|
||||
func (m *Ordered[K, T]) Len() int {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
return len(m.keys)
|
||||
}
|
||||
|
||||
// Range calls f sequentially for each key and value present in the map.
|
||||
// If f returns false, range stops the iteration.
|
||||
// TODO(bep) replace with iter.Seq2 when we bump go Go 1.24.
|
||||
func (m *Ordered[K, T]) Range(f func(key K, value T) bool) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
for _, k := range m.keys {
|
||||
if !f(k, m.values[k]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hash calculates a hash from the values.
|
||||
func (m *Ordered[K, T]) Hash() (uint64, error) {
|
||||
if m == nil {
|
||||
return 0, nil
|
||||
}
|
||||
return hashing.Hash(m.values)
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package maps
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestOrdered(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
m := NewOrdered[string, int]()
|
||||
m.Set("a", 1)
|
||||
m.Set("b", 2)
|
||||
m.Set("c", 3)
|
||||
|
||||
c.Assert(m.Keys(), qt.DeepEquals, []string{"a", "b", "c"})
|
||||
c.Assert(m.Values(), qt.DeepEquals, []int{1, 2, 3})
|
||||
|
||||
v, found := m.Get("b")
|
||||
c.Assert(found, qt.Equals, true)
|
||||
c.Assert(v, qt.Equals, 2)
|
||||
|
||||
m.Set("b", 22)
|
||||
c.Assert(m.Keys(), qt.DeepEquals, []string{"a", "b", "c"})
|
||||
c.Assert(m.Values(), qt.DeepEquals, []int{1, 22, 3})
|
||||
|
||||
m.Delete("b")
|
||||
|
||||
c.Assert(m.Keys(), qt.DeepEquals, []string{"a", "c"})
|
||||
c.Assert(m.Values(), qt.DeepEquals, []int{1, 3})
|
||||
}
|
||||
|
||||
func TestOrderedHash(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
m := NewOrdered[string, int]()
|
||||
m.Set("a", 1)
|
||||
m.Set("b", 2)
|
||||
m.Set("c", 3)
|
||||
|
||||
h1, err := m.Hash()
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
m.Set("d", 4)
|
||||
|
||||
h2, err := m.Hash()
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
c.Assert(h1, qt.Not(qt.Equals), h2)
|
||||
|
||||
m = NewOrdered[string, int]()
|
||||
m.Set("b", 2)
|
||||
m.Set("a", 1)
|
||||
m.Set("c", 3)
|
||||
|
||||
h3, err := m.Hash()
|
||||
c.Assert(err, qt.IsNil)
|
||||
// Order does not matter.
|
||||
c.Assert(h1, qt.Equals, h3)
|
||||
}
|
||||
|
||||
func TestOrderedNil(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
var m *Ordered[string, int]
|
||||
|
||||
m.Set("a", 1)
|
||||
c.Assert(m.Keys(), qt.IsNil)
|
||||
c.Assert(m.Values(), qt.IsNil)
|
||||
v, found := m.Get("a")
|
||||
c.Assert(found, qt.Equals, false)
|
||||
c.Assert(v, qt.Equals, 0)
|
||||
m.Delete("a")
|
||||
var b bool
|
||||
m.Range(func(k string, v int) bool {
|
||||
b = true
|
||||
return true
|
||||
})
|
||||
c.Assert(b, qt.Equals, false)
|
||||
c.Assert(m.Len(), qt.Equals, 0)
|
||||
c.Assert(m.Clone(), qt.IsNil)
|
||||
h, err := m.Hash()
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(h, qt.Equals, uint64(0))
|
||||
}
|
||||
+40
-1
@@ -303,7 +303,7 @@ func toMergeStrategy(v any) ParamsMergeStrategy {
|
||||
}
|
||||
|
||||
// PrepareParams
|
||||
// * makes all the keys in the given map lower cased and will do so
|
||||
// * makes all the keys in the given map lower cased and will do so recursively.
|
||||
// * This will modify the map given.
|
||||
// * Any nested map[interface{}]interface{}, map[string]interface{},map[string]string will be converted to Params.
|
||||
// * Any _merge value will be converted to proper type and value.
|
||||
@@ -343,3 +343,42 @@ func PrepareParams(m Params) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PrepareParamsClone is like PrepareParams, but it does not modify the input.
|
||||
func PrepareParamsClone(m Params) Params {
|
||||
m2 := make(Params)
|
||||
for k, v := range m {
|
||||
var retyped bool
|
||||
lKey := strings.ToLower(k)
|
||||
if lKey == MergeStrategyKey {
|
||||
v = toMergeStrategy(v)
|
||||
retyped = true
|
||||
} else {
|
||||
switch vv := v.(type) {
|
||||
case map[any]any:
|
||||
var p Params = cast.ToStringMap(v)
|
||||
v = PrepareParamsClone(p)
|
||||
retyped = true
|
||||
case map[string]any:
|
||||
var p Params = v.(map[string]any)
|
||||
v = PrepareParamsClone(p)
|
||||
retyped = true
|
||||
case map[string]string:
|
||||
p := make(Params)
|
||||
for k, v := range vv {
|
||||
p[k] = v
|
||||
}
|
||||
v = p
|
||||
PrepareParams(p)
|
||||
retyped = true
|
||||
}
|
||||
}
|
||||
|
||||
if retyped || k != lKey {
|
||||
m2[lKey] = v
|
||||
} else {
|
||||
m2[k] = v
|
||||
}
|
||||
}
|
||||
return m2
|
||||
}
|
||||
|
||||
+7
-20
@@ -22,31 +22,18 @@ import (
|
||||
"github.com/gohugoio/hugo/common/math"
|
||||
)
|
||||
|
||||
// Scratch is a writable context used for stateful operations in Page/Node rendering.
|
||||
type StoreProvider interface {
|
||||
// Store returns a Scratch that can be used to store temporary state.
|
||||
// Store is not reset on server rebuilds.
|
||||
Store() *Scratch
|
||||
}
|
||||
|
||||
// Scratch is a writable context used for stateful build operations
|
||||
type Scratch struct {
|
||||
values map[string]any
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// Scratcher provides a scratching service.
|
||||
type Scratcher interface {
|
||||
// Scratch returns a "scratch pad" that can be used to store state.
|
||||
Scratch() *Scratch
|
||||
}
|
||||
|
||||
type scratcher struct {
|
||||
s *Scratch
|
||||
}
|
||||
|
||||
func (s scratcher) Scratch() *Scratch {
|
||||
return s.s
|
||||
}
|
||||
|
||||
// NewScratcher creates a new Scratcher.
|
||||
func NewScratcher() Scratcher {
|
||||
return scratcher{s: NewScratch()}
|
||||
}
|
||||
|
||||
// Add will, for single values, add (using the + operator) the addend to the existing addend (if found).
|
||||
// Supports numeric values and strings.
|
||||
//
|
||||
|
||||
@@ -185,7 +185,7 @@ func TestScratchSetInMap(t *testing.T) {
|
||||
scratch.SetInMap("key", "zyx", "Zyx")
|
||||
scratch.SetInMap("key", "abc", "Abc (updated)")
|
||||
scratch.SetInMap("key", "def", "Def")
|
||||
c.Assert(scratch.GetSortedMapValues("key"), qt.DeepEquals, []any{0: "Abc (updated)", 1: "Def", 2: "Lux", 3: "Zyx"})
|
||||
c.Assert(scratch.GetSortedMapValues("key"), qt.DeepEquals, any([]any{"Abc (updated)", "Def", "Lux", "Zyx"}))
|
||||
}
|
||||
|
||||
func TestScratchDeleteInMap(t *testing.T) {
|
||||
@@ -199,7 +199,7 @@ func TestScratchDeleteInMap(t *testing.T) {
|
||||
scratch.DeleteInMap("key", "abc")
|
||||
scratch.SetInMap("key", "def", "Def")
|
||||
scratch.DeleteInMap("key", "lmn") // Do nothing
|
||||
c.Assert(scratch.GetSortedMapValues("key"), qt.DeepEquals, []any{0: "Def", 1: "Lux", 2: "Zyx"})
|
||||
c.Assert(scratch.GetSortedMapValues("key"), qt.DeepEquals, any([]any{"Def", "Lux", "Zyx"}))
|
||||
}
|
||||
|
||||
func TestScratchGetSortedMapValues(t *testing.T) {
|
||||
|
||||
+22
-24
@@ -26,29 +26,32 @@ func DoArithmetic(a, b any, op rune) (any, error) {
|
||||
var ai, bi int64
|
||||
var af, bf float64
|
||||
var au, bu uint64
|
||||
var isInt, isFloat, isUint bool
|
||||
switch av.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
ai = av.Int()
|
||||
switch bv.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
isInt = true
|
||||
bi = bv.Int()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
isFloat = true
|
||||
af = float64(ai) // may overflow
|
||||
ai = 0
|
||||
bf = bv.Float()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
bu = bv.Uint()
|
||||
if ai >= 0 {
|
||||
isUint = true
|
||||
au = uint64(ai)
|
||||
ai = 0
|
||||
} else {
|
||||
isInt = true
|
||||
bi = int64(bu) // may overflow
|
||||
bu = 0
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("can't apply the operator to the values")
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
isFloat = true
|
||||
af = av.Float()
|
||||
switch bv.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
@@ -66,17 +69,18 @@ func DoArithmetic(a, b any, op rune) (any, error) {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
bi = bv.Int()
|
||||
if bi >= 0 {
|
||||
isUint = true
|
||||
bu = uint64(bi)
|
||||
bi = 0
|
||||
} else {
|
||||
isInt = true
|
||||
ai = int64(au) // may overflow
|
||||
au = 0
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
isFloat = true
|
||||
af = float64(au) // may overflow
|
||||
au = 0
|
||||
bf = bv.Float()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
isUint = true
|
||||
bu = bv.Uint()
|
||||
default:
|
||||
return nil, errors.New("can't apply the operator to the values")
|
||||
@@ -94,38 +98,32 @@ func DoArithmetic(a, b any, op rune) (any, error) {
|
||||
|
||||
switch op {
|
||||
case '+':
|
||||
if ai != 0 || bi != 0 {
|
||||
if isInt {
|
||||
return ai + bi, nil
|
||||
} else if af != 0 || bf != 0 {
|
||||
} else if isFloat {
|
||||
return af + bf, nil
|
||||
} else if au != 0 || bu != 0 {
|
||||
return au + bu, nil
|
||||
}
|
||||
return 0, nil
|
||||
return au + bu, nil
|
||||
case '-':
|
||||
if ai != 0 || bi != 0 {
|
||||
if isInt {
|
||||
return ai - bi, nil
|
||||
} else if af != 0 || bf != 0 {
|
||||
} else if isFloat {
|
||||
return af - bf, nil
|
||||
} else if au != 0 || bu != 0 {
|
||||
return au - bu, nil
|
||||
}
|
||||
return 0, nil
|
||||
return au - bu, nil
|
||||
case '*':
|
||||
if ai != 0 || bi != 0 {
|
||||
if isInt {
|
||||
return ai * bi, nil
|
||||
} else if af != 0 || bf != 0 {
|
||||
} else if isFloat {
|
||||
return af * bf, nil
|
||||
} else if au != 0 || bu != 0 {
|
||||
return au * bu, nil
|
||||
}
|
||||
return 0, nil
|
||||
return au * bu, nil
|
||||
case '/':
|
||||
if bi != 0 {
|
||||
if isInt && bi != 0 {
|
||||
return ai / bi, nil
|
||||
} else if bf != 0 {
|
||||
} else if isFloat && bf != 0 {
|
||||
return af / bf, nil
|
||||
} else if bu != 0 {
|
||||
} else if isUint && bu != 0 {
|
||||
return au / bu, nil
|
||||
}
|
||||
return nil, errors.New("can't divide the value by 0")
|
||||
|
||||
@@ -30,10 +30,12 @@ func TestDoArithmetic(t *testing.T) {
|
||||
expect any
|
||||
}{
|
||||
{3, 2, '+', int64(5)},
|
||||
{0, 0, '+', int64(0)},
|
||||
{3, 2, '-', int64(1)},
|
||||
{3, 2, '*', int64(6)},
|
||||
{3, 2, '/', int64(1)},
|
||||
{3.0, 2, '+', float64(5)},
|
||||
{0.0, 0, '+', float64(0.0)},
|
||||
{3.0, 2, '-', float64(1)},
|
||||
{3.0, 2, '*', float64(6)},
|
||||
{3.0, 2, '/', float64(1.5)},
|
||||
@@ -42,18 +44,22 @@ func TestDoArithmetic(t *testing.T) {
|
||||
{3, 2.0, '*', float64(6)},
|
||||
{3, 2.0, '/', float64(1.5)},
|
||||
{3.0, 2.0, '+', float64(5)},
|
||||
{0.0, 0.0, '+', float64(0.0)},
|
||||
{3.0, 2.0, '-', float64(1)},
|
||||
{3.0, 2.0, '*', float64(6)},
|
||||
{3.0, 2.0, '/', float64(1.5)},
|
||||
{uint(3), uint(2), '+', uint64(5)},
|
||||
{uint(0), uint(0), '+', uint64(0)},
|
||||
{uint(3), uint(2), '-', uint64(1)},
|
||||
{uint(3), uint(2), '*', uint64(6)},
|
||||
{uint(3), uint(2), '/', uint64(1)},
|
||||
{uint(3), 2, '+', uint64(5)},
|
||||
{uint(0), 0, '+', uint64(0)},
|
||||
{uint(3), 2, '-', uint64(1)},
|
||||
{uint(3), 2, '*', uint64(6)},
|
||||
{uint(3), 2, '/', uint64(1)},
|
||||
{3, uint(2), '+', uint64(5)},
|
||||
{0, uint(0), '+', uint64(0)},
|
||||
{3, uint(2), '-', uint64(1)},
|
||||
{3, uint(2), '*', uint64(6)},
|
||||
{3, uint(2), '/', uint64(1)},
|
||||
@@ -66,16 +72,15 @@ func TestDoArithmetic(t *testing.T) {
|
||||
{-3, uint(2), '*', int64(-6)},
|
||||
{-3, uint(2), '/', int64(-1)},
|
||||
{uint(3), 2.0, '+', float64(5)},
|
||||
{uint(0), 0.0, '+', float64(0)},
|
||||
{uint(3), 2.0, '-', float64(1)},
|
||||
{uint(3), 2.0, '*', float64(6)},
|
||||
{uint(3), 2.0, '/', float64(1.5)},
|
||||
{3.0, uint(2), '+', float64(5)},
|
||||
{0.0, uint(0), '+', float64(0)},
|
||||
{3.0, uint(2), '-', float64(1)},
|
||||
{3.0, uint(2), '*', float64(6)},
|
||||
{3.0, uint(2), '/', float64(1.5)},
|
||||
{0, 0, '+', 0},
|
||||
{0, 0, '-', 0},
|
||||
{0, 0, '*', 0},
|
||||
{"foo", "bar", '+', "foobar"},
|
||||
{3, 0, '/', false},
|
||||
{3.0, 0, '/', false},
|
||||
|
||||
+104
-26
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -159,31 +160,6 @@ func Uglify(in string) string {
|
||||
return path.Clean(in)
|
||||
}
|
||||
|
||||
// UrlToFilename converts the URL s to a filename.
|
||||
// If ParseRequestURI fails, the input is just converted to OS specific slashes and returned.
|
||||
func UrlToFilename(s string) (string, bool) {
|
||||
u, err := url.ParseRequestURI(s)
|
||||
if err != nil {
|
||||
return filepath.FromSlash(s), false
|
||||
}
|
||||
|
||||
p := u.Path
|
||||
|
||||
if p == "" {
|
||||
p, _ = url.QueryUnescape(u.Opaque)
|
||||
return filepath.FromSlash(p), true
|
||||
}
|
||||
|
||||
p = filepath.FromSlash(p)
|
||||
|
||||
if u.Host != "" {
|
||||
// C:\data\file.txt
|
||||
p = strings.ToUpper(u.Host) + ":" + p
|
||||
}
|
||||
|
||||
return p, true
|
||||
}
|
||||
|
||||
// URLEscape escapes unicode letters.
|
||||
func URLEscape(uri string) string {
|
||||
// escape unicode letters
|
||||
@@ -193,3 +169,105 @@ func URLEscape(uri string) string {
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// TrimExt trims the extension from a path..
|
||||
func TrimExt(in string) string {
|
||||
return strings.TrimSuffix(in, path.Ext(in))
|
||||
}
|
||||
|
||||
// From https://github.com/golang/go/blob/e0c76d95abfc1621259864adb3d101cf6f1f90fc/src/cmd/go/internal/web/url.go#L45
|
||||
func UrlFromFilename(filename string) (*url.URL, error) {
|
||||
if !filepath.IsAbs(filename) {
|
||||
return nil, fmt.Errorf("filepath must be absolute")
|
||||
}
|
||||
|
||||
// If filename has a Windows volume name, convert the volume to a host and prefix
|
||||
// per https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/.
|
||||
if vol := filepath.VolumeName(filename); vol != "" {
|
||||
if strings.HasPrefix(vol, `\\`) {
|
||||
filename = filepath.ToSlash(filename[2:])
|
||||
i := strings.IndexByte(filename, '/')
|
||||
|
||||
if i < 0 {
|
||||
// A degenerate case.
|
||||
// \\host.example.com (without a share name)
|
||||
// becomes
|
||||
// file://host.example.com/
|
||||
return &url.URL{
|
||||
Scheme: "file",
|
||||
Host: filename,
|
||||
Path: "/",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// \\host.example.com\Share\path\to\file
|
||||
// becomes
|
||||
// file://host.example.com/Share/path/to/file
|
||||
return &url.URL{
|
||||
Scheme: "file",
|
||||
Host: filename[:i],
|
||||
Path: filepath.ToSlash(filename[i:]),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// C:\path\to\file
|
||||
// becomes
|
||||
// file:///C:/path/to/file
|
||||
return &url.URL{
|
||||
Scheme: "file",
|
||||
Path: "/" + filepath.ToSlash(filename),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// /path/to/file
|
||||
// becomes
|
||||
// file:///path/to/file
|
||||
return &url.URL{
|
||||
Scheme: "file",
|
||||
Path: filepath.ToSlash(filename),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UrlStringToFilename converts the URL s to a filename.
|
||||
// If ParseRequestURI fails, the input is just converted to OS specific slashes and returned.
|
||||
func UrlStringToFilename(s string) (string, bool) {
|
||||
u, err := url.ParseRequestURI(s)
|
||||
if err != nil {
|
||||
return filepath.FromSlash(s), false
|
||||
}
|
||||
|
||||
p := u.Path
|
||||
|
||||
if p == "" {
|
||||
p, _ = url.QueryUnescape(u.Opaque)
|
||||
return filepath.FromSlash(p), false
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
return p, true
|
||||
}
|
||||
|
||||
if len(p) == 0 || p[0] != '/' {
|
||||
return filepath.FromSlash(p), false
|
||||
}
|
||||
|
||||
p = filepath.FromSlash(p)
|
||||
|
||||
if len(u.Host) == 1 {
|
||||
// file://c/Users/...
|
||||
return strings.ToUpper(u.Host) + ":" + p, true
|
||||
}
|
||||
|
||||
if u.Host != "" && u.Host != "localhost" {
|
||||
if filepath.VolumeName(u.Host) != "" {
|
||||
return "", false
|
||||
}
|
||||
return `\\` + u.Host + p, true
|
||||
}
|
||||
|
||||
if vol := filepath.VolumeName(p[1:]); vol == "" || strings.HasPrefix(vol, `\\`) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return p[1:], true
|
||||
}
|
||||
|
||||
@@ -19,6 +19,13 @@ type Closer interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
// CloserFunc is a convenience type to create a Closer from a function.
|
||||
type CloserFunc func() error
|
||||
|
||||
func (f CloserFunc) Close() error {
|
||||
return f()
|
||||
}
|
||||
|
||||
type CloseAdder interface {
|
||||
Add(Closer)
|
||||
}
|
||||
|
||||
@@ -18,24 +18,24 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// EvictingStringQueue is a queue which automatically evicts elements from the head of
|
||||
// EvictingQueue is a queue which automatically evicts elements from the head of
|
||||
// the queue when attempting to add new elements onto the queue and it is full.
|
||||
// This queue orders elements LIFO (last-in-first-out). It throws away duplicates.
|
||||
// Note: This queue currently does not contain any remove (poll etc.) methods.
|
||||
type EvictingStringQueue struct {
|
||||
type EvictingQueue[T comparable] struct {
|
||||
size int
|
||||
vals []string
|
||||
set map[string]bool
|
||||
vals []T
|
||||
set map[T]bool
|
||||
mu sync.Mutex
|
||||
zero T
|
||||
}
|
||||
|
||||
// NewEvictingStringQueue creates a new queue with the given size.
|
||||
func NewEvictingStringQueue(size int) *EvictingStringQueue {
|
||||
return &EvictingStringQueue{size: size, set: make(map[string]bool)}
|
||||
// NewEvictingQueue creates a new queue with the given size.
|
||||
func NewEvictingQueue[T comparable](size int) *EvictingQueue[T] {
|
||||
return &EvictingQueue[T]{size: size, set: make(map[T]bool)}
|
||||
}
|
||||
|
||||
// Add adds a new string to the tail of the queue if it's not already there.
|
||||
func (q *EvictingStringQueue) Add(v string) *EvictingStringQueue {
|
||||
func (q *EvictingQueue[T]) Add(v T) *EvictingQueue[T] {
|
||||
q.mu.Lock()
|
||||
if q.set[v] {
|
||||
q.mu.Unlock()
|
||||
@@ -54,7 +54,7 @@ func (q *EvictingStringQueue) Add(v string) *EvictingStringQueue {
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *EvictingStringQueue) Len() int {
|
||||
func (q *EvictingQueue[T]) Len() int {
|
||||
if q == nil {
|
||||
return 0
|
||||
}
|
||||
@@ -64,19 +64,22 @@ func (q *EvictingStringQueue) Len() int {
|
||||
}
|
||||
|
||||
// Contains returns whether the queue contains v.
|
||||
func (q *EvictingStringQueue) Contains(v string) bool {
|
||||
func (q *EvictingQueue[T]) Contains(v T) bool {
|
||||
if q == nil {
|
||||
return false
|
||||
}
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
return q.set[v]
|
||||
}
|
||||
|
||||
// Peek looks at the last element added to the queue.
|
||||
func (q *EvictingStringQueue) Peek() string {
|
||||
func (q *EvictingQueue[T]) Peek() T {
|
||||
q.mu.Lock()
|
||||
l := len(q.vals)
|
||||
if l == 0 {
|
||||
q.mu.Unlock()
|
||||
return ""
|
||||
return q.zero
|
||||
}
|
||||
elem := q.vals[l-1]
|
||||
q.mu.Unlock()
|
||||
@@ -84,9 +87,12 @@ func (q *EvictingStringQueue) Peek() string {
|
||||
}
|
||||
|
||||
// PeekAll looks at all the elements in the queue, with the newest first.
|
||||
func (q *EvictingStringQueue) PeekAll() []string {
|
||||
func (q *EvictingQueue[T]) PeekAll() []T {
|
||||
if q == nil {
|
||||
return nil
|
||||
}
|
||||
q.mu.Lock()
|
||||
vals := make([]string, len(q.vals))
|
||||
vals := make([]T, len(q.vals))
|
||||
copy(vals, q.vals)
|
||||
q.mu.Unlock()
|
||||
for i, j := 0, len(vals)-1; i < j; i, j = i+1, j-1 {
|
||||
@@ -96,9 +102,9 @@ func (q *EvictingStringQueue) PeekAll() []string {
|
||||
}
|
||||
|
||||
// PeekAllSet returns PeekAll as a set.
|
||||
func (q *EvictingStringQueue) PeekAllSet() map[string]bool {
|
||||
func (q *EvictingQueue[T]) PeekAllSet() map[T]bool {
|
||||
all := q.PeekAll()
|
||||
set := make(map[string]bool)
|
||||
set := make(map[T]bool)
|
||||
for _, v := range all {
|
||||
set[v] = true
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
func TestEvictingStringQueue(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
queue := NewEvictingStringQueue(3)
|
||||
queue := NewEvictingQueue[string](3)
|
||||
|
||||
c.Assert(queue.Peek(), qt.Equals, "")
|
||||
queue.Add("a")
|
||||
@@ -53,7 +53,7 @@ func TestEvictingStringQueueConcurrent(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
val := "someval"
|
||||
|
||||
queue := NewEvictingStringQueue(3)
|
||||
queue := NewEvictingQueue[string](3)
|
||||
|
||||
for j := 0; j < 100; j++ {
|
||||
wg.Add(1)
|
||||
|
||||
@@ -133,22 +133,3 @@ func NewBool(b bool) *bool {
|
||||
type PrintableValueProvider interface {
|
||||
PrintableValue() any
|
||||
}
|
||||
|
||||
var _ PrintableValueProvider = Result[any]{}
|
||||
|
||||
// Result is a generic result type.
|
||||
type Result[T any] struct {
|
||||
// The result value.
|
||||
Value T
|
||||
|
||||
// The error value.
|
||||
Err error
|
||||
}
|
||||
|
||||
// PrintableValue returns the value or panics if there is an error.
|
||||
func (r Result[T]) PrintableValue() any {
|
||||
if r.Err != nil {
|
||||
panic(r.Err)
|
||||
}
|
||||
return r.Value
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ type Config struct {
|
||||
|
||||
// The cascade configuration section contains the top level front matter cascade configuration options,
|
||||
// a slice of page matcher and params to apply to those pages.
|
||||
Cascade *config.ConfigNamespace[[]page.PageMatcherParamsConfig, map[page.PageMatcher]maps.Params] `mapstructure:"-"`
|
||||
Cascade *config.ConfigNamespace[[]page.PageMatcherParamsConfig, *maps.Ordered[page.PageMatcher, maps.Params]] `mapstructure:"-"`
|
||||
|
||||
// The segments defines segments for the site. Used for partial/segmented builds.
|
||||
Segments *config.ConfigNamespace[map[string]segments.SegmentConfig, segments.Segments] `mapstructure:"-"`
|
||||
@@ -301,6 +301,18 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
|
||||
}
|
||||
}
|
||||
|
||||
defaultOutputFormat := outputFormats[0]
|
||||
c.DefaultOutputFormat = strings.ToLower(c.DefaultOutputFormat)
|
||||
if c.DefaultOutputFormat != "" {
|
||||
f, found := outputFormats.GetByName(c.DefaultOutputFormat)
|
||||
if !found {
|
||||
return fmt.Errorf("unknown default output format %q", c.DefaultOutputFormat)
|
||||
}
|
||||
defaultOutputFormat = f
|
||||
} else {
|
||||
c.DefaultOutputFormat = defaultOutputFormat.Name
|
||||
}
|
||||
|
||||
disabledLangs := make(map[string]bool)
|
||||
for _, lang := range c.DisableLanguages {
|
||||
disabledLangs[lang] = true
|
||||
@@ -390,23 +402,46 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
|
||||
c.Pagination.Path = c.PaginatePath
|
||||
}
|
||||
|
||||
// Legacy privacy values.
|
||||
if c.Privacy.Twitter.Disable {
|
||||
hugo.Deprecate("site config key privacy.twitter.disable", "Use privacy.x.disable instead.", "v0.141.0")
|
||||
c.Privacy.X.Disable = c.Privacy.Twitter.Disable
|
||||
}
|
||||
|
||||
if c.Privacy.Twitter.EnableDNT {
|
||||
hugo.Deprecate("site config key privacy.twitter.enableDNT", "Use privacy.x.enableDNT instead.", "v0.141.0")
|
||||
c.Privacy.X.EnableDNT = c.Privacy.Twitter.EnableDNT
|
||||
}
|
||||
|
||||
if c.Privacy.Twitter.Simple {
|
||||
hugo.Deprecate("site config key privacy.twitter.simple", "Use privacy.x.simple instead.", "v0.141.0")
|
||||
c.Privacy.X.Simple = c.Privacy.Twitter.Simple
|
||||
}
|
||||
|
||||
// Legacy services values.
|
||||
if c.Services.Twitter.DisableInlineCSS {
|
||||
hugo.Deprecate("site config key services.twitter.disableInlineCSS", "Use services.x.disableInlineCSS instead.", "v0.141.0")
|
||||
c.Services.X.DisableInlineCSS = c.Services.Twitter.DisableInlineCSS
|
||||
}
|
||||
|
||||
c.C = &ConfigCompiled{
|
||||
Timeout: timeout,
|
||||
BaseURL: baseURL,
|
||||
BaseURLLiveReload: baseURL,
|
||||
DisabledKinds: disabledKinds,
|
||||
DisabledLanguages: disabledLangs,
|
||||
IgnoredLogs: ignoredLogIDs,
|
||||
KindOutputFormats: kindOutputFormats,
|
||||
ContentTypes: media.DefaultContentTypes.FromTypes(c.MediaTypes.Config),
|
||||
CreateTitle: helpers.GetTitleFunc(c.TitleCaseStyle),
|
||||
IsUglyURLSection: isUglyURL,
|
||||
IgnoreFile: ignoreFile,
|
||||
SegmentFilter: c.Segments.Config.Get(func(s string) { logger.Warnf("Render segment %q not found in configuration", s) }, c.RootConfig.RenderSegments...),
|
||||
MainSections: c.MainSections,
|
||||
Clock: clock,
|
||||
HTTPCache: httpCache,
|
||||
transientErr: transientErr,
|
||||
Timeout: timeout,
|
||||
BaseURL: baseURL,
|
||||
BaseURLLiveReload: baseURL,
|
||||
DisabledKinds: disabledKinds,
|
||||
DisabledLanguages: disabledLangs,
|
||||
IgnoredLogs: ignoredLogIDs,
|
||||
KindOutputFormats: kindOutputFormats,
|
||||
DefaultOutputFormat: defaultOutputFormat,
|
||||
ContentTypes: media.DefaultContentTypes.FromTypes(c.MediaTypes.Config),
|
||||
CreateTitle: helpers.GetTitleFunc(c.TitleCaseStyle),
|
||||
IsUglyURLSection: isUglyURL,
|
||||
IgnoreFile: ignoreFile,
|
||||
SegmentFilter: c.Segments.Config.Get(func(s string) { logger.Warnf("Render segment %q not found in configuration", s) }, c.RootConfig.RenderSegments...),
|
||||
MainSections: c.MainSections,
|
||||
Clock: clock,
|
||||
HTTPCache: httpCache,
|
||||
transientErr: transientErr,
|
||||
}
|
||||
|
||||
for _, s := range allDecoderSetups {
|
||||
@@ -430,22 +465,23 @@ func (c *Config) IsLangDisabled(lang string) bool {
|
||||
|
||||
// ConfigCompiled holds values and functions that are derived from the config.
|
||||
type ConfigCompiled struct {
|
||||
Timeout time.Duration
|
||||
BaseURL urls.BaseURL
|
||||
BaseURLLiveReload urls.BaseURL
|
||||
ServerInterface string
|
||||
KindOutputFormats map[string]output.Formats
|
||||
ContentTypes media.ContentTypes
|
||||
DisabledKinds map[string]bool
|
||||
DisabledLanguages map[string]bool
|
||||
IgnoredLogs map[string]bool
|
||||
CreateTitle func(s string) string
|
||||
IsUglyURLSection func(section string) bool
|
||||
IgnoreFile func(filename string) bool
|
||||
SegmentFilter segments.SegmentFilter
|
||||
MainSections []string
|
||||
Clock time.Time
|
||||
HTTPCache httpcache.ConfigCompiled
|
||||
Timeout time.Duration
|
||||
BaseURL urls.BaseURL
|
||||
BaseURLLiveReload urls.BaseURL
|
||||
ServerInterface string
|
||||
KindOutputFormats map[string]output.Formats
|
||||
DefaultOutputFormat output.Format
|
||||
ContentTypes media.ContentTypes
|
||||
DisabledKinds map[string]bool
|
||||
DisabledLanguages map[string]bool
|
||||
IgnoredLogs map[string]bool
|
||||
CreateTitle func(s string) string
|
||||
IsUglyURLSection func(section string) bool
|
||||
IgnoreFile func(filename string) bool
|
||||
SegmentFilter segments.SegmentFilter
|
||||
MainSections []string
|
||||
Clock time.Time
|
||||
HTTPCache httpcache.ConfigCompiled
|
||||
|
||||
// This is set to the last transient error found during config compilation.
|
||||
// With themes/modules we compute the configuration in multiple passes, and
|
||||
@@ -505,6 +541,13 @@ type RootConfig struct {
|
||||
// Set this to true to put all languages below their language ID.
|
||||
DefaultContentLanguageInSubdir bool
|
||||
|
||||
// The default output format to use for the site.
|
||||
// If not set, we will use the first output format.
|
||||
DefaultOutputFormat string
|
||||
|
||||
// Disable generation of redirect to the default language when DefaultContentLanguageInSubdir is enabled.
|
||||
DisableDefaultLanguageRedirect bool
|
||||
|
||||
// Disable creation of alias redirect pages.
|
||||
DisableAliases bool
|
||||
|
||||
@@ -723,15 +766,16 @@ type Configs struct {
|
||||
}
|
||||
|
||||
func (c *Configs) Validate(logger loggers.Logger) error {
|
||||
for p := range c.Base.Cascade.Config {
|
||||
c.Base.Cascade.Config.Range(func(p page.PageMatcher, params maps.Params) bool {
|
||||
page.CheckCascadePattern(logger, p)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// transientErr returns the last transient error found during config compilation.
|
||||
func (c *Configs) transientErr() error {
|
||||
for _, l := range c.LanguageConfigSlice {
|
||||
for _, l := range c.LanguageConfigMap {
|
||||
if l.C.transientErr != nil {
|
||||
return l.C.transientErr
|
||||
}
|
||||
@@ -748,7 +792,6 @@ func (c *Configs) Init() error {
|
||||
var languages langs.Languages
|
||||
defaultContentLanguage := c.Base.DefaultContentLanguage
|
||||
for k, v := range c.LanguageConfigMap {
|
||||
c.LanguageConfigSlice = append(c.LanguageConfigSlice, v)
|
||||
languageConf := v.Languages[k]
|
||||
language, err := langs.NewLanguage(k, defaultContentLanguage, v.TimeZone, languageConf)
|
||||
if err != nil {
|
||||
@@ -888,30 +931,17 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
var differentRootKeys []string
|
||||
switch x := v.(type) {
|
||||
case maps.Params:
|
||||
var params maps.Params
|
||||
pv, found := x["params"]
|
||||
if found {
|
||||
params = pv.(maps.Params)
|
||||
} else {
|
||||
params = maps.Params{
|
||||
_, found := x["params"]
|
||||
if !found {
|
||||
x["params"] = maps.Params{
|
||||
maps.MergeStrategyKey: maps.ParamsMergeStrategyDeep,
|
||||
}
|
||||
x["params"] = params
|
||||
}
|
||||
|
||||
for kk, vv := range x {
|
||||
if kk == "_merge" {
|
||||
continue
|
||||
}
|
||||
if kk != maps.MergeStrategyKey && !configLanguageKeys[kk] {
|
||||
// This should have been placed below params.
|
||||
// We accidentally allowed it in the past, so we need to support it a little longer,
|
||||
// But log a warning.
|
||||
if _, found := params[kk]; !found {
|
||||
hugo.Deprecate(fmt.Sprintf("config: languages.%s.%s: custom params on the language top level", k, kk), fmt.Sprintf("Put the value below [languages.%s.params]. See https://gohugo.io/content-management/multilingual/#changes-in-hugo-01120", k), "v0.112.0")
|
||||
params[kk] = vv
|
||||
}
|
||||
}
|
||||
if kk == "baseurl" {
|
||||
// baseURL configure don the language level is a multihost setup.
|
||||
isMultihost = true
|
||||
|
||||
@@ -175,3 +175,43 @@ func TestMapUglyURLs(t *testing.T) {
|
||||
b.Assert(c.C.IsUglyURLSection("posts"), qt.IsTrue)
|
||||
b.Assert(c.C.IsUglyURLSection("blog"), qt.IsFalse)
|
||||
}
|
||||
|
||||
// Issue 13199
|
||||
func TestInvalidOutputFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
|
||||
[outputs]
|
||||
home = ['html','foo']
|
||||
-- layouts/index.html --
|
||||
x
|
||||
`
|
||||
|
||||
b, err := hugolib.TestE(t, files)
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
b.Assert(err.Error(), qt.Contains, `failed to create config: unknown output format "foo" for kind "home"`)
|
||||
}
|
||||
|
||||
// Issue 13201
|
||||
func TestLanguageConfigSlice(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
|
||||
[languages.en]
|
||||
title = 'TITLE_EN'
|
||||
weight = 2
|
||||
[languages.de]
|
||||
title = 'TITLE_DE'
|
||||
weight = 1
|
||||
[languages.fr]
|
||||
title = 'TITLE_FR'
|
||||
weight = 3
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
b.Assert(b.H.Configs.LanguageConfigSlice[0].Title, qt.Equals, `TITLE_DE`)
|
||||
}
|
||||
|
||||
@@ -137,11 +137,11 @@ func (c ConfigLanguage) Watching() bool {
|
||||
return c.m.Base.Internal.Watch
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) NewIdentityManager(name string) identity.Manager {
|
||||
func (c ConfigLanguage) NewIdentityManager(name string, opts ...identity.ManagerOption) identity.Manager {
|
||||
if !c.Watching() {
|
||||
return identity.NopManager
|
||||
}
|
||||
return identity.NewManager(name)
|
||||
return identity.NewManager(name, opts...)
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) ContentTypes() config.ContentTypesProvider {
|
||||
|
||||
@@ -470,7 +470,7 @@ func (l *configLoader) loadModules(configs *Configs, ignoreModuleDoesNotExist bo
|
||||
ignoreVendor, _ = hglob.GetGlob(hglob.NormalizePath(s))
|
||||
}
|
||||
|
||||
ex := hexec.New(conf.Security, workingDir)
|
||||
ex := hexec.New(conf.Security, workingDir, l.Logger)
|
||||
|
||||
hook := func(m *modules.ModulesConfig) error {
|
||||
for _, tc := range m.AllModules {
|
||||
|
||||
@@ -58,7 +58,7 @@ type AllProvider interface {
|
||||
BuildDrafts() bool
|
||||
Running() bool
|
||||
Watching() bool
|
||||
NewIdentityManager(name string) identity.Manager
|
||||
NewIdentityManager(name string, opts ...identity.ManagerOption) identity.Manager
|
||||
FastRenderMode() bool
|
||||
PrintUnusedTemplates() bool
|
||||
EnableMissingTranslationPlaceholders() bool
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ import (
|
||||
func DecodeNamespace[S, C any](configSource any, buildConfig func(any) (C, any, error)) (*ConfigNamespace[S, C], error) {
|
||||
// Calculate the hash of the input (not including any defaults applied later).
|
||||
// This allows us to introduce new config options without breaking the hash.
|
||||
h := hashing.HashString(configSource)
|
||||
h := hashing.HashStringHex(configSource)
|
||||
|
||||
// Build the config
|
||||
c, ext, err := buildConfig(configSource)
|
||||
|
||||
@@ -43,7 +43,7 @@ func TestNamespace(t *testing.T) {
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(ns, qt.Not(qt.IsNil))
|
||||
c.Assert(ns.SourceStructure, qt.DeepEquals, map[string]interface{}{"foo": "bar"})
|
||||
c.Assert(ns.SourceHash, qt.Equals, "1450430416588600409")
|
||||
c.Assert(ns.SourceHash, qt.Equals, "1420f6c7782f7459")
|
||||
c.Assert(ns.Config, qt.DeepEquals, &tstNsExt{Foo: "bar"})
|
||||
c.Assert(ns.Signature(), qt.DeepEquals, []*tstNsExt(nil))
|
||||
}
|
||||
|
||||
@@ -30,9 +30,10 @@ type Config struct {
|
||||
Disqus Disqus
|
||||
GoogleAnalytics GoogleAnalytics
|
||||
Instagram Instagram
|
||||
Twitter Twitter
|
||||
Twitter Twitter // deprecated in favor of X in v0.141.0
|
||||
Vimeo Vimeo
|
||||
YouTube YouTube
|
||||
X X
|
||||
}
|
||||
|
||||
// Disqus holds the privacy configuration settings related to the Disqus template.
|
||||
@@ -58,7 +59,8 @@ type Instagram struct {
|
||||
Simple bool
|
||||
}
|
||||
|
||||
// Twitter holds the privacy configuration settingsrelated to the Twitter shortcode.
|
||||
// Twitter holds the privacy configuration settings related to the Twitter shortcode.
|
||||
// Deprecated in favor of X in v0.141.0.
|
||||
type Twitter struct {
|
||||
Service `mapstructure:",squash"`
|
||||
|
||||
@@ -70,7 +72,7 @@ type Twitter struct {
|
||||
Simple bool
|
||||
}
|
||||
|
||||
// Vimeo holds the privacy configuration settingsrelated to the Vimeo shortcode.
|
||||
// Vimeo holds the privacy configuration settings related to the Vimeo shortcode.
|
||||
type Vimeo struct {
|
||||
Service `mapstructure:",squash"`
|
||||
|
||||
@@ -84,7 +86,7 @@ type Vimeo struct {
|
||||
Simple bool
|
||||
}
|
||||
|
||||
// YouTube holds the privacy configuration settingsrelated to the YouTube shortcode.
|
||||
// YouTube holds the privacy configuration settings related to the YouTube shortcode.
|
||||
type YouTube struct {
|
||||
Service `mapstructure:",squash"`
|
||||
|
||||
@@ -94,6 +96,20 @@ type YouTube struct {
|
||||
PrivacyEnhanced bool
|
||||
}
|
||||
|
||||
// X holds the privacy configuration settings related to the X shortcode.
|
||||
type X struct {
|
||||
Service `mapstructure:",squash"`
|
||||
|
||||
// When set to true, the X post and its embedded page on your site are not
|
||||
// used for purposes that include personalized suggestions and personalized
|
||||
// ads.
|
||||
EnableDNT bool
|
||||
|
||||
// If simple mode is enabled, a static and no-JS version of the X post will
|
||||
// be built.
|
||||
Simple bool
|
||||
}
|
||||
|
||||
// DecodeConfig creates a privacy Config from a given Hugo configuration.
|
||||
func DecodeConfig(cfg config.Provider) (pc Config, err error) {
|
||||
if !cfg.IsSet(privacyConfigKey) {
|
||||
|
||||
@@ -40,6 +40,10 @@ simple = true
|
||||
disable = true
|
||||
enableDNT = true
|
||||
simple = true
|
||||
[privacy.x]
|
||||
disable = true
|
||||
enableDNT = true
|
||||
simple = true
|
||||
[privacy.vimeo]
|
||||
disable = true
|
||||
enableDNT = true
|
||||
@@ -61,7 +65,8 @@ simple = true
|
||||
pc.GoogleAnalytics.RespectDoNotTrack, pc.Instagram.Disable,
|
||||
pc.Instagram.Simple, pc.Twitter.Disable, pc.Twitter.EnableDNT,
|
||||
pc.Twitter.Simple, pc.Vimeo.Disable, pc.Vimeo.EnableDNT, pc.Vimeo.Simple,
|
||||
pc.YouTube.PrivacyEnhanced, pc.YouTube.Disable,
|
||||
pc.YouTube.PrivacyEnhanced, pc.YouTube.Disable, pc.X.Disable, pc.X.EnableDNT,
|
||||
pc.X.Simple,
|
||||
}
|
||||
|
||||
c.Assert(got, qt.All(qt.Equals), true)
|
||||
|
||||
@@ -31,7 +31,8 @@ type Config struct {
|
||||
Disqus Disqus
|
||||
GoogleAnalytics GoogleAnalytics
|
||||
Instagram Instagram
|
||||
Twitter Twitter
|
||||
Twitter Twitter // deprecated in favor of X in v0.141.0
|
||||
X X
|
||||
RSS RSS
|
||||
}
|
||||
|
||||
@@ -61,6 +62,7 @@ type Instagram struct {
|
||||
}
|
||||
|
||||
// Twitter holds the functional configuration settings related to the Twitter shortcodes.
|
||||
// Deprecated in favor of X in v0.141.0.
|
||||
type Twitter struct {
|
||||
// The Simple variant of Twitter is decorated with a basic set of inline styles.
|
||||
// This means that if you want to provide your own CSS, you want
|
||||
@@ -68,6 +70,14 @@ type Twitter struct {
|
||||
DisableInlineCSS bool
|
||||
}
|
||||
|
||||
// X holds the functional configuration settings related to the X shortcodes.
|
||||
type X struct {
|
||||
// The Simple variant of X is decorated with a basic set of inline styles.
|
||||
// This means that if you want to provide your own CSS, you want
|
||||
// to disable the inline CSS provided by Hugo.
|
||||
DisableInlineCSS bool
|
||||
}
|
||||
|
||||
// RSS holds the functional configuration settings related to the RSS feeds.
|
||||
type RSS struct {
|
||||
// Limit the number of pages.
|
||||
|
||||
@@ -36,6 +36,8 @@ id = "ga_id"
|
||||
disableInlineCSS = true
|
||||
[services.twitter]
|
||||
disableInlineCSS = true
|
||||
[services.x]
|
||||
disableInlineCSS = true
|
||||
`
|
||||
cfg, err := config.FromConfigString(tomlConfig, "toml")
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
+6
-4
@@ -82,11 +82,13 @@ func NewContent(h *hugolib.HugoSites, kind, targetPath string, force bool) error
|
||||
b.setArcheTypeFilenameToUse(ext)
|
||||
|
||||
withBuildLock := func() (string, error) {
|
||||
unlock, err := h.BaseFs.LockBuild()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to acquire a build lock: %s", err)
|
||||
if !h.Configs.Base.NoBuildLock {
|
||||
unlock, err := h.BaseFs.LockBuild()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to acquire a build lock: %s", err)
|
||||
}
|
||||
defer unlock()
|
||||
}
|
||||
defer unlock()
|
||||
|
||||
if b.isDir {
|
||||
return "", b.buildDir()
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !nodeploy
|
||||
// +build !nodeploy
|
||||
//go:build withdeploy
|
||||
// +build withdeploy
|
||||
|
||||
package deploy
|
||||
|
||||
|
||||
+2
-2
@@ -11,8 +11,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !nodeploy
|
||||
// +build !nodeploy
|
||||
//go:build withdeploy
|
||||
// +build withdeploy
|
||||
|
||||
package deploy
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !solaris && !nodeploy
|
||||
// +build !solaris,!nodeploy
|
||||
//go:build !solaris && withdeploy
|
||||
// +build !solaris,withdeploy
|
||||
|
||||
package deploy
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !nodeploy
|
||||
// +build !nodeploy
|
||||
//go:build withdeploy
|
||||
// +build withdeploy
|
||||
|
||||
package deploy
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !nodeploy
|
||||
// +build !nodeploy
|
||||
//go:build withdeploy
|
||||
// +build withdeploy
|
||||
|
||||
package deployconfig
|
||||
|
||||
|
||||
+2
-2
@@ -11,8 +11,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !nodeploy
|
||||
// +build !nodeploy
|
||||
//go:build withdeploy
|
||||
// +build withdeploy
|
||||
|
||||
package deploy
|
||||
|
||||
|
||||
Vendored
+55
-13
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/internal/js"
|
||||
"github.com/gohugoio/hugo/internal/warpc"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
@@ -47,6 +49,9 @@ type Deps struct {
|
||||
// The templates to use. This will usually implement the full tpl.TemplateManager.
|
||||
tmplHandlers *tpl.TemplateHandlers
|
||||
|
||||
// The template funcs.
|
||||
TmplFuncMap map[string]any
|
||||
|
||||
// The file systems to use.
|
||||
Fs *hugofs.Fs `json:"-"`
|
||||
|
||||
@@ -83,10 +88,13 @@ type Deps struct {
|
||||
Metrics metrics.Provider
|
||||
|
||||
// BuildStartListeners will be notified before a build starts.
|
||||
BuildStartListeners *Listeners
|
||||
BuildStartListeners *Listeners[any]
|
||||
|
||||
// BuildEndListeners will be notified after a build finishes.
|
||||
BuildEndListeners *Listeners
|
||||
BuildEndListeners *Listeners[any]
|
||||
|
||||
// OnChangeListeners will be notified when something changes.
|
||||
OnChangeListeners *Listeners[identity.Identity]
|
||||
|
||||
// Resources that gets closed when the build is done or the server shuts down.
|
||||
BuildClosers *types.Closers
|
||||
@@ -98,6 +106,12 @@ type Deps struct {
|
||||
// TODO(bep) rethink this re. a plugin setup, but this will have to do for now.
|
||||
WasmDispatchers *warpc.Dispatchers
|
||||
|
||||
// The JS batcher client.
|
||||
JSBatcherClient js.BatcherClient
|
||||
|
||||
// The JS batcher client.
|
||||
// JSBatcherClient *esbuild.BatcherClient
|
||||
|
||||
isClosed bool
|
||||
|
||||
*globalErrHandler
|
||||
@@ -154,23 +168,27 @@ func (d *Deps) Init() error {
|
||||
}
|
||||
|
||||
if d.BuildStartListeners == nil {
|
||||
d.BuildStartListeners = &Listeners{}
|
||||
d.BuildStartListeners = &Listeners[any]{}
|
||||
}
|
||||
|
||||
if d.BuildEndListeners == nil {
|
||||
d.BuildEndListeners = &Listeners{}
|
||||
d.BuildEndListeners = &Listeners[any]{}
|
||||
}
|
||||
|
||||
if d.BuildClosers == nil {
|
||||
d.BuildClosers = &types.Closers{}
|
||||
}
|
||||
|
||||
if d.OnChangeListeners == nil {
|
||||
d.OnChangeListeners = &Listeners[identity.Identity]{}
|
||||
}
|
||||
|
||||
if d.Metrics == nil && d.Conf.TemplateMetrics() {
|
||||
d.Metrics = metrics.NewProvider(d.Conf.TemplateMetricsHints())
|
||||
}
|
||||
|
||||
if d.ExecHelper == nil {
|
||||
d.ExecHelper = hexec.New(d.Conf.GetConfigSection("security").(security.Config), d.Conf.WorkingDir())
|
||||
d.ExecHelper = hexec.New(d.Conf.GetConfigSection("security").(security.Config), d.Conf.WorkingDir(), d.Log)
|
||||
}
|
||||
|
||||
if d.MemCache == nil {
|
||||
@@ -268,6 +286,23 @@ func (d *Deps) Compile(prototype *Deps) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MkdirTemp returns a temporary directory path that will be cleaned up on exit.
|
||||
func (d Deps) MkdirTemp(pattern string) (string, error) {
|
||||
filename, err := os.MkdirTemp("", pattern)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
d.BuildClosers.Add(
|
||||
types.CloserFunc(
|
||||
func() error {
|
||||
return os.RemoveAll(filename)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
return filename, nil
|
||||
}
|
||||
|
||||
type globalErrHandler struct {
|
||||
logger loggers.Logger
|
||||
|
||||
@@ -306,15 +341,16 @@ func (e *globalErrHandler) StopErrorCollector() {
|
||||
}
|
||||
|
||||
// Listeners represents an event listener.
|
||||
type Listeners struct {
|
||||
type Listeners[T any] struct {
|
||||
sync.Mutex
|
||||
|
||||
// A list of funcs to be notified about an event.
|
||||
listeners []func()
|
||||
// If the return value is true, the listener will be removed.
|
||||
listeners []func(...T) bool
|
||||
}
|
||||
|
||||
// Add adds a function to a Listeners instance.
|
||||
func (b *Listeners) Add(f func()) {
|
||||
func (b *Listeners[T]) Add(f func(...T) bool) {
|
||||
if b == nil {
|
||||
return
|
||||
}
|
||||
@@ -324,12 +360,16 @@ func (b *Listeners) Add(f func()) {
|
||||
}
|
||||
|
||||
// Notify executes all listener functions.
|
||||
func (b *Listeners) Notify() {
|
||||
func (b *Listeners[T]) Notify(vs ...T) {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
temp := b.listeners[:0]
|
||||
for _, notify := range b.listeners {
|
||||
notify()
|
||||
if !notify(vs...) {
|
||||
temp = append(temp, notify)
|
||||
}
|
||||
}
|
||||
b.listeners = temp
|
||||
}
|
||||
|
||||
// ResourceProvider is used to create and refresh, and clone resources needed.
|
||||
@@ -372,9 +412,11 @@ type DepsCfg struct {
|
||||
// The logging level to use.
|
||||
LogLevel logg.Level
|
||||
|
||||
// Where to write the logs.
|
||||
// Currently we typically write everything to stdout.
|
||||
LogOut io.Writer
|
||||
// Logging output.
|
||||
StdErr io.Writer
|
||||
|
||||
// The console output.
|
||||
StdOut io.Writer
|
||||
|
||||
// The file systems to use
|
||||
Fs *hugofs.Fs
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
"stringifier",
|
||||
"struct",
|
||||
"toclevels",
|
||||
"unpublishdate",
|
||||
"zgotmplz",
|
||||
"# ----------------------------------------------------------------------",
|
||||
"# cspell: ignore foreign language words",
|
||||
@@ -129,6 +130,7 @@
|
||||
"Samsa",
|
||||
"Stucki",
|
||||
"Thénardier",
|
||||
"WASI",
|
||||
"# ----------------------------------------------------------------------",
|
||||
"# cspell: ignore operating systems and software packages",
|
||||
"# ----------------------------------------------------------------------",
|
||||
@@ -158,10 +160,12 @@
|
||||
"achristie",
|
||||
"ddmaurier",
|
||||
"dring",
|
||||
"fleqn",
|
||||
"inor",
|
||||
"jausten",
|
||||
"jdoe",
|
||||
"jsmith",
|
||||
"leqno",
|
||||
"milli",
|
||||
"rgba",
|
||||
"rsmith",
|
||||
|
||||
@@ -23,3 +23,4 @@ MD046: false
|
||||
MD049: false
|
||||
MD050: false
|
||||
MD053: false
|
||||
MD055: false
|
||||
|
||||
+1
-3
@@ -7,12 +7,10 @@ A fast and flexible static site generator built with love by [bep], [spf13], and
|
||||
[](https://app.netlify.com/sites/gohugoio/deploys)
|
||||
[](https://gohugo.io/contribute/documentation/)
|
||||
|
||||
This is the repository for the [Hugo](https://github.com/gohugoio/hugo) documentation site.
|
||||
This is the repository for the [Hugo](https://github.com/gohugoio/hugo) documentation site.
|
||||
|
||||
Please see the [contributing] section for guidelines, examples, and process.
|
||||
|
||||
|
||||
|
||||
[bep]: https://github.com/bep
|
||||
[spf13]: https://github.com/spf13
|
||||
[friends]: https://github.com/gohugoio/hugo/graphs/contributors
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 0.2em;
|
||||
padding: 2px 3px;
|
||||
margin: 0;
|
||||
font-size: 85%;
|
||||
font-size: 93.75%;
|
||||
background-color: rgba(27,31,35,0.05);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
+1
-7
@@ -4,13 +4,7 @@ code, .code, pre code, .highlight pre {
|
||||
}
|
||||
|
||||
.sans-serif {
|
||||
font-family: 'Muli',
|
||||
avenir,
|
||||
'helvetica neue', helvetica,
|
||||
ubuntu,
|
||||
roboto, noto,
|
||||
'segoe ui', arial,
|
||||
sans-serif;
|
||||
font-family: 'Muli', Avenir, 'Helvetica Neue', Helvetica, Roboto, Noto, 'Segoe UI', Arial, sans-serif;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -18,3 +18,8 @@ h6:first-of-type {
|
||||
h6 ~ p {
|
||||
margin: 0.5em 0 2em 0;
|
||||
}
|
||||
|
||||
/* QR codes */
|
||||
img.qrcode {
|
||||
width: initial;
|
||||
}
|
||||
|
||||
+20
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.1 KiB |
@@ -4769,9 +4769,9 @@ h6:hover .header-link {
|
||||
margin: 0;
|
||||
}
|
||||
code {
|
||||
padding: 0.2em;
|
||||
padding: 2px 3px;
|
||||
margin: 0;
|
||||
font-size: 85%;
|
||||
font-size: 93.75%;
|
||||
background-color: rgba(27, 31, 35, .05);
|
||||
border-radius: 3px;
|
||||
}
|
||||
@@ -5002,13 +5002,7 @@ code, .code, pre code, .highlight pre {
|
||||
font-family: 'inconsolata',Menlo,Monaco,'Courier New',monospace;
|
||||
}
|
||||
.sans-serif {
|
||||
font-family: 'Muli',
|
||||
avenir,
|
||||
'helvetica neue', helvetica,
|
||||
ubuntu,
|
||||
roboto, noto,
|
||||
'segoe ui', arial,
|
||||
sans-serif;
|
||||
font-family: 'Muli', Avenir, 'Helvetica Neue', Helvetica, Roboto, Noto, 'Segoe UI', Arial, sans-serif;
|
||||
}
|
||||
.serif {
|
||||
font-family: Palatino,"Palatino Linotype","Palatino LT STD","Book Antiqua",Georgia,serif;
|
||||
@@ -5339,6 +5333,11 @@ h6:first-of-type {
|
||||
h6 ~ p {
|
||||
margin: 0.5em 0 2em 0;
|
||||
}
|
||||
/* QR codes */
|
||||
img.qrcode {
|
||||
width: auto;
|
||||
width: initial;
|
||||
}
|
||||
.nested-blockquote blockquote {
|
||||
border-left: 4px solid #0594CB;
|
||||
padding-left: 1em;
|
||||
|
||||
@@ -6,13 +6,12 @@
|
||||
bgcolor = "#ffffff"
|
||||
|
||||
[[banners]]
|
||||
name = "Route4Me"
|
||||
link = "https://route4me.com/"
|
||||
title = "Route Planning & Route Optimization Software"
|
||||
name = "GoLand"
|
||||
title = "The complete IDE crafted for professional Go developers."
|
||||
no_query_params = true
|
||||
utm_campaign = "hugosponsor"
|
||||
bgcolor = "#334799"
|
||||
link_attr = "style='color: #ffffff; font-weight: bold; text-decoration: none; text-align: center'"
|
||||
link = "https://www.jetbrains.com/go/?utm_source=OSS&utm_medium=referral&utm_campaign=hugo"
|
||||
logo = "images/sponsors/goland.svg"
|
||||
bgcolor = "#f4f4f4"
|
||||
|
||||
[[banners]]
|
||||
name = "Your Company?"
|
||||
|
||||
+1
-6
@@ -98,12 +98,7 @@ either of these shortcodes in conjunction with this render hook.
|
||||
{{- end }}
|
||||
|
||||
{{- /* Determine content path for warning and error messages. */}}
|
||||
{{- $contentPath := "" }}
|
||||
{{- with .Page.File }}
|
||||
{{- $contentPath = .Path }}
|
||||
{{- else }}
|
||||
{{- $contentPath = .Path }}
|
||||
{{- end }}
|
||||
{{- $contentPath := .Page.String }}
|
||||
|
||||
{{- /* Parse destination. */}}
|
||||
{{- $u := urls.Parse .Destination }}
|
||||
|
||||
+1
-3
@@ -106,9 +106,7 @@
|
||||
</head>
|
||||
|
||||
<body
|
||||
class="ma0 sans-serif bg-primary-color-light{{ with getenv "HUGO_ENV" }}
|
||||
{{ . }}
|
||||
{{ end }} {{ if $hasMath }}{{ print " dn" }}{{ end }}">
|
||||
class="ma0 sans-serif bg-primary-color-light{{ with getenv "HUGO_ENV" }} {{ . }}{{ end }}">
|
||||
{{ partial "hooks/after-body-start.html" . }}
|
||||
{{ block "nav" . }}{{ partial "site-nav.html" . }}{{ end }}
|
||||
{{ block "header" . }}{{ end }}
|
||||
|
||||
@@ -22,8 +22,15 @@
|
||||
{{ $releases = where $releases "draft" false }}
|
||||
{{ $releases = where $releases "prerelease" false }}
|
||||
{{ range $releases | first 20 }}
|
||||
{{ $publishDate := .published_at | time.AsTime }}
|
||||
|
||||
{{/* Correct the v0.138.0 release date. See https://github.com/gohugoio/hugo/issues/13066. */}}
|
||||
{{ if eq .name "v0.138.0" }}
|
||||
{{ $publishDate = "2024-11-06T11:22:34Z" | time.AsTime }}
|
||||
{{ end }}
|
||||
|
||||
{{ $ctx := dict
|
||||
"Date" (.published_at | time.AsTime)
|
||||
"Date" $publishDate
|
||||
"Title" (printf "Release %s" .name)
|
||||
"Permalink" .html_url
|
||||
"Section" "news"
|
||||
|
||||
@@ -21,14 +21,21 @@
|
||||
{{- $releases = where $releases "draft" false }}
|
||||
{{- $releases = where $releases "prerelease" false }}
|
||||
{{- range $releases | first 20 }}
|
||||
{{- $publishDate := .published_at | time.AsTime }}
|
||||
|
||||
{{- /* Correct the v0.138.0 release date. See https://github.com/gohugoio/hugo/issues/13066. */}}
|
||||
{{- if eq .name "v0.138.0" }}
|
||||
{{- $publishDate = "2024-11-06T11:22:34Z" | time.AsTime }}
|
||||
{{- end }}
|
||||
|
||||
{{- $summary := printf
|
||||
"Hugo %s was released on %s. See [release notes](%s) for details."
|
||||
.tag_name
|
||||
(.published_at | time.AsTime | time.Format "2 Jan 2006")
|
||||
($publishDate | time.AsTime | time.Format "2 Jan 2006")
|
||||
.html_url
|
||||
}}
|
||||
{{- $ctx := dict
|
||||
"PublishDate" (.published_at | time.AsTime)
|
||||
"PublishDate" $publishDate
|
||||
"Title" (printf "Release %s" .name)
|
||||
"Permalink" .html_url
|
||||
"Section" "news"
|
||||
|
||||
+4
-4
@@ -4,14 +4,14 @@
|
||||
|
||||
{{ $data := "" }}
|
||||
{{ $url := urls.JoinPath "https://api.github.com/users" $author }}
|
||||
{{ with resources.GetRemote $url }}
|
||||
{{ with try (resources.GetRemote $url) }}
|
||||
{{ with .Err }}
|
||||
{{ errorf "%s" . }}
|
||||
{{ else }}
|
||||
{{ else with .Value }}
|
||||
{{ $data = . | transform.Unmarshal }}
|
||||
{{ else }}
|
||||
{{ errorf "Unable to get remote resource %q" $url }}
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{ errorf "Unable to get remote resource %q" $url }}
|
||||
{{ end }}
|
||||
|
||||
<div class="tc">
|
||||
|
||||
Generated
+4
-4
@@ -4,14 +4,14 @@
|
||||
|
||||
{{ $data := "" }}
|
||||
{{ $url := urls.JoinPath "https://api.github.com/users" $author }}
|
||||
{{ with resources.GetRemote $url }}
|
||||
{{ with try (resources.GetRemote $url) }}
|
||||
{{ with .Err }}
|
||||
{{ errorf "%s" . }}
|
||||
{{ else }}
|
||||
{{ else with .Value }}
|
||||
{{ $data = . | transform.Unmarshal }}
|
||||
{{ else }}
|
||||
{{ errorf "Unable to get remote resource %q" $url }}
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{ errorf "Unable to get remote resource %q" $url }}
|
||||
{{ end }}
|
||||
|
||||
{{ with $data }}
|
||||
|
||||
Generated
+1
-1
@@ -25,7 +25,7 @@
|
||||
{{ $query_params := .query_params | default "" }}
|
||||
{{ $url := .link }}
|
||||
{{ if not .no_query_params }}
|
||||
{{ $url = printf "%s?%s%s" .link $query_params (querify "utm_source" (.utm_source | default $utmSource ) "utm_medium" "banner" "utm_campaign" (.utm_campaign | default "hugosponsor") "utm_content" (.utm_content | default "gohugoio")) | safeURL }}
|
||||
{{ $url = printf "%s?%s%s" .link $query_params (querify "utm_source" (.utm_source | default $utmSource ) "utm_medium" (.utm_medium | default "banner") "utm_campaign" (.utm_campaign | default "hugosponsor") "utm_content" (.utm_content | default "gohugoio")) | safeURL }}
|
||||
{{ end }}
|
||||
{{ $logo := resources.Get .logo }}
|
||||
{{ $gtagID := printf "Sponsor %s %s" .name $gtag | title }}
|
||||
|
||||
@@ -2,15 +2,8 @@
|
||||
<script>
|
||||
MathJax = {
|
||||
tex: {
|
||||
inlineMath: [['$', '$'], ['\\(', '\\)']], // inline
|
||||
displayMath: [['$$', '$$'], ['\\[', '\\]']] // block
|
||||
},
|
||||
startup: {
|
||||
pageReady: () => {
|
||||
return MathJax.startup.defaultPageReady().then(() => {
|
||||
document.body.classList.remove("dn");
|
||||
});
|
||||
}
|
||||
displayMath: [['\\[', '\\]'], ['$$', '$$']], // block
|
||||
inlineMath: [['\\(', '\\)']] // inline
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
<h2>{{ $heading }}</h2>
|
||||
<ul>
|
||||
{{- range . }}
|
||||
<li><a href="{{ .RelPermalink }}">{{ .Title }}</a></li>
|
||||
<li><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></li>
|
||||
{{- end }}
|
||||
</ul>
|
||||
{{- end }}
|
||||
|
||||
-6
@@ -1,9 +1,3 @@
|
||||
<a
|
||||
href="https://twitter.com/intent/follow?screen_name=gohugoiov2"
|
||||
title="Follow on Twitter"
|
||||
class="link-transition twitter link dib z-999 pt3 pt0-l mr2">
|
||||
{{ partial "svg/twitter.svg" (dict "size" "32px") }}
|
||||
</a>
|
||||
<a
|
||||
rel="me"
|
||||
class="mstdn mr3"
|
||||
|
||||
Generated
+4
-4
@@ -11,13 +11,13 @@ Supports CSV, JSON, TOML, YAML, and XML.
|
||||
|
||||
{{ $url := . }}
|
||||
{{ $data := dict }}
|
||||
{{ with resources.GetRemote $url }}
|
||||
{{ with try (resources.GetRemote $url) }}
|
||||
{{ with .Err }}
|
||||
{{ errorf "%s" . }}
|
||||
{{ else }}
|
||||
{{ else with .Value }}
|
||||
{{ $data = .Content | transform.Unmarshal }}
|
||||
{{ else }}
|
||||
{{ errorf "Unable to get remote resource %q" $url }}
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{ errorf "Unable to get remote resource %q" $url }}
|
||||
{{ end }}
|
||||
{{ return $data }}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ If you have an "older" site running on Netlify, you may have to set GO_VERSION t
|
||||
|
||||
For more information about Go Modules, see:
|
||||
|
||||
* https://github.com/golang/go/wiki/Modules
|
||||
* https://go.dev/wiki/Modules
|
||||
* https://blog.golang.org/using-go-modules
|
||||
` }}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{{- /*
|
||||
Returns syntax-highlighted code from the given text.
|
||||
|
||||
This is useful as a terse way to highlight inline code snippets. Calling the
|
||||
highlight shortcode for inline snippets is verbose.
|
||||
*/}}
|
||||
|
||||
{{- $code := .Inner | strings.TrimSpace }}
|
||||
{{- $lang := or (.Get 0) "go" }}
|
||||
{{- $opts := dict "hl_inline" true "noClasses" true }}
|
||||
{{- transform.Highlight $code $lang $opts }}
|
||||
+18
-8
@@ -120,8 +120,8 @@ Renders the given image using the given filter, if any.
|
||||
|
||||
{{- $validFilters := slice
|
||||
"autoorient" "brightness" "colorbalance" "colorize" "contrast" "dither"
|
||||
"gamma" "gaussianblur" "grayscale" "hue" "invert" "none" "opacity" "overlay"
|
||||
"padding" "pixelate" "process" "saturation" "sepia" "sigmoid" "text"
|
||||
"gamma" "gaussianblur" "grayscale" "hue" "invert" "mask" "none" "opacity"
|
||||
"overlay" "padding" "pixelate" "process" "saturation" "sepia" "sigmoid" "text"
|
||||
"unsharpmask"
|
||||
}}
|
||||
|
||||
@@ -229,6 +229,12 @@ Renders the given image using the given filter, if any.
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 0) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $f = images.Invert }}
|
||||
{{- else if eq $filter "mask" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 1) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $ctx := dict "src" (index $filterArgs 0) "name" .Name "position" .Position }}
|
||||
{{- $maskImage := partial "inline/get-resource.html" $ctx }}
|
||||
{{- $f = images.Mask $maskImage }}
|
||||
{{- else if eq $filter "opacity" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 1) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
@@ -322,11 +328,15 @@ Renders the given image using the given filter, if any.
|
||||
{{- end }}
|
||||
|
||||
{{- /* Render. */}}
|
||||
{{- $class := "di va b--black-20" }}
|
||||
{{- if eq $filter "mask" }}
|
||||
{{- $class = "di va" }}
|
||||
{{- end }}
|
||||
{{- if $example }}
|
||||
<p>Original</p>
|
||||
<img class='di ba b--black-20' style="width: initial;" src="{{ $i.RelPermalink }}" alt="{{ $alt }}">
|
||||
<img class="{{ $class}}" style="width: initial;" src="{{ $i.RelPermalink }}" alt="{{ $alt }}">
|
||||
<p>Processed</p>
|
||||
<img class='di ba b--black-20' style="width: initial;" src="{{ $fi.RelPermalink }}" alt="{{ $alt }}">
|
||||
<img class="{{ $class }}" style="width: initial;" src="{{ $fi.RelPermalink }}" alt="{{ $alt }}">
|
||||
{{- else -}}
|
||||
<img class='di' style="width: initial;" src="{{ $fi.RelPermalink }}" alt="{{ $alt }}">
|
||||
{{- end }}
|
||||
@@ -354,15 +364,15 @@ Renders the given image using the given filter, if any.
|
||||
{{- $u := urls.Parse .src }}
|
||||
{{- $msg := "The %q shortcode was unable to resolve %s. See %s" }}
|
||||
{{- if $u.IsAbs }}
|
||||
{{- with resources.GetRemote $u.String }}
|
||||
{{- with try (resources.GetRemote $u.String) }}
|
||||
{{- with .Err }}
|
||||
{{- errorf "%s" . }}
|
||||
{{- else }}
|
||||
{{- else with .Value }}
|
||||
{{- /* This is a remote resource. */}}
|
||||
{{- $r = . }}
|
||||
{{- else }}
|
||||
{{- errorf $msg $.name $u.String $.position }}
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
{{- errorf $msg $.name $u.String $.position }}
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
{{- with .page.Resources.Get (strings.TrimPrefix "./" $u.Path) }}
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ You must call this shortcode using the {{% %}} notation.
|
||||
*/}}
|
||||
|
||||
{{- with .Get 0 }}
|
||||
{{- with site.GetPage . }}
|
||||
{{- with $.Page.GetPage . }}
|
||||
{{- .RenderShortcodes }}
|
||||
{{- else }}
|
||||
{{- errorf "The %q shortcode was unable to find %q. See %s" $.Name . $.Position }}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
Also see [Module Mounts Config](/hugo-modules/configuration/#module-configuration-mounts) for an alternative way to configure this directory (from Hugo 0.56).
|
||||
Also see [Module Mounts Config](/hugo-modules/configuration/#module-configuration-mounts) for an alternative way to configure this directory.
|
||||
|
||||
@@ -1 +1 @@
|
||||
# github.com/gohugoio/gohugoioTheme v0.0.0-20240815082608-66ccd383a90f
|
||||
# github.com/gohugoio/gohugoioTheme v0.0.0-20250116152525-2d382cae7743
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: {{ replace .File.ContentBaseName "-" " " }}
|
||||
---
|
||||
|
||||
<!--
|
||||
You can insert these definitions in other pages using the `glossary-term` shortcode, so they must be self-contained.
|
||||
|
||||
Do this:
|
||||
|
||||
A _foo_ is big bar.
|
||||
|
||||
Not this:
|
||||
|
||||
A big bar.
|
||||
|
||||
Italicize the term whenever you use it in the definition.
|
||||
|
||||
An exception to this rule occurs when a term is an alias for another. In such cases, it is sufficient to use the phrase 'See [page kind]'."
|
||||
-->
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user