mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-25 15:58:53 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c8232e3bd |
@@ -4,7 +4,7 @@ parameters:
|
||||
defaults: &defaults
|
||||
resource_class: large
|
||||
docker:
|
||||
- image: bepsays/ci-hugoreleaser:1.22300.20000
|
||||
- image: bepsays/ci-hugoreleaser:1.22200.20200
|
||||
environment: &buildenv
|
||||
GOMODCACHE: /root/project/gomodcache
|
||||
version: 2
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
environment:
|
||||
<<: [*buildenv]
|
||||
docker:
|
||||
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22300.20000
|
||||
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22200.20200
|
||||
steps:
|
||||
- *restore-cache
|
||||
- &attach-workspace
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
name: Build Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
packages: write
|
||||
|
||||
env:
|
||||
REGISTRY_IMAGE: ghcr.io/gohugoio/hugo
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
|
||||
with:
|
||||
images: ${{ env.REGISTRY_IMAGE }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1
|
||||
|
||||
- name: Login to GHCR
|
||||
# Login is only needed when the image is pushed
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@16ebe778df0e7752d2cfcbd924afdbbd89c1a755 # v6.6.1
|
||||
with:
|
||||
context: .
|
||||
push: ${{ startsWith(github.ref, 'refs/tags') }}
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: /tmp/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
|
||||
with:
|
||||
images: ${{ env.REGISTRY_IMAGE }}
|
||||
|
||||
flavor: |
|
||||
latest=false
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create manifest list and push
|
||||
if: ${{ startsWith(github.ref, 'refs/tags') }}
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
|
||||
|
||||
- name: Inspect image
|
||||
if: ${{ startsWith(github.ref, 'refs/tags') }}
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@7de207be1d3ce97a9abe6ff1306222982d1ca9f9 # v5.0.1
|
||||
- uses: dessant/lock-threads@08e671be8ac8944d0e132aa71d0ae8ccfb347675
|
||||
with:
|
||||
issue-inactive-days: 21
|
||||
add-issue-labels: 'Outdated'
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
This pull request has been automatically locked since there
|
||||
has not been any recent activity after it was closed.
|
||||
Please open a new issue for related bugs.
|
||||
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
|
||||
- uses: actions/stale@04a1828bc18ada028d85a0252a47cd2963a91abe
|
||||
with:
|
||||
operations-per-run: 999
|
||||
days-before-issue-stale: 365
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
name: TestDartSassV1
|
||||
env:
|
||||
GOPROXY: https://proxy.golang.org
|
||||
GO111MODULE: on
|
||||
DART_SASS_VERSION: 1.62.1
|
||||
DART_SASS_SHA_LINUX: 3574da75a7322a539034648b8ff84ff2cca162eb924d72b663d718cd3936f075
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.22.x]
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
check-latest: true
|
||||
cache: true
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899
|
||||
with:
|
||||
ruby-version: "2.7"
|
||||
bundler-cache: true #
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Install Mage
|
||||
run: go install github.com/magefile/mage@v1.15.0
|
||||
- name: Install asciidoctor
|
||||
uses: reitzig/actions-asciidoctor@03fcc74cd74880b697950c4930c9ec8a67c69ecc
|
||||
- name: Install docutils
|
||||
run: |
|
||||
pip install docutils
|
||||
rst2html --version
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Install pandoc on Linux
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y pandoc
|
||||
- if: matrix.os == 'macos-latest'
|
||||
run: |
|
||||
brew install pandoc
|
||||
- if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
Choco-Install -PackageName pandoc
|
||||
- run: pandoc -v
|
||||
- name: Install dart-sass-embedded Linux
|
||||
run: |
|
||||
echo "Install Dart Sass version ${DART_SASS_VERSION} ..."
|
||||
curl -LJO "https://github.com/sass/dart-sass-embedded/releases/download/${DART_SASS_VERSION}/sass_embedded-${DART_SASS_VERSION}-linux-x64.tar.gz";
|
||||
echo "${DART_SASS_SHA_LINUX} sass_embedded-${DART_SASS_VERSION}-linux-x64.tar.gz" | sha256sum -c;
|
||||
tar -xvf "sass_embedded-${DART_SASS_VERSION}-linux-x64.tar.gz";
|
||||
echo "$GITHUB_WORKSPACE/sass_embedded/" >> $GITHUB_PATH
|
||||
- name: Check
|
||||
run: |
|
||||
dart-sass-embedded --version
|
||||
mage -v check;
|
||||
env:
|
||||
HUGO_BUILD_TAGS: extended
|
||||
@@ -16,13 +16,13 @@ jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.22.x, 1.23.x]
|
||||
go-version: [1.21.x, 1.22.x]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
@@ -34,9 +34,9 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
check-latest: true
|
||||
@@ -45,18 +45,18 @@ jobs:
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # v1.190.0
|
||||
uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899
|
||||
with:
|
||||
ruby-version: "2.7"
|
||||
bundler-cache: true #
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1
|
||||
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Install Mage
|
||||
run: go install github.com/magefile/mage@v1.15.0
|
||||
- name: Install asciidoctor
|
||||
uses: reitzig/actions-asciidoctor@c642db5eedd1d729bb8c92034770d0b2f769eda6 # v2.0.2
|
||||
uses: reitzig/actions-asciidoctor@03fcc74cd74880b697950c4930c9ec8a67c69ecc
|
||||
- name: Install docutils
|
||||
run: |
|
||||
pip install docutils
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
Copyright 2022 The Hugo Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
[documentation repository]: https://github.com/gohugoio/hugoDocs
|
||||
[documentation]: https://gohugo.io/documentation
|
||||
[dragonfly bsd, freebsd, netbsd, and openbsd]: https://gohugo.io/installation/bsd
|
||||
[features]: https://gohugo.io/about/features/
|
||||
[forum]: https://discourse.gohugo.io
|
||||
[friends]: https://github.com/gohugoio/hugo/graphs/contributors
|
||||
[go]: https://go.dev/
|
||||
@@ -20,6 +19,7 @@
|
||||
[static site generator]: https://en.wikipedia.org/wiki/Static_site_generator
|
||||
[support]: https://discourse.gohugo.io
|
||||
[themes]: https://themes.gohugo.io/
|
||||
[twitter]: https://twitter.com/gohugoio
|
||||
[website]: https://gohugo.io
|
||||
[windows]: https://gohugo.io/installation/windows
|
||||
|
||||
@@ -33,7 +33,7 @@ A fast and flexible static site generator built with love by [bep], [spf13], and
|
||||
[](https://github.com/gohugoio/hugo/actions?query=workflow%3ATest)
|
||||
[](https://goreportcard.com/report/github.com/gohugoio/hugo)
|
||||
|
||||
[Website] | [Installation] | [Documentation] | [Support] | [Contributing] | <a rel="me" href="https://fosstodon.org/@gohugoio">Mastodon</a> | <a rel="me" href="https://x.com/gohugoiov2">X</a>
|
||||
[Website] | [Installation] | [Documentation] | [Support] | [Contributing] | <a rel="me" href="https://fosstodon.org/@gohugoio">Mastodon</a>
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -52,23 +52,19 @@ Use Hugo's embedded web server during development to instantly see changes to co
|
||||
|
||||
Hugo's fast asset pipelines include:
|
||||
|
||||
- Image processing – Convert, resize, crop, rotate, adjust colors, apply filters, overlay text and images, and extract EXIF data
|
||||
- JavaScript bundling – Transpile TypeScript and JSX to JavaScript, bundle, tree shake, minify, create source maps, and perform SRI hashing.
|
||||
- Sass processing – Transpile Sass to CSS, bundle, tree shake, minify, create source maps, perform SRI hashing, and integrate with PostCSS
|
||||
- Tailwind CSS processing – Compile Tailwind CSS utility classes into standard CSS, bundle, tree shake, optimize, minify, perform SRI hashing, and integrate with PostCSS
|
||||
- CSS bundling – transpilation (Sass), tree shaking, minification, source maps, SRI hashing, and PostCSS integration
|
||||
- JavaScript bundling – transpilation (TypeScript, JSX), tree shaking, minification, source maps, and SRI hashing
|
||||
- Image processing – convert, resize, crop, rotate, adjust colors, apply filters, overlay text and images, and extract EXIF data
|
||||
|
||||
And with [Hugo Modules], you can share content, assets, data, translations, themes, templates, and configuration with other projects via public or private Git repositories.
|
||||
|
||||
See the [features] section of the documentation for a comprehensive summary of Hugo's capabilities.
|
||||
|
||||
## Sponsors
|
||||
|
||||
<p> </p>
|
||||
<p float="left">
|
||||
<a href="https://www.linode.com/?utm_campaign=hugosponsor&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/linode-logo_standard_light_medium.png" width="200" alt="Linode"></a>
|
||||
|
||||
<a href="https://route4me.com/" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/Route4MeLogoBlueOnWhite.svg" width="200" alt="Route Planning & Route Optimization Software"></a>
|
||||
</p>
|
||||
<a href="https://www.linode.com/?utm_campaign=hugosponsor&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/linode-logo_standard_light_medium.png" width="200" alt="Linode"></a>
|
||||
<a href="https://cloudcannon.com/hugo-cms/?utm_campaign=HugoSponsorship&utm_source=sponsor&utm_content=gohugo" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/cloudcannon-blue.svg" width="220" alt="CloudCannon"></a>
|
||||
<p> </p>
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -102,9 +98,6 @@ Build the extended edition:
|
||||
```text
|
||||
CGO_ENABLED=1 go install -tags extended github.com/gohugoio/hugo@latest
|
||||
```
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#gohugoio/hugo&Timeline)
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -149,39 +142,40 @@ Hugo stands on the shoulders of great open source libraries. Run `hugo env --log
|
||||
|
||||
```text
|
||||
cloud.google.com/go/compute/metadata="v0.2.3"
|
||||
cloud.google.com/go/iam="v1.1.5"
|
||||
cloud.google.com/go/storage="v1.35.1"
|
||||
cloud.google.com/go="v0.110.10"
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore="v1.9.0"
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity="v1.4.0"
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal="v1.5.0"
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob="v1.2.0"
|
||||
cloud.google.com/go/iam="v1.1.3"
|
||||
cloud.google.com/go/storage="v1.31.0"
|
||||
cloud.google.com/go="v0.110.8"
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore="v1.7.0"
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity="v1.3.0"
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal="v1.3.0"
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob="v1.1.0"
|
||||
github.com/Azure/go-autorest/autorest/to="v0.4.0"
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go="v1.2.0"
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go="v1.0.0"
|
||||
github.com/BurntSushi/locker="v0.0.0-20171006230638-a6e239ea1c69"
|
||||
github.com/alecthomas/chroma/v2="v2.14.0"
|
||||
github.com/armon/go-radix="v1.0.1-0.20221118154546-54df44f2176c"
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream="v1.5.4"
|
||||
github.com/aws/aws-sdk-go-v2/config="v1.26.1"
|
||||
github.com/aws/aws-sdk-go-v2/credentials="v1.16.12"
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds="v1.14.10"
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager="v1.15.7"
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources="v1.3.5"
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2="v2.6.5"
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini="v1.7.2"
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a="v1.2.9"
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront="v1.35.4"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding="v1.10.4"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum="v1.2.9"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url="v1.10.9"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared="v1.16.9"
|
||||
github.com/aws/aws-sdk-go-v2/service/s3="v1.47.5"
|
||||
github.com/aws/aws-sdk-go-v2/service/sso="v1.18.5"
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc="v1.21.5"
|
||||
github.com/aws/aws-sdk-go-v2/service/sts="v1.26.5"
|
||||
github.com/aws/aws-sdk-go-v2="v1.26.1"
|
||||
github.com/aws/aws-sdk-go="v1.50.7"
|
||||
github.com/aws/smithy-go="v1.20.2"
|
||||
github.com/PuerkitoBio/purell="v1.1.1"
|
||||
github.com/PuerkitoBio/urlesc="v0.0.0-20170810143723-de5bf2ad4578"
|
||||
github.com/alecthomas/chroma/v2="v2.11.1"
|
||||
github.com/armon/go-radix="v1.0.0"
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream="v1.4.11"
|
||||
github.com/aws/aws-sdk-go-v2/config="v1.18.32"
|
||||
github.com/aws/aws-sdk-go-v2/credentials="v1.13.31"
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds="v1.13.7"
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager="v1.11.76"
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources="v1.1.37"
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2="v2.4.31"
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini="v1.3.38"
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a="v1.1.0"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding="v1.9.12"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum="v1.1.32"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url="v1.9.31"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared="v1.15.0"
|
||||
github.com/aws/aws-sdk-go-v2/service/s3="v1.38.1"
|
||||
github.com/aws/aws-sdk-go-v2/service/sso="v1.13.1"
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc="v1.15.1"
|
||||
github.com/aws/aws-sdk-go-v2/service/sts="v1.21.1"
|
||||
github.com/aws/aws-sdk-go-v2="v1.20.0"
|
||||
github.com/aws/aws-sdk-go="v1.48.2"
|
||||
github.com/aws/smithy-go="v1.14.0"
|
||||
github.com/bep/clocks="v0.5.0"
|
||||
github.com/bep/debounce="v1.2.0"
|
||||
github.com/bep/gitmap="v1.1.2"
|
||||
@@ -190,35 +184,32 @@ github.com/bep/godartsass/v2="v2.0.0"
|
||||
github.com/bep/godartsass="v1.2.0"
|
||||
github.com/bep/golibsass="v1.1.1"
|
||||
github.com/bep/gowebp="v0.3.0"
|
||||
github.com/bep/lazycache="v0.4.0"
|
||||
github.com/bep/logg="v0.4.0"
|
||||
github.com/bep/lazycache="v0.2.0"
|
||||
github.com/bep/logg="v0.3.0"
|
||||
github.com/bep/mclib="v1.20400.20402"
|
||||
github.com/bep/overlayfs="v0.9.2"
|
||||
github.com/bep/simplecobra="v0.4.0"
|
||||
github.com/bep/overlayfs="v0.6.0"
|
||||
github.com/bep/simplecobra="v0.3.2"
|
||||
github.com/bep/tmc="v0.5.1"
|
||||
github.com/clbanning/mxj/v2="v2.7.0"
|
||||
github.com/cli/safeexec="v1.0.1"
|
||||
github.com/cpuguy83/go-md2man/v2="v2.0.3"
|
||||
github.com/cpuguy83/go-md2man/v2="v2.0.2"
|
||||
github.com/disintegration/gift="v1.2.1"
|
||||
github.com/dlclark/regexp2="v1.11.0"
|
||||
github.com/dlclark/regexp2="v1.10.0"
|
||||
github.com/dustin/go-humanize="v1.0.1"
|
||||
github.com/evanw/esbuild="v0.21.4"
|
||||
github.com/evanw/esbuild="v0.19.7"
|
||||
github.com/fatih/color="v1.16.0"
|
||||
github.com/frankban/quicktest="v1.14.6"
|
||||
github.com/fsnotify/fsnotify="v1.7.0"
|
||||
github.com/getkin/kin-openapi="v0.123.0"
|
||||
github.com/getkin/kin-openapi="v0.120.0"
|
||||
github.com/ghodss/yaml="v1.0.0"
|
||||
github.com/go-openapi/jsonpointer="v0.20.2"
|
||||
github.com/go-openapi/swag="v0.22.8"
|
||||
github.com/go-openapi/jsonpointer="v0.19.6"
|
||||
github.com/go-openapi/swag="v0.22.4"
|
||||
github.com/gobuffalo/flect="v1.0.2"
|
||||
github.com/gobwas/glob="v0.2.3"
|
||||
github.com/gohugoio/go-i18n/v2="v2.1.3-0.20230805085216-e63c13218d0e"
|
||||
github.com/gohugoio/httpcache="v0.7.0"
|
||||
github.com/gohugoio/hugo-goldmark-extensions/extras="v0.2.0"
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough="v0.2.0"
|
||||
github.com/gohugoio/locales="v0.14.0"
|
||||
github.com/gohugoio/localescompressed="v1.0.1"
|
||||
github.com/golang-jwt/jwt/v5="v5.1.0"
|
||||
github.com/golang-jwt/jwt/v4="v4.5.0"
|
||||
github.com/golang/groupcache="v0.0.0-20210331224755-41bb18bfe9da"
|
||||
github.com/golang/protobuf="v1.5.3"
|
||||
github.com/google/go-cmp="v0.6.0"
|
||||
@@ -229,7 +220,7 @@ github.com/googleapis/enterprise-certificate-proxy="v0.3.2"
|
||||
github.com/googleapis/gax-go/v2="v2.12.0"
|
||||
github.com/gorilla/websocket="v1.5.1"
|
||||
github.com/hairyhenderson/go-codeowners="v0.4.0"
|
||||
github.com/hashicorp/golang-lru/v2="v2.0.7"
|
||||
github.com/hashicorp/golang-lru/v2="v2.0.1"
|
||||
github.com/invopop/yaml="v0.2.0"
|
||||
github.com/jdkato/prose="v1.2.1"
|
||||
github.com/jmespath/go-jmespath="v0.4.0"
|
||||
@@ -239,62 +230,63 @@ github.com/kr/text="v0.2.0"
|
||||
github.com/kylelemons/godebug="v1.1.0"
|
||||
github.com/kyokomi/emoji/v2="v2.2.12"
|
||||
github.com/mailru/easyjson="v0.7.7"
|
||||
github.com/makeworld-the-better-one/dither/v2="v2.4.0"
|
||||
github.com/marekm4/color-extractor="v1.2.1"
|
||||
github.com/mattn/go-colorable="v0.1.13"
|
||||
github.com/mattn/go-isatty="v0.0.20"
|
||||
github.com/mattn/go-runewidth="v0.0.9"
|
||||
github.com/mitchellh/hashstructure="v1.1.0"
|
||||
github.com/mitchellh/mapstructure="v1.5.1-0.20231216201459-8508981c8b6c"
|
||||
github.com/mitchellh/mapstructure="v1.5.0"
|
||||
github.com/mohae/deepcopy="v0.0.0-20170929034955-c48cc78d4826"
|
||||
github.com/muesli/smartcrop="v0.3.0"
|
||||
github.com/niklasfasching/go-org="v1.7.0"
|
||||
github.com/olekukonko/tablewriter="v0.0.5"
|
||||
github.com/pbnjay/memory="v0.0.0-20210728143218-7b4eea64cf58"
|
||||
github.com/pelletier/go-toml/v2="v2.2.2"
|
||||
github.com/pelletier/go-toml/v2="v2.1.0"
|
||||
github.com/perimeterx/marshmallow="v1.1.5"
|
||||
github.com/pkg/browser="v0.0.0-20210911075715-681adbf594b8"
|
||||
github.com/pkg/errors="v0.9.1"
|
||||
github.com/rogpeppe/go-internal="v1.12.0"
|
||||
github.com/rogpeppe/go-internal="v1.11.0"
|
||||
github.com/russross/blackfriday/v2="v2.1.0"
|
||||
github.com/rwcarlsen/goexif="v0.0.0-20190401172101-9e8deecbddbd"
|
||||
github.com/sass/dart-sass/compiler="1.77.5"
|
||||
github.com/sass/dart-sass/implementation="1.77.5"
|
||||
github.com/sass/dart-sass/protocol="2.7.1"
|
||||
github.com/sanity-io/litter="v1.5.5"
|
||||
github.com/sass/dart-sass/compiler="1.63.2"
|
||||
github.com/sass/dart-sass/implementation="1.63.2"
|
||||
github.com/sass/dart-sass/protocol="2.0.0"
|
||||
github.com/sass/libsass="3.6.5"
|
||||
github.com/spf13/afero="v1.11.0"
|
||||
github.com/spf13/cast="v1.6.0"
|
||||
github.com/spf13/cobra="v1.8.0"
|
||||
github.com/spf13/fsync="v0.10.1"
|
||||
github.com/spf13/afero="v1.10.0"
|
||||
github.com/spf13/cast="v1.5.1"
|
||||
github.com/spf13/cobra="v1.7.0"
|
||||
github.com/spf13/fsync="v0.9.0"
|
||||
github.com/spf13/pflag="v1.0.5"
|
||||
github.com/tdewolff/minify/v2="v2.20.20"
|
||||
github.com/tdewolff/parse/v2="v2.7.13"
|
||||
github.com/tdewolff/minify/v2="v2.20.7"
|
||||
github.com/tdewolff/parse/v2="v2.7.5"
|
||||
github.com/webmproject/libwebp="v1.3.2"
|
||||
github.com/yuin/goldmark-emoji="v1.0.3"
|
||||
github.com/yuin/goldmark="v1.7.4"
|
||||
github.com/yuin/goldmark-emoji="v1.0.2"
|
||||
github.com/yuin/goldmark="v1.6.0"
|
||||
go.opencensus.io="v0.24.0"
|
||||
go.uber.org/atomic="v1.11.0"
|
||||
go.uber.org/automaxprocs="v1.5.3"
|
||||
gocloud.dev="v0.36.0"
|
||||
golang.org/x/crypto="v0.23.0"
|
||||
gocloud.dev="v0.34.0"
|
||||
golang.org/x/crypto="v0.15.0"
|
||||
golang.org/x/exp="v0.0.0-20221031165847-c99f073a8326"
|
||||
golang.org/x/image="v0.16.0"
|
||||
golang.org/x/mod="v0.17.0"
|
||||
golang.org/x/net="v0.25.0"
|
||||
golang.org/x/oauth2="v0.15.0"
|
||||
golang.org/x/sync="v0.7.0"
|
||||
golang.org/x/sys="v0.20.0"
|
||||
golang.org/x/text="v0.15.0"
|
||||
golang.org/x/time="v0.5.0"
|
||||
golang.org/x/tools="v0.20.0"
|
||||
golang.org/x/xerrors="v0.0.0-20231012003039-104605ab7028"
|
||||
google.golang.org/api="v0.152.0"
|
||||
google.golang.org/genproto/googleapis/api="v0.0.0-20231120223509-83a465c0220f"
|
||||
google.golang.org/genproto/googleapis/rpc="v0.0.0-20231120223509-83a465c0220f"
|
||||
google.golang.org/genproto="v0.0.0-20231120223509-83a465c0220f"
|
||||
golang.org/x/image="v0.13.0"
|
||||
golang.org/x/mod="v0.14.0"
|
||||
golang.org/x/net="v0.18.0"
|
||||
golang.org/x/oauth2="v0.13.0"
|
||||
golang.org/x/sync="v0.5.0"
|
||||
golang.org/x/sys="v0.14.0"
|
||||
golang.org/x/text="v0.14.0"
|
||||
golang.org/x/time="v0.3.0"
|
||||
golang.org/x/tools="v0.15.0"
|
||||
golang.org/x/xerrors="v0.0.0-20220907171357-04be3eba64a2"
|
||||
google.golang.org/api="v0.151.0"
|
||||
google.golang.org/genproto/googleapis/api="v0.0.0-20231016165738-49dd2c1f3d0b"
|
||||
google.golang.org/genproto/googleapis/rpc="v0.0.0-20231030173426-d783a09b4405"
|
||||
google.golang.org/genproto="v0.0.0-20231016165738-49dd2c1f3d0b"
|
||||
google.golang.org/grpc="v1.59.0"
|
||||
google.golang.org/protobuf="v1.33.0"
|
||||
google.golang.org/protobuf="v1.31.0"
|
||||
gopkg.in/yaml.v2="v2.4.0"
|
||||
gopkg.in/yaml.v3="v3.0.1"
|
||||
howett.net/plist="v1.0.0"
|
||||
software.sslmate.com/src/go-pkcs12="v0.2.0"
|
||||
```
|
||||
</details>
|
||||
|
||||
+1
-1
@@ -4,4 +4,4 @@
|
||||
|
||||
Please report (suspected) security vulnerabilities to **[bjorn.erik.pedersen@gmail.com](mailto:bjorn.erik.pedersen@gmail.com)**. You will receive a response from us within 48 hours. If we can confirm the issue, we will release a patch as soon as possible depending on the complexity of the issue but historically within days.
|
||||
|
||||
Also see [Hugo's Security Model](https://gohugo.io/about/security/).
|
||||
Also see [Hugo's Security Model](https://gohugo.io/about/security-model/).
|
||||
|
||||
Vendored
+14
-58
@@ -38,11 +38,6 @@ import (
|
||||
|
||||
const minMaxSize = 10
|
||||
|
||||
type KeyIdentity struct {
|
||||
Key any
|
||||
Identity identity.Identity
|
||||
}
|
||||
|
||||
// New creates a new cache.
|
||||
func New(opts Options) *Cache {
|
||||
if opts.CheckInterval == 0 {
|
||||
@@ -69,14 +64,14 @@ func New(opts Options) *Cache {
|
||||
|
||||
infol := opts.Log.InfoCommand("dynacache")
|
||||
|
||||
evictedIdentities := collections.NewStack[KeyIdentity]()
|
||||
evictedIdentities := collections.NewStack[identity.Identity]()
|
||||
|
||||
onEvict := func(k, v any) {
|
||||
if !opts.Watching {
|
||||
if !opts.Running {
|
||||
return
|
||||
}
|
||||
identity.WalkIdentitiesShallow(v, func(level int, id identity.Identity) bool {
|
||||
evictedIdentities.Push(KeyIdentity{Key: k, Identity: id})
|
||||
evictedIdentities.Push(id)
|
||||
return false
|
||||
})
|
||||
resource.MarkStale(v)
|
||||
@@ -102,7 +97,7 @@ type Options struct {
|
||||
CheckInterval time.Duration
|
||||
MaxSize int
|
||||
MinMaxSize int
|
||||
Watching bool
|
||||
Running bool
|
||||
}
|
||||
|
||||
// Options for a partition.
|
||||
@@ -129,7 +124,7 @@ type Cache struct {
|
||||
partitions map[string]PartitionManager
|
||||
|
||||
onEvict func(k, v any)
|
||||
evictedIdentities *collections.Stack[KeyIdentity]
|
||||
evictedIdentities *collections.Stack[identity.Identity]
|
||||
|
||||
opts Options
|
||||
infol logg.LevelLogger
|
||||
@@ -140,35 +135,21 @@ type Cache struct {
|
||||
}
|
||||
|
||||
// DrainEvictedIdentities drains the evicted identities from the cache.
|
||||
func (c *Cache) DrainEvictedIdentities() []KeyIdentity {
|
||||
func (c *Cache) DrainEvictedIdentities() []identity.Identity {
|
||||
return c.evictedIdentities.Drain()
|
||||
}
|
||||
|
||||
// DrainEvictedIdentitiesMatching drains the evicted identities from the cache that match the given predicate.
|
||||
func (c *Cache) DrainEvictedIdentitiesMatching(predicate func(KeyIdentity) bool) []KeyIdentity {
|
||||
return c.evictedIdentities.DrainMatching(predicate)
|
||||
}
|
||||
|
||||
// ClearMatching clears all partition for which the predicate returns true.
|
||||
func (c *Cache) ClearMatching(predicatePartition func(k string, p PartitionManager) bool, predicateValue func(k, v any) bool) {
|
||||
if predicatePartition == nil {
|
||||
predicatePartition = func(k string, p PartitionManager) bool { return true }
|
||||
}
|
||||
if predicateValue == nil {
|
||||
panic("nil predicateValue")
|
||||
}
|
||||
func (c *Cache) ClearMatching(predicate func(k, v any) bool) {
|
||||
g := rungroup.Run[PartitionManager](context.Background(), rungroup.Config[PartitionManager]{
|
||||
NumWorkers: len(c.partitions),
|
||||
Handle: func(ctx context.Context, partition PartitionManager) error {
|
||||
partition.clearMatching(predicateValue)
|
||||
partition.clearMatching(predicate)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
for k, p := range c.partitions {
|
||||
if !predicatePartition(k, p) {
|
||||
continue
|
||||
}
|
||||
for _, p := range c.partitions {
|
||||
g.Enqueue(p)
|
||||
}
|
||||
|
||||
@@ -375,7 +356,6 @@ func GetOrCreatePartition[K comparable, V any](c *Cache, name string, opts Optio
|
||||
trace: c.opts.Log.Logger().WithLevel(logg.LevelTrace).WithField("partition", name),
|
||||
opts: opts,
|
||||
}
|
||||
|
||||
c.partitions[name] = partition
|
||||
|
||||
return partition
|
||||
@@ -395,37 +375,13 @@ type Partition[K comparable, V any] struct {
|
||||
|
||||
// GetOrCreate gets or creates a value for the given key.
|
||||
func (p *Partition[K, V]) GetOrCreate(key K, create func(key K) (V, error)) (V, error) {
|
||||
v, err := p.doGetOrCreate(key, create)
|
||||
if err != nil {
|
||||
return p.zero, err
|
||||
}
|
||||
if resource.StaleVersion(v) > 0 {
|
||||
p.c.Delete(key)
|
||||
return p.doGetOrCreate(key, create)
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (p *Partition[K, V]) doGetOrCreate(key K, create func(key K) (V, error)) (V, error) {
|
||||
v, _, err := p.c.GetOrCreate(key, create)
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (p *Partition[K, V]) GetOrCreateWitTimeout(key K, duration time.Duration, create func(key K) (V, error)) (V, error) {
|
||||
v, err := p.doGetOrCreateWitTimeout(key, duration, create)
|
||||
if err != nil {
|
||||
return p.zero, err
|
||||
}
|
||||
if resource.StaleVersion(v) > 0 {
|
||||
p.c.Delete(key)
|
||||
return p.doGetOrCreateWitTimeout(key, duration, create)
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
// GetOrCreateWitTimeout gets or creates a value for the given key and times out if the create function
|
||||
// takes too long.
|
||||
func (p *Partition[K, V]) doGetOrCreateWitTimeout(key K, duration time.Duration, create func(key K) (V, error)) (V, error) {
|
||||
func (p *Partition[K, V]) GetOrCreateWitTimeout(key K, duration time.Duration, create func(key K) (V, error)) (V, error) {
|
||||
resultch := make(chan V, 1)
|
||||
errch := make(chan error, 1)
|
||||
|
||||
@@ -482,7 +438,7 @@ func (p *Partition[K, V]) clearOnRebuild(changeset ...identity.Identity) {
|
||||
|
||||
shouldDelete := func(key K, v V) bool {
|
||||
// We always clear elements marked as stale.
|
||||
if resource.StaleVersion(v) > 0 {
|
||||
if resource.IsStaleAny(v) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -537,8 +493,8 @@ func (p *Partition[K, V]) Keys() []K {
|
||||
|
||||
func (p *Partition[K, V]) clearStale() {
|
||||
p.c.DeleteFunc(func(key K, v V) bool {
|
||||
staleVersion := resource.StaleVersion(v)
|
||||
if staleVersion > 0 {
|
||||
isStale := resource.IsStaleAny(v)
|
||||
if isStale {
|
||||
p.trace.Log(
|
||||
logg.StringFunc(
|
||||
func() string {
|
||||
@@ -548,7 +504,7 @@ func (p *Partition[K, V]) clearStale() {
|
||||
)
|
||||
}
|
||||
|
||||
return staleVersion > 0
|
||||
return isStale
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Vendored
+7
-7
@@ -29,12 +29,12 @@ var (
|
||||
)
|
||||
|
||||
type testItem struct {
|
||||
name string
|
||||
staleVersion uint32
|
||||
name string
|
||||
isStale bool
|
||||
}
|
||||
|
||||
func (t testItem) StaleVersion() uint32 {
|
||||
return t.staleVersion
|
||||
func (t testItem) IsStale() bool {
|
||||
return t.isStale
|
||||
}
|
||||
|
||||
func (t testItem) IdentifierBase() string {
|
||||
@@ -109,7 +109,7 @@ func newTestCache(t *testing.T) *Cache {
|
||||
|
||||
p2.GetOrCreate("clearBecauseStale", func(string) (testItem, error) {
|
||||
return testItem{
|
||||
staleVersion: 32,
|
||||
isStale: true,
|
||||
}, nil
|
||||
})
|
||||
|
||||
@@ -121,7 +121,7 @@ func newTestCache(t *testing.T) *Cache {
|
||||
|
||||
p2.GetOrCreate("clearNever", func(string) (testItem, error) {
|
||||
return testItem{
|
||||
staleVersion: 0,
|
||||
isStale: false,
|
||||
}, nil
|
||||
})
|
||||
|
||||
@@ -156,7 +156,7 @@ func TestClear(t *testing.T) {
|
||||
|
||||
cache = newTestCache(t)
|
||||
|
||||
cache.ClearMatching(nil, func(k, v any) bool {
|
||||
cache.ClearMatching(func(k, v any) bool {
|
||||
return k.(string) == "clearOnRebuild"
|
||||
})
|
||||
|
||||
|
||||
Vendored
+13
-109
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/httpcache"
|
||||
"github.com/gohugoio/hugo/common/hugio"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
|
||||
@@ -183,15 +182,6 @@ func (c *Cache) ReadOrCreate(id string,
|
||||
return
|
||||
}
|
||||
|
||||
// NamedLock locks the given id. The lock is released when the returned function is called.
|
||||
func (c *Cache) NamedLock(id string) func() {
|
||||
id = cleanID(id)
|
||||
c.nlocker.Lock(id)
|
||||
return func() {
|
||||
c.nlocker.Unlock(id)
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrCreate tries to get the file with the given id from cache. If not found or expired, create will
|
||||
// be invoked and the result cached.
|
||||
// This method is protected by a named lock using the given id as identifier.
|
||||
@@ -228,23 +218,7 @@ func (c *Cache) GetOrCreate(id string, create func() (io.ReadCloser, error)) (It
|
||||
var buff bytes.Buffer
|
||||
return info,
|
||||
hugio.ToReadCloser(&buff),
|
||||
c.writeReader(id, io.TeeReader(r, &buff))
|
||||
}
|
||||
|
||||
func (c *Cache) writeReader(id string, r io.Reader) error {
|
||||
dir := filepath.Dir(id)
|
||||
if dir != "" {
|
||||
_ = c.Fs.MkdirAll(dir, 0o777)
|
||||
}
|
||||
f, err := c.Fs.Create(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, _ = io.Copy(f, r)
|
||||
|
||||
return nil
|
||||
afero.WriteReader(c.Fs, id, io.TeeReader(r, &buff))
|
||||
}
|
||||
|
||||
// GetOrCreateBytes is the same as GetOrCreate, but produces a byte slice.
|
||||
@@ -279,10 +253,9 @@ func (c *Cache) GetOrCreateBytes(id string, create func() ([]byte, error)) (Item
|
||||
return info, b, nil
|
||||
}
|
||||
|
||||
if err := c.writeReader(id, bytes.NewReader(b)); err != nil {
|
||||
if err := afero.WriteReader(c.Fs, id, bytes.NewReader(b)); err != nil {
|
||||
return info, nil, err
|
||||
}
|
||||
|
||||
return info, b, nil
|
||||
}
|
||||
|
||||
@@ -332,8 +305,16 @@ func (c *Cache) getOrRemove(id string) hugio.ReadSeekCloser {
|
||||
return nil
|
||||
}
|
||||
|
||||
if removed, err := c.removeIfExpired(id); err != nil || removed {
|
||||
return nil
|
||||
if c.maxAge > 0 {
|
||||
fi, err := c.Fs.Stat(id)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.isExpired(fi.ModTime()) {
|
||||
c.Fs.Remove(id)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
f, err := c.Fs.Open(id)
|
||||
@@ -344,49 +325,6 @@ func (c *Cache) getOrRemove(id string) hugio.ReadSeekCloser {
|
||||
return f
|
||||
}
|
||||
|
||||
func (c *Cache) getBytesAndRemoveIfExpired(id string) ([]byte, bool) {
|
||||
if c.maxAge == 0 {
|
||||
// No caching.
|
||||
return nil, false
|
||||
}
|
||||
|
||||
f, err := c.Fs.Open(id)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
b, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
removed, err := c.removeIfExpired(id)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return b, removed
|
||||
}
|
||||
|
||||
func (c *Cache) removeIfExpired(id string) (bool, error) {
|
||||
if c.maxAge <= 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
fi, err := c.Fs.Stat(id)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if c.isExpired(fi.ModTime()) {
|
||||
c.Fs.Remove(id)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (c *Cache) isExpired(modTime time.Time) bool {
|
||||
if c.maxAge < 0 {
|
||||
return false
|
||||
@@ -460,37 +398,3 @@ func NewCaches(p *helpers.PathSpec) (Caches, error) {
|
||||
func cleanID(name string) string {
|
||||
return strings.TrimPrefix(filepath.Clean(name), helpers.FilePathSeparator)
|
||||
}
|
||||
|
||||
// AsHTTPCache returns an httpcache.Cache implementation for this file cache.
|
||||
// Note that none of the methods are protected by named locks, so you need to make sure
|
||||
// to do that in your own code.
|
||||
func (c *Cache) AsHTTPCache() httpcache.Cache {
|
||||
return &httpCache{c: c}
|
||||
}
|
||||
|
||||
type httpCache struct {
|
||||
c *Cache
|
||||
}
|
||||
|
||||
func (h *httpCache) Get(id string) (resp []byte, ok bool) {
|
||||
id = cleanID(id)
|
||||
b, removed := h.c.getBytesAndRemoveIfExpired(id)
|
||||
|
||||
return b, !removed
|
||||
}
|
||||
|
||||
func (h *httpCache) Set(id string, resp []byte) {
|
||||
if h.c.maxAge == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
id = cleanID(id)
|
||||
|
||||
if err := h.c.writeReader(id, bytes.NewReader(resp)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *httpCache) Delete(key string) {
|
||||
h.c.Fs.Remove(key)
|
||||
}
|
||||
|
||||
Vendored
+1
-11
@@ -46,7 +46,6 @@ const (
|
||||
CacheKeyAssets = "assets"
|
||||
CacheKeyModules = "modules"
|
||||
CacheKeyGetResource = "getresource"
|
||||
CacheKeyMisc = "misc"
|
||||
)
|
||||
|
||||
type Configs map[string]FileCacheConfig
|
||||
@@ -71,14 +70,10 @@ var defaultCacheConfigs = Configs{
|
||||
MaxAge: -1,
|
||||
Dir: resourcesGenDir,
|
||||
},
|
||||
CacheKeyGetResource: {
|
||||
CacheKeyGetResource: FileCacheConfig{
|
||||
MaxAge: -1, // Never expire
|
||||
Dir: cacheDirProject,
|
||||
},
|
||||
CacheKeyMisc: {
|
||||
MaxAge: -1,
|
||||
Dir: cacheDirProject,
|
||||
},
|
||||
}
|
||||
|
||||
type FileCacheConfig struct {
|
||||
@@ -125,11 +120,6 @@ func (f Caches) AssetsCache() *Cache {
|
||||
return f[CacheKeyAssets]
|
||||
}
|
||||
|
||||
// MiscCache gets the file cache for miscellaneous stuff.
|
||||
func (f Caches) MiscCache() *Cache {
|
||||
return f[CacheKeyMisc]
|
||||
}
|
||||
|
||||
// GetResourceCache gets the file cache for remote resources.
|
||||
func (f Caches) GetResourceCache() *Cache {
|
||||
return f[CacheKeyGetResource]
|
||||
|
||||
+3
-3
@@ -59,7 +59,7 @@ dir = "/path/to/c4"
|
||||
c.Assert(err, qt.IsNil)
|
||||
fs := afero.NewMemMapFs()
|
||||
decoded := testconfig.GetTestConfigs(fs, cfg).Base.Caches
|
||||
c.Assert(len(decoded), qt.Equals, 7)
|
||||
c.Assert(len(decoded), qt.Equals, 6)
|
||||
|
||||
c2 := decoded["getcsv"]
|
||||
c.Assert(c2.MaxAge.String(), qt.Equals, "11h0m0s")
|
||||
@@ -106,7 +106,7 @@ dir = "/path/to/c4"
|
||||
c.Assert(err, qt.IsNil)
|
||||
fs := afero.NewMemMapFs()
|
||||
decoded := testconfig.GetTestConfigs(fs, cfg).Base.Caches
|
||||
c.Assert(len(decoded), qt.Equals, 7)
|
||||
c.Assert(len(decoded), qt.Equals, 6)
|
||||
|
||||
for _, v := range decoded {
|
||||
c.Assert(v.MaxAge, qt.Equals, time.Duration(0))
|
||||
@@ -129,7 +129,7 @@ func TestDecodeConfigDefault(t *testing.T) {
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
decoded := testconfig.GetTestConfigs(fs, cfg).Base.Caches
|
||||
c.Assert(len(decoded), qt.Equals, 7)
|
||||
c.Assert(len(decoded), qt.Equals, 6)
|
||||
|
||||
imgConfig := decoded[filecache.CacheKeyImages]
|
||||
jsonConfig := decoded[filecache.CacheKeyGetJSON]
|
||||
|
||||
Vendored
-207
@@ -1,207 +0,0 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package httpcache
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/gohugoio/hugo/common/predicate"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// DefaultConfig holds the default configuration for the HTTP cache.
|
||||
var DefaultConfig = Config{
|
||||
Cache: Cache{
|
||||
For: GlobMatcher{
|
||||
Excludes: []string{"**"},
|
||||
},
|
||||
},
|
||||
Polls: []PollConfig{
|
||||
{
|
||||
For: GlobMatcher{
|
||||
Includes: []string{"**"},
|
||||
},
|
||||
Disable: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Config holds the configuration for the HTTP cache.
|
||||
type Config struct {
|
||||
// Configures the HTTP cache behaviour (RFC 9111).
|
||||
// When this is not enabled for a resource, Hugo will go straight to the file cache.
|
||||
Cache Cache
|
||||
|
||||
// Polls holds a list of configurations for polling remote resources to detect changes in watch mode.
|
||||
// This can be disabled for some resources, typically if they are known to not change.
|
||||
Polls []PollConfig
|
||||
}
|
||||
|
||||
type Cache struct {
|
||||
// Enable HTTP cache behaviour (RFC 9111) for these rsources.
|
||||
For GlobMatcher
|
||||
}
|
||||
|
||||
func (c *Config) Compile() (ConfigCompiled, error) {
|
||||
var cc ConfigCompiled
|
||||
|
||||
p, err := c.Cache.For.CompilePredicate()
|
||||
if err != nil {
|
||||
return cc, err
|
||||
}
|
||||
|
||||
cc.For = p
|
||||
|
||||
for _, pc := range c.Polls {
|
||||
|
||||
p, err := pc.For.CompilePredicate()
|
||||
if err != nil {
|
||||
return cc, err
|
||||
}
|
||||
|
||||
cc.PollConfigs = append(cc.PollConfigs, PollConfigCompiled{
|
||||
For: p,
|
||||
Config: pc,
|
||||
})
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// PollConfig holds the configuration for polling remote resources to detect changes in watch mode.
|
||||
type PollConfig struct {
|
||||
// What remote resources to apply this configuration to.
|
||||
For GlobMatcher
|
||||
|
||||
// Disable polling for this configuration.
|
||||
Disable bool
|
||||
|
||||
// Low is the lower bound for the polling interval.
|
||||
// This is the starting point when the resource has recently changed,
|
||||
// if that resource stops changing, the polling interval will gradually increase towards High.
|
||||
Low time.Duration
|
||||
|
||||
// High is the upper bound for the polling interval.
|
||||
// This is the interval used when the resource is stable.
|
||||
High time.Duration
|
||||
}
|
||||
|
||||
func (c PollConfig) MarshalJSON() (b []byte, err error) {
|
||||
// Marshal the durations as strings.
|
||||
type Alias PollConfig
|
||||
return json.Marshal(&struct {
|
||||
Low string
|
||||
High string
|
||||
Alias
|
||||
}{
|
||||
Low: c.Low.String(),
|
||||
High: c.High.String(),
|
||||
Alias: (Alias)(c),
|
||||
})
|
||||
}
|
||||
|
||||
type GlobMatcher struct {
|
||||
// Excludes holds a list of glob patterns that will be excluded.
|
||||
Excludes []string
|
||||
|
||||
// Includes holds a list of glob patterns that will be included.
|
||||
Includes []string
|
||||
}
|
||||
|
||||
type ConfigCompiled struct {
|
||||
For predicate.P[string]
|
||||
PollConfigs []PollConfigCompiled
|
||||
}
|
||||
|
||||
func (c *ConfigCompiled) PollConfigFor(s string) PollConfigCompiled {
|
||||
for _, pc := range c.PollConfigs {
|
||||
if pc.For(s) {
|
||||
return pc
|
||||
}
|
||||
}
|
||||
return PollConfigCompiled{}
|
||||
}
|
||||
|
||||
func (c *ConfigCompiled) IsPollingDisabled() bool {
|
||||
for _, pc := range c.PollConfigs {
|
||||
if !pc.Config.Disable {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type PollConfigCompiled struct {
|
||||
For predicate.P[string]
|
||||
Config PollConfig
|
||||
}
|
||||
|
||||
func (p PollConfigCompiled) IsZero() bool {
|
||||
return p.For == nil
|
||||
}
|
||||
|
||||
func (gm *GlobMatcher) CompilePredicate() (func(string) bool, error) {
|
||||
var p predicate.P[string]
|
||||
for _, include := range gm.Includes {
|
||||
g, err := glob.Compile(include, '/')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fn := func(s string) bool {
|
||||
return g.Match(s)
|
||||
}
|
||||
p = p.Or(fn)
|
||||
}
|
||||
|
||||
for _, exclude := range gm.Excludes {
|
||||
g, err := glob.Compile(exclude, '/')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fn := func(s string) bool {
|
||||
return !g.Match(s)
|
||||
}
|
||||
p = p.And(fn)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func DecodeConfig(bcfg config.BaseConfig, m map[string]any) (Config, error) {
|
||||
if len(m) == 0 {
|
||||
return DefaultConfig, nil
|
||||
}
|
||||
|
||||
var c Config
|
||||
|
||||
dc := &mapstructure.DecoderConfig{
|
||||
Result: &c,
|
||||
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
||||
WeaklyTypedInput: true,
|
||||
}
|
||||
|
||||
decoder, err := mapstructure.NewDecoder(dc)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
if err := decoder.Decode(m); err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package httpcache_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
)
|
||||
|
||||
func TestConfigCustom(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
[httpcache]
|
||||
[httpcache.cache.for]
|
||||
includes = ["**gohugo.io**"]
|
||||
[[httpcache.polls]]
|
||||
low = "5s"
|
||||
high = "32s"
|
||||
[httpcache.polls.for]
|
||||
includes = ["**gohugo.io**"]
|
||||
|
||||
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
httpcacheConf := b.H.Configs.Base.HTTPCache
|
||||
compiled := b.H.Configs.Base.C.HTTPCache
|
||||
|
||||
b.Assert(httpcacheConf.Cache.For.Includes, qt.DeepEquals, []string{"**gohugo.io**"})
|
||||
b.Assert(httpcacheConf.Cache.For.Excludes, qt.IsNil)
|
||||
|
||||
pc := compiled.PollConfigFor("https://gohugo.io/foo.jpg")
|
||||
b.Assert(pc.Config.Low, qt.Equals, 5*time.Second)
|
||||
b.Assert(pc.Config.High, qt.Equals, 32*time.Second)
|
||||
b.Assert(compiled.PollConfigFor("https://example.com/foo.jpg").IsZero(), qt.IsTrue)
|
||||
}
|
||||
|
||||
func TestConfigDefault(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
`
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
compiled := b.H.Configs.Base.C.HTTPCache
|
||||
|
||||
b.Assert(compiled.For("https://gohugo.io/posts.json"), qt.IsFalse)
|
||||
b.Assert(compiled.For("https://gohugo.io/foo.jpg"), qt.IsFalse)
|
||||
b.Assert(compiled.PollConfigFor("https://gohugo.io/foo.jpg").Config.Disable, qt.IsTrue)
|
||||
}
|
||||
Vendored
-42
@@ -1,42 +0,0 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package httpcache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestGlobMatcher(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
g := GlobMatcher{
|
||||
Includes: []string{"**/*.jpg", "**.png", "**/bar/**"},
|
||||
Excludes: []string{"**/foo.jpg", "**.css"},
|
||||
}
|
||||
|
||||
p, err := g.CompilePredicate()
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
c.Assert(p("foo.jpg"), qt.IsFalse)
|
||||
c.Assert(p("foo.png"), qt.IsTrue)
|
||||
c.Assert(p("foo/bar.jpg"), qt.IsTrue)
|
||||
c.Assert(p("foo/bar.png"), qt.IsTrue)
|
||||
c.Assert(p("foo/bar/foo.jpg"), qt.IsFalse)
|
||||
c.Assert(p("foo/bar/foo.css"), qt.IsFalse)
|
||||
c.Assert(p("foo.css"), qt.IsFalse)
|
||||
c.Assert(p("foo/bar/foo.css"), qt.IsFalse)
|
||||
c.Assert(p("foo/bar/foo.xml"), qt.IsTrue)
|
||||
}
|
||||
+27
-45
@@ -48,7 +48,6 @@ import (
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/resources/kinds"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -104,9 +103,6 @@ type rootCommand struct {
|
||||
commonConfigs *lazycache.Cache[int32, *commonConfig]
|
||||
hugoSites *lazycache.Cache[int32, *hugolib.HugoSites]
|
||||
|
||||
// changesFromBuild received from Hugo in watch mode.
|
||||
changesFromBuild chan []identity.Identity
|
||||
|
||||
commands []simplecobra.Commander
|
||||
|
||||
// Flags
|
||||
@@ -132,7 +128,6 @@ type rootCommand struct {
|
||||
verbose bool
|
||||
debug bool
|
||||
quiet bool
|
||||
devMode bool // Hidden flag.
|
||||
|
||||
renderToMemory bool
|
||||
|
||||
@@ -308,7 +303,7 @@ func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commo
|
||||
|
||||
func (r *rootCommand) HugFromConfig(conf *commonConfig) (*hugolib.HugoSites, error) {
|
||||
h, _, err := r.hugoSites.GetOrCreate(r.configVersionID.Load(), func(key int32) (*hugolib.HugoSites, error) {
|
||||
depsCfg := r.newDepsConfig(conf)
|
||||
depsCfg := deps.DepsCfg{Configs: conf.configs, Fs: conf.fs, LogOut: r.logger.Out(), LogLevel: r.logger.Level()}
|
||||
return hugolib.NewHugoSites(depsCfg)
|
||||
})
|
||||
return h, err
|
||||
@@ -320,27 +315,23 @@ func (r *rootCommand) Hugo(cfg config.Provider) (*hugolib.HugoSites, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
depsCfg := r.newDepsConfig(conf)
|
||||
depsCfg := deps.DepsCfg{Configs: conf.configs, Fs: conf.fs, LogOut: r.logger.Out(), LogLevel: r.logger.Level()}
|
||||
return hugolib.NewHugoSites(depsCfg)
|
||||
})
|
||||
return h, err
|
||||
}
|
||||
|
||||
func (r *rootCommand) newDepsConfig(conf *commonConfig) deps.DepsCfg {
|
||||
return deps.DepsCfg{Configs: conf.configs, Fs: conf.fs, LogOut: r.logger.Out(), LogLevel: r.logger.Level(), ChangesFromBuild: r.changesFromBuild}
|
||||
}
|
||||
|
||||
func (r *rootCommand) Name() string {
|
||||
return "hugo"
|
||||
}
|
||||
|
||||
func (r *rootCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
|
||||
b := newHugoBuilder(r, nil)
|
||||
|
||||
if !r.buildWatch {
|
||||
defer b.postBuild("Total", time.Now())
|
||||
defer r.timeTrack(time.Now(), "Total")
|
||||
}
|
||||
|
||||
b := newHugoBuilder(r, nil)
|
||||
|
||||
if err := b.loadConfig(cd, false); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -416,8 +407,6 @@ func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
r.changesFromBuild = make(chan []identity.Identity, 10)
|
||||
|
||||
r.commonConfigs = lazycache.New(lazycache.Options[int32, *commonConfig]{MaxEntries: 5})
|
||||
// We don't want to keep stale HugoSites in memory longer than needed.
|
||||
r.hugoSites = lazycache.New(lazycache.Options[int32, *hugolib.HugoSites]{
|
||||
@@ -434,33 +423,29 @@ func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
|
||||
func (r *rootCommand) createLogger(running bool) (loggers.Logger, error) {
|
||||
level := logg.LevelWarn
|
||||
|
||||
if r.devMode {
|
||||
level = logg.LevelTrace
|
||||
if r.logLevel != "" {
|
||||
switch strings.ToLower(r.logLevel) {
|
||||
case "debug":
|
||||
level = logg.LevelDebug
|
||||
case "info":
|
||||
level = logg.LevelInfo
|
||||
case "warn", "warning":
|
||||
level = logg.LevelWarn
|
||||
case "error":
|
||||
level = logg.LevelError
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid log level: %q, must be one of debug, warn, info or error", r.logLevel)
|
||||
}
|
||||
} else {
|
||||
if r.logLevel != "" {
|
||||
switch strings.ToLower(r.logLevel) {
|
||||
case "debug":
|
||||
level = logg.LevelDebug
|
||||
case "info":
|
||||
level = logg.LevelInfo
|
||||
case "warn", "warning":
|
||||
level = logg.LevelWarn
|
||||
case "error":
|
||||
level = logg.LevelError
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid log level: %q, must be one of debug, warn, info or error", r.logLevel)
|
||||
}
|
||||
} else {
|
||||
if r.verbose {
|
||||
hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0")
|
||||
hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0")
|
||||
level = logg.LevelInfo
|
||||
}
|
||||
if r.verbose {
|
||||
hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0")
|
||||
hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0")
|
||||
level = logg.LevelInfo
|
||||
}
|
||||
|
||||
if r.debug {
|
||||
hugo.Deprecate("--debug", "use --logLevel debug", "v0.114.0")
|
||||
level = logg.LevelDebug
|
||||
}
|
||||
if r.debug {
|
||||
hugo.Deprecate("--debug", "use --logLevel debug", "v0.114.0")
|
||||
level = logg.LevelDebug
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,17 +501,14 @@ Complete documentation is available at https://gohugo.io/.`
|
||||
cmd.PersistentFlags().StringVar(&r.cfgDir, "configDir", "config", "config dir")
|
||||
_ = cmd.MarkFlagDirname("configDir")
|
||||
cmd.PersistentFlags().BoolVar(&r.quiet, "quiet", false, "build in quiet mode")
|
||||
cmd.PersistentFlags().BoolVarP(&r.renderToMemory, "renderToMemory", "M", false, "render to memory (mostly useful when running the server)")
|
||||
cmd.PersistentFlags().BoolVar(&r.renderToMemory, "renderToMemory", false, "render to memory (mostly useful when running the server)")
|
||||
|
||||
cmd.PersistentFlags().BoolVarP(&r.verbose, "verbose", "v", false, "verbose output")
|
||||
cmd.PersistentFlags().BoolVarP(&r.debug, "debug", "", false, "debug output")
|
||||
cmd.PersistentFlags().BoolVarP(&r.devMode, "devMode", "", false, "only used for internal testing, flag hidden.")
|
||||
cmd.PersistentFlags().StringVar(&r.logLevel, "logLevel", "", "log level (debug|info|warn|error)")
|
||||
_ = cmd.RegisterFlagCompletionFunc("logLevel", cobra.FixedCompletions([]string{"debug", "info", "warn", "error"}, cobra.ShellCompDirectiveNoFileComp))
|
||||
cmd.Flags().BoolVarP(&r.buildWatch, "watch", "w", false, "watch filesystem for changes and recreate as needed")
|
||||
|
||||
cmd.PersistentFlags().MarkHidden("devMode")
|
||||
|
||||
// Configure local flags
|
||||
applyLocalFlagsBuild(cmd, r)
|
||||
|
||||
|
||||
+8
-14
@@ -45,10 +45,9 @@ func newGenCommand() *genCommand {
|
||||
genmandir string
|
||||
|
||||
// Chroma flags.
|
||||
style string
|
||||
highlightStyle string
|
||||
lineNumbersInlineStyle string
|
||||
lineNumbersTableStyle string
|
||||
style string
|
||||
highlightStyle string
|
||||
linesStyle string
|
||||
)
|
||||
|
||||
newChromaStyles := func() simplecobra.Commander {
|
||||
@@ -64,11 +63,8 @@ See https://xyproto.github.io/splash/docs/all.html for a preview of the availabl
|
||||
if highlightStyle != "" {
|
||||
builder.Add(chroma.LineHighlight, highlightStyle)
|
||||
}
|
||||
if lineNumbersInlineStyle != "" {
|
||||
builder.Add(chroma.LineNumbers, lineNumbersInlineStyle)
|
||||
}
|
||||
if lineNumbersTableStyle != "" {
|
||||
builder.Add(chroma.LineNumbersTable, lineNumbersTableStyle)
|
||||
if linesStyle != "" {
|
||||
builder.Add(chroma.LineNumbers, linesStyle)
|
||||
}
|
||||
style, err := builder.Build()
|
||||
if err != nil {
|
||||
@@ -82,12 +78,10 @@ See https://xyproto.github.io/splash/docs/all.html for a preview of the availabl
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
cmd.PersistentFlags().StringVar(&style, "style", "friendly", "highlighter style (see https://xyproto.github.io/splash/docs/)")
|
||||
_ = cmd.RegisterFlagCompletionFunc("style", cobra.NoFileCompletions)
|
||||
cmd.PersistentFlags().StringVar(&highlightStyle, "highlightStyle", "", `foreground and background colors for highlighted lines, e.g. --highlightStyle "#fff000 bg:#000fff"`)
|
||||
cmd.PersistentFlags().StringVar(&highlightStyle, "highlightStyle", "", "style used for highlighting lines (see https://github.com/alecthomas/chroma)")
|
||||
_ = cmd.RegisterFlagCompletionFunc("highlightStyle", cobra.NoFileCompletions)
|
||||
cmd.PersistentFlags().StringVar(&lineNumbersInlineStyle, "lineNumbersInlineStyle", "", `foreground and background colors for inline line numbers, e.g. --lineNumbersInlineStyle "#fff000 bg:#000fff"`)
|
||||
_ = cmd.RegisterFlagCompletionFunc("lineNumbersInlineStyle", cobra.NoFileCompletions)
|
||||
cmd.PersistentFlags().StringVar(&lineNumbersTableStyle, "lineNumbersTableStyle", "", `foreground and background colors for table line numbers, e.g. --lineNumbersTableStyle "#fff000 bg:#000fff"`)
|
||||
_ = cmd.RegisterFlagCompletionFunc("lineNumbersTableStyle", cobra.NoFileCompletions)
|
||||
cmd.PersistentFlags().StringVar(&linesStyle, "linesStyle", "", "style used for line numbers (see https://github.com/alecthomas/chroma)")
|
||||
_ = cmd.RegisterFlagCompletionFunc("linesStyle", cobra.NoFileCompletions)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+19
-121
@@ -43,7 +43,6 @@ import (
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/gohugoio/hugo/hugolib/filesystems"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/livereload"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
"github.com/gohugoio/hugo/watcher"
|
||||
@@ -76,14 +75,9 @@ type hugoBuilder struct {
|
||||
errState hugoBuilderErrState
|
||||
}
|
||||
|
||||
var errConfigNotSet = errors.New("config not set")
|
||||
|
||||
func (c *hugoBuilder) withConfE(fn func(conf *commonConfig) error) error {
|
||||
c.confmu.Lock()
|
||||
defer c.confmu.Unlock()
|
||||
if c.conf == nil {
|
||||
return errConfigNotSet
|
||||
}
|
||||
return fn(c.conf)
|
||||
}
|
||||
|
||||
@@ -344,27 +338,6 @@ func (c *hugoBuilder) newWatcher(pollIntervalStr string, dirList ...string) (*wa
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case changes := <-c.r.changesFromBuild:
|
||||
c.errState.setBuildErr(nil)
|
||||
unlock, err := h.LockBuild()
|
||||
if err != nil {
|
||||
c.r.logger.Errorln("Failed to acquire a build lock: %s", err)
|
||||
return
|
||||
}
|
||||
c.changeDetector.PrepareNew()
|
||||
err = c.rebuildSitesForChanges(changes)
|
||||
if err != nil {
|
||||
c.r.logger.Errorln("Error while watching:", err)
|
||||
}
|
||||
if c.s != nil && c.s.doLiveReload {
|
||||
doReload := c.changeDetector == nil || len(c.changeDetector.changed()) > 0
|
||||
doReload = doReload || c.showErrorInBrowser && c.errCount() > 0
|
||||
if doReload {
|
||||
livereload.ForceRefresh()
|
||||
}
|
||||
}
|
||||
unlock()
|
||||
|
||||
case evs := <-watcher.Events:
|
||||
unlock, err := h.LockBuild()
|
||||
if err != nil {
|
||||
@@ -612,7 +585,7 @@ func (c *hugoBuilder) fullRebuild(changeType string) {
|
||||
time.Sleep(2 * time.Second)
|
||||
}()
|
||||
|
||||
defer c.postBuild("Rebuilt", time.Now())
|
||||
defer c.r.timeTrack(time.Now(), "Rebuilt")
|
||||
|
||||
err := c.reloadConfig()
|
||||
if err != nil {
|
||||
@@ -744,20 +717,6 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
||||
staticEvents := []fsnotify.Event{}
|
||||
dynamicEvents := []fsnotify.Event{}
|
||||
|
||||
filterDuplicateEvents := func(evs []fsnotify.Event) []fsnotify.Event {
|
||||
seen := make(map[string]bool)
|
||||
var n int
|
||||
for _, ev := range evs {
|
||||
if seen[ev.Name] {
|
||||
continue
|
||||
}
|
||||
seen[ev.Name] = true
|
||||
evs[n] = ev
|
||||
n++
|
||||
}
|
||||
return evs[:n]
|
||||
}
|
||||
|
||||
h, err := c.hugo()
|
||||
if err != nil {
|
||||
c.r.logger.Errorln("Error getting the Hugo object:", err)
|
||||
@@ -847,11 +806,6 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
||||
}
|
||||
}
|
||||
|
||||
lrl := c.r.logger.InfoCommand("livereload")
|
||||
|
||||
staticEvents = filterDuplicateEvents(staticEvents)
|
||||
dynamicEvents = filterDuplicateEvents(dynamicEvents)
|
||||
|
||||
if len(staticEvents) > 0 {
|
||||
c.printChangeDetected("Static files")
|
||||
|
||||
@@ -872,20 +826,19 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
||||
if c.s != nil && c.s.doLiveReload {
|
||||
// Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized
|
||||
|
||||
// force refresh when more than one file
|
||||
if !c.errState.wasErr() && len(staticEvents) == 1 {
|
||||
ev := staticEvents[0]
|
||||
h, err := c.hugo()
|
||||
if err != nil {
|
||||
c.r.logger.Errorln("Error getting the Hugo object:", err)
|
||||
return
|
||||
}
|
||||
|
||||
path := h.BaseFs.SourceFilesystems.MakeStaticPathRelative(staticEvents[0].Name)
|
||||
path := h.BaseFs.SourceFilesystems.MakeStaticPathRelative(ev.Name)
|
||||
path = h.RelURL(paths.ToSlashTrimLeading(path), false)
|
||||
|
||||
lrl.Logf("refreshing static file %q", path)
|
||||
livereload.RefreshPath(path)
|
||||
} else {
|
||||
lrl.Logf("got %d static file change events, force refresh", len(staticEvents))
|
||||
livereload.ForceRefresh()
|
||||
}
|
||||
}
|
||||
@@ -896,43 +849,33 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
||||
h.BaseFs.SourceFilesystems,
|
||||
dynamicEvents)
|
||||
|
||||
onePageName := pickOneWriteOrCreatePath(h.Conf.ContentTypes(), partitionedEvents.ContentEvents)
|
||||
onePageName := pickOneWriteOrCreatePath(partitionedEvents.ContentEvents)
|
||||
|
||||
c.printChangeDetected("")
|
||||
c.changeDetector.PrepareNew()
|
||||
|
||||
func() {
|
||||
defer c.postBuild("Total", time.Now())
|
||||
defer c.r.timeTrack(time.Now(), "Total")
|
||||
if err := c.rebuildSites(dynamicEvents); err != nil {
|
||||
c.handleBuildErr(err, "Rebuild failed")
|
||||
}
|
||||
}()
|
||||
|
||||
if c.s != nil && c.s.doLiveReload {
|
||||
if c.errState.wasErr() {
|
||||
livereload.ForceRefresh()
|
||||
return
|
||||
}
|
||||
|
||||
changed := c.changeDetector.changed()
|
||||
if c.changeDetector != nil {
|
||||
lrl.Logf("build changed %d files", len(changed))
|
||||
if len(changed) == 0 {
|
||||
// Nothing has changed.
|
||||
if len(partitionedEvents.ContentEvents) == 0 && len(partitionedEvents.AssetEvents) > 0 {
|
||||
if c.errState.wasErr() {
|
||||
livereload.ForceRefresh()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If this change set also contains one or more CSS files, we need to
|
||||
// refresh these as well.
|
||||
var cssChanges []string
|
||||
var otherChanges []string
|
||||
|
||||
for _, ev := range changed {
|
||||
if strings.HasSuffix(ev, ".css") {
|
||||
cssChanges = append(cssChanges, ev)
|
||||
changed := c.changeDetector.changed()
|
||||
if c.changeDetector != nil && len(changed) == 0 {
|
||||
// Nothing has changed.
|
||||
return
|
||||
} else if len(changed) == 1 {
|
||||
pathToRefresh := h.PathSpec.RelURL(paths.ToSlashTrimLeading(changed[0]), false)
|
||||
livereload.RefreshPath(pathToRefresh)
|
||||
} else {
|
||||
otherChanges = append(otherChanges, ev)
|
||||
livereload.ForceRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -948,48 +891,16 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
||||
}
|
||||
}
|
||||
|
||||
if p != nil && p.RelPermalink() != "" {
|
||||
link, port := p.RelPermalink(), p.Site().ServerPort()
|
||||
lrl.Logf("navigating to %q using port %d", link, port)
|
||||
livereload.NavigateToPathForPort(link, port)
|
||||
if p != nil {
|
||||
livereload.NavigateToPathForPort(p.RelPermalink(), p.Site().ServerPort())
|
||||
} else {
|
||||
lrl.Logf("no page to navigate to, force refresh")
|
||||
livereload.ForceRefresh()
|
||||
}
|
||||
} else if len(otherChanges) > 0 {
|
||||
if len(otherChanges) == 1 {
|
||||
// Allow single changes to be refreshed without a full page reload.
|
||||
pathToRefresh := h.PathSpec.RelURL(paths.ToSlashTrimLeading(otherChanges[0]), false)
|
||||
lrl.Logf("refreshing %q", pathToRefresh)
|
||||
livereload.RefreshPath(pathToRefresh)
|
||||
} else if len(cssChanges) == 0 {
|
||||
lrl.Logf("force refresh")
|
||||
livereload.ForceRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
if len(cssChanges) > 0 {
|
||||
// Allow some time for the live reload script to get reconnected.
|
||||
if len(otherChanges) > 0 {
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
for _, ev := range cssChanges {
|
||||
pathToRefresh := h.PathSpec.RelURL(paths.ToSlashTrimLeading(ev), false)
|
||||
lrl.Logf("refreshing CSS %q", pathToRefresh)
|
||||
livereload.RefreshPath(pathToRefresh)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *hugoBuilder) postBuild(what string, start time.Time) {
|
||||
if h, err := c.hugo(); err == nil && h.Conf.Running() {
|
||||
h.LogServerAddresses()
|
||||
}
|
||||
c.r.timeTrack(start, what)
|
||||
}
|
||||
|
||||
func (c *hugoBuilder) hugo() (*hugolib.HugoSites, error) {
|
||||
var h *hugolib.HugoSites
|
||||
if err := c.withConfE(func(conf *commonConfig) error {
|
||||
@@ -1096,19 +1007,6 @@ func (c *hugoBuilder) rebuildSites(events []fsnotify.Event) error {
|
||||
return h.Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyVisited: c.visitedURLs, ErrRecovery: c.errState.wasErr()}, events...)
|
||||
}
|
||||
|
||||
func (c *hugoBuilder) rebuildSitesForChanges(ids []identity.Identity) error {
|
||||
c.errState.setBuildErr(nil)
|
||||
h, err := c.hugo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
whatChanged := &hugolib.WhatChanged{}
|
||||
whatChanged.Add(ids...)
|
||||
err = h.Build(hugolib.BuildCfg{NoBuildLock: true, WhatChanged: whatChanged, RecentlyVisited: c.visitedURLs, ErrRecovery: c.errState.wasErr()})
|
||||
c.errState.setBuildErr(err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *hugoBuilder) reloadConfig() error {
|
||||
c.r.Reset()
|
||||
c.r.configVersionID.Add(1)
|
||||
|
||||
+11
-26
@@ -41,8 +41,6 @@ func newListCommand() *listCommand {
|
||||
p.PublishDate().Format(time.RFC3339),
|
||||
strconv.FormatBool(p.Draft()),
|
||||
p.Permalink(),
|
||||
p.Kind(),
|
||||
p.Section(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,8 +67,6 @@ func newListCommand() *listCommand {
|
||||
"publishDate",
|
||||
"draft",
|
||||
"permalink",
|
||||
"kind",
|
||||
"section",
|
||||
})
|
||||
|
||||
for _, p := range h.Pages() {
|
||||
@@ -79,6 +75,9 @@ func newListCommand() *listCommand {
|
||||
if err := writer.Write(record); err != nil {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,8 +88,8 @@ func newListCommand() *listCommand {
|
||||
commands: []simplecobra.Commander{
|
||||
&simpleCommand{
|
||||
name: "drafts",
|
||||
short: "List draft content",
|
||||
long: `List draft content.`,
|
||||
short: "List all drafts",
|
||||
long: `List all of the drafts in your content directory.`,
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
shouldInclude := func(p page.Page) bool {
|
||||
if !p.Draft() || p.File() == nil {
|
||||
@@ -110,8 +109,8 @@ func newListCommand() *listCommand {
|
||||
},
|
||||
&simpleCommand{
|
||||
name: "future",
|
||||
short: "List future content",
|
||||
long: `List content with a future publication date.`,
|
||||
short: "List all posts dated in the future",
|
||||
long: `List all of the posts in your content directory which will be posted in the future.`,
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
shouldInclude := func(p page.Page) bool {
|
||||
if !resource.IsFuture(p) || p.File() == nil {
|
||||
@@ -130,8 +129,8 @@ func newListCommand() *listCommand {
|
||||
},
|
||||
&simpleCommand{
|
||||
name: "expired",
|
||||
short: "List expired content",
|
||||
long: `List content with a past expiration date.`,
|
||||
short: "List all posts already expired",
|
||||
long: `List all of the posts in your content directory which has already expired.`,
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
shouldInclude := func(p page.Page) bool {
|
||||
if !resource.IsExpired(p) || p.File() == nil {
|
||||
@@ -150,8 +149,8 @@ func newListCommand() *listCommand {
|
||||
},
|
||||
&simpleCommand{
|
||||
name: "all",
|
||||
short: "List all content",
|
||||
long: `List all content including draft, future, and expired.`,
|
||||
short: "List all posts",
|
||||
long: `List all of the posts in your content directory, include drafts, future and expired pages.`,
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
shouldInclude := func(p page.Page) bool {
|
||||
return p.File() != nil
|
||||
@@ -162,20 +161,6 @@ func newListCommand() *listCommand {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
},
|
||||
},
|
||||
&simpleCommand{
|
||||
name: "published",
|
||||
short: "List published content",
|
||||
long: `List content that is not draft, future, or expired.`,
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
shouldInclude := func(p page.Page) bool {
|
||||
return !p.Draft() && !resource.IsFuture(p) && !resource.IsExpired(p) && p.File() != nil
|
||||
}
|
||||
return list(cd, r, shouldInclude)
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -268,14 +268,13 @@ Run "go help get" for more information. All flags available for "go get" is also
|
||||
if info.Name() == "go.mod" {
|
||||
// Found a module.
|
||||
dir := filepath.Dir(path)
|
||||
|
||||
r.Println("Update module in", dir)
|
||||
cfg := config.New()
|
||||
cfg.Set("workingDir", dir)
|
||||
conf, err := r.ConfigFromProvider(r.configVersionID.Add(1), flagsToCfg(cd, cfg))
|
||||
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, cfg))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Println("Update module in", conf.configs.Base.WorkingDir)
|
||||
client := conf.configs.ModulesClient
|
||||
return client.Get(args...)
|
||||
|
||||
|
||||
+1
-1
@@ -209,7 +209,7 @@ func (c *newCommand) newSiteNextStepsText(path string, format string) string {
|
||||
1. Change the current directory to ` + path + `.
|
||||
2. Create or install a theme:
|
||||
- Create a new theme with the command "hugo new theme <THEMENAME>"
|
||||
- Or, install a theme from https://themes.gohugo.io/
|
||||
- Install a theme from https://themes.gohugo.io/
|
||||
3. Edit hugo.` + format + `, setting the "theme" property to the theme name.
|
||||
4. Create new content with the command "hugo new content `)
|
||||
|
||||
|
||||
+13
-13
@@ -46,12 +46,12 @@ import (
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/common/urls"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/gohugoio/hugo/hugolib/filesystems"
|
||||
"github.com/gohugoio/hugo/livereload"
|
||||
@@ -162,16 +162,16 @@ type dynamicEvents struct {
|
||||
|
||||
type fileChangeDetector struct {
|
||||
sync.Mutex
|
||||
current map[string]uint64
|
||||
prev map[string]uint64
|
||||
current map[string]string
|
||||
prev map[string]string
|
||||
|
||||
irrelevantRe *regexp.Regexp
|
||||
}
|
||||
|
||||
func (f *fileChangeDetector) OnFileClose(name string, checksum uint64) {
|
||||
func (f *fileChangeDetector) OnFileClose(name, md5sum string) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.current[name] = checksum
|
||||
f.current[name] = md5sum
|
||||
}
|
||||
|
||||
func (f *fileChangeDetector) PrepareNew() {
|
||||
@@ -183,16 +183,16 @@ func (f *fileChangeDetector) PrepareNew() {
|
||||
defer f.Unlock()
|
||||
|
||||
if f.current == nil {
|
||||
f.current = make(map[string]uint64)
|
||||
f.prev = make(map[string]uint64)
|
||||
f.current = make(map[string]string)
|
||||
f.prev = make(map[string]string)
|
||||
return
|
||||
}
|
||||
|
||||
f.prev = make(map[string]uint64)
|
||||
f.prev = make(map[string]string)
|
||||
for k, v := range f.current {
|
||||
f.prev[k] = v
|
||||
}
|
||||
f.current = make(map[string]uint64)
|
||||
f.current = make(map[string]string)
|
||||
}
|
||||
|
||||
func (f *fileChangeDetector) changed() []string {
|
||||
@@ -538,7 +538,7 @@ of a second, you will be able to save and see your changes nearly instantly.`
|
||||
cmd.Flags().BoolVar(&c.noHTTPCache, "noHTTPCache", false, "prevent HTTP caching")
|
||||
cmd.Flags().BoolVarP(&c.serverAppend, "appendPort", "", true, "append port to baseURL")
|
||||
cmd.Flags().BoolVar(&c.disableLiveReload, "disableLiveReload", false, "watch without enabling live browser reload on rebuild")
|
||||
cmd.Flags().BoolVarP(&c.navigateToChanged, "navigateToChanged", "N", false, "navigate to changed content file on live browser reload")
|
||||
cmd.Flags().BoolVar(&c.navigateToChanged, "navigateToChanged", false, "navigate to changed content file on live browser reload")
|
||||
cmd.Flags().BoolVar(&c.renderStaticToDisk, "renderStaticToDisk", false, "serve static files from disk and dynamic files from memory")
|
||||
cmd.Flags().BoolVar(&c.disableFastRender, "disableFastRender", false, "enables full re-renders on changes")
|
||||
cmd.Flags().BoolVar(&c.disableBrowserError, "disableBrowserError", false, "do not show build errors in the browser")
|
||||
@@ -1188,16 +1188,16 @@ func partitionDynamicEvents(sourceFs *filesystems.SourceFilesystems, events []fs
|
||||
return
|
||||
}
|
||||
|
||||
func pickOneWriteOrCreatePath(contentTypes config.ContentTypesProvider, events []fsnotify.Event) string {
|
||||
func pickOneWriteOrCreatePath(events []fsnotify.Event) string {
|
||||
name := ""
|
||||
|
||||
for _, ev := range events {
|
||||
if ev.Op&fsnotify.Write == fsnotify.Write || ev.Op&fsnotify.Create == fsnotify.Create {
|
||||
if contentTypes.IsIndexContentFile(ev.Name) {
|
||||
if files.IsIndexContentFile(ev.Name) {
|
||||
return ev.Name
|
||||
}
|
||||
|
||||
if contentTypes.IsContentFile(ev.Name) {
|
||||
if files.IsContentFile(ev.Name) {
|
||||
name = ev.Name
|
||||
}
|
||||
|
||||
|
||||
@@ -65,16 +65,3 @@ func (s *Stack[T]) Drain() []T {
|
||||
s.items = nil
|
||||
return items
|
||||
}
|
||||
|
||||
func (s *Stack[T]) DrainMatching(predicate func(T) bool) []T {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
var items []T
|
||||
for i := len(s.items) - 1; i >= 0; i-- {
|
||||
if predicate(s.items[i]) {
|
||||
items = append(items, s.items[i])
|
||||
s.items = append(s.items[:i], s.items[i+1:]...)
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package hashing provides common hashing utilities.
|
||||
package hashing
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"github.com/gohugoio/hashstructure"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
)
|
||||
|
||||
// XXHashFromReader calculates the xxHash for the given reader.
|
||||
func XXHashFromReader(r io.Reader) (uint64, int64, error) {
|
||||
h := getXxHashReadFrom()
|
||||
defer putXxHashReadFrom(h)
|
||||
|
||||
size, err := io.Copy(h, r)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return h.Sum64(), size, nil
|
||||
}
|
||||
|
||||
// XXHashFromString calculates the xxHash for the given string.
|
||||
func XXHashFromString(s string) (uint64, error) {
|
||||
h := xxhash.New()
|
||||
h.WriteString(s)
|
||||
return h.Sum64(), nil
|
||||
}
|
||||
|
||||
// XxHashFromStringHexEncoded calculates the xxHash for the given string
|
||||
// and returns the hash as a hex encoded string.
|
||||
func XxHashFromStringHexEncoded(f string) string {
|
||||
h := xxhash.New()
|
||||
h.WriteString(f)
|
||||
hash := h.Sum(nil)
|
||||
return hex.EncodeToString(hash)
|
||||
}
|
||||
|
||||
// MD5FromStringHexEncoded returns the MD5 hash of the given string.
|
||||
func MD5FromStringHexEncoded(f string) string {
|
||||
h := md5.New()
|
||||
h.Write([]byte(f))
|
||||
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
|
||||
// it in the more complex values (e.g. Page) will not hash the full content.
|
||||
func HashString(vs ...any) string {
|
||||
hash := HashUint64(vs...)
|
||||
return strconv.FormatUint(hash, 10)
|
||||
}
|
||||
|
||||
var hashOptsPool = sync.Pool{
|
||||
New: func() any {
|
||||
return &hashstructure.HashOptions{
|
||||
Hasher: xxhash.New(),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func getHashOpts() *hashstructure.HashOptions {
|
||||
return hashOptsPool.Get().(*hashstructure.HashOptions)
|
||||
}
|
||||
|
||||
func putHashOpts(opts *hashstructure.HashOptions) {
|
||||
opts.Hasher.Reset()
|
||||
hashOptsPool.Put(opts)
|
||||
}
|
||||
|
||||
// HashUint64 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
|
||||
// it in the more complex values (e.g. Page) will not hash the full content.
|
||||
func HashUint64(vs ...any) uint64 {
|
||||
var o any
|
||||
if len(vs) == 1 {
|
||||
o = toHashable(vs[0])
|
||||
} else {
|
||||
elements := make([]any, len(vs))
|
||||
for i, e := range vs {
|
||||
elements[i] = toHashable(e)
|
||||
}
|
||||
o = elements
|
||||
}
|
||||
|
||||
hashOpts := getHashOpts()
|
||||
defer putHashOpts(hashOpts)
|
||||
|
||||
hash, err := hashstructure.Hash(o, hashOpts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (x *xxhashReadFrom) ReadFrom(r io.Reader) (int64, error) {
|
||||
for {
|
||||
n, err := r.Read(x.buff)
|
||||
if n > 0 {
|
||||
x.Digest.Write(x.buff[:n])
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return int64(n), err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var xXhashReadFromPool = sync.Pool{
|
||||
New: func() any {
|
||||
return &xxhashReadFrom{Digest: xxhash.New(), buff: make([]byte, 48*1024)}
|
||||
},
|
||||
}
|
||||
|
||||
func getXxHashReadFrom() *xxhashReadFrom {
|
||||
return xXhashReadFromPool.Get().(*xxhashReadFrom)
|
||||
}
|
||||
|
||||
func putXxHashReadFrom(h *xxhashReadFrom) {
|
||||
h.Reset()
|
||||
xXhashReadFromPool.Put(h)
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package hashing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestXxHashFromReader(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
s := "Hello World"
|
||||
r := strings.NewReader(s)
|
||||
got, size, err := XXHashFromReader(r)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(size, qt.Equals, int64(len(s)))
|
||||
c.Assert(got, qt.Equals, uint64(7148569436472236994))
|
||||
}
|
||||
|
||||
func TestXxHashFromReaderPara(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 10; i++ {
|
||||
i := i
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 100; j++ {
|
||||
s := strings.Repeat("Hello ", i+j+1*42)
|
||||
r := strings.NewReader(s)
|
||||
got, size, err := XXHashFromReader(r)
|
||||
c.Assert(size, qt.Equals, int64(len(s)))
|
||||
c.Assert(err, qt.IsNil)
|
||||
expect, _ := XXHashFromString(s)
|
||||
c.Assert(got, qt.Equals, expect)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestXxHashFromString(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
s := "Hello World"
|
||||
got, err := XXHashFromString(s)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(got, qt.Equals, uint64(7148569436472236994))
|
||||
}
|
||||
|
||||
func TestXxHashFromStringHexEncoded(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
s := "The quick brown fox jumps over the lazy dog"
|
||||
got := XxHashFromStringHexEncoded(s)
|
||||
// Facit: https://asecuritysite.com/encryption/xxhash?val=The%20quick%20brown%20fox%20jumps%20over%20the%20lazy%20dog
|
||||
c.Assert(got, qt.Equals, "0b242d361fda71bc")
|
||||
}
|
||||
|
||||
func BenchmarkXXHashFromReader(b *testing.B) {
|
||||
r := strings.NewReader("Hello World")
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
XXHashFromReader(r)
|
||||
r.Seek(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkXXHashFromString(b *testing.B) {
|
||||
s := "Hello World"
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
XXHashFromString(s)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkXXHashFromStringHexEncoded(b *testing.B) {
|
||||
s := "The quick brown fox jumps over the lazy dog"
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
XxHashFromStringHexEncoded(s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashString(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(HashString("a", "b"), qt.Equals, "3176555414984061461")
|
||||
c.Assert(HashString("ab"), qt.Equals, "7347350983217793633")
|
||||
|
||||
var vals []any = []any{"a", "b", tstKeyer{"c"}}
|
||||
|
||||
c.Assert(HashString(vals...), qt.Equals, "4438730547989914315")
|
||||
c.Assert(vals[2], qt.Equals, tstKeyer{"c"})
|
||||
}
|
||||
|
||||
type tstKeyer struct {
|
||||
key string
|
||||
}
|
||||
|
||||
func (t tstKeyer) Key() string {
|
||||
return t.key
|
||||
}
|
||||
|
||||
func (t tstKeyer) String() string {
|
||||
return "key: " + t.key
|
||||
}
|
||||
|
||||
func BenchmarkHashString(b *testing.B) {
|
||||
word := " hello "
|
||||
|
||||
var tests []string
|
||||
|
||||
for i := 1; i <= 5; i++ {
|
||||
sentence := strings.Repeat(word, int(math.Pow(4, float64(i))))
|
||||
tests = append(tests, sentence)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for _, test := range tests {
|
||||
b.Run(fmt.Sprintf("n%d", len(test)), func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
HashString(test)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -68,20 +68,6 @@ func (e *TimeoutError) Is(target error) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// errMessage wraps an error with a message.
|
||||
type errMessage struct {
|
||||
msg string
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *errMessage) Error() string {
|
||||
return e.msg
|
||||
}
|
||||
|
||||
func (e *errMessage) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
// IsFeatureNotAvailableError returns true if the given error is or contains a FeatureNotAvailableError.
|
||||
func IsFeatureNotAvailableError(err error) bool {
|
||||
return errors.Is(err, &FeatureNotAvailableError{})
|
||||
@@ -135,38 +121,19 @@ func IsNotExist(err error) bool {
|
||||
|
||||
var nilPointerErrRe = regexp.MustCompile(`at <(.*)>: error calling (.*?): runtime error: invalid memory address or nil pointer dereference`)
|
||||
|
||||
const deferredPrefix = "__hdeferred/"
|
||||
|
||||
var deferredStringToRemove = regexp.MustCompile(`executing "__hdeferred/.*" `)
|
||||
|
||||
// ImproveRenderErr improves the error message for rendering errors.
|
||||
func ImproveRenderErr(inErr error) (outErr error) {
|
||||
func ImproveIfNilPointer(inErr error) (outErr error) {
|
||||
outErr = inErr
|
||||
msg := improveIfNilPointerMsg(inErr)
|
||||
if msg != "" {
|
||||
outErr = &errMessage{msg: msg, err: outErr}
|
||||
}
|
||||
|
||||
if strings.Contains(inErr.Error(), deferredPrefix) {
|
||||
msg := deferredStringToRemove.ReplaceAllString(inErr.Error(), "executing ")
|
||||
outErr = &errMessage{msg: msg, err: outErr}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func improveIfNilPointerMsg(inErr error) string {
|
||||
m := nilPointerErrRe.FindStringSubmatch(inErr.Error())
|
||||
if len(m) == 0 {
|
||||
return ""
|
||||
return
|
||||
}
|
||||
call := m[1]
|
||||
field := m[2]
|
||||
parts := strings.Split(call, ".")
|
||||
if len(parts) < 2 {
|
||||
return ""
|
||||
}
|
||||
receiverName := parts[len(parts)-2]
|
||||
receiver := strings.Join(parts[:len(parts)-1], ".")
|
||||
s := fmt.Sprintf("– %s is nil; wrap it in if or with: {{ with %s }}{{ .%s }}{{ end }}", receiverName, receiver, field)
|
||||
return nilPointerErrRe.ReplaceAllString(inErr.Error(), s)
|
||||
outErr = errors.New(nilPointerErrRe.ReplaceAllString(inErr.Error(), s))
|
||||
return
|
||||
}
|
||||
|
||||
+17
-77
@@ -21,10 +21,8 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/cli/safeexec"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
@@ -86,7 +84,7 @@ var WithEnviron = func(env []string) func(c *commandeer) {
|
||||
}
|
||||
|
||||
// New creates a new Exec using the provided security config.
|
||||
func New(cfg security.Config, workingDir string) *Exec {
|
||||
func New(cfg security.Config) *Exec {
|
||||
var baseEnviron []string
|
||||
for _, v := range os.Environ() {
|
||||
k, _ := config.SplitEnvVar(v)
|
||||
@@ -97,7 +95,6 @@ func New(cfg security.Config, workingDir string) *Exec {
|
||||
|
||||
return &Exec{
|
||||
sc: cfg,
|
||||
workingDir: workingDir,
|
||||
baseEnviron: baseEnviron,
|
||||
}
|
||||
}
|
||||
@@ -122,23 +119,15 @@ func SafeCommand(name string, arg ...string) (*exec.Cmd, error) {
|
||||
|
||||
// Exec enforces a security policy for commands run via os/exec.
|
||||
type Exec struct {
|
||||
sc security.Config
|
||||
workingDir string
|
||||
sc security.Config
|
||||
|
||||
// os.Environ filtered by the Exec.OsEnviron whitelist filter.
|
||||
baseEnviron []string
|
||||
|
||||
npxInit sync.Once
|
||||
npxAvailable bool
|
||||
}
|
||||
|
||||
func (e *Exec) New(name string, arg ...any) (Runner, error) {
|
||||
return e.new(name, "", arg...)
|
||||
}
|
||||
|
||||
// New will fail if name is not allowed according to the configured security policy.
|
||||
// Else a configured Runner will be returned ready to be Run.
|
||||
func (e *Exec) new(name string, fullyQualifiedName string, arg ...any) (Runner, error) {
|
||||
func (e *Exec) New(name string, arg ...any) (Runner, error) {
|
||||
if err := e.sc.CheckAllowedExec(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -147,51 +136,17 @@ func (e *Exec) new(name string, fullyQualifiedName string, arg ...any) (Runner,
|
||||
copy(env, e.baseEnviron)
|
||||
|
||||
cm := &commandeer{
|
||||
name: name,
|
||||
fullyQualifiedName: fullyQualifiedName,
|
||||
env: env,
|
||||
name: name,
|
||||
env: env,
|
||||
}
|
||||
|
||||
return cm.command(arg...)
|
||||
}
|
||||
|
||||
// Npx will in order:
|
||||
// 1. Try fo find the binary in the WORKINGDIR/node_modules/.bin directory.
|
||||
// 2. If not found, and npx is available, run npx --no-install <name> <args>.
|
||||
// 3. Fall back to the PATH.
|
||||
// Npx is a convenience method to create a Runner running npx --no-install <name> <args.
|
||||
func (e *Exec) Npx(name string, arg ...any) (Runner, error) {
|
||||
// npx is slow, so first try the common case.
|
||||
nodeBinFilename := filepath.Join(e.workingDir, nodeModulesBinPath, name)
|
||||
_, err := safeexec.LookPath(nodeBinFilename)
|
||||
if err == nil {
|
||||
return e.new(name, nodeBinFilename, arg...)
|
||||
}
|
||||
e.checkNpx()
|
||||
if e.npxAvailable {
|
||||
r, err := e.npx(name, arg...)
|
||||
if err == nil {
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
return e.New(name, arg...)
|
||||
}
|
||||
|
||||
const (
|
||||
npxNoInstall = "--no-install"
|
||||
npxBinary = "npx"
|
||||
nodeModulesBinPath = "node_modules/.bin"
|
||||
)
|
||||
|
||||
func (e *Exec) checkNpx() {
|
||||
e.npxInit.Do(func() {
|
||||
e.npxAvailable = InPath(npxBinary)
|
||||
})
|
||||
}
|
||||
|
||||
// npx is a convenience method to create a Runner running npx --no-install <name> <args.
|
||||
func (e *Exec) npx(name string, arg ...any) (Runner, error) {
|
||||
arg = append(arg[:0], append([]any{npxNoInstall, name}, arg[0:]...)...)
|
||||
return e.New(npxBinary, arg...)
|
||||
arg = append(arg[:0], append([]any{"--no-install", name}, arg[0:]...)...)
|
||||
return e.New("npx", arg...)
|
||||
}
|
||||
|
||||
// Sec returns the security policies this Exec is configured with.
|
||||
@@ -200,12 +155,11 @@ func (e *Exec) Sec() security.Config {
|
||||
}
|
||||
|
||||
type NotFoundError struct {
|
||||
name string
|
||||
method string
|
||||
name string
|
||||
}
|
||||
|
||||
func (e *NotFoundError) Error() string {
|
||||
return fmt.Sprintf("binary with name %q not found %s", e.name, e.method)
|
||||
return fmt.Sprintf("binary with name %q not found", e.name)
|
||||
}
|
||||
|
||||
// Runner wraps a *os.Cmd.
|
||||
@@ -228,14 +182,8 @@ func (c *cmdWrapper) Run() error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
name := c.name
|
||||
method := "in PATH"
|
||||
if name == npxBinary {
|
||||
name = c.c.Args[2]
|
||||
method = "using npx"
|
||||
}
|
||||
if notFoundRe.MatchString(c.outerr.String()) {
|
||||
return &NotFoundError{name: name, method: method}
|
||||
return &NotFoundError{name: c.name}
|
||||
}
|
||||
return fmt.Errorf("failed to execute binary %q with args %v: %s", c.name, c.c.Args[1:], c.outerr.String())
|
||||
}
|
||||
@@ -251,9 +199,8 @@ type commandeer struct {
|
||||
dir string
|
||||
ctx context.Context
|
||||
|
||||
name string
|
||||
fullyQualifiedName string
|
||||
env []string
|
||||
name string
|
||||
env []string
|
||||
}
|
||||
|
||||
func (c *commandeer) command(arg ...any) (*cmdWrapper, error) {
|
||||
@@ -273,17 +220,10 @@ func (c *commandeer) command(arg ...any) (*cmdWrapper, error) {
|
||||
}
|
||||
}
|
||||
|
||||
var bin string
|
||||
if c.fullyQualifiedName != "" {
|
||||
bin = c.fullyQualifiedName
|
||||
} else {
|
||||
var err error
|
||||
bin, err = safeexec.LookPath(c.name)
|
||||
if err != nil {
|
||||
return nil, &NotFoundError{
|
||||
name: c.name,
|
||||
method: "in PATH",
|
||||
}
|
||||
bin, err := safeexec.LookPath(c.name)
|
||||
if err != nil {
|
||||
return nil, &NotFoundError{
|
||||
name: c.name,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -268,8 +268,7 @@ func IsContextType(tp reflect.Type) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
isContext, _ := isContextCache.GetOrCreate(tp, func() (bool, error) {
|
||||
return tp.Implements(contextInterface), nil
|
||||
return isContextCache.GetOrCreate(tp, func() bool {
|
||||
return tp.Implements(contextInterface)
|
||||
})
|
||||
return isContext
|
||||
}
|
||||
|
||||
@@ -123,20 +123,6 @@ func InSlicEqualFold(arr []string, el string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ToString converts the given value to a string.
|
||||
// Note that this is a more strict version compared to cast.ToString,
|
||||
// as it will not try to convert numeric values to strings,
|
||||
// but only accept strings or fmt.Stringer.
|
||||
func ToString(v any) (string, bool) {
|
||||
switch vv := v.(type) {
|
||||
case string:
|
||||
return vv, true
|
||||
case fmt.Stringer:
|
||||
return vv.String(), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
type Tuple struct {
|
||||
First string
|
||||
Second string
|
||||
|
||||
@@ -17,35 +17,24 @@ import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// HasBytesWriter is a writer will match against a slice of patterns.
|
||||
// HasBytesWriter is a writer that will set Match to true if the given pattern
|
||||
// is found in the stream.
|
||||
type HasBytesWriter struct {
|
||||
Patterns []*HasBytesPattern
|
||||
Match bool
|
||||
Pattern []byte
|
||||
|
||||
i int
|
||||
done bool
|
||||
buff []byte
|
||||
}
|
||||
|
||||
type HasBytesPattern struct {
|
||||
Match bool
|
||||
Pattern []byte
|
||||
}
|
||||
|
||||
func (h *HasBytesWriter) patternLen() int {
|
||||
l := 0
|
||||
for _, p := range h.Patterns {
|
||||
l += len(p.Pattern)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (h *HasBytesWriter) Write(p []byte) (n int, err error) {
|
||||
if h.done {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
if len(h.buff) == 0 {
|
||||
h.buff = make([]byte, h.patternLen()*2)
|
||||
h.buff = make([]byte, len(h.Pattern)*2)
|
||||
}
|
||||
|
||||
for i := range p {
|
||||
@@ -57,23 +46,11 @@ func (h *HasBytesWriter) Write(p []byte) (n int, err error) {
|
||||
h.i = len(h.buff) / 2
|
||||
}
|
||||
|
||||
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(h.buff, h.Pattern) {
|
||||
h.Match = true
|
||||
h.done = true
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return len(p), nil
|
||||
|
||||
@@ -34,11 +34,8 @@ func TestHasBytesWriter(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
|
||||
h := &HasBytesWriter{
|
||||
Patterns: []*HasBytesPattern{
|
||||
{Pattern: []byte("__foo")},
|
||||
},
|
||||
Pattern: []byte("__foo"),
|
||||
}
|
||||
|
||||
return h, io.MultiWriter(&b, h)
|
||||
}
|
||||
|
||||
@@ -49,19 +46,19 @@ func TestHasBytesWriter(t *testing.T) {
|
||||
for i := 0; i < 22; i++ {
|
||||
h, w := neww()
|
||||
fmt.Fprintf(w, rndStr()+"abc __foobar"+rndStr())
|
||||
c.Assert(h.Patterns[0].Match, qt.Equals, true)
|
||||
c.Assert(h.Match, qt.Equals, true)
|
||||
|
||||
h, w = neww()
|
||||
fmt.Fprintf(w, rndStr()+"abc __f")
|
||||
fmt.Fprintf(w, "oo bar"+rndStr())
|
||||
c.Assert(h.Patterns[0].Match, qt.Equals, true)
|
||||
c.Assert(h.Match, qt.Equals, true)
|
||||
|
||||
h, w = neww()
|
||||
fmt.Fprintf(w, rndStr()+"abc __moo bar")
|
||||
c.Assert(h.Patterns[0].Match, qt.Equals, false)
|
||||
c.Assert(h.Match, qt.Equals, false)
|
||||
}
|
||||
|
||||
h, w := neww()
|
||||
fmt.Fprintf(w, "__foo")
|
||||
c.Assert(h.Patterns[0].Match, qt.Equals, true)
|
||||
c.Assert(h.Match, qt.Equals, true)
|
||||
}
|
||||
|
||||
@@ -81,33 +81,3 @@ func ToReadCloser(r io.Reader) io.ReadCloser {
|
||||
io.NopCloser(nil),
|
||||
}
|
||||
}
|
||||
|
||||
type ReadWriteCloser interface {
|
||||
io.Reader
|
||||
io.Writer
|
||||
io.Closer
|
||||
}
|
||||
|
||||
// PipeReadWriteCloser is a convenience type to create a pipe with a ReadCloser and a WriteCloser.
|
||||
type PipeReadWriteCloser struct {
|
||||
*io.PipeReader
|
||||
*io.PipeWriter
|
||||
}
|
||||
|
||||
// NewPipeReadWriteCloser creates a new PipeReadWriteCloser.
|
||||
func NewPipeReadWriteCloser() PipeReadWriteCloser {
|
||||
pr, pw := io.Pipe()
|
||||
return PipeReadWriteCloser{pr, pw}
|
||||
}
|
||||
|
||||
func (c PipeReadWriteCloser) Close() (err error) {
|
||||
if err = c.PipeReader.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
err = c.PipeWriter.Close()
|
||||
return
|
||||
}
|
||||
|
||||
func (c PipeReadWriteCloser) WriteString(s string) (int, error) {
|
||||
return c.PipeWriter.Write([]byte(s))
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ package hugo
|
||||
// This should be the only one.
|
||||
var CurrentVersion = Version{
|
||||
Major: 0,
|
||||
Minor: 133,
|
||||
PatchLevel: 1,
|
||||
Suffix: "",
|
||||
Minor: 125,
|
||||
PatchLevel: 0,
|
||||
Suffix: "-DEV",
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/bep/logg"
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
)
|
||||
|
||||
// PanicOnWarningHook panics on warnings.
|
||||
@@ -85,7 +85,7 @@ func (h *logOnceHandler) HandleLog(e *logg.Entry) error {
|
||||
}
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
hash := hashing.HashUint64(e.Level, e.Message, e.Fields)
|
||||
hash := identity.HashUint64(e.Level, e.Message, e.Fields)
|
||||
if h.seen[hash] {
|
||||
return errStop
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ func New(opts Options) Logger {
|
||||
logHandler = newStopHandler(logOnce, logHandler)
|
||||
}
|
||||
|
||||
if len(opts.SuppressStatements) > 0 {
|
||||
if opts.SuppressStatements != nil && len(opts.SuppressStatements) > 0 {
|
||||
logHandler = newStopHandler(newSuppressStatementsHandler(opts.SuppressStatements), logHandler)
|
||||
}
|
||||
|
||||
@@ -323,13 +323,11 @@ func (l *logAdapter) Errors() string {
|
||||
}
|
||||
|
||||
func (l *logAdapter) Erroridf(id, format string, v ...any) {
|
||||
id = strings.ToLower(id)
|
||||
format += l.idfInfoStatement("error", id, format)
|
||||
l.errorl.WithField(FieldNameStatementID, id).Logf(format, v...)
|
||||
}
|
||||
|
||||
func (l *logAdapter) Warnidf(id, format string, v ...any) {
|
||||
id = strings.ToLower(id)
|
||||
format += l.idfInfoStatement("warning", id, format)
|
||||
l.warnl.WithField(FieldNameStatementID, id).Logf(format, v...)
|
||||
}
|
||||
|
||||
+5
-42
@@ -27,12 +27,7 @@ func NewCache[K comparable, T any]() *Cache[K, T] {
|
||||
}
|
||||
|
||||
// Delete deletes the given key from the cache.
|
||||
// If c is nil, this method is a no-op.
|
||||
func (c *Cache[K, T]) Get(key K) (T, bool) {
|
||||
if c == nil {
|
||||
var zero T
|
||||
return zero, false
|
||||
}
|
||||
c.RLock()
|
||||
v, found := c.m[key]
|
||||
c.RUnlock()
|
||||
@@ -40,25 +35,22 @@ func (c *Cache[K, T]) Get(key K) (T, bool) {
|
||||
}
|
||||
|
||||
// GetOrCreate gets the value for the given key if it exists, or creates it if not.
|
||||
func (c *Cache[K, T]) GetOrCreate(key K, create func() (T, error)) (T, error) {
|
||||
func (c *Cache[K, T]) GetOrCreate(key K, create func() T) T {
|
||||
c.RLock()
|
||||
v, found := c.m[key]
|
||||
c.RUnlock()
|
||||
if found {
|
||||
return v, nil
|
||||
return v
|
||||
}
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
v, found = c.m[key]
|
||||
if found {
|
||||
return v, nil
|
||||
}
|
||||
v, err := create()
|
||||
if err != nil {
|
||||
return v, err
|
||||
return v
|
||||
}
|
||||
v = create()
|
||||
c.m[key] = v
|
||||
return v, nil
|
||||
return v
|
||||
}
|
||||
|
||||
// Set sets the given key to the given value.
|
||||
@@ -68,35 +60,6 @@ func (c *Cache[K, T]) Set(key K, value T) {
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
// ForEeach calls the given function for each key/value pair in the cache.
|
||||
func (c *Cache[K, T]) ForEeach(f func(K, T)) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
for k, v := range c.m {
|
||||
f(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache[K, T]) Drain() map[K]T {
|
||||
c.Lock()
|
||||
m := c.m
|
||||
c.m = make(map[K]T)
|
||||
c.Unlock()
|
||||
return m
|
||||
}
|
||||
|
||||
func (c *Cache[K, T]) Len() int {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
return len(c.m)
|
||||
}
|
||||
|
||||
func (c *Cache[K, T]) Reset() {
|
||||
c.Lock()
|
||||
c.m = make(map[K]T)
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
// SliceCache is a simple thread safe cache backed by a map.
|
||||
type SliceCache[T any] struct {
|
||||
m map[string][]T
|
||||
|
||||
+4
-4
@@ -112,17 +112,17 @@ func ToSliceStringMap(in any) ([]map[string]any, error) {
|
||||
}
|
||||
|
||||
// LookupEqualFold finds key in m with case insensitive equality checks.
|
||||
func LookupEqualFold[T any | string](m map[string]T, key string) (T, string, bool) {
|
||||
func LookupEqualFold[T any | string](m map[string]T, key string) (T, bool) {
|
||||
if v, found := m[key]; found {
|
||||
return v, key, true
|
||||
return v, true
|
||||
}
|
||||
for k, v := range m {
|
||||
if strings.EqualFold(k, key) {
|
||||
return v, k, true
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
var s T
|
||||
return s, "", false
|
||||
return s, false
|
||||
}
|
||||
|
||||
// MergeShallow merges src into dst, but only if the key does not already exist in dst.
|
||||
|
||||
@@ -180,18 +180,16 @@ func TestLookupEqualFold(t *testing.T) {
|
||||
"B": "bv",
|
||||
}
|
||||
|
||||
v, k, found := LookupEqualFold(m1, "b")
|
||||
v, found := LookupEqualFold(m1, "b")
|
||||
c.Assert(found, qt.IsTrue)
|
||||
c.Assert(v, qt.Equals, "bv")
|
||||
c.Assert(k, qt.Equals, "B")
|
||||
|
||||
m2 := map[string]string{
|
||||
"a": "av",
|
||||
"B": "bv",
|
||||
}
|
||||
|
||||
v, k, found = LookupEqualFold(m2, "b")
|
||||
v, found = LookupEqualFold(m2, "b")
|
||||
c.Assert(found, qt.IsTrue)
|
||||
c.Assert(k, qt.Equals, "B")
|
||||
c.Assert(v, qt.Equals, "bv")
|
||||
}
|
||||
|
||||
+5
-36
@@ -237,17 +237,12 @@ func prettifyPath(in string, b filepathPathBridge) string {
|
||||
return b.Join(b.Dir(in), name, "index"+ext)
|
||||
}
|
||||
|
||||
// CommonDirPath returns the common directory of the given paths.
|
||||
func CommonDirPath(path1, path2 string) string {
|
||||
// CommonDir returns the common directory of the given paths.
|
||||
func CommonDir(path1, path2 string) string {
|
||||
if path1 == "" || path2 == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
hadLeadingSlash := strings.HasPrefix(path1, "/") || strings.HasPrefix(path2, "/")
|
||||
|
||||
path1 = TrimLeading(path1)
|
||||
path2 = TrimLeading(path2)
|
||||
|
||||
p1 := strings.Split(path1, "/")
|
||||
p2 := strings.Split(path2, "/")
|
||||
|
||||
@@ -261,13 +256,7 @@ func CommonDirPath(path1, path2 string) string {
|
||||
}
|
||||
}
|
||||
|
||||
s := strings.Join(common, "/")
|
||||
|
||||
if hadLeadingSlash && s != "" {
|
||||
s = "/" + s
|
||||
}
|
||||
|
||||
return s
|
||||
return strings.Join(common, "/")
|
||||
}
|
||||
|
||||
// Sanitize sanitizes string to be used in Hugo's file paths and URLs, allowing only
|
||||
@@ -395,27 +384,12 @@ func PathEscape(pth string) string {
|
||||
|
||||
// ToSlashTrimLeading is just a filepath.ToSlash with an added / prefix trimmer.
|
||||
func ToSlashTrimLeading(s string) string {
|
||||
return TrimLeading(filepath.ToSlash(s))
|
||||
}
|
||||
|
||||
// TrimLeading trims the leading slash from the given string.
|
||||
func TrimLeading(s string) string {
|
||||
return strings.TrimPrefix(s, "/")
|
||||
return strings.TrimPrefix(filepath.ToSlash(s), "/")
|
||||
}
|
||||
|
||||
// ToSlashTrimTrailing is just a filepath.ToSlash with an added / suffix trimmer.
|
||||
func ToSlashTrimTrailing(s string) string {
|
||||
return TrimTrailing(filepath.ToSlash(s))
|
||||
}
|
||||
|
||||
// TrimTrailing trims the trailing slash from the given string.
|
||||
func TrimTrailing(s string) string {
|
||||
return strings.TrimSuffix(s, "/")
|
||||
}
|
||||
|
||||
// ToSlashTrim trims any leading and trailing slashes from the given string and converts it to a forward slash separated path.
|
||||
func ToSlashTrim(s string) string {
|
||||
return strings.Trim(filepath.ToSlash(s), "/")
|
||||
return strings.TrimSuffix(filepath.ToSlash(s), "/")
|
||||
}
|
||||
|
||||
// ToSlashPreserveLeading converts the path given to a forward slash separated path
|
||||
@@ -423,8 +397,3 @@ func ToSlashTrim(s string) string {
|
||||
func ToSlashPreserveLeading(s string) string {
|
||||
return "/" + strings.Trim(filepath.ToSlash(s), "/")
|
||||
}
|
||||
|
||||
// IsSameFilePath checks if s1 and s2 are the same file path.
|
||||
func IsSameFilePath(s1, s2 string) bool {
|
||||
return path.Clean(ToSlashTrim(s1)) == path.Clean(ToSlashTrim(s2))
|
||||
}
|
||||
|
||||
@@ -262,52 +262,3 @@ func TestFieldsSlash(t *testing.T) {
|
||||
c.Assert(FieldsSlash("/"), qt.DeepEquals, []string{})
|
||||
c.Assert(FieldsSlash(""), qt.DeepEquals, []string{})
|
||||
}
|
||||
|
||||
func TestCommonDirPath(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
for _, this := range []struct {
|
||||
a, b, expected string
|
||||
}{
|
||||
{"/a/b/c", "/a/b/d", "/a/b"},
|
||||
{"/a/b/c", "a/b/d", "/a/b"},
|
||||
{"a/b/c", "/a/b/d", "/a/b"},
|
||||
{"a/b/c", "a/b/d", "a/b"},
|
||||
{"/a/b/c", "/a/b/c", "/a/b/c"},
|
||||
{"/a/b/c", "/a/b/c/d", "/a/b/c"},
|
||||
{"/a/b/c", "/a/b", "/a/b"},
|
||||
{"/a/b/c", "/a", "/a"},
|
||||
{"/a/b/c", "/d/e/f", ""},
|
||||
} {
|
||||
c.Assert(CommonDirPath(this.a, this.b), qt.Equals, this.expected, qt.Commentf("a: %s b: %s", this.a, this.b))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsSameFilePath(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
for _, this := range []struct {
|
||||
a, b string
|
||||
expected bool
|
||||
}{
|
||||
{"/a/b/c", "/a/b/c", true},
|
||||
{"/a/b/c", "/a/b/c/", true},
|
||||
{"/a/b/c", "/a/b/d", false},
|
||||
{"/a/b/c", "/a/b", false},
|
||||
{"/a/b/c", "/a/b/c/d", false},
|
||||
{"/a/b/c", "/a/b/cd", false},
|
||||
{"/a/b/c", "/a/b/cc", false},
|
||||
{"/a/b/c", "/a/b/c/", true},
|
||||
{"/a/b/c", "/a/b/c//", true},
|
||||
{"/a/b/c", "/a/b/c/.", true},
|
||||
{"/a/b/c", "/a/b/c/./", true},
|
||||
{"/a/b/c", "/a/b/c/./.", true},
|
||||
{"/a/b/c", "/a/b/c/././", true},
|
||||
{"/a/b/c", "/a/b/c/././.", true},
|
||||
{"/a/b/c", "/a/b/c/./././", true},
|
||||
{"/a/b/c", "/a/b/c/./././.", true},
|
||||
{"/a/b/c", "/a/b/c/././././", true},
|
||||
} {
|
||||
c.Assert(IsSameFilePath(filepath.FromSlash(this.a), filepath.FromSlash(this.b)), qt.Equals, this.expected, qt.Commentf("a: %s b: %s", this.a, this.b))
|
||||
}
|
||||
}
|
||||
|
||||
+25
-26
@@ -25,6 +25,8 @@ import (
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
)
|
||||
|
||||
var defaultPathParser PathParser
|
||||
|
||||
// PathParser parses a path into a Path.
|
||||
type PathParser struct {
|
||||
// Maps the language code to its index in the languages/sites slice.
|
||||
@@ -32,9 +34,11 @@ type PathParser struct {
|
||||
|
||||
// Reports whether the given language is disabled.
|
||||
IsLangDisabled func(string) bool
|
||||
}
|
||||
|
||||
// Reports whether the given ext is a content file.
|
||||
IsContentExt func(string) bool
|
||||
// Parse parses component c with path s into Path using the default path parser.
|
||||
func Parse(c, s string) *Path {
|
||||
return defaultPathParser.Parse(c, s)
|
||||
}
|
||||
|
||||
// NormalizePathString returns a normalized path string using the very basic Hugo rules.
|
||||
@@ -104,6 +108,7 @@ func (pp *PathParser) parse(component, s string) (*Path, error) {
|
||||
var err error
|
||||
// Preserve the original case for titles etc.
|
||||
p.unnormalized, err = pp.doParse(component, s, pp.newPath(component))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -190,26 +195,23 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if len(p.identifiers) > 0 {
|
||||
isContentComponent := p.component == files.ComponentFolderContent || p.component == files.ComponentFolderArchetypes
|
||||
isContent := isContentComponent && pp.IsContentExt(p.Ext())
|
||||
isContentComponent := p.component == files.ComponentFolderContent || p.component == files.ComponentFolderArchetypes
|
||||
isContent := isContentComponent && files.IsContentExt(p.Ext())
|
||||
|
||||
if isContent {
|
||||
id := p.identifiers[len(p.identifiers)-1]
|
||||
b := p.s[p.posContainerHigh : id.Low-1]
|
||||
if isContent {
|
||||
switch b {
|
||||
case "index":
|
||||
p.bundleType = PathTypeLeaf
|
||||
case "_index":
|
||||
p.bundleType = PathTypeBranch
|
||||
default:
|
||||
p.bundleType = PathTypeContentSingle
|
||||
}
|
||||
switch b {
|
||||
case "index":
|
||||
p.bundleType = PathTypeLeaf
|
||||
case "_index":
|
||||
p.bundleType = PathTypeBranch
|
||||
default:
|
||||
p.bundleType = PathTypeContentSingle
|
||||
}
|
||||
|
||||
if slashCount == 2 && p.IsLeafBundle() {
|
||||
p.posSectionHigh = 0
|
||||
}
|
||||
} else if b == files.NameContentData && files.IsContentDataExt(p.Ext()) {
|
||||
p.bundleType = PathTypeContentData
|
||||
if slashCount == 2 && p.IsLeafBundle() {
|
||||
p.posSectionHigh = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,9 +246,6 @@ const (
|
||||
|
||||
// Branch bundles, e.g. /blog/_index.md
|
||||
PathTypeBranch
|
||||
|
||||
// Content data file, _content.gotmpl.
|
||||
PathTypeContentData
|
||||
)
|
||||
|
||||
type Path struct {
|
||||
@@ -522,6 +521,10 @@ func (p *Path) Identifiers() []string {
|
||||
return ids
|
||||
}
|
||||
|
||||
func (p *Path) IsHTML() bool {
|
||||
return files.IsHTML(p.Ext())
|
||||
}
|
||||
|
||||
func (p *Path) BundleType() PathType {
|
||||
return p.bundleType
|
||||
}
|
||||
@@ -538,10 +541,6 @@ func (p *Path) IsLeafBundle() bool {
|
||||
return p.bundleType == PathTypeLeaf
|
||||
}
|
||||
|
||||
func (p *Path) IsContentData() bool {
|
||||
return p.bundleType == PathTypeContentData
|
||||
}
|
||||
|
||||
func (p Path) ForBundleType(t PathType) *Path {
|
||||
p.bundleType = t
|
||||
return &p
|
||||
|
||||
@@ -27,9 +27,6 @@ var testParser = &PathParser{
|
||||
"no": 0,
|
||||
"en": 1,
|
||||
},
|
||||
IsContentExt: func(ext string) bool {
|
||||
return ext == "md"
|
||||
},
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
@@ -336,22 +333,6 @@ func TestParse(t *testing.T) {
|
||||
c.Assert(p.Path(), qt.Equals, "/a/b/c.txt")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Content data file gotmpl",
|
||||
"/a/b/_content.gotmpl",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Path(), qt.Equals, "/a/b/_content.gotmpl")
|
||||
c.Assert(p.Ext(), qt.Equals, "gotmpl")
|
||||
c.Assert(p.IsContentData(), qt.IsTrue)
|
||||
},
|
||||
},
|
||||
{
|
||||
"Content data file yaml",
|
||||
"/a/b/_content.yaml",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.IsContentData(), qt.IsFalse)
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
c.Run(test.name, func(c *qt.C) {
|
||||
|
||||
@@ -24,9 +24,6 @@ func (p P[T]) And(ps ...P[T]) P[T] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if p == nil {
|
||||
return true
|
||||
}
|
||||
return p(v)
|
||||
}
|
||||
}
|
||||
@@ -39,9 +36,6 @@ func (p P[T]) Or(ps ...P[T]) P[T] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if p == nil {
|
||||
return false
|
||||
}
|
||||
return p(v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RunEvery runs a function at intervals defined by the function itself.
|
||||
// Functions can be added and removed while running.
|
||||
type RunEvery struct {
|
||||
// Any error returned from the function will be passed to this function.
|
||||
HandleError func(string, error)
|
||||
|
||||
// If set, the function will be run immediately.
|
||||
RunImmediately bool
|
||||
|
||||
// The named functions to run.
|
||||
funcs map[string]*Func
|
||||
|
||||
mu sync.Mutex
|
||||
started bool
|
||||
closed bool
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
type Func struct {
|
||||
// The shortest interval between each run.
|
||||
IntervalLow time.Duration
|
||||
|
||||
// The longest interval between each run.
|
||||
IntervalHigh time.Duration
|
||||
|
||||
// The function to run.
|
||||
F func(interval time.Duration) (time.Duration, error)
|
||||
|
||||
interval time.Duration
|
||||
last time.Time
|
||||
}
|
||||
|
||||
func (r *RunEvery) Start() error {
|
||||
if r.started {
|
||||
return nil
|
||||
}
|
||||
|
||||
r.started = true
|
||||
r.quit = make(chan struct{})
|
||||
|
||||
go func() {
|
||||
if r.RunImmediately {
|
||||
r.run()
|
||||
}
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-r.quit:
|
||||
return
|
||||
case <-ticker.C:
|
||||
r.run()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close stops the RunEvery from running.
|
||||
func (r *RunEvery) Close() error {
|
||||
if r.closed {
|
||||
return nil
|
||||
}
|
||||
r.closed = true
|
||||
if r.quit != nil {
|
||||
close(r.quit)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add adds a function to the RunEvery.
|
||||
func (r *RunEvery) Add(name string, f Func) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.funcs == nil {
|
||||
r.funcs = make(map[string]*Func)
|
||||
}
|
||||
if f.IntervalLow == 0 {
|
||||
f.IntervalLow = 500 * time.Millisecond
|
||||
}
|
||||
if f.IntervalHigh <= f.IntervalLow {
|
||||
f.IntervalHigh = 20 * time.Second
|
||||
}
|
||||
|
||||
start := f.IntervalHigh / 3
|
||||
if start < f.IntervalLow {
|
||||
start = f.IntervalLow
|
||||
}
|
||||
f.interval = start
|
||||
f.last = time.Now()
|
||||
|
||||
r.funcs[name] = &f
|
||||
}
|
||||
|
||||
// Remove removes a function from the RunEvery.
|
||||
func (r *RunEvery) Remove(name string) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
delete(r.funcs, name)
|
||||
}
|
||||
|
||||
// Has returns whether the RunEvery has a function with the given name.
|
||||
func (r *RunEvery) Has(name string) bool {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
_, found := r.funcs[name]
|
||||
return found
|
||||
}
|
||||
|
||||
func (r *RunEvery) run() {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
for name, f := range r.funcs {
|
||||
if time.Now().Before(f.last.Add(f.interval)) {
|
||||
continue
|
||||
}
|
||||
f.last = time.Now()
|
||||
interval, err := f.F(f.interval)
|
||||
if err != nil && r.HandleError != nil {
|
||||
r.HandleError(name, err)
|
||||
}
|
||||
|
||||
if interval < f.IntervalLow {
|
||||
interval = f.IntervalLow
|
||||
}
|
||||
|
||||
if interval > f.IntervalHigh {
|
||||
interval = f.IntervalHigh
|
||||
}
|
||||
f.interval = interval
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package types
|
||||
|
||||
import "sync"
|
||||
|
||||
type Closer interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
type CloseAdder interface {
|
||||
Add(Closer)
|
||||
}
|
||||
|
||||
type Closers struct {
|
||||
mu sync.Mutex
|
||||
cs []Closer
|
||||
}
|
||||
|
||||
func (cs *Closers) Add(c Closer) {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
cs.cs = append(cs.cs, c)
|
||||
}
|
||||
|
||||
func (cs *Closers) Close() error {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
for _, c := range cs.cs {
|
||||
c.Close()
|
||||
}
|
||||
|
||||
cs.cs = cs.cs[:0]
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -120,27 +120,3 @@ var InvocationCounter atomic.Int64
|
||||
func NewBool(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
// PrintableValueProvider is implemented by types that can provide a printable value.
|
||||
type PrintableValueProvider interface {
|
||||
PrintableValue() any
|
||||
}
|
||||
|
||||
var _ PrintableValueProvider = Result[any]{}
|
||||
|
||||
// Result is a generic result type.
|
||||
type Result[T any] struct {
|
||||
// The result value.
|
||||
Value T
|
||||
|
||||
// The error value.
|
||||
Err error
|
||||
}
|
||||
|
||||
// PrintableValue returns the value or panics if there is an error.
|
||||
func (r Result[T]) PrintableValue() any {
|
||||
if r.Err != nil {
|
||||
panic(r.Err)
|
||||
}
|
||||
return r.Value
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/cache/filecache"
|
||||
"github.com/gohugoio/hugo/cache/httpcache"
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
@@ -120,10 +119,6 @@ type Config struct {
|
||||
// <docsmeta>{"identifiers": ["caches"] }</docsmeta>
|
||||
Caches filecache.Configs `mapstructure:"-"`
|
||||
|
||||
// The httpcache configuration section contains HTTP-cache-related configuration options.
|
||||
// <docsmeta>{"identifiers": ["httpcache"] }</docsmeta>
|
||||
HTTPCache httpcache.Config `mapstructure:"-"`
|
||||
|
||||
// The markup configuration section contains markup-related configuration options.
|
||||
// <docsmeta>{"identifiers": ["markup"] }</docsmeta>
|
||||
Markup markup_config.Config `mapstructure:"-"`
|
||||
@@ -179,12 +174,6 @@ type Config struct {
|
||||
// Server configuration.
|
||||
Server config.Server `mapstructure:"-"`
|
||||
|
||||
// Pagination configuration.
|
||||
Pagination config.Pagination `mapstructure:"-"`
|
||||
|
||||
// Page configuration.
|
||||
Page config.PageConfig `mapstructure:"-"`
|
||||
|
||||
// Privacy configuration.
|
||||
Privacy privacy.Config `mapstructure:"-"`
|
||||
|
||||
@@ -318,13 +307,9 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
|
||||
}
|
||||
}
|
||||
|
||||
for i, s := range c.IgnoreLogs {
|
||||
c.IgnoreLogs[i] = strings.ToLower(s)
|
||||
}
|
||||
|
||||
ignoredLogIDs := make(map[string]bool)
|
||||
for _, err := range c.IgnoreLogs {
|
||||
ignoredLogIDs[err] = true
|
||||
ignoredLogIDs[strings.ToLower(err)] = true
|
||||
}
|
||||
|
||||
baseURL, err := urls.NewBaseURLFromString(c.BaseURL)
|
||||
@@ -374,22 +359,6 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
|
||||
}
|
||||
}
|
||||
|
||||
httpCache, err := c.HTTPCache.Compile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Legacy paginate values.
|
||||
if c.Paginate != 0 {
|
||||
hugo.Deprecate("site config key paginate", "Use pagination.pagerSize instead.", "v0.128.0")
|
||||
c.Pagination.PagerSize = c.Paginate
|
||||
}
|
||||
|
||||
if c.PaginatePath != "" {
|
||||
hugo.Deprecate("site config key paginatePath", "Use pagination.path instead.", "v0.128.0")
|
||||
c.Pagination.Path = c.PaginatePath
|
||||
}
|
||||
|
||||
c.C = &ConfigCompiled{
|
||||
Timeout: timeout,
|
||||
BaseURL: baseURL,
|
||||
@@ -398,14 +367,12 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
|
||||
DisabledLanguages: disabledLangs,
|
||||
IgnoredLogs: ignoredLogIDs,
|
||||
KindOutputFormats: kindOutputFormats,
|
||||
ContentTypes: media.DefaultContentTypes.FromTypes(c.MediaTypes.Config),
|
||||
CreateTitle: helpers.GetTitleFunc(c.TitleCaseStyle),
|
||||
IsUglyURLSection: isUglyURL,
|
||||
IgnoreFile: ignoreFile,
|
||||
SegmentFilter: c.Segments.Config.Get(func(s string) { logger.Warnf("Render segment %q not found in configuration", s) }, c.RootConfig.RenderSegments...),
|
||||
MainSections: c.MainSections,
|
||||
Clock: clock,
|
||||
HTTPCache: httpCache,
|
||||
transientErr: transientErr,
|
||||
}
|
||||
|
||||
@@ -435,7 +402,6 @@ type ConfigCompiled struct {
|
||||
BaseURLLiveReload urls.BaseURL
|
||||
ServerInterface string
|
||||
KindOutputFormats map[string]output.Formats
|
||||
ContentTypes media.ContentTypes
|
||||
DisabledKinds map[string]bool
|
||||
DisabledLanguages map[string]bool
|
||||
IgnoredLogs map[string]bool
|
||||
@@ -445,7 +411,6 @@ type ConfigCompiled struct {
|
||||
SegmentFilter segments.SegmentFilter
|
||||
MainSections []string
|
||||
Clock time.Time
|
||||
HTTPCache httpcache.ConfigCompiled
|
||||
|
||||
// This is set to the last transient error found during config compilation.
|
||||
// With themes/modules we compute the configuration in multiple passes, and
|
||||
@@ -578,11 +543,9 @@ type RootConfig struct {
|
||||
HasCJKLanguage bool
|
||||
|
||||
// The default number of pages per page when paginating.
|
||||
// Deprecated: Use the Pagination struct.
|
||||
Paginate int
|
||||
|
||||
// The path to use when creating pagination URLs, e.g. "page" in /page/2/.
|
||||
// Deprecated: Use the Pagination struct.
|
||||
PaginatePath string
|
||||
|
||||
// Whether to pluralize default list titles.
|
||||
@@ -796,7 +759,7 @@ func (c *Configs) Init() error {
|
||||
c.Languages = languages
|
||||
c.LanguagesDefaultFirst = languagesDefaultFirst
|
||||
|
||||
c.ContentPathParser = &paths.PathParser{LanguageIndex: languagesDefaultFirst.AsIndexSet(), IsLangDisabled: c.Base.IsLangDisabled, IsContentExt: c.Base.C.ContentTypes.IsContentSuffix}
|
||||
c.ContentPathParser = &paths.PathParser{LanguageIndex: languagesDefaultFirst.AsIndexSet(), IsLangDisabled: c.Base.IsLangDisabled}
|
||||
|
||||
c.configLangs = make([]config.AllProvider, len(c.Languages))
|
||||
for i, l := range c.LanguagesDefaultFirst {
|
||||
|
||||
@@ -84,103 +84,3 @@ logPathWarnings = true
|
||||
b.Assert(conf.PrintI18nWarnings, qt.Equals, true)
|
||||
b.Assert(conf.PrintPathWarnings, qt.Equals, true)
|
||||
}
|
||||
|
||||
func TestRedefineContentTypes(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
[mediaTypes]
|
||||
[mediaTypes."text/html"]
|
||||
suffixes = ["html", "xhtml"]
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
conf := b.H.Configs.Base
|
||||
contentTypes := conf.C.ContentTypes
|
||||
|
||||
b.Assert(contentTypes.HTML.Suffixes(), qt.DeepEquals, []string{"html", "xhtml"})
|
||||
b.Assert(contentTypes.Markdown.Suffixes(), qt.DeepEquals, []string{"md", "mdown", "markdown"})
|
||||
}
|
||||
|
||||
func TestPaginationConfigOld(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
[languages.en]
|
||||
weight = 1
|
||||
paginatePath = "page-en"
|
||||
|
||||
[languages.de]
|
||||
weight = 2
|
||||
paginatePath = "page-de"
|
||||
paginate = 20
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
confEn := b.H.Sites[0].Conf.Pagination()
|
||||
confDe := b.H.Sites[1].Conf.Pagination()
|
||||
|
||||
b.Assert(confEn.Path, qt.Equals, "page-en")
|
||||
b.Assert(confEn.PagerSize, qt.Equals, 10)
|
||||
b.Assert(confDe.Path, qt.Equals, "page-de")
|
||||
b.Assert(confDe.PagerSize, qt.Equals, 20)
|
||||
}
|
||||
|
||||
func TestPaginationConfigNew(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
[languages.en]
|
||||
weight = 1
|
||||
[languages.en.pagination]
|
||||
pagerSize = 20
|
||||
[languages.de]
|
||||
weight = 2
|
||||
[languages.de.pagination]
|
||||
path = "page-de"
|
||||
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
confEn := b.H.Sites[0].Conf.Pagination()
|
||||
confDe := b.H.Sites[1].Conf.Pagination()
|
||||
|
||||
b.Assert(confEn.Path, qt.Equals, "page")
|
||||
b.Assert(confEn.PagerSize, qt.Equals, 20)
|
||||
b.Assert(confDe.Path, qt.Equals, "page-de")
|
||||
b.Assert(confDe.PagerSize, qt.Equals, 10)
|
||||
}
|
||||
|
||||
func TestPaginationConfigDisableAliases(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["taxonomy", "term"]
|
||||
[pagination]
|
||||
disableAliases = true
|
||||
pagerSize = 2
|
||||
-- layouts/_default/list.html --
|
||||
{{ $paginator := .Paginate site.RegularPages }}
|
||||
{{ template "_internal/pagination.html" . }}
|
||||
{{ range $paginator.Pages }}
|
||||
{{ .Title }}
|
||||
{{ end }}
|
||||
-- content/p1.md --
|
||||
---
|
||||
title: "p1"
|
||||
---
|
||||
-- content/p2.md --
|
||||
---
|
||||
title: "p2"
|
||||
---
|
||||
-- content/p3.md --
|
||||
---
|
||||
title: "p3"
|
||||
---
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileExists("public/page/1/index.html", false)
|
||||
b.AssertFileContent("public/page/2/index.html", "pagination-default")
|
||||
}
|
||||
|
||||
@@ -18,8 +18,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/cache/filecache"
|
||||
|
||||
"github.com/gohugoio/hugo/cache/httpcache"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
@@ -98,18 +96,6 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
return err
|
||||
},
|
||||
},
|
||||
"httpcache": {
|
||||
key: "httpcache",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.HTTPCache, err = httpcache.DecodeConfig(p.bcfg, p.p.GetStringMap(d.key))
|
||||
if p.c.IgnoreCache {
|
||||
p.c.HTTPCache.Cache.For.Excludes = []string{"**"}
|
||||
p.c.HTTPCache.Cache.For.Includes = []string{}
|
||||
}
|
||||
return err
|
||||
},
|
||||
},
|
||||
"build": {
|
||||
key: "build",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
@@ -327,41 +313,6 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
return err
|
||||
},
|
||||
},
|
||||
"page": {
|
||||
key: "page",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
p.c.Page = config.PageConfig{
|
||||
NextPrevSortOrder: "desc",
|
||||
NextPrevInSectionSortOrder: "desc",
|
||||
}
|
||||
if p.p.IsSet(d.key) {
|
||||
if err := mapstructure.WeakDecode(p.p.Get(d.key), &p.c.Page); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
getCompiler: func(c *Config) configCompiler {
|
||||
return &c.Page
|
||||
},
|
||||
},
|
||||
"pagination": {
|
||||
key: "pagination",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
p.c.Pagination = config.Pagination{
|
||||
PagerSize: 10,
|
||||
Path: "page",
|
||||
}
|
||||
if p.p.IsSet(d.key) {
|
||||
if err := mapstructure.WeakDecode(p.p.Get(d.key), &p.c.Pagination); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
"privacy": {
|
||||
key: "privacy",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
|
||||
@@ -71,9 +71,6 @@ func (c ConfigLanguage) Environment() string {
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) IsMultihost() bool {
|
||||
if len(c.m.Languages)-len(c.config.C.DisabledLanguages) <= 1 {
|
||||
return false
|
||||
}
|
||||
return c.m.IsMultihost
|
||||
}
|
||||
|
||||
@@ -144,10 +141,6 @@ func (c ConfigLanguage) NewIdentityManager(name string) identity.Manager {
|
||||
return identity.NewManager(name)
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) ContentTypes() config.ContentTypesProvider {
|
||||
return c.config.C.ContentTypes
|
||||
}
|
||||
|
||||
// GetConfigSection is mostly used in tests. The switch statement isn't complete, but what's in use.
|
||||
func (c ConfigLanguage) GetConfigSection(s string) any {
|
||||
switch s {
|
||||
@@ -173,8 +166,6 @@ func (c ConfigLanguage) GetConfigSection(s string) any {
|
||||
return c.m.Modules
|
||||
case "deployment":
|
||||
return c.config.Deployment
|
||||
case "httpCacheCompiled":
|
||||
return c.config.C.HTTPCache
|
||||
default:
|
||||
panic("not implemented: " + s)
|
||||
}
|
||||
@@ -248,8 +239,12 @@ func (c ConfigLanguage) CreateTitle(s string) string {
|
||||
return c.config.C.CreateTitle(s)
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) Pagination() config.Pagination {
|
||||
return c.config.Pagination
|
||||
func (c ConfigLanguage) Paginate() int {
|
||||
return c.config.Paginate
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) PaginatePath() string {
|
||||
return c.config.PaginatePath
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) StaticDirs() []string {
|
||||
|
||||
@@ -194,8 +194,8 @@ func (l configLoader) applyDefaultConfig() error {
|
||||
"footnoteAnchorPrefix": "",
|
||||
"footnoteReturnLinkContents": "",
|
||||
"newContentEditor": "",
|
||||
"paginate": 0, // Moved into the paginator struct in Hugo v0.128.0.
|
||||
"paginatePath": "", // Moved into the paginator struct in Hugo v0.128.0.
|
||||
"paginate": 10,
|
||||
"paginatePath": "page",
|
||||
"summaryLength": 70,
|
||||
"rssLimit": -1,
|
||||
"sectionPagesMenu": "",
|
||||
@@ -458,7 +458,6 @@ func (l *configLoader) loadModules(configs *Configs) (modules.ModulesConfig, *mo
|
||||
conf := configs.Base
|
||||
workingDir := bcfg.WorkingDir
|
||||
themesDir := bcfg.ThemesDir
|
||||
publishDir := bcfg.PublishDir
|
||||
|
||||
cfg := configs.LoadingInfo.Cfg
|
||||
|
||||
@@ -467,7 +466,7 @@ func (l *configLoader) loadModules(configs *Configs) (modules.ModulesConfig, *mo
|
||||
ignoreVendor, _ = hglob.GetGlob(hglob.NormalizePath(s))
|
||||
}
|
||||
|
||||
ex := hexec.New(conf.Security, workingDir)
|
||||
ex := hexec.New(conf.Security)
|
||||
|
||||
hook := func(m *modules.ModulesConfig) error {
|
||||
for _, tc := range m.AllModules {
|
||||
@@ -493,7 +492,6 @@ func (l *configLoader) loadModules(configs *Configs) (modules.ModulesConfig, *mo
|
||||
HookBeforeFinalize: hook,
|
||||
WorkingDir: workingDir,
|
||||
ThemesDir: themesDir,
|
||||
PublishDir: publishDir,
|
||||
Environment: l.Environment,
|
||||
CacheDir: conf.Caches.CacheDirModules(),
|
||||
ModuleConfig: conf.Module,
|
||||
|
||||
+4
-27
@@ -98,6 +98,10 @@ type BuildConfig struct {
|
||||
// One of never, fallback, always. Default is fallback
|
||||
UseResourceCacheWhen string
|
||||
|
||||
// When enabled, will duplicate bundled resource files across languages that
|
||||
// doesn't have a translated version.
|
||||
DuplicateResourceFiles bool
|
||||
|
||||
// When enabled, will collect and write a hugo_stats.json with some build
|
||||
// related aggregated data (e.g. CSS class names).
|
||||
// Note that this was a bool <= v0.115.0.
|
||||
@@ -410,30 +414,3 @@ func DecodeServer(cfg Provider) (Server, error) {
|
||||
|
||||
return *s, nil
|
||||
}
|
||||
|
||||
// Pagination configures the pagination behavior.
|
||||
type Pagination struct {
|
||||
// Default number of elements per pager in pagination.
|
||||
PagerSize int
|
||||
|
||||
// The path element used during pagination.
|
||||
Path string
|
||||
|
||||
// Whether to disable generation of alias for the first pagination page.
|
||||
DisableAliases bool
|
||||
}
|
||||
|
||||
// PageConfig configures the behavior of pages.
|
||||
type PageConfig struct {
|
||||
// Sort order for Page.Next and Page.Prev. Default "desc" (the default page sort order in Hugo).
|
||||
NextPrevSortOrder string
|
||||
|
||||
// Sort order for Page.NextInSection and Page.PrevInSection. Default "desc".
|
||||
NextPrevInSectionSortOrder string
|
||||
}
|
||||
|
||||
func (c *PageConfig) CompileConfig(loggers.Logger) error {
|
||||
c.NextPrevInSectionSortOrder = strings.ToLower(c.NextPrevInSectionSortOrder)
|
||||
c.NextPrevSortOrder = strings.ToLower(c.NextPrevSortOrder)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ type AllProvider interface {
|
||||
Dirs() CommonDirs
|
||||
Quiet() bool
|
||||
DirsBase() CommonDirs
|
||||
ContentTypes() ContentTypesProvider
|
||||
GetConfigSection(string) any
|
||||
GetConfig() any
|
||||
CanonifyURLs() bool
|
||||
@@ -52,7 +51,8 @@ type AllProvider interface {
|
||||
DefaultContentLanguageInSubdir() bool
|
||||
IsLangDisabled(string) bool
|
||||
SummaryLength() int
|
||||
Pagination() Pagination
|
||||
Paginate() int
|
||||
PaginatePath() string
|
||||
BuildExpired() bool
|
||||
BuildFuture() bool
|
||||
BuildDrafts() bool
|
||||
@@ -75,15 +75,6 @@ type AllProvider interface {
|
||||
EnableEmoji() bool
|
||||
}
|
||||
|
||||
// We cannot import the media package as that would create a circular dependency.
|
||||
// This interface defineds a sub set of what media.ContentTypes provides.
|
||||
type ContentTypesProvider interface {
|
||||
IsContentSuffix(suffix string) bool
|
||||
IsContentFile(filename string) bool
|
||||
IsIndexContentFile(filename string) bool
|
||||
IsHTMLSuffix(suffix string) bool
|
||||
}
|
||||
|
||||
// Provider provides the configuration settings for Hugo.
|
||||
type Provider interface {
|
||||
GetString(key string) string
|
||||
|
||||
+2
-2
@@ -16,13 +16,13 @@ package config
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
)
|
||||
|
||||
func DecodeNamespace[S, C any](configSource any, buildConfig func(any) (C, any, error)) (*ConfigNamespace[S, C], error) {
|
||||
// Calculate the hash of the input (not including any defaults applied later).
|
||||
// This allows us to introduce new config options without breaking the hash.
|
||||
h := hashing.HashString(configSource)
|
||||
h := identity.HashString(configSource)
|
||||
|
||||
// Build the config
|
||||
c, ext, err := buildConfig(configSource)
|
||||
|
||||
@@ -43,7 +43,7 @@ func TestNamespace(t *testing.T) {
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(ns, qt.Not(qt.IsNil))
|
||||
c.Assert(ns.SourceStructure, qt.DeepEquals, map[string]interface{}{"foo": "bar"})
|
||||
c.Assert(ns.SourceHash, qt.Equals, "1450430416588600409")
|
||||
c.Assert(ns.SourceHash, qt.Equals, "14368731254619220105")
|
||||
c.Assert(ns.Config, qt.DeepEquals, &tstNsExt{Foo: "bar"})
|
||||
c.Assert(ns.Signature(), qt.DeepEquals, []*tstNsExt(nil))
|
||||
}
|
||||
|
||||
@@ -37,10 +37,8 @@ var DefaultConfig = Config{
|
||||
Allow: MustNewWhitelist(
|
||||
"^(dart-)?sass(-embedded)?$", // sass, dart-sass, dart-sass-embedded.
|
||||
"^go$", // for Go Modules
|
||||
"^git$", // For Git info
|
||||
"^npx$", // used by all Node tools (Babel, PostCSS).
|
||||
"^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 enableInlineShortcodes = false\n\n [security.exec]\n allow = ['^(dart-)?sass(-embedded)?$', '^go$', '^git$', '^npx$', '^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)$']\n\n [security.funcs]\n getenv = ['^HUGO_', '^CI$']\n\n [security.http]\n methods = ['(?i)GET|POST']\n urls = ['.*']",
|
||||
"[security]\n enableInlineShortcodes = false\n\n [security.exec]\n allow = ['^(dart-)?sass(-embedded)?$', '^go$', '^npx$', '^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)$']\n\n [security.funcs]\n getenv = ['^HUGO_', '^CI$']\n\n [security.http]\n methods = ['(?i)GET|POST']\n urls = ['.*']",
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -29,6 +29,8 @@ import (
|
||||
"github.com/gohugoio/hugo/common/hstrings"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
@@ -96,7 +98,7 @@ func NewContent(h *hugolib.HugoSites, kind, targetPath string, force bool) error
|
||||
return "", fmt.Errorf("failed to resolve %q to an archetype template", targetPath)
|
||||
}
|
||||
|
||||
if !h.Conf.ContentTypes().IsContentFile(b.targetPath) {
|
||||
if !files.IsContentFile(b.targetPath) {
|
||||
return "", fmt.Errorf("target path %q is not a known content format", b.targetPath)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
baseURL = 'https://example.org/'
|
||||
languageCode = 'en-US'
|
||||
languageCode = 'en-us'
|
||||
title = 'My New Hugo Site'
|
||||
|
||||
[[menus.main]]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ or site.Language.LanguageCode }}" dir="{{ or site.Language.LanguageDirection `ltr` }}">
|
||||
<html lang="{{ or site.Language.LanguageCode site.Language.Lang }}" dir="{{ or site.Language.LanguageDirection `ltr` }}">
|
||||
<head>
|
||||
{{ partial "head.html" . }}
|
||||
</head>
|
||||
|
||||
+2
-20
@@ -133,14 +133,10 @@ func (d *Deployer) Deploy(ctx context.Context) error {
|
||||
|
||||
// Load local files from the source directory.
|
||||
var include, exclude glob.Glob
|
||||
var mappath func(string) string
|
||||
if d.target != nil {
|
||||
include, exclude = d.target.IncludeGlob, d.target.ExcludeGlob
|
||||
if d.target.StripIndexHTML {
|
||||
mappath = stripIndexHTML
|
||||
}
|
||||
}
|
||||
local, err := d.walkLocal(d.localFs, d.cfg.Matchers, include, exclude, d.mediaTypes, mappath)
|
||||
local, err := d.walkLocal(d.localFs, d.cfg.Matchers, include, exclude, d.mediaTypes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -487,7 +483,7 @@ func knownHiddenDirectory(name string) bool {
|
||||
|
||||
// walkLocal walks the source directory and returns a flat list of files,
|
||||
// using localFile.SlashPath as the map keys.
|
||||
func (d *Deployer) walkLocal(fs afero.Fs, matchers []*deployconfig.Matcher, include, exclude glob.Glob, mediaTypes media.Types, mappath func(string) string) (map[string]*localFile, error) {
|
||||
func (d *Deployer) walkLocal(fs afero.Fs, matchers []*deployconfig.Matcher, include, exclude glob.Glob, mediaTypes media.Types) (map[string]*localFile, error) {
|
||||
retval := map[string]*localFile{}
|
||||
err := afero.Walk(fs, "", func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
@@ -533,11 +529,6 @@ func (d *Deployer) walkLocal(fs afero.Fs, matchers []*deployconfig.Matcher, incl
|
||||
break
|
||||
}
|
||||
}
|
||||
// Apply any additional modifications to the local path, to map it to
|
||||
// the remote path.
|
||||
if mappath != nil {
|
||||
slashpath = mappath(slashpath)
|
||||
}
|
||||
lf, err := newLocalFile(fs, path, slashpath, m, mediaTypes)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -551,15 +542,6 @@ func (d *Deployer) walkLocal(fs afero.Fs, matchers []*deployconfig.Matcher, incl
|
||||
return retval, nil
|
||||
}
|
||||
|
||||
// stripIndexHTML remaps keys matching "<dir>/index.html" to "<dir>/".
|
||||
func stripIndexHTML(slashpath string) string {
|
||||
const suffix = "/index.html"
|
||||
if strings.HasSuffix(slashpath, suffix) {
|
||||
return slashpath[:len(slashpath)-len(suffix)+1]
|
||||
}
|
||||
return slashpath
|
||||
}
|
||||
|
||||
// walkRemote walks the target bucket and returns a flat list.
|
||||
func (d *Deployer) walkRemote(ctx context.Context, bucket *blob.Bucket, include, exclude glob.Glob) (map[string]*blob.ListObject, error) {
|
||||
retval := map[string]*blob.ListObject{}
|
||||
|
||||
+3
-66
@@ -216,9 +216,8 @@ func TestFindDiffs(t *testing.T) {
|
||||
|
||||
func TestWalkLocal(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
Given []string
|
||||
Expect []string
|
||||
MapPath func(string) string
|
||||
Given []string
|
||||
Expect []string
|
||||
}{
|
||||
"Empty": {
|
||||
Given: []string{},
|
||||
@@ -236,11 +235,6 @@ func TestWalkLocal(t *testing.T) {
|
||||
Given: []string{"file.txt", ".hidden_dir/file.txt", ".well-known/file.txt"},
|
||||
Expect: []string{"file.txt", ".well-known/file.txt"},
|
||||
},
|
||||
"StripIndexHTML": {
|
||||
Given: []string{"index.html", "file.txt", "dir/index.html", "dir/file.txt"},
|
||||
Expect: []string{"index.html", "file.txt", "dir/", "dir/file.txt"},
|
||||
MapPath: stripIndexHTML,
|
||||
},
|
||||
}
|
||||
|
||||
for desc, tc := range tests {
|
||||
@@ -260,7 +254,7 @@ func TestWalkLocal(t *testing.T) {
|
||||
}
|
||||
}
|
||||
d := newDeployer()
|
||||
if got, err := d.walkLocal(fs, nil, nil, nil, media.DefaultTypes, tc.MapPath); err != nil {
|
||||
if got, err := d.walkLocal(fs, nil, nil, nil, media.DefaultTypes); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
expect := map[string]any{}
|
||||
@@ -280,63 +274,6 @@ func TestWalkLocal(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripIndexHTML(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
Input string
|
||||
Output string
|
||||
}{
|
||||
"Unmapped": {Input: "normal_file.txt", Output: "normal_file.txt"},
|
||||
"Stripped": {Input: "directory/index.html", Output: "directory/"},
|
||||
"NoSlash": {Input: "prefix_index.html", Output: "prefix_index.html"},
|
||||
"Root": {Input: "index.html", Output: "index.html"},
|
||||
}
|
||||
for desc, tc := range tests {
|
||||
t.Run(desc, func(t *testing.T) {
|
||||
got := stripIndexHTML(tc.Input)
|
||||
if got != tc.Output {
|
||||
t.Errorf("got %q, expect %q", got, tc.Output)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripIndexHTMLMatcher(t *testing.T) {
|
||||
// StripIndexHTML should not affect matchers.
|
||||
fs := afero.NewMemMapFs()
|
||||
if err := fs.Mkdir("dir", 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, name := range []string{"index.html", "dir/index.html", "file.txt"} {
|
||||
if fd, err := fs.Create(name); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
fd.Close()
|
||||
}
|
||||
}
|
||||
d := newDeployer()
|
||||
const pattern = `\.html$`
|
||||
matcher := &deployconfig.Matcher{Pattern: pattern, Gzip: true, Re: regexp.MustCompile(pattern)}
|
||||
if got, err := d.walkLocal(fs, []*deployconfig.Matcher{matcher}, nil, nil, media.DefaultTypes, stripIndexHTML); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
for _, name := range []string{"index.html", "dir/"} {
|
||||
lf := got[name]
|
||||
if lf == nil {
|
||||
t.Errorf("missing file %q", name)
|
||||
} else if lf.matcher == nil {
|
||||
t.Errorf("file %q has nil matcher, expect %q", name, pattern)
|
||||
}
|
||||
}
|
||||
const name = "file.txt"
|
||||
lf := got[name]
|
||||
if lf == nil {
|
||||
t.Errorf("missing file %q", name)
|
||||
} else if lf.matcher != nil {
|
||||
t.Errorf("file %q has matcher %q, expect nil", name, lf.matcher.Pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalFile(t *testing.T) {
|
||||
const (
|
||||
content = "hello world!"
|
||||
|
||||
@@ -69,11 +69,6 @@ type Target struct {
|
||||
// Parsed versions of Include/Exclude.
|
||||
IncludeGlob glob.Glob `json:"-"`
|
||||
ExcludeGlob glob.Glob `json:"-"`
|
||||
|
||||
// If true, any local path matching <dir>/index.html will be mapped to the
|
||||
// remote path <dir>/. This does not affect the top-level index.html file,
|
||||
// since that would result in an empty path.
|
||||
StripIndexHTML bool
|
||||
}
|
||||
|
||||
func (tgt *Target) ParseIncludeExclude() error {
|
||||
|
||||
Vendored
+38
-78
@@ -15,15 +15,11 @@ import (
|
||||
"github.com/gohugoio/hugo/cache/filecache"
|
||||
"github.com/gohugoio/hugo/common/hexec"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/config/allconfig"
|
||||
"github.com/gohugoio/hugo/config/security"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/internal/warpc"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
"github.com/gohugoio/hugo/resources/postpub"
|
||||
@@ -89,15 +85,11 @@ type Deps struct {
|
||||
BuildEndListeners *Listeners
|
||||
|
||||
// Resources that gets closed when the build is done or the server shuts down.
|
||||
BuildClosers *types.Closers
|
||||
BuildClosers *Closers
|
||||
|
||||
// This is common/global for all sites.
|
||||
BuildState *BuildState
|
||||
|
||||
// Holds RPC dispatchers for Katex etc.
|
||||
// TODO(bep) rethink this re. a plugin setup, but this will have to do for now.
|
||||
WasmDispatchers *warpc.Dispatchers
|
||||
|
||||
*globalErrHandler
|
||||
}
|
||||
|
||||
@@ -141,15 +133,6 @@ func (d *Deps) Init() error {
|
||||
if d.BuildState == nil {
|
||||
d.BuildState = &BuildState{}
|
||||
}
|
||||
if d.BuildState.DeferredExecutions == nil {
|
||||
if d.BuildState.DeferredExecutionsGroupedByRenderingContext == nil {
|
||||
d.BuildState.DeferredExecutionsGroupedByRenderingContext = make(map[tpl.RenderingContext]*DeferredExecutions)
|
||||
}
|
||||
d.BuildState.DeferredExecutions = &DeferredExecutions{
|
||||
Executions: maps.NewCache[string, *tpl.DeferredExecution](),
|
||||
FilenamesWithPostPrefix: maps.NewCache[string, bool](),
|
||||
}
|
||||
}
|
||||
|
||||
if d.BuildStartListeners == nil {
|
||||
d.BuildStartListeners = &Listeners{}
|
||||
@@ -160,7 +143,7 @@ func (d *Deps) Init() error {
|
||||
}
|
||||
|
||||
if d.BuildClosers == nil {
|
||||
d.BuildClosers = &types.Closers{}
|
||||
d.BuildClosers = &Closers{}
|
||||
}
|
||||
|
||||
if d.Metrics == nil && d.Conf.TemplateMetrics() {
|
||||
@@ -168,37 +151,28 @@ func (d *Deps) Init() error {
|
||||
}
|
||||
|
||||
if d.ExecHelper == nil {
|
||||
d.ExecHelper = hexec.New(d.Conf.GetConfigSection("security").(security.Config), d.Conf.WorkingDir())
|
||||
d.ExecHelper = hexec.New(d.Conf.GetConfigSection("security").(security.Config))
|
||||
}
|
||||
|
||||
if d.MemCache == nil {
|
||||
d.MemCache = dynacache.New(dynacache.Options{Watching: d.Conf.Watching(), Log: d.Log})
|
||||
d.MemCache = dynacache.New(dynacache.Options{Running: d.Conf.Running(), Log: d.Log})
|
||||
}
|
||||
|
||||
if d.PathSpec == nil {
|
||||
hashBytesReceiverFunc := func(name string, match []byte) {
|
||||
s := string(match)
|
||||
switch s {
|
||||
case postpub.PostProcessPrefix:
|
||||
d.BuildState.AddFilenameWithPostPrefix(name)
|
||||
case tpl.HugoDeferredTemplatePrefix:
|
||||
d.BuildState.DeferredExecutions.FilenamesWithPostPrefix.Set(name, true)
|
||||
hashBytesReceiverFunc := func(name string, match bool) {
|
||||
if !match {
|
||||
return
|
||||
}
|
||||
d.BuildState.AddFilenameWithPostPrefix(name)
|
||||
}
|
||||
|
||||
// Skip binary files.
|
||||
mediaTypes := d.Conf.GetConfigSection("mediaTypes").(media.Types)
|
||||
hashBytesShouldCheck := func(name string) bool {
|
||||
hashBytesSHouldCheck := func(name string) bool {
|
||||
ext := strings.TrimPrefix(filepath.Ext(name), ".")
|
||||
return mediaTypes.IsTextSuffix(ext)
|
||||
}
|
||||
d.Fs.PublishDir = hugofs.NewHasBytesReceiver(
|
||||
d.Fs.PublishDir,
|
||||
hashBytesShouldCheck,
|
||||
hashBytesReceiverFunc,
|
||||
[]byte(tpl.HugoDeferredTemplatePrefix),
|
||||
[]byte(postpub.PostProcessPrefix))
|
||||
|
||||
d.Fs.PublishDir = hugofs.NewHasBytesReceiver(d.Fs.PublishDir, hashBytesSHouldCheck, hashBytesReceiverFunc, []byte(postpub.PostProcessPrefix))
|
||||
pathSpec, err := helpers.NewPathSpec(d.Fs, d.Conf, d.Log)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -234,7 +208,7 @@ func (d *Deps) Init() error {
|
||||
return fmt.Errorf("failed to create file caches from configuration: %w", err)
|
||||
}
|
||||
|
||||
resourceSpec, err := resources.NewSpec(d.PathSpec, common, fileCaches, d.MemCache, d.BuildState, d.Log, d, d.ExecHelper, d.BuildClosers, d.BuildState)
|
||||
resourceSpec, err := resources.NewSpec(d.PathSpec, common, fileCaches, d.MemCache, d.BuildState, d.Log, d, d.ExecHelper)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create resource spec: %w", err)
|
||||
}
|
||||
@@ -348,9 +322,6 @@ func (d *Deps) Close() error {
|
||||
if d.MemCache != nil {
|
||||
d.MemCache.Stop()
|
||||
}
|
||||
if d.WasmDispatchers != nil {
|
||||
d.WasmDispatchers.Close()
|
||||
}
|
||||
return d.BuildClosers.Close()
|
||||
}
|
||||
|
||||
@@ -382,9 +353,6 @@ type DepsCfg struct {
|
||||
|
||||
// i18n handling.
|
||||
TranslationProvider ResourceProvider
|
||||
|
||||
// ChangesFromBuild for changes passed back to the server/watch process.
|
||||
ChangesFromBuild chan []identity.Identity
|
||||
}
|
||||
|
||||
// BuildState are state used during a build.
|
||||
@@ -393,44 +361,9 @@ type BuildState struct {
|
||||
|
||||
mu sync.Mutex // protects state below.
|
||||
|
||||
OnSignalRebuild func(ids ...identity.Identity)
|
||||
|
||||
// A set of filenames in /public that
|
||||
// contains a post-processing prefix.
|
||||
filenamesWithPostPrefix map[string]bool
|
||||
|
||||
DeferredExecutions *DeferredExecutions
|
||||
|
||||
// Deferred executions grouped by rendering context.
|
||||
DeferredExecutionsGroupedByRenderingContext map[tpl.RenderingContext]*DeferredExecutions
|
||||
}
|
||||
|
||||
type DeferredExecutions struct {
|
||||
// A set of filenames in /public that
|
||||
// contains a post-processing prefix.
|
||||
FilenamesWithPostPrefix *maps.Cache[string, bool]
|
||||
|
||||
// Maps a placeholder to a deferred execution.
|
||||
Executions *maps.Cache[string, *tpl.DeferredExecution]
|
||||
}
|
||||
|
||||
var _ identity.SignalRebuilder = (*BuildState)(nil)
|
||||
|
||||
// StartStageRender will be called before a stage is rendered.
|
||||
func (b *BuildState) StartStageRender(stage tpl.RenderingContext) {
|
||||
}
|
||||
|
||||
// StopStageRender will be called after a stage is rendered.
|
||||
func (b *BuildState) StopStageRender(stage tpl.RenderingContext) {
|
||||
b.DeferredExecutionsGroupedByRenderingContext[stage] = b.DeferredExecutions
|
||||
b.DeferredExecutions = &DeferredExecutions{
|
||||
Executions: maps.NewCache[string, *tpl.DeferredExecution](),
|
||||
FilenamesWithPostPrefix: maps.NewCache[string, bool](),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BuildState) SignalRebuild(ids ...identity.Identity) {
|
||||
b.OnSignalRebuild(ids...)
|
||||
}
|
||||
|
||||
func (b *BuildState) AddFilenameWithPostPrefix(filename string) {
|
||||
@@ -456,3 +389,30 @@ func (b *BuildState) GetFilenamesWithPostPrefix() []string {
|
||||
func (b *BuildState) Incr() int {
|
||||
return int(atomic.AddUint64(&b.counter, uint64(1)))
|
||||
}
|
||||
|
||||
type Closer interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
type Closers struct {
|
||||
mu sync.Mutex
|
||||
cs []Closer
|
||||
}
|
||||
|
||||
func (cs *Closers) Add(c Closer) {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
cs.cs = append(cs.cs, c)
|
||||
}
|
||||
|
||||
func (cs *Closers) Close() error {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
for _, c := range cs.cs {
|
||||
c.Close()
|
||||
}
|
||||
|
||||
cs.cs = cs.cs[:0]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+25
-59
@@ -20,11 +20,13 @@
|
||||
],
|
||||
"ignoreRegExpList": [
|
||||
"# cspell: ignore fenced code blocks",
|
||||
"^(\\s*`{3,}).*[\\s\\S]*?^\\1$",
|
||||
"^(\\s*`{3,}).*[\\s\\S]*?^\\1",
|
||||
"# cspell: ignore words joined with dot",
|
||||
"\\w+\\.\\w+",
|
||||
"# cspell: ignore strings within backticks",
|
||||
"`.+`",
|
||||
"# cspell: ignore strings within single quotes",
|
||||
"'.+'",
|
||||
"# cspell: ignore strings within double quotes",
|
||||
"\".+\"",
|
||||
"# cspell: ignore strings within brackets",
|
||||
@@ -40,11 +42,14 @@
|
||||
],
|
||||
"language": "en",
|
||||
"words": [
|
||||
"antialiasing",
|
||||
"codeowners",
|
||||
"composability",
|
||||
"configurators",
|
||||
"defang",
|
||||
"deindent",
|
||||
"downscale",
|
||||
"downscaled",
|
||||
"downscaling",
|
||||
"exif",
|
||||
"geolocalized",
|
||||
@@ -52,101 +57,60 @@
|
||||
"marshal",
|
||||
"marshaling",
|
||||
"multihost",
|
||||
"multiplatfom",
|
||||
"performantly",
|
||||
"preconfigured",
|
||||
"prerendering",
|
||||
"redirection",
|
||||
"redirections",
|
||||
"shortcode",
|
||||
"shortcodes",
|
||||
"subexpression",
|
||||
"suppressible",
|
||||
"subexpressions",
|
||||
"suppressable",
|
||||
"templating",
|
||||
"transpile",
|
||||
"transpiles",
|
||||
"unmarshal",
|
||||
"unmarshaling",
|
||||
"unmarshals",
|
||||
"# ----------------------------------------------------------------------",
|
||||
"# cspell: ignore hugo terminology",
|
||||
"# ----------------------------------------------------------------------",
|
||||
"attrlink",
|
||||
"canonify",
|
||||
"codeowners",
|
||||
"dynacache",
|
||||
"eturl",
|
||||
"getenv",
|
||||
"gohugo",
|
||||
"gohugoio",
|
||||
"keyvals",
|
||||
"leftdelim",
|
||||
"linkify",
|
||||
"numworkermultiplier",
|
||||
"rightdelim",
|
||||
"shortcode",
|
||||
"stringifier",
|
||||
"struct",
|
||||
"toclevels",
|
||||
"zgotmplz",
|
||||
"unmarshaling",
|
||||
"# ----------------------------------------------------------------------",
|
||||
"# cspell: ignore foreign language words",
|
||||
"# ----------------------------------------------------------------------",
|
||||
"bezpieczeństwo",
|
||||
"blatt",
|
||||
"buch",
|
||||
"descripción",
|
||||
"dokumentation",
|
||||
"erklärungen",
|
||||
"libros",
|
||||
"mercredi",
|
||||
"miesiąc",
|
||||
"miesiąc",
|
||||
"miesiąca",
|
||||
"miesiące",
|
||||
"miesięcy",
|
||||
"miesięcy",
|
||||
"misérables",
|
||||
"mittwoch",
|
||||
"muchos",
|
||||
"novembre",
|
||||
"otro",
|
||||
"pocos",
|
||||
"produkte",
|
||||
"projekt",
|
||||
"prywatność",
|
||||
"referenz",
|
||||
"régime",
|
||||
"# ----------------------------------------------------------------------",
|
||||
"# cspell: ignore names",
|
||||
"# cspell: ignore proper nouns",
|
||||
"# ----------------------------------------------------------------------",
|
||||
"Atishay",
|
||||
"Cosette",
|
||||
"Eliott",
|
||||
"Furet",
|
||||
"Gregor",
|
||||
"Jaco",
|
||||
"Lanczos",
|
||||
"Ninke",
|
||||
"Noll",
|
||||
"Pastorius",
|
||||
"Samsa",
|
||||
"Stucki",
|
||||
"Thénardier",
|
||||
"# ----------------------------------------------------------------------",
|
||||
"# cspell: ignore operating systems and software packages",
|
||||
"# ----------------------------------------------------------------------",
|
||||
"asciidoctor",
|
||||
"brotli",
|
||||
"cifs",
|
||||
"corejs",
|
||||
"disqus",
|
||||
"docutils",
|
||||
"dpkg",
|
||||
"doas",
|
||||
"eopkg",
|
||||
"gitee",
|
||||
"gohugoio",
|
||||
"goldmark",
|
||||
"katex",
|
||||
"KaTeX",
|
||||
"kubuntu",
|
||||
"lubuntu",
|
||||
"mathjax",
|
||||
"MathJax",
|
||||
"nosql",
|
||||
"pandoc",
|
||||
"pkgin",
|
||||
@@ -155,19 +119,21 @@
|
||||
"# ----------------------------------------------------------------------",
|
||||
"# cspell: ignore miscellaneous",
|
||||
"# ----------------------------------------------------------------------",
|
||||
"achristie",
|
||||
"ddmaurier",
|
||||
"dring",
|
||||
"getenv",
|
||||
"gohugo",
|
||||
"inor",
|
||||
"jausten",
|
||||
"jdoe",
|
||||
"jsmith",
|
||||
"milli",
|
||||
"rgba",
|
||||
"rsmith",
|
||||
"stringifier",
|
||||
"struct",
|
||||
"tdewolff",
|
||||
"tjones",
|
||||
"wcag",
|
||||
"xfeff"
|
||||
"toclevels",
|
||||
"vals",
|
||||
"xfeff",
|
||||
"zgotmplz"
|
||||
]
|
||||
}
|
||||
|
||||
Generated
-15
@@ -1,15 +0,0 @@
|
||||
<svg width="150" height="36" viewBox="0 0 150 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M38.9337 13.4895H41.1147C41.4378 13.4895 41.8417 13.4895 42.1648 13.4895C42.5686 13.4895 42.8917 13.4087 43.2148 13.2472C43.5379 13.0856 43.7803 12.9241 43.9418 12.6818C44.1034 12.4394 44.2649 12.0355 44.2649 11.6317C44.2649 11.2278 44.1841 10.8239 44.0226 10.5816C43.861 10.3393 43.6187 10.0969 43.3764 10.0162C43.1341 9.85461 42.811 9.77384 42.4879 9.77384C42.1648 9.69306 41.7609 9.69306 41.4378 9.69306H39.0145V13.4895H38.9337ZM35.2181 6.46204H41.7609C42.6494 6.46204 43.4572 6.54282 44.1841 6.70437C44.9919 6.86592 45.6381 7.18902 46.2035 7.51212C46.769 7.916 47.2536 8.40065 47.5767 9.12763C47.8998 9.77384 48.0614 10.6624 48.0614 11.6317C48.0614 12.8433 47.7383 13.8934 47.0921 14.7011C46.4459 15.5089 45.5573 16.0743 44.3457 16.3166L48.6268 23.5057H44.1841L40.63 16.7205H38.7722V23.5057H35.0565V6.46204H35.2181Z" fill="#425277"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M53.3926 17.6091C53.3926 18.4976 53.6349 19.2246 54.1195 19.79C54.6042 20.3554 55.3312 20.5977 56.2197 20.5977C57.1082 20.5977 57.8352 20.3554 58.3199 19.79C58.8045 19.2246 59.0468 18.4976 59.0468 17.6091C59.0468 16.7205 58.8045 15.9935 58.3199 15.4281C57.8352 14.8627 57.1082 14.6204 56.2197 14.6204C55.3312 14.6204 54.6042 14.8627 54.1195 15.4281C53.6349 16.0743 53.3926 16.7205 53.3926 17.6091ZM49.8384 17.6091C49.8384 16.6397 50 15.832 50.3231 15.105C50.6462 14.378 51.1308 13.7318 51.6963 13.1664C52.2617 12.601 52.9887 12.1971 53.7157 11.9548C54.5234 11.6317 55.3312 11.5509 56.2197 11.5509C57.1082 11.5509 57.916 11.7124 58.7237 11.9548C59.5315 12.2779 60.1777 12.6818 60.7431 13.1664C61.3086 13.7318 61.7932 14.378 62.1163 15.105C62.4394 15.832 62.601 16.7205 62.601 17.6091C62.601 18.5784 62.4394 19.3861 62.1163 20.1131C61.7932 20.8401 61.3086 21.4863 60.7431 22.0517C60.1777 22.6171 59.4507 23.021 58.7237 23.2633C57.916 23.5864 57.1082 23.6672 56.2197 23.6672C55.3312 23.6672 54.5234 23.5057 53.7157 23.2633C52.9079 22.9402 52.2617 22.5364 51.6963 22.0517C51.1308 21.4863 50.6462 20.8401 50.3231 20.1131C50 19.3861 49.8384 18.5784 49.8384 17.6091Z" fill="#425277"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M75.929 23.5057H72.4556V21.8902H72.3748C72.2133 22.1325 72.0517 22.3748 71.8902 22.5364C71.7286 22.7787 71.4863 22.9403 71.1632 23.1826C70.8401 23.3441 70.517 23.5057 70.1939 23.5865C69.79 23.6672 69.4669 23.748 68.9823 23.748C68.0937 23.748 67.3668 23.5865 66.8013 23.3441C66.2359 23.1018 65.832 22.6979 65.5089 22.2133C65.1858 21.7286 65.0243 21.1632 64.8627 20.4362C64.782 19.79 64.7012 19.063 64.7012 18.2553V11.7932H68.2553V17.5283C68.2553 17.8514 68.2553 18.1745 68.2553 18.5784C68.2553 18.9015 68.3361 19.3054 68.4976 19.5477C68.6592 19.79 68.8207 20.1131 69.063 20.2747C69.3054 20.4362 69.6285 20.5978 70.1131 20.5978C70.5978 20.5978 70.9209 20.517 71.244 20.3554C71.5671 20.1939 71.7286 19.9516 71.8902 19.7092C72.0517 19.4669 72.1325 19.1438 72.2133 18.7399C72.2941 18.4168 72.2941 18.013 72.2941 17.6091V11.7932H75.929V23.5057Z" fill="#425277"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M86.3489 14.7011H83.1987V18.5784C83.1987 18.9015 83.1987 19.2246 83.2794 19.4669C83.2794 19.7092 83.3602 19.9515 83.5218 20.1131C83.6026 20.2746 83.7641 20.4362 84.0064 20.5977C84.2488 20.6785 84.5719 20.7593 84.895 20.7593C85.0565 20.7593 85.2988 20.7593 85.6219 20.6785C85.945 20.6785 86.1874 20.517 86.3489 20.4362V23.4249C85.945 23.5864 85.5412 23.6672 85.1373 23.748C84.7334 23.8288 84.2488 23.8288 83.8449 23.8288C83.2795 23.8288 82.714 23.748 82.1486 23.6672C81.6639 23.5057 81.1793 23.3441 80.7754 23.021C80.3715 22.7787 80.1292 22.3748 79.8869 21.8902C79.6446 21.4055 79.5638 20.9208 79.5638 20.2746V14.7011H77.3021V11.7932H79.5638V8.31988H83.1179V11.7932H86.2681V14.7011H86.3489Z" fill="#425277"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M96.1228 16.3166C96.1228 15.6704 95.9612 15.1858 95.5573 14.7819C95.1535 14.378 94.588 14.1357 93.9418 14.1357C93.5379 14.1357 93.2148 14.2165 92.8917 14.2973C92.5686 14.4588 92.3263 14.6204 92.084 14.7819C91.8417 15.0242 91.6801 15.1858 91.5993 15.5089C91.5186 15.7512 91.4378 16.0743 91.357 16.3166H96.1228ZM99.1922 21.4863C98.6268 22.2133 97.8998 22.7787 97.0113 23.1826C96.1228 23.5864 95.2342 23.748 94.2649 23.748C93.3764 23.748 92.5686 23.5864 91.7609 23.3441C90.9531 23.021 90.3069 22.6171 89.7415 22.1325C89.1761 21.567 88.6914 20.9208 88.3683 20.1939C88.0452 19.4669 87.8837 18.5784 87.8837 17.6898C87.8837 16.7205 88.0452 15.9128 88.3683 15.1858C88.6914 14.4588 89.1761 13.8126 89.7415 13.2472C90.3069 12.6817 91.0339 12.2779 91.7609 12.0355C92.5686 11.7124 93.3764 11.6317 94.2649 11.6317C95.0727 11.6317 95.8804 11.7932 96.5266 12.0355C97.1728 12.3586 97.7383 12.7625 98.2229 13.2472C98.7076 13.8126 99.0307 14.4588 99.273 15.1858C99.5153 15.9128 99.6769 16.8013 99.6769 17.6898V18.8207H91.357C91.5186 19.5477 91.8417 20.0323 92.3263 20.4362C92.811 20.8401 93.3764 21.0824 94.1034 21.0824C94.6688 21.0824 95.1535 20.9208 95.5573 20.6785C95.9612 20.4362 96.2843 20.0323 96.6074 19.6284L99.1922 21.4863Z" fill="#425277"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M108.562 11.147L104.766 17.0436H108.562V11.147ZM108.562 20.1131H101.212V17.0436L108.078 6.46205H111.955V17.0436H114.136V20.1131H111.955V23.5057H108.481V20.1131H108.562Z" fill="#FFC63C"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M116.478 6.46205H122.052L126.01 17.6091L129.968 6.46205H135.541V23.5057H131.826V10.42H131.745L127.302 23.5057H124.475L120.194 10.42V23.5057H116.478V6.46205Z" fill="#425277"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M146.446 16.3166C146.446 15.6704 146.284 15.1858 145.88 14.7819C145.477 14.378 144.911 14.1357 144.265 14.1357C143.861 14.1357 143.538 14.2165 143.215 14.2973C142.892 14.4588 142.649 14.6204 142.407 14.7819C142.165 15.0242 142.003 15.1858 141.922 15.5089C141.842 15.7512 141.761 16.0743 141.68 16.3166H146.446ZM149.515 21.4863C148.95 22.2133 148.223 22.7787 147.334 23.1826C146.446 23.5864 145.557 23.748 144.588 23.748C143.7 23.748 142.892 23.5864 142.084 23.3441C141.276 23.021 140.63 22.6171 140.065 22.1325C139.499 21.567 139.015 20.9208 138.691 20.1939C138.368 19.4669 138.207 18.5784 138.207 17.6898C138.207 16.7205 138.368 15.9128 138.691 15.1858C139.015 14.4588 139.499 13.8126 140.065 13.2472C140.63 12.6817 141.357 12.2779 142.084 12.0355C142.892 11.7124 143.7 11.6317 144.588 11.6317C145.396 11.6317 146.204 11.7932 146.85 12.0355C147.496 12.3586 148.061 12.7625 148.546 13.2472C149.031 13.8126 149.354 14.4588 149.596 15.1858C149.838 15.9128 150 16.8013 150 17.6898V18.8207H141.68C141.842 19.5477 142.165 20.0323 142.649 20.4362C143.134 20.8401 143.7 21.0824 144.426 21.0824C144.992 21.0824 145.477 20.9208 145.88 20.6785C146.284 20.4362 146.607 20.0323 146.931 19.6284L149.515 21.4863Z" fill="#425277"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M28.1906 1.53473C28.1906 0.646204 28.9176 0 29.7253 0C30.6139 0 31.2601 0.726979 31.2601 1.53473C31.2601 2.42326 30.5331 3.06947 29.7253 3.06947C28.9176 3.15024 28.1906 2.42326 28.1906 1.53473Z" fill="#FF6400"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 1.53473C0 0.726979 0.726979 0 1.53473 0C2.42326 0 3.15024 0.726979 3.15024 1.53473C3.15024 2.42326 2.42326 3.15024 1.53473 3.15024C0.726979 3.15024 0 2.42326 0 1.53473Z" fill="#FF6400"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.2941 33.6834C22.2941 32.7948 23.021 32.1486 23.8288 32.1486C24.7173 32.1486 25.3635 32.8756 25.3635 33.6834C25.3635 34.5719 24.6366 35.2181 23.8288 35.2181C23.021 35.2181 22.2941 34.5719 22.2941 33.6834Z" fill="#FF6400"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.0323 6.62363C20.2746 6.86595 20.2746 7.18906 20.0323 7.35061L17.3667 10.0162C17.1244 10.2585 17.2051 10.4201 17.5283 10.5816C19.063 11.1471 20.2746 12.3587 20.7593 13.8126C20.84 14.055 21.0824 14.1357 21.3247 13.9742L23.9903 11.3086C24.2326 11.0663 24.5557 11.0663 24.7173 11.3086L27.0598 13.6511C27.3021 13.8934 27.4636 13.8126 27.4636 13.4895V4.20036C27.4636 4.03881 27.3829 3.95804 27.2213 3.95804H17.8514C17.5282 3.95804 17.5282 4.11959 17.6898 4.36191L20.0323 6.62363ZM13.7318 10.5008C13.9741 10.4201 14.0549 10.1777 13.8934 9.93542L11.2278 7.26983C10.9854 7.0275 10.9854 6.7044 11.2278 6.54285L13.5703 4.20036C13.8126 3.95804 13.7318 3.79649 13.4087 3.79649H4.2003C4.03875 3.95804 3.8772 4.03881 3.8772 4.20036V13.4895C3.8772 13.8126 4.03875 13.8126 4.28107 13.6511L6.62356 11.3086C6.86589 11.0663 7.18899 11.0663 7.35054 11.3086L10.0161 13.9742C10.2585 14.2165 10.42 14.1357 10.5816 13.8126C11.0662 12.2779 12.2778 11.0663 13.7318 10.5008ZM15.9127 21.1632C15.5896 21.1632 15.5089 21.4055 15.5896 21.6479L17.1244 25.0404C17.2051 25.2828 17.1244 25.6059 16.882 25.7674L13.8934 27.1406C13.651 27.3021 13.651 27.4637 13.8934 27.5445L22.6171 30.6947C22.7787 30.7755 22.9402 30.6947 22.9402 30.5332L26.1712 21.8094C26.252 21.5671 26.1712 21.4055 25.8481 21.5671L22.8594 22.9403C22.6171 23.1018 22.294 22.9403 22.1324 22.6979L20.4362 19.063C20.3554 18.8207 20.1131 18.8207 19.9515 18.9823C19.4669 19.6285 18.8207 20.1939 18.0129 20.5978C17.5282 20.8401 16.882 21.0017 16.3166 21.0824C16.3166 21.0824 16.0743 21.1632 15.9127 21.1632Z" fill="#FFC63C"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.7124 15.6705C11.7124 13.4895 13.4895 11.7932 15.5896 11.7932C17.7706 11.7932 19.4668 13.5703 19.4668 15.6705C19.4668 17.8514 17.6898 19.6285 15.5896 19.6285C13.4895 19.5477 11.7124 17.8514 11.7124 15.6705Z" fill="#FF6400"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 9.3 KiB |
@@ -6,13 +6,13 @@
|
||||
bgcolor = "#ffffff"
|
||||
|
||||
[[banners]]
|
||||
name = "Route4Me"
|
||||
link = "https://route4me.com/"
|
||||
title = "Route Planning & Route Optimization Software"
|
||||
no_query_params = true
|
||||
utm_campaign = "hugosponsor"
|
||||
bgcolor = "#334799"
|
||||
link_attr = "style='color: #ffffff; font-weight: bold; text-decoration: none; text-align: center'"
|
||||
name = "CloudCannon"
|
||||
link = "https://cloudcannon.com/hugo-cms/"
|
||||
logo = "/images/sponsors/cloudcannon-white.svg"
|
||||
utm_campaign = "HugoSponsorship"
|
||||
utm_source = "sponsor"
|
||||
utm_content = "gohugo"
|
||||
bgcolor = "#034AD8"
|
||||
|
||||
[[banners]]
|
||||
name = "Your Company?"
|
||||
@@ -20,4 +20,3 @@
|
||||
utm_campaign = "hugosponsor"
|
||||
show_on_hover = true
|
||||
bgcolor = "#4e4f4f"
|
||||
link_attr = "style='color: #ffffff; font-weight: bold; text-decoration: none; text-align: center'"
|
||||
|
||||
+7
-7
@@ -1,4 +1,4 @@
|
||||
{{- /* Last modified: 2024-04-26T13:54:00-07:00 */}}
|
||||
{{- /* Last modified: 2023-09-04T09:23:04-07:00 */}}
|
||||
|
||||
{{- /*
|
||||
Copyright 2023 Veriphor LLC
|
||||
@@ -118,7 +118,7 @@ either of these shortcodes in conjunction with this render hook.
|
||||
{{- $attrs = merge $attrs (dict "rel" "external") }}
|
||||
{{- else }}
|
||||
{{- with $u.Path }}
|
||||
{{- with $p := or ($.PageInner.GetPage .) ($.PageInner.GetPage (strings.TrimRight "/" .)) }}
|
||||
{{- with $p := or ($.Page.GetPage .) ($.Page.GetPage (strings.TrimRight "/" .)) }}
|
||||
{{- /* Destination is a page. */}}
|
||||
{{- $href := .RelPermalink }}
|
||||
{{- with $u.RawQuery }}
|
||||
@@ -137,7 +137,7 @@ either of these shortcodes in conjunction with this render hook.
|
||||
{{- end }}
|
||||
{{- $attrs = dict "href" $href }}
|
||||
{{- else }}
|
||||
{{- with $.PageInner.Resources.Get $u.Path }}
|
||||
{{- with $.Page.Resources.Get $u.Path }}
|
||||
{{- /* Destination is a page resource; drop query and fragment. */}}
|
||||
{{- $attrs = dict "href" .RelPermalink }}
|
||||
{{- else }}
|
||||
@@ -185,14 +185,14 @@ either of these shortcodes in conjunction with this render hook.
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- $attrs = merge $attrs (dict "title" (.Title | transform.HTMLEscape)) }}
|
||||
{{- with .Title }}
|
||||
{{- $attrs = merge $attrs (dict "title" .) }}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Render anchor element. */ -}}
|
||||
<a
|
||||
{{- range $k, $v := $attrs }}
|
||||
{{- if $v }}
|
||||
{{- printf " %s=%q" $k $v | safeHTMLAttr }}
|
||||
{{- end }}
|
||||
{{- printf " %s=%q" $k $v | safeHTMLAttr }}
|
||||
{{- end -}}
|
||||
>{{ .Text | safeHTML }}</a>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
{{- partial "home-page-sections/features-single.html" . -}}
|
||||
|
||||
{{/*- partial "home-page-sections/showcase.html" . -*/}}
|
||||
{{- partial "home-page-sections/showcase.html" . -}}
|
||||
|
||||
<section class="w-100 ph4 ph5-ns pv4 pv6-ns mid-gray bg-white bb bt b--light-gray">
|
||||
{{- partial "home-page-sections/installation.html" . -}}
|
||||
|
||||
@@ -1,68 +1,38 @@
|
||||
{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title>Hugo News</title>
|
||||
<description>Recent news about Hugo, a static site generator written in Go, optimized for speed and designed for flexibility.</description>
|
||||
<title>{{ .Site.Title }} – {{ .Title }}</title>
|
||||
<link>{{ .Permalink }}</link>
|
||||
<generator>Hugo {{ hugo.Version }}</generator>
|
||||
<language>{{ or site.Language.LanguageCode site.Language.Lang }}</language>
|
||||
{{- with site.Copyright }}
|
||||
<copyright>{{ . }}</copyright>
|
||||
{{- end }}
|
||||
{{- with .OutputFormats.Get "RSS" }}
|
||||
{{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
|
||||
{{- end }}
|
||||
|
||||
{{- $news_items := slice }}
|
||||
|
||||
{{- /* Get releases from GitHub. */}}
|
||||
{{- $u := "https://api.github.com/repos/gohugoio/hugo/releases" }}
|
||||
{{- $releases := partial "utilities/get-remote-data.html" $u }}
|
||||
{{- $releases = where $releases "draft" false }}
|
||||
{{- $releases = where $releases "prerelease" false }}
|
||||
{{- range $releases | first 20 }}
|
||||
{{- $summary := printf
|
||||
"Hugo %s was released on %s. See [release notes](%s) for details."
|
||||
.tag_name
|
||||
(.published_at | time.AsTime | time.Format "2 Jan 2006")
|
||||
.html_url
|
||||
}}
|
||||
{{- $ctx := dict
|
||||
"PublishDate" (.published_at | time.AsTime)
|
||||
"Title" (printf "Release %s" .name)
|
||||
"Permalink" .html_url
|
||||
"Section" "news"
|
||||
"Summary" ($summary | $.Page.RenderString)
|
||||
}}
|
||||
{{- $news_items = $news_items | append $ctx }}
|
||||
{{- end }}
|
||||
|
||||
{{- /* Get content pages from news section. */}}
|
||||
{{- range where site.RegularPages "Section" "news" }}
|
||||
{{- $ctx := dict
|
||||
"PublishDate" .PublishDate
|
||||
"Title" .Title
|
||||
"RelPermalink" .RelPermalink
|
||||
"Section" "news"
|
||||
"Summary" .Summary
|
||||
"Params" (dict "description" .Description)
|
||||
}}
|
||||
{{- $news_items = $news_items | append $ctx }}
|
||||
{{- end }}
|
||||
{{- /* Sort, limit, and render lastBuildDate. */}}
|
||||
{{- $limit := cond (gt site.Config.Services.RSS.Limit 1) site.Config.Services.RSS.Limit 999 }}
|
||||
{{- $news_items = sort $news_items "PublishDate" "desc" | first $limit }}
|
||||
<lastBuildDate>{{ (index $news_items 0).PublishDate.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>
|
||||
|
||||
{{- /* Render items. */}}
|
||||
{{- range $news_items }}
|
||||
<item>
|
||||
<title>{{ .Title }}</title>
|
||||
<link>{{ .Permalink }}</link>
|
||||
<pubDate>{{ .PublishDate.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
|
||||
<guid>{{ .Permalink }}</guid>
|
||||
<description>{{ .Summary | transform.XMLEscape | safeHTML }}</description>
|
||||
</item>
|
||||
{{- end }}
|
||||
<description>Recent Hugo news from gohugo.io</description>
|
||||
<generator>Hugo -- gohugo.io</generator>{{ with .Site.LanguageCode }}
|
||||
<language>{{.}}</language>{{end}}{{ with .Site.Author.email }}
|
||||
<managingEditor>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</managingEditor>{{end}}{{ with .Site.Author.email }}
|
||||
<webMaster>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</webMaster>{{end}}{{ with .Site.Copyright }}
|
||||
<copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }}
|
||||
<lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
|
||||
<image>
|
||||
<url>{{ "img/hugo.png" | absURL }}</url>
|
||||
<title>GoHugo.io</title>
|
||||
<link>{{ .Permalink }}</link>
|
||||
</image>
|
||||
{{ with .OutputFormats.Get "RSS" }}
|
||||
{{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
|
||||
{{ end }}
|
||||
{{ range first 50 (where .Site.RegularPages "Type" "in" (slice "news" "showcase")) }}
|
||||
<item>
|
||||
<title>{{ .Section | title }}: {{ .Title }}</title>
|
||||
<link>{{ .Permalink }}</link>
|
||||
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
|
||||
{{ with .Site.Author.email }}<author>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</author>{{end}}
|
||||
<guid>{{ .Permalink }}</guid>
|
||||
<description>
|
||||
{{ $img := (.Resources.ByType "image").GetMatch "*featured*" }}
|
||||
{{ with $img }}
|
||||
{{ $img := .Resize "640x" }}
|
||||
{{ printf "<![CDATA[<img src=\"%s\" width=\"%d\" height=\"%d\"/>]]>" $img.Permalink $img.Width $img.Height | safeHTML }}
|
||||
{{ end }}
|
||||
{{ .Content | html }}
|
||||
</description>
|
||||
</item>
|
||||
{{ end }}
|
||||
</channel>
|
||||
</rss>
|
||||
</rss>
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
{{/* Get releases from GitHub. */}}
|
||||
{{ $u := "https://api.github.com/repos/gohugoio/hugo/releases" }}
|
||||
{{ $releases := partial "utilities/get-remote-data.html" $u }}
|
||||
{{ $releases := partial "inline/get-remote-data.html" $u }}
|
||||
{{ $releases = where $releases "draft" false }}
|
||||
{{ $releases = where $releases "prerelease" false }}
|
||||
{{ range $releases | first 20 }}
|
||||
@@ -55,3 +55,16 @@
|
||||
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ define "partials/inline/get-remote-data.html" }}
|
||||
{{ $u := . }}
|
||||
{{ $r := "" }}
|
||||
{{ with $r = resources.GetRemote $u }}
|
||||
{{ with .Err }}
|
||||
{{ errorf "%s" . }}
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{ errorf "Unable to get remote resource %q" $u }}
|
||||
{{ end }}
|
||||
{{ return ($r | transform.Unmarshal) }}
|
||||
{{ end }}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title>Hugo News</title>
|
||||
<description>Recent news about Hugo, a static site generator written in Go, optimized for speed and designed for flexibility.</description>
|
||||
<link>{{ .Permalink }}</link>
|
||||
<generator>Hugo {{ hugo.Version }}</generator>
|
||||
<language>{{ or site.Language.LanguageCode site.Language.Lang }}</language>
|
||||
{{- with site.Copyright }}
|
||||
<copyright>{{ . }}</copyright>
|
||||
{{- end }}
|
||||
{{- with .OutputFormats.Get "RSS" }}
|
||||
{{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
|
||||
{{- end }}
|
||||
|
||||
{{- $news_items := slice }}
|
||||
|
||||
{{- /* Get releases from GitHub. */}}
|
||||
{{- $u := "https://api.github.com/repos/gohugoio/hugo/releases" }}
|
||||
{{- $releases := partial "utilities/get-remote-data.html" $u }}
|
||||
{{- $releases = where $releases "draft" false }}
|
||||
{{- $releases = where $releases "prerelease" false }}
|
||||
{{- range $releases | first 20 }}
|
||||
{{- $summary := printf
|
||||
"Hugo %s was released on %s. See [release notes](%s) for details."
|
||||
.tag_name
|
||||
(.published_at | time.AsTime | time.Format "2 Jan 2006")
|
||||
.html_url
|
||||
}}
|
||||
{{- $ctx := dict
|
||||
"PublishDate" (.published_at | time.AsTime)
|
||||
"Title" (printf "Release %s" .name)
|
||||
"Permalink" .html_url
|
||||
"Section" "news"
|
||||
"Summary" ($summary | $.Page.RenderString)
|
||||
}}
|
||||
{{- $news_items = $news_items | append $ctx }}
|
||||
{{- end }}
|
||||
|
||||
{{- /* Get content pages from news section. */}}
|
||||
{{- range .Pages }}
|
||||
{{- $ctx := dict
|
||||
"PublishDate" .PublishDate
|
||||
"Title" .Title
|
||||
"RelPermalink" .RelPermalink
|
||||
"Section" "news"
|
||||
"Summary" .Summary
|
||||
"Params" (dict "description" .Description)
|
||||
}}
|
||||
{{- $news_items = $news_items | append $ctx }}
|
||||
{{- end }}
|
||||
{{- /* Sort, limit, and render lastBuildDate. */}}
|
||||
{{- $limit := cond (gt site.Config.Services.RSS.Limit 1) site.Config.Services.RSS.Limit 999 }}
|
||||
{{- $news_items = sort $news_items "PublishDate" "desc" | first $limit }}
|
||||
<lastBuildDate>{{ (index $news_items 0).PublishDate.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>
|
||||
|
||||
{{- /* Render items. */}}
|
||||
{{- range $news_items }}
|
||||
<item>
|
||||
<title>{{ .Title }}</title>
|
||||
<link>{{ .Permalink }}</link>
|
||||
<pubDate>{{ .PublishDate.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
|
||||
<guid>{{ .Permalink }}</guid>
|
||||
<description>{{ .Summary | transform.XMLEscape | safeHTML }}</description>
|
||||
</item>
|
||||
{{- end }}
|
||||
</channel>
|
||||
</rss>
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<div class="w-100 center pt5">
|
||||
<div class="w-100 w-40-l tc center">
|
||||
<img src="/images/Github.svg" alt="Github Logo" class="tc center">
|
||||
<img src="/images/GitHub-Mark-64px.png" alt="Github Logo" class="tc center">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Generated
+20
-27
@@ -23,35 +23,28 @@
|
||||
class="{{ $classes_box }} o-100"
|
||||
style="background-color: {{ .bgcolor }};">
|
||||
{{ $query_params := .query_params | default "" }}
|
||||
{{ $url := .link }}
|
||||
{{ if not .no_query_params }}
|
||||
{{ $url = printf "%s?%s%s" .link $query_params (querify "utm_source" (.utm_source | default $utmSource ) "utm_medium" "banner" "utm_campaign" (.utm_campaign | default "hugosponsor") "utm_content" (.utm_content | default "gohugoio")) | safeURL }}
|
||||
{{ end }}
|
||||
{{ $url := printf "%s?%s%s" .link $query_params (querify "utm_source" (.utm_source | default $utmSource ) "utm_medium" "banner" "utm_campaign" (.utm_campaign | default "hugosponsor") "utm_content" (.utm_content | default "gohugoio")) | safeURL }}
|
||||
{{ $logo := resources.Get .logo }}
|
||||
{{ $gtagID := printf "Sponsor %s %s" .name $gtag | title }}
|
||||
{{ $classes := "" }}
|
||||
{{ if .show_on_hover }}
|
||||
{{ $classes = printf "%s show-on-hover" $classes }}
|
||||
{{ end }}
|
||||
{{ if $isFooter }}
|
||||
{{ $classes = printf "%s f3" $classes }}
|
||||
{{ else }}
|
||||
{{ $classes = printf "%s f1" $classes }}
|
||||
{{ end }}
|
||||
<a
|
||||
href="{{ $url }}"
|
||||
title="{{ .title | default .name }}"
|
||||
{{ if hugo.IsProduction }}
|
||||
{{ if hugo.IsProduction }}
|
||||
{{ $gtagID := printf "Sponsor %s %s" .name $gtag | title }}
|
||||
<a
|
||||
href="{{ $url }}"
|
||||
onclick="trackOutboundLink({{ printf "'%s', '%s'" $gtagID $url | safeJS }});"
|
||||
{{ end }}
|
||||
class="w-100 grow pa3 {{ $classes }}"
|
||||
{{ with .link_attr }}{{ . | safeHTMLAttr }}{{ end }}>
|
||||
{{ with $logo }}
|
||||
{{ .Content | safeHTML }}
|
||||
{{ else }}
|
||||
{{ .name }}
|
||||
{{ end }}
|
||||
</a>
|
||||
class="w-100 grow pa3{{ if .show_on_hover }}
|
||||
show-on-hover
|
||||
{{ end }}"
|
||||
style="">
|
||||
{{ with $logo }}{{ .Content | safeHTML }}{{ end }}
|
||||
</a>
|
||||
{{ else }}
|
||||
<a
|
||||
href="{{ $url }}"
|
||||
class="w-100 grow pa3{{ if .show_on_hover }}
|
||||
show-on-hover
|
||||
{{ end }}">
|
||||
{{ with $logo }}{{ .Content | safeHTML }}{{ end }}
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
Generated
-23
@@ -1,23 +0,0 @@
|
||||
{{/*
|
||||
Parses the serialized data from the given URL and returns a map or an array.
|
||||
|
||||
Supports CSV, JSON, TOML, YAML, and XML.
|
||||
|
||||
@param {string} . The URL from which to retrieve the serialized data.
|
||||
@returns {any}
|
||||
|
||||
@example {{ partial "get-remote-data.html" "https://example.org/foo.json" }}
|
||||
*/}}
|
||||
|
||||
{{ $url := . }}
|
||||
{{ $data := dict }}
|
||||
{{ with resources.GetRemote $url }}
|
||||
{{ with .Err }}
|
||||
{{ errorf "%s" . }}
|
||||
{{ else }}
|
||||
{{ $data = .Content | transform.Unmarshal }}
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{ errorf "Unable to get remote resource %q" $url }}
|
||||
{{ end }}
|
||||
{{ return $data }}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
{{- /*
|
||||
Renders an absolute URL to the source code for an embedded template.
|
||||
|
||||
Accepts either positional or named parameters, and depends on the
|
||||
embedded_templates.toml file in the data directory.
|
||||
|
||||
@param {string} filename The embedded template's file name, excluding extension.
|
||||
|
||||
@returns template.HTML
|
||||
|
||||
@example {{% et robots.txt %}}
|
||||
@example {{% et filename=robots.txt %}}
|
||||
*/}}
|
||||
|
||||
{{- /* Get parameters. */}}
|
||||
{{- $filename := "" -}}
|
||||
{{- if .IsNamedParams -}}
|
||||
{{- $filename = .Get "filename" -}}
|
||||
{{- else -}}
|
||||
{{- $filename = .Get 0 -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Render. */}}
|
||||
{{- with $filename -}}
|
||||
{{- with site.Data.embedded_template_urls -}}
|
||||
{{- with index . $filename -}}
|
||||
{{- urls.JoinPath site.Data.embedded_template_urls.base_url . -}}
|
||||
{{- else -}}
|
||||
{{- errorf "The %q shortcode was unable to find a URL for the embedded template named %q. Check the name. See %s" $.Name $filename $.Position -}}
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
{{- errorf "The %q shortcode was unable to find the embedded_template_urls data file in the site's data directory. See %s" $.Name $.Position -}}
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
{{- errorf "The %q shortcodes requires a named or positional parameter, the file name of the embedded template, excluding its extension. See %s" .Name .Position -}}
|
||||
{{- end -}}
|
||||
+3
-5
@@ -119,8 +119,8 @@ Renders the given image using the given filter, if any.
|
||||
{{- end }}
|
||||
|
||||
{{- $validFilters := slice
|
||||
"autoorient" "brightness" "colorbalance" "colorize" "contrast" "dither"
|
||||
"gamma" "gaussianblur" "grayscale" "hue" "invert" "none" "opacity" "overlay"
|
||||
"autoorient" "brightness" "colorbalance" "colorize" "contrast" "gamma"
|
||||
"gaussianblur" "grayscale" "hue" "invert" "none" "opacity" "overlay"
|
||||
"padding" "pixelate" "process" "saturation" "sepia" "sigmoid" "text"
|
||||
"unsharpmask"
|
||||
}}
|
||||
@@ -198,8 +198,6 @@ Renders the given image using the given filter, if any.
|
||||
{{- $ctx = merge $ctx (dict "argName" "percentage" "argValue" (index $filterArgs 0) "min" -100 "max" 100) }}
|
||||
{{- template "validate-arg-value" $ctx }}
|
||||
{{- $f = images.Contrast (index $filterArgs 0) }}
|
||||
{{- else if eq $filter "dither" }}
|
||||
{{- $f = images.Dither }}
|
||||
{{- else if eq $filter "gamma" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 1) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
@@ -356,7 +354,7 @@ Renders the given image using the given filter, if any.
|
||||
{{- if $u.IsAbs }}
|
||||
{{- with resources.GetRemote $u.String }}
|
||||
{{- with .Err }}
|
||||
{{- errorf "%s" . }}
|
||||
{{- errorf "%s" }}
|
||||
{{- else }}
|
||||
{{- /* This is a remote resource. */}}
|
||||
{{- $r = . }}
|
||||
|
||||
+3
-1
@@ -27,7 +27,9 @@ button will be hidden if any of the following conditions is true:
|
||||
{{- warnf "This call to the %q shortcode should be removed: %s. The button is now hidden because the specified version (%s) is older than the display threshold." $.Name $.Position $version }}
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
<a class="dib f5 fw6 ba bw1 b--gray ph2 mt1" href="{{ printf "https://github.com/gohugoio/hugo/releases/tag/v%s" $version }}">New in v{{ $version }}</a>
|
||||
<button class="bg-white hover:bg-gray-100 text-gray-800 font-semibold py-2 mr2 px-4 border border-gray-400 rounded shadow">
|
||||
<a href="{{ printf "https://github.com/gohugoio/hugo/releases/tag/v%s" $version }}">New in v{{ $version }}</a>
|
||||
</button>
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
{{- errorf "The %q shortcode requires a positional parameter (version). See %s" .Name .Position }}
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 924 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24"><path d="M12 0C5.374 0 0 5.373 0 12c0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.5 11.5 0 0 1 12 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12"/></svg>
|
||||
|
Before Width: | Height: | Size: 795 B |
@@ -1 +1 @@
|
||||
# github.com/gohugoio/gohugoioTheme v0.0.0-20240728210410-d42c342ce472
|
||||
# github.com/gohugoio/gohugoioTheme v0.0.0-20240201183016-8e648a3b5342
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
@@ -1,6 +1,6 @@
|
||||
[[docs]]
|
||||
identifier = 'about'
|
||||
name = 'About'
|
||||
name = 'About Hugo'
|
||||
pageRef = '/about/'
|
||||
weight = 10
|
||||
|
||||
@@ -15,60 +15,60 @@ name = 'Getting started'
|
||||
weight = 30
|
||||
identifier = 'getting-started'
|
||||
pageRef = '/getting-started/'
|
||||
|
||||
[[docs]]
|
||||
name = 'Quick reference'
|
||||
weight = 40
|
||||
identifier = 'quick-reference'
|
||||
pageRef = '/quick-reference/'
|
||||
post = 'break'
|
||||
|
||||
[[docs]]
|
||||
name = 'Content management'
|
||||
weight = 50
|
||||
weight = 40
|
||||
identifier = 'content-management'
|
||||
post = 'expanded'
|
||||
pageRef = '/content-management/'
|
||||
|
||||
[[docs]]
|
||||
name = 'Templates'
|
||||
weight = 60
|
||||
weight = 50
|
||||
identifier = 'templates'
|
||||
pageRef = '/templates/'
|
||||
|
||||
[[docs]]
|
||||
name = 'Functions'
|
||||
weight = 70
|
||||
weight = 60
|
||||
identifier = 'functions'
|
||||
pageRef = '/functions/'
|
||||
|
||||
[[docs]]
|
||||
name = 'Methods'
|
||||
weight = 80
|
||||
weight = 70
|
||||
identifier = 'methods'
|
||||
pageRef = '/methods/'
|
||||
|
||||
[[docs]]
|
||||
name = 'Render hooks'
|
||||
weight = 90
|
||||
identifier = 'render-hooks'
|
||||
pageRef = '/render-hooks/'
|
||||
name = 'Quick reference'
|
||||
weight = 80
|
||||
identifier = 'quick-reference'
|
||||
pageRef = '/quick-reference/'
|
||||
|
||||
[[docs]]
|
||||
name = 'Variables'
|
||||
weight = 85
|
||||
identifier = 'variables'
|
||||
pageRef = '/variables/'
|
||||
|
||||
[[docs]]
|
||||
name = 'Hugo Modules'
|
||||
weight = 100
|
||||
weight = 90
|
||||
identifier = 'modules'
|
||||
pageRef = '/hugo-modules/'
|
||||
|
||||
[[docs]]
|
||||
name = 'Hugo Pipes'
|
||||
weight = 110
|
||||
weight = 100
|
||||
identifier = 'hugo-pipes'
|
||||
pageRef = '/hugo-pipes/'
|
||||
|
||||
[[docs]]
|
||||
name = 'CLI'
|
||||
weight = 120
|
||||
weight = 110
|
||||
post = 'break'
|
||||
identifier = 'commands'
|
||||
pageRef = '/commands/'
|
||||
@@ -77,25 +77,25 @@ pageRef = '/commands/'
|
||||
|
||||
[[docs]]
|
||||
name = 'Troubleshooting'
|
||||
weight = 130
|
||||
weight = 120
|
||||
identifier = 'troubleshooting'
|
||||
pageRef = '/troubleshooting/'
|
||||
|
||||
[[docs]]
|
||||
name = 'Developer tools'
|
||||
weight = 140
|
||||
weight = 130
|
||||
identifier = 'developer-tools'
|
||||
pageRef = '/tools/'
|
||||
|
||||
[[docs]]
|
||||
name = 'Hosting and deployment'
|
||||
weight = 150
|
||||
weight = 140
|
||||
identifier = 'hosting-and-deployment'
|
||||
pageRef = '/hosting-and-deployment/'
|
||||
|
||||
[[docs]]
|
||||
name = 'Contribute'
|
||||
weight = 160
|
||||
weight = 150
|
||||
post = 'break'
|
||||
identifier = 'contribute'
|
||||
pageRef = '/contribute/'
|
||||
@@ -128,11 +128,11 @@ weight = 10
|
||||
identifier = 'themes'
|
||||
url = 'https://themes.gohugo.io/'
|
||||
|
||||
# [[global]]
|
||||
# name = 'Showcase'
|
||||
# weight = 20
|
||||
# identifier = 'showcase'
|
||||
# pageRef = '/showcase/'
|
||||
[[global]]
|
||||
name = 'Showcase'
|
||||
weight = 20
|
||||
identifier = 'showcase'
|
||||
pageRef = '/showcase/'
|
||||
|
||||
# Anything with a weight > 100 gets an external icon
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ features:
|
||||
- heading: Shortcodes
|
||||
image_path: /images/icon-shortcodes.svg
|
||||
tagline: Hugo's shortcodes are Markdown's hidden superpower.
|
||||
copy: We love the beautiful simplicity of Markdown’s syntax, but there are times when we want more flexibility. Hugo shortcodes allow for both beauty and flexibility.
|
||||
copy: We love the beautiful simplicity of markdown’s syntax, but there are times when we want more flexibility. Hugo shortcodes allow for both beauty and flexibility.
|
||||
|
||||
- heading: Built-in Templates
|
||||
image_path: /images/icon-built-in-templates.svg
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
---
|
||||
title: About Hugo
|
||||
linkTitle: In this section
|
||||
description: Learn about Hugo and its features, security model, and privacy protections.
|
||||
linkTitle: Overview
|
||||
description: Hugo's features, roadmap, license, and motivation.
|
||||
categories: []
|
||||
keywords: []
|
||||
menu:
|
||||
docs:
|
||||
identifier: about-hugo-in-this-section
|
||||
identifier: about-hugo-overview
|
||||
parent: about
|
||||
weight: 10
|
||||
weight: 10
|
||||
aliases: [/about-hugo/,/docs/]
|
||||
---
|
||||
|
||||
Learn about Hugo and its features, privacy protections, and security model.
|
||||
Hugo is not your average static site generator.
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: Benefits of static site generators
|
||||
linkTitle: Static site generators
|
||||
description: Improved performance, security and ease of use are just a few of the reasons static site generators are so appealing.
|
||||
categories: [about]
|
||||
keywords: [ssg,static,performance,security]
|
||||
menu:
|
||||
docs:
|
||||
parent: about
|
||||
weight: 40
|
||||
weight: 40
|
||||
---
|
||||
|
||||
The purpose of website generators is to render content into HTML files. Most are "dynamic site generators." That means the HTTP server---i.e., the program that sends files to the browser to be viewed---runs the generator to create a new HTML file every time an end user requests a page.
|
||||
|
||||
Over time, dynamic site generators were programmed to cache their HTML files to prevent unnecessary delays in delivering pages to end users. A cached page is a static version of a web page.
|
||||
|
||||
Hugo takes caching a step further and all HTML files are rendered on your computer. You can review the files locally before copying them to the computer hosting the HTTP server. Since the HTML files aren't generated dynamically, we say that Hugo is a *static site generator*.
|
||||
|
||||
This has many benefits. The most noticeable is performance. HTTP servers are *very* good at sending files---so good, in fact, that you can effectively serve the same number of pages with a fraction of the memory and CPU needed for a dynamic site.
|
||||
|
||||
## More on static site generators
|
||||
|
||||
* ["An Introduction to Static Site Generators", David Walsh]
|
||||
* ["Hugo vs. WordPress page load speed comparison: Hugo leaves WordPress in its dust", GettingThingsTech][hugovwordpress]
|
||||
* ["Static Site Generators", O'Reilly]
|
||||
* [StaticGen: Top Open-Source Static Site Generators (GitHub Stars)]
|
||||
* ["Top 10 Static Website Generators", Netlify blog]
|
||||
* ["The Resurgence of Static", dotCMS][dotcms]
|
||||
|
||||
["An Introduction to Static Site Generators", David Walsh]: https://davidwalsh.name/introduction-static-site-generators
|
||||
["Static Site Generators", O'Reilly]: https://github.com/gohugoio/hugoDocs/files/1242701/static-site-generators.pdf
|
||||
["Top 10 Static Website Generators", Netlify blog]: https://www.netlify.com/blog/2016/05/02/top-ten-static-website-generators/
|
||||
[hugovwordpress]: https://gettingthingstech.com/hugo-vs.-wordpress-page-load-speed-comparison-hugo-leaves-wordpress-in-its-dust/
|
||||
[StaticGen: Top Open-Source Static Site Generators (GitHub Stars)]: https://www.staticgen.com/
|
||||
[dotcms]: https://dotcms.com/blog/post/the-resurgence-of-static
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Features
|
||||
description: Hugo's rich and powerful feature set provides the framework and tools to create static sites that build in seconds, often less.
|
||||
title: Hugo features
|
||||
description: Hugo boasts blistering speed, robust content management, and a powerful templating language making it a great fit for all kinds of static websites.
|
||||
categories: [about]
|
||||
keywords: []
|
||||
menu:
|
||||
@@ -11,129 +11,70 @@ weight: 30
|
||||
toc: true
|
||||
---
|
||||
|
||||
## Framework
|
||||
## General
|
||||
|
||||
[Multiplatform]
|
||||
: Install Hugo's single executable on Linux, macOS, Windows, and more.
|
||||
* [Extremely fast] build times (< 1 ms per page)
|
||||
* Completely cross platform, with [easy installation][install] on macOS, Linux, Windows, and more
|
||||
* Renders changes on the fly with [LiveReload] as you develop
|
||||
* [Powerful theming]
|
||||
* [Host your site anywhere][hostanywhere]
|
||||
|
||||
[Multilingual]
|
||||
: Localize your project for each language and region, including translations, images, dates, currencies, numbers, percentages, and collation sequence. Hugo's multilingual framework supports single-host and multihost configurations.
|
||||
## Organization
|
||||
|
||||
[Output formats]
|
||||
: Render each page of your site to one or more output formats, with granular control by page kind, section, and path. While HTML is the default output format, you can add JSON, RSS, CSV, and more. For example, create a REST API to access content.
|
||||
* Straightforward [organization for your projects], including website sections
|
||||
* Customizable [URLs]
|
||||
* Support for configurable [taxonomies], including categories and tags
|
||||
* [Sort content] as you desire through powerful template [functions]
|
||||
* Automatic [table of contents] generation
|
||||
* [Dynamic menu] creation
|
||||
* [Pretty URLs] support
|
||||
* [Permalink] pattern support
|
||||
* Redirects via [aliases]
|
||||
|
||||
[Templates]
|
||||
: Create templates using variables, functions, and methods to transform your content, resources, and data into a published page. While HTML templates are the most common, you can create templates for any output format.
|
||||
## Content
|
||||
|
||||
[Themes]
|
||||
: Reduce development time and cost by using one of the hundreds of themes contributed by the Hugo community. Themes are available for corporate sites, documentation projects, image portfolios, landing pages, personal and professional blogs, resumes, CVs, and more.
|
||||
* Native Markdown and Emacs Org-Mode support, as well as other languages via *external helpers* (see [supported formats])
|
||||
* TOML, YAML, and JSON metadata support in [front matter]
|
||||
* Customizable [homepage]
|
||||
* Multiple [content types]
|
||||
* Automatic and user defined [content summaries]
|
||||
* [Shortcodes] to enable rich content inside of Markdown
|
||||
* ["Minutes to Read"][pagevars] functionality
|
||||
* ["WordCount"][pagevars] functionality
|
||||
|
||||
[Modules]
|
||||
: Reduce development time and cost by creating or importing packaged combinations of archetypes, assets, content, data, templates, translation tables, static files, or configuration settings. A module may serve as the basis for a new site, or to augment an existing site.
|
||||
## Additional features
|
||||
|
||||
[Privacy]
|
||||
: Configure the behavior of Hugo's embedded templates and shortcodes to facilitate compliance with regional privacy regulations, including the [GDPR] and [CCPA].
|
||||
* Integrated [Disqus] comment support
|
||||
* Integrated [Google Analytics] support
|
||||
* Automatic [RSS] creation
|
||||
* Support for [Go] HTML templates
|
||||
* [Syntax highlighting] powered by [Chroma]
|
||||
|
||||
[Security]
|
||||
: Hugo's security model is based on the premise that template and configuration authors are trusted, but content authors are not. This model enables generation of HTML output safe against code injection. Other protections prevent "shelling out" to arbitrary applications, limit access to specific environment variables, prevent connections to arbitrary remote data sources, and more.
|
||||
|
||||
## Content authoring
|
||||
|
||||
[Content formats]
|
||||
: Create your content using Markdown, HTML, AsciiDoc, Emacs Org Mode, Pandoc, or reStructuredText. Markdown is the default content format, conforming to the [CommonMark] and [GitHub Flavored Markdown] specifications.
|
||||
|
||||
[Markdown attributes]
|
||||
: Apply HTML attributes such as `class` and `id` to Markdown images and block elements including blockquotes, fenced code blocks, headings, horizontal rules, lists, paragraphs, and tables.
|
||||
|
||||
[Markdown extensions]
|
||||
: Leverage the embedded Markdown extensions to create tables, definition lists, footnotes, task lists, inserted text, mark text, subscripts, superscripts, and more.
|
||||
|
||||
[Markdown render hooks]
|
||||
: Override the conversion of Markdown to HTML when rendering fenced code blocks, headings, images, and links. For example, render every standalone image as an HTML `figure` element.
|
||||
|
||||
[Diagrams]
|
||||
: Use fenced code blocks and Markdown render hooks to include diagrams in your content.
|
||||
|
||||
[Mathematics]
|
||||
: Include mathematical equations and expressions in Markdown using LaTeX or TeX typesetting syntax.
|
||||
|
||||
[Syntax highlighting]
|
||||
: Syntactically highlight code examples using Hugo's embedded syntax highlighter, enabled by default for fenced code blocks in Markdown. The syntax highlighter supports hundreds of code languages and dozens of styles.
|
||||
|
||||
[Shortcodes]
|
||||
: Use Hugo's embedded shortcodes, or create your own, to insert complex content. For example, use shortcodes to include `audio` and `video` elements, render tables from local or remote data sources, insert snippets from other pages, and more.
|
||||
|
||||
## Content management
|
||||
|
||||
[Content adapters]
|
||||
: Create content adapters to dynamically add content when building your site. For example, use a content adapter to create pages from a remote data source such as JSON, TOML, YAML, or XML.
|
||||
|
||||
[Taxonomies]
|
||||
: Classify content to establish simple or complex logical relationships between pages. For example, create an authors taxonomy, and assign one or more authors to each page. Among other uses, the taxonomy system provides an inverted, weighted index to render a list of related pages, ordered by relevance.
|
||||
|
||||
[Data]
|
||||
: Augment your content using local or remote data sources including CSV, JSON, TOML, YAML, and XML. For example, create a shortcode to render an HTML table from a remote CSV file.
|
||||
|
||||
[Menus]
|
||||
: Provide rapid access to content via Hugo's menu system, configured automatically, globally, or on a page-by-page basis. The menu system is a key component of Hugo's multilingual architecture.
|
||||
|
||||
[URL management]
|
||||
: Serve any page from any path via global configuration or on a page-by-page basis.
|
||||
|
||||
## Asset pipelines
|
||||
|
||||
[Image processing]
|
||||
: Convert, resize, crop, rotate, adjust colors, apply filters, overlay text and images, and extract EXIF data.
|
||||
|
||||
[JavaScript bundling]
|
||||
: Transpile TypeScript and JSX to JavaScript, bundle, tree shake, minify, create source maps, and perform SRI hashing.
|
||||
|
||||
[Sass processing]
|
||||
: Transpile Sass to CSS, bundle, tree shake, minify, create source maps, perform SRI hashing, and integrate with PostCSS.
|
||||
|
||||
[Tailwind CSS processing]
|
||||
: Compile Tailwind CSS utility classes into standard CSS, bundle, tree shake, optimize, minify, perform SRI hashing, and integrate with PostCSS.
|
||||
|
||||
## Performance
|
||||
|
||||
[Caching]
|
||||
: Reduce build time and cost by rendering a partial template once then cache the result, either globally or within a given context. For example, cache the result of an asset pipeline to prevent reprocessing on every rendered page.
|
||||
|
||||
[Segmentation]
|
||||
: Reduce build time and cost by partitioning your sites into segments. For example, render the home page and the "news section" every hour, and render the entire site once a week.
|
||||
|
||||
[Minification]
|
||||
: Minify HTML, CSS, and JavaScript to reduce file size, bandwidth consumption, and loading times.
|
||||
|
||||
[CCPA]: https://en.wikipedia.org/wiki/California_Consumer_Privacy_Act
|
||||
[Sass processing]: /functions/css/Sass/
|
||||
[Caching]: /functions/partials/includecached/
|
||||
[CommonMark]: https://spec.commonmark.org/current/
|
||||
[Content adapters]: /content-management/content-adapters/
|
||||
[Content formats]: /content-management/formats/
|
||||
[Data]: /content-management/data-sources/
|
||||
[Diagrams]: /content-management/diagrams/
|
||||
[GDPR]: https://en.wikipedia.org/wiki/General_Data_Protection_Regulation
|
||||
[GitHub Flavored Markdown]: https://github.github.com/gfm/
|
||||
[Image processing]: /content-management/image-processing/
|
||||
[JavaScript bundling]: /functions/js/build/
|
||||
[Markdown attributes]: /content-management/markdown-attributes/
|
||||
[Markdown extensions]: /getting-started/configuration-markup/#goldmark-extensions
|
||||
[Markdown render hooks]: /render-hooks/introduction/
|
||||
[Mathematics]: /content-management/mathematics/
|
||||
[Menus]: /content-management/menus/
|
||||
[Minification]: /getting-started/configuration/#configure-minify
|
||||
[Modules]: https://gohugo.io/hugo-modules/
|
||||
[Multilingual]: /content-management/multilingual/
|
||||
[Multiplatform]: /installation/
|
||||
[Output formats]: /templates/output-formats/
|
||||
[Privacy]: /about/privacy/
|
||||
[Security]: /about/security/
|
||||
[Segmentation]: /getting-started/configuration/#configure-segments
|
||||
[aliases]: /content-management/urls/#aliases
|
||||
[Chroma]: https://github.com/alecthomas/chroma
|
||||
[content summaries]: /content-management/summaries/
|
||||
[content types]: /content-management/types/
|
||||
[Disqus]: https://disqus.com/
|
||||
[Dynamic menu]: /templates/menu-templates/
|
||||
[Extremely fast]: https://github.com/bep/hugo-benchmark
|
||||
[front matter]: /content-management/front-matter/
|
||||
[functions]: /functions/
|
||||
[Go]: https://pkg.go.dev/html/template
|
||||
[Google Analytics]: https://google-analytics.com/
|
||||
[homepage]: /templates/homepage/
|
||||
[hostanywhere]: /hosting-and-deployment/
|
||||
[install]: /installation/
|
||||
[LiveReload]: /getting-started/usage/
|
||||
[organization for your projects]: /getting-started/directory-structure/
|
||||
[pagevars]: /variables/page/
|
||||
[Permalink]: /content-management/urls/#permalinks
|
||||
[Powerful theming]: /hugo-modules/theme-components/
|
||||
[Pretty URLs]: /content-management/urls/
|
||||
[RSS]: /templates/rss/
|
||||
[Shortcodes]: /content-management/shortcodes/
|
||||
[sort content]: /templates/
|
||||
[supported formats]: /content-management/formats/
|
||||
[Syntax highlighting]: /content-management/syntax-highlighting/
|
||||
[Tailwind CSS processing]: /functions/css/tailwindcss/
|
||||
[Taxonomies]: /content-management/taxonomies/
|
||||
[Templates]: templates/introduction/
|
||||
[Themes]: https://themes.gohugo.io/
|
||||
[URL management]: /content-management/urls/
|
||||
[table of contents]: /content-management/toc/
|
||||
[taxonomies]: /content-management/taxonomies/
|
||||
[URLs]: /content-management/urls/
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
---
|
||||
title: Privacy
|
||||
linkTitle: Privacy
|
||||
description: Configure your site to facilitate compliance with regional privacy regulations.
|
||||
title: Hugo and the General Data Protection Regulation
|
||||
linkTitle: Hugo and the GDPR
|
||||
description: About how to configure your Hugo site to meet the new regulations.
|
||||
categories: [about]
|
||||
keywords: ["GDPR", "Privacy", "Data Protection"]
|
||||
menu:
|
||||
docs:
|
||||
parent: about
|
||||
weight: 40
|
||||
weight: 40
|
||||
weight: 60
|
||||
weight: 60
|
||||
toc: true
|
||||
aliases: [/gdpr/,/about/hugo-and-gdpr/]
|
||||
aliases: [/privacy/,/gdpr/]
|
||||
---
|
||||
|
||||
General Data Protection Regulation ([GDPR](https://en.wikipedia.org/wiki/General_Data_Protection_Regulation)) is a regulation in EU law on data protection and privacy for all individuals within the European Union and the European Economic Area. It became enforceable on 25 May 2018.
|
||||
|
||||
**Hugo is a static site generator. By using Hugo you are already standing on very solid ground. Static HTML files on disk are much easier to reason about compared to server and database driven websites.**
|
||||
**Hugo is a static site generator. By using Hugo you are already standing on very solid ground. Static HTML files on disk are much easier to reason about compared to server and database driven web sites.**
|
||||
|
||||
But even static websites can integrate with external services, so from version `0.41`, Hugo provides a **privacy configuration** that covers the relevant built-in templates.
|
||||
|
||||
Note that:
|
||||
|
||||
* These settings have their defaults setting set to _off_, i.e. how it worked before Hugo `0.41`. You must do your own evaluation of your site and apply the appropriate settings.
|
||||
* These settings work with the [embedded templates](/templates/embedded/). Some theme may contain custom templates for embedding services like Google Analytics. In that case these options have no effect.
|
||||
* These settings work with the [internal templates](/templates/internal/). Some theme may contain custom templates for embedding services like Google Analytics. In that case these options have no effect.
|
||||
* We will continue this work and improve this further in future Hugo versions.
|
||||
|
||||
## All privacy settings
|
||||
@@ -36,6 +36,8 @@ disable = false
|
||||
[privacy.googleAnalytics]
|
||||
disable = false
|
||||
respectDoNotTrack = false
|
||||
anonymizeIP = false
|
||||
useSessionStorage = false
|
||||
[privacy.instagram]
|
||||
disable = false
|
||||
simple = false
|
||||
@@ -76,9 +78,19 @@ disable = true
|
||||
|
||||
### GoogleAnalytics
|
||||
|
||||
anonymizeIP
|
||||
: Enabling this will make it so the users' IP addresses are anonymized within Google Analytics.
|
||||
|
||||
respectDoNotTrack
|
||||
: Enabling this will make the GA templates respect the "Do Not Track" HTTP header.
|
||||
|
||||
useSessionStorage
|
||||
: Enabling this will disable the use of Cookies and use Session Storage to Store the GA Client ID.
|
||||
|
||||
{{% note %}}
|
||||
`useSessionStorage` is not supported when using Google Analytics v4 (gtag.js).
|
||||
{{% /note %}}
|
||||
|
||||
### Instagram
|
||||
|
||||
simple
|
||||
@@ -1,39 +0,0 @@
|
||||
---
|
||||
title: Introduction
|
||||
description: Hugo is a static site generator written in Go, optimized for speed and designed for flexibility.
|
||||
categories: [about]
|
||||
keywords: []
|
||||
menu:
|
||||
docs:
|
||||
identifier: about-introduction
|
||||
parent: about
|
||||
weight: 20
|
||||
weight: 20
|
||||
aliases: [/about/what-is-hugo/,/about/benefits/]
|
||||
---
|
||||
|
||||
Hugo is a [static site generator] written in [Go], optimized for speed and designed for flexibility. With its advanced templating system and fast asset pipelines, Hugo renders a complete site in seconds, often less.
|
||||
|
||||
Due to its flexible framework, multilingual support, and powerful taxonomy system, Hugo is widely used to create:
|
||||
|
||||
- Corporate, government, nonprofit, education, news, event, and project sites
|
||||
- Documentation sites
|
||||
- Image portfolios
|
||||
- Landing pages
|
||||
- Business, professional, and personal blogs
|
||||
- Resumes and CVs
|
||||
|
||||
Use Hugo's embedded web server during development to instantly see changes to content, structure, behavior, and presentation. Then deploy the site to your host, or push changes to your Git provider for automated builds and deployment.
|
||||
|
||||
And with [Hugo Modules], you can share content, assets, data, translations, themes, templates, and configuration with other projects via public or private Git repositories.
|
||||
|
||||
Learn more about Hugo's [features], [privacy protections], and [security model].
|
||||
|
||||
[Go]: https://go.dev
|
||||
[Hugo Modules]: /hugo-modules/
|
||||
[static site generator]: https://en.wikipedia.org/wiki/Static_site_generator
|
||||
[features]: /about/features
|
||||
[security model]: /about/security
|
||||
[privacy protections]: /about/privacy
|
||||
|
||||
{{< youtube 0RKpf3rK57I >}}
|
||||
@@ -2,12 +2,12 @@
|
||||
title: License
|
||||
description: Hugo is released under the Apache 2.0 license.
|
||||
categories: [about]
|
||||
keywords: [apache]
|
||||
keywords: [license,apache]
|
||||
menu:
|
||||
docs:
|
||||
parent: about
|
||||
weight: 60
|
||||
weight: 60
|
||||
weight: 70
|
||||
weight: 70
|
||||
---
|
||||
|
||||
## Apache License
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: Security model
|
||||
linkTitle: Security
|
||||
title: Hugo's security model
|
||||
description: A summary of Hugo's security model.
|
||||
categories: [about]
|
||||
keywords: [security,privacy]
|
||||
@@ -10,7 +9,7 @@ menu:
|
||||
weight: 50
|
||||
weight: 50
|
||||
toc: true
|
||||
aliases: [/about/security-model/]
|
||||
aliases: [/security/]
|
||||
---
|
||||
|
||||
## Runtime security
|
||||
@@ -22,8 +21,9 @@ But when developing and building your site, the runtime is the `hugo` executable
|
||||
**Hugo's main approach is that of sandboxing and a security policy with strict defaults:**
|
||||
|
||||
* Hugo has a virtual file system and only the main project (not third-party components) is allowed to mount directories or files outside the project root.
|
||||
* Only the main project can walk symbolic links.
|
||||
* User-defined components have read-only access to the filesystem.
|
||||
* We shell out to some external binaries to support [Asciidoctor](/content-management/formats/#formats) and similar, but those binaries and their flags are predefined and disabled by default (see [Security Policy](#security-policy)). General functions to run arbitrary external OS commands have been [discussed](https://github.com/gohugoio/hugo/issues/796), but not implemented because of security concerns.
|
||||
* We shell out to some external binaries to support [Asciidoctor](/content-management/formats/#list-of-content-formats) and similar, but those binaries and their flags are predefined and disabled by default (see [Security Policy](#security-policy)). General functions to run arbitrary external OS commands have been [discussed](https://github.com/gohugoio/hugo/issues/796), but not implemented because of security concerns.
|
||||
|
||||
## Security policy
|
||||
|
||||
@@ -33,16 +33,7 @@ The default configuration is listed below. Any build using features not in the a
|
||||
|
||||
{{< code-toggle config=security />}}
|
||||
|
||||
By default, Hugo permits the [`resources.GetRemote`] function to download files with media types corresponding to an internal allow list. To add media types to the allow list:
|
||||
|
||||
[`resources.GetRemote`]: /functions/resources/getremote
|
||||
|
||||
{{< code-toggle file=hugo >}}
|
||||
[security.http]
|
||||
mediaTypes = ['^image/avif$']
|
||||
{{< /code-toggle >}}
|
||||
|
||||
Note that these and other configuration settings in Hugo can be overridden by the OS environment. For example, if you want to block all remote HTTP fetching of data:
|
||||
Note that these and other configuration settings in Hugo can be overridden by the OS environment. If you want to block all remote HTTP fetching of data:
|
||||
|
||||
```txt
|
||||
HUGO_SECURITY_HTTP_URLS=none hugo
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: What is Hugo
|
||||
description: Hugo is a fast and modern static site generator written in Go, and designed to make website creation fun again.
|
||||
categories: [about]
|
||||
keywords: []
|
||||
menu:
|
||||
docs:
|
||||
parent: about
|
||||
weight: 20
|
||||
weight: 20
|
||||
toc: true
|
||||
aliases: [/overview/introduction/,/about/why-i-built-hugo/]
|
||||
---
|
||||
|
||||
Hugo is a general-purpose website framework. Technically speaking, Hugo is a [static site generator]. Unlike systems that dynamically build a page with each visitor request, Hugo builds pages when you create or update your content. Since websites are viewed far more often than they are edited, Hugo is designed to provide an optimal viewing experience for your website's end users and an ideal writing experience for website authors.
|
||||
|
||||
Websites built with Hugo are extremely fast and secure. Hugo sites can be hosted anywhere, including [Netlify], [Heroku], [GoDaddy], [DreamHost], [GitHub Pages], [GitLab Pages], [Surge], [Firebase], [Google Cloud Storage], [Amazon S3], [Rackspace], [Azure], and [CloudFront] and work well with CDNs. Hugo sites run without the need for a database or dependencies on expensive runtimes like Ruby, Python, or PHP.
|
||||
|
||||
We think of Hugo as the ideal website creation tool with nearly instant build times, able to rebuild whenever a change is made.
|
||||
|
||||
## How fast is Hugo?
|
||||
|
||||
{{< youtube "CdiDYZ51a2o" >}}
|
||||
|
||||
## What does Hugo do?
|
||||
|
||||
In technical terms, Hugo takes a source directory of files and templates and uses these as input to create a complete website.
|
||||
|
||||
## Who should use Hugo?
|
||||
|
||||
Hugo is for people that prefer writing in a text editor over a browser.
|
||||
|
||||
Hugo is for people who want to hand code their own website without worrying about setting up complicated runtimes, dependencies and databases.
|
||||
|
||||
Hugo is for people building a blog, a company site, a portfolio site, documentation, a single landing page, or a website with thousands of pages.
|
||||
|
||||
[@spf13]: https://twitter.com/spf13
|
||||
[Amazon S3]: https://aws.amazon.com/s3/
|
||||
[Azure]: https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blob-static-website
|
||||
[CloudFront]: https://aws.amazon.com/cloudfront/
|
||||
[DreamHost]: https://www.dreamhost.com/
|
||||
[Firebase]: https://firebase.google.com/docs/hosting/
|
||||
[GitHub Pages]: https://pages.github.com/
|
||||
[GitLab Pages]: https://about.gitlab.com/features/pages/
|
||||
[Go language]: https://go.dev/
|
||||
[GoDaddy]: https://www.godaddy.com/
|
||||
[Google Cloud Storage]: https://cloud.google.com/storage/
|
||||
[Heroku]: https://www.heroku.com/
|
||||
[Jekyll]: https://jekyllrb.com/
|
||||
[Middleman]: https://middlemanapp.com/
|
||||
[Nanoc]: https://nanoc.ws/
|
||||
[Netlify]: https://netlify.com
|
||||
[Rackspace]: https://www.rackspace.com/cloud/files
|
||||
[Surge]: https://surge.sh
|
||||
[contributing to it]: https://github.com/gohugoio/hugo
|
||||
[rackspace]: https://www.rackspace.com/openstack/public/files
|
||||
[static site generator]: /about/benefits/
|
||||
@@ -57,7 +57,7 @@ hugo [flags]
|
||||
--printUnusedTemplates print warnings on unused templates.
|
||||
--quiet build in quiet mode
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-M, --renderToMemory render to memory (mostly useful when running the server)
|
||||
--renderToMemory render to memory (mostly useful when running the server)
|
||||
-s, --source string filesystem path to read files relative from
|
||||
--templateMetrics display metrics about template executions
|
||||
--templateMetricsHints calculate some improvement hints when combined with --templateMetrics
|
||||
|
||||
@@ -31,7 +31,7 @@ See each sub-command's help for details on how to use the generated script.
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--logLevel string log level (debug|info|warn|error)
|
||||
--quiet build in quiet mode
|
||||
-M, --renderToMemory render to memory (mostly useful when running the server)
|
||||
--renderToMemory render to memory (mostly useful when running the server)
|
||||
-s, --source string filesystem path to read files relative from
|
||||
--themesDir string filesystem path to themes directory
|
||||
-v, --verbose verbose output
|
||||
|
||||
@@ -54,7 +54,7 @@ hugo completion bash
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--logLevel string log level (debug|info|warn|error)
|
||||
--quiet build in quiet mode
|
||||
-M, --renderToMemory render to memory (mostly useful when running the server)
|
||||
--renderToMemory render to memory (mostly useful when running the server)
|
||||
-s, --source string filesystem path to read files relative from
|
||||
--themesDir string filesystem path to themes directory
|
||||
-v, --verbose verbose output
|
||||
|
||||
@@ -45,7 +45,7 @@ hugo completion fish [flags]
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--logLevel string log level (debug|info|warn|error)
|
||||
--quiet build in quiet mode
|
||||
-M, --renderToMemory render to memory (mostly useful when running the server)
|
||||
--renderToMemory render to memory (mostly useful when running the server)
|
||||
-s, --source string filesystem path to read files relative from
|
||||
--themesDir string filesystem path to themes directory
|
||||
-v, --verbose verbose output
|
||||
|
||||
@@ -42,7 +42,7 @@ hugo completion powershell [flags]
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--logLevel string log level (debug|info|warn|error)
|
||||
--quiet build in quiet mode
|
||||
-M, --renderToMemory render to memory (mostly useful when running the server)
|
||||
--renderToMemory render to memory (mostly useful when running the server)
|
||||
-s, --source string filesystem path to read files relative from
|
||||
--themesDir string filesystem path to themes directory
|
||||
-v, --verbose verbose output
|
||||
|
||||
@@ -56,7 +56,7 @@ hugo completion zsh [flags]
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--logLevel string log level (debug|info|warn|error)
|
||||
--quiet build in quiet mode
|
||||
-M, --renderToMemory render to memory (mostly useful when running the server)
|
||||
--renderToMemory render to memory (mostly useful when running the server)
|
||||
-s, --source string filesystem path to read files relative from
|
||||
--themesDir string filesystem path to themes directory
|
||||
-v, --verbose verbose output
|
||||
|
||||
@@ -40,7 +40,7 @@ hugo config [command] [flags]
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
--logLevel string log level (debug|info|warn|error)
|
||||
--quiet build in quiet mode
|
||||
-M, --renderToMemory render to memory (mostly useful when running the server)
|
||||
--renderToMemory render to memory (mostly useful when running the server)
|
||||
-s, --source string filesystem path to read files relative from
|
||||
--themesDir string filesystem path to themes directory
|
||||
-v, --verbose verbose output
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user