Compare commits

..

1 Commits

Author SHA1 Message Date
Bjørn Erik Pedersen 6389adcc90 Misc remote HTTP/content adapter enhancements
* Recover from server errors
* Improve go adapter rebuilds when adding new content

See #12502
Fixes #12570
2024-06-05 11:14:22 +02:00
1144 changed files with 14854 additions and 33896 deletions
+5 -3
View File
@@ -4,7 +4,7 @@ parameters:
defaults: &defaults
resource_class: large
docker:
- image: bepsays/ci-hugoreleaser:1.22301.20401
- image: bepsays/ci-hugoreleaser:1.22200.20201
environment: &buildenv
GOMODCACHE: /root/project/gomodcache
version: 2
@@ -14,7 +14,9 @@ jobs:
environment: &buildenv
GOMODCACHE: /root/project/gomodcache
steps:
- setup_remote_docker
- &remote-docker
setup_remote_docker:
version: 20.10.14
- checkout:
path: hugo
- &git-config
@@ -58,7 +60,7 @@ jobs:
environment:
<<: [*buildenv]
docker:
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22301.20401
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22200.20201
steps:
- *restore-cache
- &attach-workspace
-48
View File
@@ -1,48 +0,0 @@
name: Build Docker image
on:
release:
types: [published]
pull_request:
permissions:
packages: write
env:
REGISTRY_IMAGE: ghcr.io/gohugoio/hugo
jobs:
build:
runs-on: ubuntu-latest
steps:
- 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 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
id: build
uses: docker/build-push-action@16ebe778df0e7752d2cfcbd924afdbbd89c1a755 # v6.6.1
with:
context: .
provenance: mode=max
sbom: true
push: ${{ github.event_name != 'pull_request' }}
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+2 -2
View File
@@ -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
+15 -15
View File
@@ -6,23 +6,23 @@ name: Test
env:
GOPROXY: https://proxy.golang.org
GO111MODULE: on
SASS_VERSION: 1.80.3
DART_SASS_SHA_LINUX: 7c933edbad0a7d389192c5b79393485c088bd2c4398e32f5754c32af006a9ffd
DART_SASS_SHA_MACOS: 79e060b0e131c3bb3c16926bafc371dc33feab122bfa8c01aa337a072097967b
DART_SASS_SHA_WINDOWS: 0bc4708b37cd1bac4740e83ac5e3176e66b774f77fd5dd364da5b5cfc9bfb469
SASS_VERSION: 1.63.2
DART_SASS_SHA_LINUX: 3ea33c95ad5c35fda6e9a0956199eef38a398f496cfb8750e02479d7d1dd42af
DART_SASS_SHA_MACOS: 11c70f259836b250b44a9cb57fed70e030f21f45069b467d371685855f1eb4f0
DART_SASS_SHA_WINDOWS: cd8cd36a619dd8e27f93d3186c52d70eb7d69472aa6c85f5094b29693e773f64
permissions:
contents: read
jobs:
test:
strategy:
matrix:
go-version: [1.22.x, 1.23.x]
os: [ubuntu-latest, windows-latest] # macos disabled for now because of disk space issues.
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
@@ -112,17 +112,17 @@ jobs:
sass --version;
mage -v check;
env:
HUGO_BUILD_TAGS: extended,withdeploy
HUGO_BUILD_TAGS: extended
- if: matrix.os == 'windows-latest'
# See issue #11052. We limit the build to regular test (no -race flag) on Windows for now.
name: Test
run: |
mage -v test;
env:
HUGO_BUILD_TAGS: extended,withdeploy
HUGO_BUILD_TAGS: extended
- name: Build tags
run: |
go install -tags extended
go install -tags extended,nodeploy
- if: matrix.os == 'ubuntu-latest'
name: Build for dragonfly
run: |
+1 -3
View File
@@ -1,5 +1,3 @@
*.test
imports.*
dist/
public/
imports.*
+24 -78
View File
@@ -2,98 +2,44 @@
# Twitter: https://twitter.com/gohugoio
# Website: https://gohugo.io/
ARG GO_VERSION="1.23.2"
ARG ALPINE_VERSION="3.20"
ARG DART_SASS_VERSION="1.79.3"
FROM golang:1.21-alpine AS build
FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.5.0 AS xx
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS gobuild
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS gorun
# Optionally set HUGO_BUILD_TAGS to "extended" or "nodeploy" when building like so:
# docker build --build-arg HUGO_BUILD_TAGS=extended .
ARG HUGO_BUILD_TAGS
FROM gobuild AS build
RUN apk add clang lld
# Set up cross-compilation helpers
COPY --from=xx / /
ARG TARGETPLATFORM
RUN xx-apk add musl-dev gcc g++
# Optionally set HUGO_BUILD_TAGS to "none" or "withdeploy" when building like so:
# docker build --build-arg HUGO_BUILD_TAGS=withdeploy .
#
# We build the extended version by default.
ARG HUGO_BUILD_TAGS="extended"
ENV CGO_ENABLED=1
ENV GOPROXY=https://proxy.golang.org
ENV GOCACHE=/root/.cache/go-build
ENV GOMODCACHE=/go/pkg/mod
ARG TARGETPLATFORM
ARG CGO=1
ENV CGO_ENABLED=${CGO}
ENV GOOS=linux
ENV GO111MODULE=on
WORKDIR /go/src/github.com/gohugoio/hugo
# For --mount=type=cache the value of target is the default cache id, so
# for the go mod cache it would be good if we could share it with other Go images using the same setup,
# but the go build cache needs to be per platform.
# See this comment: https://github.com/moby/buildkit/issues/1706#issuecomment-702238282
RUN --mount=target=. \
--mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build,id=go-build-$TARGETPLATFORM <<EOT
set -ex
xx-go build -tags "$HUGO_BUILD_TAGS" -ldflags "-s -w -X github.com/gohugoio/hugo/common/hugo.vendorInfo=docker" -o /usr/bin/hugo
xx-verify /usr/bin/hugo
EOT
COPY . /go/src/github.com/gohugoio/hugo/
# dart-sass downloads the dart-sass runtime dependency
FROM alpine:${ALPINE_VERSION} AS dart-sass
ARG TARGETARCH
ARG DART_SASS_VERSION
ARG DART_ARCH=${TARGETARCH/amd64/x64}
WORKDIR /out
ADD https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-${DART_ARCH}.tar.gz .
RUN tar -xf dart-sass-${DART_SASS_VERSION}-linux-${DART_ARCH}.tar.gz
# gcc/g++ are required to build SASS libraries for extended version
RUN apk update && \
apk add --no-cache gcc g++ musl-dev git && \
go install github.com/magefile/mage
FROM gorun AS final
RUN mage hugo && mage install
COPY --from=build /usr/bin/hugo /usr/bin/hugo
# ---
# libc6-compat are required for extended libraries (libsass, libwebp).
RUN apk add --no-cache \
libc6-compat \
git \
runuser \
nodejs \
npm
FROM alpine:3.18
RUN mkdir -p /var/hugo/bin /cache && \
addgroup -Sg 1000 hugo && \
adduser -Sg hugo -u 1000 -h /var/hugo hugo && \
chown -R hugo: /var/hugo /cache && \
# For the Hugo's Git integration to work.
runuser -u hugo -- git config --global --add safe.directory /project && \
# See https://github.com/gohugoio/hugo/issues/9810
runuser -u hugo -- git config --global core.quotepath false
COPY --from=build /go/bin/hugo /usr/bin/hugo
USER hugo:hugo
VOLUME /project
WORKDIR /project
ENV HUGO_CACHEDIR=/cache
ENV PATH="/var/hugo/bin:$PATH"
# libc6-compat & libstdc++ are required for extended SASS libraries
# ca-certificates are required to fetch outside resources (like Twitter oEmbeds)
RUN apk update && \
apk add --no-cache ca-certificates libc6-compat libstdc++ git
COPY scripts/docker/entrypoint.sh /entrypoint.sh
COPY --from=dart-sass /out/dart-sass /var/hugo/bin/dart-sass
# Update PATH to reflect the new dependencies.
# For more complex setups, we should probably find a way to
# delegate this to the script itself, but this will have to do for now.
# Also, the dart-sass binary is a little special, other binaries can be put/linked
# directly in /var/hugo/bin.
ENV PATH="/var/hugo/bin/dart-sass:$PATH"
VOLUME /site
WORKDIR /site
# Expose port for live server
EXPOSE 1313
ENTRYPOINT ["/entrypoint.sh"]
ENTRYPOINT ["hugo"]
CMD ["--help"]
+112 -68
View File
@@ -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
@@ -34,6 +34,7 @@ A fast and flexible static site generator built with love by [bep], [spf13], and
[![Go Report Card](https://goreportcard.com/badge/github.com/gohugoio/hugo)](https://goreportcard.com/report/github.com/gohugoio/hugo)
[Website] | [Installation] | [Documentation] | [Support] | [Contributing] | <a rel="me" href="https://fosstodon.org/@gohugoio">Mastodon</a>
## Overview
Hugo is a [static site generator] written in [Go], optimized for speed and designed for flexibility. With its advanced templating system and fast asset pipelines, Hugo renders a complete site in seconds, often less.
@@ -51,23 +52,18 @@ Use Hugo's embedded web server during development to instantly see changes to co
Hugo's fast asset pipelines include:
- Image processing &ndash; Convert, resize, crop, rotate, adjust colors, apply filters, overlay text and images, and extract EXIF data
- JavaScript bundling &ndash; Transpile TypeScript and JSX to JavaScript, bundle, tree shake, minify, create source maps, and perform SRI hashing.
- Sass processing &ndash; Transpile Sass to CSS, bundle, tree shake, minify, create source maps, perform SRI hashing, and integrate with PostCSS
- Tailwind CSS processing &ndash; Compile Tailwind CSS utility classes into standard CSS, bundle, tree shake, optimize, minify, perform SRI hashing, and integrate with PostCSS
- CSS bundling &ndash; transpilation (Sass), tree shaking, minification, source maps, SRI hashing, and PostCSS integration
- JavaScript bundling &ndash; transpilation (TypeScript, JSX), tree shaking, minification, source maps, and SRI hashing
- Image processing &ndash; 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>&nbsp;</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>
&nbsp;&nbsp;&nbsp;
<a href="https://www.jetbrains.com/go/?utm_source=OSS&utm_medium=referral&utm_campaign=hugo" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/goland.svg" width="200" alt="The complete IDE crafted for professional Go developers."></a>
</p>
<p>&nbsp;</p>
## Installation
@@ -101,9 +97,6 @@ Build the extended edition:
```text
CGO_ENABLED=1 go install -tags extended github.com/gohugoio/hugo@latest
```
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=gohugoio/hugo&type=Timeline)](https://star-history.com/#gohugoio/hugo&Timeline)
## Documentation
@@ -147,98 +140,149 @@ Hugo stands on the shoulders of great open source libraries. Run `hugo env --log
<summary>See current dependencies</summary>
```text
cloud.google.com/go/compute/metadata="v0.2.3"
cloud.google.com/go/iam="v1.1.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.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/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.6.0"
github.com/bep/gitmap="v1.1.2"
github.com/bep/goat="v0.5.0"
github.com/bep/godartsass/v2="v2.3.0"
github.com/bep/golibsass="v1.2.0"
github.com/bep/godartsass/v2="v2.0.0"
github.com/bep/godartsass="v1.2.0"
github.com/bep/golibsass="v1.1.1"
github.com/bep/gowebp="v0.3.0"
github.com/bep/imagemeta="v0.8.3"
github.com/bep/lazycache="v0.7.0"
github.com/bep/logg="v0.4.0"
github.com/bep/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/cespare/xxhash/v2="v2.3.0"
github.com/clbanning/mxj/v2="v2.7.0"
github.com/cli/safeexec="v1.0.1"
github.com/cpuguy83/go-md2man/v2="v2.0.4"
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/evanw/esbuild="v0.24.0"
github.com/fatih/color="v1.18.0"
github.com/dlclark/regexp2="v1.10.0"
github.com/dustin/go-humanize="v1.0.1"
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.8.0"
github.com/getkin/kin-openapi="v0.123.0"
github.com/fsnotify/fsnotify="v1.7.0"
github.com/getkin/kin-openapi="v0.120.0"
github.com/ghodss/yaml="v1.0.0"
github.com/go-openapi/jsonpointer="v0.20.2"
github.com/go-openapi/swag="v0.22.8"
github.com/gobuffalo/flect="v1.0.3"
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/hashstructure="v0.1.0"
github.com/gohugoio/httpcache="v0.7.0"
github.com/gohugoio/hugo-goldmark-extensions/extras="v0.2.0"
github.com/gohugoio/hugo-goldmark-extensions/passthrough="v0.3.0"
github.com/gohugoio/locales="v0.14.0"
github.com/gohugoio/localescompressed="v1.0.1"
github.com/golang-jwt/jwt/v4="v4.5.0"
github.com/golang/groupcache="v0.0.0-20210331224755-41bb18bfe9da"
github.com/golang/protobuf="v1.5.3"
github.com/google/go-cmp="v0.6.0"
github.com/gorilla/websocket="v1.5.3"
github.com/hairyhenderson/go-codeowners="v0.6.1"
github.com/hashicorp/golang-lru/v2="v2.0.7"
github.com/google/s2a-go="v0.1.7"
github.com/google/uuid="v1.4.0"
github.com/google/wire="v0.5.0"
github.com/googleapis/enterprise-certificate-proxy="v0.3.2"
github.com/googleapis/gax-go/v2="v2.12.0"
github.com/gorilla/websocket="v1.5.1"
github.com/hairyhenderson/go-codeowners="v0.4.0"
github.com/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"
github.com/josharian/intern="v1.0.0"
github.com/kr/pretty="v0.3.1"
github.com/kr/text="v0.2.0"
github.com/kyokomi/emoji/v2="v2.2.13"
github.com/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/mapstructure="v1.5.1-0.20231216201459-8508981c8b6c"
github.com/mitchellh/hashstructure="v1.1.0"
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.3"
github.com/pelletier/go-toml/v2="v2.1.0"
github.com/perimeterx/marshmallow="v1.1.5"
github.com/pkg/browser="v0.0.0-20240102092130-5ac0b6a4141c"
github.com/pkg/browser="v0.0.0-20210911075715-681adbf594b8"
github.com/pkg/errors="v0.9.1"
github.com/rogpeppe/go-internal="v1.13.1"
github.com/rogpeppe/go-internal="v1.11.0"
github.com/russross/blackfriday/v2="v2.1.0"
github.com/sass/dart-sass/compiler="1.81.0"
github.com/sass/dart-sass/implementation="1.81.0"
github.com/sass/dart-sass/protocol="3.1.0"
github.com/spf13/afero="v1.11.0"
github.com/spf13/cast="v1.7.0"
github.com/spf13/cobra="v1.8.1"
github.com/spf13/fsync="v0.10.1"
github.com/rwcarlsen/goexif="v0.0.0-20190401172101-9e8deecbddbd"
github.com/sanity-io/litter="v1.5.5"
github.com/sass/dart-sass/compiler="1.63.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.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.21.1"
github.com/tdewolff/parse/v2="v2.7.18"
github.com/tetratelabs/wazero="v1.8.1"
github.com/yuin/goldmark-emoji="v1.0.4"
github.com/yuin/goldmark="v1.7.8"
github.com/tdewolff/minify/v2="v2.20.7"
github.com/tdewolff/parse/v2="v2.7.5"
github.com/webmproject/libwebp="v1.3.2"
github.com/yuin/goldmark-emoji="v1.0.2"
github.com/yuin/goldmark="v1.6.0"
go.opencensus.io="v0.24.0"
go.uber.org/atomic="v1.11.0"
go.uber.org/automaxprocs="v1.5.3"
golang.org/x/crypto="v0.29.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.22.0"
golang.org/x/mod="v0.22.0"
golang.org/x/net="v0.31.0"
golang.org/x/sync="v0.9.0"
golang.org/x/sys="v0.27.0"
golang.org/x/text="v0.20.0"
golang.org/x/tools="v0.27.0"
google.golang.org/protobuf="v1.35.1"
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.31.0"
gopkg.in/yaml.v2="v2.4.0"
gopkg.in/yaml.v3="v3.0.1"
howett.net/plist="v1.0.0"
Executable
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# allow user to override go executable by running as GOEXE=xxx make ...
GOEXE="${GOEXE-go}"
# Convenience script to
# - For a given branch
# - Run benchmark tests for a given package
# - Do the same for master
# - then compare the two runs with benchcmp
benchFilter=".*"
if (( $# < 2 ));
then
echo "USAGE: ./bench.sh <git-branch> <package-to-bench> (and <benchmark filter> (regexp, optional))"
exit 1
fi
if [ $# -eq 3 ]; then
benchFilter=$3
fi
BRANCH=$1
PACKAGE=$2
git checkout $BRANCH
"${GOEXE}" test -test.run=NONE -bench="$benchFilter" -test.benchmem=true ./$PACKAGE > /tmp/bench-$PACKAGE-$BRANCH.txt
git checkout master
"${GOEXE}" test -test.run=NONE -bench="$benchFilter" -test.benchmem=true ./$PACKAGE > /tmp/bench-$PACKAGE-master.txt
benchcmp /tmp/bench-$PACKAGE-master.txt /tmp/bench-$PACKAGE-$BRANCH.txt
Executable
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
# allow user to override go executable by running as GOEXE=xxx make ...
GOEXE="${GOEXE-go}"
# Send in a regexp matching the benchmarks you want to run, i.e. './benchSite.sh "YAML"'.
# Note the quotes, which will be needed for more complex expressions.
# The above will run all variations, but only for front matter YAML.
echo "Running with BenchmarkSiteBuilding/${1}"
"${GOEXE}" test -run="NONE" -bench="BenchmarkSiteBuilding/${1}" -test.benchmem=true ./hugolib -memprofile mem.prof -count 3 -cpuprofile cpu.prof
Executable
+1
View File
@@ -0,0 +1 @@
gobench -package=./hugolib -bench="BenchmarkSiteNew/Deep_content_tree"
Executable
+1
View File
@@ -0,0 +1 @@
docker run --rm --mount type=bind,source="$(pwd)",target=/hugo -w /hugo -i -t bepsays/ci-goreleaser:1.11-2 /bin/bash
+10 -33
View File
@@ -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 {
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)
@@ -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,15 +135,10 @@ 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 {
@@ -430,25 +420,12 @@ func (p *Partition[K, V]) doGetOrCreateWitTimeout(key K, duration time.Duration,
errch := make(chan error, 1)
go func() {
var (
v V
err error
)
defer func() {
if r := recover(); r != nil {
if rerr, ok := r.(error); ok {
err = rerr
} else {
err = fmt.Errorf("panic: %v", r)
}
}
if err != nil {
errch <- err
} else {
resultch <- v
}
}()
v, _, err = p.c.GetOrCreate(key, create)
v, _, err := p.c.GetOrCreate(key, create)
if err != nil {
errch <- err
return
}
resultch <- v
}()
select {
-55
View File
@@ -14,11 +14,8 @@
package dynacache
import (
"errors"
"fmt"
"path/filepath"
"testing"
"time"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/loggers"
@@ -168,58 +165,6 @@ func TestClear(t *testing.T) {
cache.adjustCurrentMaxSize()
}
func TestPanicInCreate(t *testing.T) {
t.Parallel()
c := qt.New(t)
cache := newTestCache(t)
p1 := GetOrCreatePartition[string, testItem](cache, "/aaaa/bbbb", OptionsPartition{Weight: 30, ClearWhen: ClearOnRebuild})
willPanic := func(i int) func() {
return func() {
p1.GetOrCreate(fmt.Sprintf("panic-%d", i), func(key string) (testItem, error) {
panic(errors.New(key))
})
}
}
// GetOrCreateWitTimeout needs to recover from panics in the create func.
willErr := func(i int) error {
_, err := p1.GetOrCreateWitTimeout(fmt.Sprintf("error-%d", i), 10*time.Second, func(key string) (testItem, error) {
return testItem{}, errors.New(key)
})
return err
}
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
c.Assert(willPanic(i), qt.PanicMatches, fmt.Sprintf("panic-%d", i))
c.Assert(willErr(i), qt.ErrorMatches, fmt.Sprintf("error-%d", i))
}
}
// Test the same keys again without the panic.
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
v, err := p1.GetOrCreate(fmt.Sprintf("panic-%d", i), func(key string) (testItem, error) {
return testItem{
name: key,
}, nil
})
c.Assert(err, qt.IsNil)
c.Assert(v.name, qt.Equals, fmt.Sprintf("panic-%d", i))
v, err = p1.GetOrCreateWitTimeout(fmt.Sprintf("error-%d", i), 10*time.Second, func(key string) (testItem, error) {
return testItem{
name: key,
}, nil
})
c.Assert(err, qt.IsNil)
c.Assert(v.name, qt.Equals, fmt.Sprintf("error-%d", i))
}
}
}
func TestAdjustCurrentMaxSize(t *testing.T) {
t.Parallel()
c := qt.New(t)
+1 -11
View File
@@ -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
View File
@@ -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]
+1
View File
@@ -83,6 +83,7 @@ func (c *Config) Compile() (ConfigCompiled, error) {
}
// PollConfig holds the configuration for polling remote resources to detect changes in watch mode.
// TODO1 make sure this enabled only in watch mode.
type PollConfig struct {
// What remote resources to apply this configuration to.
For GlobMatcher
+52 -81
View File
@@ -39,9 +39,9 @@ import (
"github.com/gohugoio/hugo/common/hstrings"
"github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/allconfig"
"github.com/gohugoio/hugo/deps"
@@ -66,12 +66,6 @@ func Execute(args []string) error {
}
args = mapLegacyArgs(args)
cd, err := x.Execute(context.Background(), args)
if cd != nil {
if closer, ok := cd.Root.Command.(types.Closer); ok {
closer.Close()
}
}
if err != nil {
if err == errHelp {
cd.CobraCommand.Help()
@@ -94,17 +88,11 @@ type commonConfig struct {
fs *hugofs.Fs
}
type configKey struct {
counter int32
ignoreModulesDoesNotExists bool
}
// This is the root command.
type rootCommand struct {
Printf func(format string, v ...interface{})
Println func(a ...interface{})
StdOut io.Writer
StdErr io.Writer
Out io.Writer
logger loggers.Logger
@@ -113,8 +101,8 @@ type rootCommand struct {
// Some, but not all commands need access to these.
// Some needs more than one, so keep them in a small cache.
commonConfigs *lazycache.Cache[configKey, *commonConfig]
hugoSites *lazycache.Cache[configKey, *hugolib.HugoSites]
commonConfigs *lazycache.Cache[int32, *commonConfig]
hugoSites *lazycache.Cache[int32, *hugolib.HugoSites]
// changesFromBuild received from Hugo in watch mode.
changesFromBuild chan []identity.Identity
@@ -141,6 +129,8 @@ type rootCommand struct {
logLevel string
verbose bool
debug bool
quiet bool
devMode bool // Hidden flag.
@@ -154,18 +144,6 @@ func (r *rootCommand) isVerbose() bool {
return r.logger.Level() <= logg.LevelInfo
}
func (r *rootCommand) Close() error {
if r.hugoSites != nil {
r.hugoSites.DeleteFunc(func(key configKey, value *hugolib.HugoSites) bool {
if value != nil {
value.Close()
}
return false
})
}
return nil
}
func (r *rootCommand) Build(cd *simplecobra.Commandeer, bcfg hugolib.BuildCfg, cfg config.Provider) (*hugolib.HugoSites, error) {
h, err := r.Hugo(cfg)
if err != nil {
@@ -182,18 +160,17 @@ func (r *rootCommand) Commands() []simplecobra.Commander {
return r.commands
}
func (r *rootCommand) ConfigFromConfig(key configKey, oldConf *commonConfig) (*commonConfig, error) {
cc, _, err := r.commonConfigs.GetOrCreate(key, func(key configKey) (*commonConfig, error) {
func (r *rootCommand) ConfigFromConfig(key int32, oldConf *commonConfig) (*commonConfig, error) {
cc, _, err := r.commonConfigs.GetOrCreate(key, func(key int32) (*commonConfig, error) {
fs := oldConf.fs
configs, err := allconfig.LoadConfig(
allconfig.ConfigSourceDescriptor{
Flags: oldConf.cfg,
Fs: fs.Source,
Filename: r.cfgFile,
ConfigDir: r.cfgDir,
Logger: r.logger,
Environment: r.environment,
IgnoreModuleDoesNotExist: key.ignoreModulesDoesNotExists,
Flags: oldConf.cfg,
Fs: fs.Source,
Filename: r.cfgFile,
ConfigDir: r.cfgDir,
Logger: r.logger,
Environment: r.environment,
},
)
if err != nil {
@@ -216,11 +193,11 @@ func (r *rootCommand) ConfigFromConfig(key configKey, oldConf *commonConfig) (*c
return cc, err
}
func (r *rootCommand) ConfigFromProvider(key configKey, cfg config.Provider) (*commonConfig, error) {
func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commonConfig, error) {
if cfg == nil {
panic("cfg must be set")
}
cc, _, err := r.commonConfigs.GetOrCreate(key, func(key configKey) (*commonConfig, error) {
cc, _, err := r.commonConfigs.GetOrCreate(key, func(key int32) (*commonConfig, error) {
var dir string
if r.source != "" {
dir, _ = filepath.Abs(r.source)
@@ -243,13 +220,12 @@ func (r *rootCommand) ConfigFromProvider(key configKey, cfg config.Provider) (*c
// Load the config first to allow publishDir to be configured in config file.
configs, err := allconfig.LoadConfig(
allconfig.ConfigSourceDescriptor{
Flags: cfg,
Fs: hugofs.Os,
Filename: r.cfgFile,
ConfigDir: r.cfgDir,
Environment: r.environment,
Logger: r.logger,
IgnoreModuleDoesNotExist: key.ignoreModulesDoesNotExists,
Flags: cfg,
Fs: hugofs.Os,
Filename: r.cfgFile,
ConfigDir: r.cfgDir,
Environment: r.environment,
Logger: r.logger,
},
)
if err != nil {
@@ -331,8 +307,7 @@ func (r *rootCommand) ConfigFromProvider(key configKey, cfg config.Provider) (*c
}
func (r *rootCommand) HugFromConfig(conf *commonConfig) (*hugolib.HugoSites, error) {
k := configKey{counter: r.configVersionID.Load()}
h, _, err := r.hugoSites.GetOrCreate(k, func(key configKey) (*hugolib.HugoSites, error) {
h, _, err := r.hugoSites.GetOrCreate(r.configVersionID.Load(), func(key int32) (*hugolib.HugoSites, error) {
depsCfg := r.newDepsConfig(conf)
return hugolib.NewHugoSites(depsCfg)
})
@@ -340,12 +315,7 @@ func (r *rootCommand) HugFromConfig(conf *commonConfig) (*hugolib.HugoSites, err
}
func (r *rootCommand) Hugo(cfg config.Provider) (*hugolib.HugoSites, error) {
return r.getOrCreateHugo(cfg, false)
}
func (r *rootCommand) getOrCreateHugo(cfg config.Provider, ignoreModuleDoesNotExist bool) (*hugolib.HugoSites, error) {
k := configKey{counter: r.configVersionID.Load(), ignoreModulesDoesNotExists: ignoreModuleDoesNotExist}
h, _, err := r.hugoSites.GetOrCreate(k, func(key configKey) (*hugolib.HugoSites, error) {
h, _, err := r.hugoSites.GetOrCreate(r.configVersionID.Load(), func(key int32) (*hugolib.HugoSites, error) {
conf, err := r.ConfigFromProvider(key, cfg)
if err != nil {
return nil, err
@@ -357,7 +327,7 @@ func (r *rootCommand) getOrCreateHugo(cfg config.Provider, ignoreModuleDoesNotEx
}
func (r *rootCommand) newDepsConfig(conf *commonConfig) deps.DepsCfg {
return deps.DepsCfg{Configs: conf.configs, Fs: conf.fs, StdOut: r.logger.StdOut(), StdErr: r.logger.StdErr(), LogLevel: r.logger.Level(), ChangesFromBuild: r.changesFromBuild}
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 {
@@ -422,23 +392,21 @@ func (r *rootCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args
}
func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
r.StdOut = os.Stdout
r.StdErr = os.Stderr
r.Out = os.Stdout
if r.quiet {
r.StdOut = io.Discard
r.StdErr = io.Discard
r.Out = io.Discard
}
// Used by mkcert (server).
log.SetOutput(r.StdOut)
log.SetOutput(r.Out)
r.Printf = func(format string, v ...interface{}) {
if !r.quiet {
fmt.Fprintf(r.StdOut, format, v...)
fmt.Fprintf(r.Out, format, v...)
}
}
r.Println = func(a ...interface{}) {
if !r.quiet {
fmt.Fprintln(r.StdOut, a...)
fmt.Fprintln(r.Out, a...)
}
}
_, running := runner.Command.(*serverCommand)
@@ -450,11 +418,11 @@ func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
r.changesFromBuild = make(chan []identity.Identity, 10)
r.commonConfigs = lazycache.New(lazycache.Options[configKey, *commonConfig]{MaxEntries: 5})
r.commonConfigs = lazycache.New(lazycache.Options[int32, *commonConfig]{MaxEntries: 5})
// We don't want to keep stale HugoSites in memory longer than needed.
r.hugoSites = lazycache.New(lazycache.Options[configKey, *hugolib.HugoSites]{
r.hugoSites = lazycache.New(lazycache.Options[int32, *hugolib.HugoSites]{
MaxEntries: 1,
OnEvict: func(key configKey, value *hugolib.HugoSites) {
OnEvict: func(key int32, value *hugolib.HugoSites) {
value.Close()
runtime.GC()
},
@@ -482,21 +450,32 @@ func (r *rootCommand) createLogger(running bool) (loggers.Logger, error) {
default:
return nil, fmt.Errorf("invalid log level: %q, must be one of debug, warn, info or error", r.logLevel)
}
} else {
if r.verbose {
hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0")
hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0")
level = logg.LevelInfo
}
if r.debug {
hugo.Deprecate("--debug", "use --logLevel debug", "v0.114.0")
level = logg.LevelDebug
}
}
}
optsLogger := loggers.Options{
DistinctLevel: logg.LevelWarn,
Level: level,
StdOut: r.StdOut,
StdErr: r.StdErr,
Stdout: r.Out,
Stderr: r.Out,
StoreErrors: running,
}
return loggers.New(optsLogger), nil
}
func (r *rootCommand) resetLogs() {
func (r *rootCommand) Reset() {
r.logger.Reset()
loggers.Log().Reset()
}
@@ -507,26 +486,16 @@ func (r *rootCommand) IsTestRun() bool {
}
func (r *rootCommand) Init(cd *simplecobra.Commandeer) error {
return r.initRootCommand("", cd)
}
func (r *rootCommand) initRootCommand(subCommandName string, cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
commandName := "hugo"
if subCommandName != "" {
commandName = subCommandName
}
cmd.Use = fmt.Sprintf("%s [flags]", commandName)
cmd.Short = "Build your site"
cmd.Long = `COMMAND_NAME is the main command, used to build your Hugo site.
cmd.Use = "hugo [flags]"
cmd.Short = "hugo builds your site"
cmd.Long = `hugo is the main command, used to build your Hugo site.
Hugo is a Fast and Flexible Static Site Generator
built with love by spf13 and friends in Go.
Complete documentation is available at https://gohugo.io/.`
cmd.Long = strings.ReplaceAll(cmd.Long, "COMMAND_NAME", commandName)
// Configure persistent flags
cmd.PersistentFlags().StringVarP(&r.source, "source", "s", "", "filesystem path to read files relative from")
_ = cmd.MarkFlagDirname("source")
@@ -549,6 +518,8 @@ Complete documentation is available at https://gohugo.io/.`
cmd.PersistentFlags().BoolVar(&r.quiet, "quiet", false, "build in quiet mode")
cmd.PersistentFlags().BoolVarP(&r.renderToMemory, "renderToMemory", "M", false, "render to memory (mostly useful when running the server)")
cmd.PersistentFlags().BoolVarP(&r.verbose, "verbose", "v", false, "verbose output")
cmd.PersistentFlags().BoolVarP(&r.debug, "debug", "", false, "debug output")
cmd.PersistentFlags().BoolVarP(&r.devMode, "devMode", "", false, "only used for internal testing, flag hidden.")
cmd.PersistentFlags().StringVar(&r.logLevel, "logLevel", "", "log level (debug|info|warn|error)")
_ = cmd.RegisterFlagCompletionFunc("logLevel", cobra.FixedCompletions([]string{"debug", "info", "warn", "error"}, cobra.ShellCompDirectiveNoFileComp))
-33
View File
@@ -14,8 +14,6 @@
package commands
import (
"context"
"github.com/bep/simplecobra"
)
@@ -23,7 +21,6 @@ import (
func newExec() (*simplecobra.Exec, error) {
rootCmd := &rootCommand{
commands: []simplecobra.Commander{
newHugoBuildCmd(),
newVersionCmd(),
newEnvCommand(),
newServerCommand(),
@@ -41,33 +38,3 @@ func newExec() (*simplecobra.Exec, error) {
return simplecobra.New(rootCmd)
}
func newHugoBuildCmd() simplecobra.Commander {
return &hugoBuildCommand{}
}
// hugoBuildCommand just delegates to the rootCommand.
type hugoBuildCommand struct {
rootCmd *rootCommand
}
func (c *hugoBuildCommand) Commands() []simplecobra.Commander {
return nil
}
func (c *hugoBuildCommand) Name() string {
return "build"
}
func (c *hugoBuildCommand) Init(cd *simplecobra.Commandeer) error {
c.rootCmd = cd.Root.Command.(*rootCommand)
return c.rootCmd.initRootCommand("build", cd)
}
func (c *hugoBuildCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
return c.rootCmd.PreRun(cd, runner)
}
func (c *hugoBuildCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
return c.rootCmd.Run(ctx, cd, args)
}
+4 -4
View File
@@ -58,7 +58,7 @@ func (c *configCommand) Name() string {
}
func (c *configCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
conf, err := c.r.ConfigFromProvider(configKey{counter: c.r.configVersionID.Load()}, flagsToCfg(cd, nil))
conf, err := c.r.ConfigFromProvider(c.r.configVersionID.Load(), flagsToCfg(cd, nil))
if err != nil {
return err
}
@@ -110,8 +110,8 @@ func (c *configCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, arg
func (c *configCommand) Init(cd *simplecobra.Commandeer) error {
c.r = cd.Root.Command.(*rootCommand)
cmd := cd.CobraCommand
cmd.Short = "Display site configuration"
cmd.Long = `Display site configuration, both default and custom settings.`
cmd.Short = "Print the site configuration"
cmd.Long = `Print the site configuration, both default and custom settings.`
cmd.Flags().StringVar(&c.format, "format", "toml", "preferred file format (toml, yaml or json)")
_ = cmd.RegisterFlagCompletionFunc("format", cobra.FixedCompletions([]string{"toml", "yaml", "json"}, cobra.ShellCompDirectiveNoFileComp))
cmd.Flags().StringVar(&c.lang, "lang", "", "the language to display config for. Defaults to the first language defined.")
@@ -209,7 +209,7 @@ func (c *configMountsCommand) Name() string {
func (c *configMountsCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
r := c.configCmd.r
conf, err := r.ConfigFromProvider(configKey{counter: c.r.configVersionID.Load()}, flagsToCfg(cd, nil))
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, nil))
if err != nil {
return err
}
+2 -2
View File
@@ -105,8 +105,8 @@ func (c *convertCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, ar
func (c *convertCommand) Init(cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
cmd.Short = "Convert front matter to another format"
cmd.Long = `Convert front matter to another format.
cmd.Short = "Convert your content to different formats"
cmd.Long = `Convert your content (e.g. front matter) to different formats.
See convert's subcommands toJSON, toTOML and toYAML for more information.`
+29 -5
View File
@@ -11,8 +11,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build withdeploy
// +build withdeploy
//go:build !nodeploy
// +build !nodeploy
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
@@ -20,6 +33,7 @@ import (
"context"
"github.com/gohugoio/hugo/deploy"
"github.com/gohugoio/hugo/deploy/deployconfig"
"github.com/bep/simplecobra"
"github.com/spf13/cobra"
@@ -28,8 +42,8 @@ import (
func newDeployCommand() simplecobra.Commander {
return &simpleCommand{
name: "deploy",
short: "Deploy your site to a cloud provider",
long: `Deploy your site to a cloud provider
short: "Deploy your site to a Cloud provider.",
long: `Deploy your site to a Cloud provider.
See https://gohugo.io/hosting-and-deployment/hugo-deploy/ for detailed
documentation.
@@ -46,7 +60,17 @@ documentation.
return deployer.Deploy(ctx)
},
withc: func(cmd *cobra.Command, r *rootCommand) {
applyDeployFlags(cmd, r)
cmd.ValidArgsFunction = cobra.NoFileCompletions
cmd.Flags().String("target", "", "target deployment from deployments section in config file; defaults to the first one")
_ = cmd.RegisterFlagCompletionFunc("target", cobra.NoFileCompletions)
cmd.Flags().Bool("confirm", false, "ask for confirmation before making changes to the target")
cmd.Flags().Bool("dryRun", false, "dry run")
cmd.Flags().Bool("force", false, "force upload of all files")
cmd.Flags().Bool("invalidateCDN", deployconfig.DefaultConfig.InvalidateCDN, "invalidate the CDN cache listed in the deployment target")
cmd.Flags().Int("maxDeletes", deployconfig.DefaultConfig.MaxDeletes, "maximum # of files to delete, or -1 to disable")
_ = cmd.RegisterFlagCompletionFunc("maxDeletes", cobra.NoFileCompletions)
cmd.Flags().Int("workers", deployconfig.DefaultConfig.Workers, "number of workers to transfer files. defaults to 10")
_ = cmd.RegisterFlagCompletionFunc("workers", cobra.NoFileCompletions)
},
}
}
-33
View File
@@ -1,33 +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 commands
import (
"github.com/gohugoio/hugo/deploy/deployconfig"
"github.com/spf13/cobra"
)
func applyDeployFlags(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
cmd.Flags().String("target", "", "target deployment from deployments section in config file; defaults to the first one")
_ = cmd.RegisterFlagCompletionFunc("target", cobra.NoFileCompletions)
cmd.Flags().Bool("confirm", false, "ask for confirmation before making changes to the target")
cmd.Flags().Bool("dryRun", false, "dry run")
cmd.Flags().Bool("force", false, "force upload of all files")
cmd.Flags().Bool("invalidateCDN", deployconfig.DefaultConfig.InvalidateCDN, "invalidate the CDN cache listed in the deployment target")
cmd.Flags().Int("maxDeletes", deployconfig.DefaultConfig.MaxDeletes, "maximum # of files to delete, or -1 to disable")
_ = cmd.RegisterFlagCompletionFunc("maxDeletes", cobra.NoFileCompletions)
cmd.Flags().Int("workers", deployconfig.DefaultConfig.Workers, "number of workers to transfer files. defaults to 10")
_ = cmd.RegisterFlagCompletionFunc("workers", cobra.NoFileCompletions)
}
+3 -5
View File
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !withdeploy
// +build !withdeploy
//go:build nodeploy
// +build nodeploy
// Copyright 2024 The Hugo Authors. All rights reserved.
//
@@ -31,7 +31,6 @@ package commands
import (
"context"
"errors"
"github.com/bep/simplecobra"
"github.com/spf13/cobra"
@@ -41,10 +40,9 @@ func newDeployCommand() simplecobra.Commander {
return &simpleCommand{
name: "deploy",
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
return errors.New("deploy not supported in this version of Hugo; install a release with 'withdeploy' in the archive filename or build yourself with the 'withdeploy' build tag. Also see https://github.com/gohugoio/hugo/pull/12995")
return nil
},
withc: func(cmd *cobra.Command, r *rootCommand) {
applyDeployFlags(cmd, r)
cmd.Hidden = true
},
}
+4 -4
View File
@@ -25,8 +25,8 @@ import (
func newEnvCommand() simplecobra.Commander {
return &simpleCommand{
name: "env",
short: "Display version and environment info",
long: "Display version and environment info. This is useful in Hugo bug reports",
short: "Print Hugo version and environment info",
long: "Print Hugo version and environment info. This is useful in Hugo bug reports",
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
r.Printf("%s\n", hugo.BuildVersionString())
r.Printf("GOOS=%q\n", runtime.GOOS)
@@ -61,8 +61,8 @@ func newVersionCmd() simplecobra.Commander {
r.Println(hugo.BuildVersionString())
return nil
},
short: "Display version",
long: "Display version and environment info. This is useful in Hugo bug reports.",
short: "Print Hugo version and environment info",
long: "Print Hugo version and environment info. This is useful in Hugo bug reports.",
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
},
+2 -5
View File
@@ -75,9 +75,7 @@ See https://xyproto.github.io/splash/docs/all.html for a preview of the availabl
return err
}
formatter := html.New(html.WithAllClasses(true))
w := os.Stdout
fmt.Fprintf(w, "/* Generated using: hugo %s */\n\n", strings.Join(os.Args[1:], " "))
formatter.WriteCSS(w, style)
formatter.WriteCSS(os.Stdout, style)
return nil
},
withc: func(cmd *cobra.Command, r *rootCommand) {
@@ -275,8 +273,7 @@ func (c *genCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args [
func (c *genCommand) Init(cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
cmd.Short = "Generate documentation and syntax highlighting styles"
cmd.Long = "Generate documentation for your project using Hugo's documentation engine, including syntax highlighting for various programming languages."
cmd.Short = "A collection of several useful generators."
cmd.RunE = nil
return nil
+42 -112
View File
@@ -27,6 +27,7 @@ import (
"sync/atomic"
"time"
"github.com/bep/logg"
"github.com/bep/simplecobra"
"github.com/fsnotify/fsnotify"
"github.com/gohugoio/hugo/common/herrors"
@@ -135,6 +136,10 @@ func (e *hugoBuilderErrState) wasErr() bool {
return e.waserr
}
func (c *hugoBuilder) errCount() int {
return c.r.logger.LoggCount(logg.LevelError) + loggers.Log().LoggCount(logg.LevelError)
}
// getDirList provides NewWatcher() with a list of directories to watch for changes.
func (c *hugoBuilder) getDirList() ([]string, error) {
h, err := c.hugo()
@@ -340,6 +345,7 @@ func (c *hugoBuilder) newWatcher(pollIntervalStr string, dirList ...string) (*wa
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)
@@ -352,7 +358,7 @@ func (c *hugoBuilder) newWatcher(pollIntervalStr string, dirList ...string) (*wa
}
if c.s != nil && c.s.doLiveReload {
doReload := c.changeDetector == nil || len(c.changeDetector.changed()) > 0
doReload = doReload || c.showErrorInBrowser && c.errState.buildErr() != nil
doReload = doReload || c.showErrorInBrowser && c.errCount() > 0
if doReload {
livereload.ForceRefresh()
}
@@ -366,7 +372,7 @@ func (c *hugoBuilder) newWatcher(pollIntervalStr string, dirList ...string) (*wa
return
}
c.handleEvents(watcher, staticSyncer, evs, configSet)
if c.showErrorInBrowser && c.errState.buildErr() != nil {
if c.showErrorInBrowser && c.errCount() > 0 {
// Need to reload browser to show the error
livereload.ForceRefresh()
}
@@ -413,17 +419,11 @@ func (c *hugoBuilder) build() error {
}
func (c *hugoBuilder) buildSites(noBuildLock bool) (err error) {
defer func() {
c.errState.setBuildErr(err)
}()
var h *hugolib.HugoSites
h, err = c.hugo()
h, err := c.hugo()
if err != nil {
return
return err
}
err = h.Build(hugolib.BuildCfg{NoBuildLock: noBuildLock})
return
return h.Build(hugolib.BuildCfg{NoBuildLock: noBuildLock})
}
func (c *hugoBuilder) copyStatic() (map[string]uint64, error) {
@@ -619,9 +619,6 @@ func (c *hugoBuilder) fullRebuild(changeType string) {
// Set the processing on pause until the state is recovered.
c.errState.setPaused(true)
c.handleBuildErr(err, "Failed to reload config")
if c.s.doLiveReload {
livereload.ForceRefresh()
}
} else {
c.errState.setPaused(false)
}
@@ -747,20 +744,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)
@@ -782,7 +765,6 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
istemp := strings.HasSuffix(ext, "~") ||
(ext == ".swp") || // vim
(ext == ".swx") || // vim
(ext == ".bck") || // helix
(ext == ".tmp") || // generic temp file
(ext == ".DS_Store") || // OSX Thumbnail
baseName == "4913" || // vim
@@ -851,11 +833,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")
@@ -876,20 +853,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()
}
}
@@ -913,34 +889,20 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
}()
if c.s != nil && c.s.doLiveReload {
if c.errState.wasErr() {
livereload.ForceRefresh()
return
}
changed := c.changeDetector.changed()
if c.changeDetector != nil {
if len(changed) >= 10 {
lrl.Logf("build changed %d files", len(changed))
} else {
lrl.Logf("build changed %d files: %q", len(changed), changed)
}
if len(changed) == 0 {
// Nothing has changed.
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()
}
}
@@ -956,36 +918,11 @@ 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 || len(otherChanges) > 1 {
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)
}
}
}
}
@@ -1054,7 +991,7 @@ func (c *hugoBuilder) loadConfig(cd *simplecobra.Commandeer, running bool) error
"fastRenderMode": c.fastRenderMode,
})
conf, err := c.r.ConfigFromProvider(configKey{counter: c.r.configVersionID.Load()}, flagsToCfg(cd, cfg))
conf, err := c.r.ConfigFromProvider(c.r.configVersionID.Load(), flagsToCfg(cd, cfg))
if err != nil {
return err
}
@@ -1088,49 +1025,42 @@ func (c *hugoBuilder) printChangeDetected(typ string) {
c.r.logger.Println(htime.Now().Format(layout))
}
func (c *hugoBuilder) rebuildSites(events []fsnotify.Event) (err error) {
defer func() {
c.errState.setBuildErr(err)
}()
func (c *hugoBuilder) rebuildSites(events []fsnotify.Event) error {
if err := c.errState.buildErr(); err != nil {
ferrs := herrors.UnwrapFileErrorsWithErrorContext(err)
for _, err := range ferrs {
events = append(events, fsnotify.Event{Name: err.Position().Filename, Op: fsnotify.Write})
}
}
var h *hugolib.HugoSites
h, err = c.hugo()
c.errState.setBuildErr(nil)
h, err := c.hugo()
if err != nil {
return
return err
}
err = h.Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyVisited: c.visitedURLs, ErrRecovery: c.errState.wasErr()}, events...)
return
return h.Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyVisited: c.visitedURLs, ErrRecovery: c.errState.wasErr()}, events...)
}
func (c *hugoBuilder) rebuildSitesForChanges(ids []identity.Identity) (err error) {
defer func() {
c.errState.setBuildErr(err)
}()
var h *hugolib.HugoSites
h, err = c.hugo()
func (c *hugoBuilder) rebuildSitesForChanges(ids []identity.Identity) error {
c.errState.setBuildErr(nil)
h, err := c.hugo()
if err != nil {
return
return err
}
whatChanged := &hugolib.WhatChanged{}
whatChanged.Add(ids...)
err = h.Build(hugolib.BuildCfg{NoBuildLock: true, WhatChanged: whatChanged, RecentlyVisited: c.visitedURLs, ErrRecovery: c.errState.wasErr()})
return
c.errState.setBuildErr(err)
return err
}
func (c *hugoBuilder) reloadConfig() error {
c.r.resetLogs()
c.r.Reset()
c.r.configVersionID.Add(1)
if err := c.withConfE(func(conf *commonConfig) error {
oldConf := conf
newConf, err := c.r.ConfigFromConfig(configKey{counter: c.r.configVersionID.Load()}, conf)
newConf, err := c.r.ConfigFromConfig(c.r.configVersionID.Load(), conf)
if err != nil {
return err
}
+2 -2
View File
@@ -90,8 +90,8 @@ func (c *importCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, arg
func (c *importCommand) Init(cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
cmd.Short = "Import a site from another system"
cmd.Long = `Import a site from another system.
cmd.Short = "Import your site from others."
cmd.Long = `Import your site from other web site generators like Jekyll.
Import requires a subcommand, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`."
+3 -3
View File
@@ -57,7 +57,7 @@ func newListCommand() *listCommand {
return err
}
writer := csv.NewWriter(r.StdOut)
writer := csv.NewWriter(r.Out)
defer writer.Flush()
writer.Write([]string{
@@ -199,8 +199,8 @@ func (c *listCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args
func (c *listCommand) Init(cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
cmd.Short = "List content"
cmd.Long = `List content.
cmd.Short = "Listing out various types of content"
cmd.Long = `Listing out various types of content.
List requires a subcommand, e.g. hugo list drafts`
+9 -14
View File
@@ -94,7 +94,7 @@ so this may/will change in future versions of Hugo.
applyLocalFlagsBuildConfig(cmd, r)
},
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
h, err := r.getOrCreateHugo(flagsToCfg(cd, nil), true)
h, err := r.Hugo(flagsToCfg(cd, nil))
if err != nil {
return err
}
@@ -102,11 +102,7 @@ so this may/will change in future versions of Hugo.
if len(args) >= 1 {
initPath = args[0]
}
c := h.Configs.ModulesClient
if err := c.Init(initPath); err != nil {
return err
}
return nil
return h.Configs.ModulesClient.Init(initPath)
},
},
&simpleCommand{
@@ -119,7 +115,7 @@ so this may/will change in future versions of Hugo.
cmd.Flags().BoolVarP(&clean, "clean", "", false, "delete module cache for dependencies that fail verification")
},
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, nil))
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, nil))
if err != nil {
return err
}
@@ -139,7 +135,7 @@ Note that for vendored modules, that is the version listed and not the one from
cmd.Flags().BoolVarP(&clean, "clean", "", false, "delete module cache for dependencies that fail verification")
},
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, nil))
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, nil))
if err != nil {
return err
}
@@ -272,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(configKey{counter: 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...)
@@ -288,7 +283,7 @@ Run "go help get" for more information. All flags available for "go get" is also
})
return nil
} else {
conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, nil))
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, nil))
if err != nil {
return err
}
@@ -317,7 +312,7 @@ func (c *modCommands) Name() string {
}
func (c *modCommands) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
_, err := c.r.ConfigFromProvider(configKey{counter: c.r.configVersionID.Load()}, nil)
_, err := c.r.ConfigFromProvider(c.r.configVersionID.Load(), nil)
if err != nil {
return err
}
@@ -328,7 +323,7 @@ func (c *modCommands) Run(ctx context.Context, cd *simplecobra.Commandeer, args
func (c *modCommands) Init(cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
cmd.Short = "Manage modules"
cmd.Short = "Various Hugo Modules helpers."
cmd.Long = `Various helpers to help manage the modules in your project's dependency graph.
Most operations here requires a Go version installed on your system (>= Go 1.12) and the relevant VCS client (typically Git).
This is not needed if you only operate on modules inside /themes or if you have vendored them via "hugo mod vendor".
+4 -4
View File
@@ -40,7 +40,7 @@ func newNewCommand() *newCommand {
&simpleCommand{
name: "content",
use: "content [path]",
short: "Create new content",
short: "Create new content for your site",
long: `Create a new content file and automatically set the date and title.
It will guess which kind of file to create based on the path provided.
@@ -93,7 +93,7 @@ Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
cfg.Set("workingDir", createpath)
cfg.Set("publishDir", "public")
conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, cfg))
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, cfg))
if err != nil {
return err
}
@@ -136,7 +136,7 @@ according to your needs.`,
cfg := config.New()
cfg.Set("publishDir", "public")
conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, cfg))
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, cfg))
if err != nil {
return err
}
@@ -181,7 +181,7 @@ func (c *newCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args [
func (c *newCommand) Init(cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
cmd.Short = "Create new content"
cmd.Short = "Create new content for your site"
cmd.Long = `Create a new content file and automatically set the date and title.
It will guess which kind of file to create based on the path provided.
+24 -34
View File
@@ -32,7 +32,6 @@ import (
"path"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"sync"
@@ -41,7 +40,6 @@ import (
"time"
"github.com/bep/mclib"
"github.com/pkg/browser"
"github.com/bep/debounce"
"github.com/bep/simplecobra"
@@ -164,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() {
@@ -185,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 {
@@ -211,17 +209,16 @@ func (f *fileChangeDetector) changed() []string {
}
}
return f.filterIrrelevantAndSort(c)
return f.filterIrrelevant(c)
}
func (f *fileChangeDetector) filterIrrelevantAndSort(in []string) []string {
func (f *fileChangeDetector) filterIrrelevant(in []string) []string {
var filtered []string
for _, v := range in {
if !f.irrelevantRe.MatchString(v) {
filtered = append(filtered, v)
}
}
sort.Strings(filtered)
return filtered
}
@@ -451,7 +448,6 @@ type serverCommand struct {
// Flags.
renderStaticToDisk bool
navigateToChanged bool
openBrowser bool
serverAppend bool
serverInterface string
tlsCertFile string
@@ -512,7 +508,7 @@ func (c *serverCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, arg
func (c *serverCommand) Init(cd *simplecobra.Commandeer) error {
cmd := cd.CobraCommand
cmd.Short = "Start the embedded web server"
cmd.Short = "A high performance webserver"
cmd.Long = `Hugo provides its own webserver which builds and serves the site.
While hugo server is high performance, it is a webserver with limited options.
@@ -543,7 +539,6 @@ of a second, you will be able to save and see your changes nearly instantly.`
cmd.Flags().BoolVarP(&c.serverAppend, "appendPort", "", true, "append port to baseURL")
cmd.Flags().BoolVar(&c.disableLiveReload, "disableLiveReload", false, "watch without enabling live browser reload on rebuild")
cmd.Flags().BoolVarP(&c.navigateToChanged, "navigateToChanged", "N", false, "navigate to changed content file on live browser reload")
cmd.Flags().BoolVarP(&c.openBrowser, "openBrowser", "O", false, "open the site in a browser after server startup")
cmd.Flags().BoolVar(&c.renderStaticToDisk, "renderStaticToDisk", false, "serve static files from disk and dynamic files from memory")
cmd.Flags().BoolVar(&c.disableFastRender, "disableFastRender", false, "enables full re-renders on changes")
cmd.Flags().BoolVar(&c.disableBrowserError, "disableBrowserError", false, "do not show build errors in the browser")
@@ -653,8 +648,9 @@ func (c *serverCommand) setServerInfoInConfig() error {
}
func (c *serverCommand) getErrorWithContext() any {
buildErr := c.errState.buildErr()
if buildErr == nil {
errCount := c.errCount()
if errCount == 0 {
return nil
}
@@ -663,7 +659,7 @@ func (c *serverCommand) getErrorWithContext() any {
m["Error"] = cleanErrorLog(c.r.logger.Errors())
m["Version"] = hugo.BuildVersionString()
ferrors := herrors.UnwrapFileErrorsWithErrorContext(buildErr)
ferrors := herrors.UnwrapFileErrorsWithErrorContext(c.errState.buildErr())
m["Files"] = ferrors
return m
@@ -834,25 +830,22 @@ func (c *serverCommand) fixURL(baseURLFromConfig, baseURLFromFlag string, port i
return u.String(), nil
}
func (c *serverCommand) partialReRender(urls ...string) (err error) {
func (c *serverCommand) partialReRender(urls ...string) error {
defer func() {
c.errState.setWasErr(false)
}()
c.errState.setBuildErr(nil)
visited := types.NewEvictingStringQueue(len(urls))
for _, url := range urls {
visited.Add(url)
}
var h *hugolib.HugoSites
h, err = c.hugo()
h, err := c.hugo()
if err != nil {
return
return err
}
// Note: We do not set NoBuildLock as the file lock is not acquired at this stage.
err = h.Build(hugolib.BuildCfg{NoBuildLock: false, RecentlyVisited: visited, PartialReRender: true, ErrRecovery: c.errState.wasErr()})
return
return h.Build(hugolib.BuildCfg{NoBuildLock: false, RecentlyVisited: visited, PartialReRender: true, ErrRecovery: c.errState.wasErr()})
}
func (c *serverCommand) serve() error {
@@ -1003,13 +996,6 @@ func (c *serverCommand) serve() error {
c.r.Println("Press Ctrl+C to stop")
if c.openBrowser {
// There may be more than one baseURL in multihost mode, open the first.
if err := browser.OpenURL(baseURLs[0].String()); err != nil {
c.r.logger.Warnf("Failed to open browser: %s", err)
}
}
err = func() error {
for {
select {
@@ -1026,6 +1012,10 @@ func (c *serverCommand) serve() error {
c.r.Println("Error:", err)
}
if h := c.hugoTry(); h != nil {
h.Close()
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
wg2, ctx := errgroup.WithContext(ctx)
-13
View File
@@ -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 -3
View File
@@ -21,8 +21,6 @@ const (
ErrRemoteGetCSV = "error-remote-getcsv"
WarnFrontMatterParamsOverrides = "warning-frontmatter-params-overrides"
WarnRenderShortcodesInHTML = "warning-rendershortcodes-in-html"
WarnGoldmarkRawHTML = "warning-goldmark-raw-html"
)
// Field/method names with special meaning.
@@ -41,7 +39,7 @@ const (
ResourceTransformationFingerprint = "fingerprint"
)
// IsResourceTransformationPermalinkHash returns whether the given name is a resource transformation that changes the permalink based on the content.
// IsResourceTransformationLinkChange returns whether the given name is a resource transformation that changes the permalink based on the content.
func IsResourceTransformationPermalinkHash(name string) bool {
return name == ResourceTransformationFingerprint
}
-166
View File
@@ -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)
}
-144
View File
@@ -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)
}
})
}
}
+4 -52
View File
@@ -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{})
@@ -133,55 +119,21 @@ func IsNotExist(err error) bool {
return false
}
// IsExist returns true if the error is a file exists error.
// Unlike os.IsExist, this also considers wrapped errors.
func IsExist(err error) bool {
if os.IsExist(err) {
return true
}
// os.IsExist does not consider wrapped errors.
if os.IsExist(errors.Unwrap(err)) {
return true
}
return false
}
var nilPointerErrRe = regexp.MustCompile(`at <(.*)>: error calling (.*?): runtime error: invalid memory address or nil pointer dereference`)
const deferredPrefix = "__hdeferred/"
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
}
+12 -1
View File
@@ -20,6 +20,8 @@ import (
"io"
"path/filepath"
godartsassv1 "github.com/bep/godartsass"
"github.com/bep/godartsass/v2"
"github.com/bep/golibsass/libsass/libsasserrors"
"github.com/gohugoio/hugo/common/paths"
@@ -151,6 +153,8 @@ func (e *fileError) causeString() string {
// Avoid repeating the file info in the error message.
case godartsass.SassError:
return v.Message
case godartsassv1.SassError:
return v.Message
case libsasserrors.Error:
return v.Message
default:
@@ -384,7 +388,14 @@ func extractPosition(e error) (pos text.Position) {
case godartsass.SassError:
span := v.Span
start := span.Start
filename, _ := paths.UrlStringToFilename(span.Url)
filename, _ := paths.UrlToFilename(span.Url)
pos.Filename = filename
pos.Offset = start.Offset
pos.ColumnNumber = start.Column
case godartsassv1.SassError:
span := v.Span
start := span.Start
filename, _ := paths.UrlToFilename(span.Url)
pos.Filename = filename
pos.Offset = start.Offset
pos.ColumnNumber = start.Column
+17 -77
View File
@@ -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,
}
}
+2 -24
View File
@@ -223,27 +223,6 @@ func AsTime(v reflect.Value, loc *time.Location) (time.Time, bool) {
return time.Time{}, false
}
// ToSliceAny converts the given value to a slice of any if possible.
func ToSliceAny(v any) ([]any, bool) {
if v == nil {
return nil, false
}
switch vv := v.(type) {
case []any:
return vv, true
default:
vvv := reflect.ValueOf(v)
if vvv.Kind() == reflect.Slice {
out := make([]any, vvv.Len())
for i := 0; i < vvv.Len(); i++ {
out[i] = vvv.Index(i).Interface()
}
return out, true
}
}
return nil, false
}
func CallMethodByName(cxt context.Context, name string, v reflect.Value) []reflect.Value {
fn := v.MethodByName(name)
var args []reflect.Value
@@ -289,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
}
-13
View File
@@ -50,19 +50,6 @@ func TestIsContextType(t *testing.T) {
c.Assert(IsContextType(reflect.TypeOf(valueCtx)), qt.IsTrue)
}
func TestToSliceAny(t *testing.T) {
c := qt.New(t)
checkOK := func(in any, expected []any) {
out, ok := ToSliceAny(in)
c.Assert(ok, qt.Equals, true)
c.Assert(out, qt.DeepEquals, expected)
}
checkOK([]any{1, 2, 3}, []any{1, 2, 3})
checkOK([]int{1, 2, 3}, []any{1, 2, 3})
}
func BenchmarkIsContextType(b *testing.B) {
type k string
b.Run("value", func(b *testing.B) {
+9 -32
View File
@@ -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
+5 -8
View File
@@ -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)
}
+2 -2
View File
@@ -74,13 +74,13 @@ type StringReader interface {
ReadString() string
}
// NewReadSeekerNoOpCloserFromBytes uses bytes.NewReader to create a new ReadSeekerNoOpCloser
// NewReadSeekerNoOpCloserFromString uses strings.NewReader to create a new ReadSeekerNoOpCloser
// from the given bytes slice.
func NewReadSeekerNoOpCloserFromBytes(content []byte) readSeekerNopCloser {
return readSeekerNopCloser{bytes.NewReader(content)}
}
// NewOpenReadSeekCloser creates a new ReadSeekCloser from the given ReadSeeker.
// NewReadSeekCloser creates a new ReadSeekCloser from the given ReadSeeker.
// The ReadSeeker will be seeked to the beginning before returned.
func NewOpenReadSeekCloser(r ReadSeekCloser) OpenReadSeekCloser {
return func() (ReadSeekCloser, error) {
-30
View File
@@ -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 -46
View File
@@ -14,7 +14,6 @@
package hugo
import (
"context"
"fmt"
"html/template"
"os"
@@ -25,13 +24,13 @@ import (
"sync"
"time"
godartsassv1 "github.com/bep/godartsass"
"github.com/bep/logg"
"github.com/mitchellh/mapstructure"
"github.com/bep/godartsass/v2"
"github.com/gohugoio/hugo/common/hcontext"
"github.com/gohugoio/hugo/common/hexec"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/spf13/afero"
@@ -54,8 +53,6 @@ var (
vendorInfo string
)
var _ maps.StoreProvider = (*HugoInfo)(nil)
// HugoInfo contains information about the current Hugo environment
type HugoInfo struct {
CommitHash string
@@ -72,11 +69,6 @@ type HugoInfo struct {
conf ConfigProvider
deps []*Dependency
store *maps.Scratch
// Context gives access to some of the context scoped variables.
Context Context
}
// Version returns the current version as a comparable version string.
@@ -119,10 +111,6 @@ func (i HugoInfo) Deps() []*Dependency {
return i.deps
}
func (i HugoInfo) Store() *maps.Scratch {
return i.store
}
// Deprecated: Use hugo.IsMultihost instead.
func (i HugoInfo) IsMultiHost() bool {
Deprecate("hugo.IsMultiHost", "Use hugo.IsMultihost instead.", "v0.124.0")
@@ -139,26 +127,6 @@ func (i HugoInfo) IsMultilingual() bool {
return i.conf.IsMultilingual()
}
type contextKey string
var markupScope = hcontext.NewContextDispatcher[string](contextKey("markupScope"))
type Context struct{}
func (c Context) MarkupScope(ctx context.Context) string {
return GetMarkupScope(ctx)
}
// SetMarkupScope sets the markup scope in the context.
func SetMarkupScope(ctx context.Context, s string) context.Context {
return markupScope.Set(ctx, s)
}
// GetMarkupScope gets the markup scope from the context.
func GetMarkupScope(ctx context.Context) string {
return markupScope.Get(ctx)
}
// ConfigProvider represents the config options that are relevant for HugoInfo.
type ConfigProvider interface {
Environment() string
@@ -192,7 +160,6 @@ func NewInfo(conf ConfigProvider, deps []*Dependency) HugoInfo {
Environment: conf.Environment(),
conf: conf,
deps: deps,
store: maps.NewScratch(),
GoVersion: goVersion,
}
}
@@ -309,14 +276,14 @@ func GetDependencyListNonGo() []string {
if IsExtended {
deps = append(
deps,
formatDep("github.com/sass/libsass", "3.6.6"),
formatDep("github.com/sass/libsass", "3.6.5"),
formatDep("github.com/webmproject/libwebp", "v1.3.2"),
)
}
if dartSass := dartSassVersion(); dartSass.ProtocolVersion != "" {
dartSassPath := "github.com/sass/dart-sass-embedded"
if IsDartSassGeV2() {
if IsDartSassV2() {
dartSassPath = "github.com/sass/dart-sass"
}
deps = append(deps,
@@ -363,15 +330,22 @@ type Dependency struct {
}
func dartSassVersion() godartsass.DartSassVersion {
if DartSassBinaryName == "" || !IsDartSassGeV2() {
if DartSassBinaryName == "" {
return godartsass.DartSassVersion{}
}
v, _ := godartsass.Version(DartSassBinaryName)
return v
if IsDartSassV2() {
v, _ := godartsass.Version(DartSassBinaryName)
return v
}
v, _ := godartsassv1.Version(DartSassBinaryName)
var vv godartsass.DartSassVersion
mapstructure.WeakDecode(v, &vv)
return vv
}
// DartSassBinaryName is the name of the Dart Sass binary to use.
// TODO(bep) find a better place for this.
// TODO(beop) find a better place for this.
var DartSassBinaryName string
func init() {
@@ -396,10 +370,7 @@ var (
dartSassBinaryNamesV2 = []string{"dart-sass", "sass"}
)
// TODO(bep) we eventually want to remove this, but keep it for a while to throw an informative error.
// We stopped supporting the old binary in Hugo 0.139.0.
func IsDartSassGeV2() bool {
// dart-sass-embedded was the first version of the embedded Dart Sass before it was moved into the main project.
func IsDartSassV2() bool {
return !strings.Contains(DartSassBinaryName, "embedded")
}
@@ -426,7 +397,7 @@ func DeprecateLevel(item, alternative, version string, level logg.Level) {
loggers.Log().Logger().WithLevel(level).WithField(loggers.FieldNameCmd, "deprecated").Logf(msg)
}
// We usually do about one minor version a month.
// We ususally do about one minor version a month.
// We want people to run at least the current and previous version without any warnings.
// We want people who don't update Hugo that often to see the warnings and errors before we remove the feature.
func deprecationLogLevelFromVersion(ver string) logg.Level {
-18
View File
@@ -14,7 +14,6 @@
package hugo
import (
"context"
"fmt"
"testing"
@@ -63,23 +62,6 @@ func TestDeprecationLogLevelFromVersion(t *testing.T) {
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelWarn)
ver.Minor -= 6
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelError)
// Added just to find the threshold for where we can remove deprecated items.
// Subtract 5 from the minor version of the first ERRORed version => 0.122.0.
c.Assert(deprecationLogLevelFromVersion("0.127.0"), qt.Equals, logg.LevelError)
}
func TestMarkupScope(t *testing.T) {
c := qt.New(t)
conf := testConfig{environment: "production", workingDir: "/mywork", running: false}
info := NewInfo(conf, nil)
ctx := context.Background()
ctx = SetMarkupScope(ctx, "foo")
c.Assert(info.Context.MarkupScope(ctx), qt.Equals, "foo")
}
type testConfig struct {
-19
View File
@@ -1,19 +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.
//go:build !withdeploy
// +build !withdeploy
package hugo
var IsWithdeploy = false
-3
View File
@@ -152,9 +152,6 @@ func BuildVersionString() string {
if IsExtended {
version += "+extended"
}
if IsWithdeploy {
version += "+withdeploy"
}
osArch := bi.GoOS + "/" + bi.GoArch
+1 -1
View File
@@ -17,7 +17,7 @@ package hugo
// This should be the only one.
var CurrentVersion = Version{
Major: 0,
Minor: 141,
Minor: 127,
PatchLevel: 0,
Suffix: "-DEV",
}
+2 -2
View File
@@ -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
}
+9 -19
View File
@@ -18,19 +18,18 @@ package loggers
import (
"fmt"
"io"
"regexp"
"strings"
"sync"
"github.com/bep/logg"
)
// newNoAnsiEscapeHandler creates a new noAnsiEscapeHandler
func newNoAnsiEscapeHandler(outWriter, errWriter io.Writer, noLevelPrefix bool, predicate func(*logg.Entry) bool) *noAnsiEscapeHandler {
// newNoColoursHandler creates a new NoColoursHandler
func newNoColoursHandler(outWriter, errWriter io.Writer, noLevelPrefix bool, predicate func(*logg.Entry) bool) *noColoursHandler {
if predicate == nil {
predicate = func(e *logg.Entry) bool { return true }
}
return &noAnsiEscapeHandler{
return &noColoursHandler{
noLevelPrefix: noLevelPrefix,
outWriter: outWriter,
errWriter: errWriter,
@@ -38,15 +37,15 @@ func newNoAnsiEscapeHandler(outWriter, errWriter io.Writer, noLevelPrefix bool,
}
}
type noAnsiEscapeHandler struct {
type noColoursHandler struct {
mu sync.Mutex
outWriter io.Writer
errWriter io.Writer
outWriter io.Writer // Defaults to os.Stdout.
errWriter io.Writer // Defaults to os.Stderr.
predicate func(*logg.Entry) bool
noLevelPrefix bool
}
func (h *noAnsiEscapeHandler) HandleLog(e *logg.Entry) error {
func (h *noColoursHandler) HandleLog(e *logg.Entry) error {
if !h.predicate(e) {
return nil
}
@@ -72,12 +71,10 @@ func (h *noAnsiEscapeHandler) HandleLog(e *logg.Entry) error {
prefix = prefix + ": "
}
msg := stripANSI(e.Message)
if h.noLevelPrefix {
fmt.Fprintf(w, "%s%s", prefix, msg)
fmt.Fprintf(w, "%s%s", prefix, e.Message)
} else {
fmt.Fprintf(w, "%s %s%s", levelString[e.Level], prefix, msg)
fmt.Fprintf(w, "%s %s%s", levelString[e.Level], prefix, e.Message)
}
for _, field := range e.Fields {
@@ -91,10 +88,3 @@ func (h *noAnsiEscapeHandler) HandleLog(e *logg.Entry) error {
return nil
}
var ansiRe = regexp.MustCompile(`\x1b\[[0-9;]*m`)
// stripANSI removes ANSI escape codes from s.
func stripANSI(s string) string {
return ansiRe.ReplaceAllString(s, "")
}
-40
View File
@@ -1,40 +0,0 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
// Some functions in this file (see comments) is based on the Go source code,
// copyright The Go Authors and governed by a BSD-style license.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package loggers
import (
"bytes"
"testing"
"github.com/bep/logg"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/terminal"
)
func TestNoAnsiEscapeHandler(t *testing.T) {
c := qt.New(t)
test := func(s string) {
c.Assert(stripANSI(terminal.Notice(s)), qt.Equals, s)
}
test(`error in "file.md:1:2"`)
var buf bytes.Buffer
h := newNoAnsiEscapeHandler(&buf, &buf, false, nil)
h.HandleLog(&logg.Entry{Message: terminal.Notice(`error in "file.md:1:2"`), Level: logg.LevelInfo})
c.Assert(buf.String(), qt.Equals, "INFO error in \"file.md:1:2\"\n")
}
+23 -29
View File
@@ -38,8 +38,8 @@ var (
// Options defines options for the logger.
type Options struct {
Level logg.Level
StdOut io.Writer
StdErr io.Writer
Stdout io.Writer
Stderr io.Writer
DistinctLevel logg.Level
StoreErrors bool
HandlerPost func(e *logg.Entry) error
@@ -48,22 +48,21 @@ type Options struct {
// New creates a new logger with the given options.
func New(opts Options) Logger {
if opts.StdOut == nil {
opts.StdOut = os.Stdout
if opts.Stdout == nil {
opts.Stdout = os.Stdout
}
if opts.StdErr == nil {
opts.StdErr = os.Stderr
if opts.Stderr == nil {
opts.Stderr = os.Stdout
}
if opts.Level == 0 {
opts.Level = logg.LevelWarn
}
var logHandler logg.Handler
if terminal.PrintANSIColors(os.Stderr) {
logHandler = newDefaultHandler(opts.StdErr, opts.StdErr)
if terminal.PrintANSIColors(os.Stdout) {
logHandler = newDefaultHandler(opts.Stdout, opts.Stderr)
} else {
logHandler = newNoAnsiEscapeHandler(opts.StdErr, opts.StdErr, false, nil)
logHandler = newNoColoursHandler(opts.Stdout, opts.Stderr, false, nil)
}
errorsw := &strings.Builder{}
@@ -96,7 +95,7 @@ func New(opts Options) Logger {
}
if opts.StoreErrors {
h := newNoAnsiEscapeHandler(io.Discard, errorsw, true, func(e *logg.Entry) bool {
h := newNoColoursHandler(io.Discard, errorsw, true, func(e *logg.Entry) bool {
return e.Level >= logg.LevelError
})
@@ -111,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)
}
@@ -138,8 +137,7 @@ func New(opts Options) Logger {
logCounters: logCounters,
errors: errorsw,
reset: reset,
stdOut: opts.StdOut,
stdErr: opts.StdErr,
out: opts.Stdout,
level: opts.Level,
logger: logger,
tracel: l.WithLevel(logg.LevelTrace),
@@ -155,6 +153,8 @@ func NewDefault() Logger {
opts := Options{
DistinctLevel: logg.LevelWarn,
Level: logg.LevelWarn,
Stdout: os.Stdout,
Stderr: os.Stdout,
}
return New(opts)
}
@@ -163,6 +163,8 @@ func NewTrace() Logger {
opts := Options{
DistinctLevel: logg.LevelWarn,
Level: logg.LevelTrace,
Stdout: os.Stdout,
Stderr: os.Stdout,
}
return New(opts)
}
@@ -187,8 +189,7 @@ type Logger interface {
Level() logg.Level
LoggCount(logg.Level) int
Logger() logg.Logger
StdOut() io.Writer
StdErr() io.Writer
Out() io.Writer
Printf(format string, v ...any)
Println(v ...any)
PrintTimerIfDelayed(start time.Time, name string)
@@ -206,8 +207,7 @@ type logAdapter struct {
logCounters *logLevelCounter
errors *strings.Builder
reset func()
stdOut io.Writer
stdErr io.Writer
out io.Writer
level logg.Level
logger logg.Logger
tracel logg.LevelLogger
@@ -259,12 +259,8 @@ func (l *logAdapter) Logger() logg.Logger {
return l.logger
}
func (l *logAdapter) StdOut() io.Writer {
return l.stdOut
}
func (l *logAdapter) StdErr() io.Writer {
return l.stdErr
func (l *logAdapter) Out() io.Writer {
return l.out
}
// PrintTimerIfDelayed prints a time statement to the FEEDBACK logger
@@ -275,7 +271,7 @@ func (l *logAdapter) PrintTimerIfDelayed(start time.Time, name string) {
if milli < 500 {
return
}
fmt.Fprintf(l.stdErr, "%s in %v ms", name, milli)
l.Printf("%s in %v ms", name, milli)
}
func (l *logAdapter) Printf(format string, v ...any) {
@@ -283,11 +279,11 @@ func (l *logAdapter) Printf(format string, v ...any) {
if !strings.HasSuffix(format, "\n") {
format += "\n"
}
fmt.Fprintf(l.stdOut, format, v...)
fmt.Fprintf(l.out, format, v...)
}
func (l *logAdapter) Println(v ...any) {
fmt.Fprintln(l.stdOut, v...)
fmt.Fprintln(l.out, v...)
}
func (l *logAdapter) Reset() {
@@ -327,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...)
}
+8 -8
View File
@@ -31,8 +31,8 @@ func TestLogDistinct(t *testing.T) {
opts := loggers.Options{
DistinctLevel: logg.LevelWarn,
StoreErrors: true,
StdOut: io.Discard,
StdErr: io.Discard,
Stdout: io.Discard,
Stderr: io.Discard,
}
l := loggers.New(opts)
@@ -54,8 +54,8 @@ func TestHookLast(t *testing.T) {
HandlerPost: func(e *logg.Entry) error {
panic(e.Message)
},
StdOut: io.Discard,
StdErr: io.Discard,
Stdout: io.Discard,
Stderr: io.Discard,
}
l := loggers.New(opts)
@@ -70,8 +70,8 @@ func TestOptionStoreErrors(t *testing.T) {
opts := loggers.Options{
StoreErrors: true,
StdErr: &sb,
StdOut: &sb,
Stderr: &sb,
Stdout: &sb,
}
l := loggers.New(opts)
@@ -131,8 +131,8 @@ func TestReset(t *testing.T) {
opts := loggers.Options{
StoreErrors: true,
DistinctLevel: logg.LevelWarn,
StdOut: io.Discard,
StdErr: io.Discard,
Stdout: io.Discard,
Stderr: io.Discard,
}
l := loggers.New(opts)
+11 -83
View File
@@ -13,14 +13,11 @@
package maps
import (
"sync"
)
import "sync"
// Cache is a simple thread safe cache backed by a map.
type Cache[K comparable, T any] struct {
m map[K]T
hasBeenInitialized bool
m map[K]T
sync.RWMutex
}
@@ -37,115 +34,46 @@ func (c *Cache[K, T]) Get(key K) (T, bool) {
return zero, false
}
c.RLock()
v, found := c.get(key)
c.RUnlock()
return v, found
}
func (c *Cache[K, T]) get(key K) (T, bool) {
v, found := c.m[key]
c.RUnlock()
return v, found
}
// GetOrCreate gets the value for the given key if it exists, or creates it if not.
func (c *Cache[K, T]) GetOrCreate(key K, create func() (T, 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
}
// InitAndGet initializes the cache if not already done and returns the value for the given key.
// The init state will be reset on Reset or Drain.
func (c *Cache[K, T]) InitAndGet(key K, init func(get func(key K) (T, bool), set func(key K, value T)) error) (T, error) {
var v T
c.RLock()
if !c.hasBeenInitialized {
c.RUnlock()
if err := func() error {
c.Lock()
defer c.Unlock()
// Double check in case another goroutine has initialized it in the meantime.
if !c.hasBeenInitialized {
err := init(c.get, c.set)
if err != nil {
return err
}
c.hasBeenInitialized = true
}
return nil
}(); err != nil {
return v, err
}
// Reacquire the read lock.
c.RLock()
}
v = c.m[key]
c.RUnlock()
return v, nil
return v
}
// Set sets the given key to the given value.
func (c *Cache[K, T]) Set(key K, value T) {
c.Lock()
c.set(key, value)
c.Unlock()
}
func (c *Cache[K, T]) set(key K, value T) {
c.m[key] = value
c.Unlock()
}
// ForEeach calls the given function for each key/value pair in the cache.
// If the function returns false, the iteration stops.
func (c *Cache[K, T]) ForEeach(f func(K, T) bool) {
func (c *Cache[K, T]) ForEeach(f func(K, T)) {
c.RLock()
defer c.RUnlock()
for k, v := range c.m {
if !f(k, v) {
return
}
f(k, v)
}
}
func (c *Cache[K, T]) Drain() map[K]T {
c.Lock()
m := c.m
c.m = make(map[K]T)
c.hasBeenInitialized = false
c.Unlock()
return m
}
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.hasBeenInitialized = false
c.Unlock()
}
// SliceCache is a simple thread safe cache backed by a map.
type SliceCache[T any] struct {
m map[string][]T
+20 -7
View File
@@ -22,18 +22,31 @@ import (
"github.com/gohugoio/hugo/common/math"
)
type StoreProvider interface {
// Store returns a Scratch that can be used to store temporary state.
// Store is not reset on server rebuilds.
Store() *Scratch
}
// Scratch is a writable context used for stateful build operations
// Scratch is a writable context used for stateful operations in Page/Node rendering.
type Scratch struct {
values map[string]any
mu sync.RWMutex
}
// Scratcher provides a scratching service.
type Scratcher interface {
// Scratch returns a "scratch pad" that can be used to store state.
Scratch() *Scratch
}
type scratcher struct {
s *Scratch
}
func (s scratcher) Scratch() *Scratch {
return s.s
}
// NewScratcher creates a new Scratcher.
func NewScratcher() Scratcher {
return scratcher{s: NewScratch()}
}
// Add will, for single values, add (using the + operator) the addend to the existing addend (if found).
// Supports numeric values and strings.
//
+2 -2
View File
@@ -185,7 +185,7 @@ func TestScratchSetInMap(t *testing.T) {
scratch.SetInMap("key", "zyx", "Zyx")
scratch.SetInMap("key", "abc", "Abc (updated)")
scratch.SetInMap("key", "def", "Def")
c.Assert(scratch.GetSortedMapValues("key"), qt.DeepEquals, any([]any{"Abc (updated)", "Def", "Lux", "Zyx"}))
c.Assert(scratch.GetSortedMapValues("key"), qt.DeepEquals, []any{0: "Abc (updated)", 1: "Def", 2: "Lux", 3: "Zyx"})
}
func TestScratchDeleteInMap(t *testing.T) {
@@ -199,7 +199,7 @@ func TestScratchDeleteInMap(t *testing.T) {
scratch.DeleteInMap("key", "abc")
scratch.SetInMap("key", "def", "Def")
scratch.DeleteInMap("key", "lmn") // Do nothing
c.Assert(scratch.GetSortedMapValues("key"), qt.DeepEquals, any([]any{"Def", "Lux", "Zyx"}))
c.Assert(scratch.GetSortedMapValues("key"), qt.DeepEquals, []any{0: "Def", 1: "Lux", 2: "Zyx"})
}
func TestScratchGetSortedMapValues(t *testing.T) {
+24 -22
View File
@@ -26,32 +26,29 @@ func DoArithmetic(a, b any, op rune) (any, error) {
var ai, bi int64
var af, bf float64
var au, bu uint64
var isInt, isFloat, isUint bool
switch av.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
ai = av.Int()
switch bv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
isInt = true
bi = bv.Int()
case reflect.Float32, reflect.Float64:
isFloat = true
af = float64(ai) // may overflow
ai = 0
bf = bv.Float()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
bu = bv.Uint()
if ai >= 0 {
isUint = true
au = uint64(ai)
ai = 0
} else {
isInt = true
bi = int64(bu) // may overflow
bu = 0
}
default:
return nil, errors.New("can't apply the operator to the values")
}
case reflect.Float32, reflect.Float64:
isFloat = true
af = av.Float()
switch bv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
@@ -69,18 +66,17 @@ func DoArithmetic(a, b any, op rune) (any, error) {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
bi = bv.Int()
if bi >= 0 {
isUint = true
bu = uint64(bi)
bi = 0
} else {
isInt = true
ai = int64(au) // may overflow
au = 0
}
case reflect.Float32, reflect.Float64:
isFloat = true
af = float64(au) // may overflow
au = 0
bf = bv.Float()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
isUint = true
bu = bv.Uint()
default:
return nil, errors.New("can't apply the operator to the values")
@@ -98,32 +94,38 @@ func DoArithmetic(a, b any, op rune) (any, error) {
switch op {
case '+':
if isInt {
if ai != 0 || bi != 0 {
return ai + bi, nil
} else if isFloat {
} else if af != 0 || bf != 0 {
return af + bf, nil
} else if au != 0 || bu != 0 {
return au + bu, nil
}
return au + bu, nil
return 0, nil
case '-':
if isInt {
if ai != 0 || bi != 0 {
return ai - bi, nil
} else if isFloat {
} else if af != 0 || bf != 0 {
return af - bf, nil
} else if au != 0 || bu != 0 {
return au - bu, nil
}
return au - bu, nil
return 0, nil
case '*':
if isInt {
if ai != 0 || bi != 0 {
return ai * bi, nil
} else if isFloat {
} else if af != 0 || bf != 0 {
return af * bf, nil
} else if au != 0 || bu != 0 {
return au * bu, nil
}
return au * bu, nil
return 0, nil
case '/':
if isInt && bi != 0 {
if bi != 0 {
return ai / bi, nil
} else if isFloat && bf != 0 {
} else if bf != 0 {
return af / bf, nil
} else if isUint && bu != 0 {
} else if bu != 0 {
return au / bu, nil
}
return nil, errors.New("can't divide the value by 0")
+3 -8
View File
@@ -30,12 +30,10 @@ func TestDoArithmetic(t *testing.T) {
expect any
}{
{3, 2, '+', int64(5)},
{0, 0, '+', int64(0)},
{3, 2, '-', int64(1)},
{3, 2, '*', int64(6)},
{3, 2, '/', int64(1)},
{3.0, 2, '+', float64(5)},
{0.0, 0, '+', float64(0.0)},
{3.0, 2, '-', float64(1)},
{3.0, 2, '*', float64(6)},
{3.0, 2, '/', float64(1.5)},
@@ -44,22 +42,18 @@ func TestDoArithmetic(t *testing.T) {
{3, 2.0, '*', float64(6)},
{3, 2.0, '/', float64(1.5)},
{3.0, 2.0, '+', float64(5)},
{0.0, 0.0, '+', float64(0.0)},
{3.0, 2.0, '-', float64(1)},
{3.0, 2.0, '*', float64(6)},
{3.0, 2.0, '/', float64(1.5)},
{uint(3), uint(2), '+', uint64(5)},
{uint(0), uint(0), '+', uint64(0)},
{uint(3), uint(2), '-', uint64(1)},
{uint(3), uint(2), '*', uint64(6)},
{uint(3), uint(2), '/', uint64(1)},
{uint(3), 2, '+', uint64(5)},
{uint(0), 0, '+', uint64(0)},
{uint(3), 2, '-', uint64(1)},
{uint(3), 2, '*', uint64(6)},
{uint(3), 2, '/', uint64(1)},
{3, uint(2), '+', uint64(5)},
{0, uint(0), '+', uint64(0)},
{3, uint(2), '-', uint64(1)},
{3, uint(2), '*', uint64(6)},
{3, uint(2), '/', uint64(1)},
@@ -72,15 +66,16 @@ func TestDoArithmetic(t *testing.T) {
{-3, uint(2), '*', int64(-6)},
{-3, uint(2), '/', int64(-1)},
{uint(3), 2.0, '+', float64(5)},
{uint(0), 0.0, '+', float64(0)},
{uint(3), 2.0, '-', float64(1)},
{uint(3), 2.0, '*', float64(6)},
{uint(3), 2.0, '/', float64(1.5)},
{3.0, uint(2), '+', float64(5)},
{0.0, uint(0), '+', float64(0)},
{3.0, uint(2), '-', float64(1)},
{3.0, uint(2), '*', float64(6)},
{3.0, uint(2), '/', float64(1.5)},
{0, 0, '+', 0},
{0, 0, '-', 0},
{0, 0, '*', 0},
{"foo", "bar", '+', "foobar"},
{3, 0, '/', false},
{3.0, 0, '/', false},
+5 -36
View File
@@ -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))
}
-49
View File
@@ -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))
}
}
+2 -2
View File
@@ -153,7 +153,7 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
} else {
high = len(p.s)
}
id := types.LowHigh[string]{Low: i + 1, High: high}
id := types.LowHigh{Low: i + 1, High: high}
if len(p.identifiers) == 0 {
p.identifiers = append(p.identifiers, id)
} else if len(p.identifiers) == 1 {
@@ -260,7 +260,7 @@ type Path struct {
component string
bundleType PathType
identifiers []types.LowHigh[string]
identifiers []types.LowHigh
posIdentifierLanguage int
disabled bool
+17 -95
View File
@@ -1,4 +1,4 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
// Copyright 2021 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ import (
"net/url"
"path"
"path/filepath"
"runtime"
"strings"
)
@@ -160,77 +159,9 @@ func Uglify(in string) string {
return path.Clean(in)
}
// URLEscape escapes unicode letters.
func URLEscape(uri string) string {
// escape unicode letters
u, err := url.Parse(uri)
if err != nil {
panic(err)
}
return u.String()
}
// TrimExt trims the extension from a path..
func TrimExt(in string) string {
return strings.TrimSuffix(in, path.Ext(in))
}
// From https://github.com/golang/go/blob/e0c76d95abfc1621259864adb3d101cf6f1f90fc/src/cmd/go/internal/web/url.go#L45
func UrlFromFilename(filename string) (*url.URL, error) {
if !filepath.IsAbs(filename) {
return nil, fmt.Errorf("filepath must be absolute")
}
// If filename has a Windows volume name, convert the volume to a host and prefix
// per https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/.
if vol := filepath.VolumeName(filename); vol != "" {
if strings.HasPrefix(vol, `\\`) {
filename = filepath.ToSlash(filename[2:])
i := strings.IndexByte(filename, '/')
if i < 0 {
// A degenerate case.
// \\host.example.com (without a share name)
// becomes
// file://host.example.com/
return &url.URL{
Scheme: "file",
Host: filename,
Path: "/",
}, nil
}
// \\host.example.com\Share\path\to\file
// becomes
// file://host.example.com/Share/path/to/file
return &url.URL{
Scheme: "file",
Host: filename[:i],
Path: filepath.ToSlash(filename[i:]),
}, nil
}
// C:\path\to\file
// becomes
// file:///C:/path/to/file
return &url.URL{
Scheme: "file",
Path: "/" + filepath.ToSlash(filename),
}, nil
}
// /path/to/file
// becomes
// file:///path/to/file
return &url.URL{
Scheme: "file",
Path: filepath.ToSlash(filename),
}, nil
}
// UrlToFilename converts the URL s to a filename.
// If ParseRequestURI fails, the input is just converted to OS specific slashes and returned.
func UrlStringToFilename(s string) (string, bool) {
func UrlToFilename(s string) (string, bool) {
u, err := url.ParseRequestURI(s)
if err != nil {
return filepath.FromSlash(s), false
@@ -240,34 +171,25 @@ func UrlStringToFilename(s string) (string, bool) {
if p == "" {
p, _ = url.QueryUnescape(u.Opaque)
return filepath.FromSlash(p), false
}
if runtime.GOOS != "windows" {
return p, true
}
if len(p) == 0 || p[0] != '/' {
return filepath.FromSlash(p), false
return filepath.FromSlash(p), true
}
p = filepath.FromSlash(p)
if len(u.Host) == 1 {
// file://c/Users/...
return strings.ToUpper(u.Host) + ":" + p, true
if u.Host != "" {
// C:\data\file.txt
p = strings.ToUpper(u.Host) + ":" + p
}
if u.Host != "" && u.Host != "localhost" {
if filepath.VolumeName(u.Host) != "" {
return "", false
}
return `\\` + u.Host + p, true
}
if vol := filepath.VolumeName(p[1:]); vol == "" || strings.HasPrefix(vol, `\\`) {
return "", false
}
return p[1:], true
return p, true
}
// URLEscape escapes unicode letters.
func URLEscape(uri string) string {
// escape unicode letters
u, err := url.Parse(uri)
if err != nil {
panic(err)
}
return u.String()
}
-7
View File
@@ -19,13 +19,6 @@ type Closer interface {
Close() error
}
// CloserFunc is a convenience type to create a Closer from a function.
type CloserFunc func() error
func (f CloserFunc) Close() error {
return f()
}
type CloseAdder interface {
Add(Closer)
}
-3
View File
@@ -65,9 +65,6 @@ func (q *EvictingStringQueue) Len() int {
// Contains returns whether the queue contains v.
func (q *EvictingStringQueue) Contains(v string) bool {
if q == nil {
return false
}
q.mu.Lock()
defer q.mu.Unlock()
return q.set[v]
+2 -18
View File
@@ -13,24 +13,8 @@
package hstring
import (
"html/template"
type RenderedString string
"github.com/gohugoio/hugo/common/types"
)
var _ types.PrintableValueProvider = HTML("")
// HTML is a string that represents rendered HTML.
// When printed in templates it will be rendered as template.HTML and considered safe so no need to pipe it into `safeHTML`.
// This type was introduced as a wasy to prevent a common case of inifinite recursion in the template rendering
// when the `linkify` option is enabled with a common (wrong) construct like `{{ .Text | .Page.RenderString }}` in a hook template.
type HTML string
func (s HTML) String() string {
func (s RenderedString) String() string {
return string(s)
}
func (s HTML) PrintableValue() any {
return template.HTML(s)
}
+2 -2
View File
@@ -25,6 +25,6 @@ func TestRenderedString(t *testing.T) {
c := qt.New(t)
// Validate that it will behave like a string in Hugo settings.
c.Assert(cast.ToString(HTML("Hugo")), qt.Equals, "Hugo")
c.Assert(template.HTML(HTML("Hugo")), qt.Equals, template.HTML("Hugo"))
c.Assert(cast.ToString(RenderedString("Hugo")), qt.Equals, "Hugo")
c.Assert(template.HTML(RenderedString("Hugo")), qt.Equals, template.HTML("Hugo"))
}
+2 -34
View File
@@ -107,20 +107,12 @@ func Unwrapv(v any) any {
return v
}
// LowHigh represents a byte or slice boundary.
type LowHigh[S ~[]byte | string] struct {
// LowHigh is typically used to represent a slice boundary.
type LowHigh struct {
Low int
High int
}
func (l LowHigh[S]) IsZero() bool {
return l.Low < 0 || (l.Low == 0 && l.High == 0)
}
func (l LowHigh[S]) Value(source S) S {
return source[l.Low:l.High]
}
// This is only used for debugging purposes.
var InvocationCounter atomic.Int64
@@ -128,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
}
-22
View File
@@ -27,25 +27,3 @@ func TestKeyValues(t *testing.T) {
c.Assert(kv.KeyString(), qt.Equals, "key")
c.Assert(kv.Values, qt.DeepEquals, []any{"a1", "a2"})
}
func TestLowHigh(t *testing.T) {
c := qt.New(t)
lh := LowHigh[string]{
Low: 2,
High: 10,
}
s := "abcdefghijklmnopqrstuvwxyz"
c.Assert(lh.IsZero(), qt.IsFalse)
c.Assert(lh.Value(s), qt.Equals, "cdefghij")
lhb := LowHigh[[]byte]{
Low: 2,
High: 10,
}
sb := []byte(s)
c.Assert(lhb.IsZero(), qt.IsFalse)
c.Assert(lhb.Value(sb), qt.DeepEquals, []byte("cdefghij"))
}
+20 -32
View File
@@ -179,12 +179,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 +312,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)
@@ -379,17 +369,6 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
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,
@@ -505,9 +484,6 @@ type RootConfig struct {
// Set this to true to put all languages below their language ID.
DefaultContentLanguageInSubdir bool
// Disable generation of redirect to the default language when DefaultContentLanguageInSubdir is enabled.
DisableDefaultLanguageRedirect bool
// Disable creation of alias redirect pages.
DisableAliases bool
@@ -581,11 +557,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.
@@ -734,7 +708,7 @@ func (c *Configs) Validate(logger loggers.Logger) error {
// transientErr returns the last transient error found during config compilation.
func (c *Configs) transientErr() error {
for _, l := range c.LanguageConfigMap {
for _, l := range c.LanguageConfigSlice {
if l.C.transientErr != nil {
return l.C.transientErr
}
@@ -751,6 +725,7 @@ func (c *Configs) Init() error {
var languages langs.Languages
defaultContentLanguage := c.Base.DefaultContentLanguage
for k, v := range c.LanguageConfigMap {
c.LanguageConfigSlice = append(c.LanguageConfigSlice, v)
languageConf := v.Languages[k]
language, err := langs.NewLanguage(k, defaultContentLanguage, v.TimeZone, languageConf)
if err != nil {
@@ -811,7 +786,7 @@ func (c *Configs) Init() error {
}
if len(c.Modules) == 0 {
return errors.New("no modules loaded (need at least the main module)")
return errors.New("no modules loaded (ned at least the main module)")
}
// Apply default project mounts.
@@ -890,17 +865,30 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
var differentRootKeys []string
switch x := v.(type) {
case maps.Params:
_, found := x["params"]
if !found {
x["params"] = maps.Params{
var params maps.Params
pv, found := x["params"]
if found {
params = pv.(maps.Params)
} else {
params = maps.Params{
maps.MergeStrategyKey: maps.ParamsMergeStrategyDeep,
}
x["params"] = params
}
for kk, vv := range x {
if kk == "_merge" {
continue
}
if kk != maps.MergeStrategyKey && !configLanguageKeys[kk] {
// This should have been placed below params.
// We accidentally allowed it in the past, so we need to support it a little longer,
// But log a warning.
if _, found := params[kk]; !found {
hugo.Deprecate(fmt.Sprintf("config: languages.%s.%s: custom params on the language top level", k, kk), fmt.Sprintf("Put the value below [languages.%s.params]. See https://gohugo.io/content-management/multilingual/#changes-in-hugo-01120", k), "v0.112.0")
params[kk] = vv
}
}
if kk == "baseurl" {
// baseURL configure don the language level is a multihost setup.
isMultihost = true
@@ -102,116 +102,3 @@ suffixes = ["html", "xhtml"]
b.Assert(contentTypes.HTML.Suffixes(), qt.DeepEquals, []string{"html", "xhtml"})
b.Assert(contentTypes.Markdown.Suffixes(), qt.DeepEquals, []string{"md", "mdown", "markdown"})
}
func TestPaginationConfig(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")
}
func TestMapUglyURLs(t *testing.T) {
files := `
-- hugo.toml --
[uglyurls]
posts = true
`
b := hugolib.Test(t, files)
c := b.H.Configs.Base
b.Assert(c.C.IsUglyURLSection("posts"), qt.IsTrue)
b.Assert(c.C.IsUglyURLSection("blog"), qt.IsFalse)
}
// Issue 13199
func TestInvalidOutputFormat(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
[outputs]
home = ['html','foo']
-- layouts/index.html --
x
`
b, err := hugolib.TestE(t, files)
b.Assert(err, qt.IsNotNil)
b.Assert(err.Error(), qt.Contains, `failed to create config: unknown output format "foo" for kind "home"`)
}
// Issue 13201
func TestLanguageConfigSlice(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
[languages.en]
title = 'TITLE_EN'
weight = 2
[languages.de]
title = 'TITLE_DE'
weight = 1
[languages.fr]
title = 'TITLE_FR'
weight = 3
`
b := hugolib.Test(t, files)
b.Assert(b.H.Configs.LanguageConfigSlice[0].Title, qt.Equals, `TITLE_DE`)
}
-37
View File
@@ -327,41 +327,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 {
@@ -419,8 +384,6 @@ var allDecoderSetups = map[string]decodeWeight{
p.c.UglyURLs = vv
case string:
p.c.UglyURLs = vv == "true"
case maps.Params:
p.c.UglyURLs = cast.ToStringMapBool(maps.CleanConfigStringMap(vv))
default:
p.c.UglyURLs = cast.ToStringMapBool(v)
}
+8 -4
View File
@@ -137,11 +137,11 @@ func (c ConfigLanguage) Watching() bool {
return c.m.Base.Internal.Watch
}
func (c ConfigLanguage) NewIdentityManager(name string, opts ...identity.ManagerOption) identity.Manager {
func (c ConfigLanguage) NewIdentityManager(name string) identity.Manager {
if !c.Watching() {
return identity.NopManager
}
return identity.NewManager(name, opts...)
return identity.NewManager(name)
}
func (c ConfigLanguage) ContentTypes() config.ContentTypesProvider {
@@ -248,8 +248,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 {
+15 -21
View File
@@ -64,7 +64,7 @@ func LoadConfig(d ConfigSourceDescriptor) (*Configs, error) {
return nil, fmt.Errorf("failed to create config from result: %w", err)
}
moduleConfig, modulesClient, err := l.loadModules(configs, d.IgnoreModuleDoesNotExist)
moduleConfig, modulesClient, err := l.loadModules(configs)
if err != nil {
return nil, fmt.Errorf("failed to load modules: %w", err)
}
@@ -116,9 +116,6 @@ type ConfigSourceDescriptor struct {
// Defaults to os.Environ if not set.
Environ []string
// If set, this will be used to ignore the module does not exist error.
IgnoreModuleDoesNotExist bool
}
func (d ConfigSourceDescriptor) configFilenames() []string {
@@ -197,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": "",
@@ -456,12 +453,11 @@ func (l *configLoader) loadConfigMain(d ConfigSourceDescriptor) (config.LoadConf
return res, l.ModulesConfig, err
}
func (l *configLoader) loadModules(configs *Configs, ignoreModuleDoesNotExist bool) (modules.ModulesConfig, *modules.Client, error) {
func (l *configLoader) loadModules(configs *Configs) (modules.ModulesConfig, *modules.Client, error) {
bcfg := configs.LoadingInfo.BaseConfig
conf := configs.Base
workingDir := bcfg.WorkingDir
themesDir := bcfg.ThemesDir
publishDir := bcfg.PublishDir
cfg := configs.LoadingInfo.Cfg
@@ -470,7 +466,7 @@ func (l *configLoader) loadModules(configs *Configs, ignoreModuleDoesNotExist bo
ignoreVendor, _ = hglob.GetGlob(hglob.NormalizePath(s))
}
ex := hexec.New(conf.Security, workingDir)
ex := hexec.New(conf.Security)
hook := func(m *modules.ModulesConfig) error {
for _, tc := range m.AllModules {
@@ -490,18 +486,16 @@ func (l *configLoader) loadModules(configs *Configs, ignoreModuleDoesNotExist bo
}
modulesClient := modules.NewClient(modules.ClientConfig{
Fs: l.Fs,
Logger: l.Logger,
Exec: ex,
HookBeforeFinalize: hook,
WorkingDir: workingDir,
ThemesDir: themesDir,
PublishDir: publishDir,
Environment: l.Environment,
CacheDir: conf.Caches.CacheDirModules(),
ModuleConfig: conf.Module,
IgnoreVendor: ignoreVendor,
IgnoreModuleDoesNotExist: ignoreModuleDoesNotExist,
Fs: l.Fs,
Logger: l.Logger,
Exec: ex,
HookBeforeFinalize: hook,
WorkingDir: workingDir,
ThemesDir: themesDir,
Environment: l.Environment,
CacheDir: conf.Caches.CacheDirModules(),
ModuleConfig: conf.Module,
IgnoreVendor: ignoreVendor,
})
moduleConfig, err := modulesClient.Collect()
-27
View File
@@ -410,30 +410,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
}
+3 -2
View File
@@ -52,13 +52,14 @@ type AllProvider interface {
DefaultContentLanguageInSubdir() bool
IsLangDisabled(string) bool
SummaryLength() int
Pagination() Pagination
Paginate() int
PaginatePath() string
BuildExpired() bool
BuildFuture() bool
BuildDrafts() bool
Running() bool
Watching() bool
NewIdentityManager(name string, opts ...identity.ManagerOption) identity.Manager
NewIdentityManager(name string) identity.Manager
FastRenderMode() bool
PrintUnusedTemplates() bool
EnableMissingTranslationPlaceholders() bool
+2 -2
View File
@@ -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)
+1 -1
View File
@@ -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))
}
-2
View File
@@ -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.
+1 -1
View File
@@ -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 = ['.*']",
)
}
@@ -0,0 +1,5 @@
+++
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
date = {{ .Date }}
draft = true
+++
-21
View File
@@ -76,11 +76,6 @@ func CreateSite(createpath string, sourceFs afero.Fs, force bool, format string)
return err
}
err = newSiteCreateArchetype(sourceFs, createpath, format)
if err != nil {
return err
}
return copyFiles(createpath, sourceFs, siteFs)
}
@@ -114,19 +109,3 @@ func newSiteCreateConfig(fs afero.Fs, createpath string, format string) (err err
return helpers.WriteToDisk(filepath.Join(createpath, "hugo."+format), &buf, fs)
}
func newSiteCreateArchetype(fs afero.Fs, createpath string, format string) (err error) {
in := map[string]any{
"title": "{{ replace .File.ContentBaseName \"-\" \" \" | title }}",
"date": "{{ .Date }}",
"draft": true,
}
var buf bytes.Buffer
err = parser.InterfaceToFrontMatter(in, metadecoders.FormatFromString(format), &buf)
if err != nil {
return err
}
return helpers.WriteToDisk(filepath.Join(createpath, "archetypes", "default.md"), &buf, fs)
}
+1 -1
View File
@@ -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="{{ site.Language.LanguageCode }}" dir="{{ or site.Language.LanguageDirection `ltr` }}">
<html lang="{{ or site.Language.LanguageCode }}" dir="{{ or site.Language.LanguageDirection `ltr` }}">
<head>
{{ partial "head.html" . }}
</head>
+2 -2
View File
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build withdeploy
// +build withdeploy
//go:build !nodeploy
// +build !nodeploy
package deploy
+4 -22
View File
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build withdeploy
// +build withdeploy
//go:build !nodeploy
// +build !nodeploy
package deploy
@@ -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{}
+2 -2
View File
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !solaris && withdeploy
// +build !solaris,withdeploy
//go:build !solaris && !nodeploy
// +build !solaris,!nodeploy
package deploy
+5 -68
View File
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build withdeploy
// +build withdeploy
//go:build !nodeploy
// +build !nodeploy
package deploy
@@ -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!"
-5
View File
@@ -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 {
+2 -2
View File
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build withdeploy
// +build withdeploy
//go:build !nodeploy
// +build !nodeploy
package deployconfig
+2 -2
View File
@@ -11,8 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build withdeploy
// +build withdeploy
//go:build !nodeploy
// +build !nodeploy
package deploy
+19 -122
View File
@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
@@ -16,7 +15,6 @@ 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"
@@ -24,8 +22,6 @@ import (
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/internal/js"
"github.com/gohugoio/hugo/internal/warpc"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/postpub"
@@ -49,9 +45,6 @@ type Deps struct {
// The templates to use. This will usually implement the full tpl.TemplateManager.
tmplHandlers *tpl.TemplateHandlers
// The template funcs.
TmplFuncMap map[string]any
// The file systems to use.
Fs *hugofs.Fs `json:"-"`
@@ -88,13 +81,10 @@ type Deps struct {
Metrics metrics.Provider
// BuildStartListeners will be notified before a build starts.
BuildStartListeners *Listeners[any]
BuildStartListeners *Listeners
// BuildEndListeners will be notified after a build finishes.
BuildEndListeners *Listeners[any]
// OnChangeListeners will be notified when something changes.
OnChangeListeners *Listeners[identity.Identity]
BuildEndListeners *Listeners
// Resources that gets closed when the build is done or the server shuts down.
BuildClosers *types.Closers
@@ -102,18 +92,6 @@ type Deps struct {
// 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
// The JS batcher client.
JSBatcherClient js.BatcherClient
// The JS batcher client.
// JSBatcherClient *esbuild.BatcherClient
isClosed bool
*globalErrHandler
}
@@ -157,38 +135,25 @@ 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[any]{}
d.BuildStartListeners = &Listeners{}
}
if d.BuildEndListeners == nil {
d.BuildEndListeners = &Listeners[any]{}
d.BuildEndListeners = &Listeners{}
}
if d.BuildClosers == nil {
d.BuildClosers = &types.Closers{}
}
if d.OnChangeListeners == nil {
d.OnChangeListeners = &Listeners[identity.Identity]{}
}
if d.Metrics == nil && d.Conf.TemplateMetrics() {
d.Metrics = metrics.NewProvider(d.Conf.TemplateMetricsHints())
}
if d.ExecHelper == nil {
d.ExecHelper = hexec.New(d.Conf.GetConfigSection("security").(security.Config), d.Conf.WorkingDir())
d.ExecHelper = hexec.New(d.Conf.GetConfigSection("security").(security.Config))
}
if d.MemCache == nil {
@@ -196,29 +161,20 @@ func (d *Deps) Init() error {
}
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
@@ -286,23 +242,6 @@ func (d *Deps) Compile(prototype *Deps) error {
return nil
}
// MkdirTemp returns a temporary directory path that will be cleaned up on exit.
func (d Deps) MkdirTemp(pattern string) (string, error) {
filename, err := os.MkdirTemp("", pattern)
if err != nil {
return "", err
}
d.BuildClosers.Add(
types.CloserFunc(
func() error {
return os.RemoveAll(filename)
},
),
)
return filename, nil
}
type globalErrHandler struct {
logger loggers.Logger
@@ -341,16 +280,15 @@ func (e *globalErrHandler) StopErrorCollector() {
}
// Listeners represents an event listener.
type Listeners[T any] struct {
type Listeners struct {
sync.Mutex
// A list of funcs to be notified about an event.
// If the return value is true, the listener will be removed.
listeners []func(...T) bool
listeners []func()
}
// Add adds a function to a Listeners instance.
func (b *Listeners[T]) Add(f func(...T) bool) {
func (b *Listeners) Add(f func()) {
if b == nil {
return
}
@@ -360,16 +298,12 @@ func (b *Listeners[T]) Add(f func(...T) bool) {
}
// Notify executes all listener functions.
func (b *Listeners[T]) Notify(vs ...T) {
func (b *Listeners) Notify() {
b.Lock()
defer b.Unlock()
temp := b.listeners[:0]
for _, notify := range b.listeners {
if !notify(vs...) {
temp = append(temp, notify)
}
notify()
}
b.listeners = temp
}
// ResourceProvider is used to create and refresh, and clone resources needed.
@@ -387,17 +321,9 @@ func (d *Deps) TextTmpl() tpl.TemplateParseFinder {
}
func (d *Deps) Close() error {
if d.isClosed {
return nil
}
d.isClosed = true
if d.MemCache != nil {
d.MemCache.Stop()
}
if d.WasmDispatchers != nil {
d.WasmDispatchers.Close()
}
return d.BuildClosers.Close()
}
@@ -412,11 +338,9 @@ type DepsCfg struct {
// The logging level to use.
LogLevel logg.Level
// Logging output.
StdErr io.Writer
// The console output.
StdOut io.Writer
// Where to write the logs.
// Currently we typically write everything to stdout.
LogOut io.Writer
// The file systems to use
Fs *hugofs.Fs
@@ -447,37 +371,10 @@ type BuildState struct {
// 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...)
}
+25 -63
View File
@@ -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,103 +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",
"unpublishdate",
"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",
"WASI",
"# ----------------------------------------------------------------------",
"# 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",
@@ -157,21 +119,21 @@
"# ----------------------------------------------------------------------",
"# cspell: ignore miscellaneous",
"# ----------------------------------------------------------------------",
"achristie",
"ddmaurier",
"dring",
"fleqn",
"getenv",
"gohugo",
"inor",
"jausten",
"jdoe",
"jsmith",
"leqno",
"milli",
"rgba",
"rsmith",
"stringifier",
"struct",
"tdewolff",
"tjones",
"wcag",
"xfeff"
"toclevels",
"vals",
"xfeff",
"zgotmplz"
]
}
+3 -1
View File
@@ -7,10 +7,12 @@ A fast and flexible static site generator built with love by [bep], [spf13], and
[![Netlify Status](https://api.netlify.com/api/v1/badges/e0dbbfc7-34f1-4393-a679-c16e80162705/deploy-status)](https://app.netlify.com/sites/gohugoio/deploys)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://gohugo.io/contribute/documentation/)
This is the repository for the [Hugo](https://github.com/gohugoio/hugo) documentation site.
This is the repository for the [Hugo](https://github.com/gohugoio/hugo) documentation site.
Please see the [contributing] section for guidelines, examples, and process.
[bep]: https://github.com/bep
[spf13]: https://github.com/spf13
[friends]: https://github.com/gohugoio/hugo/graphs/contributors
@@ -4,7 +4,13 @@ code, .code, pre code, .highlight pre {
}
.sans-serif {
font-family: 'Muli', Avenir, 'Helvetica Neue', Helvetica, Roboto, Noto, 'Segoe UI', Arial, sans-serif;
font-family: 'Muli',
avenir,
'helvetica neue', helvetica,
ubuntu,
roboto, noto,
'segoe ui', arial,
sans-serif;
}
@@ -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

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.1 KiB

@@ -5002,7 +5002,13 @@ code, .code, pre code, .highlight pre {
font-family: 'inconsolata',Menlo,Monaco,'Courier New',monospace;
}
.sans-serif {
font-family: 'Muli', Avenir, 'Helvetica Neue', Helvetica, Roboto, Noto, 'Segoe UI', Arial, sans-serif;
font-family: 'Muli',
avenir,
'helvetica neue', helvetica,
ubuntu,
roboto, noto,
'segoe ui', arial,
sans-serif;
}
.serif {
font-family: Palatino,"Palatino Linotype","Palatino LT STD","Book Antiqua",Georgia,serif;
@@ -6,18 +6,17 @@
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 = "GoLand"
title = "The complete IDE crafted for professional Go developers."
no_query_params = true
link = "https://www.jetbrains.com/go/?utm_source=OSS&utm_medium=referral&utm_campaign=hugo"
logo = "images/sponsors/goland.svg"
bgcolor = "#f4f4f4"
name = "Your Company?"
link = "https://bep.is/en/hugo-sponsor-2023-01/"
utm_campaign = "hugosponsor"
show_on_hover = true
bgcolor = "#4e4f4f"
@@ -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>

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