mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-25 15:58:53 +00:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 76a5e1880a | |||
| 0bb337b22a | |||
| 03dc9170b3 | |||
| c829b736cc | |||
| 995a2159e5 | |||
| f88f0a9f59 | |||
| dd3f2731e0 | |||
| f772998fa3 | |||
| 8a55df7af2 | |||
| 52c9bd7908 | |||
| 44da086082 | |||
| 2ffaf1fc1e | |||
| a808f6e40d | |||
| 94f3908ec6 | |||
| 75fcc7524a | |||
| 33d1f2c853 | |||
| 64da6d7c62 | |||
| 70db201ed4 | |||
| 8a468df065 | |||
| b5fa03d3aa | |||
| 9da472dd3b | |||
| 615e45d6e8 | |||
| 635532a20f | |||
| a243a6154c | |||
| 9c71f600bf | |||
| 420527fc6b | |||
| 7fe786e3b0 | |||
| 03b244fc0b | |||
| 961181334f | |||
| e35b7f049d | |||
| 7d90277a28 | |||
| 0796fa7ace | |||
| 6bf15241a1 | |||
| 861ede6d10 | |||
| f228c87d41 | |||
| 1b701b72ca | |||
| a32d70b712 | |||
| 948cfb98f9 | |||
| 8930802ef2 | |||
| 7df45f615a | |||
| f961093ea9 | |||
| 89b8c32200 | |||
| 984358f047 | |||
| d1f191c58d | |||
| a198116669 | |||
| ce2470e701 | |||
| 921db7b52c | |||
| 786ce71e59 | |||
| 5ad2846161 | |||
| 36ad9f583a | |||
| 7c0a0bc979 | |||
| d83ce27ae0 | |||
| 5a5f4a5495 | |||
| c6acc246ab | |||
| 29ed932513 | |||
| 7b4ddd1863 | |||
| 671897ae91 | |||
| a879ebfaaa | |||
| 499794d19e | |||
| feb3d494b9 | |||
| 65c82178b7 | |||
| 332d5ec823 | |||
| 212cc11ada | |||
| dfb35dcd7a |
@@ -4,7 +4,7 @@ parameters:
|
||||
defaults: &defaults
|
||||
resource_class: large
|
||||
docker:
|
||||
- image: bepsays/ci-hugoreleaser:1.22600.20300
|
||||
- image: bepsays/ci-hugoreleaser:1.22600.20500
|
||||
environment: &buildenv
|
||||
GOMODCACHE: /root/project/gomodcache
|
||||
version: 2
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
environment:
|
||||
<<: [*buildenv]
|
||||
docker:
|
||||
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22600.20300
|
||||
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22600.20500
|
||||
steps:
|
||||
- *restore-cache
|
||||
- &attach-workspace
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
have_fun: false
|
||||
memory_config:
|
||||
disabled: false
|
||||
code_review:
|
||||
disable: false
|
||||
comment_severity_threshold: HIGH
|
||||
max_review_comments: -1
|
||||
pull_request_opened:
|
||||
help: true
|
||||
summary: false
|
||||
code_review: false
|
||||
include_drafts: false
|
||||
ignore_patterns: []
|
||||
@@ -51,6 +51,7 @@ jobs:
|
||||
run: |
|
||||
gem install asciidoctor -v "2.0.26"
|
||||
gem install asciidoctor-diagram -v "3.1.0"
|
||||
gem install asciidoctor-html5s -v "0.5.1"
|
||||
- name: Install GoAT
|
||||
run: go install github.com/blampe/goat/cmd/goat@177de93b192b8ffae608e5d9ec421cc99bf68402
|
||||
- name: Install Python
|
||||
@@ -63,7 +64,7 @@ jobs:
|
||||
run: go install github.com/gohugoio/gotmplfmt@latest
|
||||
- name: Install docutils
|
||||
run: |
|
||||
pip install docutils
|
||||
pip install docutils Pygments
|
||||
rst2html --version
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Install pandoc on Linux
|
||||
|
||||
+2
-1
@@ -5,4 +5,5 @@ dist/
|
||||
public/
|
||||
.DS_Store
|
||||
cache/filecache/_gen/
|
||||
.claude/
|
||||
.claude/
|
||||
dump.txt
|
||||
@@ -12,6 +12,7 @@
|
||||
* In tests, use `qt` matchers (e.g. `b.Assert(err, qt.ErrorMatches, ...)`) instead of raw `if`/`t.Fatal` checks.
|
||||
* In tests, always use the latest Hugo specification, e.g. for layouts, it's `layouts/page.html` and not `layouts/_default/single.html`, `layouts/list.html` and not `layouts/_default/list.html`
|
||||
* Never name tests `TestIssue1234`; always give the test function a descriptive name, e.g. `TestDisablePathToLower`, and add any issue reference as a Go doc function comment, e.g. `// See issue 1234.`.
|
||||
* If you borrow a test case (e.g. from the issue), that test's author must be added as co-author in the commit.
|
||||
* If you're a security researcher, read @SECURITY.md carefully.
|
||||
* Brevity is good. This applies to code, comments and commit messages. Don't write a novel.
|
||||
* Use `./check.sh ./somepackage/...` when iterating.
|
||||
|
||||
@@ -72,6 +72,7 @@ To make the contribution process as seamless as possible, we ask for the followi
|
||||
* When you’re ready to create a pull request, be sure to:
|
||||
* Sign the [CLA](https://cla-assistant.io/gohugoio/hugo).
|
||||
* Have test cases for the new code. If you have questions about how to do this, please ask in your pull request.
|
||||
* If you borrow a test case (e.g. from the issue), that test's author must be added as [co-author](https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors) in the commit.
|
||||
* Run `go fmt`.
|
||||
* Add documentation if you are adding new features or changing functionality. The docs site lives in `/docs`.
|
||||
* Squash your commits into a single commit. `git rebase -i`. It’s okay to force update your pull request with `git push -f`.
|
||||
|
||||
@@ -37,7 +37,6 @@ A fast and flexible static site generator built with love by [bep][], [spf13][],
|
||||
|
||||
[](https://godoc.org/github.com/gohugoio/hugo)
|
||||
[](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>
|
||||
|
||||
@@ -139,10 +138,6 @@ To build and install the extended/deploy edition, first install a C compiler suc
|
||||
CGO_ENABLED=1 go install -tags extended,withdeploy github.com/gohugoio/hugo@latest
|
||||
```
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#gohugoio/hugo&Timeline)
|
||||
|
||||
## Documentation
|
||||
|
||||
Hugo's [documentation][] includes installation instructions, a quick start guide, conceptual explanations, reference information, and examples.
|
||||
@@ -195,10 +190,48 @@ Hugo stands on the shoulders of great open source libraries. Run `hugo env --log
|
||||
<summary>See current dependencies</summary>
|
||||
|
||||
```text
|
||||
cel.dev/expr="v0.25.1"
|
||||
cloud.google.com/go/auth/oauth2adapt="v0.2.8"
|
||||
cloud.google.com/go/auth="v0.20.0"
|
||||
cloud.google.com/go/compute/metadata="v0.9.0"
|
||||
cloud.google.com/go/iam="v1.5.3"
|
||||
cloud.google.com/go/monitoring="v1.24.3"
|
||||
cloud.google.com/go/storage="v1.57.2"
|
||||
cloud.google.com/go="v0.123.0"
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore="v1.20.0"
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity="v1.13.1"
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal="v1.11.2"
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob="v1.6.3"
|
||||
github.com/Azure/go-autorest/autorest/to="v0.4.1"
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go="v1.6.0"
|
||||
github.com/BurntSushi/locker="v0.0.0-20171006230638-a6e239ea1c69"
|
||||
github.com/JohannesKaufmann/dom="v0.2.0"
|
||||
github.com/JohannesKaufmann/html-to-markdown/v2="v2.5.0"
|
||||
github.com/alecthomas/chroma/v2="v2.21.1"
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp="v1.31.0"
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric="v0.54.0"
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping="v0.54.0"
|
||||
github.com/JohannesKaufmann/dom="v0.3.1"
|
||||
github.com/JohannesKaufmann/html-to-markdown/v2="v2.5.2"
|
||||
github.com/alecthomas/chroma/v2="v2.27.0"
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream="v1.7.8"
|
||||
github.com/aws/aws-sdk-go-v2/config="v1.32.2"
|
||||
github.com/aws/aws-sdk-go-v2/credentials="v1.19.2"
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds="v1.18.14"
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager="v1.20.12"
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources="v1.4.22"
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2="v2.7.22"
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini="v1.8.4"
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a="v1.4.22"
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront="v1.61.1"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding="v1.13.7"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum="v1.9.13"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url="v1.13.21"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared="v1.19.21"
|
||||
github.com/aws/aws-sdk-go-v2/service/s3="v1.97.3"
|
||||
github.com/aws/aws-sdk-go-v2/service/signin="v1.0.2"
|
||||
github.com/aws/aws-sdk-go-v2/service/sso="v1.30.5"
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc="v1.35.10"
|
||||
github.com/aws/aws-sdk-go-v2/service/sts="v1.41.2"
|
||||
github.com/aws/aws-sdk-go-v2="v1.41.6"
|
||||
github.com/aws/smithy-go="v1.25.0"
|
||||
github.com/aymerick/douceur="v0.2.0"
|
||||
github.com/bep/clocks="v0.5.0"
|
||||
github.com/bep/debounce="v1.2.0"
|
||||
@@ -206,105 +239,129 @@ github.com/bep/gitmap="v1.9.0"
|
||||
github.com/bep/goat="v0.5.0"
|
||||
github.com/bep/godartsass/v2="v2.5.0"
|
||||
github.com/bep/golibsass="v1.2.0"
|
||||
github.com/bep/goportabletext="v0.1.0"
|
||||
github.com/bep/helpers="v0.6.0"
|
||||
github.com/bep/imagemeta="v0.12.0"
|
||||
github.com/bep/lazycache="v0.8.0"
|
||||
github.com/bep/golocales="v0.2.0"
|
||||
github.com/bep/goportabletext="v0.2.0"
|
||||
github.com/bep/helpers="v0.12.0"
|
||||
github.com/bep/imagemeta="v0.17.2"
|
||||
github.com/bep/lazycache="v0.8.1"
|
||||
github.com/bep/logg="v0.4.0"
|
||||
github.com/bep/mclib="v1.20400.20402"
|
||||
github.com/bep/overlayfs="v0.10.0"
|
||||
github.com/bep/simplecobra="v0.6.1"
|
||||
github.com/bep/textandbinarywriter="v0.0.0-20251212174530-cd9f0732f60f"
|
||||
github.com/bep/tmc="v0.5.1"
|
||||
github.com/bits-and-blooms/bitset="v1.24.4"
|
||||
github.com/bep/mclib="v1.20401.20400"
|
||||
github.com/bep/overlayfs="v0.11.0"
|
||||
github.com/bep/simplecobra="v0.7.0"
|
||||
github.com/bep/textandbinarywriter="v0.1.0"
|
||||
github.com/bep/tmc="v0.6.0"
|
||||
github.com/bits-and-blooms/bitset="v1.24.5"
|
||||
github.com/cespare/xxhash/v2="v2.3.0"
|
||||
github.com/clbanning/mxj/v2="v2.7.0"
|
||||
github.com/clipperhouse/displaywidth="v0.6.0"
|
||||
github.com/clipperhouse/stringish="v0.1.1"
|
||||
github.com/clipperhouse/uax29/v2="v2.3.0"
|
||||
github.com/clipperhouse/displaywidth="v0.10.0"
|
||||
github.com/clipperhouse/uax29/v2="v2.6.0"
|
||||
github.com/cncf/xds/go="v0.0.0-20251210132809-ee656c7534f5"
|
||||
github.com/cpuguy83/go-md2man/v2="v2.0.6"
|
||||
github.com/disintegration/gift="v1.2.1"
|
||||
github.com/dlclark/regexp2="v1.11.5"
|
||||
github.com/evanw/esbuild="v0.27.2"
|
||||
github.com/dlclark/regexp2/v2="v2.2.1"
|
||||
github.com/dustin/go-humanize="v1.0.1"
|
||||
github.com/envoyproxy/go-control-plane/envoy="v1.36.0"
|
||||
github.com/envoyproxy/protoc-gen-validate="v1.3.0"
|
||||
github.com/evanw/esbuild="v0.28.1"
|
||||
github.com/fatih/color="v1.18.0"
|
||||
github.com/felixge/httpsnoop="v1.0.4"
|
||||
github.com/frankban/quicktest="v1.14.6"
|
||||
github.com/fsnotify/fsnotify="v1.9.0"
|
||||
github.com/getkin/kin-openapi="v0.133.0"
|
||||
github.com/go-openapi/jsonpointer="v0.21.0"
|
||||
github.com/go-openapi/swag="v0.23.0"
|
||||
github.com/getkin/kin-openapi="v0.140.0"
|
||||
github.com/go-jose/go-jose/v4="v4.1.4"
|
||||
github.com/go-logr/logr="v1.4.3"
|
||||
github.com/go-logr/stdr="v1.2.2"
|
||||
github.com/go-openapi/jsonpointer="v0.22.5"
|
||||
github.com/go-openapi/swag/jsonname="v0.25.5"
|
||||
github.com/gobuffalo/flect="v1.0.3"
|
||||
github.com/gobwas/glob="v0.2.3"
|
||||
github.com/goccy/go-yaml="v1.19.1"
|
||||
github.com/goccy/go-yaml="v1.19.2"
|
||||
github.com/gohugoio/gift="v0.2.0"
|
||||
github.com/gohugoio/go-i18n/v2="v2.1.3-0.20251018145728-cfcc22d823c6"
|
||||
github.com/gohugoio/go-radix="v1.2.0"
|
||||
github.com/gohugoio/hashstructure="v0.6.0"
|
||||
github.com/gohugoio/httpcache="v0.8.0"
|
||||
github.com/gohugoio/hugo-goldmark-extensions/extras="v0.5.0"
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough="v0.3.1"
|
||||
github.com/gohugoio/locales="v0.14.0"
|
||||
github.com/gohugoio/localescompressed="v1.0.1"
|
||||
github.com/gohugoio/hugo-goldmark-extensions/extras="v0.7.0"
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough="v0.5.0"
|
||||
github.com/golang-jwt/jwt/v5="v5.3.0"
|
||||
github.com/google/go-cmp="v0.7.0"
|
||||
github.com/google/s2a-go="v0.1.9"
|
||||
github.com/google/uuid="v1.6.0"
|
||||
github.com/google/wire="v0.7.0"
|
||||
github.com/googleapis/enterprise-certificate-proxy="v0.3.14"
|
||||
github.com/googleapis/gax-go/v2="v2.21.0"
|
||||
github.com/gorilla/css="v1.0.1"
|
||||
github.com/gorilla/websocket="v1.5.3"
|
||||
github.com/hairyhenderson/go-codeowners="v0.7.0"
|
||||
github.com/hashicorp/golang-lru/v2="v2.0.7"
|
||||
github.com/jdkato/prose="v1.2.1"
|
||||
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.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-colorable="v0.1.14"
|
||||
github.com/mattn/go-isatty="v0.0.22"
|
||||
github.com/mattn/go-runewidth="v0.0.19"
|
||||
github.com/microcosm-cc/bluemonday="v1.0.27"
|
||||
github.com/mitchellh/mapstructure="v1.5.1-0.20231216201459-8508981c8b6c"
|
||||
github.com/mohae/deepcopy="v0.0.0-20170929034955-c48cc78d4826"
|
||||
github.com/muesli/smartcrop="v0.3.0"
|
||||
github.com/niklasfasching/go-org="v1.9.1"
|
||||
github.com/oasdiff/yaml3="v0.0.0-20250309153720-d2182401db90"
|
||||
github.com/oasdiff/yaml="v0.0.0-20250309154309-f31be36b4037"
|
||||
github.com/oasdiff/yaml3="v0.0.13"
|
||||
github.com/oasdiff/yaml="v0.1.0"
|
||||
github.com/olekukonko/cat="v0.0.0-20250911104152-50322a0618f6"
|
||||
github.com/olekukonko/errors="v1.1.0"
|
||||
github.com/olekukonko/ll="v0.1.3"
|
||||
github.com/olekukonko/tablewriter="v1.1.2"
|
||||
github.com/olekukonko/errors="v1.2.0"
|
||||
github.com/olekukonko/ll="v0.1.6"
|
||||
github.com/olekukonko/tablewriter="v1.1.4"
|
||||
github.com/pbnjay/memory="v0.0.0-20210728143218-7b4eea64cf58"
|
||||
github.com/pelletier/go-toml/v2="v2.2.4"
|
||||
github.com/perimeterx/marshmallow="v1.1.5"
|
||||
github.com/pelletier/go-toml/v2="v2.4.3"
|
||||
github.com/pkg/browser="v0.0.0-20240102092130-5ac0b6a4141c"
|
||||
github.com/pkg/errors="v0.9.1"
|
||||
github.com/rogpeppe/go-internal="v1.14.1"
|
||||
github.com/rogpeppe/go-internal="v1.15.0"
|
||||
github.com/russross/blackfriday/v2="v2.1.0"
|
||||
github.com/sass/dart-sass/compiler="1.97.1"
|
||||
github.com/sass/dart-sass/implementation="1.97.1"
|
||||
github.com/sass/dart-sass/protocol="3.2.0"
|
||||
github.com/santhosh-tekuri/jsonschema/v6="v6.0.2"
|
||||
github.com/spf13/afero="v1.15.0"
|
||||
github.com/spf13/cast="v1.10.0"
|
||||
github.com/spf13/cobra="v1.10.2"
|
||||
github.com/spf13/fsync="v0.10.1"
|
||||
github.com/spf13/pflag="v1.0.9"
|
||||
github.com/tdewolff/minify/v2="v2.24.8"
|
||||
github.com/tdewolff/parse/v2="v2.8.5"
|
||||
github.com/tetratelabs/wazero="v1.10.1"
|
||||
github.com/spf13/pflag="v1.0.10"
|
||||
github.com/spiffe/go-spiffe/v2="v2.6.0"
|
||||
github.com/tdewolff/minify/v2="v2.24.13"
|
||||
github.com/tdewolff/parse/v2="v2.8.12"
|
||||
github.com/tetratelabs/wazero="v1.12.0"
|
||||
github.com/webmproject/libwebp="v1.6.0"
|
||||
github.com/woodsbury/decimal128="v1.3.0"
|
||||
github.com/yuin/goldmark-emoji="v1.0.6"
|
||||
github.com/yuin/goldmark="v1.7.13"
|
||||
github.com/yuin/goldmark="v1.8.2"
|
||||
go.opentelemetry.io/auto/sdk="v1.2.1"
|
||||
go.opentelemetry.io/contrib/detectors/gcp="v1.39.0"
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc="v0.67.0"
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp="v0.67.0"
|
||||
go.opentelemetry.io/otel/metric="v1.43.0"
|
||||
go.opentelemetry.io/otel/sdk/metric="v1.43.0"
|
||||
go.opentelemetry.io/otel/sdk="v1.43.0"
|
||||
go.opentelemetry.io/otel/trace="v1.43.0"
|
||||
go.opentelemetry.io/otel="v1.43.0"
|
||||
go.uber.org/automaxprocs="v1.5.3"
|
||||
go.yaml.in/yaml/v3="v3.0.4"
|
||||
golang.org/x/crypto="v0.46.0"
|
||||
golang.org/x/image="v0.34.0"
|
||||
golang.org/x/mod="v0.31.0"
|
||||
golang.org/x/net="v0.48.0"
|
||||
golang.org/x/sync="v0.19.0"
|
||||
golang.org/x/sys="v0.39.0"
|
||||
golang.org/x/text="v0.32.0"
|
||||
golang.org/x/tools="v0.40.0"
|
||||
google.golang.org/protobuf="v1.36.10"
|
||||
gopkg.in/yaml.v3="v3.0.1"
|
||||
gocloud.dev="v0.45.0"
|
||||
golang.org/x/crypto="v0.53.0"
|
||||
golang.org/x/image="v0.43.0"
|
||||
golang.org/x/mod="v0.37.0"
|
||||
golang.org/x/net="v0.56.0"
|
||||
golang.org/x/oauth2="v0.36.0"
|
||||
golang.org/x/sync="v0.21.0"
|
||||
golang.org/x/sys="v0.46.0"
|
||||
golang.org/x/text="v0.38.0"
|
||||
golang.org/x/time="v0.15.0"
|
||||
golang.org/x/tools="v0.47.0"
|
||||
golang.org/x/xerrors="v0.0.0-20240903120638-7835f813f4da"
|
||||
google.golang.org/api="v0.276.0"
|
||||
google.golang.org/genproto/googleapis/api="v0.0.0-20260319201613-d00831a3d3e7"
|
||||
google.golang.org/genproto/googleapis/rpc="v0.0.0-20260401024825-9d38bb4040a9"
|
||||
google.golang.org/genproto="v0.0.0-20260319201613-d00831a3d3e7"
|
||||
google.golang.org/grpc="v1.80.0"
|
||||
google.golang.org/protobuf="v1.36.11"
|
||||
rsc.io/qr="v0.2.0"
|
||||
software.sslmate.com/src/go-pkcs12="v0.2.0"
|
||||
software.sslmate.com/src/go-pkcs12="v0.7.0"
|
||||
```
|
||||
</details>
|
||||
|
||||
Vendored
+54
-13
@@ -17,6 +17,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
@@ -59,6 +60,29 @@ func (c *Cache) Prune(force bool) (int, error) {
|
||||
|
||||
counter := 0
|
||||
|
||||
seen := c.entryLocker.seen
|
||||
seenByLower := make(map[string]string, seen.Len())
|
||||
for id := range seen.All() {
|
||||
seenByLower[strings.ToLower(id)] = id
|
||||
}
|
||||
|
||||
// Names on disk matching a used cache key except for the case, and the used keys
|
||||
// actually walked. See the note about case-insensitive filesystems below.
|
||||
var candidates map[string]string
|
||||
visited := make(map[string]bool, seen.Len())
|
||||
|
||||
remove := func(name string) error {
|
||||
err := c.Fs.Remove(name)
|
||||
if err == nil {
|
||||
counter++
|
||||
return nil
|
||||
}
|
||||
if !herrors.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err := afero.Walk(c.Fs, "", func(name string, info os.FileInfo, err error) error {
|
||||
if info == nil {
|
||||
return nil
|
||||
@@ -93,27 +117,44 @@ func (c *Cache) Prune(force bool) (int, error) {
|
||||
|
||||
shouldRemove := force || c.isExpired(info.ModTime())
|
||||
|
||||
if !shouldRemove && c.entryLocker.seen.Len() > 0 {
|
||||
// Remove it if it's not been touched/used in the last build.
|
||||
shouldRemove = !c.entryLocker.seen.Has(name)
|
||||
if seen.Has(name) {
|
||||
visited[name] = true
|
||||
} else if !shouldRemove && seen.Len() > 0 {
|
||||
if id, found := seenByLower[strings.ToLower(name)]; found {
|
||||
// On case-insensitive filesystems this is the same file as id; e.g. an
|
||||
// entry created before Hugo started lowercasing the content paths in
|
||||
// v0.123 (content/MyBundle => _gen/images/MyBundle). Decided once the
|
||||
// walk is done: if id is walked too, they are distinct files and this
|
||||
// one is stale. See issue 15101.
|
||||
if candidates == nil {
|
||||
candidates = make(map[string]string)
|
||||
}
|
||||
candidates[name] = id
|
||||
} else {
|
||||
// Remove it if it's not been touched/used in the last build.
|
||||
shouldRemove = true
|
||||
}
|
||||
}
|
||||
|
||||
if shouldRemove {
|
||||
err := c.Fs.Remove(name)
|
||||
if err == nil {
|
||||
counter++
|
||||
}
|
||||
|
||||
if err != nil && !herrors.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return remove(name)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return counter, err
|
||||
}
|
||||
|
||||
return counter, err
|
||||
for name, id := range candidates {
|
||||
if visited[id] {
|
||||
if err := remove(name); err != nil {
|
||||
return counter, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return counter, nil
|
||||
}
|
||||
|
||||
func (c *Cache) pruneRootDirs(force bool) (int, error) {
|
||||
|
||||
+61
@@ -20,11 +20,72 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/cache/filecache"
|
||||
"github.com/gohugoio/hugo/htesting"
|
||||
"github.com/spf13/afero"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
// A cache entry created before Hugo started lowercasing content paths in v0.123
|
||||
// (e.g. _gen/images/MyBundle) is on a case-insensitive filesystem the same file as
|
||||
// the lowercased cache key used today, and must not be pruned.
|
||||
// See issue 15101.
|
||||
func TestPruneCacheEntryWithOtherCase(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
dir := t.TempDir()
|
||||
if isCaseInsensitive, err := htesting.IsCaseInsensitiveFs(dir); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !isCaseInsensitive {
|
||||
t.Skip("skip test on case-sensitive filesystem")
|
||||
}
|
||||
|
||||
fs := afero.NewBasePathFs(afero.NewOsFs(), dir)
|
||||
newCache := func() *filecache.Cache {
|
||||
return filecache.NewCache(fs, filecache.FileCacheConfig{Dir: "cache", MaxAge: -1})
|
||||
}
|
||||
|
||||
c.Assert(newCache().SetBytes("MyBundle/i1", []byte("abc")), qt.IsNil)
|
||||
|
||||
cache := newCache()
|
||||
_, b, err := cache.GetOrCreateBytes("mybundle/i1", func() ([]byte, error) {
|
||||
return []byte("def"), nil
|
||||
})
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(string(b), qt.Equals, "abc")
|
||||
|
||||
count, err := cache.Prune(false)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(count, qt.Equals, 0)
|
||||
c.Assert(cache.GetString("MyBundle/i1"), qt.Equals, "abc")
|
||||
}
|
||||
|
||||
// On a case-sensitive filesystem the entries above are distinct files,
|
||||
// and the one not used in this build should be pruned.
|
||||
func TestPruneCacheEntryWithOtherCaseCaseSensitiveFs(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
cache := filecache.NewCache(fs, filecache.FileCacheConfig{Dir: "cache", MaxAge: -1})
|
||||
|
||||
c.Assert(cache.SetBytes("MyBundle/i1", []byte("abc")), qt.IsNil)
|
||||
|
||||
cache = filecache.NewCache(fs, filecache.FileCacheConfig{Dir: "cache", MaxAge: -1})
|
||||
_, b, err := cache.GetOrCreateBytes("mybundle/i1", func() ([]byte, error) {
|
||||
return []byte("def"), nil
|
||||
})
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(string(b), qt.Equals, "def")
|
||||
|
||||
count, err := cache.Prune(false)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(count, qt.Equals, 1)
|
||||
c.Assert(cache.GetString("MyBundle/i1"), qt.Equals, "")
|
||||
c.Assert(cache.GetString("mybundle/i1"), qt.Equals, "def")
|
||||
}
|
||||
|
||||
func TestPrune(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -38,13 +38,20 @@ run_gofmt() {
|
||||
|
||||
# Run staticcheck
|
||||
run_staticcheck() {
|
||||
# Check if staticcheck is installed, install if not
|
||||
if ! command -v staticcheck &> /dev/null; then
|
||||
local staticcheck_bin
|
||||
if command -v staticcheck &> /dev/null; then
|
||||
staticcheck_bin=$(command -v staticcheck)
|
||||
else
|
||||
echo "==> Installing staticcheck..."
|
||||
go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||
staticcheck_bin="$(go env GOBIN)"
|
||||
if [ -z "$staticcheck_bin" ]; then
|
||||
staticcheck_bin="$(go env GOPATH)/bin"
|
||||
fi
|
||||
staticcheck_bin="$staticcheck_bin/staticcheck"
|
||||
fi
|
||||
echo "==> Running staticcheck..."
|
||||
staticcheck $PACKAGES
|
||||
"$staticcheck_bin" $PACKAGES
|
||||
echo " OK"
|
||||
}
|
||||
|
||||
|
||||
+21
-109
@@ -21,12 +21,8 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/chroma/v2"
|
||||
"github.com/alecthomas/chroma/v2/formatters/html"
|
||||
"github.com/alecthomas/chroma/v2/styles"
|
||||
"github.com/bep/simplecobra"
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
@@ -34,6 +30,7 @@ import (
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/gohugoio/hugo/markup/highlight"
|
||||
"github.com/gohugoio/hugo/parser"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/cobra/doc"
|
||||
@@ -49,6 +46,8 @@ func newGenCommand() *genCommand {
|
||||
style string
|
||||
mode string
|
||||
modeSelector bool
|
||||
classDark string
|
||||
classLight string
|
||||
highlightStyle string
|
||||
lineNumbersInlineStyle string
|
||||
lineNumbersTableStyle string
|
||||
@@ -65,85 +64,26 @@ func newGenCommand() *genCommand {
|
||||
See https://gohugo.io/quick-reference/syntax-highlighting-styles/ for a preview of the available styles.`,
|
||||
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
style = strings.ToLower(style)
|
||||
if !slices.Contains(styles.Names(), style) {
|
||||
return fmt.Errorf("invalid style: %s", style)
|
||||
}
|
||||
var chromaStyle *chroma.Style
|
||||
if mode != "" {
|
||||
var chromaMode chroma.Mode
|
||||
switch mode {
|
||||
case "light":
|
||||
chromaMode = chroma.Light
|
||||
case "dark":
|
||||
chromaMode = chroma.Dark
|
||||
default:
|
||||
return fmt.Errorf("invalid mode: %s", mode)
|
||||
}
|
||||
|
||||
chromaStyle = styles.GetForMode(style, chromaMode)
|
||||
if chromaStyle.Mode() != chromaMode {
|
||||
return fmt.Errorf("style %q does not have a %q mode", style, mode)
|
||||
}
|
||||
} else {
|
||||
chromaStyle = styles.Get(style)
|
||||
}
|
||||
builder := chromaStyle.Builder()
|
||||
if highlightStyle != "" {
|
||||
builder.Add(chroma.LineHighlight, highlightStyle)
|
||||
}
|
||||
if lineNumbersInlineStyle != "" {
|
||||
builder.Add(chroma.LineNumbers, lineNumbersInlineStyle)
|
||||
}
|
||||
if lineNumbersTableStyle != "" {
|
||||
builder.Add(chroma.LineNumbersTable, lineNumbersTableStyle)
|
||||
}
|
||||
style, err := builder.Build()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if omitEmpty {
|
||||
// See https://github.com/alecthomas/chroma/commit/5b2a4c5a26c503c79bc86ba3c4ae5b330028bd3d
|
||||
hugo.Deprecate("--omitEmpty", "Flag is no longer needed, empty classes are now always omitted.", "v0.149.0")
|
||||
}
|
||||
options := []html.Option{
|
||||
html.WithCSSComments(!omitClassComments),
|
||||
}
|
||||
if !modeSelector {
|
||||
// Only needed for the shared-scope overlay; --modeSelector
|
||||
// gives each mode its own scope and makes this redundant.
|
||||
options = append(options, html.WithCustomCSS(chromaCSSOverrides(style)))
|
||||
}
|
||||
|
||||
formatter := html.New(options...)
|
||||
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "/* Generated using: hugo %s */\n\n", strings.Join(os.Args[1:], " "))
|
||||
formatter.WriteCSS(&buf, style)
|
||||
css := buf.String()
|
||||
if modeSelector {
|
||||
// Scope every selector under a top level mode class, e.g. ".dark .chroma".
|
||||
// This allows generating both light and dark stylesheets and toggling
|
||||
// them with a parent class on the page.
|
||||
// There's no upstream option for this (I think), so do string replacements for now.
|
||||
// TODO(bep) upstream option for this.
|
||||
var prefix string
|
||||
switch style.Mode() {
|
||||
case chroma.Light:
|
||||
prefix = ".light "
|
||||
case chroma.Dark:
|
||||
prefix = ".dark "
|
||||
default:
|
||||
return fmt.Errorf("style %q does not have a %q mode", style.Name, mode)
|
||||
}
|
||||
replacer := strings.NewReplacer(
|
||||
".bg {", prefix+".bg {",
|
||||
".chroma ", prefix+".chroma ",
|
||||
)
|
||||
css = replacer.Replace(css)
|
||||
css, err := highlight.ChromaStylesCSS(highlight.ChromaStylesOptions{
|
||||
Style: style,
|
||||
Mode: mode,
|
||||
ModeSelector: modeSelector,
|
||||
ClassDark: classDark,
|
||||
ClassLight: classLight,
|
||||
HighlightStyle: highlightStyle,
|
||||
LineNumbersInlineStyle: lineNumbersInlineStyle,
|
||||
LineNumbersTableStyle: lineNumbersTableStyle,
|
||||
OmitClassComments: omitClassComments,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("/* Generated using: hugo %s */\n\n", strings.Join(os.Args[1:], " "))
|
||||
fmt.Print(css)
|
||||
return nil
|
||||
},
|
||||
@@ -155,6 +95,10 @@ See https://gohugo.io/quick-reference/syntax-highlighting-styles/ for a preview
|
||||
_ = cmd.RegisterFlagCompletionFunc("mode", cobra.FixedCompletions([]string{"light", "dark"}, cobra.ShellCompDirectiveNoFileComp))
|
||||
cmd.PersistentFlags().BoolVar(&modeSelector, "modeSelector", false, `scope selectors under a top level mode class, e.g. ".dark .chroma"`)
|
||||
_ = cmd.RegisterFlagCompletionFunc("modeSelector", cobra.NoFileCompletions)
|
||||
cmd.PersistentFlags().StringVar(&classDark, "classDark", "dark", `class name used by --modeSelector for dark styles`)
|
||||
_ = cmd.RegisterFlagCompletionFunc("classDark", cobra.NoFileCompletions)
|
||||
cmd.PersistentFlags().StringVar(&classLight, "classLight", "light", `class name used by --modeSelector for light styles`)
|
||||
_ = cmd.RegisterFlagCompletionFunc("classLight", cobra.NoFileCompletions)
|
||||
cmd.PersistentFlags().StringVar(&highlightStyle, "highlightStyle", "", `foreground and background colors for highlighted lines, e.g. --highlightStyle "#fff000 bg:#000fff"`)
|
||||
_ = cmd.RegisterFlagCompletionFunc("highlightStyle", cobra.NoFileCompletions)
|
||||
cmd.PersistentFlags().StringVar(&lineNumbersInlineStyle, "lineNumbersInlineStyle", "", `foreground and background colors for inline line numbers, e.g. --lineNumbersInlineStyle "#fff000 bg:#000fff"`)
|
||||
@@ -330,38 +274,6 @@ url: %s
|
||||
}
|
||||
}
|
||||
|
||||
// chromaCSSOverrides re-emits leaf token colors that Chroma's minifier drops
|
||||
// because they equal the style's default foreground (the .chroma color). Without
|
||||
// modeSelector scoping, a paired light/dark stylesheet shares the same .chroma
|
||||
// scope, and the light sheet's explicit rule (e.g. .chroma .nx) would otherwise
|
||||
// leak into dark mode, since an explicit declaration beats inheritance.
|
||||
func chromaCSSOverrides(style *chroma.Style) map[chroma.TokenType]string {
|
||||
bg := style.Get(chroma.Background)
|
||||
m := make(map[chroma.TokenType]string)
|
||||
for tt := range chroma.StandardTypes {
|
||||
if tt == chroma.Background || !style.Has(tt) || !chromaLeafToken(tt) {
|
||||
continue
|
||||
}
|
||||
entry := style.Get(tt)
|
||||
if !entry.Sub(bg).IsZero() || !entry.Colour.IsSet() {
|
||||
continue
|
||||
}
|
||||
if css := html.StyleEntryToCSS(chroma.StyleEntry{Colour: entry.Colour}); css != "" {
|
||||
m[tt] = css
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func chromaLeafToken(tt chroma.TokenType) bool {
|
||||
for other := range chroma.StandardTypes {
|
||||
if other != tt && (other.Category() == tt || other.SubCategory() == tt) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type genCommand struct {
|
||||
rootCmd *rootCommand
|
||||
|
||||
|
||||
+1
-1
@@ -1108,7 +1108,7 @@ func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
|
||||
|
||||
fromPath := ev.Name
|
||||
|
||||
relPath, found := sourceFs.MakePathRelative(fromPath, true)
|
||||
relPath, found := sourceFs.MakePathRelative(fromPath, false)
|
||||
|
||||
if !found {
|
||||
// Not member of this virtual host.
|
||||
|
||||
+41
-21
@@ -18,6 +18,7 @@ import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
@@ -105,6 +106,16 @@ func MD5FromStringHexEncoded(f string) string {
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// MD5FromReaderHexEncoded returns the MD5 hash of the given reader.
|
||||
func MD5FromReaderHexEncoded(r io.Reader) string {
|
||||
h := md5.New()
|
||||
_, err := io.Copy(h, r)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// HashString returns a hash from the given elements.
|
||||
// It will panic if the hash cannot be calculated.
|
||||
// Note that this hash should be used primarily for identity, not for change detection as
|
||||
@@ -124,11 +135,38 @@ func HashStringHex(vs ...any) string {
|
||||
var hashOptsPool = sync.Pool{
|
||||
New: func() any {
|
||||
return &hashstructure.HashOptions{
|
||||
Hasher: xxhash.New(),
|
||||
Hasher: xxhash.New(),
|
||||
UnwrapFunc: unwrapForHashing,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// hashstructure only sees exported struct fields, so rewrite known identity types before hashing,
|
||||
// e.g. a Resource or Page nested in an options map hashes by its Key.
|
||||
func unwrapForHashing(v reflect.Value) (reflect.Value, error) {
|
||||
if v.Kind() != reflect.Struct {
|
||||
return v, nil
|
||||
}
|
||||
var in any
|
||||
if v.CanAddr() {
|
||||
// The common case; pointer receiver methods on a struct
|
||||
// reached through a pointer.
|
||||
in = v.Addr().Interface()
|
||||
} else {
|
||||
in = v.Interface()
|
||||
}
|
||||
switch t := in.(type) {
|
||||
case hashstructure.Hashable:
|
||||
// Let hashstructure handle it.
|
||||
return v, nil
|
||||
case keyer:
|
||||
return reflect.ValueOf(t.Key()), nil
|
||||
case identity.IdentityProvider:
|
||||
return reflect.ValueOf(t.GetIdentity()), nil
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func getHashOpts() *hashstructure.HashOptions {
|
||||
return hashOptsPool.Get().(*hashstructure.HashOptions)
|
||||
}
|
||||
@@ -145,15 +183,10 @@ func putHashOpts(opts *hashstructure.HashOptions) {
|
||||
func HashUint64(vs ...any) uint64 {
|
||||
var o any
|
||||
if len(vs) == 1 {
|
||||
o = toHashable(vs[0])
|
||||
o = vs[0]
|
||||
} else {
|
||||
elements := make([]any, len(vs))
|
||||
for i, e := range vs {
|
||||
elements[i] = toHashable(e)
|
||||
}
|
||||
o = elements
|
||||
o = vs
|
||||
}
|
||||
|
||||
hash, err := Hash(o)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -176,19 +209,6 @@ type keyer interface {
|
||||
Key() string
|
||||
}
|
||||
|
||||
// For structs, hashstructure.Hash only works on the exported fields,
|
||||
// so rewrite the input slice for known identity types.
|
||||
func toHashable(v any) any {
|
||||
switch t := v.(type) {
|
||||
case keyer:
|
||||
return t.Key()
|
||||
case identity.IdentityProvider:
|
||||
return t.GetIdentity()
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
type xxhashReadFrom struct {
|
||||
buff []byte
|
||||
*xxhash.Digest
|
||||
|
||||
@@ -21,8 +21,9 @@ import (
|
||||
type HasBytesWriter struct {
|
||||
Patterns []*HasBytesPattern
|
||||
|
||||
i int
|
||||
done bool
|
||||
// The tail of the bytes written so far, retained so we can detect a
|
||||
// pattern that straddles the boundary between two Write calls.
|
||||
buff []byte
|
||||
}
|
||||
|
||||
@@ -31,10 +32,13 @@ type HasBytesPattern struct {
|
||||
Pattern []byte
|
||||
}
|
||||
|
||||
func (h *HasBytesWriter) patternLen() int {
|
||||
// maxPatternLen returns the length of the longest pattern.
|
||||
func (h *HasBytesWriter) maxPatternLen() int {
|
||||
l := 0
|
||||
for _, p := range h.Patterns {
|
||||
l += len(p.Pattern)
|
||||
if len(p.Pattern) > l {
|
||||
l = len(p.Pattern)
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
@@ -44,36 +48,55 @@ func (h *HasBytesWriter) Write(p []byte) (n int, err error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
if len(h.buff) == 0 {
|
||||
h.buff = make([]byte, h.patternLen()*2)
|
||||
keep := h.maxPatternLen() - 1
|
||||
|
||||
// Join the tail retained from previous Writes with the head of this chunk
|
||||
// so a pattern straddling the boundary is still detected. Only the
|
||||
// boundary window is copied; the chunk itself is scanned in place below.
|
||||
var boundary []byte
|
||||
if keep > 0 && len(h.buff) > 0 {
|
||||
head := p
|
||||
if len(head) > keep {
|
||||
head = head[:keep]
|
||||
}
|
||||
boundary = make([]byte, 0, len(h.buff)+len(head))
|
||||
boundary = append(boundary, h.buff...)
|
||||
boundary = append(boundary, head...)
|
||||
}
|
||||
|
||||
for i := range p {
|
||||
h.buff[h.i] = p[i]
|
||||
h.i++
|
||||
if h.i == len(h.buff) {
|
||||
// Shift left.
|
||||
copy(h.buff, h.buff[len(h.buff)/2:])
|
||||
h.i = len(h.buff) / 2
|
||||
// Scan each not-yet-matched pattern once per Write instead of once per byte.
|
||||
done := true
|
||||
for _, pp := range h.Patterns {
|
||||
if pp.Match {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, pp := range h.Patterns {
|
||||
if bytes.Contains(h.buff, pp.Pattern) {
|
||||
pp.Match = true
|
||||
done := true
|
||||
for _, ppp := range h.Patterns {
|
||||
if !ppp.Match {
|
||||
done = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if done {
|
||||
h.done = true
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
if bytes.Contains(p, pp.Pattern) || bytes.Contains(boundary, pp.Pattern) {
|
||||
pp.Match = true
|
||||
continue
|
||||
}
|
||||
done = false
|
||||
}
|
||||
|
||||
if done {
|
||||
// All patterns found; no need to look at any more data.
|
||||
h.done = true
|
||||
h.buff = nil
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// Retain the last keep bytes of (previous tail + this chunk) to detect a
|
||||
// pattern straddling into the next Write.
|
||||
switch {
|
||||
case keep <= 0:
|
||||
h.buff = h.buff[:0]
|
||||
case len(p) >= keep:
|
||||
h.buff = append(h.buff[:0], p[len(p)-keep:]...)
|
||||
default:
|
||||
// Chunk shorter than keep: slide the window over the retained tail.
|
||||
if total := len(h.buff) + len(p); total > keep {
|
||||
h.buff = h.buff[total-keep:]
|
||||
}
|
||||
h.buff = append(h.buff, p...)
|
||||
}
|
||||
|
||||
return len(p), nil
|
||||
|
||||
@@ -65,3 +65,61 @@ func TestHasBytesWriter(t *testing.T) {
|
||||
fmt.Fprintf(w, "__foo")
|
||||
c.Assert(h.Patterns[0].Match, qt.Equals, true)
|
||||
}
|
||||
|
||||
func TestHasBytesWriterMultiplePatterns(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
neww := func() (*HasBytesWriter, io.Writer) {
|
||||
var b bytes.Buffer
|
||||
h := &HasBytesWriter{
|
||||
Patterns: []*HasBytesPattern{
|
||||
{Pattern: []byte("__hdeferred/")},
|
||||
{Pattern: []byte("__h_pp_l1")},
|
||||
},
|
||||
}
|
||||
return h, io.MultiWriter(&b, h)
|
||||
}
|
||||
|
||||
// Neither pattern present.
|
||||
h, w := neww()
|
||||
fmt.Fprint(w, "the quick brown fox jumps over the lazy dog")
|
||||
c.Assert(h.Patterns[0].Match, qt.Equals, false)
|
||||
c.Assert(h.Patterns[1].Match, qt.Equals, false)
|
||||
c.Assert(h.done, qt.Equals, false)
|
||||
|
||||
// Only the second pattern present; the writer must not report a match
|
||||
// for the first, and must not prematurely mark itself done.
|
||||
h, w = neww()
|
||||
fmt.Fprint(w, "prefix __h_pp_l1 suffix")
|
||||
c.Assert(h.Patterns[0].Match, qt.Equals, false)
|
||||
c.Assert(h.Patterns[1].Match, qt.Equals, true)
|
||||
c.Assert(h.done, qt.Equals, false)
|
||||
|
||||
// Both patterns present across multiple writes; done once all match.
|
||||
h, w = neww()
|
||||
fmt.Fprint(w, "aaa __hdef")
|
||||
fmt.Fprint(w, "erred/xyz bbb __h_p")
|
||||
fmt.Fprint(w, "p_l1 ccc")
|
||||
c.Assert(h.Patterns[0].Match, qt.Equals, true)
|
||||
c.Assert(h.Patterns[1].Match, qt.Equals, true)
|
||||
c.Assert(h.done, qt.Equals, true)
|
||||
}
|
||||
|
||||
func BenchmarkHasBytesWriter(b *testing.B) {
|
||||
// A large chunk of output containing neither pattern is the common case
|
||||
// (a normal rendered page): the writer must scan all of it.
|
||||
content := []byte(strings.Repeat("<div class=\"nav\"><a href=\"/foo/bar\">baz</a></div>\n", 4000))
|
||||
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
h := &HasBytesWriter{
|
||||
Patterns: []*HasBytesPattern{
|
||||
{Pattern: []byte("__hdeferred/")},
|
||||
{Pattern: []byte("__h_pp_l1")},
|
||||
},
|
||||
}
|
||||
if _, err := h.Write(content); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-2
@@ -33,6 +33,7 @@ import (
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/version"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
"github.com/gohugoio/hugo/internal/warpc"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
|
||||
@@ -211,7 +212,10 @@ func GetDependencyList() []string {
|
||||
|
||||
// GetDependencyListNonGo returns a list of non-Go dependencies.
|
||||
func GetDependencyListNonGo() []string {
|
||||
deps := []string{formatDep("github.com/webmproject/libwebp", "v1.6.0")} // via WASM. TODO(bep) get versions from the plugin setup.
|
||||
var deps []string
|
||||
for _, dep := range warpc.GetWASMDeps() {
|
||||
deps = append(deps, formatDep(dep[0], dep[1]))
|
||||
}
|
||||
|
||||
if IsExtended {
|
||||
deps = append(
|
||||
@@ -225,7 +229,8 @@ func GetDependencyListNonGo() []string {
|
||||
if IsDartSassGeV2() {
|
||||
dartSassPath = "github.com/sass/dart-sass"
|
||||
}
|
||||
deps = append(deps,
|
||||
deps = append(
|
||||
deps,
|
||||
formatDep(dartSassPath+"/protocol", dartSass.ProtocolVersion),
|
||||
formatDep(dartSassPath+"/compiler", dartSass.CompilerVersion),
|
||||
formatDep(dartSassPath+"/implementation", dartSass.ImplementationVersion),
|
||||
|
||||
@@ -35,8 +35,8 @@ func TestDeprecationLogLevelFromVersion(t *testing.T) {
|
||||
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.136.0.
|
||||
c.Assert(deprecationLogLevelFromVersion("0.141.0"), qt.Equals, logg.LevelError)
|
||||
// Subtract 5 from the minor version of the first ERRORed version => 0.145.0.
|
||||
c.Assert(deprecationLogLevelFromVersion("0.150.0"), qt.Equals, logg.LevelError)
|
||||
}
|
||||
|
||||
func TestMarkupScope(t *testing.T) {
|
||||
|
||||
@@ -19,7 +19,7 @@ import "github.com/gohugoio/hugo/common/version"
|
||||
// This should be the only one.
|
||||
var CurrentVersion = version.Version{
|
||||
Major: 0,
|
||||
Minor: 164,
|
||||
Minor: 165,
|
||||
PatchLevel: 0,
|
||||
Suffix: "-DEV",
|
||||
Suffix: "",
|
||||
}
|
||||
|
||||
@@ -458,36 +458,6 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy privacy values.
|
||||
if c.Privacy.Twitter.Disable {
|
||||
hugo.DeprecateWithLogger("project config key privacy.twitter.disable", "Use privacy.x.disable instead.", "v0.141.0", logger.Logger())
|
||||
c.Privacy.X.Disable = c.Privacy.Twitter.Disable
|
||||
}
|
||||
if c.Privacy.Twitter.EnableDNT {
|
||||
hugo.DeprecateWithLogger("project config key privacy.twitter.enableDNT", "Use privacy.x.enableDNT instead.", "v0.141.0", logger.Logger())
|
||||
c.Privacy.X.EnableDNT = c.Privacy.Twitter.EnableDNT
|
||||
}
|
||||
if c.Privacy.Twitter.Simple {
|
||||
hugo.DeprecateWithLogger("project config key privacy.twitter.simple", "Use privacy.x.simple instead.", "v0.141.0", logger.Logger())
|
||||
c.Privacy.X.Simple = c.Privacy.Twitter.Simple
|
||||
}
|
||||
|
||||
// Legacy services values.
|
||||
if c.Services.Twitter.DisableInlineCSS {
|
||||
hugo.DeprecateWithLogger("project config key services.twitter.disableInlineCSS", "Use services.x.disableInlineCSS instead.", "v0.141.0", logger.Logger())
|
||||
c.Services.X.DisableInlineCSS = c.Services.Twitter.DisableInlineCSS
|
||||
}
|
||||
|
||||
// Legacy permalink tokens
|
||||
for _, pc := range c.Permalinks {
|
||||
if strings.Contains(pc.Pattern, ":filename") {
|
||||
hugo.DeprecateWithLogger("the \":filename\" permalink token", "Use \":contentbasename\" instead.", "0.144.0", logger.Logger())
|
||||
}
|
||||
if strings.Contains(pc.Pattern, ":slugorfilename") {
|
||||
hugo.DeprecateWithLogger("the \":slugorfilename\" permalink token", "Use \":slugorcontentbasename\" instead.", "0.144.0", logger.Logger())
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy render hook values.
|
||||
alternativeDetails := fmt.Sprintf(
|
||||
"Set to %q if previous value was false, or set to %q if previous value was true.",
|
||||
|
||||
@@ -25,11 +25,13 @@ weight = 2
|
||||
[[module.mounts]]
|
||||
source = 'content/en'
|
||||
target = 'content'
|
||||
lang = 'en'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = 'en'
|
||||
[[module.mounts]]
|
||||
source = 'content/sv'
|
||||
target = 'content'
|
||||
lang = 'sv'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = 'sv'
|
||||
-- content/en/p1.md --
|
||||
---
|
||||
title: "p1"
|
||||
|
||||
@@ -19,19 +19,23 @@ defaultContentLanguage = 'en'
|
||||
[[module.mounts]]
|
||||
source = 'content/en'
|
||||
target = 'content/en'
|
||||
lang = 'en'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = 'en'
|
||||
[[module.mounts]]
|
||||
source = 'content/nn'
|
||||
target = 'content/nn'
|
||||
lang = 'nn'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = 'nn'
|
||||
[[module.mounts]]
|
||||
source = 'content/no'
|
||||
target = 'content/no'
|
||||
lang = 'no'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = 'no'
|
||||
[[module.mounts]]
|
||||
source = 'content/sv'
|
||||
target = 'content/sv'
|
||||
lang = 'sv'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = 'sv'
|
||||
[[module.mounts]]
|
||||
source = 'layouts'
|
||||
target = 'layouts'
|
||||
|
||||
@@ -30,7 +30,6 @@ type Config struct {
|
||||
Disqus Disqus
|
||||
GoogleAnalytics GoogleAnalytics
|
||||
Instagram Instagram
|
||||
Twitter Twitter `json:"-"` // deprecated in favor of X in v0.141.0
|
||||
Vimeo Vimeo
|
||||
YouTube YouTube
|
||||
X X
|
||||
@@ -59,19 +58,6 @@ type Instagram struct {
|
||||
Simple bool
|
||||
}
|
||||
|
||||
// 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"`
|
||||
|
||||
// When set to true, the Tweet 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 Tweet will be built.
|
||||
Simple bool
|
||||
}
|
||||
|
||||
// Vimeo holds the privacy configuration settings related to the Vimeo shortcode.
|
||||
type Vimeo struct {
|
||||
Service `mapstructure:",squash"`
|
||||
|
||||
@@ -39,12 +39,11 @@ const securityConfigKey = "security"
|
||||
var DefaultConfig = Config{
|
||||
Exec: Exec{
|
||||
Allow: MustNewWhitelist(
|
||||
"^(dart-)?sass(-embedded)?$", // sass, dart-sass, dart-sass-embedded.
|
||||
"^go$", // for Go Modules
|
||||
"^git$", // For Git info
|
||||
"^node$", // Used as the runtime for Node tools.
|
||||
"^(dart-)?sass$", // sass, dart-sass
|
||||
"^go$", // for Go Modules
|
||||
"^git$", // For Git info
|
||||
"^node$", // Used as the runtime for Node tools.
|
||||
"^postcss$",
|
||||
"^tailwindcss$",
|
||||
),
|
||||
// These have been tested to work with Hugo's external programs
|
||||
// on Windows, Linux and MacOS.
|
||||
|
||||
@@ -135,7 +135,7 @@ func TestToTOML(t *testing.T) {
|
||||
got := DefaultConfig.ToTOML()
|
||||
|
||||
c.Assert(got, qt.Equals,
|
||||
"[security]\n allowContent = ['! ^text/html$']\n enableInlineShortcodes = false\n\n [security.exec]\n allow = ['^(dart-)?sass(-embedded)?$', '^go$', '^git$', '^node$', '^postcss$', '^tailwindcss$']\n osEnv = ['(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG|SYSTEMDRIVE|PROGRAMDATA)$']\n\n [security.funcs]\n getenv = ['^HUGO_', '^CI$']\n\n [security.http]\n methods = ['(?i)GET|POST']\n urls = ['(?i)^https?://[a-z0-9]', '! ^https?://\\d+\\.', '! (?i)localhost', '! (?i)^https?://[^/?#]*@']\n\n [security.node]\n [security.node.permissions]\n allowAddons = ['tailwindcss']\n allowChildProcess = ['tailwindcss']\n allowRead = ['.']\n allowWorker = ['tailwindcss']\n allowWrite = []\n disable = false",
|
||||
"[security]\n allowContent = ['! ^text/html$']\n enableInlineShortcodes = false\n\n [security.exec]\n allow = ['^(dart-)?sass$', '^go$', '^git$', '^node$', '^postcss$']\n osEnv = ['(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG|SYSTEMDRIVE|PROGRAMDATA)$']\n\n [security.funcs]\n getenv = ['^HUGO_', '^CI$']\n\n [security.http]\n methods = ['(?i)GET|POST']\n urls = ['(?i)^https?://[a-z0-9]', '! ^https?://\\d+\\.', '! (?i)localhost', '! (?i)^https?://[^/?#]*@']\n\n [security.node]\n [security.node.permissions]\n allowAddons = ['tailwindcss']\n allowChildProcess = ['tailwindcss']\n allowRead = ['.']\n allowWorker = ['tailwindcss']\n allowWrite = []\n disable = false",
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ type Config struct {
|
||||
Disqus Disqus
|
||||
GoogleAnalytics GoogleAnalytics
|
||||
Instagram Instagram `json:"-"` // the embedded instagram shortcode no longer uses this
|
||||
Twitter Twitter `json:"-"` // deprecated in favor of X in v0.141.0
|
||||
X X
|
||||
RSS RSS
|
||||
}
|
||||
@@ -61,15 +60,6 @@ type Instagram struct {
|
||||
AccessToken string // this is no longer used by the embedded instagram shortcode
|
||||
}
|
||||
|
||||
// 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
|
||||
// to disable the inline CSS provided by Hugo.
|
||||
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.
|
||||
|
||||
+37
-6
@@ -74,7 +74,6 @@ func TestNewContentFromFile(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
for i, cas := range cases {
|
||||
|
||||
c.Run(cas.name, func(c *qt.C) {
|
||||
c.Parallel()
|
||||
|
||||
@@ -109,7 +108,6 @@ func TestNewContentFromFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +157,37 @@ site RegularPages: {{ len site.RegularPages }}
|
||||
cContains(c, readFileFromFs(t, fs.Source, filepath.Join("content", "mypage.md")), `draft: true`)
|
||||
}
|
||||
|
||||
// See issue 15078.
|
||||
func TestNewContentWithBuildCascade(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mm := afero.NewMemMapFs()
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(initFs(mm), qt.IsNil)
|
||||
c.Assert(mm.MkdirAll(filepath.Join("content", "posts", "drafts"), 0o755), qt.IsNil)
|
||||
c.Assert(afero.WriteFile(mm, filepath.Join("content", "posts", "drafts", "_index.md"), []byte(`---
|
||||
title: Drafts
|
||||
build:
|
||||
render: never
|
||||
cascade:
|
||||
draft: true
|
||||
build:
|
||||
render: link
|
||||
draft: true
|
||||
---
|
||||
`), 0o755), qt.IsNil)
|
||||
|
||||
cfg, fs := newTestCfg(c, mm)
|
||||
conf := testconfig.GetTestConfigs(fs.Source, cfg)
|
||||
h, err := hugolib.NewHugoSites(deps.DepsCfg{Configs: conf, Fs: fs})
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
const target = "posts/drafts/trip-to-puebla/index.md"
|
||||
c.Assert(create.NewContent(h, "", target, false), qt.IsNil)
|
||||
c.Assert(readFileFromFs(c, fs.Source, filepath.Join("content", target)), qt.Contains, `title: "Trip to Puebla"`)
|
||||
}
|
||||
|
||||
func initFs(fs afero.Fs) error {
|
||||
perm := os.FileMode(0o755)
|
||||
var err error
|
||||
@@ -308,10 +337,10 @@ theme = "mytheme"
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
languageName = "English"
|
||||
label = "English"
|
||||
[languages.nn]
|
||||
weight = 2
|
||||
languageName = "Nynorsk"
|
||||
label = "Nynorsk"
|
||||
|
||||
[module]
|
||||
[[module.mounts]]
|
||||
@@ -320,11 +349,13 @@ languageName = "Nynorsk"
|
||||
[[module.mounts]]
|
||||
source = 'content'
|
||||
target = 'content'
|
||||
lang = 'en'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = 'en'
|
||||
[[module.mounts]]
|
||||
source = 'content_nn'
|
||||
target = 'content'
|
||||
lang = 'nn'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = 'nn'
|
||||
`
|
||||
if mm == nil {
|
||||
mm = afero.NewMemMapFs()
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: crypto.Hash
|
||||
description: Hashes the given input with the given algorithm and returns its checksum encoded to a hexadecimal string.
|
||||
categories: []
|
||||
keywords: []
|
||||
params:
|
||||
functions_and_methods:
|
||||
aliases: []
|
||||
returnType: string
|
||||
signatures: ['crypto.Hash [ALGORITHM] INPUT']
|
||||
---
|
||||
|
||||
The `ALGORITHM` is one of `md5`, `sha1`, `sha256` (the default), `sha384`, or `sha512`:
|
||||
|
||||
```go-html-template
|
||||
{{ crypto.Hash "sha256" "Hello world" }} → 64ec88ca00b268e5ba1a35678a1b5316d212f4f366b2477232534a8aeca37f3c
|
||||
{{ "Hello world" | crypto.Hash "sha512" }} → b7f783baed8297f0db917462184ff4f08e69c2d5e5f79a942600f9725f58ce1f29c18139bf80b06c0fff2bdd34738452ecf40c488c22a7e3d80cdf6f9c1c0d47
|
||||
```
|
||||
|
||||
If you omit the algorithm, it defaults to `sha256`:
|
||||
|
||||
```go-html-template
|
||||
{{ "Hello world" | crypto.Hash }} → 64ec88ca00b268e5ba1a35678a1b5316d212f4f366b2477232534a8aeca37f3c
|
||||
```
|
||||
|
||||
The supported algorithms match those used for the [Subresource Integrity] hash in [`.Data.Integrity`] on a fingerprinted resource. Combine `crypto.Hash` with [`encoding.HexDecode`] and [`encoding.Base64Encode`] to construct an SRI hash from a string:
|
||||
|
||||
```go-html-template
|
||||
{{ $algo := "sha256" }}
|
||||
{{ $integrity := printf "%s-%s" $algo ("Hello world" | crypto.Hash $algo | encoding.HexDecode | encoding.Base64Encode) }}
|
||||
```
|
||||
|
||||
[Subresource Integrity]: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
|
||||
[`.Data.Integrity`]: /methods/resource/data/
|
||||
[`encoding.HexDecode`]: /functions/encoding/hexdecode/
|
||||
[`encoding.Base64Encode`]: /functions/encoding/base64encode/
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: encoding.HexDecode
|
||||
description: Returns the hexadecimal decoding of the given content.
|
||||
categories: []
|
||||
keywords: []
|
||||
params:
|
||||
functions_and_methods:
|
||||
aliases: []
|
||||
returnType: string
|
||||
signatures: [encoding.HexDecode INPUT]
|
||||
---
|
||||
|
||||
```go-html-template
|
||||
{{ "48656c6c6f20776f726c64" | encoding.HexDecode }} → Hello world
|
||||
```
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: encoding.HexEncode
|
||||
description: Returns the hexadecimal encoding of the given content.
|
||||
categories: []
|
||||
keywords: []
|
||||
params:
|
||||
functions_and_methods:
|
||||
aliases: []
|
||||
returnType: string
|
||||
signatures: [encoding.HexEncode INPUT]
|
||||
---
|
||||
|
||||
```go-html-template
|
||||
{{ "Hello world" | encoding.HexEncode }} → 48656c6c6f20776f726c64
|
||||
```
|
||||
@@ -2,7 +2,7 @@ module github.com/gohugoio/hugo
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69
|
||||
github.com/JohannesKaufmann/html-to-markdown/v2 v2.5.1
|
||||
github.com/JohannesKaufmann/html-to-markdown/v2 v2.5.2
|
||||
github.com/alecthomas/chroma/v2 v2.27.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.6
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.61.1
|
||||
@@ -15,7 +15,7 @@ require (
|
||||
github.com/bep/golocales v0.2.0
|
||||
github.com/bep/goportabletext v0.2.0
|
||||
github.com/bep/helpers v0.12.0
|
||||
github.com/bep/imagemeta v0.17.2
|
||||
github.com/bep/imagemeta v1.0.0
|
||||
github.com/bep/lazycache v0.8.1
|
||||
github.com/bep/logg v0.4.0
|
||||
github.com/bep/mclib v1.20401.20400
|
||||
@@ -27,19 +27,19 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0
|
||||
github.com/clbanning/mxj/v2 v2.7.0
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/evanw/esbuild v0.28.1
|
||||
github.com/evanw/esbuild v0.28.2
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/fortytw2/leaktest v1.3.0
|
||||
github.com/frankban/quicktest v1.14.6
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/getkin/kin-openapi v0.139.0
|
||||
github.com/fsnotify/fsnotify v1.10.1
|
||||
github.com/getkin/kin-openapi v0.146.0
|
||||
github.com/gobuffalo/flect v1.0.3
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/goccy/go-yaml v1.19.2
|
||||
github.com/gohugoio/gift v0.2.0
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20251018145728-cfcc22d823c6
|
||||
github.com/gohugoio/go-radix v1.2.0
|
||||
github.com/gohugoio/hashstructure v0.6.0
|
||||
github.com/gohugoio/hashstructure v1.0.0
|
||||
github.com/gohugoio/httpcache v0.8.0
|
||||
github.com/gohugoio/hugo-goldmark-extensions/extras v0.7.0
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.5.0
|
||||
@@ -47,38 +47,38 @@ require (
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/hairyhenderson/go-codeowners v0.7.0
|
||||
github.com/jdkato/prose v1.2.1
|
||||
github.com/kyokomi/emoji/v2 v2.2.13
|
||||
github.com/kyokomi/emoji/v2 v2.2.14
|
||||
github.com/magefile/mage v1.17.2
|
||||
github.com/makeworld-the-better-one/dither/v2 v2.4.0
|
||||
github.com/marekm4/color-extractor v1.2.1
|
||||
github.com/mattn/go-isatty v0.0.22
|
||||
github.com/mattn/go-isatty v0.0.24
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c
|
||||
github.com/muesli/smartcrop v0.3.0
|
||||
github.com/niklasfasching/go-org v1.9.1
|
||||
github.com/olekukonko/tablewriter v1.1.4
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
|
||||
github.com/pelletier/go-toml/v2 v2.3.1
|
||||
github.com/pelletier/go-toml/v2 v2.4.3
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
|
||||
github.com/rogpeppe/go-internal v1.15.0
|
||||
github.com/rogpeppe/go-internal v1.16.0
|
||||
github.com/spf13/afero v1.15.0
|
||||
github.com/spf13/cast v1.10.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/fsync v0.10.1
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/tdewolff/minify/v2 v2.24.13
|
||||
github.com/tdewolff/parse/v2 v2.8.12
|
||||
github.com/tdewolff/minify/v2 v2.24.16
|
||||
github.com/tdewolff/parse/v2 v2.8.15
|
||||
github.com/tetratelabs/wazero v1.12.0
|
||||
github.com/yuin/goldmark v1.8.2
|
||||
github.com/yuin/goldmark v1.8.5
|
||||
github.com/yuin/goldmark-emoji v1.0.6
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
gocloud.dev v0.45.0
|
||||
golang.org/x/image v0.42.0
|
||||
golang.org/x/mod v0.36.0
|
||||
golang.org/x/net v0.55.0
|
||||
golang.org/x/sync v0.21.0
|
||||
golang.org/x/text v0.38.0
|
||||
golang.org/x/tools v0.45.0
|
||||
golang.org/x/image v0.44.0
|
||||
golang.org/x/mod v0.38.0
|
||||
golang.org/x/net v0.57.0
|
||||
golang.org/x/sync v0.22.0
|
||||
golang.org/x/text v0.40.0
|
||||
golang.org/x/tools v0.48.0
|
||||
google.golang.org/api v0.276.0
|
||||
rsc.io/qr v0.2.0
|
||||
)
|
||||
@@ -99,10 +99,10 @@ require (
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.1 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect
|
||||
github.com/JohannesKaufmann/dom v0.2.0 // indirect
|
||||
github.com/JohannesKaufmann/dom v0.3.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.2 // indirect
|
||||
@@ -125,18 +125,18 @@ require (
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.10.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.6.0 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||
github.com/dlclark/regexp2 v1.12.0 // indirect
|
||||
github.com/dlclark/regexp2/v2 v2.2.1 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.5 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.25.5 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
@@ -146,29 +146,24 @@ require (
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/oasdiff/yaml v0.1.0 // indirect
|
||||
github.com/oasdiff/yaml3 v0.0.13 // indirect
|
||||
github.com/oasdiff/yaml v0.1.1 // indirect
|
||||
github.com/oasdiff/yaml3 v0.0.14 // indirect
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
|
||||
github.com/olekukonko/errors v1.2.0 // indirect
|
||||
github.com/olekukonko/ll v0.1.6 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
|
||||
github.com/woodsbury/decimal128 v1.3.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.43.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
|
||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||
@@ -177,17 +172,16 @@ require (
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.51.0 // indirect
|
||||
golang.org/x/crypto v0.54.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
google.golang.org/grpc v1.80.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
|
||||
google.golang.org/grpc v1.82.1 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
howett.net/plist v1.0.1 // indirect
|
||||
software.sslmate.com/src/go-pkcs12 v0.7.0 // indirect
|
||||
)
|
||||
|
||||
@@ -84,24 +84,20 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 h1:rIkQfkCOVKc1OiRCNcSDD8ml5RJlZbH/Xsq7lbpynwc=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0/go.mod h1:RD2SsorTmYhF6HkTmDw7KmPYQk8OBYwTkuasChwv7R4=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0 h1:xfK3bbi6F2RDtaZFtUdKO3osOBIhNb+xTs8lFW6yx9o=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
|
||||
github.com/JohannesKaufmann/dom v0.2.0 h1:1bragmEb19K8lHAqgFgqCpiPCFEZMTXzOIEjuxkUfLQ=
|
||||
github.com/JohannesKaufmann/dom v0.2.0/go.mod h1:57iSUl5RKric4bUkgos4zu6Xt5LMHUnw3TF1l5CbGZo=
|
||||
github.com/JohannesKaufmann/html-to-markdown/v2 v2.5.1 h1:IpUgup6ucCE4wB59wAP0Y2qSApYjFhSfGVjShUBoVSw=
|
||||
github.com/JohannesKaufmann/html-to-markdown/v2 v2.5.1/go.mod h1:KUwy/WLgv9kv2yeBZkPCgDokHzg0M6EdRc17thnbVFw=
|
||||
github.com/JohannesKaufmann/dom v0.3.1 h1:J16l9JAHWgkFPR3VIPbQ1gvS0cWab6laK1q7PFL3qh0=
|
||||
github.com/JohannesKaufmann/dom v0.3.1/go.mod h1:BZPkf8ZeYrBgABjwJn9iiKt8aiCtkxpHkevms+Yp2DE=
|
||||
github.com/JohannesKaufmann/html-to-markdown/v2 v2.5.2 h1:XFJZFWESIWlUEHHjzBuv8RvrtCWnSGlimEX17ysSDb8=
|
||||
github.com/JohannesKaufmann/html-to-markdown/v2 v2.5.2/go.mod h1:BHWO8lJzttJLqwuV8Rb1B3OG2OSzLbssZDI1FRg2eAA=
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.24.1 h1:m5ffpfZbIb++k8AqFEKy9uVgY12xIQtBsQlc6DfZJQM=
|
||||
github.com/alecthomas/chroma/v2 v2.24.1/go.mod h1:l+ohZ9xRXIbGe7cIW+YZgOGbvuVLjMps/FYN/CwuabI=
|
||||
github.com/alecthomas/chroma/v2 v2.26.1 h1:2X21EdxGZNv5GF9mG5u+uzc02GCFyGxbcBm3Grd9A78=
|
||||
github.com/alecthomas/chroma/v2 v2.26.1/go.mod h1:lxhRRa9H4hPmRLOOdYga4zkQIQjq3dtrrdwQeCfu78Y=
|
||||
github.com/alecthomas/chroma/v2 v2.27.0 h1:FodwmyOBgJULFYmDqibcp9pvfDLWdtPRh9v/r5BXYZs=
|
||||
github.com/alecthomas/chroma/v2 v2.27.0/go.mod h1:NjJ3ciIgrqBNeIkWZ4e46nseoLDslxU1LmfCoL+wcY8=
|
||||
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
||||
@@ -168,16 +164,14 @@ github.com/bep/goportabletext v0.2.0 h1:CZ9f8jADBWqHwBymQiJJPCTSV/tHSA+PYzlUf86Y
|
||||
github.com/bep/goportabletext v0.2.0/go.mod h1:xDeA5+qcgKzJq6Q6XjAiBKtxLD3Yn7f6XP4joD3J3qU=
|
||||
github.com/bep/helpers v0.12.0 h1:tD6V2DQW0B+FUynF2etR/106S/TO9akm+vA/Hk24GxY=
|
||||
github.com/bep/helpers v0.12.0/go.mod h1:PfE7MGdA8sSQ19nyDh4tYbs5rAlStlJaDI21f/fnNps=
|
||||
github.com/bep/imagemeta v0.17.2 h1:fDyXM1eAqCfBeqGLqS6UsN4OfuLM0cdu70KuLCehjOg=
|
||||
github.com/bep/imagemeta v0.17.2/go.mod h1:+Hlp195TfZpzsqCxtDKTG6eWdyz2+F2V/oCYfr3CZKA=
|
||||
github.com/bep/imagemeta v1.0.0 h1:8JZmahegn5p1M9CpFmga7LoSxErpu1uFfpSzMUNju7A=
|
||||
github.com/bep/imagemeta v1.0.0/go.mod h1:+Hlp195TfZpzsqCxtDKTG6eWdyz2+F2V/oCYfr3CZKA=
|
||||
github.com/bep/lazycache v0.8.1 h1:ko6ASLjkPxyV5DMWoNNZ8B2M0weyjqXX8IZkjBoBtvg=
|
||||
github.com/bep/lazycache v0.8.1/go.mod h1:pbEiFsZoq7cLXvrTll0AHOPEurB1aGGxx4jKjOtlx9w=
|
||||
github.com/bep/logg v0.4.0 h1:luAo5mO4ZkhA5M1iDVDqDqnBBnlHjmtZF6VAyTp+nCQ=
|
||||
github.com/bep/logg v0.4.0/go.mod h1:Ccp9yP3wbR1mm++Kpxet91hAZBEQgmWgFgnXX3GkIV0=
|
||||
github.com/bep/mclib v1.20401.20400 h1:silTOMNlNI7yHBb+HxEE0THIVFVWo/0I4SCH69FxtmU=
|
||||
github.com/bep/mclib v1.20401.20400/go.mod h1:v5Hh3EIinPn7epigP28uf9JCkZlYzBS2vEOPe2wrHzM=
|
||||
github.com/bep/overlayfs v0.10.0 h1:wS3eQ6bRsLX+4AAmwGjvoFSAQoeheamxofFiJ2SthSE=
|
||||
github.com/bep/overlayfs v0.10.0/go.mod h1:ouu4nu6fFJaL0sPzNICzxYsBeWwrjiTdFZdK4lI3tro=
|
||||
github.com/bep/overlayfs v0.11.0 h1:aymHDGC0CHpvn0XvTfgpK6skCp16oMi+tdUF32l6pPs=
|
||||
github.com/bep/overlayfs v0.11.0/go.mod h1:L+ggdoKm+Y7Xb4a1osd+/LOPG4qsY62snqRqJH5Mspc=
|
||||
github.com/bep/simplecobra v0.7.0 h1:kG8ZPwEc1o96hlIVGXcrrvwC8RornBqvMD3+pS0Z7y0=
|
||||
@@ -204,8 +198,8 @@ github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsV
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
|
||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
|
||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
@@ -215,8 +209,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
|
||||
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2/v2 v2.1.1 h1:LCUGyd9Wf+r+VVOl8Ny38JTpWJcAsdVnCIuhhtthmKw=
|
||||
github.com/dlclark/regexp2/v2 v2.1.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
|
||||
github.com/dlclark/regexp2/v2 v2.2.1 h1:mf4KkFUj0gJuarK8P+LgiS+Lit7m9N1yAwEfPbee7R0=
|
||||
github.com/dlclark/regexp2/v2 v2.2.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
@@ -228,17 +220,15 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
|
||||
github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
|
||||
github.com/evanw/esbuild v0.28.0 h1:V96ghtc5p5JnNUQIUsc5H3kr+AcFcMqOJll2ZmJW6Lo=
|
||||
github.com/evanw/esbuild v0.28.0/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
|
||||
github.com/evanw/esbuild v0.28.1 h1:ds+yuRyUaZGx++GR56CrCeuXh8PVhVM4xq8v7PNELFc=
|
||||
github.com/evanw/esbuild v0.28.1/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=
|
||||
github.com/evanw/esbuild v0.28.2 h1:A2uETn4jrQTcXaT/shwTDTYBxDjl7fV7nXmUrJxfA2w=
|
||||
github.com/evanw/esbuild v0.28.2/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
@@ -248,10 +238,10 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu
|
||||
github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/getkin/kin-openapi v0.139.0 h1:pBFXcZJFwz9J1X64jzxlOoNgFm+TF7kNrs9+HJVN6Ic=
|
||||
github.com/getkin/kin-openapi v0.139.0/go.mod h1:NGxPfE4PwS/TRXEbyx2RrxDFPZvxcWw31Tw8XXjPZLs=
|
||||
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/getkin/kin-openapi v0.146.0 h1:RA/1RdxrSJW4oc1+6IfnYB6AO9CaGy8GTKPh0k4Ordo=
|
||||
github.com/getkin/kin-openapi v0.146.0/go.mod h1:3BH9M9XDe/y9M5DSvEocVYAYq1w0qrhJHjC/vZi0AaY=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
@@ -262,14 +252,14 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA=
|
||||
github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0=
|
||||
github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo=
|
||||
github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU=
|
||||
github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM=
|
||||
github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4=
|
||||
github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
@@ -282,8 +272,8 @@ github.com/gohugoio/go-i18n/v2 v2.1.3-0.20251018145728-cfcc22d823c6 h1:pxlAea9eR
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20251018145728-cfcc22d823c6/go.mod h1:m5hu1im5Qc7LDycVLvee6MPobJiRLBYHklypFJR0/aE=
|
||||
github.com/gohugoio/go-radix v1.2.0 h1:D5GTk8jIoeXirBSc2P4E4NdHKDrenk9k9N0ctU5Yrhg=
|
||||
github.com/gohugoio/go-radix v1.2.0/go.mod h1:k6vDa0ebpbpgtzSj9lPGJcA4AZwJ9xUNObUy2vczPFM=
|
||||
github.com/gohugoio/hashstructure v0.6.0 h1:7wMB/2CfXoThFYhdWRGv3u3rUM761Cq29CxUW+NltUg=
|
||||
github.com/gohugoio/hashstructure v0.6.0/go.mod h1:lapVLk9XidheHG1IQ4ZSbyYrXcaILU1ZEP/+vno5rBQ=
|
||||
github.com/gohugoio/hashstructure v1.0.0 h1:vWYuyzs1n0LdI0F54TJQeYAiB44fHX7H9hCp9X6gHKg=
|
||||
github.com/gohugoio/hashstructure v1.0.0/go.mod h1:FSbTK4QwxucJ2bC4Lvrs9a6x0DbQDXNoyBO+h4nlCgE=
|
||||
github.com/gohugoio/httpcache v0.8.0 h1:hNdsmGSELztetYCsPVgjA960zSa4dfEqqF/SficorCU=
|
||||
github.com/gohugoio/httpcache v0.8.0/go.mod h1:fMlPrdY/vVJhAriLZnrF5QpN3BNAcoBClgAyQd+lGFI=
|
||||
github.com/gohugoio/hugo-goldmark-extensions/extras v0.7.0 h1:I/n6v7VImJ3aISLnn73JAHXyjcQsMVvbguQPTk9Ehus=
|
||||
@@ -387,8 +377,6 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
|
||||
github.com/jdkato/prose v1.2.1 h1:Fp3UnJmLVISmlc57BgKUzdjr0lOtjqTZicL3PaYy6cU=
|
||||
github.com/jdkato/prose v1.2.1/go.mod h1:AiRHgVagnEx2JbQRQowVBKjG0bcs/vtkGCH1dYAL1rA=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
|
||||
@@ -404,28 +392,24 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/kyokomi/emoji/v2 v2.2.13 h1:GhTfQa67venUUvmleTNFnb+bi7S3aocF7ZCXU9fSO7U=
|
||||
github.com/kyokomi/emoji/v2 v2.2.13/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE=
|
||||
github.com/kyokomi/emoji/v2 v2.2.14 h1:YOF6VL52613M0Qr9v4puJDD9QQPmyyjXedDDlrGzH80=
|
||||
github.com/kyokomi/emoji/v2 v2.2.14/go.mod h1:1AnYl9IgmJZXKd5m1PEijyyUw85SqYsuAr8lpU/s+9s=
|
||||
github.com/magefile/mage v1.17.2 h1:fyXVu1eadI8Ap1HCCNgEhJ5McIWiYhLR8uol64ZZc40=
|
||||
github.com/magefile/mage v1.17.2/go.mod h1:Yj51kqllmsgFpvvSzgrZPK9WtluG3kUhFaBUVLo4feA=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/makeworld-the-better-one/dither/v2 v2.4.0 h1:Az/dYXiTcwcRSe59Hzw4RI1rSnAZns+1msaCXetrMFE=
|
||||
github.com/makeworld-the-better-one/dither/v2 v2.4.0/go.mod h1:VBtN8DXO7SNtyGmLiGA7IsFeKrBkQPze1/iAeM95arc=
|
||||
github.com/marekm4/color-extractor v1.2.1 h1:3Zb2tQsn6bITZ8MBVhc33Qn1k5/SEuZ18mrXGUqIwn0=
|
||||
github.com/marekm4/color-extractor v1.2.1/go.mod h1:90VjmiHI6M8ez9eYUaXLdcKnS+BAOp7w+NpwBdkJmpA=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
||||
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
|
||||
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
|
||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE=
|
||||
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/montanaflynn/stats v0.6.3/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/muesli/smartcrop v0.3.0 h1:JTlSkmxWg/oQ1TcLDoypuirdE8Y/jzNirQeLkxpA6Oc=
|
||||
github.com/muesli/smartcrop v0.3.0/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78RwcQLUkocpI=
|
||||
@@ -434,10 +418,10 @@ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/niklasfasching/go-org v1.9.1 h1:/3s4uTPOF06pImGa2Yvlp24yKXZoTYM+nsIlMzfpg/0=
|
||||
github.com/niklasfasching/go-org v1.9.1/go.mod h1:ZAGFFkWvUQcpazmi/8nHqwvARpr1xpb+Es67oUGX/48=
|
||||
github.com/oasdiff/yaml v0.1.0 h1:0bqZjfKc/8S9urj4JuwepX41WX9EoA6ifhU3SV06cXg=
|
||||
github.com/oasdiff/yaml v0.1.0/go.mod h1:kOlRmMdL2X3vucLCEQO5u61SU22RysnfXvcttrZA1O0=
|
||||
github.com/oasdiff/yaml3 v0.0.13 h1:06svmvOHOVBqF81+sY2EUScvUI/iS/vl2VIeUUxZQwg=
|
||||
github.com/oasdiff/yaml3 v0.0.13/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
|
||||
github.com/oasdiff/yaml v0.1.1 h1:6nHx+pn9gBRM6YpBlFZFQGCCd1nuvqOBtTD3KKTgGxY=
|
||||
github.com/oasdiff/yaml v0.1.1/go.mod h1:EYJNoyktvWMJ0Hmhx+6qTaqMOsalUaRGT8Sj1hNcegU=
|
||||
github.com/oasdiff/yaml3 v0.0.14 h1:aLJee3hxBK2H5wdXd9iPcIXb93Nty1Ge0pT171eHtkw=
|
||||
github.com/oasdiff/yaml3 v0.0.14/go.mod h1:csto2xfDjYccdUn/yw/bPjj/cYTdp6HtFA0J4TWG+gg=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
|
||||
github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo=
|
||||
@@ -448,10 +432,8 @@ github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVW
|
||||
github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
|
||||
github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc=
|
||||
github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
|
||||
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
@@ -468,8 +450,8 @@ github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.15.0 h1:D0RCU5rMAp+SpgkiNdrjfJ+LX4J1M32V2NeCY7EJ6hc=
|
||||
github.com/rogpeppe/go-internal v1.15.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
|
||||
github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g=
|
||||
github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
|
||||
@@ -507,25 +489,20 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tdewolff/minify/v2 v2.24.13 h1:xrcF7gKDnUszseEY9WX9mUlZII2v2Go/QAcAwRASw58=
|
||||
github.com/tdewolff/minify/v2 v2.24.13/go.mod h1:emvwoYeIl8bfAKqRU5ww95LX9Gpggpqv/naal9a8Yq0=
|
||||
github.com/tdewolff/parse/v2 v2.8.12 h1:5BBjfaCv482v3nltlS0u6wH1xJaxjR6ofDrWttNvROg=
|
||||
github.com/tdewolff/parse/v2 v2.8.12/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo=
|
||||
github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||
github.com/tdewolff/minify/v2 v2.24.16 h1:1d+IHElvmYM2zJtuykMK/esiqc4H/7pqPKAa7jpSUUU=
|
||||
github.com/tdewolff/minify/v2 v2.24.16/go.mod h1:G6yep6HFo3u2eoOcIVBKSNwpoW2wXvPjvnFRj3w1zlc=
|
||||
github.com/tdewolff/parse/v2 v2.8.15 h1:3/Psth16LHpsy2NS/7ShXDsoWt1x0f3tQMSbmQi+jO8=
|
||||
github.com/tdewolff/parse/v2 v2.8.15/go.mod h1:XdsoSFThlVIRIajAuqz1evNY7bagZS8LBOPA3aVopwQ=
|
||||
github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk=
|
||||
github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||
github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU=
|
||||
github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0=
|
||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0=
|
||||
github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE=
|
||||
github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
|
||||
github.com/yuin/goldmark v1.8.5/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs=
|
||||
github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
@@ -536,8 +513,8 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.43.0 h1:62yY3dT7/ShwOxzA0RsKRgshBmfElKI4d/Myu2OxDFU=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.43.0/go.mod h1:RyaZMFY7yi1kAs45S6mbFGz8O8rqB0dTY14uzvG4LCs=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
|
||||
@@ -567,8 +544,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
|
||||
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -581,8 +558,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.42.0 h1:1gSs6ehNWXLbkHBIPcWztk3D/6aIA/8hauiAYtlodVY=
|
||||
golang.org/x/image v0.42.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
|
||||
golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I=
|
||||
golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -604,8 +581,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
|
||||
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -637,8 +614,8 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
|
||||
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -660,8 +637,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -698,8 +675,8 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -707,8 +684,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -761,8 +738,8 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
|
||||
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -837,10 +814,10 @@ google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6D
|
||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
|
||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@@ -857,8 +834,8 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
||||
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
||||
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
|
||||
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
||||
@@ -16,6 +16,7 @@ package htesting
|
||||
import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
@@ -58,6 +59,27 @@ func CreateTempDir(fs afero.Fs, prefix string) (string, func(), error) {
|
||||
return tempDir, func() { fs.RemoveAll(tempDir) }, nil
|
||||
}
|
||||
|
||||
// IsCaseInsensitiveFs reports whether dir lives on a case-insensitive filesystem
|
||||
// (e.g. the default on macOS and Windows).
|
||||
func IsCaseInsensitiveFs(dir string) (bool, error) {
|
||||
f, err := os.CreateTemp(dir, "case-*.tmp")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
_, err = os.Stat(filepath.Join(dir, strings.ToUpper(filepath.Base(f.Name()))))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// BailOut panics with a stack trace after the given duration. Useful for
|
||||
// hanging tests.
|
||||
func BailOut(after time.Duration) {
|
||||
|
||||
@@ -15,10 +15,37 @@ package hugofs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// hasSymlinkParent reports whether any directory between name and base is a symlink.
|
||||
// Lstat only refrains from following the last element of a path, so callers checking
|
||||
// name itself need this to keep a symlinked parent from escaping base.
|
||||
// base itself is not checked; an empty base walks to the root of fs.
|
||||
func hasSymlinkParent(fs afero.Fs, base, name string) (bool, error) {
|
||||
for name != base {
|
||||
parent := filepath.Dir(name)
|
||||
if parent == name || parent == base || parent == "." {
|
||||
return false, nil
|
||||
}
|
||||
fi, err := LstatIfPossible(fs, parent)
|
||||
if err != nil {
|
||||
if herrors.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
if fi.Mode()&os.ModeSymlink != 0 {
|
||||
return true, nil
|
||||
}
|
||||
name = parent
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// NewDropSymlinksFs returns an afero.Fs wrapper that treats symlinks as non-existing files.
|
||||
func NewDropSymlinksFs(base afero.Fs) *DropSymlinksFs {
|
||||
return &DropSymlinksFs{base}
|
||||
@@ -48,5 +75,12 @@ func (fs *DropSymlinksFs) Stat(name string) (os.FileInfo, error) {
|
||||
if fi.Mode()&os.ModeSymlink != 0 {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
symlinkParent, err := hasSymlinkParent(fs.Fs, "", name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if symlinkParent {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
return fi, nil
|
||||
}
|
||||
|
||||
@@ -729,10 +729,18 @@ func (fs *RootMappingFs) statRoot(root *RootMapping, filename string) (FileMetaI
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Don't allow symlinks to escape the mount.
|
||||
// Don't allow symlinks to escape the mount, neither as the file itself
|
||||
// nor as any directory between it and the mount root.
|
||||
if fi.Mode()&os.ModeSymlink != 0 {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
symlinkParent, err := hasSymlinkParent(fs.Fs, root.To, filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if symlinkParent {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
var opener func() (afero.File, error)
|
||||
if !fi.IsDir() {
|
||||
|
||||
+16
-6
@@ -96,7 +96,7 @@ func (s *Site) writeDestAlias(path, permalink string, outputFormat output.Format
|
||||
func (s *Site) publishDestAlias(allowRoot bool, path, permalink string, outputFormat output.Format, p page.Page) (err error) {
|
||||
handler := newAliasHandler(s.GetTemplateStore(), s.Log, allowRoot)
|
||||
|
||||
targetPath, err := handler.targetPathAlias(path)
|
||||
targetPath, err := handler.targetPathAlias(path, outputFormat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -120,7 +120,7 @@ func (s *Site) publishDestAlias(allowRoot bool, path, permalink string, outputFo
|
||||
return s.publisher.Publish(pd)
|
||||
}
|
||||
|
||||
func (a aliasHandler) targetPathAlias(src string) (string, error) {
|
||||
func (a aliasHandler) targetPathAlias(src string, of output.Format) (string, error) {
|
||||
originalAlias := src
|
||||
if len(src) <= 0 {
|
||||
return "", fmt.Errorf("alias \"\" is an empty string")
|
||||
@@ -176,13 +176,23 @@ func (a aliasHandler) targetPathAlias(src string) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Add the final touch
|
||||
// Add the final touch. When the alias does not already end in one of the
|
||||
// output format's configured suffixes, treat it as a directory and append
|
||||
// the format's base name and suffix (e.g. index.html).
|
||||
alias = strings.TrimPrefix(alias, "/")
|
||||
baseFile := of.BaseName + of.MediaType.FirstSuffix.FullSuffix
|
||||
if strings.HasSuffix(alias, "/") {
|
||||
alias = alias + "index.html"
|
||||
} else if !strings.HasSuffix(alias, ".html") {
|
||||
alias = alias + "/" + "index.html"
|
||||
alias = alias + baseFile
|
||||
} else if !pathHasOutputFormatSuffix(alias, of) {
|
||||
alias = alias + "/" + baseFile
|
||||
}
|
||||
|
||||
return filepath.FromSlash(alias), nil
|
||||
}
|
||||
|
||||
// pathHasOutputFormatSuffix reports whether the last element of p ends in one
|
||||
// of the suffixes configured for the output format's media type.
|
||||
func pathHasOutputFormatSuffix(p string, of output.Format) bool {
|
||||
ext := path.Ext(p)
|
||||
return ext != "" && of.MediaType.HasSuffix(ext[1:])
|
||||
}
|
||||
|
||||
+77
-1
@@ -21,6 +21,7 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/output"
|
||||
)
|
||||
|
||||
func TestAlias(t *testing.T) {
|
||||
@@ -158,7 +159,7 @@ func TestTargetPathHTMLRedirectAlias(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
path, err := h.targetPathAlias(test.value)
|
||||
path, err := h.targetPathAlias(test.value, output.HTMLFormat)
|
||||
if (err == nil) != test.errIsNil {
|
||||
t.Errorf("Expected err == nil => %t, got: %t. err: %s", test.errIsNil, err == nil, err)
|
||||
continue
|
||||
@@ -789,3 +790,78 @@ build:
|
||||
b.AssertFileExists("public/p2-alias/index.html", false)
|
||||
b.AssertFileExists("public/p3-alias/index.html", false)
|
||||
}
|
||||
|
||||
// See issue 15066.
|
||||
func TestAliasExplicitExtension(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
|
||||
uglyURLs = UGLYURLS
|
||||
|
||||
[mediaTypes]
|
||||
[mediaTypes.'text/html']
|
||||
suffixes = SUFFIXES
|
||||
|
||||
[outputFormats]
|
||||
[outputFormats.html]
|
||||
mediaType = 'text/html'
|
||||
|
||||
-- content/s1/p1.md --
|
||||
---
|
||||
title: p1
|
||||
aliases: [a, b.foo, c.html, d.htm, e.foo.html, f.foo.htm]
|
||||
---
|
||||
-- layouts/page.html --
|
||||
{{ .Title }}
|
||||
`
|
||||
|
||||
r := strings.NewReplacer("UGLYURLS", "false", "SUFFIXES", "['html', 'htm']")
|
||||
f := r.Replace(files)
|
||||
|
||||
b := Test(t, f)
|
||||
|
||||
b.AssertFileContent("public/s1/a/index.html", "url=/s1/p1/")
|
||||
b.AssertFileContent("public/s1/b.foo/index.html", "url=/s1/p1/")
|
||||
b.AssertFileContent("public/s1/c.html", "url=/s1/p1/")
|
||||
b.AssertFileContent("public/s1/d.htm", "url=/s1/p1/")
|
||||
b.AssertFileContent("public/s1/e.foo.html", "url=/s1/p1/")
|
||||
b.AssertFileContent("public/s1/f.foo.htm", "url=/s1/p1/")
|
||||
|
||||
r = strings.NewReplacer("UGLYURLS", "false", "SUFFIXES", "['htm', 'html']")
|
||||
f = r.Replace(files)
|
||||
|
||||
b = Test(t, f)
|
||||
|
||||
b.AssertFileContent("public/s1/a/index.htm", "url=/s1/p1/")
|
||||
b.AssertFileContent("public/s1/b.foo/index.htm", "url=/s1/p1/")
|
||||
b.AssertFileContent("public/s1/c.html", "url=/s1/p1/")
|
||||
b.AssertFileContent("public/s1/d.htm", "url=/s1/p1/")
|
||||
b.AssertFileContent("public/s1/e.foo.html", "url=/s1/p1/")
|
||||
b.AssertFileContent("public/s1/f.foo.htm", "url=/s1/p1/")
|
||||
|
||||
r = strings.NewReplacer("UGLYURLS", "true", "SUFFIXES", "['html', 'htm']")
|
||||
f = r.Replace(files)
|
||||
|
||||
b = Test(t, f)
|
||||
|
||||
b.AssertFileContent("public/s1/a.html", "url=/s1/p1.html")
|
||||
b.AssertFileContent("public/s1/b.foo.html", "url=/s1/p1.html")
|
||||
b.AssertFileContent("public/s1/c.html", "url=/s1/p1.html")
|
||||
b.AssertFileContent("public/s1/d.htm", "url=/s1/p1.html")
|
||||
b.AssertFileContent("public/s1/e.foo.html", "url=/s1/p1.html")
|
||||
b.AssertFileContent("public/s1/f.foo.htm", "url=/s1/p1.html")
|
||||
|
||||
r = strings.NewReplacer("UGLYURLS", "true", "SUFFIXES", "['htm', 'html']")
|
||||
f = r.Replace(files)
|
||||
|
||||
b = Test(t, f)
|
||||
|
||||
b.AssertFileContent("public/s1/a.htm", "url=/s1/p1.htm")
|
||||
b.AssertFileContent("public/s1/b.foo.htm", "url=/s1/p1.htm")
|
||||
b.AssertFileContent("public/s1/c.html", "url=/s1/p1.htm")
|
||||
b.AssertFileContent("public/s1/d.htm", "url=/s1/p1.htm")
|
||||
b.AssertFileContent("public/s1/e.foo.html", "url=/s1/p1.htm")
|
||||
b.AssertFileContent("public/s1/f.foo.htm", "url=/s1/p1.htm")
|
||||
}
|
||||
|
||||
+8
-25
@@ -315,11 +315,11 @@ baseName = "o1main"
|
||||
|
||||
[languages]
|
||||
[languages.en]
|
||||
languageName = "English"
|
||||
label = "English"
|
||||
[languages.en.params]
|
||||
pl1 = "p1-en-main"
|
||||
[languages.nb]
|
||||
languageName = "Norsk"
|
||||
label = "Norsk"
|
||||
[languages.nb.params]
|
||||
pl1 = "p1-nb-main"
|
||||
|
||||
@@ -368,7 +368,7 @@ baseName = "o2theme"
|
||||
|
||||
[languages]
|
||||
[languages.en]
|
||||
languageName = "English2"
|
||||
label = "English2"
|
||||
[languages.en.params]
|
||||
pl1 = "p1-en-theme"
|
||||
pl2 = "p2-en-theme"
|
||||
@@ -377,7 +377,7 @@ name = "menu-lang-en-main"
|
||||
[[languages.en.menu.theme]]
|
||||
name = "menu-lang-en-theme"
|
||||
[languages.nb]
|
||||
languageName = "Norsk2"
|
||||
label = "Norsk2"
|
||||
[languages.nb.params]
|
||||
pl1 = "p1-nb-theme"
|
||||
pl2 = "p2-nb-theme"
|
||||
@@ -905,16 +905,16 @@ func TestConfigMergeLanguageDeepEmptyLeftSide(t *testing.T) {
|
||||
[params]
|
||||
p1 = "p1base"
|
||||
[languages.en]
|
||||
languageCode = 'en-US'
|
||||
languageName = 'English'
|
||||
locale = 'en-US'
|
||||
label = 'English'
|
||||
weight = 1
|
||||
[languages.en.markup.goldmark.extensions.typographer]
|
||||
leftDoubleQuote = '“' # default “
|
||||
rightDoubleQuote = '”' # default ”
|
||||
|
||||
[languages.de]
|
||||
languageCode = 'de-DE'
|
||||
languageName = 'Deutsch'
|
||||
locale = 'de-DE'
|
||||
label = 'Deutsch'
|
||||
weight = 2
|
||||
[languages.de.params]
|
||||
p1 = "p1de"
|
||||
@@ -1000,23 +1000,6 @@ HTACCESS.
|
||||
b.AssertFileContent("public/.htaccess", "HTACCESS")
|
||||
}
|
||||
|
||||
func TestConfigLanguageCodeTopLevel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
languageCode = "en-US"
|
||||
-- layouts/home.html --
|
||||
LanguageCode: {{ .Site.LanguageCode }}|{{ site.Language.LanguageCode }}|
|
||||
|
||||
|
||||
`
|
||||
b := Test(t, files, TestOptInfo())
|
||||
|
||||
b.AssertLogContains("deprecated")
|
||||
b.AssertFileContent("public/index.html", "LanguageCode: en-US|en-US|")
|
||||
}
|
||||
|
||||
// See #11159
|
||||
func TestConfigOutputFormatsPerLanguage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -114,6 +114,7 @@ func (f ContentFactory) CreateContentPlaceHolder(filename string, force bool) (s
|
||||
// the paths correct.
|
||||
placeholder := `---
|
||||
title: "Content Placeholder"
|
||||
draft: false
|
||||
build:
|
||||
render: never
|
||||
list: never
|
||||
|
||||
@@ -362,7 +362,7 @@ func (a *allPagesAssembler) doCreatePages(prefix string, depth int) error {
|
||||
|
||||
switch v := n.(type) {
|
||||
case contentNodeSeq, contentNodes:
|
||||
return handleContentNodeSeq(contentNodeToSeq(v))
|
||||
return handleContentNodeSeq(cnh.contentNodeToSeq(v))
|
||||
case *pageMetaSource:
|
||||
n2, err = handlePageMetaSource(v, nil, false)
|
||||
if err != nil {
|
||||
@@ -781,7 +781,7 @@ func (a *allPagesAssembler) doCreatePages(prefix string, depth int) error {
|
||||
|
||||
if _, found := nodes[p.s.siteVector]; !found {
|
||||
var rs *resourceSource
|
||||
match := cnh.findContentNodeForSiteVector(p.s.siteVector, duplicateResourceFiles, contentNodeToSeq(n))
|
||||
match := cnh.findContentNodeForSiteVector(p.s.siteVector, duplicateResourceFiles, cnh.contentNodeToSeq(n))
|
||||
if match == nil {
|
||||
return true
|
||||
}
|
||||
@@ -1385,8 +1385,11 @@ func (a *allPagesAssembler) createMissingTaxonomies() error {
|
||||
for viewName, languages := range viewLanguages {
|
||||
key := viewName.pluralTreeKey
|
||||
if a.h.isRebuild() {
|
||||
if v := tree.Get(key); v != nil {
|
||||
// Already there.
|
||||
// Note that we cannot use tree.Get here, as the tree at this point
|
||||
// may hold not yet assembled *pageMetaSource nodes.
|
||||
// We're only interested in whether the auto created (not file backed)
|
||||
// taxonomy node is still there.
|
||||
if n, found := tree.GetRaw(key); found && cnh.hasAutoContentNode(n) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ var _ contentNode = (*contentNodeSeq)(nil)
|
||||
|
||||
var (
|
||||
_ contentNode = (*resourceSource)(nil)
|
||||
_ contentNodeContentWeightProvider = (*pageMetaSource)(nil)
|
||||
_ contentNodeContentWeightProvider = (*pageState)(nil)
|
||||
_ contentNodeForSites = (*pageState)(nil)
|
||||
_ contentNodePage = (*contentNodes)(nil)
|
||||
@@ -395,7 +396,15 @@ func contentNodeToContentNodesPage(n contentNode) (contentNodesMap, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func contentNodeToSeq(n contentNodeForEach) contentNodeSeq {
|
||||
// hasAutoContentNode reports whether n holds at least one node that is not backed by a file.
|
||||
func (h helperContentNode) hasAutoContentNode(n contentNodeForEach) bool {
|
||||
return !n.forEeachContentNode(func(_ sitesmatrix.Vector, nn contentNode) bool {
|
||||
wp, ok := nn.(contentNodeContentWeightProvider)
|
||||
return ok && wp.contentWeight() > 0
|
||||
})
|
||||
}
|
||||
|
||||
func (h helperContentNode) contentNodeToSeq(n contentNodeForEach) contentNodeSeq {
|
||||
if nn, ok := n.(contentNodeSeq); ok {
|
||||
return nn
|
||||
}
|
||||
@@ -406,7 +415,7 @@ func contentNodeToSeq(n contentNodeForEach) contentNodeSeq {
|
||||
}
|
||||
}
|
||||
|
||||
func contentNodeToSeq2(n contentNodeForEach) contentNodeSeq2 {
|
||||
func (h helperContentNode) contentNodeToSeq2(n contentNodeForEach) contentNodeSeq2 {
|
||||
if nn, ok := n.(contentNodeSeq2); ok {
|
||||
return nn
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ func (s *contentNodeShifter) ForEeachInAllDimensions(n contentNode, f func(conte
|
||||
|
||||
func (s *contentNodeShifter) ForEeachInDimension(n contentNode, vec sitesmatrix.Vector, d int, f func(contentNode) bool) {
|
||||
LOOP1:
|
||||
for vec2, v := range contentNodeToSeq2(n) {
|
||||
for vec2, v := range cnh.contentNodeToSeq2(n) {
|
||||
for i, v := range vec2 {
|
||||
if i != d && v != vec[i] {
|
||||
continue LOOP1
|
||||
@@ -170,7 +170,7 @@ func (s *contentNodeShifter) Shift(n contentNode, siteVector sitesmatrix.Vector,
|
||||
}
|
||||
// The exact match is an auto page (not backed by a file).
|
||||
// Check if there's a file-backed complement that should take precedence.
|
||||
if vvv := cnh.findContentNodeForSiteVector(siteVector, fallback, contentNodeToSeq(n)); vvv != nil {
|
||||
if vvv := cnh.findContentNodeForSiteVector(siteVector, fallback, cnh.contentNodeToSeq(n)); vvv != nil {
|
||||
return vvv, true
|
||||
}
|
||||
return exact, true
|
||||
@@ -180,7 +180,7 @@ func (s *contentNodeShifter) Shift(n contentNode, siteVector sitesmatrix.Vector,
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if vvv := cnh.findContentNodeForSiteVector(siteVector, fallback, contentNodeToSeq(n)); vvv != nil {
|
||||
if vvv := cnh.findContentNodeForSiteVector(siteVector, fallback, cnh.contentNodeToSeq(n)); vvv != nil {
|
||||
return vvv, true
|
||||
}
|
||||
|
||||
|
||||
@@ -426,16 +426,10 @@ func (d *SourceFilesystem) RealFilename(rel string) string {
|
||||
}
|
||||
|
||||
// Contains returns whether the given filename is a member of the current filesystem.
|
||||
// It's stat-free, so it also works for deleted files.
|
||||
func (d *SourceFilesystem) Contains(filename string) bool {
|
||||
for _, dir := range d.mounts() {
|
||||
if !dir.IsDir() {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(filename, dir.Meta().Filename) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
cps, _ := d.ReverseLookup(filename, false)
|
||||
return len(cps) > 0
|
||||
}
|
||||
|
||||
// RealDirs gets a list of absolute paths to directories starting from the given
|
||||
|
||||
@@ -69,18 +69,18 @@ path="d"
|
||||
|
||||
[languages.en]
|
||||
title = "Title in English"
|
||||
languageName = "English"
|
||||
label = "English"
|
||||
weight = 1
|
||||
[languages.nn]
|
||||
languageName = "Nynorsk"
|
||||
label = "Nynorsk"
|
||||
weight = 2
|
||||
title = "Tittel på nynorsk"
|
||||
[languages.nb]
|
||||
languageName = "Bokmål"
|
||||
label = "Bokmål"
|
||||
weight = 3
|
||||
title = "Tittel på bokmål"
|
||||
[languages.fr]
|
||||
languageName = "French"
|
||||
label = "French"
|
||||
weight = 4
|
||||
title = "French Title"
|
||||
|
||||
@@ -312,7 +312,7 @@ func TestMountSameSource(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = 'https://example.org/'
|
||||
languageCode = 'en-us'
|
||||
locale = 'en-us'
|
||||
title = 'Hugo GitHub Issue #9426'
|
||||
|
||||
disableKinds = ['RSS','sitemap','taxonomy','term']
|
||||
|
||||
@@ -1080,6 +1080,8 @@ func (h *HugoSites) processPartialFileEvents(ctx context.Context, l logg.LevelLo
|
||||
changes = append(changes, identity.GenghisKhan)
|
||||
case files.ComponentFolderArchetypes:
|
||||
// Ignore for now.
|
||||
case files.ComponentFolderStatic:
|
||||
// Handled by the static file syncer.
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown component: %q", pathInfo.Component()))
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ defaultContentLanguage = "nn"
|
||||
|
||||
[languages]
|
||||
[languages.nn]
|
||||
languageName = "Nynorsk"
|
||||
label = "Nynorsk"
|
||||
weight = 1
|
||||
title = "Tittel på Nynorsk"
|
||||
[languages.nn.params]
|
||||
@@ -59,7 +59,7 @@ p1 = "p1nn"
|
||||
|
||||
[languages.en]
|
||||
title = "Title in English"
|
||||
languageName = "English"
|
||||
label = "English"
|
||||
weight = 2
|
||||
[languages.en.params]
|
||||
p1 = "p1en"
|
||||
|
||||
@@ -31,13 +31,13 @@ staticDir2 = ["staticen"]
|
||||
baseURL = "https://example.com/docs"
|
||||
weight = 10
|
||||
title = "In English"
|
||||
languageName = "English"
|
||||
label = "English"
|
||||
[languages.fr]
|
||||
staticDir2 = ["staticfr"]
|
||||
baseURL = "https://example.fr"
|
||||
weight = 20
|
||||
title = "Le Français"
|
||||
languageName = "Français"
|
||||
label = "Français"
|
||||
-- assets/css/main.css --
|
||||
body { color: red; }
|
||||
-- content/mysect/mybundle/index.md --
|
||||
|
||||
@@ -72,11 +72,13 @@ weight = 100
|
||||
[[module.mounts]]
|
||||
source = 'content/en'
|
||||
target = 'content'
|
||||
lang = 'en'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = 'en'
|
||||
[[module.mounts]]
|
||||
source = 'content/nn'
|
||||
target = 'content'
|
||||
lang = 'nn'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = 'nn'
|
||||
[[module.mounts]]
|
||||
source = 'content/all'
|
||||
target = 'content'
|
||||
@@ -611,11 +613,13 @@ target = "data"
|
||||
[[module.mounts]]
|
||||
source = "content/en"
|
||||
target = "content"
|
||||
lang = "en"
|
||||
[module.mounts.sites.matrix]
|
||||
languages = "en"
|
||||
[[module.mounts]]
|
||||
source = "content/nn"
|
||||
target = "content"
|
||||
lang = "nn"
|
||||
[module.mounts.sites.matrix]
|
||||
languages = "nn"
|
||||
[[module.imports]]
|
||||
path = "mytheme"
|
||||
-- layouts/home.html --
|
||||
@@ -775,19 +779,23 @@ defaultContentLanguage = 'en'
|
||||
[[module.mounts]]
|
||||
source = 'content/en'
|
||||
target = 'content/en'
|
||||
lang = 'en'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = 'en'
|
||||
[[module.mounts]]
|
||||
source = 'content/nn'
|
||||
target = 'content/nn'
|
||||
lang = 'nn'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = 'nn'
|
||||
[[module.mounts]]
|
||||
source = 'content/no'
|
||||
target = 'content/no'
|
||||
lang = 'no'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = 'no'
|
||||
[[module.mounts]]
|
||||
source = 'content/sv'
|
||||
target = 'content/sv'
|
||||
lang = 'sv'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = 'sv'
|
||||
[[module.mounts]]
|
||||
source = 'layouts'
|
||||
target = 'layouts'
|
||||
|
||||
@@ -231,11 +231,12 @@ type IntegrationTestBuilder struct {
|
||||
|
||||
Cfg IntegrationTestConfig
|
||||
|
||||
changedFiles []string
|
||||
createdFiles []string
|
||||
removedFiles []string
|
||||
renamedFiles []string
|
||||
renamedDirs []string
|
||||
changedFiles []string
|
||||
createdFiles []string
|
||||
removedFiles []string
|
||||
renamedFiles []string
|
||||
atomicSavedFiles []string
|
||||
renamedDirs []string
|
||||
|
||||
buildCount int
|
||||
GCCount int
|
||||
@@ -748,6 +749,14 @@ func (s *IntegrationTestBuilder) EditFileReplaceAll(filename, old, new string) *
|
||||
})
|
||||
}
|
||||
|
||||
// EditFileAtomicReplaceAll edits a file and simulates an editor atomic save by
|
||||
// emitting a fsnotify.Remove event for an existing path (see changeEvents).
|
||||
func (s *IntegrationTestBuilder) EditFileAtomicReplaceAll(filename, old, new string) *IntegrationTestBuilder {
|
||||
return s.EditFileAtomicReplaceFunc(filename, func(s string) string {
|
||||
return strings.ReplaceAll(s, old, new)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) EditFileReplaceFunc(filename string, replacementFunc func(s string) string) *IntegrationTestBuilder {
|
||||
absFilename := s.absFilename(filename)
|
||||
b, err := afero.ReadFile(s.fs.Source, absFilename)
|
||||
@@ -758,6 +767,16 @@ func (s *IntegrationTestBuilder) EditFileReplaceFunc(filename string, replacemen
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) EditFileAtomicReplaceFunc(filename string, replacementFunc func(s string) string) *IntegrationTestBuilder {
|
||||
absFilename := s.absFilename(filename)
|
||||
b, err := afero.ReadFile(s.fs.Source, absFilename)
|
||||
s.Assert(err, qt.IsNil)
|
||||
s.atomicSavedFiles = append(s.atomicSavedFiles, absFilename)
|
||||
oldContent := string(b)
|
||||
s.writeSource(absFilename, replacementFunc(oldContent))
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) EditFileAppend(filename, contnt string) *IntegrationTestBuilder {
|
||||
absFilename := s.absFilename(filename)
|
||||
b, err := afero.ReadFile(s.fs.Source, absFilename)
|
||||
@@ -1043,6 +1062,7 @@ func (s *IntegrationTestBuilder) reset() {
|
||||
s.createdFiles = nil
|
||||
s.removedFiles = nil
|
||||
s.renamedFiles = nil
|
||||
s.atomicSavedFiles = nil
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) build(cfg BuildCfg) error {
|
||||
@@ -1091,6 +1111,15 @@ func (s *IntegrationTestBuilder) changeEvents() []fsnotify.Event {
|
||||
})
|
||||
}
|
||||
|
||||
for _, v := range s.atomicSavedFiles {
|
||||
events = append(events, fsnotify.Event{
|
||||
Name: v,
|
||||
// The watcher was watching the inode that got replaced by the rename,
|
||||
// so we get a Remove for a file that's still on disk.
|
||||
Op: fsnotify.Remove,
|
||||
})
|
||||
}
|
||||
|
||||
for _, v := range s.renamedDirs {
|
||||
events = append(events, fsnotify.Event{
|
||||
Name: v,
|
||||
|
||||
@@ -63,7 +63,7 @@ func TestContentMountMerge(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = 'https://example.org/'
|
||||
languageCode = 'en-us'
|
||||
locale = 'en-us'
|
||||
title = 'Hugo Forum Topic #37225'
|
||||
theme = 'mytheme'
|
||||
|
||||
@@ -72,44 +72,49 @@ defaultContentLanguage = 'en'
|
||||
defaultContentLanguageInSubdir = true
|
||||
|
||||
[languages.en]
|
||||
languageName = 'English'
|
||||
label = 'English'
|
||||
weight = 1
|
||||
[languages.de]
|
||||
languageName = 'Deutsch'
|
||||
label = 'Deutsch'
|
||||
weight = 2
|
||||
[languages.nl]
|
||||
languageName = 'Nederlands'
|
||||
label = 'Nederlands'
|
||||
weight = 3
|
||||
|
||||
# EN content
|
||||
[[module.mounts]]
|
||||
source = 'content/en'
|
||||
target = 'content'
|
||||
lang = 'en'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = 'en'
|
||||
|
||||
# DE content
|
||||
[[module.mounts]]
|
||||
source = 'content/de'
|
||||
target = 'content'
|
||||
lang = 'de'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = 'de'
|
||||
|
||||
# This fills in the gaps in DE content with EN content
|
||||
[[module.mounts]]
|
||||
source = 'content/en'
|
||||
target = 'content'
|
||||
lang = 'de'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = 'de'
|
||||
|
||||
# NL content
|
||||
[[module.mounts]]
|
||||
source = 'content/nl'
|
||||
target = 'content'
|
||||
lang = 'nl'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = 'nl'
|
||||
|
||||
# This should fill in the gaps in NL content with EN content
|
||||
[[module.mounts]]
|
||||
source = 'content/en'
|
||||
target = 'content'
|
||||
lang = 'nl'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = 'nl'
|
||||
|
||||
-- content/de/_index.md --
|
||||
---
|
||||
@@ -153,7 +158,8 @@ title: "p3 (nl)"
|
||||
[[module.mounts]]
|
||||
source = 'content/nlt'
|
||||
target = 'content'
|
||||
lang = 'nl'
|
||||
[module.mounts.sites.matrix]
|
||||
languages = 'nl'
|
||||
-- themes/mytheme/content/nlt/p3.md --
|
||||
---
|
||||
title: "p3 theme (nl)"
|
||||
@@ -255,19 +261,22 @@ weight = 2
|
||||
[[module.mounts]]
|
||||
source = "content/en"
|
||||
target = "content"
|
||||
lang = "en"
|
||||
[module.mounts.sites.matrix]
|
||||
languages = "en"
|
||||
|
||||
[[module.mounts]]
|
||||
source = "content/es"
|
||||
target = "content"
|
||||
lang = "es"
|
||||
[module.mounts.sites.matrix]
|
||||
languages = "es"
|
||||
|
||||
# Populate the missing es content with en content
|
||||
|
||||
[[module.mounts]]
|
||||
source = "content/en"
|
||||
target = "content"
|
||||
lang = "es"
|
||||
[module.mounts.sites.matrix]
|
||||
languages = "es"
|
||||
-- layouts/all.html --
|
||||
{{ .Title }}
|
||||
-- content/en/p1.md --
|
||||
|
||||
+14
-12
@@ -19,7 +19,6 @@ import (
|
||||
"iter"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
@@ -174,10 +173,6 @@ func (ps *pageState) Param(key any) (any, error) {
|
||||
return resource.Param(ps, ps.s.Params(), key)
|
||||
}
|
||||
|
||||
func (ps *pageState) Key() string {
|
||||
return "page-" + strconv.FormatUint(ps.pid, 10)
|
||||
}
|
||||
|
||||
// RelatedKeywords implements the related.Document interface needed for fast page searches.
|
||||
func (ps *pageState) RelatedKeywords(cfg related.IndexConfig) ([]related.Keyword, error) {
|
||||
v, found, err := page.NamedPageMetaValue(ps, cfg.Name)
|
||||
@@ -225,10 +220,7 @@ func (ps *pageState) forEeachContentNode(f func(v sitesmatrix.Vector, n contentN
|
||||
}
|
||||
|
||||
func (ps *pageState) contentWeight() int {
|
||||
if ps.m.f == nil {
|
||||
return 0
|
||||
}
|
||||
return ps.m.f.FileInfo().Meta().Weight
|
||||
return ps.m.pageMetaSource.contentWeight()
|
||||
}
|
||||
|
||||
func (ps *pageState) nodeSourceEntryID() any {
|
||||
@@ -628,15 +620,25 @@ func (po *pageOutput) GetInternalTemplateBasePathAndDescriptor() (string, tplimp
|
||||
}
|
||||
|
||||
func (ps *pageState) resolveTemplate(layouts ...string) (*tplimpl.TemplInfo, bool, error) {
|
||||
dir, d := ps.GetInternalTemplateBasePathAndDescriptor()
|
||||
pth, d := ps.GetInternalTemplateBasePathAndDescriptor()
|
||||
var subPath string
|
||||
|
||||
if len(layouts) > 0 {
|
||||
d.LayoutFromUser = layouts[0]
|
||||
layout := layouts[0]
|
||||
if i := strings.LastIndexByte(layout, '/'); i != -1 {
|
||||
// A layout in a sub path, e.g. "foo/mylayout".
|
||||
subPath, layout = layout[:i], layout[i+1:]
|
||||
if layout == "" {
|
||||
return nil, false, nil
|
||||
}
|
||||
}
|
||||
d.LayoutFromUser = layout
|
||||
d.LayoutFromUserMustMatch = true
|
||||
}
|
||||
|
||||
q := tplimpl.TemplateQuery{
|
||||
Path: dir,
|
||||
Path: pth,
|
||||
SubPath: subPath,
|
||||
Category: tplimpl.CategoryLayout,
|
||||
Sites: ps.s.siteVector,
|
||||
Desc: d,
|
||||
|
||||
+7
-11
@@ -21,7 +21,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bep/logg"
|
||||
"github.com/gobuffalo/flect"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
@@ -98,6 +97,13 @@ func (m *pageMetaSource) String() string {
|
||||
return fmt.Sprintf("pageMetaSource(%s)", m.pathInfo)
|
||||
}
|
||||
|
||||
func (m *pageMetaSource) contentWeight() int {
|
||||
if m.f == nil {
|
||||
return 0
|
||||
}
|
||||
return m.f.FileInfo().Meta().Weight
|
||||
}
|
||||
|
||||
func (m *pageMetaSource) nodeCategoryPage() {
|
||||
// Marker method.
|
||||
}
|
||||
@@ -693,16 +699,6 @@ params:
|
||||
continue
|
||||
}
|
||||
|
||||
if loki == "path" || loki == "kind" || loki == "lang" {
|
||||
// See issue 12484.
|
||||
// Only warn when the key was set at the top level of the
|
||||
// original front matter; cascade.params can legitimately carry
|
||||
// these names as user params (issue 14848).
|
||||
if _, ok := pm.pageConfigSource.Frontmatter[loki]; ok {
|
||||
hugo.DeprecateLevelMin(loki+" in front matter", "", "v0.144.0", logg.LevelWarn)
|
||||
}
|
||||
}
|
||||
|
||||
switch loki {
|
||||
case "title":
|
||||
pcfg.Title = cast.ToString(v)
|
||||
|
||||
@@ -140,8 +140,8 @@ func (po *pageOutput) Aliases() []string {
|
||||
|
||||
a = path.Join(baseDir, a)
|
||||
|
||||
if conf.C.IsUglyURLSection(p.Section()) && !strings.HasSuffix(a, ".html") {
|
||||
a += ".html"
|
||||
if conf.C.IsUglyURLSection(p.Section()) && !pathHasOutputFormatSuffix(a, f) {
|
||||
a += f.MediaType.FirstSuffix.FullSuffix
|
||||
}
|
||||
|
||||
aliases[i] = a
|
||||
@@ -149,6 +149,11 @@ func (po *pageOutput) Aliases() []string {
|
||||
return aliases
|
||||
}
|
||||
|
||||
// Key returns a unique key for this page output, used to e.g. hashing.
|
||||
func (po *pageOutput) Key() string {
|
||||
return po.p.Path() + po.f.Name
|
||||
}
|
||||
|
||||
func (po *pageOutput) incrRenderState() {
|
||||
po.renderState++
|
||||
po.renderOnce = true
|
||||
|
||||
+203
-2
@@ -881,12 +881,12 @@ defaultContentLanguage = "en"
|
||||
[languages]
|
||||
[languages.en]
|
||||
title = "Repro"
|
||||
languageName = "English"
|
||||
label = "English"
|
||||
contentDir = "content/en"
|
||||
|
||||
[languages.zh_CN]
|
||||
title = "Repro"
|
||||
languageName = "简体中文"
|
||||
label = "简体中文"
|
||||
contentDir = "content/zh_CN"
|
||||
|
||||
[outputFormats]
|
||||
@@ -2167,3 +2167,204 @@ EF
|
||||
b.AssertFileContent("public/s4/p9/index.html", "EF")
|
||||
b.AssertFileContent("public/s4/p10/index.html", "EF")
|
||||
}
|
||||
|
||||
// See issue 15056.
|
||||
func TestRenderViewSubdir(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
|
||||
-- content/s1/p1.md --
|
||||
---
|
||||
title: p1
|
||||
---
|
||||
-- layouts/s1/p1/page.html --
|
||||
{{ .Render "a" }}|{{ .Render "b" }}|{{ .Render "c" }}|{{ .Render "foo/d" }}|{{ .Render "foo/e" }}|{{ .Render "foo/f" }}|{{ .Render "sub/foo/g" }}
|
||||
-- layouts/s1/p1/a.html --
|
||||
a{{- /**/ -}}
|
||||
-- layouts/s1/b.html --
|
||||
b{{- /**/ -}}
|
||||
-- layouts/c.html --
|
||||
c{{- /**/ -}}
|
||||
-- layouts/s1/p1/foo/d.html --
|
||||
d{{- /**/ -}}
|
||||
-- layouts/s1/foo/e.html --
|
||||
e{{- /**/ -}}
|
||||
-- layouts/foo/f.html --
|
||||
f{{- /**/ -}}
|
||||
-- layouts/sub/foo/g.html --
|
||||
g{{- /**/ -}}
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
b.AssertFileContent("public/s1/p1/index.html", "a|b|c|d|e|f|g")
|
||||
}
|
||||
|
||||
// See issue 15056.
|
||||
func TestRenderViewSubdirRootPage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
|
||||
-- content/p1.md --
|
||||
---
|
||||
title: p1
|
||||
---
|
||||
-- layouts/page.html --
|
||||
{{ .Render "foo/a" }}
|
||||
-- layouts/foo/a.html --
|
||||
a{{- /**/ -}}
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
b.AssertFileContent("public/p1/index.html", "a")
|
||||
}
|
||||
|
||||
// See issue 15056.
|
||||
func TestRenderViewSubdirNotFound(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
|
||||
-- content/s1/p1.md --
|
||||
---
|
||||
title: p1
|
||||
---
|
||||
-- layouts/s1/p1/page.html --
|
||||
{{ .Render "foo/missing" }}
|
||||
-- layouts/s1/p1/missing.html --
|
||||
should-not-match{{- /**/ -}}
|
||||
`
|
||||
|
||||
b, err := TestE(t, files)
|
||||
b.Assert(err, qt.ErrorMatches, `.*template "foo/missing" not found.*`)
|
||||
}
|
||||
|
||||
// See issue 15056.
|
||||
func TestRenderViewSubdirTrailingSlash(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
|
||||
-- content/s1/p1.md --
|
||||
---
|
||||
title: p1
|
||||
---
|
||||
-- layouts/s1/p1/page.html --
|
||||
{{ .Render "foo/" }}
|
||||
-- layouts/s1/p1/foo.html --
|
||||
should-not-match{{- /**/ -}}
|
||||
`
|
||||
|
||||
b, err := TestE(t, files)
|
||||
b.Assert(err, qt.ErrorMatches, `.*template "foo/" not found.*`)
|
||||
}
|
||||
|
||||
// See issue 15056.
|
||||
func TestRenderViewSubdirCaseInsensitiveArgument(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
|
||||
-- content/s1/p1.md --
|
||||
---
|
||||
title: p1
|
||||
---
|
||||
-- layouts/s1/p1/page.html --
|
||||
{{ .Render "Foo/Bar" }}
|
||||
-- layouts/s1/p1/foo/bar.html --
|
||||
bar{{- /**/ -}}
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
b.AssertFileContent("public/s1/p1/index.html", "bar")
|
||||
}
|
||||
|
||||
// See issue 15056.
|
||||
func TestRenderViewSubdirCaseInsensitivePath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
|
||||
-- content/s1/p1.md --
|
||||
---
|
||||
title: p1
|
||||
---
|
||||
-- layouts/s1/p1/page.html --
|
||||
{{ .Render "foo/bar" }}
|
||||
-- layouts/s1/p1/Foo/Bar.html --
|
||||
bar{{- /**/ -}}
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
b.AssertFileContent("public/s1/p1/index.html", "bar")
|
||||
}
|
||||
|
||||
// See issue 15056.
|
||||
func TestRenderViewSubdirWithUnderscore(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
|
||||
-- content/p1.md --
|
||||
---
|
||||
title: p1
|
||||
---
|
||||
-- content/s1/p2.md --
|
||||
---
|
||||
title: p2
|
||||
---
|
||||
-- content/s1/p3.md --
|
||||
---
|
||||
title: p3
|
||||
---
|
||||
-- layouts/page.html --
|
||||
{{ .Title }}: {{ .Render "_views/a" }}
|
||||
-- layouts/_views/a.html --
|
||||
layouts/_views/a.html
|
||||
-- layouts/s1/_views/a.html --
|
||||
layouts/s1/_views/a.html
|
||||
-- layouts/s1/p3/_views/a.html --
|
||||
layouts/s1/p3/_views/a.html
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/p1/index.html", "p1: layouts/_views/a.html")
|
||||
b.AssertFileContent("public/s1/p2/index.html", "p2: layouts/s1/_views/a.html") // fails
|
||||
b.AssertFileContent("public/s1/p3/index.html", "p3: layouts/s1/p3/_views/a.html") // fails
|
||||
}
|
||||
|
||||
func TestPageConttentWeight(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
-- content/mysection/page1.md --
|
||||
-- content/myothersection/page2.md --
|
||||
-- content/myothersection/_index.md --
|
||||
-- layouts/all.html --
|
||||
All.
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
check := func(p page.Page, ok bool) {
|
||||
cw := p.(contentNodeContentWeightProvider).contentWeight()
|
||||
b.Assert(ok, qt.Equals, cw > 0)
|
||||
}
|
||||
|
||||
s := b.H.Sites[0]
|
||||
for _, p := range s.RegularPages() {
|
||||
check(p, true)
|
||||
}
|
||||
check(s.home, false)
|
||||
mysection, _ := s.GetPage("mysection")
|
||||
check(mysection, false)
|
||||
myothersection, _ := s.GetPage("myothersection") // backed by a content file.
|
||||
check(myothersection, true)
|
||||
}
|
||||
|
||||
@@ -608,7 +608,7 @@ func TestGetPageMultilingual(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "http://example.org/"
|
||||
languageCode = "en-us"
|
||||
locale = "en-us"
|
||||
defaultContentLanguage = "ru"
|
||||
title = "My New Hugo Project"
|
||||
uglyurls = true
|
||||
|
||||
@@ -897,6 +897,17 @@ func TestRebuildEditSectionRemoveDate(t *testing.T) {
|
||||
b.AssertFileContent("public/mysection/index.html", "all. My Section|Param: |Lastmod: 2021-02-01|")
|
||||
}
|
||||
|
||||
// Atomic save (write temp file, then rename into place) of a section's branch bundle.
|
||||
// See issue 15130.
|
||||
func TestRebuildEditAtomicSection(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := TestRunning(t, rebuildBasicFiles)
|
||||
b.AssertFileContent("public/mysection/index.html", "My Section")
|
||||
b.EditFileAtomicReplaceAll("content/mysection/_index.md", "My Section", "My Changed Section").Build()
|
||||
b.AssertRenderCountPage(5)
|
||||
b.AssertFileContent("public/mysection/index.html", "My Changed Section")
|
||||
}
|
||||
|
||||
func TestRebuildVariations(t *testing.T) {
|
||||
// t.Parallel() not supported, see https://github.com/fortytw2/leaktest/issues/4
|
||||
// This leaktest seems to be a little bit shaky on Travis.
|
||||
@@ -2170,3 +2181,33 @@ Foo.
|
||||
b.EditFileReplaceAll("layouts/tags/list.html", "Foo", "Bar").Build()
|
||||
b.AssertFileContent("public/tags/index.html", "Bar.")
|
||||
}
|
||||
|
||||
// Atomic save (write temp file, then rename into place) of a content backed taxonomy root.
|
||||
// See issue 15130.
|
||||
func TestRebuildEditAtomicTaxonomyRoot(t *testing.T) {
|
||||
t.Parallel()
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableLiveReload = true
|
||||
[taxonomies]
|
||||
object = "objects"
|
||||
-- content/objects/_index.md --
|
||||
---
|
||||
title: "Objects"
|
||||
---
|
||||
Objects content.
|
||||
-- content/p1.md --
|
||||
---
|
||||
title: "P1"
|
||||
objects: ["o1"]
|
||||
---
|
||||
-- layouts/all.html --
|
||||
{{ .Title }}|{{ .Kind }}|{{ .Content }}
|
||||
`
|
||||
b := TestRunning(t, files)
|
||||
b.AssertFileContent("public/objects/index.html", "Objects|taxonomy|<p>Objects content.</p>")
|
||||
b.EditFileAtomicReplaceAll("content/objects/_index.md", "Objects content.", "Objects content edited.").Build()
|
||||
b.AssertFileContent("public/objects/index.html", "Objects|taxonomy|<p>Objects content edited.</p>")
|
||||
b.AssertFileContent("public/objects/o1/index.html", "O1|term|")
|
||||
}
|
||||
|
||||
+6
-12
@@ -1257,20 +1257,14 @@ func (h *HugoSites) fileEventsApplyInfo(events []fsnotify.Event) []fileEventInfo
|
||||
removed := false
|
||||
added := false
|
||||
|
||||
if ev.Op&fsnotify.Remove == fsnotify.Remove {
|
||||
removed = true
|
||||
}
|
||||
|
||||
fi, statErr := h.Fs.Source.Stat(ev.Name)
|
||||
|
||||
// Some editors (Vim) sometimes issue only a Rename operation when writing an existing file
|
||||
// Sometimes a rename operation means that file has been renamed other times it means
|
||||
// it's been updated.
|
||||
if ev.Op.Has(fsnotify.Rename) {
|
||||
// If the file is still on disk, it's only been updated, if it's not, it's been moved
|
||||
if statErr != nil {
|
||||
removed = true
|
||||
}
|
||||
// Some editors (Vim) sometimes issue only a Rename operation when writing an existing file,
|
||||
// and an atomic save (write temp file, then rename it into place) makes the watcher
|
||||
// report the replaced file as removed (kqueue/macOS).
|
||||
// So, if the file is still on disk, it's only been updated, if it's not, it's gone.
|
||||
if ev.Op.Has(fsnotify.Remove) || ev.Op.Has(fsnotify.Rename) {
|
||||
removed = statErr != nil
|
||||
}
|
||||
if ev.Op.Has(fsnotify.Create) {
|
||||
added = true
|
||||
|
||||
@@ -41,12 +41,12 @@ pagerSize = 1
|
||||
|
||||
[languages]
|
||||
[languages.nn]
|
||||
languageName = "Nynorsk"
|
||||
label = "Nynorsk"
|
||||
weight = 1
|
||||
title = "Hugo på norsk"
|
||||
|
||||
[languages.en]
|
||||
languageName = "English"
|
||||
label = "English"
|
||||
weight = 2
|
||||
title = "Hugo in English"
|
||||
-- layouts/single.html --
|
||||
|
||||
@@ -451,7 +451,8 @@ func TestClassCollector(t *testing.T) {
|
||||
|
||||
files := fmt.Sprintf(`
|
||||
-- hugo.toml --
|
||||
minify = %t
|
||||
[minify]
|
||||
minifyOutput = %t
|
||||
|
||||
[build]
|
||||
writeStats = true
|
||||
|
||||
@@ -59,10 +59,10 @@ defaultContentLanguage = "en"
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
languageName = "English"
|
||||
label = "English"
|
||||
[languages.nn]
|
||||
weight = 2
|
||||
languageName = "Nynorsk"
|
||||
label = "Nynorsk"
|
||||
-- content/sect/doc1.md --
|
||||
---
|
||||
title: doc1
|
||||
@@ -139,7 +139,7 @@ disableKinds = ["term", "taxonomy"]
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
languageName = "English"
|
||||
label = "English"
|
||||
[languages.nn]
|
||||
weight = 2
|
||||
-- layouts/list.xml --
|
||||
|
||||
@@ -1570,11 +1570,11 @@ defaultContentRoleInSubDir: true
|
||||
defaultContentLanguage: en
|
||||
languages:
|
||||
en:
|
||||
languageName: English
|
||||
label: English
|
||||
bn:
|
||||
languageName: Bengali
|
||||
label: Bengali
|
||||
fr:
|
||||
languageName: French
|
||||
label: French
|
||||
-- layouts/all.html --
|
||||
All.
|
||||
|
||||
|
||||
+3
-2
@@ -1,7 +1,8 @@
|
||||
# Release env.
|
||||
# These will be replaced by script before release.
|
||||
HUGORELEASER_TAG=v0.163.3
|
||||
HUGORELEASER_COMMITISH=4d22555aebf458d5d150500c9ac4bee5b24cf0d3
|
||||
HUGORELEASER_TAG=v0.164.0
|
||||
HUGORELEASER_COMMITISH=ce2470e7012b5ab5fc4e10ebe4027e9f8d9e00dc
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -183,6 +183,9 @@ type ExternalOptions struct {
|
||||
// Maps a component import to another.
|
||||
Shims map[string]string
|
||||
|
||||
// User provided import context. If set, we will look here first.
|
||||
ImportContext any
|
||||
|
||||
// Configuring a loader for a given file type lets you load that file type with an
|
||||
// import statement or a require call. For example, configuring the .png file extension
|
||||
// to use the data URL loader means importing a .png file gives you a data URL
|
||||
|
||||
@@ -260,6 +260,19 @@ func createBuildPlugins(rs *resources.Spec, assetsResolver *fsResolver, depsMana
|
||||
}
|
||||
}
|
||||
|
||||
if opts.ImportOnResolveFunc != nil {
|
||||
// Relative imports resolved against the importing file's directory,
|
||||
// e.g. "./foo.css". The path as written was tried above.
|
||||
for _, p := range pathsToTry {
|
||||
if p == impPath {
|
||||
continue
|
||||
}
|
||||
if s := opts.ImportOnResolveFunc(filepath.ToSlash(p), args); s != "" {
|
||||
return api.OnResolveResult{Path: s, Namespace: NsHugoImportResolveFunc}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var m *hugofs.FileMeta
|
||||
for _, p := range pathsToTry {
|
||||
m = assetsResolver.resolveComponent(p, isCSSToken)
|
||||
|
||||
@@ -25,6 +25,13 @@ import (
|
||||
"github.com/gohugoio/hugo/common/hugio"
|
||||
)
|
||||
|
||||
// Keep in sync with genavif/Makefile.
|
||||
const (
|
||||
libavifVersion = "v1.4.1"
|
||||
aomVersion = "v3.14.1"
|
||||
dav1dVersion = "1.5.3"
|
||||
)
|
||||
|
||||
var (
|
||||
_ SourceProvider = AvifInput{}
|
||||
_ DestinationProvider = AvifInput{}
|
||||
|
||||
@@ -280,6 +280,15 @@ static avifPixelFormat avifFormatForHint(const char *hint)
|
||||
return AVIF_PIXEL_FORMAT_YUV420; // photo, picture, and the default.
|
||||
}
|
||||
|
||||
// encoder_errf writes "prefix: result[: diag]", where diag is libavif's
|
||||
// detailed diagnostics (e.g. the underlying aom error, such as an allocation
|
||||
// failure hidden behind the generic "Encoding of color planes failed").
|
||||
static void encoder_errf(char *dst, size_t size, const char *prefix, avifResult result, const avifEncoder *encoder)
|
||||
{
|
||||
snprintf(dst, size, "%s: %s%s%s", prefix, avifResultToString(result),
|
||||
encoder->diag.error[0] ? ": " : "", encoder->diag.error);
|
||||
}
|
||||
|
||||
void handle_commands(FILE *stream)
|
||||
{
|
||||
|
||||
@@ -725,7 +734,7 @@ void handle_commands(FILE *stream)
|
||||
|
||||
result = avifEncoderAddImage(encoder, image, 1, AVIF_ADD_IMAGE_FLAG_SINGLE);
|
||||
if (result != AVIF_RESULT_OK) {
|
||||
snprintf(output.header.err, sizeof(output.header.err), "encodeNRGBA: Failed to add image to encoder: %s", avifResultToString(result));
|
||||
encoder_errf(output.header.err, sizeof(output.header.err), "encodeNRGBA: Failed to add image to encoder", result, encoder);
|
||||
avifEncoderDestroy(encoder);
|
||||
avifImageDestroy(image);
|
||||
write_output_message(&output);
|
||||
@@ -735,7 +744,7 @@ void handle_commands(FILE *stream)
|
||||
avifRWData raw = { NULL, 0 };
|
||||
result = avifEncoderFinish(encoder, &raw);
|
||||
if (result != AVIF_RESULT_OK) {
|
||||
snprintf(output.header.err, sizeof(output.header.err), "encodeNRGBA: Failed to finish encoding: %s", avifResultToString(result));
|
||||
encoder_errf(output.header.err, sizeof(output.header.err), "encodeNRGBA: Failed to finish encoding", result, encoder);
|
||||
avifEncoderDestroy(encoder);
|
||||
avifImageDestroy(image);
|
||||
write_output_message(&output);
|
||||
@@ -850,7 +859,7 @@ void handle_commands(FILE *stream)
|
||||
|
||||
avifResult result = avifEncoderAddImage(encoder, image, 1, AVIF_ADD_IMAGE_FLAG_SINGLE);
|
||||
if (result != AVIF_RESULT_OK) {
|
||||
snprintf(output.header.err, sizeof(output.header.err), "encodeGray: Failed to add image to encoder: %s", avifResultToString(result));
|
||||
encoder_errf(output.header.err, sizeof(output.header.err), "encodeGray: Failed to add image to encoder", result, encoder);
|
||||
avifEncoderDestroy(encoder);
|
||||
avifImageDestroy(image);
|
||||
write_output_message(&output);
|
||||
@@ -860,7 +869,7 @@ void handle_commands(FILE *stream)
|
||||
avifRWData raw = { NULL, 0 };
|
||||
result = avifEncoderFinish(encoder, &raw);
|
||||
if (result != AVIF_RESULT_OK) {
|
||||
snprintf(output.header.err, sizeof(output.header.err), "encodeGray: Failed to finish encoding: %s", avifResultToString(result));
|
||||
encoder_errf(output.header.err, sizeof(output.header.err), "encodeGray: Failed to finish encoding", result, encoder);
|
||||
avifEncoderDestroy(encoder);
|
||||
avifImageDestroy(image);
|
||||
write_output_message(&output);
|
||||
|
||||
@@ -17,6 +17,9 @@ import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
// Keep in sync with js/package.json.
|
||||
const katexVersion = "0.16.21"
|
||||
|
||||
//go:embed wasm/renderkatex.wasm
|
||||
var katexWasm []byte
|
||||
|
||||
|
||||
+17
-3
@@ -48,6 +48,17 @@ const (
|
||||
|
||||
const currentVersion = 1
|
||||
|
||||
// GetWASMDeps returns version information for the C libraries bundled as WASM.
|
||||
func GetWASMDeps() [][2]string {
|
||||
return [][2]string{
|
||||
{"aomedia.googlesource.com/aom", aomVersion},
|
||||
{"code.videolan.org/videolan/dav1d", dav1dVersion},
|
||||
{"github.com/AOMediaCodec/libavif", libavifVersion},
|
||||
{"github.com/KaTeX/KaTeX", katexVersion},
|
||||
{"github.com/webmproject/libwebp", libwebpVersion},
|
||||
}
|
||||
}
|
||||
|
||||
//go:embed wasm/quickjs.wasm
|
||||
var quickjsWasm []byte
|
||||
|
||||
@@ -348,7 +359,8 @@ func (d *dispatcher[Q, R]) inputBlobs() error {
|
||||
call.done()
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
},
|
||||
); err != nil {
|
||||
inputErr = err
|
||||
break
|
||||
}
|
||||
@@ -384,7 +396,8 @@ func (d *dispatcher[Q, R]) inputJSON() error {
|
||||
call.done()
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
},
|
||||
); err != nil {
|
||||
inputErr = err
|
||||
break
|
||||
}
|
||||
@@ -679,7 +692,8 @@ func newDispatcher[Q, R any](opts Options) (*dispatcherPool[Q, R], error) {
|
||||
if err != nil {
|
||||
return toErr("quickjs", errBuff, err)
|
||||
}
|
||||
ctx = experimental.WithImportResolver(ctx,
|
||||
ctx = experimental.WithImportResolver(
|
||||
ctx,
|
||||
func(name string) api.Module {
|
||||
if name == opts.Runtime.Name {
|
||||
return runtimeInstance
|
||||
|
||||
Binary file not shown.
@@ -28,6 +28,9 @@ import (
|
||||
"github.com/gohugoio/hugo/common/hugio"
|
||||
)
|
||||
|
||||
// Keep in sync with genwebp/Makefile.
|
||||
const libwebpVersion = "v1.6.0"
|
||||
|
||||
var (
|
||||
_ SourceProvider = WebpInput{}
|
||||
_ DestinationProvider = WebpInput{}
|
||||
|
||||
@@ -167,14 +167,14 @@ b = 'b translated'
|
||||
b.Assert(err.Error(), qt.Contains, "failed to load translations: reserved keys [description] mixed with unreserved keys [a b]: see the lang.Translate documentation for a list of reserved keys")
|
||||
}
|
||||
|
||||
func TestI18nUseLanguageCodeWhenBothTranslationFilesArePresent(t *testing.T) {
|
||||
func TestI18nUseLocaleWhenBothTranslationFilesArePresent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
filesTemplate := `
|
||||
-- hugo.yaml --
|
||||
languages:
|
||||
en:
|
||||
languageCode: en-us
|
||||
locale: en-us
|
||||
-- i18n/en.yml --
|
||||
hello: Greetings from en!
|
||||
-- i18n/en-us.yml --
|
||||
@@ -189,11 +189,11 @@ hello: Greetings from en-us!
|
||||
}
|
||||
|
||||
runTest(filesTemplate)
|
||||
runTest(strings.ReplaceAll(filesTemplate, "languageCode: en-us", "languageCode: En-US"))
|
||||
runTest(strings.ReplaceAll(filesTemplate, "locale: en-us", "locale: En-US"))
|
||||
runTest(strings.ReplaceAll(filesTemplate, "-- i18n/en-us.yml --", "-- i18n/en-US.yml --"))
|
||||
}
|
||||
|
||||
func TestI18nUseLangWhenLanguageCodeFileIsMissing(t *testing.T) {
|
||||
func TestI18nUseLangWhenLocaleFileIsMissing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
filesTemplate := `
|
||||
@@ -202,7 +202,7 @@ languages:
|
||||
en:
|
||||
title: English
|
||||
pt:
|
||||
languageCode: pt-br
|
||||
locale: pt-br
|
||||
-- i18n/en.yml --
|
||||
hello: Greetings from en!
|
||||
-- i18n/pt.yml --
|
||||
|
||||
@@ -15,6 +15,7 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -27,6 +28,7 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -78,10 +80,22 @@ func TestMain(m *testing.M) {
|
||||
})
|
||||
}
|
||||
|
||||
var isCaseInsensitiveFs = sync.OnceValues(func() (bool, error) {
|
||||
return htesting.IsCaseInsensitiveFs(os.TempDir())
|
||||
})
|
||||
|
||||
var commonTestScriptsParam = testscript.Params{
|
||||
Setup: func(env *testscript.Env) error {
|
||||
return testSetupFunc()(env)
|
||||
},
|
||||
Condition: func(cond string) (bool, error) {
|
||||
switch cond {
|
||||
case "caseinsensitivefs":
|
||||
return isCaseInsensitiveFs()
|
||||
default:
|
||||
return false, fmt.Errorf("unknown condition %q", cond)
|
||||
}
|
||||
},
|
||||
Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){
|
||||
// log prints to stderr.
|
||||
"log": func(ts *testscript.TestScript, neg bool, args []string) {
|
||||
@@ -252,6 +266,34 @@ var commonTestScriptsParam = testscript.Params{
|
||||
ts.Fatalf("failed to create symlink: %v", err)
|
||||
}
|
||||
},
|
||||
// base64decode decodes a base64-encoded file into a binary file.
|
||||
"base64decode": func(ts *testscript.TestScript, neg bool, args []string) {
|
||||
if len(args) != 2 {
|
||||
ts.Fatalf("usage: base64decode src_base64_file dest_binary_file")
|
||||
}
|
||||
|
||||
// Resolve paths relative to the test sandbox's current directory
|
||||
src := ts.MkAbs(args[0])
|
||||
dest := ts.MkAbs(args[1])
|
||||
|
||||
// Read the base64 text
|
||||
base64Data, err := os.ReadFile(src)
|
||||
if err != nil {
|
||||
ts.Fatalf("failed to read base64 file: %v", err)
|
||||
}
|
||||
|
||||
// Decode base64 back to raw bytes.
|
||||
binaryBytes, err := base64.StdEncoding.DecodeString(strings.TrimSpace(string(base64Data)))
|
||||
if err != nil {
|
||||
ts.Fatalf("failed to decode base64 data: %v", err)
|
||||
}
|
||||
|
||||
// Write the binary file.
|
||||
err = os.WriteFile(dest, binaryBytes, 0o644)
|
||||
if err != nil {
|
||||
ts.Fatalf("failed to write binary file: %v", err)
|
||||
}
|
||||
},
|
||||
|
||||
// httpget checks that a HTTP resource's body matches (if it compiles as a regexp) or contains all of the strings given as arguments.
|
||||
"httpget": func(ts *testscript.TestScript, neg bool, args []string) {
|
||||
|
||||
@@ -1153,3 +1153,59 @@ func validatePublishedSite_20(b *hugolib.IntegrationTestBuilder) {
|
||||
b.AssertFileExists(path, true)
|
||||
}
|
||||
}
|
||||
|
||||
// See issue 15121
|
||||
func TestAsciiDocHTML5BackendTOC(t *testing.T) {
|
||||
if !htesting.IsRealCI() {
|
||||
t.Skip()
|
||||
}
|
||||
if ok, err := asciidocext.Supports(); !ok {
|
||||
t.Skip(err)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
resetDefaultAsciiDocExtConfig()
|
||||
})
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
|
||||
|
||||
[markup.asciidocExt]
|
||||
backend = 'html5s'
|
||||
extensions = ['asciidoctor-html5s']
|
||||
|
||||
[security.exec]
|
||||
allow = ['^asciidoctor$']
|
||||
-- content/p1.adoc --
|
||||
---
|
||||
title: p1
|
||||
---
|
||||
|
||||
:toc:
|
||||
|
||||
## Section 1
|
||||
|
||||
### Section 1.1
|
||||
|
||||
### Section 1.2
|
||||
|
||||
## Section 2
|
||||
-- layouts/page.html --
|
||||
{{ .TableOfContents }}
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContentExact("public/p1/index.html", `<nav id="TableOfContents">
|
||||
<ul>
|
||||
<li><a href="#_section_1">Section 1</a>
|
||||
<ul>
|
||||
<li><a href="#_section_1_1">Section 1.1</a></li>
|
||||
<li><a href="#_section_1_2">Section 1.2</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#_section_2">Section 2</a></li>
|
||||
</ul>
|
||||
</nav>`)
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@ func (a *AsciiDocConverter) extractTOC(src []byte) ([]byte, *tableofcontents.Fra
|
||||
toVisit []*html.Node
|
||||
)
|
||||
f = func(n *html.Node) bool {
|
||||
if n.Type == html.ElementNode && n.Data == "div" && attr(n, "id") == "toc" {
|
||||
if n.Type == html.ElementNode && (n.Data == "div" || n.Data == "nav") && attr(n, "id") == "toc" {
|
||||
toc = parseTOC(n)
|
||||
if !a.Cfg.MarkupConfig().AsciiDocExt.PreserveTOC {
|
||||
n.Parent.RemoveChild(n)
|
||||
@@ -364,7 +364,7 @@ func parseTOC(doc *html.Node) *tableofcontents.Fragments {
|
||||
f = func(n *html.Node, row, level int) {
|
||||
if n.Type == html.ElementNode {
|
||||
switch n.Data {
|
||||
case "ul":
|
||||
case "ul", "ol":
|
||||
if level == 0 {
|
||||
row++
|
||||
}
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
// Copyright 2026 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package highlight
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/chroma/v2"
|
||||
"github.com/alecthomas/chroma/v2/formatters/html"
|
||||
"github.com/alecthomas/chroma/v2/styles"
|
||||
)
|
||||
|
||||
// ChromaStylesOptions holds options for ChromaStylesCSS.
|
||||
type ChromaStylesOptions struct {
|
||||
// Highlighter style, e.g. "monokai".
|
||||
Style string
|
||||
|
||||
// Style mode, "light" or "dark".
|
||||
Mode string
|
||||
|
||||
// Scope selectors under a top level mode class, e.g. ".dark .chroma".
|
||||
ModeSelector bool
|
||||
|
||||
// Class name used by ModeSelector for dark styles, defaults to "dark".
|
||||
ClassDark string
|
||||
|
||||
// Class name used by ModeSelector for light styles, defaults to "light".
|
||||
ClassLight string
|
||||
|
||||
// Foreground and background colors for highlighted lines, e.g. "#fff000 bg:#000fff".
|
||||
HighlightStyle string
|
||||
|
||||
// Foreground and background colors for inline line numbers.
|
||||
LineNumbersInlineStyle string
|
||||
|
||||
// Foreground and background colors for table line numbers.
|
||||
LineNumbersTableStyle string
|
||||
|
||||
// Omit CSS class comment prefixes in the generated CSS.
|
||||
OmitClassComments bool
|
||||
}
|
||||
|
||||
// ChromaStylesCSS generates a CSS stylesheet for the Chroma code highlighter.
|
||||
// This stylesheet is needed if markup.highlight.noClasses is disabled in config.
|
||||
func ChromaStylesCSS(opts ChromaStylesOptions) (string, error) {
|
||||
name := strings.ToLower(opts.Style)
|
||||
if !slices.Contains(styles.Names(), name) {
|
||||
return "", fmt.Errorf("invalid style: %s", name)
|
||||
}
|
||||
var chromaStyle *chroma.Style
|
||||
if opts.Mode != "" {
|
||||
var mode chroma.Mode
|
||||
switch opts.Mode {
|
||||
case "light":
|
||||
mode = chroma.Light
|
||||
case "dark":
|
||||
mode = chroma.Dark
|
||||
default:
|
||||
return "", fmt.Errorf("invalid mode: %s", opts.Mode)
|
||||
}
|
||||
|
||||
chromaStyle = styles.GetForMode(name, mode)
|
||||
if chromaStyle.Mode() != mode {
|
||||
return "", fmt.Errorf("style %q does not have a %q mode", name, opts.Mode)
|
||||
}
|
||||
} else {
|
||||
chromaStyle = styles.Get(name)
|
||||
}
|
||||
builder := chromaStyle.Builder()
|
||||
if opts.HighlightStyle != "" {
|
||||
builder.Add(chroma.LineHighlight, opts.HighlightStyle)
|
||||
}
|
||||
if opts.LineNumbersInlineStyle != "" {
|
||||
builder.Add(chroma.LineNumbers, opts.LineNumbersInlineStyle)
|
||||
}
|
||||
if opts.LineNumbersTableStyle != "" {
|
||||
builder.Add(chroma.LineNumbersTable, opts.LineNumbersTableStyle)
|
||||
}
|
||||
style, err := builder.Build()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
options := []html.Option{
|
||||
html.WithCSSComments(!opts.OmitClassComments),
|
||||
html.WithCustomCSS(chromaCSSOverrides(style)),
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := html.New(options...).WriteCSS(&buf, style); err != nil {
|
||||
return "", err
|
||||
}
|
||||
css := buf.String()
|
||||
if opts.ModeSelector {
|
||||
// Scope every selector under a top level mode class, e.g. ".dark .chroma".
|
||||
// This allows generating both light and dark stylesheets and toggling
|
||||
// them with a parent class on the page.
|
||||
// There's no upstream option for this (I think), so do string replacements for now.
|
||||
// TODO(bep) upstream option for this.
|
||||
classDark, classLight := opts.ClassDark, opts.ClassLight
|
||||
if classDark == "" {
|
||||
classDark = "dark"
|
||||
}
|
||||
if classLight == "" {
|
||||
classLight = "light"
|
||||
}
|
||||
var prefix string
|
||||
switch style.Mode() {
|
||||
case chroma.Light:
|
||||
prefix = "." + classLight + " "
|
||||
case chroma.Dark:
|
||||
prefix = "." + classDark + " "
|
||||
default:
|
||||
return "", fmt.Errorf("style %q does not have a light or dark mode", style.Name)
|
||||
}
|
||||
replacer := strings.NewReplacer(
|
||||
".bg {", prefix+".bg {",
|
||||
".chroma ", prefix+".chroma ",
|
||||
)
|
||||
css = replacer.Replace(css)
|
||||
}
|
||||
|
||||
return css, nil
|
||||
}
|
||||
|
||||
// chromaCSSOverrides re-emits leaf token colors that Chroma's minifier drops
|
||||
// because they equal the style's default foreground (the .chroma color).
|
||||
// In a paired light/dark setup, the other sheet's explicit rule (e.g. .chroma .nx)
|
||||
// would otherwise leak into this mode, since an explicit declaration beats
|
||||
// inheritance from .chroma.
|
||||
// TODO(bep): replace with an upstream option, see issue 15161.
|
||||
func chromaCSSOverrides(style *chroma.Style) map[chroma.TokenType]string {
|
||||
bg := style.Get(chroma.Background)
|
||||
m := make(map[chroma.TokenType]string)
|
||||
for tt := range chroma.StandardTypes {
|
||||
if tt == chroma.Background || !style.Has(tt) || !chromaLeafToken(tt) {
|
||||
continue
|
||||
}
|
||||
entry := style.Get(tt)
|
||||
if !entry.Sub(bg).IsZero() || !entry.Colour.IsSet() {
|
||||
continue
|
||||
}
|
||||
if css := html.StyleEntryToCSS(chroma.StyleEntry{Colour: entry.Colour}); css != "" {
|
||||
m[tt] = css
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func chromaLeafToken(tt chroma.TokenType) bool {
|
||||
for other := range chroma.StandardTypes {
|
||||
if other != tt && (other.Category() == tt || other.SubCategory() == tt) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -14,11 +14,14 @@
|
||||
package markup_config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hmaps"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/markup/asciidocext/asciidocext_config"
|
||||
"github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
|
||||
"github.com/gohugoio/hugo/markup/highlight"
|
||||
"github.com/gohugoio/hugo/markup/rst/rst_config"
|
||||
"github.com/gohugoio/hugo/markup/tableofcontents"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
@@ -39,10 +42,19 @@ type Config struct {
|
||||
|
||||
// Configuration for the AsciiDoc external markdown engine.
|
||||
AsciiDocExt asciidocext_config.Config
|
||||
|
||||
// Configuration for the reStructuredText external markdown engine.
|
||||
RST rst_config.Config
|
||||
}
|
||||
|
||||
func (c *Config) Init() error {
|
||||
return c.Goldmark.Init()
|
||||
if err := c.Goldmark.Init(); err != nil {
|
||||
return fmt.Errorf("goldmark: %s", err)
|
||||
}
|
||||
if err := c.RST.Init(); err != nil {
|
||||
return fmt.Errorf("rst: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Decode(cfg config.Provider) (conf Config, err error) {
|
||||
@@ -119,4 +131,5 @@ var Default = Config{
|
||||
|
||||
Goldmark: goldmark_config.Default,
|
||||
AsciiDocExt: asciidocext_config.Default,
|
||||
RST: rst_config.Default,
|
||||
}
|
||||
|
||||
+20
-2
@@ -17,6 +17,7 @@ package rst
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hexec"
|
||||
@@ -71,6 +72,11 @@ func (c *rstConverter) getRstContent(src []byte, ctx converter.DocumentContext)
|
||||
|
||||
logger.Infoln("Rendering", ctx.DocumentName, "with", binaryName, "...")
|
||||
|
||||
args := []string{"--leave-comments", "--initial-header-level=2"}
|
||||
if c.cfg.Conf != nil && c.cfg.MarkupConfig().RST.SyntaxHighlight != "long" {
|
||||
args = append(args, "--syntax-highlight="+c.cfg.MarkupConfig().RST.SyntaxHighlight)
|
||||
}
|
||||
|
||||
var result []byte
|
||||
var err error
|
||||
|
||||
@@ -80,10 +86,9 @@ func (c *rstConverter) getRstContent(src []byte, ctx converter.DocumentContext)
|
||||
// handle Windows manually because it doesn't do shebangs
|
||||
if runtime.GOOS == "windows" {
|
||||
pythonBinary, _ := internal.GetPythonBinaryAndExecPath()
|
||||
args := []string{binaryPath, "--leave-comments", "--initial-header-level=2"}
|
||||
args = append([]string{binaryPath}, args...)
|
||||
result, err = internal.ExternallyRenderContent(c.cfg, ctx, src, pythonBinary, args)
|
||||
} else {
|
||||
args := []string{"--leave-comments", "--initial-header-level=2"}
|
||||
result, err = internal.ExternallyRenderContent(c.cfg, ctx, src, binaryName, args)
|
||||
}
|
||||
|
||||
@@ -128,3 +133,16 @@ func Supports() bool {
|
||||
}
|
||||
return hasBin
|
||||
}
|
||||
|
||||
// SupportsPygments returns whether Pygments is (or should be) installed on this computer.
|
||||
func SupportsPygments() bool {
|
||||
for _, python := range []string{"python3", "python"} {
|
||||
if err := exec.Command(python, "-c", "import pygments").Run(); err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if htesting.SupportsAll() {
|
||||
panic("Pygments not installed")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2026 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package rst_config holds reStructuredText related configuration.
|
||||
package rst_config
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Default holds Hugo's default reStructuredText configuration.
|
||||
var Default = Config{
|
||||
SyntaxHighlight: "long",
|
||||
}
|
||||
|
||||
// Config configures reStructuredText.
|
||||
type Config struct {
|
||||
// Configures Pygments syntax highlighting. Valid values are "short", "long" (default) and "none".
|
||||
SyntaxHighlight string
|
||||
}
|
||||
|
||||
func (c *Config) Init() error {
|
||||
if c.SyntaxHighlight != "short" && c.SyntaxHighlight != "long" && c.SyntaxHighlight != "none" {
|
||||
return fmt.Errorf("invalid value for syntaxHighlight: %q", c.SyntaxHighlight)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright 2026 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rst_test
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/gohugoio/hugo/markup/rst"
|
||||
)
|
||||
|
||||
// --syntax-highlight, default = long.
|
||||
func TestRSTSyntaxHighlight(t *testing.T) {
|
||||
if !rst.Supports() {
|
||||
t.Skip("rst not installed")
|
||||
}
|
||||
if !rst.SupportsPygments() {
|
||||
t.Skip("Pygments not installed")
|
||||
}
|
||||
|
||||
execAllow := `'^rst2html(\.py)?$'`
|
||||
if runtime.GOOS == "windows" {
|
||||
execAllow = `'^python(\.exe)?$'`
|
||||
}
|
||||
|
||||
filesTemplate := strings.ReplaceAll(`
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.org"
|
||||
disableKinds = ["home", "section", "taxonomy", "term", "rss", "sitemap"]
|
||||
|
||||
markup.rst.syntaxHighlight = 'SYNTAX_HIGHLIGHT'
|
||||
|
||||
[security.exec]
|
||||
allow = [RST_EXEC_ALLOW]
|
||||
|
||||
-- layouts/page.html --
|
||||
{{ .Content }}
|
||||
-- content/p.rst --
|
||||
---
|
||||
title: p
|
||||
---
|
||||
|
||||
.. code:: go
|
||||
|
||||
if true {}
|
||||
`, "RST_EXEC_ALLOW", execAllow)
|
||||
|
||||
files := strings.ReplaceAll(filesTemplate, "SYNTAX_HIGHLIGHT", "none")
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/p/index.html", `
|
||||
! Pygments
|
||||
`)
|
||||
|
||||
b.AssertFileContent("public/p/index.html", `
|
||||
if true {}
|
||||
! if</span>
|
||||
`)
|
||||
|
||||
files = strings.ReplaceAll(filesTemplate, "SYNTAX_HIGHLIGHT", "short")
|
||||
b = hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/p/index.html", `
|
||||
<span class="k">if</span>
|
||||
! <span class="keyword">if</span>
|
||||
`)
|
||||
|
||||
files = strings.ReplaceAll(filesTemplate, "SYNTAX_HIGHLIGHT", "long")
|
||||
b = hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/p/index.html", `
|
||||
! <span class="k">if</span>
|
||||
<span class="keyword">if</span>
|
||||
`)
|
||||
|
||||
// Default long.
|
||||
files = strings.ReplaceAll(filesTemplate, "markup.rst.syntaxHighlight = 'SYNTAX_HIGHLIGHT'", "")
|
||||
b = hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/p/index.html", `
|
||||
! <span class="k">if</span>
|
||||
<span class="keyword">if</span>
|
||||
`)
|
||||
|
||||
// Invalid.
|
||||
files = strings.ReplaceAll(filesTemplate, "SYNTAX_HIGHLIGHT", "foo")
|
||||
b, err := hugolib.TestE(t, files)
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
b.Assert(err.Error(), qt.Contains, "rst: invalid value for syntaxHighlight: \"foo\"")
|
||||
}
|
||||
+11
-2
@@ -21,6 +21,7 @@ type BuiltinTypes struct {
|
||||
TextType Type
|
||||
TOMLType Type
|
||||
YAMLType Type
|
||||
SourceMapType Type
|
||||
|
||||
// Common image types
|
||||
PNGType Type
|
||||
@@ -36,6 +37,8 @@ type BuiltinTypes struct {
|
||||
// Common font types
|
||||
TrueTypeFontType Type
|
||||
OpenTypeFontType Type
|
||||
WOFFFontType Type
|
||||
WOFF2FontType Type
|
||||
|
||||
// Common document types
|
||||
PDFType Type
|
||||
@@ -80,6 +83,7 @@ var Builtin = BuiltinTypes{
|
||||
TextType: Type{Type: "text/plain"},
|
||||
TOMLType: Type{Type: "application/toml"},
|
||||
YAMLType: Type{Type: "application/yaml"},
|
||||
SourceMapType: Type{Type: "application/source-map"},
|
||||
|
||||
// Common image types
|
||||
PNGType: Type{Type: "image/png"},
|
||||
@@ -95,6 +99,8 @@ var Builtin = BuiltinTypes{
|
||||
// Common font types
|
||||
TrueTypeFontType: Type{Type: "font/ttf"},
|
||||
OpenTypeFontType: Type{Type: "font/otf"},
|
||||
WOFFFontType: Type{Type: "font/woff"},
|
||||
WOFF2FontType: Type{Type: "font/woff2"},
|
||||
|
||||
// Common document types
|
||||
PDFType: Type{Type: "application/pdf"},
|
||||
@@ -139,6 +145,7 @@ var defaultMediaTypesConfig = map[string]any{
|
||||
"text/plain": map[string]any{"suffixes": []string{"txt"}},
|
||||
"application/toml": map[string]any{"suffixes": []string{"toml"}},
|
||||
"application/yaml": map[string]any{"suffixes": []string{"yaml", "yml"}},
|
||||
"application/source-map": map[string]any{"suffixes": []string{"map"}},
|
||||
|
||||
// Common image types
|
||||
"image/png": map[string]any{"suffixes": []string{"png"}},
|
||||
@@ -152,8 +159,10 @@ var defaultMediaTypesConfig = map[string]any{
|
||||
"image/heic": map[string]any{"suffixes": []string{"heic"}},
|
||||
|
||||
// Common font types
|
||||
"font/ttf": map[string]any{"suffixes": []string{"ttf"}},
|
||||
"font/otf": map[string]any{"suffixes": []string{"otf"}},
|
||||
"font/ttf": map[string]any{"suffixes": []string{"ttf"}},
|
||||
"font/otf": map[string]any{"suffixes": []string{"otf"}},
|
||||
"font/woff": map[string]any{"suffixes": []string{"woff"}},
|
||||
"font/woff2": map[string]any{"suffixes": []string{"woff2"}},
|
||||
|
||||
// Common document types
|
||||
"application/pdf": map[string]any{"suffixes": []string{"pdf"}},
|
||||
|
||||
@@ -142,6 +142,9 @@ func TestDefaultTypes(t *testing.T) {
|
||||
{Builtin.PDFType, "application", "pdf", "pdf", "application/pdf", "application/pdf"},
|
||||
{Builtin.TrueTypeFontType, "font", "ttf", "ttf", "font/ttf", "font/ttf"},
|
||||
{Builtin.OpenTypeFontType, "font", "otf", "otf", "font/otf", "font/otf"},
|
||||
{Builtin.WOFFFontType, "font", "woff", "woff", "font/woff", "font/woff"},
|
||||
{Builtin.WOFF2FontType, "font", "woff2", "woff2", "font/woff2", "font/woff2"},
|
||||
{Builtin.SourceMapType, "application", "source-map", "map", "application/source-map", "application/source-map"},
|
||||
} {
|
||||
c.Assert(test.tp.MainType, qt.Equals, test.expectedMainType)
|
||||
c.Assert(test.tp.SubType, qt.Equals, test.expectedSubType)
|
||||
@@ -151,5 +154,5 @@ func TestDefaultTypes(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
c.Assert(len(DefaultTypes), qt.Equals, 44)
|
||||
c.Assert(len(DefaultTypes), qt.Equals, 47)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2025 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/resources/resource"
|
||||
)
|
||||
|
||||
// Artifact represents an additional output file published as part of a
|
||||
// resource transformation, e.g. a source map or a file emitted by ESBuild's
|
||||
// file loader. It is exposed to templates via Resource.Data.Artifacts.
|
||||
type Artifact interface {
|
||||
resource.MediaTypeProvider
|
||||
resource.ResourceLinksProvider
|
||||
}
|
||||
|
||||
// NewArtifact creates a new Artifact with the given permalinks and media type.
|
||||
func NewArtifact(permalink, relPermalink string, mediaType media.Type) Artifact {
|
||||
return &artifact{permalink: permalink, relPermalink: relPermalink, mediaType: mediaType}
|
||||
}
|
||||
|
||||
type artifact struct {
|
||||
permalink string
|
||||
relPermalink string
|
||||
mediaType media.Type
|
||||
}
|
||||
|
||||
func (a *artifact) MediaType() media.Type {
|
||||
return a.mediaType
|
||||
}
|
||||
|
||||
func (a *artifact) Permalink() string {
|
||||
return a.permalink
|
||||
}
|
||||
|
||||
func (a *artifact) RelPermalink() string {
|
||||
return a.relPermalink
|
||||
}
|
||||
@@ -16,13 +16,12 @@ package images
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"image/color"
|
||||
"math"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hstrings"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type colorGoProvider interface {
|
||||
@@ -65,10 +64,8 @@ func (c Color) String() string {
|
||||
// For hashstructure. This struct is used in template func options
|
||||
// that needs to be able to hash a Color.
|
||||
// For internal use only.
|
||||
func (c Color) Hash() (uint64, error) {
|
||||
h := fnv.New64a()
|
||||
h.Write([]byte(c.hex))
|
||||
return h.Sum64(), nil
|
||||
func (c Color) Key() string {
|
||||
return c.hex
|
||||
}
|
||||
|
||||
func (c *Color) init() error {
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
|
||||
package internal
|
||||
|
||||
import "github.com/gohugoio/hugo/common/hashing"
|
||||
import (
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
)
|
||||
|
||||
// ResourceTransformationKey are provided by the different transformation implementations.
|
||||
// It identifies the transformation (name) and its configuration (elements).
|
||||
|
||||
@@ -39,6 +39,11 @@ func (b *Builder) Build(dir string) *Config {
|
||||
}
|
||||
var roots []string
|
||||
for root := range b.sourceRoots.All() {
|
||||
if root == dir {
|
||||
// We need mappings for other source roots (e.g. mounted modules), but not for the current one.
|
||||
// See issue 15169.
|
||||
continue
|
||||
}
|
||||
rel, err := filepath.Rel(dir, filepath.Join(root, "*"))
|
||||
if err == nil {
|
||||
roots = append(roots, rel)
|
||||
|
||||
@@ -37,4 +37,10 @@ func TestJsConfigBuilder(t *testing.T) {
|
||||
c.Assert(string(data), qt.Not(qt.Contains), "baseUrl")
|
||||
|
||||
c.Assert(NewBuilder().Build("/a/b"), qt.IsNil)
|
||||
|
||||
// Issue 15169.
|
||||
b = NewBuilder()
|
||||
b.AddSourceRoot("/c/assets")
|
||||
conf = b.Build("/c/assets")
|
||||
c.Assert(conf, qt.IsNil)
|
||||
}
|
||||
|
||||
@@ -100,12 +100,6 @@ func (i HugoInfo) Store() *hstore.Scratch {
|
||||
return i.store
|
||||
}
|
||||
|
||||
// Deprecated: Use hugo.IsMultihost instead.
|
||||
func (i HugoInfo) IsMultiHost() bool {
|
||||
hugo.Deprecate("hugo.IsMultiHost", "Use hugo.IsMultihost instead.", "v0.124.0")
|
||||
return i.opts.Conf.IsMultihost()
|
||||
}
|
||||
|
||||
// IsMultihost reports whether each configured language has a unique baseURL.
|
||||
func (i HugoInfo) IsMultihost() bool {
|
||||
return i.opts.Conf.IsMultihost()
|
||||
|
||||
@@ -29,13 +29,13 @@ disableKinds = ['page','rss','section','sitemap','taxonomy','term']
|
||||
defaultContentLanguage = 'de'
|
||||
defaultContentLanguageInSubdir = true
|
||||
[languages.en]
|
||||
languageName = 'English'
|
||||
label = 'English'
|
||||
weight = 1
|
||||
[languages.de]
|
||||
languageName = 'Deutsch'
|
||||
label = 'Deutsch'
|
||||
weight = 2
|
||||
-- layouts/home.html --
|
||||
{{ hugo.Sites.Default.Language.LanguageName }}
|
||||
{{ hugo.Sites.Default.Language.Label }}
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
@@ -60,7 +60,7 @@ func newTestPageWithFile(filename string) *testPage {
|
||||
"en",
|
||||
"UTC",
|
||||
langs.LanguageConfig{
|
||||
LanguageName: "English",
|
||||
Label: "English",
|
||||
},
|
||||
loggers.NewDefault(),
|
||||
)
|
||||
|
||||
@@ -96,6 +96,9 @@ type ResourceSourceDescriptor struct {
|
||||
// Delay publishing until either Permalink or RelPermalink is called. Maybe never.
|
||||
LazyPublish bool
|
||||
|
||||
// Whether to include the hash of the source content in the resource key.
|
||||
IncludeHashInKey bool
|
||||
|
||||
// Set when its known up front, else it's resolved from the target filename.
|
||||
MediaType media.Type
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hashstructure"
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
"github.com/gohugoio/hugo/common/hmaps"
|
||||
"github.com/gohugoio/hugo/common/hreflect"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
@@ -33,44 +35,56 @@ var _ ResourceFinder = (*Resources)(nil)
|
||||
// I.e. both pages and images etc.
|
||||
type Resources []Resource
|
||||
|
||||
type resourceMount struct {
|
||||
R Resources
|
||||
Base string
|
||||
Target string
|
||||
}
|
||||
|
||||
func (r resourceMount) Get(namev any) Resource {
|
||||
name1, err := cast.ToStringE(namev)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
isTargetAbs := strings.HasPrefix(r.Target, "/")
|
||||
|
||||
if r.Target != "" {
|
||||
name1 = strings.TrimPrefix(name1, r.Target)
|
||||
if !isTargetAbs {
|
||||
name1 = paths.TrimLeading(name1)
|
||||
}
|
||||
}
|
||||
|
||||
if r.Base != "" && isTargetAbs {
|
||||
name1 = path.Join(r.Base, name1)
|
||||
}
|
||||
|
||||
for _, res := range r.R {
|
||||
name2 := res.Name()
|
||||
|
||||
if r.Base != "" && !isTargetAbs {
|
||||
name2 = paths.TrimLeading(strings.TrimPrefix(name2, r.Base))
|
||||
}
|
||||
|
||||
if strings.EqualFold(name1, name2) {
|
||||
return res
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mount mounts the given resources from base to the given target path.
|
||||
// Note that leading slashes in target marks an absolute path.
|
||||
// This method is currently only useful in js.Batch.
|
||||
// This method can be used in any of the template funcs that takes an importContext option, e.g. css.Build.
|
||||
func (r Resources) Mount(base, target string) ResourceGetter {
|
||||
return resourceGetterFunc(func(namev any) Resource {
|
||||
name1, err := cast.ToStringE(namev)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
isTargetAbs := strings.HasPrefix(target, "/")
|
||||
|
||||
if target != "" {
|
||||
name1 = strings.TrimPrefix(name1, target)
|
||||
if !isTargetAbs {
|
||||
name1 = paths.TrimLeading(name1)
|
||||
}
|
||||
}
|
||||
|
||||
if base != "" && isTargetAbs {
|
||||
name1 = path.Join(base, name1)
|
||||
}
|
||||
|
||||
for _, res := range r {
|
||||
name2 := res.Name()
|
||||
|
||||
if base != "" && !isTargetAbs {
|
||||
name2 = paths.TrimLeading(strings.TrimPrefix(name2, base))
|
||||
}
|
||||
|
||||
if strings.EqualFold(name1, name2) {
|
||||
return res
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
return resourceMount{
|
||||
R: r,
|
||||
Base: base,
|
||||
Target: target,
|
||||
}
|
||||
}
|
||||
|
||||
type ResourcesProvider interface {
|
||||
@@ -276,12 +290,6 @@ type StaleInfoResourceGetter interface {
|
||||
ResourceGetter
|
||||
}
|
||||
|
||||
type resourceGetterFunc func(name any) Resource
|
||||
|
||||
func (f resourceGetterFunc) Get(name any) Resource {
|
||||
return f(name)
|
||||
}
|
||||
|
||||
// ResourceFinder provides methods to find Resources.
|
||||
// Note that GetRemote (as found in resources.GetRemote) is
|
||||
// not covered by this interface, as this is only available as a global template function.
|
||||
@@ -318,6 +326,8 @@ type ResourceFinder interface {
|
||||
ByType(typ any) Resources
|
||||
}
|
||||
|
||||
var _ hashstructure.Hashable = (*cachedResourceGetter)(nil)
|
||||
|
||||
// NewCachedResourceGetter creates a new ResourceGetter from the given objects.
|
||||
// If multiple objects are provided, they are merged into one where
|
||||
// the first match wins.
|
||||
@@ -329,12 +339,19 @@ func NewCachedResourceGetter(os ...any) *cachedResourceGetter {
|
||||
}
|
||||
}
|
||||
|
||||
hash := hashing.HashUint64(getters)
|
||||
|
||||
return &cachedResourceGetter{
|
||||
cache: hmaps.NewCache[string, Resource](),
|
||||
delegate: getters,
|
||||
hash: hash,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cachedResourceGetter) Hash() (uint64, error) {
|
||||
return c.hash, nil
|
||||
}
|
||||
|
||||
type multiResourceGetter []ResourceGetter
|
||||
|
||||
func (m multiResourceGetter) Get(name any) Resource {
|
||||
@@ -354,6 +371,7 @@ var (
|
||||
type cachedResourceGetter struct {
|
||||
cache *hmaps.Cache[string, Resource]
|
||||
delegate ResourceGetter
|
||||
hash uint64
|
||||
}
|
||||
|
||||
func (c *cachedResourceGetter) Get(name any) Resource {
|
||||
@@ -390,8 +408,6 @@ func unwrapResourceGetter(v any) (ResourceGetter, bool) {
|
||||
return vv, true
|
||||
case ResourcesProvider:
|
||||
return vv.Resources(), true
|
||||
case func(name any) Resource:
|
||||
return resourceGetterFunc(vv), true
|
||||
default:
|
||||
vvv, ok := hreflect.ToSliceAny(v)
|
||||
if !ok {
|
||||
|
||||
@@ -304,8 +304,9 @@ func (c *Client) FromOpts(opts Options) (resource.Resource, error) {
|
||||
}
|
||||
return c.rs.NewResource(
|
||||
resources.ResourceSourceDescriptor{
|
||||
LazyPublish: true,
|
||||
GroupIdentity: identity.Anonymous, // All usage of this resource are tracked via its string content.
|
||||
LazyPublish: true,
|
||||
IncludeHashInKey: !opts.TargetPathHasHash,
|
||||
GroupIdentity: identity.Anonymous, // All usage of this resource are tracked via its string content.
|
||||
OpenReadSeekCloser: func() (hugio.ReadSeekCloser, error) {
|
||||
return newReadSeeker()
|
||||
},
|
||||
|
||||
@@ -201,7 +201,7 @@ func (r *Spec) NewResource(rd ResourceSourceDescriptor) (resource.Resource, erro
|
||||
h: &resourceHash{},
|
||||
publishInit: &hsync.OnceMore{},
|
||||
keyInit: &sync.Once{},
|
||||
includeHashInKey: isImage,
|
||||
includeHashInKey: isImage || rd.IncludeHashInKey,
|
||||
paths: rp,
|
||||
spec: r,
|
||||
sd: rd,
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
package cssjs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
@@ -30,6 +31,8 @@ import (
|
||||
"github.com/gohugoio/hugo/common/text"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/resources"
|
||||
"github.com/gohugoio/hugo/resources/resource"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
@@ -46,10 +49,12 @@ type fileOffset struct {
|
||||
}
|
||||
|
||||
type importResolver struct {
|
||||
ctx context.Context
|
||||
r io.Reader
|
||||
inPath string
|
||||
opts InlineImports
|
||||
|
||||
importContext resource.ResourceGetter
|
||||
contentSeen map[string]bool
|
||||
dependencyManager identity.Manager
|
||||
linemap map[int]fileOffset
|
||||
@@ -57,8 +62,9 @@ type importResolver struct {
|
||||
logger loggers.Logger
|
||||
}
|
||||
|
||||
func newImportResolver(r io.Reader, inPath string, opts InlineImports, fs afero.Fs, logger loggers.Logger, dependencyManager identity.Manager) *importResolver {
|
||||
return &importResolver{
|
||||
func newImportResolver(ctx context.Context, r io.Reader, inPath string, opts InlineImports, fs afero.Fs, logger loggers.Logger, dependencyManager identity.Manager) *importResolver {
|
||||
imp := &importResolver{
|
||||
ctx: ctx,
|
||||
r: r,
|
||||
dependencyManager: dependencyManager,
|
||||
inPath: inPath,
|
||||
@@ -66,6 +72,10 @@ func newImportResolver(r io.Reader, inPath string, opts InlineImports, fs afero.
|
||||
linemap: make(map[int]fileOffset), contentSeen: make(map[string]bool),
|
||||
opts: opts,
|
||||
}
|
||||
if opts.ImportContext != nil {
|
||||
imp.importContext = resource.NewCachedResourceGetter(opts.ImportContext)
|
||||
}
|
||||
return imp
|
||||
}
|
||||
|
||||
func (imp *importResolver) contentHash(filename string) ([]byte, string) {
|
||||
@@ -73,9 +83,13 @@ func (imp *importResolver) contentHash(filename string) ([]byte, string) {
|
||||
if err != nil {
|
||||
return nil, ""
|
||||
}
|
||||
return b, hashBytes(b)
|
||||
}
|
||||
|
||||
func hashBytes(b []byte) string {
|
||||
h := sha256.New()
|
||||
h.Write(b)
|
||||
return b, hex.EncodeToString(h.Sum(nil))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func (imp *importResolver) importRecursive(
|
||||
@@ -105,8 +119,35 @@ func (imp *importResolver) importRecursive(
|
||||
} else {
|
||||
path := strings.Trim(strings.TrimPrefix(line, importIdentifier), " \"';")
|
||||
filename := filepath.Join(basePath, path)
|
||||
imp.dependencyManager.AddIdentity(identity.CleanStringIdentity(filename))
|
||||
importContent, hash := imp.contentHash(filename)
|
||||
var (
|
||||
importContent []byte
|
||||
hash string
|
||||
nestedInPath string
|
||||
)
|
||||
if imp.importContext != nil {
|
||||
// Try first the path as written in the import statement,
|
||||
// then resolved relative to the importing file.
|
||||
for _, name := range []string{path, filepath.ToSlash(filename)} {
|
||||
r := imp.importContext.Get(name)
|
||||
if r == nil {
|
||||
continue
|
||||
}
|
||||
imp.dependencyManager.AddIdentity(identity.FirstIdentity(r))
|
||||
s, err := resources.InternalResourceSourceContent(imp.ctx, r)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
importContent, hash = []byte(s), hashBytes([]byte(s))
|
||||
nestedInPath = name
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if importContent == nil {
|
||||
imp.dependencyManager.AddIdentity(identity.CleanStringIdentity(filename))
|
||||
importContent, hash = imp.contentHash(filename)
|
||||
nestedInPath = filepath.ToSlash(filename)
|
||||
}
|
||||
|
||||
if importContent == nil {
|
||||
if imp.opts.SkipInlineImportsNotFound {
|
||||
@@ -135,7 +176,7 @@ func (imp *importResolver) importRecursive(
|
||||
imp.contentSeen[hash] = true
|
||||
|
||||
// Handle recursive imports.
|
||||
l, nested, err := imp.importRecursive(i+lineNum, string(importContent), filepath.ToSlash(filename))
|
||||
l, nested, err := imp.importRecursive(i+lineNum, string(importContent), nestedInPath)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
package cssjs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -21,10 +22,12 @@ import (
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/htesting/hqt"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/resources/resource"
|
||||
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
@@ -103,6 +106,7 @@ LOCAL_STYLE
|
||||
@import "e.css";`)
|
||||
|
||||
imp := newImportResolver(
|
||||
t.Context(),
|
||||
mainStyles,
|
||||
"styles.css",
|
||||
InlineImports{},
|
||||
@@ -130,6 +134,70 @@ E_STYLE`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestImportResolverImportContext(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
writeFile := func(name, content string) {
|
||||
c.Assert(afero.WriteFile(fs, name, []byte(content), 0o777), qt.IsNil)
|
||||
}
|
||||
|
||||
// Loses to the import context entry with the same name.
|
||||
writeFile("css/a.css", "A_STYLE_FS")
|
||||
writeFile("css/b.css", "B_STYLE_FS")
|
||||
|
||||
importContext := testImportContext{
|
||||
"a.css": "@import \"c.css\";\nA_STYLE",
|
||||
"c.css": "C_STYLE",
|
||||
"css/d.css": "D_STYLE",
|
||||
}
|
||||
|
||||
mainStyles := strings.NewReader(`@import "a.css";
|
||||
@import "b.css";
|
||||
@import "./d.css";
|
||||
LOCAL_STYLE`)
|
||||
|
||||
imp := newImportResolver(
|
||||
t.Context(),
|
||||
mainStyles,
|
||||
"css/styles.css",
|
||||
InlineImports{ImportContext: importContext},
|
||||
fs, loggers.NewDefault(),
|
||||
identity.NopManager,
|
||||
)
|
||||
|
||||
r, err := imp.resolve()
|
||||
c.Assert(err, qt.IsNil)
|
||||
rs := helpers.ReaderToString(r)
|
||||
result := regexp.MustCompile(`\n+`).ReplaceAllString(rs, "\n")
|
||||
|
||||
c.Assert(result, hqt.IsSameString, `C_STYLE
|
||||
A_STYLE
|
||||
B_STYLE_FS
|
||||
D_STYLE
|
||||
LOCAL_STYLE`)
|
||||
}
|
||||
|
||||
type testImportContext map[string]string
|
||||
|
||||
func (g testImportContext) Get(name any) resource.Resource {
|
||||
s := cast.ToString(name)
|
||||
if content, found := g[s]; found {
|
||||
return testImportContextResource{name: s, content: content}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type testImportContextResource struct {
|
||||
resource.Resource
|
||||
name string
|
||||
content string
|
||||
}
|
||||
|
||||
func (r testImportContextResource) Name() string { return r.name }
|
||||
|
||||
func (r testImportContextResource) Content(context.Context) (any, error) { return r.content, nil }
|
||||
|
||||
func BenchmarkImportResolver(b *testing.B) {
|
||||
c := qt.New(b)
|
||||
fs := afero.NewMemMapFs()
|
||||
@@ -161,6 +229,7 @@ LOCAL_STYLE
|
||||
for b.Loop() {
|
||||
b.StopTimer()
|
||||
imp := newImportResolver(
|
||||
b.Context(),
|
||||
strings.NewReader(mainStyles),
|
||||
"styles.css",
|
||||
InlineImports{},
|
||||
|
||||
@@ -87,6 +87,10 @@ type InlineImports struct {
|
||||
// Note that the inline importer does not process url location or imports with media queries,
|
||||
// so those will be left as-is even without enabling this option.
|
||||
SkipInlineImportsNotFound bool
|
||||
|
||||
// User provided import context. If set, imports are looked up here first,
|
||||
// by the path as written in the @import statement, then in the assets filesystem.
|
||||
ImportContext any
|
||||
}
|
||||
|
||||
// Some of the options from https://github.com/postcss/postcss-cli
|
||||
@@ -213,6 +217,7 @@ func (t *postcssTransformation) Transform(ctx *resources.ResourceTransformationC
|
||||
src := ctx.From
|
||||
|
||||
imp := newImportResolver(
|
||||
ctx.Ctx,
|
||||
ctx.From,
|
||||
ctx.InPath,
|
||||
options.InlineImports,
|
||||
|
||||
@@ -124,6 +124,7 @@ func (t *tailwindcssTransformation) Transform(ctx *resources.ResourceTransformat
|
||||
src := ctx.From
|
||||
|
||||
imp := newImportResolver(
|
||||
ctx.Ctx,
|
||||
ctx.From,
|
||||
ctx.InPath,
|
||||
options.InlineImports,
|
||||
|
||||
@@ -28,6 +28,7 @@ func TestTailwindV4Basic(t *testing.T) {
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
security.exec.allow = ['^go$', '^git$', '^node$', '^tailwindcss$']
|
||||
-- package.json --
|
||||
{
|
||||
"license": "MIT",
|
||||
@@ -64,6 +65,46 @@ CSS: {{ $css.Content | safeCSS }}|
|
||||
b.AssertFileContent("public/index.html", "/*! tailwindcss v4.")
|
||||
}
|
||||
|
||||
// See issue 15103.
|
||||
func TestTailwindCSSImportContext(t *testing.T) {
|
||||
t.Parallel()
|
||||
htesting.SkipSlowTestUnlessCI(t)
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
|
||||
security.exec.allow = ['^go$', '^git$', '^node$', '^tailwindcss$']
|
||||
-- assets/css/main.css --
|
||||
@import "tailwindcss";
|
||||
|
||||
@import "foo.css";
|
||||
@import "bar.css";
|
||||
-- assets/css/foo.css --
|
||||
.foo {color: orange;}
|
||||
-- layouts/home.html --
|
||||
{{ $foo := resources.FromString "foo.css" ".foo {color: blue;}" }}
|
||||
{{ $bar := resources.FromString "bar.css" ".bar {color: green;}" }}
|
||||
{{ $opts := dict "importContext" (slice $foo $bar) }}
|
||||
{{ $css := resources.Get "css/main.css" | css.TailwindCSS $opts }}
|
||||
CSS: {{ $css.Content | safeCSS }}|
|
||||
-- package.json --
|
||||
{
|
||||
"devDependencies": {
|
||||
"@tailwindcss/cli": "^4.1.7",
|
||||
"tailwindcss": "^4.1.7"
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files, hugolib.TestOptOsFs(), hugolib.TestOptWithNpmInstall(), hugolib.TestOptInfo())
|
||||
|
||||
// foo.css resolves in the import context before the assets filesystem.
|
||||
b.AssertFileContent("public/index.html",
|
||||
".foo {\n color: blue;\n }",
|
||||
".bar {\n color: green;\n }",
|
||||
)
|
||||
}
|
||||
|
||||
func TestTailwindCSSNoInlineImportsIssue13719(t *testing.T) {
|
||||
t.Parallel()
|
||||
htesting.SkipSlowTestUnlessCI(t)
|
||||
@@ -73,6 +114,8 @@ func TestTailwindCSSNoInlineImportsIssue13719(t *testing.T) {
|
||||
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
|
||||
theme = 'my-theme'
|
||||
|
||||
security.exec.allow = ['^go$', '^git$', '^node$', '^tailwindcss$']
|
||||
|
||||
[[module.mounts]]
|
||||
source = 'assets'
|
||||
target = 'assets'
|
||||
|
||||
@@ -18,12 +18,15 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/evanw/esbuild/pkg/api"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugolib/filesystems"
|
||||
"github.com/gohugoio/hugo/internal/js/esbuild"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
|
||||
"github.com/gohugoio/hugo/resources"
|
||||
"github.com/gohugoio/hugo/resources/resource"
|
||||
@@ -42,6 +45,8 @@ func New(fs *filesystems.SourceFilesystem, rs *resources.Spec, cssMode bool) *Cl
|
||||
}
|
||||
|
||||
// Process processes a resource with the user provided options.
|
||||
// If importContext is not nil, imports are resolved in it first,
|
||||
// then in the assets filesystem (the same precedence as in js.Batch).
|
||||
func (c *Client) Process(res resources.ResourceTransformer, opts map[string]any) (resource.Resource, error) {
|
||||
return res.Transform(
|
||||
&buildTransformation{c: c, optsm: opts},
|
||||
@@ -85,9 +90,27 @@ func (c *Client) transform(opts esbuild.Options, transformCtx *resources.Resourc
|
||||
hasLinkedSourceMap := opts.ExternalOptions.SourceMap == "linked"
|
||||
hasSourceMap := hasLinkedSourceMap || opts.ExternalOptions.SourceMap == "external"
|
||||
|
||||
spec := c.c.Spec()
|
||||
mediaTypes := spec.MediaTypes()
|
||||
baseURL := spec.Cfg.BaseURL().WithoutPath
|
||||
newArtifact := func(relPermalink, fileName string) resources.Artifact {
|
||||
mt, _, found := mediaTypes.GetFirstBySuffix(strings.TrimPrefix(path.Ext(fileName), "."))
|
||||
if !found {
|
||||
mt = media.Builtin.OctetType
|
||||
}
|
||||
return resources.NewArtifact(baseURL+relPermalink, relPermalink, mt)
|
||||
}
|
||||
fileLoaderRelPermalink := func(base string) string {
|
||||
if opts.PublicPath != "" {
|
||||
return strings.TrimSuffix(opts.PublicPath, "/") + "/" + paths.PathEscape(base)
|
||||
}
|
||||
return pathSpec.GetBasePath(false) + paths.PathEscape(paths.AddLeadingSlash(path.Join(outDir, base)))
|
||||
}
|
||||
|
||||
// Classify output files by path rather than relying on array ordering,
|
||||
// which esbuild does not guarantee.
|
||||
var mainOutput []byte
|
||||
var artifacts []resources.Artifact
|
||||
for _, file := range result.OutputFiles {
|
||||
basePath := path.Base(filepath.ToSlash(file.Path))
|
||||
if strings.HasSuffix(basePath, ".map") {
|
||||
@@ -95,6 +118,7 @@ func (c *Client) transform(opts esbuild.Options, transformCtx *resources.Resourc
|
||||
if err = transformCtx.PublishSourceMap(file.Contents); err != nil {
|
||||
return result, err
|
||||
}
|
||||
artifacts = append(artifacts, newArtifact(pathSpec.GetBasePath(false)+paths.PathEscape(paths.AddLeadingSlash(transformCtx.OutPath+".map")), basePath))
|
||||
}
|
||||
} else if isStdinEntryOutput(basePath) {
|
||||
mainOutput = file.Contents
|
||||
@@ -103,9 +127,13 @@ func (c *Client) transform(opts esbuild.Options, transformCtx *resources.Resourc
|
||||
if err = publishFileLoaderArtifact(pathSpec, opts.PublicPath, outDir, basePath, file.Contents, transformCtx); err != nil {
|
||||
return result, err
|
||||
}
|
||||
artifacts = append(artifacts, newArtifact(fileLoaderRelPermalink(basePath), basePath))
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(artifacts, func(i, j int) bool { return artifacts[i].RelPermalink() < artifacts[j].RelPermalink() })
|
||||
transformCtx.Data["Artifacts"] = artifacts
|
||||
|
||||
if mainOutput == nil {
|
||||
return result, fmt.Errorf("esbuild: entry point output not found")
|
||||
}
|
||||
|
||||
@@ -78,6 +78,42 @@ JS Content:{{ $js.Content }}:End:
|
||||
})
|
||||
}
|
||||
|
||||
// Issue #15173.
|
||||
func TestBuildDataArtifacts(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.org/"
|
||||
disableKinds=["page", "section", "taxonomy", "term", "sitemap", "robotsTXT"]
|
||||
-- assets/js/main.js --
|
||||
import { hello } from './util';
|
||||
hello();
|
||||
-- assets/js/util.js --
|
||||
export function hello() {
|
||||
return 'abcd';
|
||||
}
|
||||
-- layouts/home.html --
|
||||
{{ with resources.Get "js/main.js" | js.Build (dict "minify" true "sourcemap" "external") }}
|
||||
COUNT: {{ len .Data.Artifacts }}
|
||||
{{ range .Data.Artifacts }}
|
||||
ARTIFACT: {{ .RelPermalink }}|{{ .Permalink }}|{{ .MediaType.Type }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ with resources.Get "js/main.js" | js.Build }}
|
||||
COUNT2: {{ len .Data.Artifacts }}
|
||||
{{ end }}
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files, hugolib.TestOptOsFs())
|
||||
b.AssertFileContent("public/index.html",
|
||||
"COUNT: 1",
|
||||
"ARTIFACT: /js/main.js.map|https://example.org/js/main.js.map|application/source-map",
|
||||
"COUNT2: 0",
|
||||
)
|
||||
b.AssertFileExists("public/js/main.js.map", true)
|
||||
}
|
||||
|
||||
func TestBuildWithModAndNpm(t *testing.T) {
|
||||
if !htesting.IsCI() {
|
||||
t.Skip("skip (relative) long running modules test when running locally")
|
||||
|
||||
@@ -18,10 +18,14 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/evanw/esbuild/pkg/api"
|
||||
"github.com/gohugoio/hugo/common/hmaps"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/internal/js/esbuild"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/resources"
|
||||
"github.com/gohugoio/hugo/resources/internal"
|
||||
"github.com/gohugoio/hugo/resources/resource"
|
||||
)
|
||||
|
||||
type buildTransformation struct {
|
||||
@@ -69,6 +73,29 @@ func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx
|
||||
opts.MediaType = ctx.InMediaType
|
||||
opts.Stdin = true
|
||||
opts.IsCSS = t.c.c.CssMode
|
||||
var ic resource.ResourceGetter
|
||||
if opts.ImportContext != nil {
|
||||
ic = resource.NewCachedResourceGetter(opts.ImportContext)
|
||||
}
|
||||
|
||||
if ic != nil {
|
||||
resolved := hmaps.NewCache[string, resource.Resource]()
|
||||
opts.ImportOnResolveFunc = func(imp string, args api.OnResolveArgs) string {
|
||||
if r := esbuild.ResolveResource(imp, ic); r != nil {
|
||||
p := esbuild.PrefixHugoVirtual + resources.InternalResourceTargetPath(r)
|
||||
resolved.Set(p, r)
|
||||
ctx.DependencyManager.AddIdentity(identity.FirstIdentity(r))
|
||||
return p
|
||||
}
|
||||
return ""
|
||||
}
|
||||
opts.ImportOnLoadFunc = func(args api.OnLoadArgs) (string, error) {
|
||||
if r, found := resolved.Get(args.Path); found {
|
||||
return resources.InternalResourceSourceContent(ctx.Ctx, r)
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
_, err = t.c.transform(opts, ctx)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user